Skip to content

Commit

Permalink
feat: add filtering, to hide errored newrby devices
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-sanderson committed Feb 26, 2025
1 parent ad15ba6 commit 20f538d
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,13 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
}

if (selectedDevice !== undefined) {
return <BluetoothSelectedDevice device={selectedDevice} onReScanClick={onReScanClick} />;
return (
<BluetoothSelectedDevice
device={selectedDevice}
onReScanClick={onReScanClick}
onCancel={handlePairingCancel}
/>
);
}

const content = scanFailed ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export const BluetoothDeviceComponent = ({ device, flex, margin }: BluetoothDevi

<Column justifyContent="start" alignItems="start" flex="1">
<Text typographyStyle="body">Trezor Safe 7</Text>

<Text typographyStyle="hint" variant="tertiary">
<pre>{device.macAddress}</pre>
</Text>
<Row>
<Text typographyStyle="hint" variant="tertiary">
{colorName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BluetoothDeviceComponent } from './BluetoothDeviceComponent';

const map: Record<DeviceBluetoothStatusType, string> = {
connecting: 'Connecting',
connected: 'Conencted',
connected: 'Connected',
error: 'Error',
pairing: 'Pairing',
paired: 'Paired',
Expand All @@ -23,19 +23,17 @@ export const BluetoothDeviceItem = ({ device, onClick }: BluetoothDeviceItemProp
const isLoading = device.status?.type === 'pairing' || device.status?.type === 'connecting';

return (
<Row onClick={isDisabled ? undefined : onClick} gap={spacings.md} alignItems="stretch">
<Row onClick={/*isDisabled ? undefined : */ onClick} gap={spacings.md} alignItems="stretch">
<BluetoothDeviceComponent device={device.device} flex="1" />
{device.status && (
<Button
variant="primary"
size="small"
margin={{ vertical: spacings.xxs }}
isDisabled={isDisabled}
isLoading={isLoading}
>
{map[device.status?.type]}
</Button>
)}
<Button
variant="primary"
size="small"
margin={{ vertical: spacings.xxs }}
isDisabled={isDisabled}
isLoading={isLoading}
>
{device.status ? map[device.status?.type] : 'Connect'}
</Button>
</Row>
);
};
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
import { ReactNode } from 'react';

import { BluetoothDeviceState, DeviceBluetoothStatusType } from '@suite-common/bluetooth';
import { Card, ElevationContext, Icon, Row, Spinner, Text } from '@trezor/components';
import {
Button,
Card,
Column,
ElevationContext,
Icon,
Row,
Spinner,
Text,
} from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice } from '@trezor/transport-bluetooth';

import { BluetoothDeviceComponent } from './BluetoothDeviceComponent';
import { BluetoothTips } from './BluetoothTips';

const PairedComponent = () => (
<Row gap={spacings.xs} alignItems="center">
<Row gap={spacings.md} alignItems="center">
<Icon size="small" name="check"></Icon>
<Text variant="primary">Paired</Text>
</Row>
);

const PairingComponent = () => (
<Row gap={spacings.xs} alignItems="center">
<Spinner size={spacings.sm} />
<Row gap={spacings.xxs} alignItems="center">
<Spinner size={spacings.md} />
<Text variant="tertiary">Pairing</Text>
</Row>
);

const ConnectingComponent = () => (
<Row gap={spacings.xs} alignItems="center">
<Spinner size={spacings.sm} />
<Row gap={spacings.xxs} alignItems="center">
<Spinner size={spacings.md} />
<Text variant="tertiary">Connecting</Text>
</Row>
);

export type OkComponentProps = {
device: BluetoothDeviceState<BluetoothDevice>;
onCancel: () => void;
};

const OkComponent = ({ device }: OkComponentProps) => {
const OkComponent = ({ device, onCancel }: OkComponentProps) => {
const map: Record<DeviceBluetoothStatusType, ReactNode> = {
connecting: <ConnectingComponent />,
connected: null,
Expand All @@ -46,7 +56,12 @@ const OkComponent = ({ device }: OkComponentProps) => {
<Row gap={spacings.md} alignItems="center" justifyContent="stretch">
<BluetoothDeviceComponent device={device.device} flex="1" />

{device?.status?.type !== undefined ? map[device?.status?.type] : null}
<Column alignItems="center" gap={spacings.md}>
{device?.status?.type !== undefined ? map[device?.status?.type] : null}
<Button onClick={onCancel} variant="tertiary" size="small">
Cancel
</Button>
</Column>
</Row>
);
};
Expand All @@ -67,18 +82,20 @@ const ErrorComponent = ({ device, onReScanClick }: ErrorComponentProps) => {
export type BluetoothSelectedDeviceProps = {
device: BluetoothDeviceState<BluetoothDevice>;
onReScanClick: () => void;
onCancel: () => void;
};

export const BluetoothSelectedDevice = ({
device,
onReScanClick,
onCancel,
}: BluetoothSelectedDeviceProps) => (
<ElevationContext baseElevation={0}>
{device?.status?.type === 'error' ? (
<ErrorComponent onReScanClick={onReScanClick} device={device} />
) : (
<Card>
<OkComponent device={device} />
<OkComponent device={device} onCancel={onCancel} />
</Card>
)}
</ElevationContext>
Expand Down
13 changes: 9 additions & 4 deletions suite-common/bluetooth/src/bluetoothSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ export const prepareSelectAllDevices = <T extends BluetoothDeviceCommon>() =>
(nearbyDevices, knownDevices) => {
const map = new Map<string, BluetoothDeviceState<T>>();

nearbyDevices.forEach(nearbyDevice => {
map.set(nearbyDevice.device.id, nearbyDevice);
});
nearbyDevices
// Devices with error status should be displayed in the list, as it
// won't be possible to connect to them ever again. User has to start
// pairing again, which would produce a device with new id.
.filter(nearbyDevice => nearbyDevice.status?.type !== 'error')
.forEach(nearbyDevice => {
map.set(nearbyDevice.device.id, nearbyDevice);
});

knownDevices.forEach(knownDevice => {
// All known devices are automatically considered being in 'connecting' state
// underlying code should connect them automatically
// underlying code should connect them automatically.
map.set(knownDevice.id, { device: knownDevice, status: { type: 'connecting' } });
});

Expand Down
20 changes: 17 additions & 3 deletions suite-common/bluetooth/tests/bluetoothSelectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,35 @@ const deviceB: BluetoothDeviceCommon = {
lastUpdatedTimestamp: 2,
};

const pairingDeviceStateC: BluetoothDeviceState<BluetoothDeviceCommon> = {
device: {
id: 'C',
data: [],
name: 'Trezor C',
lastUpdatedTimestamp: 3,
},
status: { type: 'pairing' },
};

describe('bluetoothSelectors', () => {
it('selects knownDevices and nearbyDevices in one list fot the UI', () => {
it('selects knownDevices and nearbyDevices in one list fot the UI, all known devices are', () => {
const selectAllDevices = prepareSelectAllDevices<BluetoothDeviceCommon>();

const state: WithBluetoothState<BluetoothDeviceCommon> = {
bluetooth: {
...initialState,
nearbyDevices: [pairingDeviceStateA],
nearbyDevices: [pairingDeviceStateA, pairingDeviceStateC],
knownDevices: [pairingDeviceStateA.device, deviceB],
},
};

const devices = selectAllDevices(state);

expect(devices).toEqual([{ device: deviceB, status: null }, pairingDeviceStateA]);
expect(devices).toEqual([
pairingDeviceStateC,
{ device: deviceB, status: { type: 'connecting' } },
{ device: pairingDeviceStateA.device, status: { type: 'connecting' } }, // overrides to 'connecting'
]);

const devicesSecondTime = selectAllDevices(state);
expect(devices === devicesSecondTime).toBe(true); // Asserts that `reselect` memoization works
Expand Down

0 comments on commit 20f538d

Please sign in to comment.