diff --git a/suite-common/icons/assets/icons/linkChain.svg b/suite-common/icons/assets/icons/linkChain.svg
deleted file mode 100644
index baf197562b7..00000000000
--- a/suite-common/icons/assets/icons/linkChain.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/suite-common/icons/assets/icons/linkChainBroken.svg b/suite-common/icons/assets/icons/linkChainBroken.svg
deleted file mode 100644
index d04f0182f98..00000000000
--- a/suite-common/icons/assets/icons/linkChainBroken.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/suite-common/icons/src/icons.ts b/suite-common/icons/src/icons.ts
index 7336ffb2c24..a61e5cbde4f 100644
--- a/suite-common/icons/src/icons.ts
+++ b/suite-common/icons/src/icons.ts
@@ -57,8 +57,6 @@ export const icons = {
label: require('../assets/icons/label.svg'),
lifebuoy: require('../assets/icons/lifebuoy.svg'),
link: require('../assets/icons/link.svg'),
- linkChain: require('../assets/icons/linkChain.svg'),
- linkChainBroken: require('../assets/icons/linkChainBroken.svg'),
lock: require('../assets/icons/lock.svg'),
minus: require('../assets/icons/minus.svg'),
minusCircle: require('../assets/icons/minusCircle.svg'),
diff --git a/suite-common/suite-utils/src/device.ts b/suite-common/suite-utils/src/device.ts
index 8f94d483460..d9eaab80fc0 100644
--- a/suite-common/suite-utils/src/device.ts
+++ b/suite-common/suite-utils/src/device.ts
@@ -409,6 +409,20 @@ export const getPhysicalDeviceUniqueIds = (devices: Device[]) =>
export const getPhysicalDeviceCount = (devices: Device[]) =>
getPhysicalDeviceUniqueIds(devices).length;
+export const getSortedDevicesWithoutInstances = (
+ devices: TrezorDevice[],
+ excludedDeviceId?: string | null,
+) =>
+ getDeviceInstancesGroupedByDeviceId(devices)
+ .flatMap(group => group[0])
+ .filter(d => d?.id !== excludedDeviceId && d?.id)
+ .sort((a, b) => {
+ if (!a.connected) return -1;
+ if (!b.connected) return 1;
+
+ return 0;
+ });
+
export const parseFirmwareChangelog = (release?: FirmwareRelease) => {
if (!release?.changelog?.length || !release) {
return null;
diff --git a/suite-common/wallet-core/src/device/deviceReducer.ts b/suite-common/wallet-core/src/device/deviceReducer.ts
index a9e04b3aeed..b27c0822c2b 100644
--- a/suite-common/wallet-core/src/device/deviceReducer.ts
+++ b/suite-common/wallet-core/src/device/deviceReducer.ts
@@ -1,7 +1,7 @@
import { memoize } from 'proxy-memoize';
import * as deviceUtils from '@suite-common/suite-utils';
-import { getStatus } from '@suite-common/suite-utils';
+import { getDeviceInstances, getStatus } from '@suite-common/suite-utils';
import { Device, Features, UI } from '@trezor/connect';
import { getFirmwareVersion, getFirmwareVersionArray } from '@trezor/device-utils';
import { Network, networks } from '@suite-common/wallet-config';
@@ -858,3 +858,22 @@ export const selectDeviceState = (state: DeviceRootState) => {
return device?.state ?? null;
};
+
+export const selectDeviceInstances = memoize((state: DeviceRootState) => {
+ const device = selectDevice(state);
+
+ if (!device) {
+ return [];
+ }
+
+ const allDevices = selectDevices(state);
+
+ return getDeviceInstances(device, allDevices);
+});
+
+export const selectInstacelessUnselectedDevices = memoize((state: DeviceRootState) => {
+ const device = selectDevice(state);
+ const allDevices = selectDevices(state);
+
+ return deviceUtils.getSortedDevicesWithoutInstances(allDevices, device?.id);
+});
diff --git a/suite-native/atoms/src/TextDivider.tsx b/suite-native/atoms/src/TextDivider.tsx
index fc66e300bd6..67f10ee00f9 100644
--- a/suite-native/atoms/src/TextDivider.tsx
+++ b/suite-native/atoms/src/TextDivider.tsx
@@ -12,16 +12,17 @@ type TextDividerProps = {
const separatorStyle = prepareNativeStyle<{ horizontalMargin?: number }>(
(utils, { horizontalMargin }) => ({
- borderColor: utils.colors.borderElevation0,
- borderWidth: utils.borders.widths.small,
+ backgroundColor: utils.colors.borderElevation0,
+ height: utils.borders.widths.small,
flex: 1,
// We want the separator to be full width, but we need to offset it by the parent padding
marginHorizontal: typeof horizontalMargin === 'number' ? -horizontalMargin : 0,
}),
);
-const separatorTitleStyle = prepareNativeStyle(_ => ({
+const separatorTitleStyle = prepareNativeStyle(utils => ({
paddingHorizontal: 12,
+ paddingVertical: utils.spacings.extraSmall,
}));
export const TextDivider = ({ title, horizontalMargin = 0 }: TextDividerProps) => {
@@ -31,7 +32,7 @@ export const TextDivider = ({ title, horizontalMargin = 0 }: TextDividerProps) =
-
+
diff --git a/suite-native/device-manager/package.json b/suite-native/device-manager/package.json
index 44bacbeabc3..10db41e4701 100644
--- a/suite-native/device-manager/package.json
+++ b/suite-native/device-manager/package.json
@@ -15,6 +15,7 @@
"@reduxjs/toolkit": "1.9.5",
"@suite-common/icons": "workspace:*",
"@suite-common/suite-types": "workspace:*",
+ "@suite-common/suite-utils": "workspace:*",
"@suite-common/wallet-core": "workspace:*",
"@suite-native/analytics": "workspace:*",
"@suite-native/atoms": "workspace:*",
diff --git a/suite-native/device-manager/src/components/AddHiddenWalletButton.tsx b/suite-native/device-manager/src/components/AddHiddenWalletButton.tsx
index 920818253c8..5b1ff8d54eb 100644
--- a/suite-native/device-manager/src/components/AddHiddenWalletButton.tsx
+++ b/suite-native/device-manager/src/components/AddHiddenWalletButton.tsx
@@ -1,12 +1,20 @@
import { useDispatch, useSelector } from 'react-redux';
-import { Button } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';
import { createDeviceInstance, selectDevice } from '@suite-common/wallet-core';
+import { Text } from '@suite-native/atoms';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { Icon } from '@suite-common/icons';
import { useDeviceManager } from '../hooks/useDeviceManager';
+import { DeviceAction } from './DeviceAction';
+
+const textStyle = prepareNativeStyle(_ => ({
+ flex: 1,
+}));
export const AddHiddenWalletButton = () => {
+ const { applyStyle } = useNativeStyles();
const dispatch = useDispatch();
const device = useSelector(selectDevice);
@@ -21,8 +29,15 @@ export const AddHiddenWalletButton = () => {
};
return (
-
+
+
+
+
+
+
);
};
diff --git a/suite-native/device-manager/src/components/DeviceAction.tsx b/suite-native/device-manager/src/components/DeviceAction.tsx
new file mode 100644
index 00000000000..e97c45d8e4d
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceAction.tsx
@@ -0,0 +1,60 @@
+import { ReactNode } from 'react';
+import { Pressable } from 'react-native';
+
+import { HStack } from '@suite-native/atoms';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+
+type DeviceActionProps = {
+ testID: string;
+ onPress: () => void;
+ children: ReactNode;
+ flex?: number;
+ showAsFullWidth?: boolean;
+};
+
+const contentStyle = prepareNativeStyle(utils => ({
+ paddingHorizontal: utils.spacings.medium,
+ paddingVertical: 12,
+ alignItems: 'center',
+ height: 44,
+ gap: utils.spacings.small,
+ backgroundColor: utils.colors.backgroundSurfaceElevation1,
+ borderWidth: utils.borders.widths.small,
+ borderRadius: 12,
+ borderColor: utils.colors.borderElevation1,
+}));
+
+const pressableStyle = prepareNativeStyle<{ showAsFullWidth: boolean; flex: number | undefined }>(
+ (_, { showAsFullWidth, flex }) => {
+ return {
+ flex,
+ extend: {
+ condition: showAsFullWidth,
+ style: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ },
+ };
+ },
+);
+
+export const DeviceAction = ({
+ testID,
+ onPress,
+ children,
+ flex,
+ showAsFullWidth = false,
+}: DeviceActionProps) => {
+ const { applyStyle } = useNativeStyles();
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceControlButtons.tsx b/suite-native/device-manager/src/components/DeviceControlButtons.tsx
deleted file mode 100644
index df8bf131939..00000000000
--- a/suite-native/device-manager/src/components/DeviceControlButtons.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useDispatch, useSelector } from 'react-redux';
-
-import { useNavigation } from '@react-navigation/native';
-
-import { analytics, EventType } from '@suite-native/analytics';
-import { Box, Button, HStack } from '@suite-native/atoms';
-import { deviceActions, selectDevice } from '@suite-common/wallet-core';
-import { Translation } from '@suite-native/intl';
-import {
- RootStackParamList,
- RootStackRoutes,
- StackToStackCompositeNavigationProps,
-} from '@suite-native/navigation';
-
-import { useDeviceManager } from '../hooks/useDeviceManager';
-
-type NavigationProp = StackToStackCompositeNavigationProps<
- RootStackParamList,
- RootStackRoutes.AppTabs,
- RootStackParamList
->;
-
-export const DeviceControlButtons = () => {
- const selectedDevice = useSelector(selectDevice);
-
- const { setIsDeviceManagerVisible } = useDeviceManager();
-
- const navigation = useNavigation();
- const dispatch = useDispatch();
-
- if (!selectedDevice) return null;
-
- const handleEject = () => {
- setIsDeviceManagerVisible(false);
- dispatch(deviceActions.deviceDisconnect(selectedDevice));
- analytics.report({
- type: EventType.EjectDeviceClick,
- payload: { origin: 'deviceManager' },
- });
- };
-
- const handleDeviceRedirect = () => {
- setIsDeviceManagerVisible(false);
- navigation.navigate(RootStackRoutes.DeviceInfo);
- analytics.report({ type: EventType.DeviceManagerClick, payload: { action: 'deviceInfo' } });
- };
-
- return (
-
-
-
-
-
-
-
-
- );
-};
diff --git a/suite-native/device-manager/src/components/DeviceInfoButton.tsx b/suite-native/device-manager/src/components/DeviceInfoButton.tsx
new file mode 100644
index 00000000000..b449994c3e3
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceInfoButton.tsx
@@ -0,0 +1,68 @@
+import { useSelector } from 'react-redux';
+
+import { useNavigation } from '@react-navigation/native';
+
+import { analytics, EventType } from '@suite-native/analytics';
+import { HStack, Text } from '@suite-native/atoms';
+import { selectDevice } from '@suite-common/wallet-core';
+import { Translation } from '@suite-native/intl';
+import {
+ RootStackParamList,
+ RootStackRoutes,
+ StackToStackCompositeNavigationProps,
+} from '@suite-native/navigation';
+import { Icon } from '@suite-common/icons';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+
+import { useDeviceManager } from '../hooks/useDeviceManager';
+import { DeviceAction } from './DeviceAction';
+
+type NavigationProp = StackToStackCompositeNavigationProps<
+ RootStackParamList,
+ RootStackRoutes.AppTabs,
+ RootStackParamList
+>;
+
+type DeviceInfoButtonProps = {
+ showAsFullWidth: boolean;
+};
+
+const contentStyle = prepareNativeStyle<{ showAsFullWidth: boolean }>((_, { showAsFullWidth }) => ({
+ extend: {
+ condition: showAsFullWidth,
+ style: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ },
+}));
+
+export const DeviceInfoButton = ({ showAsFullWidth }: DeviceInfoButtonProps) => {
+ const { applyStyle } = useNativeStyles();
+ const navigation = useNavigation();
+ const { setIsDeviceManagerVisible } = useDeviceManager();
+ const selectedDevice = useSelector(selectDevice);
+
+ const handleDeviceRedirect = () => {
+ setIsDeviceManagerVisible(false);
+ navigation.navigate(RootStackRoutes.DeviceInfo);
+ analytics.report({ type: EventType.DeviceManagerClick, payload: { action: 'deviceInfo' } });
+ };
+
+ if (!selectedDevice) return null;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem.tsx b/suite-native/device-manager/src/components/DeviceItem.tsx
deleted file mode 100644
index 1225293b18e..00000000000
--- a/suite-native/device-manager/src/components/DeviceItem.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useDispatch, useSelector } from 'react-redux';
-import { Pressable } from 'react-native';
-
-import { analytics, EventType } from '@suite-native/analytics';
-import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
-import { HStack } from '@suite-native/atoms';
-import {
- DeviceRootState,
- PORTFOLIO_TRACKER_DEVICE_ID,
- selectDeviceById,
- selectDeviceId,
- selectDeviceThunk,
-} from '@suite-common/wallet-core';
-import { Icon } from '@suite-common/icons';
-import { TrezorDevice } from '@suite-common/suite-types';
-
-import { useDeviceManager } from '../hooks/useDeviceManager';
-import { DeviceItemContent } from './DeviceItemContent';
-
-type DeviceItemProps = {
- id: TrezorDevice['id'];
-};
-
-const deviceItemWrapperStyle = prepareNativeStyle(utils => ({
- justifyContent: 'space-between',
- alignItems: 'center',
- borderRadius: utils.borders.radii.medium,
- backgroundColor: utils.colors.backgroundTertiaryDefaultOnElevation1,
- paddingHorizontal: utils.spacings.medium,
- paddingVertical: 12,
-}));
-
-export const DeviceItem = ({ id: deviceItemId }: DeviceItemProps) => {
- const dispatch = useDispatch();
- const { applyStyle } = useNativeStyles();
-
- const selectedDeviceId = useSelector(selectDeviceId);
- const device = useSelector((state: DeviceRootState) => selectDeviceById(state, deviceItemId));
-
- const { setIsDeviceManagerVisible } = useDeviceManager();
-
- const handleSelectDevice = () => {
- setIsDeviceManagerVisible(false);
-
- if (deviceItemId === selectedDeviceId) return;
-
- dispatch(selectDeviceThunk(device));
-
- analytics.report({
- type: EventType.DeviceManagerClick,
- payload: {
- action:
- deviceItemId === PORTFOLIO_TRACKER_DEVICE_ID
- ? 'portfolioTracker'
- : 'connectDeviceButton',
- },
- });
- };
-
- return (
-
-
-
-
-
-
- );
-};
diff --git a/suite-native/device-manager/src/components/DeviceItem/ConnectionDot.tsx b/suite-native/device-manager/src/components/DeviceItem/ConnectionDot.tsx
new file mode 100644
index 00000000000..488fdc1376f
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/ConnectionDot.tsx
@@ -0,0 +1,26 @@
+import { View } from 'react-native';
+
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+
+type ConnectionDotProps = {
+ isConnected: boolean;
+};
+
+const dotStyle = prepareNativeStyle<{ isConnected: boolean }>((utils, { isConnected }) => ({
+ width: utils.spacings.small,
+ height: utils.spacings.small,
+ borderRadius: utils.borders.radii.round,
+ backgroundColor: utils.colors.textSubdued,
+ extend: {
+ condition: isConnected,
+ style: {
+ backgroundColor: utils.colors.textSecondaryHighlight,
+ },
+ },
+}));
+
+export const ConnectionDot = ({ isConnected }: ConnectionDotProps) => {
+ const { applyStyle } = useNativeStyles();
+
+ return ;
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem/DeviceItem.tsx b/suite-native/device-manager/src/components/DeviceItem/DeviceItem.tsx
new file mode 100644
index 00000000000..6d956f109cf
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/DeviceItem.tsx
@@ -0,0 +1,33 @@
+import { Pressable } from 'react-native';
+
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { HStack } from '@suite-native/atoms';
+import { Icon } from '@suite-common/icons';
+import { TrezorDevice } from '@suite-common/suite-types';
+
+import { DeviceItemContent } from './DeviceItemContent';
+
+type DeviceItemProps = {
+ deviceState: NonNullable;
+ onPress: () => void;
+};
+
+const deviceItemWrapperStyle = prepareNativeStyle(utils => ({
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 10,
+ paddingHorizontal: utils.spacings.medium,
+}));
+
+export const DeviceItem = ({ deviceState, onPress }: DeviceItemProps) => {
+ const { applyStyle } = useNativeStyles();
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem/DeviceItemContent.tsx b/suite-native/device-manager/src/components/DeviceItem/DeviceItemContent.tsx
new file mode 100644
index 00000000000..b8a2f8baee1
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/DeviceItemContent.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+import { HStack, Box } from '@suite-native/atoms';
+import { Translation, useTranslate } from '@suite-native/intl';
+import {
+ selectDeviceByState,
+ DeviceRootState,
+ PORTFOLIO_TRACKER_DEVICE_ID,
+ selectAreAllDevicesDisconnectedOrAccountless,
+} from '@suite-common/wallet-core';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { TypographyStyle } from '@trezor/theme';
+import { TrezorDevice } from '@suite-common/suite-types';
+
+import { DeviceItemIcon } from './DeviceItemIcon';
+import { SimpleDeviceItemContent } from './SimpleDeviceItemContent';
+import { WalletDetailDeviceItemContent } from './WalletDetailDeviceItemContent';
+
+export type DeviceItemContentVariant = 'simple' | 'walletDetail';
+export type DeviceItemContentMode = 'compact' | 'header';
+
+export type DeviceItemContentProps = {
+ deviceState: NonNullable;
+ isPortfolioLabelDisplayed?: boolean;
+ headerTextVariant?: TypographyStyle;
+ variant?: DeviceItemContentVariant;
+ isCompact?: boolean;
+};
+
+const contentWrapperStyle = prepareNativeStyle<{ height: number }>((utils, { height }) => ({
+ flexShrink: 1,
+ height,
+ alignItems: 'center',
+ spacing: utils.spacings.medium,
+}));
+const itemStyle = prepareNativeStyle(_ => ({
+ flexShrink: 1,
+}));
+
+export const DeviceItemContent = ({
+ deviceState,
+ isPortfolioLabelDisplayed = true,
+ headerTextVariant = 'body',
+ variant = 'simple',
+ isCompact = true,
+}: DeviceItemContentProps) => {
+ const { translate } = useTranslate();
+ const { applyStyle } = useNativeStyles();
+
+ const device = useSelector((state: DeviceRootState) => selectDeviceByState(state, deviceState));
+ const areAllDevicesDisconnectedOrAccountless = useSelector(
+ selectAreAllDevicesDisconnectedOrAccountless,
+ );
+
+ const isPortfolioTrackerDevice = device?.id === PORTFOLIO_TRACKER_DEVICE_ID;
+
+ const deviceHeader =
+ (isPortfolioTrackerDevice ? device?.name : device?.label) ??
+ translate('deviceManager.defaultHeader');
+
+ const walletNameLabel = device?.useEmptyPassphrase ? (
+
+ ) : (
+
+ );
+
+ if (!device) {
+ return null;
+ }
+
+ return (
+
+
+
+ {variant === 'simple' ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem/DeviceItemIcon.tsx b/suite-native/device-manager/src/components/DeviceItem/DeviceItemIcon.tsx
new file mode 100644
index 00000000000..09f85890ab3
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/DeviceItemIcon.tsx
@@ -0,0 +1,31 @@
+import { useSelector } from 'react-redux';
+
+import { Icon, IconSize } from '@suite-common/icons';
+import {
+ DeviceRootState,
+ PORTFOLIO_TRACKER_DEVICE_ID,
+ selectDeviceModelById,
+} from '@suite-common/wallet-core';
+import { TrezorDevice } from '@suite-common/suite-types';
+
+import { DeviceModelIcon } from '../DeviceModelIcon';
+
+type DeviceItemIconProps = {
+ deviceId: TrezorDevice['id'];
+ iconSize: IconSize;
+};
+
+export const DeviceItemIcon = ({ deviceId, iconSize }: DeviceItemIconProps) => {
+ const deviceModel = useSelector((state: DeviceRootState) =>
+ selectDeviceModelById(state, deviceId),
+ );
+
+ if (deviceId === PORTFOLIO_TRACKER_DEVICE_ID) {
+ return ;
+ }
+ if (deviceModel !== null) {
+ return ;
+ }
+
+ return ;
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem/SimpleDeviceItemContent.tsx b/suite-native/device-manager/src/components/DeviceItem/SimpleDeviceItemContent.tsx
new file mode 100644
index 00000000000..6a9e6d11e0a
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/SimpleDeviceItemContent.tsx
@@ -0,0 +1,95 @@
+import { useSelector } from 'react-redux';
+import { ReactNode } from 'react';
+
+import { HStack, Text, Box } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import {
+ DeviceRootState,
+ selectAreAllDevicesDisconnectedOrAccountless,
+ selectDeviceByState,
+} from '@suite-common/wallet-core';
+import { TypographyStyle } from '@trezor/theme';
+import { TrezorDevice } from '@suite-common/suite-types';
+
+import { ConnectionDot } from './ConnectionDot';
+
+export type SimpleDeviceItemContentProps = {
+ deviceState: NonNullable;
+ headerTextVariant?: TypographyStyle;
+ header: ReactNode;
+ isPortfolioLabelDisplayed?: boolean;
+ isPortfolioTrackerDevice: boolean;
+};
+
+const headerStyle = prepareNativeStyle(_ => ({
+ flexShrink: 1,
+ overflow: 'visible',
+}));
+
+export const SimpleDeviceItemContent = ({
+ deviceState,
+ headerTextVariant,
+ isPortfolioLabelDisplayed,
+ header,
+ isPortfolioTrackerDevice,
+}: SimpleDeviceItemContentProps) => {
+ const { applyStyle } = useNativeStyles();
+ const device = useSelector((state: DeviceRootState) => selectDeviceByState(state, deviceState));
+ const areAllDevicesDisconnectedOrAccountless = useSelector(
+ selectAreAllDevicesDisconnectedOrAccountless,
+ );
+
+ if (!device) {
+ return null;
+ }
+
+ const isPortfolioTrackerSubHeaderVisible =
+ isPortfolioTrackerDevice &&
+ isPortfolioLabelDisplayed &&
+ !areAllDevicesDisconnectedOrAccountless;
+
+ const isConnectionStateVisible =
+ !isPortfolioTrackerDevice && !areAllDevicesDisconnectedOrAccountless;
+
+ return (
+ <>
+
+ {areAllDevicesDisconnectedOrAccountless ? (
+
+ ) : (
+ header
+ )}
+
+
+ {isPortfolioTrackerSubHeaderVisible && (
+
+
+
+ )}
+ {isConnectionStateVisible && (
+
+
+
+
+
+
+ )}
+
+ >
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceItem/WalletDetailDeviceItemContent.tsx b/suite-native/device-manager/src/components/DeviceItem/WalletDetailDeviceItemContent.tsx
new file mode 100644
index 00000000000..8d05051753f
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceItem/WalletDetailDeviceItemContent.tsx
@@ -0,0 +1,77 @@
+import { useSelector } from 'react-redux';
+import { ReactNode } from 'react';
+
+import { HStack, Text, Box } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { DeviceRootState, selectDeviceByState } from '@suite-common/wallet-core';
+import { TypographyStyle } from '@trezor/theme';
+import { TrezorDevice } from '@suite-common/suite-types/src/device';
+
+import { ConnectionDot } from './ConnectionDot';
+
+export type WalletDetailDeviceItemContentProps = {
+ deviceState: NonNullable;
+ headerTextVariant?: TypographyStyle;
+ header: ReactNode;
+ subHeader?: ReactNode;
+ isPortfolioLabelDisplayed?: boolean;
+ isPortfolioTrackerDevice: boolean;
+};
+
+const headerStyle = prepareNativeStyle(utils => ({
+ flexShrink: 1,
+ paddingRight: utils.spacings.small,
+ alignItems: 'center',
+ gap: utils.spacings.small,
+}));
+
+const headerTextStyle = prepareNativeStyle(() => ({
+ flexShrink: 1,
+}));
+
+export const WalletDetailDeviceItemContent = ({
+ deviceState,
+ headerTextVariant,
+ isPortfolioLabelDisplayed,
+ header,
+ subHeader,
+ isPortfolioTrackerDevice,
+}: WalletDetailDeviceItemContentProps) => {
+ const { applyStyle } = useNativeStyles();
+ const device = useSelector((state: DeviceRootState) => selectDeviceByState(state, deviceState));
+
+ if (!device) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+ {header}
+
+ {!isPortfolioTrackerDevice && }
+
+
+ {isPortfolioTrackerDevice && isPortfolioLabelDisplayed && (
+
+
+
+ )}
+ {!isPortfolioTrackerDevice && (
+
+
+ {subHeader}
+
+
+ )}
+
+ >
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceItemContent.tsx b/suite-native/device-manager/src/components/DeviceItemContent.tsx
deleted file mode 100644
index 4f3bb14bb85..00000000000
--- a/suite-native/device-manager/src/components/DeviceItemContent.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import { useSelector } from 'react-redux';
-
-import { Icon, IconName } from '@suite-common/icons';
-import { HStack, Box, Text } from '@suite-native/atoms';
-import { Translation } from '@suite-native/intl';
-import {
- selectDeviceNameById,
- DeviceRootState,
- PORTFOLIO_TRACKER_DEVICE_ID,
- selectDeviceLabelById,
-} from '@suite-common/wallet-core';
-import { TrezorDevice } from '@suite-common/suite-types';
-import { TypographyStyle } from '@trezor/theme';
-import { useActiveColorScheme } from '@suite-native/theme';
-
-type DeviceItemContentProps = {
- deviceId?: TrezorDevice['id'];
- isPortfolioLabelDisplayed?: boolean;
- headerTextVariant?: TypographyStyle;
-};
-
-type DeviceItemIconProps = Pick;
-
-const DeviceItemIcon = ({ deviceId }: DeviceItemIconProps) => {
- const activeColorScheme = useActiveColorScheme();
-
- // TODO: when we enable remember mode, icon representing disconnected device have to be handled.
- const connectedDeviceIcon: IconName =
- activeColorScheme === 'standard' ? 'trezorConnectedLight' : 'trezorConnectedDark';
-
- switch (deviceId) {
- case undefined:
- return ;
- case PORTFOLIO_TRACKER_DEVICE_ID:
- return ;
- default:
- return ;
- }
-};
-
-export const DeviceItemContent = ({
- deviceId,
- isPortfolioLabelDisplayed = true,
- headerTextVariant = 'body',
-}: DeviceItemContentProps) => {
- const deviceName = useSelector((state: DeviceRootState) =>
- selectDeviceNameById(state, deviceId),
- );
- const deviceLabel = useSelector((state: DeviceRootState) =>
- selectDeviceLabelById(state, deviceId),
- );
-
- const isPortfolioTrackerDevice = deviceId === PORTFOLIO_TRACKER_DEVICE_ID;
-
- const deviceHeader = (isPortfolioTrackerDevice ? deviceName : deviceLabel) ?? (
-
- );
-
- return (
-
-
-
- {deviceHeader}
- {deviceId && (
-
- {isPortfolioTrackerDevice ? (
- isPortfolioLabelDisplayed && (
-
-
-
- )
- ) : (
- // TODO: when we enable remember mode, grey 'Disconnected' label has to be displayed.
-
-
-
- )}
-
- )}
-
-
- );
-};
diff --git a/suite-native/device-manager/src/components/DeviceList.tsx b/suite-native/device-manager/src/components/DeviceList.tsx
new file mode 100644
index 00000000000..54ee40aeda7
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceList.tsx
@@ -0,0 +1,181 @@
+import { useSelector } from 'react-redux';
+import Animated, {
+ runOnJS,
+ useAnimatedStyle,
+ useSharedValue,
+ withDelay,
+ withTiming,
+} from 'react-native-reanimated';
+import { useEffect, useState } from 'react';
+
+import { useNavigation } from '@react-navigation/native';
+import { A } from '@mobily/ts-belt';
+
+import {
+ ConnectDeviceStackRoutes,
+ RootStackParamList,
+ RootStackRoutes,
+ StackToStackCompositeNavigationProps,
+} from '@suite-native/navigation';
+import { analytics, EventType } from '@suite-native/analytics';
+import { selectDevice, selectInstacelessUnselectedDevices } from '@suite-common/wallet-core';
+import { Button, VStack, Box, TextDivider } from '@suite-native/atoms';
+import { TrezorDevice } from '@suite-common/suite-types';
+import { Translation } from '@suite-native/intl';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+
+import { DeviceItem } from './DeviceItem/DeviceItem';
+import { useDeviceManager } from '../hooks/useDeviceManager';
+import { MANAGER_MODAL_BOTTOM_RADIUS } from './DeviceManagerModal';
+
+type NavigationProp = StackToStackCompositeNavigationProps<
+ RootStackParamList,
+ RootStackRoutes.AppTabs,
+ RootStackParamList
+>;
+
+type DeviceListProps = {
+ isVisible: boolean;
+ onSelectDevice: (device: TrezorDevice) => void;
+};
+
+const ITEM_HEIGHT = 66;
+const BUTTON_HEIGHT = 48;
+const VERTICAL_PADDING = 16;
+const SEPARATOR_VERTICAL_PADDING = 4;
+const SEPARATOR_HEIGHT = 26;
+const TOP_LIST_PADDING = 12;
+
+const buttonWrapperStyle = prepareNativeStyle(utils => ({
+ paddingHorizontal: utils.spacings.medium,
+}));
+
+const listStaticStyle = prepareNativeStyle(utils => ({
+ backgroundColor: utils.colors.backgroundSurfaceElevation1,
+ borderBottomLeftRadius: MANAGER_MODAL_BOTTOM_RADIUS,
+ borderBottomRightRadius: MANAGER_MODAL_BOTTOM_RADIUS,
+ borderWidth: utils.borders.widths.small,
+ borderColor: utils.colors.borderElevation0,
+ marginTop: -MANAGER_MODAL_BOTTOM_RADIUS,
+ marginBottom: -MANAGER_MODAL_BOTTOM_RADIUS,
+ paddingTop: MANAGER_MODAL_BOTTOM_RADIUS + TOP_LIST_PADDING,
+ paddingBottom: VERTICAL_PADDING,
+ zIndex: 10,
+ ...utils.boxShadows.small,
+}));
+
+export const DeviceList = ({ isVisible, onSelectDevice }: DeviceListProps) => {
+ const { applyStyle, utils } = useNativeStyles();
+ const navigation = useNavigation();
+ const { setIsDeviceManagerVisible } = useDeviceManager();
+ const device = useSelector(selectDevice);
+ const notSelectedInstancelessDevices = useSelector(selectInstacelessUnselectedDevices);
+ const opacity = useSharedValue(0);
+ const height = useSharedValue(0);
+ const [isShown, setIsShown] = useState(false);
+
+ const handleConnectDevice = () => {
+ if (device) {
+ onSelectDevice(device);
+ }
+ setIsDeviceManagerVisible(false);
+ navigation.navigate(RootStackRoutes.ConnectDeviceStack, {
+ screen: ConnectDeviceStackRoutes.ConnectAndUnlockDevice,
+ });
+ analytics.report({
+ type: EventType.DeviceManagerClick,
+ payload: { action: 'connectDeviceButton' },
+ });
+ };
+
+ //to hide/show the list with animation
+ useEffect(() => {
+ if (isVisible) {
+ const hasNotSelectedInstancelessDevices = notSelectedInstancelessDevices.length > 0;
+
+ const paddingsHeight = VERTICAL_PADDING * 2;
+
+ const otherDevicesHeight = notSelectedInstancelessDevices.length * ITEM_HEIGHT;
+
+ const separatorHeight = hasNotSelectedInstancelessDevices ? SEPARATOR_HEIGHT : 0;
+
+ const separatorPaddding = hasNotSelectedInstancelessDevices
+ ? SEPARATOR_VERTICAL_PADDING * 2
+ : 0;
+
+ const radiusesHeight = MANAGER_MODAL_BOTTOM_RADIUS * 2;
+
+ const h =
+ otherDevicesHeight +
+ radiusesHeight +
+ separatorHeight +
+ separatorPaddding +
+ BUTTON_HEIGHT +
+ paddingsHeight;
+
+ opacity.value = withDelay(100, withTiming(1, { duration: 200 }));
+ height.value = withTiming(h, { duration: 300 });
+
+ setIsShown(true);
+ } else {
+ opacity.value = withTiming(0, { duration: 300 });
+ height.value = withDelay(
+ 100,
+ withTiming(0, { duration: 200 }, () => {
+ runOnJS(setIsShown)(false);
+ }),
+ );
+ }
+ }, [height, isVisible, opacity, notSelectedInstancelessDevices.length, utils.spacings.small]);
+
+ const listAnimatedStyle = useAnimatedStyle(
+ () => ({
+ opacity: opacity.value,
+ height: height.value,
+ }),
+ [],
+ );
+
+ if (!isShown) {
+ return null;
+ }
+
+ return (
+
+
+
+ {notSelectedInstancelessDevices.map(d => {
+ if (d.state === undefined) {
+ return null;
+ }
+
+ return (
+ onSelectDevice(d)}
+ />
+ );
+ })}
+
+
+ {A.isEmpty(notSelectedInstancelessDevices) ? (
+
+
+
+ ) : (
+ <>
+
+
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/DeviceManagerContent.tsx b/suite-native/device-manager/src/components/DeviceManagerContent.tsx
index aa62db94171..ffd23bfe920 100644
--- a/suite-native/device-manager/src/components/DeviceManagerContent.tsx
+++ b/suite-native/device-manager/src/components/DeviceManagerContent.tsx
@@ -1,90 +1,98 @@
-import { useSelector } from 'react-redux';
-import { useMemo } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useState } from 'react';
-import { A } from '@mobily/ts-belt';
-import { useNavigation } from '@react-navigation/native';
-
-import { analytics, EventType } from '@suite-native/analytics';
-import { Button, Text, VStack } from '@suite-native/atoms';
+import { HStack, VStack } from '@suite-native/atoms';
import {
- selectDevices,
+ PORTFOLIO_TRACKER_DEVICE_ID,
+ selectDevice,
+ selectDeviceThunk,
+ selectIsDeviceDiscoveryActive,
selectIsPortfolioTrackerDevice,
- selectDeviceId,
- selectIsNoPhysicalDeviceConnected,
} from '@suite-common/wallet-core';
-import {
- ConnectDeviceStackRoutes,
- RootStackParamList,
- RootStackRoutes,
- StackToStackCompositeNavigationProps,
-} from '@suite-native/navigation';
-import { Translation } from '@suite-native/intl';
import { FeatureFlag, useFeatureFlag } from '@suite-native/feature-flags';
+import { EventType, analytics } from '@suite-native/analytics';
+import { TrezorDevice } from '@suite-common/suite-types';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { AddHiddenWalletButton } from './AddHiddenWalletButton';
+import { DeviceList } from './DeviceList';
+import { DeviceInfoButton } from './DeviceInfoButton';
import { DeviceManagerModal } from './DeviceManagerModal';
-import { DeviceItem } from './DeviceItem';
-import { DeviceControlButtons } from './DeviceControlButtons';
+import { DevicesToggleButton } from './DevicesToggleButton';
+import { WalletList } from './WalletList';
import { useDeviceManager } from '../hooks/useDeviceManager';
-import { AddHiddenWalletButton } from './AddHiddenWalletButton';
-type NavigationProp = StackToStackCompositeNavigationProps<
- RootStackParamList,
- RootStackRoutes.AppTabs,
- RootStackParamList
->;
+const deviceButtonsStyle = prepareNativeStyle(utils => ({
+ width: '100%',
+ paddingHorizontal: utils.spacings.medium,
+ paddingBottom: utils.spacings.medium,
+}));
export const DeviceManagerContent = () => {
- const navigation = useNavigation();
+ const { applyStyle } = useNativeStyles();
+ const [isChangeDeviceRequested, setIsChangeDeviceRequested] = useState(false);
- const devices = useSelector(selectDevices);
- const selectedDeviceId = useSelector(selectDeviceId);
const isPortfolioTrackerDevice = useSelector(selectIsPortfolioTrackerDevice);
- const isNoPhysicalDeviceConnected = useSelector(selectIsNoPhysicalDeviceConnected);
+ const [isPassphraseFeatureEnabled] = useFeatureFlag(FeatureFlag.IsPassphraseEnabled);
+ const isDiscoveryActive = useSelector(selectIsDeviceDiscoveryActive);
+ const device = useSelector(selectDevice);
const { setIsDeviceManagerVisible } = useDeviceManager();
- const [isPassphraseFeatureEnabled] = useFeatureFlag(FeatureFlag.IsPassphraseEnabled);
+ const toggleIsChangeDeviceRequested = () =>
+ setIsChangeDeviceRequested(!isChangeDeviceRequested);
+ const dispatch = useDispatch();
- const handleConnectDevice = () => {
+ const handleSelectDevice = (selectedDevice: TrezorDevice) => {
+ dispatch(selectDeviceThunk(selectedDevice));
+ setIsChangeDeviceRequested(false);
setIsDeviceManagerVisible(false);
- navigation.navigate(RootStackRoutes.ConnectDeviceStack, {
- screen: ConnectDeviceStackRoutes.ConnectAndUnlockDevice,
- });
+
analytics.report({
type: EventType.DeviceManagerClick,
- payload: { action: 'connectDeviceButton' },
+ payload: {
+ action:
+ selectedDevice.id === PORTFOLIO_TRACKER_DEVICE_ID
+ ? 'portfolioTracker'
+ : 'connectDeviceButton',
+ },
});
};
- const listedDevice = useMemo(
- () => devices.filter(device => device.id !== selectedDeviceId),
- [devices, selectedDeviceId],
- );
+ if (!device) {
+ return null;
+ }
+
+ const isAddHiddenWalletButtonVisible =
+ isPassphraseFeatureEnabled && !isDiscoveryActive && device?.connected;
return (
-
- {!isPortfolioTrackerDevice && }
- {!isPortfolioTrackerDevice && isPassphraseFeatureEnabled && }
- {A.isNotEmpty(listedDevice) && (
-
-
-
-
- {listedDevice.map(device => (
-
- ))}
-
- )}
- {isNoPhysicalDeviceConnected && (
-
-
-
-
-
-
- )}
+
+ )
+ }
+ onClose={() => setIsChangeDeviceRequested(false)}
+ >
+
+
+ {!isPortfolioTrackerDevice && (
+ <>
+
+
+
+ {isAddHiddenWalletButtonVisible && }
+
+ >
+ )}
+
);
};
diff --git a/suite-native/device-manager/src/components/DeviceManagerModal.tsx b/suite-native/device-manager/src/components/DeviceManagerModal.tsx
index c1aa581c68f..63ccd01bb49 100644
--- a/suite-native/device-manager/src/components/DeviceManagerModal.tsx
+++ b/suite-native/device-manager/src/components/DeviceManagerModal.tsx
@@ -1,47 +1,62 @@
import { GestureResponderEvent, Modal, Pressable } from 'react-native';
import { ReactNode } from 'react';
import { useSafeAreaInsets, EdgeInsets } from 'react-native-safe-area-context';
-import Animated, { SlideInUp } from 'react-native-reanimated';
+import Animated, { FadeIn, SlideInUp } from 'react-native-reanimated';
+import { useSelector } from 'react-redux';
-import { ScreenHeaderWrapper, VStack } from '@suite-native/atoms';
+import { ScreenHeaderWrapper, Box, HStack } from '@suite-native/atoms';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { selectDeviceState } from '@suite-common/wallet-core';
-import { DeviceSwitch } from './DeviceSwitch';
import { useDeviceManager } from '../hooks/useDeviceManager';
+import { DeviceItemContent } from './DeviceItem/DeviceItemContent';
type DeviceManagerModalProps = {
children: ReactNode;
+ customSwitchRightView?: ReactNode;
+ onClose?: () => void;
};
-const MANAGER_MODAL_BOTTOM_RADIUS = 20;
+export const MANAGER_MODAL_BOTTOM_RADIUS = 12;
const modalBackgroundOverlayStyle = prepareNativeStyle(utils => ({
flex: 1,
backgroundColor: utils.transparentize(0.3, utils.colors.backgroundNeutralBold),
}));
-const deviceManagerModalWrapperStyle = prepareNativeStyle<{ insets: EdgeInsets }>(
+const deviceManagerModalWrapperStyle = prepareNativeStyle(utils => ({
+ backgroundColor: utils.colors.backgroundSurfaceElevation0,
+ borderBottomLeftRadius: MANAGER_MODAL_BOTTOM_RADIUS,
+ borderBottomRightRadius: MANAGER_MODAL_BOTTOM_RADIUS,
+}));
+
+const deviceSwitchWrapperStyle = prepareNativeStyle<{ insets: EdgeInsets }>(
(utils, { insets }) => ({
- paddingTop: Math.max(insets.top, utils.spacings.small),
+ paddingTop: insets.top + utils.spacings.large,
backgroundColor: utils.colors.backgroundSurfaceElevation1,
borderBottomLeftRadius: MANAGER_MODAL_BOTTOM_RADIUS,
borderBottomRightRadius: MANAGER_MODAL_BOTTOM_RADIUS,
+ borderWidth: utils.borders.widths.small,
+ borderColor: utils.colors.borderElevation0,
+ zIndex: 20,
+ ...utils.boxShadows.small,
}),
);
-const contentWrapperStyle = prepareNativeStyle(utils => ({
- paddingHorizontal: utils.spacings.medium,
- paddingBottom: utils.spacings.medium,
-}));
-
-export const DeviceManagerModal = ({ children }: DeviceManagerModalProps) => {
+export const DeviceManagerModal = ({
+ children,
+ customSwitchRightView,
+ onClose,
+}: DeviceManagerModalProps) => {
const { applyStyle } = useNativeStyles();
+ const deviceState = useSelector(selectDeviceState);
const insets = useSafeAreaInsets();
const { setIsDeviceManagerVisible, isDeviceManagerVisible } = useDeviceManager();
const handleClose = () => {
+ onClose?.();
setIsDeviceManagerVisible(false);
};
@@ -56,18 +71,36 @@ export const DeviceManagerModal = ({ children }: DeviceManagerModalProps) => {
visible={isDeviceManagerVisible}
presentationStyle="overFullScreen"
animationType="fade"
+ statusBarTranslucent={true}
>
-
-
-
-
- {children}
-
+
+
+
+
+ {deviceState && (
+
+ )}
+
+ {customSwitchRightView}
+
+
+
+ {children}
diff --git a/suite-native/device-manager/src/components/DeviceModelIcon.tsx b/suite-native/device-manager/src/components/DeviceModelIcon.tsx
new file mode 100644
index 00000000000..eb1284b5eaa
--- /dev/null
+++ b/suite-native/device-manager/src/components/DeviceModelIcon.tsx
@@ -0,0 +1,18 @@
+import { Icon, IconName, IconSize } from '@suite-common/icons';
+import { DeviceModelInternal } from '@trezor/connect';
+
+type DeviceModelIconProps = {
+ deviceModel: DeviceModelInternal;
+ size?: IconSize | number;
+};
+
+const icons = {
+ T1B1: 'trezorT1B1',
+ T2T1: 'trezorT2T1',
+ T2B1: 'trezorT2B1',
+ T3T1: 'trezorT3T1',
+} as const satisfies Record;
+
+export const DeviceModelIcon = ({ deviceModel, size }: DeviceModelIconProps) => (
+
+);
diff --git a/suite-native/device-manager/src/components/DeviceSwitch.tsx b/suite-native/device-manager/src/components/DeviceSwitch.tsx
index 38b2e2939c5..a6238104d53 100644
--- a/suite-native/device-manager/src/components/DeviceSwitch.tsx
+++ b/suite-native/device-manager/src/components/DeviceSwitch.tsx
@@ -1,17 +1,14 @@
-import { Pressable, TouchableOpacity } from 'react-native';
+import { Pressable } from 'react-native';
import { useSelector } from 'react-redux';
import { Box, HStack } from '@suite-native/atoms';
import { Icon } from '@suite-common/icons';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
-import {
- selectDeviceId,
- selectAreAllDevicesDisconnectedOrAccountless,
-} from '@suite-common/wallet-core';
+import { selectDeviceInstances, selectDeviceState } from '@suite-common/wallet-core';
import { SCREEN_HEADER_HEIGHT } from '../constants';
import { useDeviceManager } from '../hooks/useDeviceManager';
-import { DeviceItemContent } from './DeviceItemContent';
+import { DeviceItemContent } from './DeviceItem/DeviceItemContent';
type SwitchStyleProps = { isDeviceManagerVisible: boolean };
@@ -43,10 +40,8 @@ const switchWrapperStyle = prepareNativeStyle(_ => ({
export const DeviceSwitch = () => {
const { applyStyle } = useNativeStyles();
- const areAllDevicesDisconnectedOrAccountless = useSelector(
- selectAreAllDevicesDisconnectedOrAccountless,
- );
- const deviceId = useSelector(selectDeviceId);
+ const deviceState = useSelector(selectDeviceState);
+ const wallets = useSelector(selectDeviceInstances);
const { setIsDeviceManagerVisible, isDeviceManagerVisible } = useDeviceManager();
@@ -58,26 +53,16 @@ export const DeviceSwitch = () => {
-
- {!isDeviceManagerVisible && (
-
+ {deviceState && (
+ 1 ? 'walletDetail' : 'simple'}
+ />
)}
+
- {isDeviceManagerVisible && (
-
-
-
-
-
- )}
);
diff --git a/suite-native/device-manager/src/components/DevicesToggleButton.tsx b/suite-native/device-manager/src/components/DevicesToggleButton.tsx
new file mode 100644
index 00000000000..aabc81bf1ad
--- /dev/null
+++ b/suite-native/device-manager/src/components/DevicesToggleButton.tsx
@@ -0,0 +1,26 @@
+import { Button, Text } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+
+type DeviceButtonState = 'open' | 'closed';
+
+type DevicesToggleButtonProps = {
+ deviceButtonState: DeviceButtonState;
+ onDeviceButtonTap: () => void;
+};
+
+export const DevicesToggleButton = ({
+ deviceButtonState = 'closed',
+ onDeviceButtonTap,
+}: DevicesToggleButtonProps) => (
+
+);
diff --git a/suite-native/device-manager/src/components/PortfolioTrackerDeviceManagerContent.tsx b/suite-native/device-manager/src/components/PortfolioTrackerDeviceManagerContent.tsx
index e6f95ff7b01..dc894fe9da3 100644
--- a/suite-native/device-manager/src/components/PortfolioTrackerDeviceManagerContent.tsx
+++ b/suite-native/device-manager/src/components/PortfolioTrackerDeviceManagerContent.tsx
@@ -13,6 +13,7 @@ import {
} from '@suite-native/navigation';
import { selectIsDeviceDiscoveryEmpty } from '@suite-common/wallet-core';
import { useOpenLink } from '@suite-native/link';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { DeviceManagerModal } from './DeviceManagerModal';
import { useDeviceManager } from '../hooks/useDeviceManager';
@@ -23,8 +24,18 @@ type NavigationProp = StackToStackCompositeNavigationProps<
RootStackParamList
>;
+const contentStyle = prepareNativeStyle(utils => ({
+ paddingHorizontal: utils.spacings.medium,
+
+ spacing: utils.spacings.medium,
+
+ paddingBottom: utils.spacings.medium,
+ marginTop: utils.spacings.medium,
+}));
+
export const PortfolioTrackerDeviceManagerContent = () => {
const openLink = useOpenLink();
+ const { applyStyle } = useNativeStyles();
const isDeviceDiscoveryEmpty = useSelector(selectIsDeviceDiscoveryEmpty);
@@ -71,27 +82,29 @@ export const PortfolioTrackerDeviceManagerContent = () => {
return (
-
-
-
-
-
-
-
);
diff --git a/suite-native/device-manager/src/components/WalletItem.tsx b/suite-native/device-manager/src/components/WalletItem.tsx
new file mode 100644
index 00000000000..85cf54249c5
--- /dev/null
+++ b/suite-native/device-manager/src/components/WalletItem.tsx
@@ -0,0 +1,76 @@
+import { Pressable } from 'react-native';
+import { useSelector } from 'react-redux';
+
+import { HStack, Radio, Text } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { Icon } from '@suite-common/icons';
+import { TrezorDevice } from '@suite-common/suite-types';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { selectDevice, selectDeviceByState } from '@suite-common/wallet-core';
+
+type WalletItemProps = {
+ deviceState: NonNullable;
+ onPress: () => void;
+ isSelectable?: boolean;
+};
+
+const walletItemStyle = prepareNativeStyle<{ isSelected: boolean }>((utils, { isSelected }) => ({
+ paddingHorizontal: utils.spacings.medium,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: 60,
+ gap: 12,
+ backgroundColor: utils.colors.backgroundSurfaceElevation1,
+ borderWidth: utils.borders.widths.small,
+ borderRadius: 12,
+ borderColor: utils.colors.borderElevation1,
+ extend: {
+ condition: isSelected,
+ style: {
+ borderWidth: utils.borders.widths.large,
+ borderColor: utils.colors.borderSecondary,
+ },
+ },
+}));
+
+export const WalletItem = ({ deviceState, onPress, isSelectable = true }: WalletItemProps) => {
+ const { applyStyle } = useNativeStyles();
+ const device = useSelector((state: any) => selectDeviceByState(state, deviceState));
+ const selectedDevice = useSelector(selectDevice);
+
+ if (!device) {
+ return null;
+ }
+
+ const walletNameLabel = device.useEmptyPassphrase ? (
+
+ ) : (
+
+ );
+
+ const isSelected =
+ selectedDevice?.id === device.id && selectedDevice?.instance === device.instance;
+
+ const showAsSelected = isSelected && isSelectable;
+
+ return (
+
+
+
+
+ {walletNameLabel}
+
+ {isSelectable && }
+
+
+ );
+};
diff --git a/suite-native/device-manager/src/components/WalletList.tsx b/suite-native/device-manager/src/components/WalletList.tsx
new file mode 100644
index 00000000000..21a5cd122d5
--- /dev/null
+++ b/suite-native/device-manager/src/components/WalletList.tsx
@@ -0,0 +1,34 @@
+import { useSelector } from 'react-redux';
+
+import { selectDeviceInstances } from '@suite-common/wallet-core';
+import { VStack } from '@suite-native/atoms';
+import { TrezorDevice } from '@suite-common/suite-types';
+
+import { WalletItem } from './WalletItem';
+
+type WalletListProps = {
+ onSelectDevice: (device: TrezorDevice) => void;
+};
+
+export const WalletList = ({ onSelectDevice }: WalletListProps) => {
+ const devices = useSelector(selectDeviceInstances);
+
+ return (
+
+ {devices.map(device => {
+ if (!device.state) {
+ return null;
+ }
+
+ return (
+ 1}
+ onPress={() => onSelectDevice(device)}
+ />
+ );
+ })}
+
+ );
+};
diff --git a/suite-native/device-manager/src/index.ts b/suite-native/device-manager/src/index.ts
index dd2e85001eb..28822510f7b 100644
--- a/suite-native/device-manager/src/index.ts
+++ b/suite-native/device-manager/src/index.ts
@@ -1,2 +1,3 @@
export * from './components/DeviceManager';
+export * from './components/DeviceModelIcon';
export * from './components/DeviceManagerScreenHeader';
diff --git a/suite-native/device-manager/tsconfig.json b/suite-native/device-manager/tsconfig.json
index 4cc2d8793b5..d9ca9221060 100644
--- a/suite-native/device-manager/tsconfig.json
+++ b/suite-native/device-manager/tsconfig.json
@@ -6,6 +6,9 @@
{
"path": "../../suite-common/suite-types"
},
+ {
+ "path": "../../suite-common/suite-utils"
+ },
{
"path": "../../suite-common/wallet-core"
},
diff --git a/suite-native/device/package.json b/suite-native/device/package.json
index 088fdabc257..9150e0ac633 100644
--- a/suite-native/device/package.json
+++ b/suite-native/device/package.json
@@ -13,7 +13,9 @@
"@mobily/ts-belt": "^3.13.1",
"@react-navigation/native": "6.1.10",
"@reduxjs/toolkit": "1.9.5",
+ "@suite-common/icons": "workspace:*",
"@suite-common/redux-utils": "workspace:*",
+ "@suite-common/suite-types": "workspace:*",
"@suite-common/wallet-core": "workspace:*",
"@suite-common/wallet-types": "workspace:*",
"@suite-native/alerts": "workspace:*",
diff --git a/suite-native/device/tsconfig.json b/suite-native/device/tsconfig.json
index 9e357b6dc88..5b2f99fc5e6 100644
--- a/suite-native/device/tsconfig.json
+++ b/suite-native/device/tsconfig.json
@@ -2,9 +2,15 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "libDev" },
"references": [
+ {
+ "path": "../../suite-common/icons"
+ },
{
"path": "../../suite-common/redux-utils"
},
+ {
+ "path": "../../suite-common/suite-types"
+ },
{
"path": "../../suite-common/wallet-core"
},
diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts
index 5b0a7b5b7c5..41ec2ddfe0c 100644
--- a/suite-native/intl/src/en.ts
+++ b/suite-native/intl/src/en.ts
@@ -588,16 +588,13 @@ export const en = {
},
deviceManager: {
deviceButtons: {
- eject: 'Eject',
deviceInfo: 'Device info',
- addHiddenWallet: 'Add hidden wallet',
- },
- deviceList: {
- sectionTitle: 'Open',
+ addHiddenWallet: 'Open passphrase',
+ devices: 'Change',
},
- connectDevice: {
- sectionTitle: 'Connect Trezor device',
- connectButton: 'Connect',
+ connectButton: {
+ another: 'Connect another device',
+ first: 'Connect your device',
},
portfolioTracker: {
explore: 'Explore Trezor',
@@ -605,14 +602,20 @@ export const en = {
exploreShop: 'Explore Trezor Shop',
},
status: {
- portfolioTracker: 'Sync & track coins',
+ portfolioTracker: 'Track your coins without Trezor',
connected: 'Connected',
+ disconnected: 'Disconnected',
},
syncCoinsButton: {
syncMyCoins: 'Sync my coins',
syncAnother: 'Sync another coin',
},
defaultHeader: 'Hi there!',
+ wallet: {
+ standard: 'Standard wallet',
+ portfolio: 'Portfolio tracker',
+ defaultPassphrase: 'Passphrase wallet #{index}',
+ },
},
deviceInfo: {
installedFw: 'Installed firmware: {version}',
diff --git a/suite-native/module-settings/src/components/TrezorModelIcon.tsx b/suite-native/module-settings/src/components/TrezorModelIcon.tsx
deleted file mode 100644
index 4fdaa4beae1..00000000000
--- a/suite-native/module-settings/src/components/TrezorModelIcon.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Icon, IconName } from '@suite-common/icons';
-import { AcquiredDevice } from '@suite-common/suite-types';
-import { DeviceModelInternal } from '@trezor/connect';
-
-const icons = {
- T1B1: 'trezorT1B1',
- T2T1: 'trezorT2T1',
- T2B1: 'trezorT2B1',
- T3T1: 'trezorT3T1',
-} as const satisfies Record;
-
-type TrezorModelIconProps = { device: AcquiredDevice };
-
-export const TrezorModelIcon = ({ device }: TrezorModelIconProps) => {
- const model = device.features?.internal_model as DeviceModelInternal;
- const iconName = icons[model];
- if (!iconName) return null;
-
- return ;
-};
diff --git a/suite-native/module-settings/src/components/ViewOnly/DevicesManagement.tsx b/suite-native/module-settings/src/components/ViewOnly/DevicesManagement.tsx
index b9d8f4c8a95..9819169cef6 100644
--- a/suite-native/module-settings/src/components/ViewOnly/DevicesManagement.tsx
+++ b/suite-native/module-settings/src/components/ViewOnly/DevicesManagement.tsx
@@ -1,31 +1,14 @@
import { useSelector } from 'react-redux';
import { Box, Card, Divider, HStack, Text } from '@suite-native/atoms';
-import { Translation, TxKeyPath } from '@suite-native/intl';
+import { Translation } from '@suite-native/intl';
+import { DeviceModelIcon } from '@suite-native/device-manager';
import { selectPhysicalDevicesGrouppedById } from '@suite-common/wallet-core';
-import { Icon, IconName } from '@suite-common/icons';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
-import { Color } from '@trezor/theme';
import { About, AboutProps } from './About';
-import { TrezorModelIcon } from './TrezorModelIcon';
import { WalletRow } from './WalletRow';
-type ConnectionStyle = { color: Color; iconName: IconName; translationKey: TxKeyPath };
-
-const connectionStyleMap = {
- connected: {
- color: 'textSecondaryHighlight',
- iconName: 'linkChain',
- translationKey: 'moduleSettings.viewOnly.connected',
- },
- disconnected: {
- color: 'textSubdued',
- iconName: 'linkChainBroken',
- translationKey: 'moduleSettings.viewOnly.disconnected',
- },
-} as const satisfies Record;
-
const cardStyle = prepareNativeStyle(utils => ({
padding: 0,
marginTop: utils.spacings.large,
@@ -37,6 +20,13 @@ const deviceStyle = prepareNativeStyle(utils => ({
gap: 12,
}));
+const dotStyle = prepareNativeStyle<{ isConnected: boolean }>((utils, { isConnected }) => ({
+ width: utils.spacings.small,
+ height: utils.spacings.small,
+ borderRadius: utils.borders.radii.round,
+ backgroundColor: isConnected ? utils.colors.textSecondaryHighlight : utils.colors.textSubdued,
+}));
+
export const DevicesManagement = ({ onPressAbout }: AboutProps) => {
const deviceGroups = useSelector(selectPhysicalDevicesGrouppedById);
const { applyStyle } = useNativeStyles();
@@ -48,25 +38,39 @@ export const DevicesManagement = ({ onPressAbout }: AboutProps) => {
{deviceGroups.map(devices => {
const [firstDevice] = devices;
- const connectionStyle =
- connectionStyleMap[firstDevice.connected ? 'connected' : 'disconnected'];
+ const deviceModel = firstDevice.features?.internal_model;
return (
-
+ {deviceModel && (
+
+ )}
{firstDevice.label}
-
-
+
-
-
+
+
diff --git a/suite-native/module-settings/src/components/ViewOnly/TrezorModelIcon.tsx b/suite-native/module-settings/src/components/ViewOnly/TrezorModelIcon.tsx
deleted file mode 100644
index 4fdaa4beae1..00000000000
--- a/suite-native/module-settings/src/components/ViewOnly/TrezorModelIcon.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Icon, IconName } from '@suite-common/icons';
-import { AcquiredDevice } from '@suite-common/suite-types';
-import { DeviceModelInternal } from '@trezor/connect';
-
-const icons = {
- T1B1: 'trezorT1B1',
- T2T1: 'trezorT2T1',
- T2B1: 'trezorT2B1',
- T3T1: 'trezorT3T1',
-} as const satisfies Record;
-
-type TrezorModelIconProps = { device: AcquiredDevice };
-
-export const TrezorModelIcon = ({ device }: TrezorModelIconProps) => {
- const model = device.features?.internal_model as DeviceModelInternal;
- const iconName = icons[model];
- if (!iconName) return null;
-
- return ;
-};
diff --git a/suite-native/module-settings/src/components/ViewOnly/WalletRow.tsx b/suite-native/module-settings/src/components/ViewOnly/WalletRow.tsx
index 24d341d820f..9bde3424a7e 100644
--- a/suite-native/module-settings/src/components/ViewOnly/WalletRow.tsx
+++ b/suite-native/module-settings/src/components/ViewOnly/WalletRow.tsx
@@ -6,7 +6,7 @@ import { deviceActions, toggleRememberDevice } from '@suite-common/wallet-core';
import { useAlert } from '@suite-native/alerts';
import { useToast } from '@suite-native/toasts';
import { Icon } from '@suite-common/icons';
-import { AcquiredDevice } from '@suite-common/suite-types';
+import { TrezorDevice } from '@suite-common/suite-types';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
const walletRowStyle = prepareNativeStyle(utils => ({
@@ -16,7 +16,7 @@ const walletRowStyle = prepareNativeStyle(utils => ({
height: 60,
}));
-export const WalletRow = ({ device }: { device: AcquiredDevice }) => {
+export const WalletRow = ({ device }: { device: TrezorDevice }) => {
const dispatch = useDispatch();
const { showAlert, hideAlert } = useAlert();
const { showToast } = useToast();
diff --git a/yarn.lock b/yarn.lock
index b5ca83237c6..d2734b71b0f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9059,6 +9059,7 @@ __metadata:
"@reduxjs/toolkit": "npm:1.9.5"
"@suite-common/icons": "workspace:*"
"@suite-common/suite-types": "workspace:*"
+ "@suite-common/suite-utils": "workspace:*"
"@suite-common/wallet-core": "workspace:*"
"@suite-native/analytics": "workspace:*"
"@suite-native/atoms": "workspace:*"
@@ -9095,7 +9096,9 @@ __metadata:
"@mobily/ts-belt": "npm:^3.13.1"
"@react-navigation/native": "npm:6.1.10"
"@reduxjs/toolkit": "npm:1.9.5"
+ "@suite-common/icons": "workspace:*"
"@suite-common/redux-utils": "workspace:*"
+ "@suite-common/suite-types": "workspace:*"
"@suite-common/wallet-core": "workspace:*"
"@suite-common/wallet-types": "workspace:*"
"@suite-native/alerts": "workspace:*"