Skip to content

Commit

Permalink
feat(suite-native): enter passphrased wallet (#12105)
Browse files Browse the repository at this point in the history
  • Loading branch information
juriczech authored Apr 25, 2024
1 parent 3590071 commit bb2e075
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 78 deletions.
7 changes: 7 additions & 0 deletions suite-common/wallet-core/src/device/deviceReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,3 +847,10 @@ export const selectIsDeviceInViewOnlyMode = (state: DeviceRootState) => {

return !isDeviceConnected && isDeviceRemembered;
};

export const selectIsDeviceUsingPassphrase = (state: DeviceRootState) => {
const isDeviceProtectedByPassphrase = selectIsDeviceProtectedByPassphrase(state);
const device = selectDevice(state);

return isDeviceProtectedByPassphrase && device?.useEmptyPassphrase === false;
};
11 changes: 7 additions & 4 deletions suite-native/app/src/navigation/AppTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { HomeStackNavigator } from '@suite-native/module-home';
import { AccountsStackNavigator } from '@suite-native/module-accounts-management';
import { SettingsStackNavigator } from '@suite-native/module-settings';
import { AppTabsParamList, AppTabsRoutes, TabBar } from '@suite-native/navigation';
import { useHandleDeviceRequestsPassphrase } from '@suite-native/device';

import { rootTabsOptions } from './routes';

const Tab = createBottomTabNavigator<AppTabsParamList>();

export const AppTabNavigator = () => (
<>
export const AppTabNavigator = () => {
useHandleDeviceRequestsPassphrase();

return (
<Tab.Navigator
initialRouteName={AppTabsRoutes.HomeStack}
screenOptions={{
Expand All @@ -27,5 +30,5 @@ export const AppTabNavigator = () => (
<Tab.Screen name={AppTabsRoutes.ReceiveStack} component={ReceiveStackNavigator} />
<Tab.Screen name={AppTabsRoutes.SettingsStack} component={SettingsStackNavigator} />
</Tab.Navigator>
</>
);
);
};
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
import { useNavigation } from '@react-navigation/native';
import { useDispatch, useSelector } from 'react-redux';

import { Button } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';
import {
PassphraseStackParamList,
PassphraseStackRoutes,
RootStackRoutes,
StackToStackCompositeNavigationProps,
RootStackParamList,
} from '@suite-native/navigation';
import { createDeviceInstance, selectDevice } from '@suite-common/wallet-core';

import { useDeviceManager } from '../hooks/useDeviceManager';

type NavigationProp = StackToStackCompositeNavigationProps<
PassphraseStackParamList,
PassphraseStackRoutes.PassphraseForm,
RootStackParamList
>;

export const AddHiddenWalletButton = () => {
const navigation = useNavigation<NavigationProp>();
const dispatch = useDispatch();

const device = useSelector(selectDevice);

const { setIsDeviceManagerVisible } = useDeviceManager();

const handleAddHiddenWallet = () => {
setIsDeviceManagerVisible(false);
if (!device) return;

navigation.navigate(RootStackRoutes.PassphraseStack, {
screen: PassphraseStackRoutes.PassphraseForm,
});

// await dispatch(createDeviceInstance({ device: instance }));
setIsDeviceManagerVisible(false);
dispatch(createDeviceInstance({ device }));
};

return (
Expand Down
15 changes: 11 additions & 4 deletions suite-native/device/src/hooks/useHandleDeviceConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
selectDeviceRequestedPin,
selectIsDeviceConnectedAndAuthorized,
selectIsNoPhysicalDeviceConnected,
selectIsDeviceUsingPassphrase,
} from '@suite-common/wallet-core';
import { selectIsOnboardingFinished } from '@suite-native/module-settings';
import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
Expand All @@ -36,7 +37,7 @@ export const useHandleDeviceConnection = () => {
const isDeviceConnectedAndAuthorized = useSelector(selectIsDeviceConnectedAndAuthorized);
const hasDeviceRequestedPin = useSelector(selectDeviceRequestedPin);
const { isBiometricsOverlayVisible } = useIsBiometricsOverlayVisible();

const isDeviceUsingPassphrase = useSelector(selectIsDeviceUsingPassphrase);
const navigation = useNavigation<NavigationProp>();
const dispatch = useDispatch();

Expand All @@ -50,9 +51,14 @@ export const useHandleDeviceConnection = () => {
!isBiometricsOverlayVisible
) {
requestPrioritizedDeviceAccess(() => dispatch(authorizeDevice()));
navigation.navigate(RootStackRoutes.ConnectDeviceStack, {
screen: ConnectDeviceStackRoutes.ConnectingDevice,
});

// Note: Passphrase protected device (excluding empty passphrase, e. g. standard wallet with passphrase protection on device),
// post auth navigation is handled in @suite-native/module-passphrase for custom UX flow.
if (!isDeviceUsingPassphrase) {
navigation.navigate(RootStackRoutes.ConnectDeviceStack, {
screen: ConnectDeviceStackRoutes.ConnectingDevice,
});
}
}
}, [
dispatch,
Expand All @@ -62,6 +68,7 @@ export const useHandleDeviceConnection = () => {
isDeviceConnectedAndAuthorized,
isBiometricsOverlayVisible,
navigation,
isDeviceUsingPassphrase,
]);

// In case that the physical device is disconnected, redirect to the home screen and
Expand Down
34 changes: 34 additions & 0 deletions suite-native/device/src/hooks/useHandleDeviceRequestsPassphrase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useCallback, useEffect } from 'react';

import { useNavigation } from '@react-navigation/native';

import TrezorConnect from '@trezor/connect';
import {
PassphraseStackParamList,
PassphraseStackRoutes,
RootStackParamList,
RootStackRoutes,
StackToStackCompositeNavigationProps,
} from '@suite-native/navigation';

type NavigationProp = StackToStackCompositeNavigationProps<
PassphraseStackParamList,
PassphraseStackRoutes.PassphraseForm,
RootStackParamList
>;

export const useHandleDeviceRequestsPassphrase = () => {
const navigation = useNavigation<NavigationProp>();

const handleNavigateToPassphraseForm = useCallback(() => {
navigation.navigate(RootStackRoutes.PassphraseStack, {
screen: PassphraseStackRoutes.PassphraseForm,
});
}, [navigation]);

useEffect(() => {
TrezorConnect.on('ui-request_passphrase', handleNavigateToPassphraseForm);

return () => TrezorConnect.off('ui-request_passphrase', handleNavigateToPassphraseForm);
}, [handleNavigateToPassphraseForm]);
};
1 change: 1 addition & 0 deletions suite-native/device/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './middlewares/buttonRequestMiddleware';
export * from './hooks/useHandleDeviceConnection';
export * from './hooks/useDetectDeviceError';
export * from './hooks/useDelayedNavigation';
export * from './hooks/useHandleDeviceRequestsPassphrase';
export * from './hooks/useReportDeviceConnectToAnalytics';
export * from './screens/DeviceInfoModalScreen';
export * from './components/ConnectDeviceAnimation';
Expand Down
7 changes: 6 additions & 1 deletion suite-native/module-passphrase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@
"dependencies": {
"@react-navigation/native": "6.1.10",
"@react-navigation/native-stack": "6.9.18",
"@reduxjs/toolkit": "1.9.5",
"@suite-common/icons": "workspace:*",
"@suite-common/redux-utils": "workspace:*",
"@suite-common/validators": "workspace:*",
"@suite-common/wallet-constants": "workspace:*",
"@suite-common/wallet-core": "workspace:*",
"@suite-native/atoms": "workspace:*",
"@suite-native/forms": "workspace:*",
"@suite-native/intl": "workspace:*",
"@suite-native/navigation": "workspace:*",
"@suite-native/theme": "workspace:*",
"@trezor/connect": "workspace:*",
"@trezor/styles": "workspace:*",
"react": "18.2.0",
"react-native": "0.73.6",
"react-native-reanimated": "3.8.1",
"react-native-svg": "14.1.0"
"react-native-svg": "14.1.0",
"react-redux": "8.0.7"
}
}
7 changes: 7 additions & 0 deletions suite-native/module-passphrase/redux.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AsyncThunkAction } from '@reduxjs/toolkit';

declare module 'redux' {
export interface Dispatch {
<TThunk extends AsyncThunkAction<any, any, any>>(thunk: TThunk): ReturnType<TThunk>;
}
}

This file was deleted.

42 changes: 39 additions & 3 deletions suite-native/module-passphrase/src/components/PassphraseForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';

import { useNavigation } from '@react-navigation/native';

import { Form, TextInputField, useForm } from '@suite-native/forms';
import {
Expand All @@ -9,6 +13,18 @@ import {
import { Button, VStack } from '@suite-native/atoms';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { Translation, useTranslate } from '@suite-native/intl';
import {
deviceActions,
onPassphraseSubmit,
selectDevice,
selectDeviceButtonRequestsCodes,
} from '@suite-common/wallet-core';
import {
PassphraseStackParamList,
PassphraseStackRoutes,
RootStackParamList,
StackToStackCompositeNavigationProps,
} from '@suite-native/navigation';

type PassphraseFormProps = {
onFocus: () => void;
Expand All @@ -19,23 +35,43 @@ const formStyle = prepareNativeStyle(utils => ({
borderRadius: utils.borders.radii.large,
}));

type NavigationProp = StackToStackCompositeNavigationProps<
PassphraseStackParamList,
PassphraseStackRoutes.PassphraseForm,
RootStackParamList
>;

export const PassphraseForm = ({ onFocus }: PassphraseFormProps) => {
const dispatch = useDispatch();

const { applyStyle } = useNativeStyles();

const { translate } = useTranslate();

const device = useSelector(selectDevice);
const buttonRequestCodes = useSelector(selectDeviceButtonRequestsCodes);

const navigation = useNavigation<NavigationProp>();

const form = useForm<PassphraseFormValues>({
validation: passphraseFormSchema,
defaultValues: {
passphrase: '',
},
});

useEffect(() => {
if (buttonRequestCodes.includes('ButtonRequest_Other')) {
navigation.navigate(PassphraseStackRoutes.PassphraseConfirmOnDevice);
dispatch(deviceActions.removeButtonRequests({ device }));
}
}, [buttonRequestCodes, device, dispatch, navigation]);

const { handleSubmit, watch } = form;

const handleCreateHiddenWallet = handleSubmit(values => {
console.warn(values);
// TODO create wallet
const handleCreateHiddenWallet = handleSubmit(({ passphrase }) => {
dispatch(deviceActions.removeButtonRequests({ device }));
dispatch(onPassphraseSubmit({ value: passphrase, passphraseOnDevice: false }));
});

const inputHasValue = !!watch('passphrase').length;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,51 @@
import { ScreenHeaderWrapper } from '@suite-native/atoms';
import { GoBackIcon } from '@suite-native/navigation';
import { useDispatch } from 'react-redux';

import { useNavigation } from '@react-navigation/native';

import { IconButton, ScreenHeaderWrapper } from '@suite-native/atoms';
import {
AppTabsRoutes,
HomeStackRoutes,
PassphraseStackParamList,
PassphraseStackRoutes,
RootStackParamList,
RootStackRoutes,
StackToTabCompositeProps,
} from '@suite-native/navigation';

import { cancelPassphraseAndSelectStandardDeviceThunk } from '../passphraseThunks';

type NavigationProp = StackToTabCompositeProps<
PassphraseStackParamList,
PassphraseStackRoutes,
RootStackParamList
>;

export const PassphraseScreenHeader = () => {
const navigation = useNavigation<NavigationProp>();

const dispatch = useDispatch();

const handleClose = () => {
dispatch(cancelPassphraseAndSelectStandardDeviceThunk());
navigation.navigate(RootStackRoutes.AppTabs, {
screen: AppTabsRoutes.HomeStack,
params: {
screen: HomeStackRoutes.Home,
},
});
};

return (
<ScreenHeaderWrapper>
<GoBackIcon closeActionType="close" />
<IconButton
iconName="close"
size="medium"
colorScheme="tertiaryElevation1"
accessibilityRole="button"
accessibilityLabel="close"
onPress={handleClose}
/>
</ScreenHeaderWrapper>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '@suite-native/navigation';

import { PassphraseFormScreen } from '../screens/PassphraseFormScreen';
import { PassphraseLoadingScreen } from '../screens/PassphraseLoadingScreen';
import { PassphraseConfirmOnDeviceScreen } from '../screens/PassphraseConfirmOnDeviceScreen';

export const PassphraseStack = createNativeStackNavigator<PassphraseStackParamList>();

Expand All @@ -17,6 +19,14 @@ export const PassphraseStackNavigator = () => {
name={PassphraseStackRoutes.PassphraseForm}
component={PassphraseFormScreen}
/>
<PassphraseStack.Screen
name={PassphraseStackRoutes.PassphraseConfirmOnDevice}
component={PassphraseConfirmOnDeviceScreen}
/>
<PassphraseStack.Screen
name={PassphraseStackRoutes.PassphraseLoading}
component={PassphraseLoadingScreen}
/>
</PassphraseStack.Navigator>
);
};
Loading

0 comments on commit bb2e075

Please sign in to comment.