Phecda

feat: authentication flow

@@ -10,7 +10,11 @@ import { @@ -10,7 +10,11 @@ import {
10 HeaderStyleInterpolators, 10 HeaderStyleInterpolators,
11 } from '@react-navigation/stack'; 11 } from '@react-navigation/stack';
12 import Ionicons from 'react-native-vector-icons/Ionicons'; 12 import Ionicons from 'react-native-vector-icons/Ionicons';
13 -import { MainTabParamList, MainStackParamList } from '../type/Navigation'; 13 +import {
  14 + MainTabParamList,
  15 + MainStackParamList,
  16 + AuthStackParamList,
  17 +} from '../type/Navigation';
14 import SystemInfo from './SystemInfo'; 18 import SystemInfo from './SystemInfo';
15 import DesignList from './DesignList'; 19 import DesignList from './DesignList';
16 import { useDarkMode } from 'react-native-dark-mode'; 20 import { useDarkMode } from 'react-native-dark-mode';
@@ -27,6 +31,9 @@ import { useDeepLinking } from '../utility/handleDeepLinking'; @@ -27,6 +31,9 @@ import { useDeepLinking } from '../utility/handleDeepLinking';
27 import { navigationRef, useMountedRef } from '../utility/rootNavigation'; 31 import { navigationRef, useMountedRef } from '../utility/rootNavigation';
28 import ShortcutPage from './ShortcutItem'; 32 import ShortcutPage from './ShortcutItem';
29 import useQuickAction from '../utility/useQuickAction'; 33 import useQuickAction from '../utility/useQuickAction';
  34 +import { useReduxState } from '../store/hooks';
  35 +import LoginScreen from './Login';
  36 +import MeScreen from './Me';
30 37
31 const MainTab = createBottomTabNavigator<MainTabParamList>(); 38 const MainTab = createBottomTabNavigator<MainTabParamList>();
32 39
@@ -36,7 +43,7 @@ function getTabHeader( @@ -36,7 +43,7 @@ function getTabHeader(
36 } 43 }
37 ) { 44 ) {
38 const { state } = route; 45 const { state } = route;
39 - if (!state) return 'SystemInfo'; 46 + if (!state) return 'Library';
40 const { routeNames, index } = state; 47 const { routeNames, index } = state;
41 const routeName = routeNames[index] as keyof MainTabParamList; 48 const routeName = routeNames[index] as keyof MainTabParamList;
42 return routeName; 49 return routeName;
@@ -78,6 +85,10 @@ const Home = () => { @@ -78,6 +85,10 @@ const Home = () => {
78 name={focused ? 'ios-list-box' : 'ios-list'} 85 name={focused ? 'ios-list-box' : 'ios-list'}
79 /> 86 />
80 ); 87 );
  88 + case 'Me':
  89 + return (
  90 + <Ionicons size={size} color={color} name={'ios-person'} />
  91 + );
81 default: 92 default:
82 break; 93 break;
83 } 94 }
@@ -88,15 +99,19 @@ const Home = () => { @@ -88,15 +99,19 @@ const Home = () => {
88 <MainTab.Screen name="Library" component={Library} /> 99 <MainTab.Screen name="Library" component={Library} />
89 <MainTab.Screen name="SystemInfo" component={SystemInfo} /> 100 <MainTab.Screen name="SystemInfo" component={SystemInfo} />
90 <MainTab.Screen name="DesignList" component={DesignList} /> 101 <MainTab.Screen name="DesignList" component={DesignList} />
  102 + <MainTab.Screen name="Me" component={MeScreen} />
91 </MainTab.Navigator> 103 </MainTab.Navigator>
92 ); 104 );
93 }; 105 };
94 106
95 const MainStack = createStackNavigator<MainStackParamList>(); 107 const MainStack = createStackNavigator<MainStackParamList>();
96 108
  109 +const AuthStack = createStackNavigator<AuthStackParamList>();
  110 +
