Skip to content

Commit

Permalink
WIP: design
Browse files Browse the repository at this point in the history
feat: implement Bluetooth Onboarding UI

feat: different 'Not the Trezor you are looking for' UI

feat: implement Bluetooth Onboarding UI 2

feat: implement Bluetooth Onboarding UI

feat: different 'Not the Trezor you are looking for' UI

feat: implement Bluetooth Onboarding UI 2

feat: use CollapsibleBox component in Bluetooth UI

fix: after rebase

enable BT onboarding

WIP: transport name

breaking change

aftr rebase
  • Loading branch information
peter-sanderson authored and szymonlesisz committed Jan 30, 2025
1 parent ea89b74 commit 2c5ce6a
Show file tree
Hide file tree
Showing 32 changed files with 1,143 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ export const RotateDeviceImage = ({
return null;
}

// conflict here
// const isDeviceImageRotating =
// deviceModel &&
// [
// DeviceModelInternal.T2B1,
// DeviceModelInternal.T3B1,
// DeviceModelInternal.T3T1,
// DeviceModelInternal.T3W1,
// ].includes(deviceModel);

return (
<DeviceAnimation
className={className}
Expand Down
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 @@ -31,6 +31,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 @@ -136,6 +137,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
39 changes: 39 additions & 0 deletions packages/suite/src/actions/bluetooth/bluetoothActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createAction } from '@reduxjs/toolkit';

import { BluetoothDevice } from '@trezor/transport-bluetooth';

import {
BluetoothScanStatus,
DeviceBluetoothStatus,
} from '../../reducers/bluetooth/bluetoothReducer';

export const BLUETOOTH_PREFIX = '@suite/bluetooth';

export const bluetoothAdapterEventAction = createAction(
`${BLUETOOTH_PREFIX}/adapter-event`,
({ isPowered }: { isPowered: boolean }) => ({ payload: { isPowered } }),
);

export const bluetoothDeviceListUpdate = createAction(
`${BLUETOOTH_PREFIX}/device-list-update`,
({ devices }: { devices: BluetoothDevice[] }) => ({ payload: { devices } }),
);

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

export const bluetoothScanStatusAction = createAction(
`${BLUETOOTH_PREFIX}/scan-status`,
({ status }: { status: BluetoothScanStatus }) => ({ payload: { status } }),
);

export const allBluetoothActions = {
bluetoothAdapterEventAction,
bluetoothDeviceListUpdate,
bluetoothConnectDeviceEventAction,
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 { DeviceConnectionStatus, bluetoothManager } 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,
}),
);
});
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Button } from '@trezor/components';

