From f1b822d8f5f71af74bcc6d50de45dcced202c8af Mon Sep 17 00:00:00 2001 From: yanas Date: Mon, 20 Jan 2025 15:47:55 +0100 Subject: [PATCH] fix(suite-native): prohibit swiping back from authorize device screens --- .../app/src/navigation/RootStackNavigator.tsx | 1 + .../src/screens/CoinEnablingInitScreen.tsx | 13 ++---- .../screens/AccountImportLoadingScreen.tsx | 11 ++--- .../screens/AccountImportSummaryScreen.tsx | 12 ++---- .../connect/ConnectDeviceScreenHeader.tsx | 41 ++++++++----------- .../passphrase/PassphraseScreenHeader.tsx | 14 ++----- .../AuthorizeDeviceStackNavigator.tsx | 4 +- .../connect/ConnectingDeviceScreen.tsx | 3 ++ suite-native/navigation/src/index.ts | 1 + .../src/useHandleHardwareBackNavigation.ts | 15 +++++++ 10 files changed, 53 insertions(+), 62 deletions(-) create mode 100644 suite-native/navigation/src/useHandleHardwareBackNavigation.ts diff --git a/suite-native/app/src/navigation/RootStackNavigator.tsx b/suite-native/app/src/navigation/RootStackNavigator.tsx index 1fdc377016b..54a8232b408 100644 --- a/suite-native/app/src/navigation/RootStackNavigator.tsx +++ b/suite-native/app/src/navigation/RootStackNavigator.tsx @@ -109,6 +109,7 @@ export const RootStackNavigator = () => { options={{ ...stackNavigationOptionsConfig, animation: 'slide_from_bottom', + gestureEnabled: false, }} /> ({ export const CoinEnablingInitScreen = () => { const dispatch = useDispatch(); const navigation = useNavigation(); + useHandleHardwareBackNavigation(); const { applyStyle, utils } = useNativeStyles(); const enabledNetworkSymbols = useSelector(selectDeviceEnabledDiscoveryNetworkSymbols); @@ -69,13 +69,6 @@ export const CoinEnablingInitScreen = () => { } }; - useEffect(() => { - //prevent dismissing screen via HW - const subscription = BackHandler.addEventListener('hardwareBackPress', () => true); - - return () => subscription.remove(); - }, []); - // 'transparent' color is not working in context of LinearGradient on iOS. RGBA has to be used instead. const transparentColor = hexToRgba(utils.colors.backgroundSurfaceElevation0, 0.01); diff --git a/suite-native/module-accounts-import/src/screens/AccountImportLoadingScreen.tsx b/suite-native/module-accounts-import/src/screens/AccountImportLoadingScreen.tsx index edf92819121..48008d85a56 100644 --- a/suite-native/module-accounts-import/src/screens/AccountImportLoadingScreen.tsx +++ b/suite-native/module-accounts-import/src/screens/AccountImportLoadingScreen.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useState } from 'react'; -import { BackHandler } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { selectFiatCurrencyCode } from '@suite-native/settings'; @@ -9,6 +8,7 @@ import { RootStackParamList, Screen, StackToStackCompositeScreenProps, + useHandleHardwareBackNavigation, } from '@suite-native/navigation'; import { AccountInfo } from '@trezor/connect'; import { SpinnerLoadingState } from '@suite-native/atoms'; @@ -36,6 +36,8 @@ export const AccountImportLoadingScreen = ({ const [accountInfoFetchResult, setAccountInfoFetchResult] = useState('idle'); + useHandleHardwareBackNavigation(); + const fetchAccountInfo = useCallback(async () => { try { const response = await dispatch( @@ -71,13 +73,6 @@ export const AccountImportLoadingScreen = ({ [error, showImportError], ); - useEffect(() => { - // prevent dismissing screen via HW - const subscription = BackHandler.addEventListener('hardwareBackPress', () => true); - - return () => subscription.remove(); - }, []); - useEffect(() => { fetchAccountInfo(); }, [fetchAccountInfo]); diff --git a/suite-native/module-accounts-import/src/screens/AccountImportSummaryScreen.tsx b/suite-native/module-accounts-import/src/screens/AccountImportSummaryScreen.tsx index 8f3c12514cb..0bbe4fd5f01 100644 --- a/suite-native/module-accounts-import/src/screens/AccountImportSummaryScreen.tsx +++ b/suite-native/module-accounts-import/src/screens/AccountImportSummaryScreen.tsx @@ -1,5 +1,3 @@ -import { useEffect } from 'react'; -import { BackHandler } from 'react-native'; import { useSelector } from 'react-redux'; import { @@ -16,6 +14,7 @@ import { AccountsImportStackRoutes, RootStackParamList, StackToTabCompositeScreenProps, + useHandleHardwareBackNavigation, } from '@suite-native/navigation'; import { AccountAlreadyImportedScreen } from '../components/AccountAlreadyImportedScreen'; @@ -31,6 +30,8 @@ export const AccountImportSummaryScreen = ({ const { accountInfo, networkSymbol } = route.params; const isRegtestEnabled = useFeatureFlag(FeatureFlag.IsRegtestEnabled); + useHandleHardwareBackNavigation(); + const account = useSelector((state: AccountsRootState & DeviceRootState) => selectDeviceAccountByDescriptorAndNetworkSymbol( state, @@ -40,13 +41,6 @@ export const AccountImportSummaryScreen = ({ ); const portfolioTrackerSupportedNetworks = useSelector(selectPortfolioTrackerNetworkSymbols); - useEffect(() => { - // prevent dismissing screen via HW - const subscription = BackHandler.addEventListener('hardwareBackPress', () => true); - - return () => subscription.remove(); - }, []); - const isAccountImportSupported = portfolioTrackerSupportedNetworks.some( supportedSymbol => supportedSymbol === networkSymbol, diff --git a/suite-native/module-authorize-device/src/components/connect/ConnectDeviceScreenHeader.tsx b/suite-native/module-authorize-device/src/components/connect/ConnectDeviceScreenHeader.tsx index e326a0f6dcf..aa1766aa8a3 100644 --- a/suite-native/module-authorize-device/src/components/connect/ConnectDeviceScreenHeader.tsx +++ b/suite-native/module-authorize-device/src/components/connect/ConnectDeviceScreenHeader.tsx @@ -1,4 +1,3 @@ -import { BackHandler } from 'react-native'; import { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -11,6 +10,7 @@ import { NavigateParameters, RootStackParamList, StackToTabCompositeProps, + useHandleHardwareBackNavigation, } from '@suite-native/navigation'; import { IconButton, ScreenHeaderWrapper } from '@suite-native/atoms'; import { useAlert } from '@suite-native/alerts'; @@ -49,18 +49,22 @@ export const ConnectDeviceScreenHeader = ({ const handleCancel = useCallback(() => { if (hasDiscovery) { - // Do not allow to cancel PIN entry while discovery is in progress - showAlert({ - title: , - description: ( - - ), - pictogramVariant: 'critical', - primaryButtonTitle: ( - - ), - onPressPrimaryButton: hideAlert, - }); + if (hasDeviceRequestedPin) { + // Do not allow to cancel PIN entry while discovery is in progress + showAlert({ + title: ( + + ), + description: ( + + ), + pictogramVariant: 'critical', + primaryButtonTitle: ( + + ), + onPressPrimaryButton: hideAlert, + }); + } } else { // Remove unauthorized passphrase device if it was created before prompting the PIN. if (isCreatingNewWalletInstance) { @@ -92,16 +96,7 @@ export const ConnectDeviceScreenHeader = ({ onCancelNavigationTarget, ]); - // Handle hardware back button press same as cancel button - useEffect(() => { - const subscription = BackHandler.addEventListener('hardwareBackPress', () => { - handleCancel(); - - return true; - }); - - return () => subscription.remove(); - }, [handleCancel]); + useHandleHardwareBackNavigation(handleCancel); // Hide alert when navigating away from the PIN entry screen (PIN entered or canceled on device) // eslint-disable-next-line arrow-body-style diff --git a/suite-native/module-authorize-device/src/components/passphrase/PassphraseScreenHeader.tsx b/suite-native/module-authorize-device/src/components/passphrase/PassphraseScreenHeader.tsx index 9cb9fbc47cb..5fdb3c94058 100644 --- a/suite-native/module-authorize-device/src/components/passphrase/PassphraseScreenHeader.tsx +++ b/suite-native/module-authorize-device/src/components/passphrase/PassphraseScreenHeader.tsx @@ -1,6 +1,5 @@ import { useDispatch, useSelector } from 'react-redux'; -import { useCallback, useEffect } from 'react'; -import { BackHandler } from 'react-native'; +import { useCallback } from 'react'; import { useNavigation, useRoute } from '@react-navigation/native'; @@ -20,6 +19,7 @@ import { RootStackParamList, StackToTabCompositeProps, useNavigateToInitialScreen, + useHandleHardwareBackNavigation, } from '@suite-native/navigation'; import { useAlert } from '@suite-native/alerts'; import { EventType, analytics } from '@suite-native/analytics'; @@ -78,15 +78,7 @@ export const PassphraseScreenHeader = () => { } }, [handleClose, navigateToInitialScreen, isCreatingNewWalletInstance, showAlert]); - useEffect(() => { - const subscription = BackHandler.addEventListener('hardwareBackPress', () => { - handleCancel(); - - return true; - }); - - return () => subscription.remove(); - }, [handleCancel]); + useHandleHardwareBackNavigation(handleCancel); return ( diff --git a/suite-native/module-authorize-device/src/navigation/AuthorizeDeviceStackNavigator.tsx b/suite-native/module-authorize-device/src/navigation/AuthorizeDeviceStackNavigator.tsx index d07ab9c7442..38d99304316 100644 --- a/suite-native/module-authorize-device/src/navigation/AuthorizeDeviceStackNavigator.tsx +++ b/suite-native/module-authorize-device/src/navigation/AuthorizeDeviceStackNavigator.tsx @@ -36,7 +36,9 @@ export const AuthorizeDeviceStackNavigator = () => { useHandleDuplicatePassphrase(); return ( - + { // For proper screen transitions on both cancel and success PIN entry // we need to remove those screens from the stack so we can navigate diff --git a/suite-native/module-authorize-device/src/screens/connect/ConnectingDeviceScreen.tsx b/suite-native/module-authorize-device/src/screens/connect/ConnectingDeviceScreen.tsx index a337db85eea..09f5228d7de 100644 --- a/suite-native/module-authorize-device/src/screens/connect/ConnectingDeviceScreen.tsx +++ b/suite-native/module-authorize-device/src/screens/connect/ConnectingDeviceScreen.tsx @@ -1,5 +1,6 @@ import { ActivityIndicator } from 'react-native'; +import { useHandleHardwareBackNavigation } from '@suite-native/navigation'; import { Text, VStack, Box } from '@suite-native/atoms'; import { Icon } from '@suite-native/icons'; import { useNativeStyles, prepareNativeStyle } from '@trezor/styles'; @@ -16,6 +17,8 @@ const screenStyle = prepareNativeStyle(() => ({ export const ConnectingDeviceScreen = () => { useOnDeviceReadyNavigation(); + useHandleHardwareBackNavigation(); + const { applyStyle } = useNativeStyles(); return ( diff --git a/suite-native/navigation/src/index.ts b/suite-native/navigation/src/index.ts index 4aebbaac396..4d7fea50d5b 100644 --- a/suite-native/navigation/src/index.ts +++ b/suite-native/navigation/src/index.ts @@ -2,6 +2,7 @@ export * from './navigators'; export * from './routes'; export * from './types'; export * from './config'; +export * from './useHandleHardwareBackNavigation'; export * from './useNavigateToInitialScreen'; export * from './useScrollDivider'; export * from './components/TabBar'; diff --git a/suite-native/navigation/src/useHandleHardwareBackNavigation.ts b/suite-native/navigation/src/useHandleHardwareBackNavigation.ts new file mode 100644 index 00000000000..1a5ad55816c --- /dev/null +++ b/suite-native/navigation/src/useHandleHardwareBackNavigation.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; +import { BackHandler } from 'react-native'; + +export const useHandleHardwareBackNavigation = (onPress?: () => void) => { + useEffect(() => { + // do nothing unless onPress has some custom handling + const subscription = BackHandler.addEventListener('hardwareBackPress', () => { + onPress?.(); + + return true; + }); + + return () => subscription.remove(); + }, [onPress]); +};