97 const Container = () => { 111 const Container = () => {
98 const inDarkMode = useDarkMode(); 112 const inDarkMode = useDarkMode();
99 const strings = useI18nStrings(); 113 const strings = useI18nStrings();
  114 + const user = useReduxState('user');
100 115
101 useMountedRef(); 116 useMountedRef();
102 useDeepLinking(); 117 useDeepLinking();
@@ -107,6 +122,7 @@ const Container = () => { @@ -107,6 +122,7 @@ const Container = () => {
107 ref={navigationRef} 122 ref={navigationRef}
108 theme={inDarkMode ? themeForNav.dark : themeForNav.light} 123 theme={inDarkMode ? themeForNav.dark : themeForNav.light}
109 > 124 >
  125 + {user.token ? (
110 <MainStack.Navigator 126 <MainStack.Navigator
111 screenOptions={{ 127 screenOptions={{
112 headerStyleInterpolator: HeaderStyleInterpolators.forUIKit, 128 headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
@@ -137,6 +153,11 @@ const Container = () => { @@ -137,6 +153,11 @@ const Container = () => {
137 <MainStack.Screen name="RNCode" component={ReadableCode} /> 153 <MainStack.Screen name="RNCode" component={ReadableCode} />
138 <MainStack.Screen name="ShortcutItem" component={ShortcutPage} /> 154 <MainStack.Screen name="ShortcutItem" component={ShortcutPage} />
139 </MainStack.Navigator> 155 </MainStack.Navigator>
  156 + ) : (
  157 + <AuthStack.Navigator>
  158 + <AuthStack.Screen name="Login" component={LoginScreen} />
  159 + </AuthStack.Navigator>
  160 + )}
140 </NavigationContainer> 161 </NavigationContainer>
141 ); 162 );
142 }; 163 };
  1 +import React, { useCallback, useState } from 'react';
  2 +import { BGScroll, Card, ListItem, Divider } from '../component/View';
  3 +import { useReduxDispatch, useReduxState } from '../store/hooks';
  4 +import { rootActions } from '../store';
  5 +import { Alert } from 'react-native';
  6 +
  7 +const LoginScreen = () => {
  8 + const { list, current } = useReduxState('user');
  9 + const dispatch = useReduxDispatch();
  10 +
  11 + const [userName, setUserName] = useState(current?.name);
  12 +
  13 + const onLogin = useCallback(() => {
  14 + const newToken = userName ? 'yes!' + userName : 'no:(';
  15 + if (userName) {
  16 + const filtered = list.filter(u => u.name === userName)[0];
  17 + if (filtered) {
  18 + dispatch(rootActions.userActions.setCurrentUser(filtered));
  19 + } else {
  20 + const newUser = { name: userName };
  21 + dispatch(rootActions.userActions.addUser(newUser));
  22 + dispatch(rootActions.userActions.setCurrentUser(newUser));
  23 + }
  24 + }
  25 + dispatch(rootActions.userActions.setToken(newToken));
  26 + }, [userName, dispatch, list]);
  27 +
  28 + return (
  29 + <BGScroll>
  30 + <Card round>
  31 + <ListItem
  32 + title="User Name"
  33 + rightTitle={userName}
  34 + onPress={() => {
  35 + Alert.prompt('Your name?', undefined, setUserName);
  36 + }}
  37 + />
  38 + <Divider />
  39 + <ListItem title="login" onPress={onLogin} />
  40 + </Card>
  41 + </BGScroll>
  42 + );
  43 +};
  44 +
  45 +export default LoginScreen;
  1 +import React, { useCallback } from 'react';
  2 +import { BGScroll, Card, ListItem } from '../../component/View';
  3 +import { useReduxState, useReduxDispatch } from '../../store/hooks';
  4 +import { rootActions } from '../../store';
  5 +
  6 +const MeScreen = () => {
  7 + const { current } = useReduxState('user');
  8 + const dispatch = useReduxDispatch();
  9 +
  10 + const onLogout = useCallback(() => {
  11 + dispatch(rootActions.userActions.setToken(null));
  12 + }, [dispatch]);
  13 +
  14 + return (
  15 + <BGScroll white>
  16 + <Card shadow>
  17 + <ListItem title={current?.name ?? '请登录'} />
  18 + </Card>
  19 + <Card shadow>
  20 + <ListItem title={'Log out'} onPress={onLogout} />
  21 + </Card>
  22 + </BGScroll>
  23 + );
  24 +};
  25 +
  26 +export default MeScreen;
1 import { createAction } from 'typesafe-actions'; 1 import { createAction } from 'typesafe-actions';
2 import { UserActionTypes, User } from './types'; 2 import { UserActionTypes, User } from './types';
3 3
4 -export const setToken = createAction(UserActionTypes.SET_TOKEN)<string>(); 4 +export const setToken = createAction(UserActionTypes.SET_TOKEN)<
  5 + string | null
  6 +>();
5 7
6 export const setCurrentUser = createAction(UserActionTypes.SET_CURRENT)<User>(); 8 export const setCurrentUser = createAction(UserActionTypes.SET_CURRENT)<User>();
7 9
@@ -6,6 +6,7 @@ export type MainTabParamList = { @@ -6,6 +6,7 @@ export type MainTabParamList = {
6 Library: undefined; 6 Library: undefined;
7 SystemInfo: undefined; 7 SystemInfo: undefined;
8 DesignList: undefined; 8 DesignList: undefined;
  9 + Me: undefined;
9 }; 10 };
10 11
11 export type MainStackParamList = { 12 export type MainStackParamList = {
@@ -18,6 +19,10 @@ export type MainStackParamList = { @@ -18,6 +19,10 @@ export type MainStackParamList = {
18 ShortcutItem: { id?: string }; 19 ShortcutItem: { id?: string };
19 }; 20 };
20 21
  22 +export type AuthStackParamList = {
  23 + Login: undefined;
  24 +};
  25 +
21 export type MainTabScreenProps<RouteName extends keyof MainTabParamList> = { 26 export type MainTabScreenProps<RouteName extends keyof MainTabParamList> = {
22 navigation: CompositeNavigationProp< 27 navigation: CompositeNavigationProp<
23 BottomTabNavigationProp<MainTabParamList, RouteName>, 28 BottomTabNavigationProp<MainTabParamList, RouteName>,
@@ -30,3 +35,8 @@ export type MainStackScreenProps<RouteName extends keyof MainStackParamList> = { @@ -30,3 +35,8 @@ export type MainStackScreenProps<RouteName extends keyof MainStackParamList> = {
30 navigation: StackNavigationProp<MainStackParamList, RouteName>; 35 navigation: StackNavigationProp<MainStackParamList, RouteName>;
31 route: RouteProp<MainStackParamList, RouteName>; 36 route: RouteProp<MainStackParamList, RouteName>;
32 }; 37 };
  38 +
  39 +export type AuthStackScreenProps<RouteName extends keyof AuthStackParamList> = {
  40 + navigation: StackNavigationProp<AuthStackParamList, RouteName>;
  41 + route: RouteProp<AuthStackParamList, RouteName>;
  42 +};