From 95f7171f3dcb2e82be937d7dfc4b62eeed4830f4 Mon Sep 17 00:00:00 2001 From: Peter Sanderson Date: Mon, 25 Nov 2024 15:35:30 +0100 Subject: [PATCH] feat: implement Bluetooth Onboarding UI --- .../src/components/NewModal/types.tsx | 2 +- .../skeletons/SkeletonRectangle.tsx | 9 +- packages/suite-desktop-api/src/api.ts | 4 +- packages/suite-desktop-api/src/main.ts | 1 + packages/suite-desktop-api/src/messages.ts | 9 + .../PrerequisitesGuide/DeviceConnect.tsx | 17 +- .../PrerequisitesGuide/PrerequisitesGuide.tsx | 68 +++-- .../suite/bluetooth/BluetoothConnect.tsx | 201 +++++++++++++++ .../suite/bluetooth/BluetoothDevice.tsx | 21 ++ .../suite/bluetooth/BluetoothDeviceItem.tsx | 37 +++ .../suite/bluetooth/BluetoothDeviceList.tsx | 45 ++++ .../suite/bluetooth/BluetoothPairingPin.tsx | 58 +++++ .../suite/bluetooth/BluetoothTips.tsx | 55 ++++ .../errors/BluetoothNotAllowedForSuite.tsx | 3 + .../bluetooth/errors/BluetoothNotEnabled.tsx | 48 ++++ .../errors/BluetoothVersionNotCompatible.tsx | 30 +++ .../src/components/suite/bluetooth/types.ts | 2 + .../SelectBluetoothDeviceModal.tsx | 243 ------------------ .../UserContextModal/UserContextModal.tsx | 5 +- .../src/components/suite/modals/index.tsx | 1 - 20 files changed, 574 insertions(+), 285 deletions(-) create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothDevice.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothDeviceItem.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothDeviceList.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothPairingPin.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/BluetoothTips.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/errors/BluetoothNotAllowedForSuite.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/errors/BluetoothNotEnabled.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/errors/BluetoothVersionNotCompatible.tsx create mode 100644 packages/suite/src/components/suite/bluetooth/types.ts delete mode 100644 packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/SelectBluetoothDeviceModal.tsx diff --git a/packages/components/src/components/NewModal/types.tsx b/packages/components/src/components/NewModal/types.tsx index 8b28791aea4..bb68483d4fa 100644 --- a/packages/components/src/components/NewModal/types.tsx +++ b/packages/components/src/components/NewModal/types.tsx @@ -1,6 +1,6 @@ import { UIVariant, UISize, UIHorizontalAlignment, UIVerticalAlignment } from '../../config/types'; -export const newModalVariants = ['primary', 'warning', 'destructive'] as const; +export const newModalVariants = ['primary', 'warning', 'destructive', 'info'] as const; export type NewModalVariant = Extract; export const newModalSizes = ['huge', 'large', 'medium', 'small', 'tiny'] as const; diff --git a/packages/components/src/components/skeletons/SkeletonRectangle.tsx b/packages/components/src/components/skeletons/SkeletonRectangle.tsx index b9b8d0f82a4..51b1245e6a1 100644 --- a/packages/components/src/components/skeletons/SkeletonRectangle.tsx +++ b/packages/components/src/components/skeletons/SkeletonRectangle.tsx @@ -1,6 +1,6 @@ import styled, { css } from 'styled-components'; -import { Elevation, borders, mapElevationToBackground } from '@trezor/theme'; +import { Elevation, borders, mapElevationToBackground, nextElevation } from '@trezor/theme'; import { SkeletonBaseProps } from './types'; import { getValue, shimmerEffect } from './utils'; @@ -18,7 +18,12 @@ const StyledSkeletonRectangle = styled.div< >` width: ${({ $width }) => getValue($width) ?? '80px'}; height: ${({ $height }) => getValue($height) ?? '20px'}; - background: ${({ $background, ...props }) => $background ?? mapElevationToBackground(props)}; + background: ${({ $background, ...props }) => + $background ?? + mapElevationToBackground({ + theme: props.theme, + $elevation: props.$elevation, + })}; border-radius: ${({ $borderRadius }) => getValue($borderRadius) ?? borders.radii.xs}; background-size: 200%; diff --git a/packages/suite-desktop-api/src/api.ts b/packages/suite-desktop-api/src/api.ts index 22956082af5..55c6e4f92c7 100644 --- a/packages/suite-desktop-api/src/api.ts +++ b/packages/suite-desktop-api/src/api.ts @@ -15,7 +15,7 @@ import { Status, BridgeSettings, TorSettings, - TraySettings, + TraySettings, ElectronBluetoothDevice } from './messages'; // Event messages from renderer to main process @@ -75,7 +75,7 @@ export interface RendererChannels { 'handshake/event': HandshakeEvent; 'bluetooth/event': any; 'bluetooth/adapter-event': boolean; - 'bluetooth/select-device-event': { uuid: string; name: string }[]; + 'bluetooth/select-device-event': ElectronBluetoothDevice[]; 'bluetooth/pair-device-event': { paired: boolean; pin: string }; 'bluetooth/connect-device-event': any; } diff --git a/packages/suite-desktop-api/src/main.ts b/packages/suite-desktop-api/src/main.ts index 7c89873fe0e..6537c3d3045 100644 --- a/packages/suite-desktop-api/src/main.ts +++ b/packages/suite-desktop-api/src/main.ts @@ -19,6 +19,7 @@ export type { BootstrapTorEvent, TorStatusEvent, HandshakeTorModule, + ElectronBluetoothDevice, } from './messages'; export { TorStatus } from './enums'; diff --git a/packages/suite-desktop-api/src/messages.ts b/packages/suite-desktop-api/src/messages.ts index 2e14ffd1fa3..3f0de52038a 100644 --- a/packages/suite-desktop-api/src/messages.ts +++ b/packages/suite-desktop-api/src/messages.ts @@ -126,3 +126,12 @@ export type InvokeResult = ExtractUndefined extends undefined ? { success: true; payload?: Payload } | { success: false; error: string; code?: string } : { success: true; payload: Payload } | { success: false; error: string; code?: string }; + +export interface ElectronBluetoothDevice { + uuid: string; + name: string; + paired: boolean; + connected: boolean; + timestamp: number; + rssi: number; +} diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceConnect.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceConnect.tsx index 09df9698ec9..a68af26a991 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceConnect.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceConnect.tsx @@ -1,3 +1,5 @@ +import { Button } from '@trezor/components'; + import { Translation, TroubleshootingTips, WebUsbButton } from 'src/components/suite'; import { TROUBLESHOOTING_TIP_BRIDGE_STATUS, @@ -7,16 +9,18 @@ import { TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER, TROUBLESHOOTING_TIP_UDEV, } from 'src/components/suite/troubleshooting/tips'; -import { Button } from '@trezor/components'; -import { openModal } from '../../../actions/suite/modalActions'; -import { useDispatch } from '../../../hooks/suite'; interface DeviceConnectProps { isWebUsbTransport: boolean; isBluetooth: boolean; + onBluetoothClick: () => void; } -export const DeviceConnect = ({ isWebUsbTransport, isBluetooth }: DeviceConnectProps) => { +export const DeviceConnect = ({ + isWebUsbTransport, + onBluetoothClick, + isBluetooth, +}: DeviceConnectProps) => { const items = isWebUsbTransport ? [ TROUBLESHOOTING_TIP_UDEV, @@ -32,8 +36,6 @@ export const DeviceConnect = ({ isWebUsbTransport, isBluetooth }: DeviceConnectP TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER, ]; - const dispatch = useDispatch(); - return ( } @@ -46,7 +48,8 @@ export const DeviceConnect = ({ isWebUsbTransport, isBluetooth }: DeviceConnectP size="tiny" onClick={e => { e.stopPropagation(); - dispatch(openModal({ type: 'select-bluetooth-device' })); + onBluetoothClick(); + // dispatch(openModal({ type: 'select-bluetooth-device' })); }} > Connect Safe 7 via bluetooth diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx index ebe61fd5978..8fa3d6aaf89 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx @@ -1,10 +1,10 @@ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import styled from 'styled-components'; import { motion } from 'framer-motion'; import { getStatus, deviceNeedsAttention } from '@suite-common/suite-utils'; -import { Button, motionEasing } from '@trezor/components'; +import { Button, ElevationContext, ElevationDown, motionEasing } from '@trezor/components'; import { selectDevices, selectDevice } from '@suite-common/wallet-core'; import { ConnectDevicePrompt, Translation } from 'src/components/suite'; @@ -26,6 +26,7 @@ import { DeviceNoFirmware } from './DeviceNoFirmware'; import { DeviceUpdateRequired } from './DeviceUpdateRequired'; import { DeviceDisconnectRequired } from './DeviceDisconnectRequired'; import { MultiShareBackupInProgress } from './MultiShareBackupInProgress'; +import { BluetoothConnect } from '../bluetooth/BluetoothConnect'; const Wrapper = styled.div` display: flex; @@ -48,7 +49,10 @@ interface PrerequisitesGuideProps { } export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProps) => { + const [isBluetoothConnectOpen, setIsBluetoothConnectOpen] = useState(false); + const dispatch = useDispatch(); + const device = useSelector(selectDevice); const devices = useSelector(selectDevices); const connectedDevicesCount = devices.filter(d => d.connected === true).length; @@ -70,6 +74,7 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp setIsBluetoothConnectOpen(true)} /> ); case 'device-unacquired': @@ -97,7 +102,7 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp return <>; } }, - [prerequisite, isWebUsbTransport, device], + [prerequisite, isWebUsbTransport, isBluetooth, device], ); const handleSwitchDeviceClick = () => @@ -105,30 +110,41 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp return ( - - - {allowSwitchDevice && connectedDevicesCount > 1 && ( - - - + {isBluetoothConnectOpen ? ( + + {/* Here we need to draw the inner card with elevation -1 (custom design) */} + + setIsBluetoothConnectOpen(false)} /> + + + ) : ( + <> + + + {allowSwitchDevice && connectedDevicesCount > 1 && ( + + + + )} + + + + + )} - - - - ); }; diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx new file mode 100644 index 00000000000..94717fe1290 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx @@ -0,0 +1,201 @@ +import { useEffect, useState, useCallback } from 'react'; + +import { desktopApi, ElectronBluetoothDevice } from '@trezor/suite-desktop-api'; +import TrezorConnect from '@trezor/connect'; +import { Button, Card, ElevationUp, Row, Column, Spinner, Text } from '@trezor/components'; +import { spacings } from '@trezor/theme'; +import { notificationsActions } from '@suite-common/toast-notifications'; + +import { BluetoothNotEnabled } from './errors/BluetoothNotEnabled'; +import { BluetoothDeviceList } from './BluetoothDeviceList'; +import { BluetoothVersionNotCompatible } from './errors/BluetoothVersionNotCompatible'; +import { useDispatch } from '../../../hooks/suite'; +import { BluetoothTips } from './BluetoothTips'; +import { BluetoothPairingPin } from './BluetoothPairingPin'; + +const FAKE_SCAN_TIMEOUT = 30_000; + +type BluetoothConnectProps = { + onClose: () => void; +}; + +export const BluetoothConnect = ({ onClose }: BluetoothConnectProps) => { + const dispatch = useDispatch(); + + const [isBluetoothEnabled, setBluetoothEnabled] = useState(true); // Intentionally by default true so UI wont flicker + const [fakeScanFinished, setFakeScanFinished] = useState(false); + const [connectingStatus, setConnectingStatus] = useState(undefined); + const [pairingPin, setPairingPin] = useState<{ paired: boolean; pin: string } | undefined>( + undefined, + ); + const [deviceList, setDeviceList] = useState([]); + const [selectedDevice, setSelectedDevice] = useState( + undefined, + ); + + const onCancel = useCallback(() => {}, []); + + useEffect(() => { + desktopApi.on('bluetooth/adapter-event', isPowered => { + console.warn('bluetooth/adapter-event', isPowered); + setBluetoothEnabled(isPowered); + if (!isPowered) { + setDeviceList([]); + } + }); + + desktopApi.on('bluetooth/select-device-event', list => { + console.warn('bluetooth/select-device-event', list); + setDeviceList([ + { + uuid: '6ab9d595-c4d3-4d2e-9b91-8a7d5a15fc28', + name: 'HardcodedDevice ', + paired: false, + connected: false, + timestamp: 1732533276, + rssi: 0, + }, + ...list, + ]); + }); + + desktopApi.on('bluetooth/connect-device-event', ({ device, phase }) => { + console.warn('bluetooth/connect-device-event', phase, device); + setConnectingStatus({ device, status: phase }); + if (phase === 'connected') { + TrezorConnect.on('device-connect', () => { + // onCancel(); + }); + } + // if (phase === 'error') { + // } + }); + + desktopApi.on('bluetooth/pair-device-event', event => { + setPairingPin(event); + }); + + return () => { + desktopApi.removeAllListeners('bluetooth/adapter-event'); + desktopApi.removeAllListeners('bluetooth/select-device-event'); + desktopApi.removeAllListeners('bluetooth/pair-device-event'); + desktopApi.removeAllListeners('bluetooth/connect-device-event'); + }; + }, [onCancel]); + + useEffect(() => { + setTimeout(() => { + setFakeScanFinished(true); + }, FAKE_SCAN_TIMEOUT); + desktopApi.bluetoothRequestDevice(); + }, []); + + const onReScanClick = () => { + setFakeScanFinished(false); + + setTimeout(() => { + setFakeScanFinished(true); + }, FAKE_SCAN_TIMEOUT); + }; + + const onSelect = async (deviceId: string) => { + console.log('selecting....', deviceId); + + setSelectedDevice(deviceList.find(d => d.uuid === deviceId)); + + // Todo: remove lines below + setPairingPin({ pin: '123456', paired: false }); + + const result = await desktopApi.bluetoothConnectDevice(deviceId); + console.warn('On select Result!', result); + + if (!result.success) { + setPairingPin(undefined); + dispatch( + notificationsActions.addToast({ + type: 'error', + error: result.error, + }), + ); + } + }; + + // const isLoading = connectingStatus && connectingStatus.status !== 'error'; + + if (!isBluetoothEnabled) { + return ; + } + + // Todo: incompatible version + if (false) { + return ; + } + + if (isBluetoothEnabled && selectedDevice !== undefined && pairingPin !== undefined) { + return ( + {}} + /> + ); + } + + // {isBluetoothEnabled && connectingStatus && ( + //
Connection status: {connectingStatus.status}
+ // )} + // {isBluetoothEnabled && connectingStatus?.status === 'pairing' && ( + //
+ // {pairingPin &&

{pairingPin}

} + //

