Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(suite-native): FW install in onboarding flow #17111

Merged
merged 2 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ import { ConfirmOnTrezorImage, setDeviceForceRememberedThunk } from '@suite-nati
import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { Translation } from '@suite-native/intl';
import { SUITE_LITE_SUPPORT_URL, useOpenLink } from '@suite-native/link';
import {
DeviceSettingsStackParamList,
DeviceStackRoutes,
Screen,
StackNavigationProps,
} from '@suite-native/navigation';
import TrezorConnect from '@trezor/connect';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

Expand All @@ -34,11 +28,6 @@ import {
import { useFirmware } from '../hooks/useFirmware';
import { useFirmwareAnalytics } from '../hooks/useFirmwareAnalytics';

type NavigationProp = StackNavigationProps<
DeviceSettingsStackParamList,
DeviceStackRoutes.FirmwareUpdateInProgress
>;

const bottomButtonsContainerStyle = prepareNativeStyle<{ bottom: number }>((utils, { bottom }) => ({
position: 'absolute',
left: utils.spacings.sp16,
Expand All @@ -52,11 +41,21 @@ const cancelButtonStyle = prepareNativeStyle(utils => ({
top: utils.spacings.sp8,
}));

export const FirmwareUpdateInProgressScreen = () => {
type FirmwareInstallationScreenContentProps = {
onFirmwareInstallationSuccess: () => void;
onFirmwareInstallationFailure: () => void;
};

// This component is shared between `module-onboarding` and `module-device-settings`.
// Avoid doing anything module specific in this file!!!
export const FirmwareInstallationScreenContent = ({
onFirmwareInstallationSuccess,
onFirmwareInstallationFailure,
}: FirmwareInstallationScreenContentProps) => {
const dispatch = useDispatch();
const { applyStyle } = useNativeStyles();
const navigation = useNavigation<NavigationProp>();
const [isMayBeStuckedBottomSheetOpened, setIsMayBeStuckedBottomSheetOpened] =
const navigation = useNavigation();
const [isMayBeStuckBottomSheetOpened, setIsMayBeStuckBottomSheetOpened] =
useState<boolean>(false);
const { bottom: bottomSafeAreaInset } = useSafeAreaInsets();
const {
Expand Down Expand Up @@ -93,27 +92,21 @@ export const FirmwareUpdateInProgressScreen = () => {
};
}, [dispatch, resetReducer]);

const handleFirmwareUpdateFinished = useCallback(() => {
requestPrioritizedDeviceAccess({
const handleFirmwareUpdateFinished = useCallback(async () => {
await requestPrioritizedDeviceAccess({
deviceCallback: () => dispatch(authorizeDeviceThunk()),
});

const initialRoute = navigation.getState().routes.at(0)?.name;
if (initialRoute) {
navigation.navigate(initialRoute);
} else {
// This cause should not happen, but just to be safe
navigation.popToTop();
}
}, [dispatch, navigation]);
setIsFirmwareInstallationRunning(false);
onFirmwareInstallationSuccess();
}, [dispatch, onFirmwareInstallationSuccess, setIsFirmwareInstallationRunning]);

const handleCancel = useCallback(() => {
navigation.goBack();
}, [navigation]);

const startFirmwareUpdate = useCallback(async () => {
setIsFirmwareInstallationRunning(true);

const result = await firmwareUpdate();

if (!result) {
Expand All @@ -128,7 +121,7 @@ export const FirmwareUpdateInProgressScreen = () => {
result.payload?.code === 'Failure_ActionCancelled'
) {
handleAnalyticsReportCancelled();
navigation.navigate(DeviceStackRoutes.FirmwareUpdate);
onFirmwareInstallationFailure();

return;
}
Expand All @@ -139,17 +132,9 @@ export const FirmwareUpdateInProgressScreen = () => {
}

handleAnalyticsReportFinished();

// wait few seconds to animation to finish and let user orientate little bit
setTimeout(() => {
// setting this to false will trigger standart device connection flow
setIsFirmwareInstallationRunning(false);
handleFirmwareUpdateFinished();
}, 5000);
}, [
setIsFirmwareInstallationRunning,
navigation,
handleFirmwareUpdateFinished,
onFirmwareInstallationFailure,
firmwareUpdate,
handleAnalyticsReportFinished,
handleAnalyticsReportCancelled,
Expand All @@ -163,12 +148,12 @@ export const FirmwareUpdateInProgressScreen = () => {
startFirmwareUpdate();
}, [startFirmwareUpdate, resetReducer, handleAnalyticsReportStarted]);

const openMayBeStuckedBottomSheet = useCallback(() => {
setIsMayBeStuckedBottomSheetOpened(true);
const openMayBeStuckBottomSheet = useCallback(() => {
setIsMayBeStuckBottomSheetOpened(true);
}, []);

const closeMayBeStuckedBottomSheet = useCallback(() => {
setIsMayBeStuckedBottomSheetOpened(false);
const closeMayBeStuckBottomSheet = useCallback(() => {
setIsMayBeStuckBottomSheetOpened(false);
}, []);

const handleContactSupport = useCallback(() => {
Expand All @@ -186,6 +171,7 @@ export const FirmwareUpdateInProgressScreen = () => {
}, [startFirmwareUpdate, handleAnalyticsReportStarted]);

const isError = status === 'error';
const isDone = status === 'done';

const indicatorStatus: UpdateProgressIndicatorStatus = useMemo(() => {
const isStarting = (status === 'started' && operation === null) || status === 'initial';
Expand All @@ -204,7 +190,7 @@ export const FirmwareUpdateInProgressScreen = () => {
const bottomButtonOffset = showConfirmOnDevice ? 180 : bottomSafeAreaInset + 12;

return (
<Screen>
<>
{isError && (
<Animated.View entering={FadeIn} style={applyStyle(cancelButtonStyle)}>
<IconButton
Expand Down Expand Up @@ -256,11 +242,25 @@ export const FirmwareUpdateInProgressScreen = () => {
bottom: bottomButtonOffset,
})}
>
<Button onPress={openMayBeStuckedBottomSheet} colorScheme="tertiaryElevation0">
<Button onPress={openMayBeStuckBottomSheet} colorScheme="tertiaryElevation0">
<Translation id="moduleDeviceSettings.firmware.firmwareUpdateProgress.stuckButton" />
</Button>
</Animated.View>
)}
{isDone && (
<Animated.View
entering={FadeInDown}
exiting={FadeOutDown}
layout={LinearTransition}
style={applyStyle(bottomButtonsContainerStyle, {
bottom: bottomButtonOffset,
})}
>
<Button onPress={handleFirmwareUpdateFinished}>
<Translation id="generic.buttons.continue" />
</Button>
</Animated.View>
)}
{showConfirmOnDevice && (
<ConfirmOnTrezorImage
bottomSheetText={
Expand All @@ -269,10 +269,10 @@ export const FirmwareUpdateInProgressScreen = () => {
/>
)}
<MayBeStuckedBottomSheet
isOpened={isMayBeStuckedBottomSheetOpened}
onClose={closeMayBeStuckedBottomSheet}
isOpened={isMayBeStuckBottomSheetOpened}
onClose={closeMayBeStuckBottomSheet}
onAnalyticsReportStucked={handleAnalyticsReportStucked}
/>
</Screen>
</>
);
};
2 changes: 1 addition & 1 deletion suite-native/firmware/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './nativeFirmwareSlice';
export * from './components/UpdateProgressIndicatorDemo';
export * from './screens/FirmwareUpdateInProgressScreen';
export * from './components/FirmwareInstallationScreenContent';
export * from './hooks/useIsFirmwareUpdateFeatureEnabled';
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import { FirmwareUpdateInProgressScreen } from '@suite-native/firmware';
import {
DeviceSettingsStackParamList,
DeviceStackRoutes,
Expand All @@ -11,6 +10,7 @@ import { DeviceAuthenticityStackNavigator } from './DeviceAuthenticityStackNavig
import { DevicePinProtectionStackNavigator } from './DevicePinProtectionStackNavigator';
import { ContinueOnTrezorScreen } from '../screens/ContinueOnTrezorScreen';
import { DeviceSettingsModalScreen } from '../screens/DeviceSettingsModalScreen';
import { FirmwareInstallationScreen } from '../screens/FirmwareInstallationScreen';
import { FirmwareUpdateScreen } from '../screens/FirmwareUpdateScreen/FirmwareUpdateScreen';
const DeviceSettingsStack = createNativeStackNavigator<DeviceSettingsStackParamList>();

Expand Down Expand Up @@ -40,8 +40,8 @@ export const DeviceSettingsStackNavigator = () => (
component={ContinueOnTrezorScreen}
/>
<DeviceSettingsStack.Screen
name={DeviceStackRoutes.FirmwareUpdateInProgress}
component={FirmwareUpdateInProgressScreen}
name={DeviceStackRoutes.FirmwareInstallation}
component={FirmwareInstallationScreen}
/>
</DeviceSettingsStack.Navigator>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useNavigation } from '@react-navigation/native';

import { FirmwareInstallationScreenContent } from '@suite-native/firmware';
import {
DeviceSettingsStackParamList,
DeviceStackRoutes,
Screen,
StackNavigationProps,
} from '@suite-native/navigation';

type NavigationProp = StackNavigationProps<
DeviceSettingsStackParamList,
DeviceStackRoutes.FirmwareInstallation
>;

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

const handleFirmwareInstallationSuccess = () => {
const initialRoute = navigation.getState().routes.at(0)?.name;
if (initialRoute) {
navigation.navigate(initialRoute);
} else {
// This cause should not happen, but just to be safe
navigation.popToTop();
}
};

const handleFirmwareInstallationFailure = () => {
navigation.navigate(DeviceStackRoutes.FirmwareUpdate);
};

return (
<Screen>
<FirmwareInstallationScreenContent
onFirmwareInstallationSuccess={handleFirmwareInstallationSuccess}
onFirmwareInstallationFailure={handleFirmwareInstallationFailure}
/>
</Screen>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const FirmwareUpdateScreen = () => {
'moduleDeviceSettings.firmware.seedBottomSheet.continueButton',
),
onPressPrimaryButton: () => {
navigation.navigate(DeviceStackRoutes.FirmwareUpdateInProgress);
navigation.navigate(DeviceStackRoutes.FirmwareInstallation);
},
secondaryButtonTitle: translate(
'moduleDeviceSettings.firmware.seedBottomSheet.closeButton',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {

import { AnalyticsConsentScreen } from '../screens/AnalyticsConsentScreen';
import { BiometricsScreen } from '../screens/BiometricsScreen';
import { FirmwareInstallationScreen } from '../screens/FirmwareInstallationScreen';
import { SecurityCheckScreen } from '../screens/SecurityCheckScreen';
import { SuspiciousDeviceScreen } from '../screens/SuspiciousDeviceScreen';
import { UninitializedDeviceLandingScreen } from '../screens/UninitializedDeviceLandingScreen';
Expand Down Expand Up @@ -41,5 +42,9 @@ export const OnboardingStackNavigator = () => (
name={OnboardingStackRoutes.SecurityCheck}
component={SecurityCheckScreen}
/>
<OnboardingStack.Screen
name={OnboardingStackRoutes.FirmwareInstallationScreen}
component={FirmwareInstallationScreen}
/>
</OnboardingStack.Navigator>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FirmwareInstallationScreenContent } from '@suite-native/firmware';
import { Screen } from '@suite-native/navigation';
import { useToast } from '@suite-native/toasts';

export const FirmwareInstallationScreen = () => {
const { showToast } = useToast();
const handleFirmwareInstallationSuccess = () => {
showToast({
variant: 'warning',
message: 'Firmware installation successful! TODO: redirect to the device AC screen.',
});
};
Comment on lines +7 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement actual redirection in success handler.

The success handler only shows a toast without implementing the actual redirection to the device AC screen.

Consider implementing the redirection logic:

 const handleFirmwareInstallationSuccess = () => {
     showToast({
         variant: 'warning',
         message: 'Firmware installation successful! TODO: redirect to the device AC screen.',
     });
+    // TODO: Add navigation to device AC screen
+    // navigation.navigate(OnboardingStackRoutes.DeviceAC);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleFirmwareInstallationSuccess = () => {
showToast({
variant: 'warning',
message: 'Firmware installation successful! TODO: redirect to the device AC screen.',
});
};
const handleFirmwareInstallationSuccess = () => {
showToast({
variant: 'warning',
message: 'Firmware installation successful! TODO: redirect to the device AC screen.',
});
// TODO: Add navigation to device AC screen
// navigation.navigate(OnboardingStackRoutes.DeviceAC);
};


return (
<Screen>
<FirmwareInstallationScreenContent
onFirmwareInstallationSuccess={handleFirmwareInstallationSuccess}
// TODO: will be implemented as part of follow up issue: https://github.com/trezor/trezor-suite/issues/16291
onFirmwareInstallationFailure={() => null}
/>
Comment on lines +18 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement failure handler.

The failure handler is currently a no-op. While issue #16291 is tracking this, consider implementing a basic error handling flow for better user experience until the full implementation is ready.

Consider implementing basic error handling:

-// TODO: will be implemented as part of follow up issue: https://github.com/trezor/trezor-suite/issues/16291
-onFirmwareInstallationFailure={() => null}
+onFirmwareInstallationFailure={() => {
+    showToast({
+        variant: 'error',
+        message: 'Firmware installation failed. Please try again.',
+    });
+}}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: will be implemented as part of follow up issue: https://github.com/trezor/trezor-suite/issues/16291
onFirmwareInstallationFailure={() => null}
/>
onFirmwareInstallationFailure={() => {
showToast({
variant: 'error',
message: 'Firmware installation failed. Please try again.',
});
}}
/>

</Screen>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import { TitleHeader, VStack } from '@suite-native/atoms';
import { IconName } from '@suite-native/icons';
import { Translation } from '@suite-native/intl';
import { Link } from '@suite-native/link';
import { DeviceSuspicionCause, Screen, ScreenHeader } from '@suite-native/navigation';
import { useToast } from '@suite-native/toasts';
import {
DeviceSuspicionCause,
OnboardingStackParamList,
OnboardingStackRoutes,
Screen,
ScreenHeader,
StackProps,
} from '@suite-native/navigation';
import { TREZOR_RESELLERS_URL } from '@trezor/urls';

import { SecurityCheckStepCard } from '../components/SecurityCheckStepCard';
Expand Down Expand Up @@ -56,8 +62,9 @@ const stepToContentMap = {
}
>;

export const SecurityCheckScreen = () => {
const { showToast } = useToast();
export const SecurityCheckScreen = ({
navigation,
}: StackProps<OnboardingStackParamList, OnboardingStackRoutes.SecurityCheck>) => {
const [currentStep, setCurrentStep] = useState<number>(1);

const handlePressConfirmButton = () => {
Expand All @@ -66,8 +73,8 @@ export const SecurityCheckScreen = () => {

return;
}
// TODO: navigate to Firmware install
showToast({ variant: 'warning', message: 'TODO: implement next screen' });

navigation.navigate(OnboardingStackRoutes.FirmwareInstallationScreen);
};

return (
Expand Down
3 changes: 2 additions & 1 deletion suite-native/navigation/src/navigators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export type OnboardingStackParamList = {
suspicionCause: DeviceSuspicionCause;
};
[OnboardingStackRoutes.SecurityCheck]: undefined;
[OnboardingStackRoutes.FirmwareInstallationScreen]: undefined;
};

export type AccountsImportStackParamList = {
Expand Down Expand Up @@ -167,7 +168,7 @@ export type DeviceSettingsStackParamList = {
[DeviceStackRoutes.DevicePinProtection]: undefined;
[DeviceStackRoutes.DeviceAuthenticity]: undefined;
[DeviceStackRoutes.FirmwareUpdate]: undefined;
[DeviceStackRoutes.FirmwareUpdateInProgress]: undefined;
[DeviceStackRoutes.FirmwareInstallation]: undefined;
[DeviceStackRoutes.ContinueOnTrezor]: undefined;
};

Expand Down
3 changes: 2 additions & 1 deletion suite-native/navigation/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum OnboardingStackRoutes {
UninitializedDeviceLanding = 'UninitializedDeviceLanding',
SuspiciousDevice = 'SuspiciousDevice',
SecurityCheck = 'SecurityCheck',
FirmwareInstallationScreen = 'FirmwareInstallationScreen',
}

export enum AccountsImportStackRoutes {
Expand All @@ -47,7 +48,7 @@ export enum DeviceStackRoutes {
DevicePinProtection = 'DevicePinProtection',
DeviceAuthenticity = 'DeviceAuthenticity',
FirmwareUpdate = 'FirmwareUpdate',
FirmwareUpdateInProgress = 'FirmwareUpdateInProgress',
FirmwareInstallation = 'FirmwareInstallation',
ContinueOnTrezor = 'ContinueOnTrezor',
}

Expand Down