diff --git a/packages/urls/src/urls.ts b/packages/urls/src/urls.ts
index a1e43788611..f29c1b50502 100644
--- a/packages/urls/src/urls.ts
+++ b/packages/urls/src/urls.ts
@@ -33,6 +33,8 @@ export const TREZOR_SUPPORT_FW_ALREADY_INSTALLED: Url =
'https://trezor.io/support/a/firmware-is-already-installed';
export const TREZOR_SUPPORT_IS_MY_DEVICE_SAFE: Url =
'https://trezor.io/support/a/is-my-device-safe-to-use';
+export const TREZOR_SUPPORT_DIFFERENT_PACKAGING: Url =
+ 'https://trezor.io/support/a/why-is-my-box-different-from-what-is-shown-on-the-website';
export const HELP_CENTER_PIN_URL: Url =
'https://trezor.io/learn/a/pin-protection-on-trezor-devices';
diff --git a/suite-native/atoms/src/IconListItem.tsx b/suite-native/atoms/src/IconListItem.tsx
index acac47b1395..f9887825883 100644
--- a/suite-native/atoms/src/IconListItem.tsx
+++ b/suite-native/atoms/src/IconListItem.tsx
@@ -2,7 +2,7 @@ import { ReactNode } from 'react';
import { FlexAlignType } from 'react-native';
import { IconName, IconSize } from '@suite-native/icons';
-import { Color } from '@trezor/theme';
+import { Color, TypographyStyle } from '@trezor/theme';
import { Box } from './Box';
import { OrderedListIcon } from './OrderedListIcon';
@@ -42,6 +42,10 @@ type IconListItemProps = {
verticalAlign?: FlexAlignType;
};
+type IconListTextItemProps = IconListItemProps & {
+ textVariant?: TypographyStyle;
+};
+
export const IconListItem = ({
icon,
children,
@@ -59,8 +63,12 @@ export const IconListItem = ({
);
};
-export const IconListTextItem = ({ children, ...rest }: IconListItemProps) => (
+export const IconListTextItem = ({
+ children,
+ textVariant = 'hint',
+ ...rest
+}: IconListTextItemProps) => (
- {children}
+ {children}
);
diff --git a/suite-native/device/src/hooks/useHandleDeviceConnection.ts b/suite-native/device/src/hooks/useHandleDeviceConnection.ts
index a6b15ddd7ac..edc38abe307 100644
--- a/suite-native/device/src/hooks/useHandleDeviceConnection.ts
+++ b/suite-native/device/src/hooks/useHandleDeviceConnection.ts
@@ -41,6 +41,19 @@ type NavigationProp = StackToStackCompositeNavigationProps<
RootStackParamList
>;
+// We encourage user to disconnect device when he is redirected to suspicious device screen.
+// We should not redirect him away so he can read the screen content and decide what to do.
+// If the device is connected again, he still should stay on that screen.
+const isSuspiciousDeviceScreenFocused = (navigation: NavigationProp) => {
+ const previousRoute = navigation.getState()?.routes.at(-1);
+ const innerStackRoute = previousRoute?.state?.routes.at(-1);
+
+ return (
+ previousRoute?.name === RootStackRoutes.OnboardingStack &&
+ innerStackRoute?.name === OnboardingStackRoutes.SuspiciousDevice
+ );
+};
+
export const useHandleDeviceConnection = () => {
const isNoPhysicalDeviceConnected = useSelector(selectIsNoPhysicalDeviceConnected);
const isPortfolioTrackerDevice = useSelector(selectIsPortfolioTrackerDevice);
@@ -77,6 +90,7 @@ export const useHandleDeviceConnection = () => {
// When is an uninitialized device model that supports device setup, navigate to device onboarding.
useEffect(() => {
+ if (isSuspiciousDeviceScreenFocused(navigation)) return;
if (
isDeviceSetupSupported &&
!isDeviceInitialized &&
@@ -111,7 +125,7 @@ export const useHandleDeviceConnection = () => {
// At the moment when unauthorized physical device is selected,
// redirect to the Connecting screen where is handled the connection logic.
useEffect(() => {
- if (isFirmwareInstallationRunning) return;
+ if (isFirmwareInstallationRunning || isSuspiciousDeviceScreenFocused(navigation)) return;
if (
isDeviceInitialized &&
@@ -166,7 +180,7 @@ export const useHandleDeviceConnection = () => {
// TODO: this hook is getting very complex, and it's hard to understand the logic when it navigates there and back again.
// Ideally there'd be a single source of truth, a function returning "where we should be as per current state"
// rather than multiple useEffects with imperative instructions "go there when X changes"
- if (isDeviceCompromisedModalFocused) {
+ if (isDeviceCompromisedModalFocused || isSuspiciousDeviceScreenFocused(navigation)) {
return;
}
navigation.navigate(RootStackRoutes.AppTabs, {
diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts
index e1b89ec6bbc..a386799a44e 100644
--- a/suite-native/intl/src/en.ts
+++ b/suite-native/intl/src/en.ts
@@ -843,6 +843,15 @@ export const en = {
},
lookDifferentLabel: 'My device looks different',
},
+ suspiciousDeviceScreen: {
+ title: 'Let’s play it safe',
+ subtitle:
+ 'We want to be sure your device is in the best shape before you start using it.',
+ bullet1: 'Disconnect your device from your phone.',
+ bullet2: 'Avoid using this device or sending any funds to it.',
+ bullet3: 'Go to the support page linked below and use the Chat option on the website.',
+ contactSupportButton: 'Contact Trezor Support',
+ },
},
moduleAccountManagement: {
accountSettingsScreen: {
diff --git a/suite-native/module-onboarding/src/navigation/OnboardingStackNavigator.tsx b/suite-native/module-onboarding/src/navigation/OnboardingStackNavigator.tsx
index 08cf84e32a4..b7492f24e9d 100644
--- a/suite-native/module-onboarding/src/navigation/OnboardingStackNavigator.tsx
+++ b/suite-native/module-onboarding/src/navigation/OnboardingStackNavigator.tsx
@@ -8,6 +8,7 @@ import {
import { AnalyticsConsentScreen } from '../screens/AnalyticsConsentScreen';
import { BiometricsScreen } from '../screens/BiometricsScreen';
+import { SuspiciousDeviceScreen } from '../screens/SuspiciousDeviceScreen';
import { UninitializedDeviceLandingScreen } from '../screens/UninitializedDeviceLandingScreen';
import { WelcomeScreen } from '../screens/WelcomeScreen';
@@ -31,5 +32,9 @@ export const OnboardingStackNavigator = () => (
name={OnboardingStackRoutes.UninitializedDeviceLanding}
component={UninitializedDeviceLandingScreen}
/>
+
);
diff --git a/suite-native/module-onboarding/src/screens/SuspiciousDeviceScreen.tsx b/suite-native/module-onboarding/src/screens/SuspiciousDeviceScreen.tsx
new file mode 100644
index 00000000000..b38b64f4899
--- /dev/null
+++ b/suite-native/module-onboarding/src/screens/SuspiciousDeviceScreen.tsx
@@ -0,0 +1,126 @@
+import { useSelector } from 'react-redux';
+
+import { selectIsNoPhysicalDeviceConnected } from '@suite-common/wallet-core';
+import { Button, IconListTextItem, TitleHeader, VStack } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { useOpenLink } from '@suite-native/link';
+import {
+ AppTabsRoutes,
+ DeviceSuspicionCause,
+ HomeStackRoutes,
+ OnboardingStackParamList,
+ OnboardingStackRoutes,
+ RootStackParamList,
+ RootStackRoutes,
+ Screen,
+ ScreenHeader,
+ StackToStackCompositeScreenProps,
+} from '@suite-native/navigation';
+import {
+ TREZOR_SUPPORT_DIFFERENT_PACKAGING,
+ TREZOR_SUPPORT_FW_ALREADY_INSTALLED,
+ TREZOR_SUPPORT_IS_MY_DEVICE_SAFE,
+ Url,
+} from '@trezor/urls';
+
+const causeToLinkMap = {
+ deviceLooksDifferent: TREZOR_SUPPORT_IS_MY_DEVICE_SAFE,
+ firmwareAlreadyInstalled: TREZOR_SUPPORT_FW_ALREADY_INSTALLED,
+ untrustedReseller: TREZOR_SUPPORT_IS_MY_DEVICE_SAFE,
+ securitySeal: TREZOR_SUPPORT_IS_MY_DEVICE_SAFE,
+ packaging: TREZOR_SUPPORT_DIFFERENT_PACKAGING,
+} as const satisfies Record;
+
+export const SuspiciousDeviceScreen = ({
+ route,
+ navigation,
+}: StackToStackCompositeScreenProps<
+ OnboardingStackParamList,
+ OnboardingStackRoutes.SuspiciousDevice,
+ RootStackParamList
+>) => {
+ const { suspicionCause } = route.params;
+ const openLink = useOpenLink();
+ const isNoPhysicalDeviceConnected = useSelector(selectIsNoPhysicalDeviceConnected);
+
+ const supportLink = causeToLinkMap[suspicionCause];
+
+ const handleContactSupportButtonPress = () => {
+ openLink(`${supportLink}/#open-chat`);
+ };
+
+ const handleBackButtonPress = () => {
+ if (isNoPhysicalDeviceConnected) {
+ // Exit the onboarding flow if device was disconnected while was user on this screen.
+ navigation.navigate(RootStackRoutes.AppTabs, {
+ screen: AppTabsRoutes.HomeStack,
+ params: {
+ screen: HomeStackRoutes.Home,
+ },
+ });
+
+ return;
+ }
+
+ navigation.goBack();
+ };
+
+ return (
+ }>
+
+
+ }
+ subtitle={
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/suite-native/module-onboarding/src/screens/UninitializedDeviceLandingScreen.tsx b/suite-native/module-onboarding/src/screens/UninitializedDeviceLandingScreen.tsx
index 543ce5deb25..50e408d1aa7 100644
--- a/suite-native/module-onboarding/src/screens/UninitializedDeviceLandingScreen.tsx
+++ b/suite-native/module-onboarding/src/screens/UninitializedDeviceLandingScreen.tsx
@@ -4,7 +4,13 @@ import { selectDeviceModel, selectHasDeviceFirmwareInstalled } from '@suite-comm
import { Box, Button, Image, Text, TextButton, TitleHeader, VStack } from '@suite-native/atoms';
import { SetupSupportingDeviceModel } from '@suite-native/device';
import { Translation } from '@suite-native/intl';
-import { Screen, ScreenHeader } from '@suite-native/navigation';
+import {
+ OnboardingStackParamList,
+ OnboardingStackRoutes,
+ Screen,
+ ScreenHeader,
+ StackProps,
+} from '@suite-native/navigation';
import { useToast } from '@suite-native/toasts';
import { DeviceModelInternal } from '@trezor/connect';
import { getScreenHeight } from '@trezor/env-utils';
@@ -67,7 +73,9 @@ const UninitializedDeviceLandingScreenContent = () => {
);
};
-export const UninitializedDeviceLandingScreen = () => {
+export const UninitializedDeviceLandingScreen = ({
+ navigation,
+}: StackProps) => {
const { showToast } = useToast();
const hasDeviceFirmwareInstalled = useSelector(selectHasDeviceFirmwareInstalled);
@@ -76,9 +84,16 @@ export const UninitializedDeviceLandingScreen = () => {
showToast({ variant: 'warning', message: 'TODO: implement next screen' });
};
- const handleDeclineButtonPress = () => {
- // TODO: navigate to screen where user can see more info regarding tampered devices.
- showToast({ variant: 'warning', message: 'TODO: implement next screen' });
+ const handleNeverUsedThisDeviceButtonPress = () => {
+ navigation.navigate(OnboardingStackRoutes.SuspiciousDevice, {
+ suspicionCause: 'firmwareAlreadyInstalled',
+ });
+ };
+
+ const handleDeviceLooksDifferentButtonPress = () => {
+ navigation.navigate(OnboardingStackRoutes.SuspiciousDevice, {
+ suspicionCause: 'deviceLooksDifferent',
+ });
};
return (
@@ -89,7 +104,7 @@ export const UninitializedDeviceLandingScreen = () => {
@@ -105,7 +120,7 @@ export const UninitializedDeviceLandingScreen = () => {
{hasDeviceFirmwareInstalled && (