Skip to content

Commit 757edc7

Browse files
authored
refactor: wallet create/restore loading screen (#2282)
* refactor: wallet create/restore loading screen * fix: do not open the CreateWallet screen if the wallet does not exists
1 parent e4a5eb3 commit 757edc7

File tree

7 files changed

+236
-174
lines changed

7 files changed

+236
-174
lines changed

e2e/channels.e2e.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ d('Transfer', () => {
275275

276276
// TODO: mine single blocks and check updated transfer time
277277

278-
// Sometimes the channel is only opened after restart
278+
// Sometimes the channel is only opened after restart
279279
await device.launchApp();
280280

281281
// wait for channel to be opened

src/App.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { SlashtagsProvider } from './components/SlashtagsProvider';
2020
import { toastConfig } from './components/Toast';
2121
import { useAppSelector } from './hooks/redux';
2222
import AppUpdate from './screens/AppUpdate';
23-
import RestoringScreen from './screens/Onboarding/Restoring';
2423
import { themeSelector } from './store/reselect/settings';
2524
import { criticalUpdateSelector } from './store/reselect/ui';
2625
import { requiresRemoteRestoreSelector } from './store/reselect/user';
@@ -79,9 +78,9 @@ const App = (): ReactElement => {
7978
</Suspense>
8079
) : hasCriticalUpdate ? (
8180
<AppUpdate />
82-
) : walletExists ? (
81+
) : walletExists && !requiresRemoteRestore ? (
8382
<SlashtagsProvider>
84-
{requiresRemoteRestore ? <RestoringScreen /> : <AppOnboarded />}
83+
<AppOnboarded />
8584
</SlashtagsProvider>
8685
) : (
8786
<Suspense fallback={null}>

src/navigation/onboarding/OnboardingNavigator.tsx

+25-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import SlideshowScreen from '../../screens/Onboarding/Slideshow';
77
import RestoreFromSeed from '../../screens/Onboarding/RestoreFromSeed';
88
import MultipleDevices from '../../screens/Onboarding/MultipleDevices';
99
import Passphrase from '../../screens/Onboarding/Passphrase';
10+
import CreateWallet, {
11+
TCreateWalletParams,
12+
} from '../../screens/Onboarding/CreateWallet';
1013
import { NavigationContainer } from '../../styles/components';
14+
import { useAppSelector } from '../../hooks/redux';
15+
import { requiresRemoteRestoreSelector } from '../../store/reselect/user';
16+
import { walletExistsSelector } from '../../store/reselect/wallet';
1117

1218
export type OnboardingStackParamList = {
1319
TermsOfUse: undefined;
@@ -16,6 +22,7 @@ export type OnboardingStackParamList = {
1622
RestoreFromSeed: undefined;
1723
MultipleDevices: undefined;
1824
Passphrase: undefined;
25+
CreateWallet: TCreateWalletParams;
1926
};
2027

2128
const Stack = createNativeStackNavigator<OnboardingStackParamList>();
@@ -25,10 +32,22 @@ const navOptionHandler = {
2532
detachPreviousScreen: false,
2633
};
2734

35+
const navOptionHandlerNoBack = {
36+
...navOptionHandler,
37+
gestureEnabled: false,
38+
};
39+
2840
const OnboardingNavigator = (): ReactElement => {
41+
const requiresRemoteRestore = useAppSelector(requiresRemoteRestoreSelector);
42+
const walletExists = useAppSelector(walletExistsSelector);
43+
44+
// If a wallet exists but remote LDK revocery is not complete, show the CreateWallet screen
45+
const initialRouteName =
46+
walletExists && requiresRemoteRestore ? 'CreateWallet' : 'TermsOfUse';
47+
2948
return (
3049
<NavigationContainer>
31-
<Stack.Navigator initialRouteName="TermsOfUse">
50+
<Stack.Navigator initialRouteName={initialRouteName}>
3251
<Stack.Screen
3352
name="TermsOfUse"
3453
component={TermsOfUse}
@@ -59,6 +78,11 @@ const OnboardingNavigator = (): ReactElement => {
5978
component={Passphrase}
6079
options={navOptionHandler}
6180
/>
81+
<Stack.Screen
82+
name="CreateWallet"
83+
component={CreateWallet}
84+
options={navOptionHandlerNoBack}
85+
/>
6286
</Stack.Navigator>
6387
</NavigationContainer>
6488
);

src/screens/Onboarding/Restoring.tsx renamed to src/screens/Onboarding/CreateWallet.tsx

+123-51
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,100 @@
11
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
2-
import { StyleSheet, View } from 'react-native';
2+
import { Image, StyleSheet, View } from 'react-native';
33
import { Trans, useTranslation } from 'react-i18next';
44

5-
import { Display, BodyM } from '../../styles/text';
6-
import { View as ThemedView } from '../../styles/components';
7-
import { IColors } from '../../styles/colors';
8-
import { restoreRemoteBackups, startWalletServices } from '../../utils/startup';
9-
import { showToast } from '../../utils/notifications';
10-
import { sleep } from '../../utils/helpers';
11-
import { updateUser } from '../../store/slices/user';
5+
import Dialog from '../../components/Dialog';
126
import SafeAreaInset from '../../components/SafeAreaInset';
7+
import { SlashtagsProvider } from '../../components/SlashtagsProvider';
138
import Button from '../../components/buttons/Button';
14-
import Dialog from '../../components/Dialog';
15-
import LoadingWalletScreen from './Loading';
169
import { useAppDispatch, useAppSelector } from '../../hooks/redux';
1710
import { useProfile, useSlashtags } from '../../hooks/slashtags';
18-
import { setOnboardingProfileStep } from '../../store/slices/slashtags';
11+
import { OnboardingStackScreenProps } from '../../navigation/types';
1912
import { onboardingProfileStepSelector } from '../../store/reselect/slashtags';
20-
import { Image } from 'react-native';
13+
import { requiresRemoteRestoreSelector } from '../../store/reselect/user';
14+
import { walletExistsSelector } from '../../store/reselect/wallet';
15+
import { setOnboardingProfileStep } from '../../store/slices/slashtags';
16+
import { updateUser } from '../../store/slices/user';
17+
import { View as ThemedView } from '../../styles/components';
18+
import { BodyM, Display } from '../../styles/text';
19+
import { sleep } from '../../utils/helpers';
20+
import { showToast } from '../../utils/notifications';
21+
import {
22+
createNewWallet,
23+
restoreRemoteBackups,
24+
restoreSeed,
25+
startWalletServices,
26+
} from '../../utils/startup';
27+
import LoadingWalletScreen from './Loading';
2128

2229
const checkImageSrc = require('../../assets/illustrations/check.png');
2330
const crossImageSrc = require('../../assets/illustrations/cross.png');
2431

25-
let attemptedAutoRestore = false;
32+
// prettier-ignore
33+
export type TCreateWalletParams = {
34+
action: 'create';
35+
bip39Passphrase?: string;
36+
} | {
37+
action: 'restore';
38+
mnemonic: string;
39+
bip39Passphrase?: string;
40+
} | undefined;
2641

27-
const RestoringScreen = (): ReactElement => {
42+
const CreateWallet = ({
43+
navigation,
44+
route,
45+
}: OnboardingStackScreenProps<'CreateWallet'>): ReactElement => {
46+
const params = route.params;
2847
const { t } = useTranslation('onboarding');
29-
const { url } = useSlashtags();
30-
const { profile } = useProfile(url);
3148
const dispatch = useAppDispatch();
32-
const onboardingStep = useAppSelector(onboardingProfileStepSelector);
33-
const [showRestored, setShowRestored] = useState(false);
34-
const [showFailed, setShowFailed] = useState(false);
35-
const [proceedWBIsLoading, setProceedWBIsLoading] = useState(false);
49+
const requiresRemoteRestore = useAppSelector(requiresRemoteRestoreSelector);
50+
const walletExists = useAppSelector(walletExistsSelector);
51+
const [status, setStatus] = useState<'loading' | 'success' | 'failed'>(
52+
'loading',
53+
);
3654
const [tryAgainCount, setTryAgainCount] = useState(0);
55+
const [proceedWBIsLoading, setProceedWBIsLoading] = useState(false);
3756
const [showCautionDialog, setShowCautionDialog] = useState(false);
3857

39-
const onRemoteRestore = useCallback(async (): Promise<void> => {
40-
attemptedAutoRestore = true;
41-
setShowFailed(false);
42-
setShowRestored(false);
58+
const handleCreate = useCallback(
59+
async (bip39Passphrase?: string) => {
60+
await sleep(500); // wait for animation to start
61+
const res = await createNewWallet({ bip39Passphrase });
62+
if (res.isErr()) {
63+
showToast({
64+
type: 'warning',
65+
title: t('error_create'),
66+
description: res.error.message,
67+
});
68+
navigation.goBack();
69+
}
70+
},
71+
[t, navigation],
72+
);
73+
74+
const handleRestore = useCallback(
75+
async (mnemonic: string, bip39Passphrase?: string) => {
76+
await sleep(500); // wait for animation to start
77+
const res = await restoreSeed({
78+
mnemonic,
79+
bip39Passphrase,
80+
});
81+
if (res.isErr()) {
82+
showToast({
83+
type: 'warning',
84+
title: t('restore_error_title'),
85+
description: res.error.message,
86+
});
87+
navigation.goBack();
88+
}
89+
},
90+
[t, navigation],
91+
);
4392

93+
const handleRemoteRestore = useCallback(async (): Promise<void> => {
94+
setStatus('loading');
4495
const res = await restoreRemoteBackups();
4596
await sleep(1000);
46-
if (res.isErr()) {
47-
return setShowFailed(true);
48-
}
49-
50-
setShowRestored(true);
97+
setStatus(res.isErr() ? 'failed' : 'success');
5198
}, []);
5299

53100
const proceedWithoutBackup = useCallback(async () => {
@@ -69,41 +116,46 @@ const RestoringScreen = (): ReactElement => {
69116
}, [t, dispatch]);
70117

71118
useEffect(() => {
72-
if (attemptedAutoRestore) {
119+
if (walletExists || !params) {
73120
return;
74121
}
75122

76-
onRemoteRestore().then();
77-
}, [onRemoteRestore]);
123+
if (params.action === 'create') {
124+
handleCreate(params.bip39Passphrase);
125+
} else {
126+
handleRestore(params.mnemonic, params.bip39Passphrase);
127+
}
128+
}, [walletExists, params, handleCreate, handleRestore]);
78129

79130
useEffect(() => {
80-
// If the user has a name, we can assume they have completed the profile onboarding
81-
if (onboardingStep !== 'Done' && profile.name) {
82-
dispatch(setOnboardingProfileStep('Done'));
131+
if (!walletExists || !requiresRemoteRestore) {
132+
return;
83133
}
84-
}, [profile.name, onboardingStep, dispatch]);
85134

86-
let color: keyof IColors = 'brand';
87-
let content = <LoadingWalletScreen isRestoring={true} />;
135+
handleRemoteRestore();
136+
}, [walletExists, requiresRemoteRestore, handleRemoteRestore]);
137+
138+
let content = <LoadingWalletScreen isRestoring={requiresRemoteRestore} />;
88139

89-
if (showRestored || showFailed) {
90-
color = showRestored ? 'green' : 'red';
140+
if (status === 'success' || status === 'failed') {
141+
const success = status === 'success';
142+
const color = success ? 'green' : 'red';
91143
const title = t(
92-
showRestored ? 'restore_success_header' : 'restore_failed_header',
144+
success ? 'restore_success_header' : 'restore_failed_header',
93145
);
94146
const subtitle = t(
95-
showRestored ? 'restore_success_text' : 'restore_failed_text',
147+
success ? 'restore_success_text' : 'restore_failed_text',
96148
);
97-
const imageSrc = showRestored ? checkImageSrc : crossImageSrc;
98-
const buttonText = t(showRestored ? 'get_started' : 'try_again');
149+
const imageSrc = success ? checkImageSrc : crossImageSrc;
150+
const buttonText = t(success ? 'get_started' : 'try_again');
99151

100152
const onPress = (): void => {
101-
if (showRestored) {
102-
//App.tsx will show wallet now
153+
if (success) {
154+
// App.tsx will show wallet now
103155
dispatch(updateUser({ requiresRemoteRestore: false }));
104156
} else {
105-
onRemoteRestore().then().catch(console.error);
106-
setTryAgainCount(tryAgainCount + 1);
157+
setTryAgainCount((v) => v + 1);
158+
handleRemoteRestore();
107159
}
108160
};
109161

@@ -126,10 +178,10 @@ const RestoringScreen = (): ReactElement => {
126178
<Button
127179
size="large"
128180
text={buttonText}
129-
testID={showRestored ? 'GetStartedButton' : 'TryAgainButton'}
181+
testID={success ? 'GetStartedButton' : 'TryAgainButton'}
130182
onPress={onPress}
131183
/>
132-
{tryAgainCount > 1 && showFailed && (
184+
{tryAgainCount > 1 && !success && (
133185
<Button
134186
style={styles.proceedButton}
135187
text={t('restore_no_backup_button')}
@@ -155,13 +207,33 @@ const RestoringScreen = (): ReactElement => {
155207
/>
156208

157209
<SafeAreaInset type="bottom" minPadding={16} />
210+
211+
<SlashtagsProvider>
212+
<SkipSlashtagsOnboading />
213+
</SlashtagsProvider>
158214
</View>
159215
);
160216
}
161217

162218
return <ThemedView style={styles.root}>{content}</ThemedView>;
163219
};
164220

221+
// this component is used to skip the slashtags onboarding process if profile is already created
222+
const SkipSlashtagsOnboading = (): ReactElement => {
223+
const dispatch = useAppDispatch();
224+
const onboardingStep = useAppSelector(onboardingProfileStepSelector);
225+
const { url } = useSlashtags();
226+
const { profile } = useProfile(url);
227+
228+
useEffect(() => {
229+
if (onboardingStep !== 'Done' && profile.name) {
230+
dispatch(setOnboardingProfileStep('Done'));
231+
}
232+
}, [profile.name, onboardingStep, dispatch]);
233+
234+
return <></>;
235+
};
236+
165237
const styles = StyleSheet.create({
166238
root: {
167239
flex: 1,
@@ -195,4 +267,4 @@ const styles = StyleSheet.create({
195267
},
196268
});
197269

198-
export default RestoringScreen;
270+
export default CreateWallet;

src/screens/Onboarding/Loading.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const LoadingWalletScreen = ({
3939
}): ReactElement => {
4040
const { t } = useTranslation('onboarding');
4141
const { width } = useWindowDimensions();
42-
const animationDuration = isRestoring ? 16000 : 1500;
42+
const animationDuration = isRestoring ? 20000 : 8000;
4343

4444
const progressValue = useSharedValue(0);
4545
const progressText = useDerivedValue(() => {
@@ -54,8 +54,7 @@ const LoadingWalletScreen = ({
5454
duration: animationDuration,
5555
easing: Easing.linear,
5656
});
57-
// eslint-disable-next-line react-hooks/exhaustive-deps
58-
}, []);
57+
}, [animationDuration, progressValue]);
5958

6059
const circleAnimation = (): { initialValues: {}; animations: {} } => {
6160
'worklet';

0 commit comments

Comments
 (0)