Paring Trezor with your operating system.

+ //

Confirm connection on you device then compare and confirm PIN.

+ //
+ // )} + + // This is fake, we scan for devices all the time + const isScanning = !fakeScanFinished; + const scanFailed = deviceList.length === 0 && fakeScanFinished; + + return ( + + + + + {isScanning ? ( + <> + + Scanning + + ) : ( + + {deviceList.length} Trezors Found + + )} + + + + + {scanFailed ? ( + + ) : ( + + )} + + + + ); +}; diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothDevice.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothDevice.tsx new file mode 100644 index 00000000000..38ce821e2be --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothDevice.tsx @@ -0,0 +1,21 @@ +import { Column, FlexProps, Icon, Row, Text } from '@trezor/components'; +import { mapTrezorModelToIcon } from '@trezor/product-components'; +import { spacings } from '@trezor/theme'; +import { ElectronBluetoothDevice } from '@trezor/suite-desktop-api'; + +type BluetoothDeviceProps = { + device: ElectronBluetoothDevice; + flex?: FlexProps['flex']; + margin?: FlexProps['margin']; +}; + +export const BluetoothDevice = ({ device, flex, margin }: BluetoothDeviceProps) => ( + + {/* Todo: finalise if we will provide model name before pairing */} + + + Trezor Safe 7 + {device.name} + + +); diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothDeviceItem.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothDeviceItem.tsx new file mode 100644 index 00000000000..128c603f153 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothDeviceItem.tsx @@ -0,0 +1,37 @@ +import { ElectronBluetoothDevice } from '@trezor/suite-desktop-api'; +import { Button, Row } from '@trezor/components'; +import { spacings } from '@trezor/theme'; + +import { BluetoothDevice } from './BluetoothDevice'; + +type BluetoothDeviceItemProps = { + device: ElectronBluetoothDevice; + onClick: any; + connecting: any; + lastSeenTimestamp: any; +}; + +export const BluetoothDeviceItem = ({ device, onClick, connecting }: BluetoothDeviceItemProps) => { + // const timestamp = device.timestamp + // ? new Date(device.timestamp * 1000).toLocaleTimeString('en-US', { hour12: false }) + // : 'Unknown'; + + // const lastSeenInSec = Math.floor((Date.now() - lastSeenTimestamp) / 1000); + // const seenQuiteLongAgo = lastSeenInSec > 5; + + // Last seen: {timestamp} + // + // Paired: {device.paired ? 'yes' : 'no'}, Pairing mode:{' '} + // {device.pairable ? 'yes' : 'no'}, Signal strength: {device.rssi} + // + // {seenQuiteLongAgo && Last seen: {lastSeenInSec}s ago} + + return ( + + + + + ); +}; diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothDeviceList.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothDeviceList.tsx new file mode 100644 index 00000000000..e486ed8618c --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothDeviceList.tsx @@ -0,0 +1,45 @@ +import { Card, Column, SkeletonRectangle, Row } from '@trezor/components'; +import { spacings } from '@trezor/theme'; +import { ElectronBluetoothDevice } from '@trezor/suite-desktop-api'; + +import { BluetoothDeviceItem } from './BluetoothDeviceItem'; + +type BluetoothDeviceListProps = { + deviceList: ElectronBluetoothDevice[]; + onSelect: (uuid: string) => void; + isScanning: boolean; +}; + +const SkeletonDevice = () => ( + + + + + + + + +); + +export const BluetoothDeviceList = ({ + onSelect, + deviceList, + isScanning, +}: BluetoothDeviceListProps) => { + return ( + + + {deviceList.map(d => ( + onSelect(d.uuid)} + connecting={false} + lastSeenTimestamp={0} + /> + ))} + {isScanning && } + + + ); +}; diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothPairingPin.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothPairingPin.tsx new file mode 100644 index 00000000000..9d9ddaa5587 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothPairingPin.tsx @@ -0,0 +1,58 @@ +import styled from 'styled-components'; + +import { Row, NewModal, Card } from '@trezor/components'; +import { spacings, spacingsPx, typography } from '@trezor/theme'; +import { ElectronBluetoothDevice } from '@trezor/suite-desktop-api'; + +import { BluetoothDevice } from './BluetoothDevice'; + +const Pin = styled.div` + display: flex; + flex: 1; + + ${typography.titleLarge} /* Amount */ margin: 0 auto; + + letter-spacing: ${spacingsPx.md}; +`; + +type BluetoothPairingPinProps = { + onCancel: () => void; + onConfirm: () => void; + pairingPin: { paired: boolean; pin: string }; + device: ElectronBluetoothDevice; +}; + +export const BluetoothPairingPin = ({ + onConfirm, + onCancel, + pairingPin, + device, +}: BluetoothPairingPinProps) => ( + + Connect + + Cancel + + + } + > + + + {pairingPin.pin} + + + + +); diff --git a/packages/suite/src/components/suite/bluetooth/BluetoothTips.tsx b/packages/suite/src/components/suite/bluetooth/BluetoothTips.tsx new file mode 100644 index 00000000000..9c2ce4957a3 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/BluetoothTips.tsx @@ -0,0 +1,55 @@ +import { Button, Card, Column, Divider, Icon, IconName, Row, Text } from '@trezor/components'; +import { spacings } from '@trezor/theme'; + +type BluetoothTipProps = { + icon: IconName; + header: string; + text: string; +}; + +const BluetoothTip = ({ icon, header, text }: BluetoothTipProps) => ( + + + + {header} + + {text} + + + +); + +type BluetoothTipsProps = { + onReScanClick: () => void; +}; + +export const BluetoothTips = ({ onReScanClick }: BluetoothTipsProps) => ( + + + + Check tips & try again + + + + + + + + + + +); diff --git a/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotAllowedForSuite.tsx b/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotAllowedForSuite.tsx new file mode 100644 index 00000000000..591b2b8185b --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotAllowedForSuite.tsx @@ -0,0 +1,3 @@ +export const BluetoothNotAllowedForSuite = () => { + return <>; +}; diff --git a/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotEnabled.tsx b/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotEnabled.tsx new file mode 100644 index 00000000000..84e7004af97 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/errors/BluetoothNotEnabled.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; + +import { Text, NewModal, Column, Banner } from '@trezor/components'; +import { desktopApi } from '@trezor/suite-desktop-api'; +import { spacings } from '@trezor/theme'; + +type BluetoothNotEnabledProps = { + onCancel: () => void; +}; + +export const BluetoothNotEnabled = ({ onCancel }: BluetoothNotEnabledProps) => { + const [hasDeeplinkFailed, setHasDeeplinkFailed] = useState(false); + + const openSettings = async () => { + const opened = await desktopApi.bluetoothOpenSettings(); + if (!opened.success || !opened.payload) { + setHasDeeplinkFailed(true); + } + }; + + return ( + + Enable bluetooth + + Cancel + + + } + > + + Enable bluetooth on your computer + + Or connect your Trezor via cable. + + {hasDeeplinkFailed && ( + + Cannot open bluetooth settings. Please enable bluetooth manually. + + )} + + + ); +}; diff --git a/packages/suite/src/components/suite/bluetooth/errors/BluetoothVersionNotCompatible.tsx b/packages/suite/src/components/suite/bluetooth/errors/BluetoothVersionNotCompatible.tsx new file mode 100644 index 00000000000..1c176f682cf --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/errors/BluetoothVersionNotCompatible.tsx @@ -0,0 +1,30 @@ +import { Text, NewModal, Column } from '@trezor/components'; +import { spacings } from '@trezor/theme'; + +type BluetoothVersionNotCompatibleProps = { + onCancel: () => void; +}; + +export const BluetoothVersionNotCompatible = ({ onCancel }: BluetoothVersionNotCompatibleProps) => ( + + + Cancel + + + } + > + + + Your computer’s bluetooth version is not compatible with whatever we’re using copy. + + + Use cable, buy a 5.0+ dongle. + + + +); diff --git a/packages/suite/src/components/suite/bluetooth/types.ts b/packages/suite/src/components/suite/bluetooth/types.ts new file mode 100644 index 00000000000..30ec68f0fa7 --- /dev/null +++ b/packages/suite/src/components/suite/bluetooth/types.ts @@ -0,0 +1,2 @@ +// TODO: this from transport or from connect? + diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/SelectBluetoothDeviceModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/SelectBluetoothDeviceModal.tsx deleted file mode 100644 index 0c0b8061201..00000000000 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/SelectBluetoothDeviceModal.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import { useEffect, useState } from 'react'; - -import styled from 'styled-components'; - -import TrezorConnect from '@trezor/connect'; -import { Button, Spinner, Banner } from '@trezor/components'; -import { desktopApi } from '@trezor/suite-desktop-api'; -import { UserContextPayload } from '@suite-common/suite-types'; -import { notificationsActions } from '@suite-common/toast-notifications'; - -import { Modal } from 'src/components/suite'; -import { useDispatch } from 'src/hooks/suite'; - -const SmallModal = styled(Modal)` - width: 560px; - min-height: 300px; -`; - -const Loader = styled.div` - display: flex; - flex-direction: row; -`; - -const StyledSpinner = styled(Spinner)` - margin-right: 12px; -`; - -const DeviceList = styled.div` - padding: 8px; -`; - -const DeviceButton = styled(Button)` - margin: 8px 0px; - width: 100%; - flex-direction: row; - border-radius: 0%; -`; - -const DeviceLabel = styled.div` - display: flex; - flex-direction: column; -`; - -const DeviceDetails = styled.div` - display: flex; - flex-direction: row; - font-size: 12px; -`; - -type SelectBluetoothDeviceProps = Omit< - Extract, - 'type' -> & { - onCancel: () => void; -}; - -// TODO: this from transport or from connect? -interface ElectronBluetoothDevice { - uuid: string; - name: string; - paired: boolean; - connected: boolean; - timestamp: number; - rssi: number; -} - -const ScannedDeviceItem = ({ - device, - onClick, - connecting, -}: { - device: ElectronBluetoothDevice; - onClick: any; - connecting: any; - lastSeenTimestamp: any; -}) => { - const timestamp = device.timestamp - ? new Date(device.timestamp * 1000).toLocaleTimeString('en-US', { hour12: false }) - : 'Unknown'; - // const lastSeenInSec = Math.floor((Date.now() - lastSeenTimestamp) / 1000); - // const seenQuiteLongAgo = lastSeenInSec > 5; - - return ( - - - {`${device.name} (${device.uuid})`} - Last seen: {timestamp} - - Paired: {device.paired ? 'yes' : 'no'}, Pairing mode:{' '} - {device.pairable ? 'yes' : 'no'}, Signal strength: {device.rssi} - - - {/* {seenQuiteLongAgo && Last seen: {lastSeenInSec}s ago} */} - - ); -}; - -const EnableAdapter = () => { - const openSettings = async () => { - const opened = await desktopApi.bluetoothOpenSettings(); - if (opened.success && opened.payload) { - console.warn('opened!'); - } else { - console.warn('not opened!'); - } - }; - - return ( -
- - Bluetooth is not enabled - - -
- ); -}; - -export const SelectBluetoothDeviceModal = ({ onCancel }: SelectBluetoothDeviceProps) => { - const dispatch = useDispatch(); - const [isBluetoothEnabled, setBluetoothEnabled] = useState(true); - const [connectingStatus, setConnectingStatus] = useState(undefined); - const [pairingPin, setPairingPin] = useState<{ paired: boolean; pin: string } | undefined>( - undefined, - ); - const [deviceList, setDeviceList] = useState([]); - - useEffect(() => { - desktopApi.on('bluetooth/adapter-event', powered => { - console.warn('bluetooth/adapter-event', powered); - setBluetoothEnabled(powered); - if (!powered) { - setDeviceList([]); - } - }); - - desktopApi.on('bluetooth/select-device-event', list => { - console.warn('bluetooth/select-device-event', list); - setDeviceList(list); - }); - - desktopApi.on('bluetooth/connect-device-event', ({ device, phase }) => { - console.warn('bluetooth/connect-device-event', phase, device); - setConnectingStatus({ device, status: phase }); - if (phase === 'connected') { - TrezorConnect.on('device-connect', () => { - onCancel(); - }); - } - if (phase === 'error') { - } - }); - - desktopApi.on('bluetooth/pair-device-event', event => { - setPairingPin(event); - }); - - return () => { - desktopApi.removeAllListeners('bluetooth/adapter-event'); - desktopApi.removeAllListeners('bluetooth/select-device-event'); - desktopApi.removeAllListeners('bluetooth/pair-device-event'); - desktopApi.removeAllListeners('bluetooth/connect-device-event'); - }; - }, [onCancel]); - - useEffect(() => { - desktopApi.bluetoothRequestDevice(); - }, []); - - const onSelect = async (deviceId?: string) => { - const result = await desktopApi.bluetoothConnectDevice(deviceId); - console.warn('On select Result!', result); - - if (!result.success) { - setPairingPin(undefined); - dispatch( - notificationsActions.addToast({ - type: 'error', - error: result.error, - }), - ); - } - }; - - const close = () => { - onSelect(); - onCancel(); - }; - - const isLoading = connectingStatus && connectingStatus.status !== 'error'; - - return ( - Select bluetooth device} - isCancelable - hasBackdropCancel={false} - onCancel={close} - > - {!isBluetoothEnabled && } - {isBluetoothEnabled && pairingPin && ( -
-

Pin {pairingPin.pin}

-

Status: {pairingPin.paired}

-

Confirm PIN on your device.

-
- )} - {isBluetoothEnabled && !pairingPin && deviceList.length > 0 && ( - - {deviceList.map(d => ( - onSelect(d.uuid)} - connecting={isLoading} - lastSeenTimestamp={0} - /> - ))} - - )} - {isBluetoothEnabled && deviceList.length === 0 && ( - - - Looking for devices... - - )} - {isBluetoothEnabled && connectingStatus && ( -
Connection status: {connectingStatus.status}
- )} - - {isBluetoothEnabled && connectingStatus?.status === 'pairing' && ( -
- {pairingPin &&

{pairingPin}

} -

Paring Trezor with your operating system.

-

Confirm connection on you device then compare and confirm PIN.

-
- )} -
- ); -}; diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx index dbc0f47b031..68e58eef185 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx @@ -24,7 +24,6 @@ import { DisableTorModal, DisableTorStopCoinjoinModal, RequestEnableTorModal, - SelectBluetoothDeviceModal, TorLoadingModal, CancelCoinjoinModal, CriticalCoinjoinPhaseModal, @@ -207,8 +206,8 @@ export const UserContextModal = ({ return ; case 'passphrase-mismatch-warning': return ; - case 'select-bluetooth-device': - return ; + // case 'select-bluetooth-device': + // return ; default: return null; } diff --git a/packages/suite/src/components/suite/modals/index.tsx b/packages/suite/src/components/suite/modals/index.tsx index f328f6240f1..ec560a00949 100644 --- a/packages/suite/src/components/suite/modals/index.tsx +++ b/packages/suite/src/components/suite/modals/index.tsx @@ -48,4 +48,3 @@ export { ClaimModal } from './ReduxModal/UserContextModal/ClaimModal/ClaimModal' export { MultiShareBackupModal } from './ReduxModal/UserContextModal/MultiShareBackupModal/MultiShareBackupModal'; export { CopyAddressModal } from './ReduxModal/CopyAddressModal'; export { UnhideTokenModal } from './ReduxModal/UnhideTokenModal'; -export { SelectBluetoothDeviceModal } from './ReduxModal/UserContextModal/SelectBluetoothDeviceModal';