import { Translation, TroubleshootingTips, WebUsbButton } from 'src/components/suite';
import {
TROUBLESHOOTING_TIP_BRIDGE_STATUS,
Expand All @@ -10,9 +12,15 @@ import {

interface DeviceConnectProps {
isWebUsbTransport: boolean;
isBluetooth: boolean;
onBluetoothClick: () => void;
}

export const DeviceConnect = ({ isWebUsbTransport }: DeviceConnectProps) => {
export const DeviceConnect = ({
isWebUsbTransport,
onBluetoothClick,
isBluetooth,
}: DeviceConnectProps) => {
const items = isWebUsbTransport
? [
TROUBLESHOOTING_TIP_UDEV,
Expand All @@ -32,7 +40,23 @@ export const DeviceConnect = ({ isWebUsbTransport }: DeviceConnectProps) => {
<TroubleshootingTips
label={<Translation id="TR_STILL_DONT_SEE_YOUR_TREZOR" />}
items={items}
cta={isWebUsbTransport ? <WebUsbButton data-testid="@webusb-button" /> : undefined}
cta={
// eslint-disable-next-line no-nested-ternary
isBluetooth ? (
<Button
variant="tertiary"
size="tiny"
onClick={e => {
e.stopPropagation();
onBluetoothClick();
}}
>
Connect Safe 7 via bluetooth
</Button>
) : isWebUsbTransport ? (
<WebUsbButton data-testid="@webusb-button" />
) : undefined
}
data-testid="@connect-device-prompt/no-device-detected"
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useMemo } from 'react';
import { useMemo, useState } from 'react';

import { motion } from 'framer-motion';
import styled from 'styled-components';

import { deviceNeedsAttention, getStatus } from '@suite-common/suite-utils';
import { selectDevices, selectSelectedDevice } from '@suite-common/wallet-core';
import { Button, motionEasing } from '@trezor/components';
import { Button, ElevationContext, ElevationDown, Flex, motionEasing } from '@trezor/components';

import { goto } from 'src/actions/suite/routerActions';
import { ConnectDevicePrompt, Translation } from 'src/components/suite';
Expand All @@ -26,6 +26,7 @@ import { DeviceUpdateRequired } from './DeviceUpdateRequired';
import { DeviceUsedElsewhere } from './DeviceUsedElsewhere';
import { MultiShareBackupInProgress } from './MultiShareBackupInProgress';
import { Transport } from './Transport';
import { BluetoothConnect } from '../bluetooth/BluetoothConnect';

const Wrapper = styled.div`
display: flex;
Expand All @@ -48,11 +49,14 @@ interface PrerequisitesGuideProps {
}

export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProps) => {
const [isBluetoothConnectOpen, setIsBluetoothConnectOpen] = useState(false);

const dispatch = useDispatch();
const device = useSelector(selectSelectedDevice);
const devices = useSelector(selectDevices);
const connectedDevicesCount = devices.filter(d => d.connected === true).length;
const isWebUsbTransport = useSelector(selectHasTransportOfType('WebUsbTransport'));
const isBluetooth = useSelector(selectHasTransportOfType('BluetoothTransport'));
const prerequisite = useSelector(selectPrerequisite);

const TipComponent = useMemo(
Expand All @@ -63,7 +67,13 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp
case 'device-disconnect-required':
return <DeviceDisconnectRequired />;
case 'device-disconnected':
return <DeviceConnect isWebUsbTransport={isWebUsbTransport} />;
return (
<DeviceConnect
isWebUsbTransport={isWebUsbTransport}
isBluetooth={isBluetooth}
onBluetoothClick={() => setIsBluetoothConnectOpen(true)}
/>
);
case 'device-unacquired':
return <DeviceAcquire />;
case 'device-used-elsewhere':
Expand Down Expand Up @@ -91,38 +101,54 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp
return <></>;
}
},
[prerequisite, isWebUsbTransport, device],
[prerequisite, isWebUsbTransport, isBluetooth, device],
);

const handleSwitchDeviceClick = () =>
dispatch(goto('suite-switch-device', { params: { cancelable: true } }));

return (
<Wrapper>
<ConnectDevicePrompt
connected={!!device}
showWarning={
!!(device && deviceNeedsAttention(getStatus(device))) ||
prerequisite === 'transport-bridge'
}
prerequisite={prerequisite}
/>

{allowSwitchDevice && connectedDevicesCount > 1 && (
<ButtonWrapper>
<Button variant="tertiary" onClick={handleSwitchDeviceClick}>
<Translation id="TR_SWITCH_DEVICE" />
</Button>
</ButtonWrapper>
)}
{isBluetoothConnectOpen ? (
<ElevationContext baseElevation={-1}>
{/* Here we need to draw the inner card with elevation -1 (custom design) */}
<ElevationDown>
<Flex width={470}>
<BluetoothConnect
onClose={() => setIsBluetoothConnectOpen(false)}
uiMode="spatial"
/>
</Flex>
</ElevationDown>
</ElevationContext>
) : (
<>
<ConnectDevicePrompt
connected={!!device}
showWarning={
!!(device && deviceNeedsAttention(getStatus(device))) ||
prerequisite === 'transport-bridge'
}
prerequisite={prerequisite}
/>

<TipsContainer
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6, duration: 0.5, ease: motionEasing.enter }}
>
<TipComponent />
</TipsContainer>
{allowSwitchDevice && connectedDevicesCount > 1 && (
<ButtonWrapper>
<Button variant="tertiary" onClick={handleSwitchDeviceClick}>
<Translation id="TR_SWITCH_DEVICE" />
</Button>
</ButtonWrapper>
)}

<TipsContainer
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6, duration: 0.5, ease: motionEasing.enter }}
>
<TipComponent />
</TipsContainer>
</>
)}
</Wrapper>
);
};
Loading

0 comments on commit 2c5ce6a

Please sign in to comment.