Skip to content

Commit

Permalink
fixup! WIP: design
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-sanderson committed Dec 6, 2024
1 parent 5c99d12 commit 6de6a8e
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 136 deletions.
3 changes: 3 additions & 0 deletions packages/suite-desktop-ui/src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useDebugLanguageShortcut, useFormattersConfig } from 'src/hooks/suite';
import history from 'src/support/history';
import { ModalContextProvider } from 'src/support/suite/ModalContext';
import { desktopHandshake } from 'src/actions/suite/suiteActions';
import { initBluetoothThunk } from 'src/actions/bluetooth/initBluetoothThunk';
import * as STORAGE from 'src/actions/suite/constants/storageConstants';

import { DesktopUpdater } from './support/DesktopUpdater';
Expand Down Expand Up @@ -134,6 +135,8 @@ export const init = async (container: HTMLElement) => {
TrezorConnect[method] = proxy[method];
});

store.dispatch(initBluetoothThunk());

// finally render whole app
root.render(
<ReduxProvider store={store}>
Expand Down
10 changes: 2 additions & 8 deletions packages/suite/src/actions/bluetooth/bluetoothActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@ export const bluetoothDeviceListUpdate = createAction(

export const bluetoothConnectDeviceEventAction = createAction(
`${BLUETOOTH_PREFIX}/device-connection-status`,
({ connectionStatus }: { connectionStatus: DeviceBluetoothStatus }) => ({
payload: { connectionStatus },
({ connectionStatus, uuid }: { uuid: string; connectionStatus: DeviceBluetoothStatus }) => ({
payload: { uuid, connectionStatus },
}),
);

export const bluetoothSelectDeviceAction = createAction(
`${BLUETOOTH_PREFIX}/select-device`,
({ uuid }: { uuid: string | undefined }) => ({ payload: { uuid } }),
);

export const bluetoothScanStatusAction = createAction(
`${BLUETOOTH_PREFIX}/scan-status`,
({ status }: { status: BluetoothScanStatus }) => ({ payload: { status } }),
Expand All @@ -40,6 +35,5 @@ export const allBluetoothActions = {
bluetoothAdapterEventAction,
bluetoothDeviceListUpdate,
bluetoothConnectDeviceEventAction,
bluetoothSelectDeviceAction,
bluetoothScanStatusAction,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothManager } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

type ThunkResponse = ReturnType<typeof bluetoothManager.connectDevice>;

export const bluetoothConnectDeviceThunk = createThunk<ThunkResponse, { uuid: string }, void>(
`${BLUETOOTH_PREFIX}/bluetoothConnectDeviceThunk`,
async ({ uuid }, { fulfillWithValue }) => {
const result = await bluetoothManager.connectDevice(uuid);

return fulfillWithValue(result);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothManager } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

export const bluetoothStartScanningThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/bluetoothStartScanningThunk`,
_ => {
// This can fail, but if there is an error we already got it from `adapter-event`
// and user is informed about it (bluetooth turned-off, ...)
bluetoothManager.startScan();
},
);
12 changes: 12 additions & 0 deletions packages/suite/src/actions/bluetooth/bluetoothStopScanningThunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothManager } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

export const bluetoothStopScanningThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/bluetoothStopScanningThunk`,
_ => {
// This can fail, but there is nothing we can do about it
bluetoothManager.stopScan();
},
);
44 changes: 44 additions & 0 deletions packages/suite/src/actions/bluetooth/initBluetoothThunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createThunk } from '@suite-common/redux-utils/';
import { bluetoothManager, DeviceConnectionStatus } from '@trezor/transport-bluetooth';
import { Without } from '@trezor/type-utils';

import {
BLUETOOTH_PREFIX,
bluetoothAdapterEventAction,
bluetoothConnectDeviceEventAction,
bluetoothDeviceListUpdate,
} from './bluetoothActions';

type DeviceConnectionStatusWithOptionalUuid = Without<DeviceConnectionStatus, 'uuid'> & {
uuid?: string;
};

export const initBluetoothThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/initBluetoothThunk`,
(_, { dispatch }) => {
bluetoothManager.on('adapter-event', isPowered => {
console.warn('adapter-event', isPowered);
dispatch(bluetoothAdapterEventAction({ isPowered }));
});

bluetoothManager.on('device-list-update', devices => {
console.warn('device-list-update', devices);
dispatch(bluetoothDeviceListUpdate({ devices }));
});

bluetoothManager.on('device-connection-status', connectionStatus => {
console.warn('device-connection-status', connectionStatus);
const copyConnectionStatus: DeviceConnectionStatusWithOptionalUuid = {
...connectionStatus,
};
delete copyConnectionStatus.uuid; // So we dont pollute redux store

dispatch(
bluetoothConnectDeviceEventAction({
uuid: connectionStatus.uuid,
connectionStatus: copyConnectionStatus,
}),
);
});
},
);
83 changes: 29 additions & 54 deletions packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import TrezorConnect from '@trezor/connect';
import { Card, Column, ElevationUp } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { notificationsActions } from '@suite-common/toast-notifications';
import { bluetoothManager } from '@trezor/transport-bluetooth';

import { BluetoothNotEnabled } from './errors/BluetoothNotEnabled';
import { BluetoothDeviceList } from './BluetoothDeviceList';
Expand All @@ -15,19 +14,18 @@ import { BluetoothScanFooter } from './BluetoothScanFooter';
import { useDispatch, useSelector } from '../../../hooks/suite';
import { BluetoothSelectedDevice } from './BluetoothSelectedDevice';
import {
bluetoothAdapterEventAction,
bluetoothConnectDeviceEventAction,
bluetoothDeviceListUpdate,
bluetoothScanStatusAction,
bluetoothSelectDeviceAction,
} from '../../../actions/bluetooth/bluetoothActions';
import {
selectBluetoothDeviceList,
selectBluetoothEnabled,
selectBluetoothScanStatus,
selectBluetoothSelectedDevice,
} from '../../../reducers/bluetooth/bluetoothSelectors';
import { BluetoothPairingPin } from './BluetoothPairingPin';
import { bluetoothStartScanningThunk } from '../../../actions/bluetooth/bluetoothStartScanningThunk';
import { bluetoothStopScanningThunk } from '../../../actions/bluetooth/bluetoothStopScanningThunk';
import { bluetoothConnectDeviceThunk } from '../../../actions/bluetooth/bluetoothConnectDeviceThunk';

const SCAN_TIMEOUT = 30_000;

Expand All @@ -40,42 +38,22 @@ type TimerId = ReturnType<typeof setTimeout>; // Todo: TimerId import type after

export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) => {
const dispatch = useDispatch();
const [selectedDeviceUuid, setSelectedDeviceUuid] = useState<string | null>(null);
const [scannerTimerId, setScannerTimerId] = useState<TimerId | null>(null);

const isBluetoothEnabled = useSelector(selectBluetoothEnabled);
const scanStatus = useSelector(selectBluetoothScanStatus);
const selectedDevice = useSelector(selectBluetoothSelectedDevice);
const deviceList = useSelector(selectBluetoothDeviceList);
const devices = Object.values(deviceList);

// Todo: move this to some Singleton component to synchronize the bluetoothManager with Redux State
// Todo: or move to action in the same manner as TrezorConnect.init() is initialized
// See: package/suite/services
useEffect(() => {
bluetoothManager.on('adapter-event', isPowered => {
console.warn('adapter-event', isPowered);
dispatch(bluetoothAdapterEventAction({ isPowered }));
});

bluetoothManager.on('device-list-update', devices => {
console.warn('device-list-update', devices);
dispatch(bluetoothDeviceListUpdate({ devices }));
});

bluetoothManager.on('device-connection-status', connectionStatus => {
console.warn('device-connection-status', connectionStatus);
dispatch(bluetoothConnectDeviceEventAction({ connectionStatus }));
});
const selectedDevice =
selectedDeviceUuid !== null ? deviceList[selectedDeviceUuid] ?? null : null;

// Todo: this shall not be on top-level, this shall be called onClick on the connect button
bluetoothManager.startScan();
useEffect(() => {
dispatch(bluetoothStartScanningThunk());

return () => {
bluetoothManager.removeAllListeners('adapter-event');
bluetoothManager.removeAllListeners('device-list-update');
bluetoothManager.removeAllListeners('device-connection-status');

// Todo: move this to action and run on close or something
bluetoothManager.stopScan();
dispatch(bluetoothStopScanningThunk());
};
}, [dispatch]);

Expand All @@ -95,7 +73,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
}, [dispatch]);

const onReScanClick = () => {
dispatch(bluetoothSelectDeviceAction({ uuid: undefined }));
setSelectedDeviceUuid(null);
dispatch(bluetoothScanStatusAction({ status: 'running' }));

clearScamTimer();
Expand All @@ -106,15 +84,15 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
};

const onSelect = async (uuid: string) => {
dispatch(bluetoothSelectDeviceAction({ uuid }));
setSelectedDeviceUuid(uuid);

// Todo move this to action and call this in thunk
const result = await bluetoothManager.connectDevice(uuid);
const result = await dispatch(bluetoothConnectDeviceThunk({ uuid })).unwrap();

if (!result.success) {
dispatch(
bluetoothConnectDeviceEventAction({
connectionStatus: { type: 'error', uuid, error: result.error },
uuid,
connectionStatus: { type: 'error', error: result.error },
}),
);
dispatch(
Expand All @@ -128,7 +106,8 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>

dispatch(
bluetoothConnectDeviceEventAction({
connectionStatus: { type: 'connected', uuid },
uuid,
connectionStatus: { type: 'connected' },
}),
);

Expand All @@ -148,6 +127,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
}

// Todo: incompatible version
// eslint-disable-next-line no-constant-condition
if (false) {
return <BluetoothVersionNotCompatible onCancel={onClose} />;
}
Expand All @@ -156,19 +136,15 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>

// This is fake, we scan for devices all the time
const isScanning = scanStatus !== 'done';
const scanFailed = deviceList.length === 0 && scanStatus === 'done';
const scanFailed = devices.length === 0 && scanStatus === 'done';

const handlePairingCancel = () => {
dispatch(bluetoothSelectDeviceAction({ uuid: undefined }));
setSelectedDeviceUuid(null);
onReScanClick();
};

if (selectedDevice !== undefined && selectedDevice.status.type !== 'pairing') {
return <BluetoothSelectedDevice device={selectedDevice} onReScanClick={onReScanClick} />;
}

if (
selectedDevice !== undefined &&
selectedDevice !== null &&
selectedDevice.status.type === 'pairing' &&
(selectedDevice.status.pin?.length ?? 0) > 0
) {
Expand All @@ -181,15 +157,14 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
);
}

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

const content = scanFailed ? (
<BluetoothTips onReScanClick={onReScanClick} header="Check tips & try again" />
) : (
<BluetoothDeviceList
isDisabled={selectedDevice !== undefined}
onSelect={onSelect}
deviceList={deviceList}
isScanning={isScanning}
/>
<BluetoothDeviceList onSelect={onSelect} deviceList={devices} isScanning={isScanning} />
);

return (
Expand All @@ -203,7 +178,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
<BluetoothScanHeader
isScanning={isScanning}
onClose={onClose}
numberOfDevices={deviceList.length}
numberOfDevices={devices.length}
/>

{/* Here we need to do +1 in elevation because of custom design on the welcome screen */}
Expand All @@ -212,7 +187,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
{uiMode === 'card' && (
<BluetoothScanFooter
onReScanClick={onReScanClick}
numberOfDevices={deviceList.length}
numberOfDevices={devices.length}
scanStatus={scanStatus}
/>
)}
Expand All @@ -225,7 +200,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
<ElevationUp>
<BluetoothScanFooter
onReScanClick={onReScanClick}
numberOfDevices={deviceList.length}
numberOfDevices={devices.length}
scanStatus={scanStatus}
/>
</ElevationUp>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BluetoothDevice } from './BluetoothDevice';
type BluetoothDeviceItemProps = {
device: BluetoothDeviceType;
onClick: () => void;
isDisabled: boolean;
isDisabled?: boolean;
};

export const BluetoothDeviceItem = ({ device, onClick, isDisabled }: BluetoothDeviceItemProps) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export const BluetoothDeviceList = ({
onSelect,
deviceList,
isScanning,
isDisabled,
}: BluetoothDeviceListProps) => (
<Card>
<Column gap={spacings.md} alignItems="stretch">
Expand All @@ -35,7 +34,6 @@ export const BluetoothDeviceList = ({
key={d.device.uuid}
device={d.device}
onClick={() => onSelect(d.device.uuid)}
isDisabled={isDisabled}
/>
))}
{isScanning && <SkeletonDevice />}
Expand Down
Loading

0 comments on commit 6de6a8e

Please sign in to comment.