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 16, 2025
1 parent 21e3aa9 commit 56ff564
Show file tree
Hide file tree
Showing 31 changed files with 1,140 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 @@ -32,9 +32,12 @@ export const RotateDeviceImage = ({

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

return (
<>
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 @@ -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
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 { 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,
}),
);
});
},
);
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,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, motionEasing, ElevationContext, ElevationDown, Flex } from '@trezor/components';
import { selectDevices, selectSelectedDevice } from '@suite-common/wallet-core';

import { ConnectDevicePrompt, Translation } from 'src/components/suite';
Expand All @@ -26,6 +26,7 @@ import { DeviceUpdateRequired } from './DeviceUpdateRequired';
import { DeviceDisconnectRequired } from './DeviceDisconnectRequired';
import { MultiShareBackupInProgress } from './MultiShareBackupInProgress';
import { DeviceUsedElsewhere } from './DeviceUsedElsewhere';
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 56ff564

Please sign in to comment.