diff --git a/.vscode/settings.json b/.vscode/settings.json
index 997989e7..418d8a40 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,6 +13,7 @@
"Prefs",
"reduxjs",
"temurin",
- "tlyric"
+ "tlyric",
+ "userprofiles"
]
}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 50859e50..c07fb907 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,7 +3,7 @@ import * as SplashScreen from 'expo-splash-screen';
import React, { useEffect } from 'react';
import { AppContainer, RootStack } from './components';
import { useAppDispatch } from './hook';
-import { setBlurRadius, setFavs } from './redux/slices';
+import { setBlurRadius, setFavs, setUser } from './redux/slices';
SplashScreen.preventAutoHideAsync();
@@ -14,6 +14,8 @@ export enum StorageKeys {
BlurRadius = 'blurRadius',
// eslint-disable-next-line no-unused-vars
Favs = 'favs',
+ // eslint-disable-next-line no-unused-vars
+ User = 'user',
}
export default function App() {
@@ -30,6 +32,11 @@ export default function App() {
if (storedFavs) {
dispatch(setFavs(JSON.parse(storedFavs)));
}
+
+ const storedUser = await AsyncStorage.getItem(StorageKeys.User);
+ if (storedUser) {
+ dispatch(setUser(JSON.parse(storedUser)));
+ }
}
restorePrefs();
diff --git a/src/components/RootStack.tsx b/src/components/RootStack.tsx
index f1a7cdec..71e7a42d 100644
--- a/src/components/RootStack.tsx
+++ b/src/components/RootStack.tsx
@@ -10,6 +10,7 @@ import {
AlbumDetail,
Artist,
Comments,
+ Login,
LyricsScreen,
MvDetail,
MvPlayer,
@@ -51,6 +52,7 @@ export function RootStack() {
+
);
}
diff --git a/src/components/UserHeader.tsx b/src/components/UserHeader.tsx
index 7de9f2c8..5dc7699e 100644
--- a/src/components/UserHeader.tsx
+++ b/src/components/UserHeader.tsx
@@ -1,12 +1,14 @@
+import { useNavigation } from '@react-navigation/native';
import React, { memo } from 'react';
-import { Dimensions, ImageBackground, StyleSheet, View } from 'react-native';
-import { Avatar, Text, useTheme } from 'react-native-paper';
+import { Dimensions, ImageBackground, StyleSheet } from 'react-native';
+import { Avatar, Text, TouchableRipple, useTheme } from 'react-native-paper';
import useSWR from 'swr';
import { useAppSelector } from '../hook';
import { selectUser } from '../redux/slices';
import { Main } from '../types/userDetail';
export const UserHeader = memo(({ userId }: { userId?: number }) => {
+ const navigation = useNavigation();
const appTheme = useTheme();
const currentUser = useAppSelector(selectUser);
@@ -29,20 +31,26 @@ export const UserHeader = memo(({ userId }: { userId?: number }) => {
imageStyle={styles.img}
source={{ uri: data?.profile.backgroundUrl }}
>
-
+ borderless
+ onPress={() => {
+ //@ts-expect-error
+ navigation.push('Login');
+ }}
+ >
+
+
-
-
- {data?.profile.nickname}
-
-
- {data?.profile.signature}
-
-
+
+ {data?.profile.nickname}
+
+
+ {data?.profile.signature}
+
);
});
@@ -56,8 +64,6 @@ const styles = StyleSheet.create({
},
avatar: {
marginTop: '30%',
- },
- caption: {
- marginTop: '1%'
+ borderRadius: 50,
}
});
diff --git a/src/components/UserItem.tsx b/src/components/UserItem.tsx
new file mode 100644
index 00000000..e9722e13
--- /dev/null
+++ b/src/components/UserItem.tsx
@@ -0,0 +1,45 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import React from 'react';
+import { ToastAndroid } from 'react-native';
+import HapticFeedback, { HapticFeedbackTypes } from 'react-native-haptic-feedback';
+import { Avatar, List } from 'react-native-paper';
+import { StorageKeys } from '../App';
+import { useAppDispatch } from '../hook';
+import { setUser } from '../redux/slices';
+import { Userprofile } from '../types/searchUsers';
+
+export const UserItem = ({ item }: { item: Userprofile; }) => {
+ const dispatch = useAppDispatch();
+
+ const login = () => {
+ const user = {
+ username: item.nickname,
+ id: item.userId
+ };
+
+ dispatch(setUser(user));
+ AsyncStorage.setItem(
+ StorageKeys.User, JSON.stringify(user)
+ );
+
+ HapticFeedback.trigger(
+ HapticFeedbackTypes.effectHeavyClick
+ );
+ ToastAndroid.show(
+ `Logged in as ${item.nickname}`,
+ ToastAndroid.SHORT
+ );
+ };
+
+ return (
+ (
+
+ )} />
+ );
+};
diff --git a/src/components/UserList.tsx b/src/components/UserList.tsx
new file mode 100644
index 00000000..a22cc2a6
--- /dev/null
+++ b/src/components/UserList.tsx
@@ -0,0 +1,75 @@
+import React, { useMemo, useState } from 'react';
+import { FlatList, StyleSheet, ToastAndroid } from 'react-native';
+import { ActivityIndicator, List, useTheme } from 'react-native-paper';
+import useSWRInfinite from 'swr/infinite';
+import { UserItem } from '.';
+import { useDebounce } from '../hook';
+import { Main } from '../types/searchUsers';
+
+export const UserList = ({ searchQuery }: { searchQuery: string; }) => {
+ const appTheme = useTheme();
+
+ const [refreshing, setRefreshing] = useState(false);
+ const { data, setSize, error, mutate } = useSWRInfinite(
+ (index) => `https://music.163.com/api/search/get/web?s=${searchQuery}&type=1002&offset=${index * 15}&limit=15`,
+ { suspense: true }
+ );
+
+ const users = useMemo(() => {
+ if (data && data?.[0].code === 200) {
+ return data?.map((item) => item.result.userprofiles).flat();
+ } else {
+ ToastAndroid.show(
+ `Error: ${data?.[0].code} ${data?.[0].message}`,
+ ToastAndroid.SHORT
+ );
+ return [];
+ }
+ }, [data]);
+
+ const hasMore = useMemo(() => data && data[0].result && users.length !== data[0].result.userprofileCount,
+ [data, users]);
+
+ const loadMore = useDebounce(() => {
+ if (hasMore) {
+ setSize(prev => prev + 1);
+ }
+ }, 1000);
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+ }
+ keyExtractor={(item) => item.userId.toString()}
+ initialNumToRender={7}
+ onEndReachedThreshold={0.1}
+ onEndReached={loadMore}
+ onRefresh={async () => {
+ setRefreshing(true);
+ await mutate();
+ setRefreshing(false);
+ }}
+ refreshing={refreshing}
+ ListFooterComponent={hasMore && users.length
+ ? : null}
+ ListEmptyComponent={} />
+ );
+};
+
+const styles = StyleSheet.create({
+ footerLoading: {
+ marginVertical: '5%'
+ }
+});
diff --git a/src/components/index.ts b/src/components/index.ts
index 188797c8..64583a7b 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -18,7 +18,6 @@ export * from './DialogWithRadioBtns';
export * from './DrawerItemList';
export * from './DrawerNavi';
export * from './FavToggle';
-export * from './TracksHeader';
export * from './FavsList';
export * from './ForwardButton';
export * from './HeaderCard';
@@ -43,6 +42,9 @@ export * from './TrackInfoBar';
export * from './TrackItem';
export * from './TrackListSheet';
export * from './TrackMenu';
+export * from './TracksHeader';
export * from './UserHeader';
+export * from './UserItem';
+export * from './UserList';
export * from './VersionItem';
diff --git a/src/pages/favs.tsx b/src/pages/favs.tsx
index 7ea2f1c6..44a5754e 100644
--- a/src/pages/favs.tsx
+++ b/src/pages/favs.tsx
@@ -48,7 +48,7 @@ export function Favs() {
);
}
-export const styles = StyleSheet.create({
+const styles = StyleSheet.create({
appbar: {
backgroundColor: 'transparent'
},
diff --git a/src/pages/index.ts b/src/pages/index.ts
index c5d46eaa..0fc5b951 100644
--- a/src/pages/index.ts
+++ b/src/pages/index.ts
@@ -2,6 +2,7 @@ export * from './album';
export * from './artist';
export * from './comments';
export * from './favs';
+export * from './login';
export * from './lyrics';
export * from './mv';
export * from './mvDetail';
diff --git a/src/pages/login.tsx b/src/pages/login.tsx
new file mode 100644
index 00000000..5df3d4b6
--- /dev/null
+++ b/src/pages/login.tsx
@@ -0,0 +1,53 @@
+import React, { Suspense, useState } from 'react';
+import { StatusBar, StyleSheet } from 'react-native';
+import { ActivityIndicator, Searchbar } from 'react-native-paper';
+import { BlurBackground, UserList } from '../components';
+import { useAppSelector } from '../hook';
+import { selectUser } from '../redux/slices';
+
+export const Login = () => {
+ const user = useAppSelector(selectUser);
+
+ const [showQuery, setShowQuery] = useState(user.username);
+ const [searchQuery, setSearchQuery] = useState(user.username);
+
+ const search = () => {
+ if (showQuery && searchQuery !== showQuery) {
+ setSearchQuery(showQuery);
+ }
+ };
+
+ return (
+
+
+
+
+ }>
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ searchbar: {
+ marginTop: StatusBar.currentHeight,
+ marginHorizontal: '2%',
+ backgroundColor: 'transparent'
+ },
+ loading: {
+ marginTop: '20%'
+ },
+});
diff --git a/src/types/searchUsers.d.ts b/src/types/searchUsers.d.ts
new file mode 100644
index 00000000..241cd2d9
--- /dev/null
+++ b/src/types/searchUsers.d.ts
@@ -0,0 +1,53 @@
+export interface Main {
+ result: Result;
+ code: number;
+ message: string;
+}
+
+export interface Result {
+ userprofiles: Userprofile[];
+ userprofileCount: number;
+}
+
+export interface Userprofile {
+ defaultAvatar: boolean;
+ province: number;
+ authStatus: number;
+ followed: boolean;
+ avatarUrl: string;
+ accountStatus: number;
+ gender: number;
+ city: number;
+ birthday: number;
+ userId: number;
+ userType: number;
+ nickname: string;
+ signature: string;
+ description: string;
+ detailDescription: string;
+ avatarImgId: number;
+ backgroundImgId: number;
+ backgroundUrl: string;
+ authority: number;
+ mutual: boolean;
+ expertTags: null;
+ experts: null;
+ djStatus: number;
+ vipType: number;
+ remarkName: null;
+ authenticationTypes: number;
+ avatarDetail: null;
+ avatarImgIdStr: string;
+ backgroundImgIdStr: string;
+ anchor: boolean;
+ avatarImgId_str: string;
+ followeds: number;
+ follows: number;
+ alg: Alg;
+ playlistCount: number;
+ playlistBeSubscribedCount: number;
+}
+
+export enum Alg {
+ AlgUserBasic = "alg_user_basic",
+}