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
  • Loading branch information
peter-sanderson authored and szymonlesisz committed Nov 29, 2024
1 parent ed423ce commit 9bdd3ac
Show file tree
Hide file tree
Showing 21 changed files with 889 additions and 91 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
106 changes: 51 additions & 55 deletions packages/suite-desktop-core/src/modules/bluetooth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ export const init: ModuleInit = ({ mainWindowProxy }) => {

ipcMain.handle('bluetooth/connect-device', async (_, uuid) => {
const api = new TrezorBle({});
await api.connect();
const connectionError = await api
.connect()
.catch(error => ({ success: false, error: error.message }));
if (connectionError) {
return connectionError;
}

const emitStatus = (event: BluetoothDeviceConnectionStatus) => {
mainWindowProxy
Expand All @@ -89,37 +94,39 @@ export const init: ModuleInit = ({ mainWindowProxy }) => {
}
});

const result = await api.sendMessage('connect_device', uuid);
const result = await api
.sendMessage('connect_device', uuid)
.then(() => ({ success: true }) as const)
.catch(error => ({ success: false, error: error.message }));

console.warn('Connect result', result);

api.disconnect();

if (result !== true) {
console.warn('ERROR!', result);
// mainWindowProxy
// .getInstance()
// ?.webContents.send('bluetooth/connect-device-event', { phase: 'error' });

return { success: false, error: result };
} else {
return { success: true };
}
return result;
});

ipcMain.handle('bluetooth/forget-device', async (_, uuid) => {
const api = new TrezorBle({});
await api.connect();
const connectionError = await api
.connect()
.catch(error => ({ success: false, error: error.message }));
if (connectionError) {
return connectionError;
}

const result = await api.sendMessage('forget_device', uuid);
const result = await api
.sendMessage('forget_device', uuid)
.then(() => ({ success: true }) as const)
.catch(error => ({ success: false, error: error.message }));
console.warn('Forget result', result);

api.disconnect();

return { success: true };
return result;
});

ipcMain.on('bluetooth/request-device', async () => {
console.warn('CALLIN BT REQUEST DEVICE!');
const api = new TrezorBle({});

const emitSelect = ({ devices }: { devices: BluetoothDevice[] }) => {
Expand All @@ -134,16 +141,29 @@ export const init: ModuleInit = ({ mainWindowProxy }) => {
if (!powered) {
// api.sendMessage('stop_scan');
} else {
api.sendMessage('start_scan');
api.sendMessage('start_scan').catch(error => {
console.warn('Start scan error', error);
});
}
};

emitSelect({ devices: [] });

// TODO: here race condition with bluetooth/select-device response?
await api.connect();
// const info = await api.sendMessage('get_info', true);
const info = await api.sendMessage('get_info');
const connectionError = await api
.connect()
.catch(error => ({ success: false, error: error.message }));
if (connectionError) {
return connectionError;
}

const info = await api
.sendMessage('get_info')
.catch(error => ({ success: false, error: error.message }));
if ('success' in info) {
console.warn('Api info error', info);

return info;
}

console.warn('Api info', info);

Expand All @@ -157,40 +177,16 @@ export const init: ModuleInit = ({ mainWindowProxy }) => {
api.on('DeviceDisconnected', emitSelect);
api.on('AdapterStateChanged', emitAdapterState);

const devices = await api.sendMessage('start_scan');

emitSelect({ devices });

// const clear = () => {
// ipcMain.removeAllListeners('bluetooth/stop-scan');
// // ipcMain.removeHandler('bluetooth/connect-device');
// api.removeAllListeners();
// // await api.sendMessage('stop_scan');
// api.disconnect();
// };

// const handler = async (_: any, deviceId: string) => {
// logger.info(SERVICE_NAME, 'Received device ' + deviceId);
// console.warn('bluetooth/connect-device', deviceId);
// if (!deviceId) {
// clear();

// return { success: true };
// }
// const connected = await api.sendMessage('connect_device', deviceId);
// if (connected !== true) {
// console.warn('ERROR!', connected);
// // mainWindowProxy
// // .getInstance()
// // ?.webContents.send('bluetooth/connect-device-event', { phase: 'error' });

// return { success: false, error: connected };
// } else {
// clear();
// }

// return { success: true };
// };
const result = await api
.sendMessage('start_scan')
.catch(error => ({ success: false, error: error.message }));
if ('success' in result) {
console.warn('Start scan error', result);

return result;
}

emitSelect({ devices: result });

ipcMain.on('bluetooth/stop-scan', () => {
ipcMain.removeAllListeners('bluetooth/stop-scan');
Expand Down
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,14 +1,14 @@
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';
import { isWebUsb } from 'src/utils/suite/transport';
import { isBluetoothTransport, isWebUsb } from 'src/utils/suite/transport';
import { useDispatch, useSelector } from 'src/hooks/suite';
import { selectPrerequisite } from 'src/reducers/suite/suiteReducer';
import { goto } from 'src/actions/suite/routerActions';
Expand All @@ -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;
Expand All @@ -48,14 +49,18 @@ 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;
const transport = useSelector(state => state.suite.transport);
const prerequisite = useSelector(selectPrerequisite);

const isWebUsbTransport = isWebUsb(transport);
const isBluetooth = isBluetoothTransport(transport);

const TipComponent = useMemo(
() => (): React.JSX.Element => {
Expand All @@ -65,7 +70,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-unreadable':
Expand All @@ -91,38 +102,49 @@ 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>
<BluetoothConnect onClose={() => setIsBluetoothConnectOpen(false)} />
</ElevationDown>
</ElevationContext>
) : (
<>
<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>
)}

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

<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 9bdd3ac

Please sign in to comment.