diff --git a/packages/components/src/layouts/Navigation/Tab/TabBar/DesktopLeftSideBar.tsx b/packages/components/src/layouts/Navigation/Tab/TabBar/DesktopLeftSideBar.tsx index cc8a321a580..0aa0c4a7763 100644 --- a/packages/components/src/layouts/Navigation/Tab/TabBar/DesktopLeftSideBar.tsx +++ b/packages/components/src/layouts/Navigation/Tab/TabBar/DesktopLeftSideBar.tsx @@ -1,8 +1,7 @@ -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; -import { CommonActions, useNavigation } from '@react-navigation/native'; +import { CommonActions } from '@react-navigation/native'; import { MotiView } from 'moti'; -import { useIntl } from 'react-intl'; import { StyleSheet } from 'react-native'; import { getTokens, useMedia, useTheme } from 'tamagui'; @@ -14,23 +13,9 @@ import { import useProviderSideBarValue from '@onekeyhq/components/src/hocs/Provider/hooks/useProviderSideBarValue'; import { useSafeAreaInsets } from '@onekeyhq/components/src/hooks'; import type { IKeyOfIcons } from '@onekeyhq/components/src/primitives'; -import { - Icon, - Stack, - XStack, - YStack, -} from '@onekeyhq/components/src/primitives'; -import { DOWNLOAD_URL } from '@onekeyhq/shared/src/config/appConfig'; -import { ETranslations } from '@onekeyhq/shared/src/locale'; +import { Icon, XStack, YStack } from '@onekeyhq/components/src/primitives'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; -import { - EModalRoutes, - EModalSettingRoutes, - ERootRoutes, -} from '@onekeyhq/shared/src/routes'; import { type EShortcutEvents } from '@onekeyhq/shared/src/shortcuts/shortcuts.enum'; -import { shortcutsKeys } from '@onekeyhq/shared/src/shortcuts/shortcutsKeys.enum'; -import { openUrlExternal } from '@onekeyhq/shared/src/utils/openUrlUtils'; import { DesktopDragZoneAbsoluteBar } from '../../../DesktopDragZoneBox'; @@ -87,26 +72,6 @@ function TabItemView({ return contentMemo; } -function DownloadButton() { - const intl = useIntl(); - const onPress = useCallback(() => { - openUrlExternal(DOWNLOAD_URL); - }, []); - if (!platformEnv.isWeb) { - return null; - } - return ( - - ); -} - function OneKeyLogo() { if (!platformEnv.isWeb) { return null; @@ -127,7 +92,6 @@ export function DesktopLeftSideBar({ extraConfig?: ITabNavigatorExtraConfig; }) { const { routes } = state; - const intl = useIntl(); const { leftSidebarCollapsed: isCollapse } = useProviderSideBarValue(); const { top } = useSafeAreaInsets(); // used for ipad const theme = useTheme(); @@ -192,15 +156,6 @@ export function DesktopLeftSideBar({ ], ); - const appNavigation = useNavigation(); - const openSettingPage = useCallback(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - appNavigation.navigate(ERootRoutes.Modal, { - screen: EModalRoutes.SettingModal, - params: EModalSettingRoutes.SettingListModal, - }); - }, [appNavigation]); - return ( {tabs} - - - - - + diff --git a/packages/kit-bg/src/services/ServiceAccount/ServiceAccount.ts b/packages/kit-bg/src/services/ServiceAccount/ServiceAccount.ts index 4f56eb45259..b06ca73cd92 100644 --- a/packages/kit-bg/src/services/ServiceAccount/ServiceAccount.ts +++ b/packages/kit-bg/src/services/ServiceAccount/ServiceAccount.ts @@ -70,6 +70,7 @@ import timerUtils from '@onekeyhq/shared/src/utils/timerUtils'; import type { IServerNetwork } from '@onekeyhq/shared/types'; import type { IBatchCreateAccount, + IHwQrWalletWithDevice, INetworkAccount, } from '@onekeyhq/shared/types/account'; import type { IGeneralInputValidation } from '@onekeyhq/shared/types/address'; @@ -271,6 +272,36 @@ class ServiceAccount extends ServiceBase { }; } + @backgroundMethod() + async getAllHwQrWalletWithDevice() { + const { wallets, allDevices } = await this.getAllWallets({ + refillWalletInfo: true, + }); + // const { devices } = await this.getAllDevices(); + + const result: { + [walletId: string]: IHwQrWalletWithDevice; + } = {}; + + for (const wallet of wallets) { + if ( + !accountUtils.isHwHiddenWallet({ wallet }) && + (accountUtils.isHwWallet({ walletId: wallet.id }) || + accountUtils.isQrWallet({ walletId: wallet.id })) + ) { + const device = (allDevices ?? []).find( + (d) => d.id === wallet.associatedDevice, + ); + result[wallet.id] = { + wallet, + device, + }; + } + } + + return result; + } + @backgroundMethod() async isWalletHasIndexedAccounts({ walletId }: { walletId: string }) { const { accounts: indexedAccounts } = await this.getIndexedAccountsOfWallet( diff --git a/packages/kit/assets/device-management-guide.png b/packages/kit/assets/device-management-guide.png new file mode 100644 index 00000000000..32b738b825b Binary files /dev/null and b/packages/kit/assets/device-management-guide.png differ diff --git a/packages/kit/src/hooks/useShowAddressBook.ts b/packages/kit/src/hooks/useShowAddressBook.ts new file mode 100644 index 00000000000..d844eab767a --- /dev/null +++ b/packages/kit/src/hooks/useShowAddressBook.ts @@ -0,0 +1,50 @@ +import { useCallback } from 'react'; + +import { useIntl } from 'react-intl'; + +import { useAddressBookPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; +import { defaultLogger } from '@onekeyhq/shared/src/logger/logger'; +import { + EModalAddressBookRoutes, + EModalRoutes, +} from '@onekeyhq/shared/src/routes'; + +import backgroundApiProxy from '../background/instance/backgroundApiProxy'; +import { showAddressSafeNotificationDialog } from '../components/AddressInput/AddressSafeDialog'; + +import useAppNavigation from './useAppNavigation'; + +export function useShowAddressBook({ + useNewModal = false, +}: { + useNewModal?: boolean; +}) { + const intl = useIntl(); + const navigation = useAppNavigation(); + const [{ hideDialogInfo }] = useAddressBookPersistAtom(); + + const showAddressBook = useCallback(async () => { + await backgroundApiProxy.servicePassword.promptPasswordVerify(); + if (useNewModal) { + navigation.pushModal(EModalRoutes.AddressBookModal, { + screen: EModalAddressBookRoutes.ListItemModal, + params: {}, + }); + } else { + navigation.push(EModalAddressBookRoutes.ListItemModal); + } + defaultLogger.setting.page.enterAddressBook(); + }, [navigation, useNewModal]); + + const onPress = useCallback(async () => { + if (!hideDialogInfo) { + await showAddressSafeNotificationDialog({ + intl, + }); + await backgroundApiProxy.serviceAddressBook.hideDialogInfo(); + } + await showAddressBook(); + }, [hideDialogInfo, showAddressBook, intl]); + + return onPress; +} diff --git a/packages/kit/src/provider/Container/PortalBodyContainer/SideBanner.tsx b/packages/kit/src/provider/Container/PortalBodyContainer/SideBanner.tsx index d4bed74eea1..9026aa4f56f 100644 --- a/packages/kit/src/provider/Container/PortalBodyContainer/SideBanner.tsx +++ b/packages/kit/src/provider/Container/PortalBodyContainer/SideBanner.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useIntl } from 'react-intl'; +import { StyleSheet } from 'react-native'; import { EPortalContainerConstantName, @@ -12,12 +13,26 @@ import { Stack, useMedia, } from '@onekeyhq/components'; +import { DesktopTabItem } from '@onekeyhq/components/src/layouts/Navigation/Tab/TabBar/DesktopTabItem'; import SidebarBannerImage from '@onekeyhq/kit/assets/sidebar-banner.png'; import { useSpotlight } from '@onekeyhq/kit/src/components/Spotlight'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { useShowAddressBook } from '@onekeyhq/kit/src/hooks/useShowAddressBook'; +import { DOWNLOAD_URL } from '@onekeyhq/shared/src/config/appConfig'; import { ETranslations } from '@onekeyhq/shared/src/locale'; +import platformEnv from '@onekeyhq/shared/src/platformEnv'; +import { + EModalDeviceManagementRoutes, + EModalRoutes, + EModalSettingRoutes, + EOnboardingPages, +} from '@onekeyhq/shared/src/routes'; +import { shortcutsKeys } from '@onekeyhq/shared/src/shortcuts/shortcutsKeys.enum'; import { ESpotlightTour } from '@onekeyhq/shared/src/spotlight'; import { openUrlExternal } from '@onekeyhq/shared/src/utils/openUrlUtils'; +import backgroundApiProxy from '../../../background/instance/backgroundApiProxy'; + import type { GestureResponderEvent } from 'react-native'; function BasicSidebarBanner() { @@ -85,11 +100,103 @@ function BasicSidebarBanner() { ) : null; } +function DownloadButton() { + const intl = useIntl(); + const onPress = useCallback(() => { + openUrlExternal(DOWNLOAD_URL); + }, []); + + if (!platformEnv.isWeb) { + return null; + } + + return ( + + ); +} + +function BottomMenu() { + const intl = useIntl(); + const appNavigation = useAppNavigation(); + + const openSettingPage = useCallback(() => { + appNavigation.pushModal(EModalRoutes.SettingModal, { + screen: EModalSettingRoutes.SettingListModal, + }); + }, [appNavigation]); + + const openAddressBookPage = useShowAddressBook({ + useNewModal: true, + }); + + const openDeviceManagementPage = useCallback(async () => { + const allHwQrWallet = + await backgroundApiProxy.serviceAccount.getAllHwQrWalletWithDevice(); + if (Object.keys(allHwQrWallet).length > 0) { + appNavigation.pushModal(EModalRoutes.DeviceManagementModal, { + screen: EModalDeviceManagementRoutes.DeviceListModal, + }); + return; + } + + appNavigation.pushModal(EModalRoutes.OnboardingModal, { + screen: EOnboardingPages.DeviceManagementGuide, + }); + }, [appNavigation]); + + return ( + + + + + + + + ); +} + export const SidebarBanner = () => { const { gtMd } = useMedia(); return gtMd ? ( - + ) : null; }; diff --git a/packages/kit/src/routes/Modal/router.tsx b/packages/kit/src/routes/Modal/router.tsx index f06696022cd..c765ef9a94c 100644 --- a/packages/kit/src/routes/Modal/router.tsx +++ b/packages/kit/src/routes/Modal/router.tsx @@ -12,6 +12,7 @@ import { AssetSelectorRouter } from '../../views/AssetSelector/router'; import { ChainSelectorRouter } from '../../views/ChainSelector/router'; import { CloudBackupPages } from '../../views/CloudBackup/router'; import { DAppConnectionRouter } from '../../views/DAppConnection/router'; +import { DeviceManagementStacks } from '../../views/DeviceManagement/router'; import { ModalDiscoveryStack } from '../../views/Discovery/router'; import { ModalFiatCryptoRouter } from '../../views/FiatCrypto/router'; import { ModalFirmwareUpdateStack } from '../../views/FirmwareUpdate/router'; @@ -163,6 +164,10 @@ const router: IModalRootNavigatorConfig[] = [ name: EModalRoutes.ShortcutsModal, children: ShortcutsModalRouter, }, + { + name: EModalRoutes.DeviceManagementModal, + children: DeviceManagementStacks, + }, ]; // Pages in Dev Mode diff --git a/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/DeviceManagementButton.tsx b/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/DeviceManagementButton.tsx new file mode 100644 index 00000000000..c75b3aaaf04 --- /dev/null +++ b/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/DeviceManagementButton.tsx @@ -0,0 +1,58 @@ +import { useIntl } from 'react-intl'; + +import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; +import { AccountSelectorProviderMirror } from '@onekeyhq/kit/src/components/AccountSelector'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { useActiveAccount } from '@onekeyhq/kit/src/states/jotai/contexts/accountSelector'; +import type { IDBWallet } from '@onekeyhq/kit-bg/src/dbs/local/types'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import { + EModalDeviceManagementRoutes, + EModalRoutes, +} from '@onekeyhq/shared/src/routes'; +import networkUtils from '@onekeyhq/shared/src/utils/networkUtils'; +import { EAccountSelectorSceneName } from '@onekeyhq/shared/types'; + +import { WalletOptionItem } from './WalletOptionItem'; + +function DeviceManagementButtonView({ + wallet, +}: { + wallet: IDBWallet | undefined; +}) { + const intl = useIntl(); + const navigation = useAppNavigation(); + + return ( + { + navigation.pushModal(EModalRoutes.DeviceManagementModal, { + screen: EModalDeviceManagementRoutes.DeviceDetailModal, + params: { + walletId: wallet?.id || '', + }, + }); + }} + /> + ); +} + +export function DeviceManagementButton({ + wallet, +}: { + wallet: IDBWallet | undefined; +}) { + return ( + + + + ); +} diff --git a/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/index.tsx b/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/index.tsx index f073f16e41d..7249c38aeb2 100644 --- a/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/index.tsx +++ b/packages/kit/src/views/AccountManagerStacks/pages/AccountSelectorStack/WalletDetails/WalletOptions/index.tsx @@ -6,20 +6,17 @@ import { HiddenWalletAddButton } from '@onekeyhq/kit/src/views/AccountManagerSta import { defaultLogger } from '@onekeyhq/shared/src/logger/logger'; import accountUtils from '@onekeyhq/shared/src/utils/accountUtils'; -import { Advance } from './Advance'; import { BatchCreateAccountButton } from './BatchCreateAccountButton'; -import { CheckFirmwareUpdateButton } from './CheckFirmwareUpdateButton'; -import { HardwareHomeScreenButton } from './HardwareHomeScreenButton'; +import { DeviceManagementButton } from './DeviceManagementButton'; import { HdWalletBackupButton } from './HdWalletBackupButton'; import { HiddenWalletRememberSwitch } from './HiddenWalletRememberSwitch'; -import { Verification } from './Verification'; import { WalletProfile } from './WalletProfile'; import type { IWalletDetailsProps } from '..'; type IWalletOptionsProps = Partial; -function WalletOptionsView({ wallet, device }: IWalletOptionsProps) { +function WalletOptionsView({ wallet }: IWalletOptionsProps) { const [editMode] = useAccountSelectorEditModeAtom(); const walletSpecifiedOptions = useMemo(() => { @@ -48,10 +45,11 @@ function WalletOptionsView({ wallet, device }: IWalletOptionsProps) { // HW Normal Wallet return ( <> - + {/* - + */} + @@ -77,7 +75,7 @@ function WalletOptionsView({ wallet, device }: IWalletOptionsProps) { } return null; - }, [device, wallet]); + }, [wallet]); return ( // diff --git a/packages/kit/src/views/DeviceManagement/hooks/useBuyOneKeyHeaderRightButton.tsx b/packages/kit/src/views/DeviceManagement/hooks/useBuyOneKeyHeaderRightButton.tsx new file mode 100644 index 00000000000..7f2f39e5a13 --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/hooks/useBuyOneKeyHeaderRightButton.tsx @@ -0,0 +1,34 @@ +import { useCallback } from 'react'; + +import { HeaderIconButton } from '@onekeyhq/components/src/layouts/Navigation/Header'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { EOnboardingPages } from '@onekeyhq/shared/src/routes'; +import { EModalDeviceManagementRoutes } from '@onekeyhq/shared/src/routes/deviceManagement'; + +export function useBuyOneKeyHeaderRightButton( + params?: + | { + inDeviceManagementStack?: boolean; + } + | undefined, +) { + const navigation = useAppNavigation(); + + const toOneKeyHardwareWalletPage = useCallback(() => { + if (params?.inDeviceManagementStack) { + navigation.push(EModalDeviceManagementRoutes.BuyOneKeyHardwareWallet); + } else { + navigation.push(EOnboardingPages.OneKeyHardwareWallet); + } + }, [params?.inDeviceManagementStack, navigation]); + + return { + headerRight: () => ( + + ), + toOneKeyHardwareWalletPage, + }; +} diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceAdvanceSection.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceAdvanceSection.tsx new file mode 100644 index 00000000000..a80de810a9b --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceAdvanceSection.tsx @@ -0,0 +1,68 @@ +import { useIntl } from 'react-intl'; + +import { SizableText, Switch, XStack, YStack } from '@onekeyhq/components'; +import { ListItem } from '@onekeyhq/kit/src/components/ListItem'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; + +function DeviceAdvanceSection({ + passphraseEnabled, + pinOnAppEnabled, + onPassphraseEnabledChange, + onPinOnAppEnabledChange, + inputPinOnSoftwareSupport, +}: { + passphraseEnabled: boolean; + pinOnAppEnabled: boolean; + onPassphraseEnabledChange: (value: boolean) => void; + onPinOnAppEnabledChange: (value: boolean) => void; + inputPinOnSoftwareSupport: boolean; +}) { + const intl = useIntl(); + return ( + + + + {intl.formatMessage({ + id: ETranslations.global_advance, + })} + + + + + { + onPassphraseEnabledChange(!passphraseEnabled); + }} + /> + + {inputPinOnSoftwareSupport ? ( + + { + onPinOnAppEnabledChange(!pinOnAppEnabled); + }} + /> + + ) : null} + + + ); +} + +export default DeviceAdvanceSection; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceBasicInfoSection.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceBasicInfoSection.tsx new file mode 100644 index 00000000000..7f071ca8ede --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceBasicInfoSection.tsx @@ -0,0 +1,141 @@ +import { useIntl } from 'react-intl'; + +import type { IIconProps, IKeyOfIcons } from '@onekeyhq/components'; +import { Badge, Icon, SizableText, XStack, YStack } from '@onekeyhq/components'; +import { ListItem } from '@onekeyhq/kit/src/components/ListItem'; +import { WalletAvatar } from '@onekeyhq/kit/src/components/WalletAvatar'; +import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult'; +import { WalletRenameButton } from '@onekeyhq/kit/src/views/AccountManagerStacks/components/WalletRename'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import accountUtils from '@onekeyhq/shared/src/utils/accountUtils'; +import deviceUtils from '@onekeyhq/shared/src/utils/deviceUtils'; +import type { IHwQrWalletWithDevice } from '@onekeyhq/shared/types/account'; + +function DeviceBasicInfoSection({ + data, + onPressHomescreen, + onPressAuthRequest, + onPressCheckForUpdates, +}: { + data: IHwQrWalletWithDevice; + onPressHomescreen: () => void; + onPressAuthRequest: () => void; + onPressCheckForUpdates: () => void; +}) { + const { wallet, device } = data; + const intl = useIntl(); + const isQrWallet = accountUtils.isQrWallet({ walletId: wallet.id }); + + const { result: deviceInfo } = usePromiseResult( + async () => { + if (!device || !device.featuresInfo) { + return { + firmwareVersion: '-', + walletAvatarBadge: isQrWallet ? 'QR' : undefined, + verifiedBadgeIconName: 'DocumentSearch2Outline' as IKeyOfIcons, + verifiedBadgeIconColor: '$iconSubdued' as IIconProps['color'], + }; + } + + const versions = await deviceUtils.getDeviceVersion({ + device, + features: device.featuresInfo, + }); + + let iconName: IKeyOfIcons = 'DocumentSearch2Outline'; + let iconColor: IIconProps['color'] = '$iconSubdued'; + + if (device.verifiedAtVersion) { + iconName = 'BadgeVerifiedSolid'; + iconColor = '$iconSuccess'; + } else if (device.verifiedAtVersion === '') { + iconName = 'ErrorSolid'; + iconColor = '$iconCritical'; + } + + return { + firmwareVersion: versions?.firmwareVersion ?? '-', + walletAvatarBadge: isQrWallet ? 'QR' : undefined, + verifiedBadgeIconName: iconName, + verifiedBadgeIconColor: iconColor, + }; + }, + [device, isQrWallet], + { + initResult: { + firmwareVersion: '-', + walletAvatarBadge: isQrWallet ? 'QR' : undefined, + verifiedBadgeIconName: 'DocumentSearch2Outline', + verifiedBadgeIconColor: '$iconSubdued', + }, + }, + ); + + return ( + + + + + + + + + + {isQrWallet ? null : ( + + + {`v${deviceInfo.firmwareVersion}`} + + + + + + {intl.formatMessage({ + id: ETranslations.global_verified, + })} + + + + + )} + + + {isQrWallet ? null : ( + + + + + + )} + + ); +} + +export default DeviceBasicInfoSection; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceQrInfoSection.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceQrInfoSection.tsx new file mode 100644 index 00000000000..8f1d8fd9bbf --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceQrInfoSection.tsx @@ -0,0 +1,39 @@ +import { useIntl } from 'react-intl'; + +import { SizableText, YStack } from '@onekeyhq/components'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; + +function DeviceQrInfoSection() { + const intl = useIntl(); + + return ( + + + + {intl.formatMessage({ + id: ETranslations.global_about_qr_details_question_a, + })} + + + {intl.formatMessage({ + id: ETranslations.global_about_qr_details_answer_a, + })} + + + + + {intl.formatMessage({ + id: ETranslations.global_about_qr_details_question_b, + })} + + + {intl.formatMessage({ + id: ETranslations.global_about_qr_details_answer_b, + })} + + + + ); +} + +export default DeviceQrInfoSection; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceSpecsSection.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceSpecsSection.tsx new file mode 100644 index 00000000000..45e475adcab --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceSpecsSection.tsx @@ -0,0 +1,122 @@ +import { useMemo } from 'react'; + +import { useIntl } from 'react-intl'; + +import { SizableText, XStack, YStack } from '@onekeyhq/components'; +import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import deviceUtils from '@onekeyhq/shared/src/utils/deviceUtils'; +import type { IHwQrWalletWithDevice } from '@onekeyhq/shared/types/account'; + +type ISpecItemProps = { + title: string; + value: string; +}; + +function SpecItem({ title, value }: ISpecItemProps) { + return ( + + + {title} + + + {value} + + + ); +} + +function DeviceSpecsSection({ data }: { data: IHwQrWalletWithDevice }) { + const intl = useIntl(); + const { device } = data; + const defaultDeviceInfo = useMemo( + () => ({ + model: '-', + bleName: '-', + bleVersion: '-', + bootloaderVersion: '-', + firmwareVersion: '-', + serialNumber: '-', + }), + [], + ); + const { result: deviceInfo } = usePromiseResult( + async () => { + if (!device || !device.featuresInfo) { + return defaultDeviceInfo; + } + + const versions = await deviceUtils.getDeviceVersion({ + device, + features: device.featuresInfo, + }); + + const model = await deviceUtils.buildDeviceLabel({ + features: device.featuresInfo, + buildModelName: true, + }); + + return { + model, + bleName: device.featuresInfo.ble_name ?? '-', + bleVersion: versions?.bleVersion ?? '-', + bootloaderVersion: versions?.bootloaderVersion ?? '-', + firmwareVersion: versions?.firmwareVersion ?? '-', + serialNumber: + device.featuresInfo.onekey_serial ?? + device.featuresInfo.serial_no ?? + '-', + }; + }, + [device, defaultDeviceInfo], + { + initResult: defaultDeviceInfo, + }, + ); + + return ( + + + + {intl.formatMessage({ + id: ETranslations.global_device_info, + })} + + + + + + + + + + + ); +} + +export default DeviceSpecsSection; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/index.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/index.tsx new file mode 100644 index 00000000000..ad8dd0f1417 --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/index.tsx @@ -0,0 +1,202 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { useRoute } from '@react-navigation/native'; +import { useIntl } from 'react-intl'; + +import { Page, YStack } from '@onekeyhq/components'; +import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult'; +import { useFirmwareUpdateActions } from '@onekeyhq/kit/src/views/FirmwareUpdate/hooks/useFirmwareUpdateActions'; +import { useFirmwareVerifyDialog } from '@onekeyhq/kit/src/views/Onboarding/pages/ConnectHardwareWallet/FirmwareVerifyDialog'; +import { + EAppEventBusNames, + appEventBus, +} from '@onekeyhq/shared/src/eventBus/appEventBus'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import type { + EModalDeviceManagementRoutes, + IModalDeviceManagementParamList, +} from '@onekeyhq/shared/src/routes'; +import { + EAccountManagerStacksRoutes, + EModalRoutes, +} from '@onekeyhq/shared/src/routes'; +import accountUtils from '@onekeyhq/shared/src/utils/accountUtils'; +import type { IHwQrWalletWithDevice } from '@onekeyhq/shared/types/account'; + +import DeviceAdvanceSection from './DeviceAdvanceSection'; +import DeviceBasicInfoSection from './DeviceBasicInfoSection'; +import DeviceQrInfoSection from './DeviceQrInfoSection'; +import DeviceSpecsSection from './DeviceSpecsSection'; + +import type { RouteProp } from '@react-navigation/native'; + +function DeviceDetailsModal() { + const intl = useIntl(); + const navigation = useAppNavigation(); + const route = + useRoute< + RouteProp< + IModalDeviceManagementParamList, + EModalDeviceManagementRoutes.DeviceDetailModal + > + >(); + const { walletId } = route.params; + + const [passphraseEnabled, setPassphraseEnabled] = useState(false); + const [pinOnAppEnabled, setPinOnAppEnabled] = useState(false); + const { + result, + isLoading, + run: refreshData, + } = usePromiseResult(async () => { + const r = + await backgroundApiProxy.serviceAccount.getAllHwQrWalletWithDevice(); + + const device = r?.[walletId]?.device; + setPassphraseEnabled(Boolean(device?.featuresInfo?.passphrase_protection)); + setPinOnAppEnabled(Boolean(device?.settings?.inputPinOnSoftware)); + + return r?.[walletId] ?? undefined; + }, [walletId]); + + useEffect(() => { + const fn = () => { + void refreshData(); + }; + appEventBus.on(EAppEventBusNames.WalletUpdate, fn); + return () => { + appEventBus.off(EAppEventBusNames.WalletUpdate, fn); + }; + }, [refreshData]); + + const isQrWallet = result + ? accountUtils.isQrWallet({ walletId: result.wallet.id }) + : false; + + // Basic Info Section + const onPressHomescreen = useCallback(() => { + if (!result?.device) return; + navigation.pushModal(EModalRoutes.AccountManagerStacks, { + screen: EAccountManagerStacksRoutes.HardwareHomeScreenModal, + params: { + device: result?.device, + }, + }); + }, [result?.device, navigation]); + + const { showFirmwareVerifyDialog } = useFirmwareVerifyDialog(); + const onPressAuthRequest = useCallback(async () => { + if (!result?.device) { + return; + } + await showFirmwareVerifyDialog({ + device: result.device, + features: result.device.featuresInfo, + onContinue: async ({ checked }) => { + console.log(checked); + }, + }); + }, [result?.device, showFirmwareVerifyDialog]); + + const actions = useFirmwareUpdateActions(); + const onPressCheckForUpdates = useCallback(() => { + actions.openChangeLogModal({ + connectId: result?.device?.connectId, + }); + }, [result?.device?.connectId, actions]); + + // Advance Section + const inputPinOnSoftwareSupport = ['classic', 'mini', 'classic1s'].includes( + result?.device?.deviceType || '', + ); + + const onPassphraseEnabledChange = useCallback( + async (value: boolean) => { + try { + await backgroundApiProxy.serviceHardware.setPassphraseEnabled({ + walletId: result?.wallet.id || '', + passphraseEnabled: value, + }); + setPassphraseEnabled(value); + } catch (error) { + console.error(error); + } + }, + [result?.wallet.id], + ); + + const onPinOnAppEnabledChange = useCallback( + async (value: boolean) => { + try { + setPinOnAppEnabled(value); + await backgroundApiProxy.serviceHardware.setInputPinOnSoftware({ + walletId: result?.wallet.id || '', + inputPinOnSoftware: value, + }); + } catch (error) { + console.error(error); + setPinOnAppEnabled(!value); + } + }, + [result?.wallet.id], + ); + + const renderContent = useCallback(() => { + if (isLoading || !result) { + return null; + } + + if (isQrWallet) { + return ; + } + + return ( + <> + + + + ); + }, [ + isLoading, + result, + isQrWallet, + passphraseEnabled, + pinOnAppEnabled, + inputPinOnSoftwareSupport, + onPassphraseEnabledChange, + onPinOnAppEnabledChange, + ]); + + return ( + + + + + {result ? ( + <> + + {renderContent()} + + ) : null} + + + + ); +} + +export default DeviceDetailsModal; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceGuideModal/index.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceGuideModal/index.tsx new file mode 100644 index 00000000000..9be91ddb117 --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceGuideModal/index.tsx @@ -0,0 +1,69 @@ +import { useCallback } from 'react'; + +import { useIntl } from 'react-intl'; + +import { Button, Image, Page, SizableText, YStack } from '@onekeyhq/components'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import { EOnboardingPages } from '@onekeyhq/shared/src/routes'; + +import { useBuyOneKeyHeaderRightButton } from '../../hooks/useBuyOneKeyHeaderRightButton'; + +function DeviceGuideModal() { + const intl = useIntl(); + const navigation = useAppNavigation(); + const { headerRight } = useBuyOneKeyHeaderRightButton(); + + const handleStartConnect = useCallback(() => { + navigation.push(EOnboardingPages.ConnectYourDevice); + }, [navigation]); + + return ( + + + + + OneKey devices + + + + + {intl.formatMessage({ + id: ETranslations.global_no_device_connected, + })} + + + {intl.formatMessage({ + id: ETranslations.global_no_device_connected_desc, + })} + + + + + + + + ); +} + +export default DeviceGuideModal; diff --git a/packages/kit/src/views/DeviceManagement/pages/DeviceManagementListModal/index.tsx b/packages/kit/src/views/DeviceManagement/pages/DeviceManagementListModal/index.tsx new file mode 100644 index 00000000000..645ed3da17d --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/pages/DeviceManagementListModal/index.tsx @@ -0,0 +1,176 @@ +import { useCallback, useEffect, useMemo } from 'react'; + +import { useIntl } from 'react-intl'; + +import { + Anchor, + Icon, + ListView, + Page, + SizableText, + XStack, +} from '@onekeyhq/components'; +import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; +import { ListItem } from '@onekeyhq/kit/src/components/ListItem'; +import type { IWalletAvatarProps } from '@onekeyhq/kit/src/components/WalletAvatar'; +import { WalletAvatar } from '@onekeyhq/kit/src/components/WalletAvatar'; +import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; +import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult'; +import { + EAppEventBusNames, + appEventBus, +} from '@onekeyhq/shared/src/eventBus/appEventBus'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; +import { + EModalDeviceManagementRoutes, + EModalRoutes, + EOnboardingPages, +} from '@onekeyhq/shared/src/routes'; +import accountUtils from '@onekeyhq/shared/src/utils/accountUtils'; +import type { IHwQrWalletWithDevice } from '@onekeyhq/shared/types/account'; + +import { useBuyOneKeyHeaderRightButton } from '../../hooks/useBuyOneKeyHeaderRightButton'; + +function DeviceManagementListModal() { + const intl = useIntl(); + const appNavigation = useAppNavigation(); + const { result: hwQrWalletList = [], run: refreshHwQrWalletList } = + usePromiseResult>(async () => { + const r = + await backgroundApiProxy.serviceAccount.getAllHwQrWalletWithDevice(); + return Object.values(r).filter((item): item is IHwQrWalletWithDevice => + Boolean(item.device), + ); + }, []); + + useEffect(() => { + const fn = () => { + void refreshHwQrWalletList(); + }; + appEventBus.on(EAppEventBusNames.WalletUpdate, fn); + return () => { + appEventBus.off(EAppEventBusNames.WalletUpdate, fn); + }; + }, [refreshHwQrWalletList]); + + const onAddDevice = useCallback(async () => { + appNavigation.pushModal(EModalRoutes.OnboardingModal, { + screen: EOnboardingPages.ConnectYourDevice, + }); + }, [appNavigation]); + + const onWalletPressed = useCallback( + (wallet: IHwQrWalletWithDevice['wallet']) => { + if (wallet.id) { + appNavigation.push(EModalDeviceManagementRoutes.DeviceDetailModal, { + walletId: wallet.id, + }); + } + }, + [appNavigation], + ); + + const renderItem = useCallback( + ({ item }: { item: IHwQrWalletWithDevice }) => { + const walletAvatarProps: IWalletAvatarProps = { + wallet: item.wallet, + status: 'default', + badge: accountUtils.isQrWallet({ walletId: item.wallet.id }) + ? 'QR' + : undefined, + }; + return ( + } + onPress={() => { + onWalletPressed(item.wallet); + }} + /> + ); + }, + [onWalletPressed], + ); + + const footer = useMemo( + () => ( + ( + + + + )} + title={intl.formatMessage({ + id: ETranslations.global_add_new_device, + })} + drillIn + onPress={onAddDevice} + /> + ), + [intl, onAddDevice], + ); + + const { headerRight } = useBuyOneKeyHeaderRightButton({ + inDeviceManagementStack: true, + }); + + return ( + + + + item.wallet.id} + data={hwQrWalletList} + renderItem={renderItem} + estimatedItemSize={68} + ListFooterComponent={footer} + /> + + + {intl.formatMessage({ + id: ETranslations.global_onekey_prompt_dont_have_yet, + })} + + + {intl.formatMessage({ id: ETranslations.global_buy_one })} + + + + + ); +} + +export default DeviceManagementListModal; diff --git a/packages/kit/src/views/DeviceManagement/router/index.tsx b/packages/kit/src/views/DeviceManagement/router/index.tsx new file mode 100644 index 00000000000..db04c79b37c --- /dev/null +++ b/packages/kit/src/views/DeviceManagement/router/index.tsx @@ -0,0 +1,45 @@ +import type { IModalFlowNavigatorConfig } from '@onekeyhq/components'; +import { LazyLoadPage } from '@onekeyhq/kit/src/components/LazyLoadPage'; +import type { IModalDeviceManagementParamList } from '@onekeyhq/shared/src/routes'; +import { EModalDeviceManagementRoutes } from '@onekeyhq/shared/src/routes/deviceManagement'; + +const DeviceGuideModal = LazyLoadPage( + () => import('../pages/DeviceGuideModal'), +); + +const DeviceListModal = LazyLoadPage( + () => import('../pages/DeviceManagementListModal'), +); + +const DeviceDetailModal = LazyLoadPage( + () => import('../pages/DeviceDetailsModal'), +); + +const BuyOneKeyHardwareWallet = LazyLoadPage( + () => + import( + '@onekeyhq/kit/src/views/Onboarding/pages/ConnectHardwareWallet/OneKeyHardwareWallet' + ), +); + +export const DeviceManagementStacks: IModalFlowNavigatorConfig< + EModalDeviceManagementRoutes, + IModalDeviceManagementParamList +>[] = [ + { + name: EModalDeviceManagementRoutes.GuideModal, + component: DeviceGuideModal, + }, + { + name: EModalDeviceManagementRoutes.DeviceListModal, + component: DeviceListModal, + }, + { + name: EModalDeviceManagementRoutes.DeviceDetailModal, + component: DeviceDetailModal, + }, + { + name: EModalDeviceManagementRoutes.BuyOneKeyHardwareWallet, + component: BuyOneKeyHardwareWallet, + }, +]; diff --git a/packages/kit/src/views/Onboarding/pages/ConnectHardwareWallet/ConnectYourDevice.tsx b/packages/kit/src/views/Onboarding/pages/ConnectHardwareWallet/ConnectYourDevice.tsx index 9661c01fc4e..f67d2cf7445 100644 --- a/packages/kit/src/views/Onboarding/pages/ConnectHardwareWallet/ConnectYourDevice.tsx +++ b/packages/kit/src/views/Onboarding/pages/ConnectHardwareWallet/ConnectYourDevice.tsx @@ -86,6 +86,7 @@ import { type IOneKeyDeviceFeatures, } from '@onekeyhq/shared/types/device'; +import { useBuyOneKeyHeaderRightButton } from '../../../DeviceManagement/hooks/useBuyOneKeyHeaderRightButton'; import { useFirmwareUpdateActions } from '../../../FirmwareUpdate/hooks/useFirmwareUpdateActions'; import { useFirmwareVerifyDialog } from './FirmwareVerifyDialog'; @@ -102,10 +103,6 @@ type IConnectYourDeviceItem = { device: SearchDevice | undefined; }; -const headerRight = (onPress: () => void) => ( - -); - function DeviceListItem({ item }: { item: IConnectYourDeviceItem }) { const [isLoading, setIsLoading] = useState(false); return ( @@ -1185,9 +1182,8 @@ export function ConnectYourDevicePage() { channel ?? EConnectDeviceChannel.usbOrBle, ); - const toOneKeyHardwareWalletPage = useCallback(() => { - navigation.push(EOnboardingPages.OneKeyHardwareWallet); - }, [navigation]); + const { headerRight, toOneKeyHardwareWalletPage } = + useBuyOneKeyHeaderRightButton(); return ( @@ -1195,7 +1191,7 @@ export function ConnectYourDevicePage() { title={intl.formatMessage({ id: ETranslations.onboarding_connect_your_device, })} - headerRight={() => headerRight(toOneKeyHardwareWalletPage)} + headerRight={headerRight} /> diff --git a/packages/kit/src/views/Onboarding/router/index.ts b/packages/kit/src/views/Onboarding/router/index.ts index 5cce64c197c..05eb4fa7e26 100644 --- a/packages/kit/src/views/Onboarding/router/index.ts +++ b/packages/kit/src/views/Onboarding/router/index.ts @@ -76,6 +76,10 @@ const ImportKeyTag = LazyLoadPage( () => import('../pages/ImportWallet/ImportKeyTag'), ); +const DeviceManagementGuideModal = LazyLoadPage( + () => import('../../DeviceManagement/pages/DeviceGuideModal'), +); + export const OnboardingRouter: IModalFlowNavigatorConfig< EOnboardingPages, IOnboardingParamList @@ -178,4 +182,10 @@ export const OnboardingRouter: IModalFlowNavigatorConfig< component: FinalizeWalletSetup, allowDisableClose: true, }, + + // device management guide page + { + name: EOnboardingPages.DeviceManagementGuide, + component: DeviceManagementGuideModal, + }, ]; diff --git a/packages/kit/src/views/Setting/pages/List/DefaultSection/index.tsx b/packages/kit/src/views/Setting/pages/List/DefaultSection/index.tsx index 42d65b3f8ce..2a7154945cc 100644 --- a/packages/kit/src/views/Setting/pages/List/DefaultSection/index.tsx +++ b/packages/kit/src/views/Setting/pages/List/DefaultSection/index.tsx @@ -4,15 +4,12 @@ import { useIntl } from 'react-intl'; import { YStack } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; -import { showAddressSafeNotificationDialog } from '@onekeyhq/kit/src/components/AddressInput/AddressSafeDialog'; import { ListItem } from '@onekeyhq/kit/src/components/ListItem'; import useAppNavigation from '@onekeyhq/kit/src/hooks/useAppNavigation'; import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult'; +import { useShowAddressBook } from '@onekeyhq/kit/src/hooks/useShowAddressBook'; import { useBackupEntryStatus } from '@onekeyhq/kit/src/views/CloudBackup/components/useBackupEntryStatus'; -import { - useAddressBookPersistAtom, - usePasswordPersistAtom, -} from '@onekeyhq/kit-bg/src/states/jotai/atoms'; +import { usePasswordPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { EAppEventBusNames, appEventBus, @@ -24,7 +21,6 @@ import { ECloudBackupRoutes, EDAppConnectionModal, ELiteCardRoutes, - EModalAddressBookRoutes, EModalKeyTagRoutes, EModalRoutes, } from '@onekeyhq/shared/src/routes'; @@ -47,22 +43,9 @@ export const useOnLock = () => { const AddressBookItem = () => { const intl = useIntl(); - const navigation = useAppNavigation(); - const showAddressBook = useCallback(async () => { - await backgroundApiProxy.servicePassword.promptPasswordVerify(); - navigation.push(EModalAddressBookRoutes.ListItemModal); - defaultLogger.setting.page.enterAddressBook(); - }, [navigation]); - const [{ hideDialogInfo }] = useAddressBookPersistAtom(); - const onPress = useCallback(async () => { - if (!hideDialogInfo) { - await showAddressSafeNotificationDialog({ - intl, - }); - await backgroundApiProxy.serviceAddressBook.hideDialogInfo(); - } - await showAddressBook(); - }, [hideDialogInfo, showAddressBook, intl]); + const onPress = useShowAddressBook({ + useNewModal: false, + }); return ( { - if (features.label) { + if (features.label && !buildModelName) { return features.label; } const defaultLabelsByDeviceType: Record = { diff --git a/packages/shared/types/account.ts b/packages/shared/types/account.ts index 2fa050bae84..e4f66650dea 100644 --- a/packages/shared/types/account.ts +++ b/packages/shared/types/account.ts @@ -1,4 +1,8 @@ -import type { IDBAccount } from '@onekeyhq/kit-bg/src/dbs/local/types'; +import type { + IDBAccount, + IDBDevice, + IDBWallet, +} from '@onekeyhq/kit-bg/src/dbs/local/types'; import type { IAirGapAccount } from '@onekeyhq/qr-wallet-sdk'; import type { INetworkAccountAddressDetail } from './address'; @@ -42,3 +46,8 @@ export enum ERequestWalletTypeEnum { THIRD_PARTY = 'third-party', UNKNOWN = 'unknown', } + +export type IHwQrWalletWithDevice = { + device: IDBDevice | undefined; + wallet: IDBWallet; +};