Skip to content

Commit

Permalink
feat: Add user login functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
thoulee21 committed May 25, 2024
1 parent a03d260 commit bbfefef
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"Prefs",
"reduxjs",
"temurin",
"tlyric"
"tlyric",
"userprofiles"
]
}
9 changes: 8 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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() {
Expand All @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/components/RootStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AlbumDetail,
Artist,
Comments,
Login,
LyricsScreen,
MvDetail,
MvPlayer,
Expand Down Expand Up @@ -51,6 +52,7 @@ export function RootStack() {
<Stack.Screen name="MvDetail" component={MvDetail} />
<Stack.Screen name="Artist" component={Artist} />
<Stack.Screen name="AlbumDetail" component={AlbumDetail} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
);
}
40 changes: 23 additions & 17 deletions src/components/UserHeader.tsx
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -29,20 +31,26 @@ export const UserHeader = memo(({ userId }: { userId?: number }) => {
imageStyle={styles.img}
source={{ uri: data?.profile.backgroundUrl }}
>
<Avatar.Image
<TouchableRipple
style={styles.avatar}
size={70}
source={{ uri: data?.profile.avatarUrl }}
/>
borderless
onPress={() => {
//@ts-expect-error
navigation.push('Login');
}}
>
<Avatar.Image
size={70}
source={{ uri: data?.profile.avatarUrl }}
/>
</TouchableRipple>

<View style={styles.caption}>
<Text variant="labelLarge">
{data?.profile.nickname}
</Text>
<Text variant="labelMedium">
{data?.profile.signature}
</Text>
</View>
<Text variant="labelLarge">
{data?.profile.nickname}
</Text>
<Text variant="labelMedium">
{data?.profile.signature}
</Text>
</ImageBackground>
);
});
Expand All @@ -56,8 +64,6 @@ const styles = StyleSheet.create({
},
avatar: {
marginTop: '30%',
},
caption: {
marginTop: '1%'
borderRadius: 50,
}
});
45 changes: 45 additions & 0 deletions src/components/UserItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<List.Item
title={item.nickname}
description={item.signature}
onPress={login}
left={(props) => (
<Avatar.Image {...props}
size={40}
source={{ uri: item.avatarUrl }} />
)} />
);
};
75 changes: 75 additions & 0 deletions src/components/UserList.tsx
Original file line number Diff line number Diff line change
@@ -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<Main>(
(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 (
<List.Item
title={error.message}
titleStyle={{ color: appTheme.colors.error }}
description="Try to search later or with another query" />
);
}

return (
<FlatList
data={users}
renderItem={({ item }) => <UserItem item={item} />}
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
? <ActivityIndicator
style={styles.footerLoading} /> : null}
ListEmptyComponent={<List.Item
title="No users found"
description={data?.[0].message || 'Try another search query'} />} />
);
};

const styles = StyleSheet.create({
footerLoading: {
marginVertical: '5%'
}
});
4 changes: 3 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

2 changes: 1 addition & 1 deletion src/pages/favs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function Favs() {
);
}

export const styles = StyleSheet.create({
const styles = StyleSheet.create({
appbar: {
backgroundColor: 'transparent'
},
Expand Down
1 change: 1 addition & 0 deletions src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
53 changes: 53 additions & 0 deletions src/pages/login.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<BlurBackground>
<Searchbar
style={styles.searchbar}
placeholder="Search for a user"
onChangeText={setShowQuery}
value={showQuery}
onIconPress={search}
onSubmitEditing={search}
selectTextOnFocus
/>

<Suspense fallback={
<ActivityIndicator
size="large"
style={styles.loading}
/>
}>
<UserList searchQuery={searchQuery} />
</Suspense>
</BlurBackground>
);
};

const styles = StyleSheet.create({
searchbar: {
marginTop: StatusBar.currentHeight,
marginHorizontal: '2%',
backgroundColor: 'transparent'
},
loading: {
marginTop: '20%'
},
});
53 changes: 53 additions & 0 deletions src/types/searchUsers.d.ts
Original file line number Diff line number Diff line change
@@ -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",
}

0 comments on commit bbfefef

Please sign in to comment.