Skip to content

Commit 82635fb

Browse files
✨ (core) [LBD-30]: Device switch (#230)
2 parents a3c69f3 + d4a24dd commit 82635fb

File tree

8 files changed

+140
-159
lines changed

8 files changed

+140
-159
lines changed

packages/ledger-button/src/components/atom/chip/ledger-chip.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface LedgerChipAttributes {
1616

1717
const chipContainerVariants = cva([
1818
"lb-flex lb-h-40 lb-max-w-208 lb-cursor-pointer lb-items-center lb-justify-center lb-gap-8 lb-rounded-full lb-px-16 lb-py-8",
19-
"bg-muted-transparent hover:bg-muted-transparent-hover active:bg-muted-transparent-pressed",
19+
"lb-bg-muted-transparent hover:lb-bg-muted-transparent-hover active:lb-bg-muted-transparent-pressed",
2020
]);
2121

2222
const chipLabelVariants = cva(["body-2 lb-text-ellipsis lb-text-base"]);

packages/ledger-button/src/components/molecule/device-item/ledger-device-item.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,30 @@ export class LedgerDeviceItem extends LitElement {
144144
}
145145

146146
private renderDeviceIcon() {
147+
return html`
148+
<div class="lb-relative">
149+
<div
150+
class="lb-rounded-full lb-bg-muted-transparent lb-p-8 lb-drop-shadow-md"
151+
>
152+
<device-icon .modelId=${this.deviceModelId}></device-icon>
153+
</div>
154+
${this.renderConnectionBadge()}
155+
</div>
156+
`;
157+
}
158+
159+
private renderConnectionBadge() {
160+
if (!this.connectionType) return "";
161+
147162
return html`
148163
<div
149-
class="lb-rounded-full lb-bg-muted-transparent lb-p-8 lb-drop-shadow-md"
164+
class="lb-absolute -lb-bottom-2 -lb-right-2 lb-flex lb-h-20 lb-w-20 lb-items-center lb-justify-center lb-rounded-full lb-bg-base lb-shadow-sm"
150165
>
151-
<device-icon .modelId=${this.deviceModelId}></device-icon>
166+
<ledger-icon
167+
type=${this.connectionType}
168+
size="small"
169+
fillColor="var(--color-foreground)"
170+
></ledger-icon>
152171
</div>
153172
`;
154173
}

packages/ledger-button/src/components/molecule/toolbar/ledger-toolbar.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,24 @@ export class LedgerToolbar extends LitElement {
8383
<div
8484
class="lb-relative lb-flex lb-w-full lb-min-w-full lb-items-center lb-justify-between lb-px-24 lb-py-16"
8585
>
86-
<div class="lb-flex lb-h-32 lb-w-32 lb-items-center lb-justify-center">
87-
<slot name="left-icon">
88-
${this.canGoBack
89-
? html`
90-
<ledger-button
91-
data-testid="close-button"
92-
.icon=${true}
93-
variant="noBackground"
94-
iconType="back"
95-
size="xs"
96-
@click=${this.handleGoBackClick}
97-
>
98-
</ledger-button>
99-
`
100-
: html` <ledger-icon type="ledger" size="medium"></ledger-icon> `}
101-
</slot>
86+
<div class="lb-flex lb-w-72 lb-items-center lb-justify-start">
87+
<div class="lb-flex lb-h-32 lb-w-32 lb-items-center lb-justify-center">
88+
<slot name="left-icon">
89+
${this.canGoBack
90+
? html`
91+
<ledger-button
92+
data-testid="close-button"
93+
.icon=${true}
94+
variant="noBackground"
95+
iconType="back"
96+
size="xs"
97+
@click=${this.handleGoBackClick}
98+
>
99+
</ledger-button>
100+
`
101+
: html` <ledger-icon type="ledger" size="medium"></ledger-icon> `}
102+
</slot>
103+
</div>
102104
</div>
103105
<div
104106
class="lb-pointer-events-none lb-absolute lb-left-0 lb-right-0 lb-flex lb-items-center lb-justify-center"
@@ -120,7 +122,7 @@ export class LedgerToolbar extends LitElement {
120122
</div>
121123
</div>
122124
123-
<div class="lb-flex lb-items-center lb-gap-8">
125+
<div class="lb-flex lb-w-72 lb-items-center lb-justify-end lb-gap-8">
124126
${this.showSettings
125127
? html`
126128
<div

packages/ledger-button/src/domain/device-switch/device-switch-controller.ts

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,26 @@
1-
import type { DiscoveredDevice } from "@ledgerhq/ledger-wallet-provider-core";
2-
import { LitElement } from "lit";
3-
41
import type { DeviceModelId } from "../../components/atom/icon/device-icon/device-icon.js";
52
import type { CoreContext } from "../../context/core-context.js";
63
import type { Navigation } from "../../shared/navigation.js";
74
import type { Destinations } from "../../shared/routes.js";
85

96
export class DeviceSwitchController {
10-
private devices: DiscoveredDevice[] = [];
11-
127
constructor(
13-
private readonly host: LitElement,
148
private readonly coreContext: CoreContext,
159
private readonly navigation: Navigation,
1610
private readonly destinations: Destinations,
1711
) {}
1812

19-
async hostConnected() {
20-
await this.loadAvailableDevices();
21-
}
22-
23-
async loadAvailableDevices() {
24-
try {
25-
this.devices = await this.coreContext.listAvailableDevices();
26-
27-
this.host.requestUpdate();
28-
} catch {
29-
this.devices = [];
30-
this.host.requestUpdate();
31-
}
32-
}
33-
34-
getDevices(): DiscoveredDevice[] {
35-
return this.devices;
36-
}
37-
38-
async connectToDevice(detail: {
39-
title: string;
40-
connectionType: "bluetooth" | "usb" | "";
41-
timestamp: number;
42-
}) {
43-
const connectionType = detail.connectionType;
44-
if (!connectionType) {
45-
return;
46-
}
47-
48-
// Navigate to connection status screen to show device animation
49-
this.navigation.navigateTo(this.destinations.deviceConnectionStatus);
13+
async connectNewDevice(connectionType: "bluetooth" | "usb") {
14+
await this.coreContext.disconnectFromDevice();
5015

5116
try {
5217
await this.coreContext.connectToDevice(connectionType);
53-
54-
const pendingTransactionParams =
55-
this.coreContext.getPendingTransactionParams();
56-
57-
if (pendingTransactionParams) {
58-
this.navigation.navigateTo(this.destinations.signTransaction);
59-
} else {
60-
this.navigation.navigateTo(this.destinations.ledgerSync);
61-
}
18+
this.navigation.navigateTo(this.destinations.onboardingFlow);
6219
} catch {
6320
this.navigation.navigateTo(this.destinations.onboardingFlow);
6421
}
6522
}
6623

67-
async addNewDevice() {
68-
this.navigation.navigateTo(this.destinations.onboardingFlow);
69-
}
70-
7124
getConnectionTypeFromTransport(transport: string): "bluetooth" | "usb" | "" {
7225
const transportLower = transport.toLowerCase();
7326

packages/ledger-button/src/domain/device-switch/device-switch.ts

Lines changed: 35 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import "../../components/index.js";
22

33
import { consume } from "@lit/context";
44
import { css, html, LitElement } from "lit";
5-
import { customElement, property, state } from "lit/decorators.js";
5+
import { customElement, property } from "lit/decorators.js";
66

7-
import type { DeviceItemClickEventDetail } from "../../components/molecule/device-item/ledger-device-item.js";
7+
import type { ConnectionItemClickEventDetail } from "../../components/molecule/connection-item/ledger-connection-item.js";
88
import { CoreContext, coreContext } from "../../context/core-context.js";
99
import {
1010
langContext,
@@ -58,93 +58,50 @@ export class DeviceSwitchScreen extends LitElement {
5858
@property({ attribute: false })
5959
public languageContext!: LanguageContext;
6060

61-
@state()
62-
private isLoading = true;
63-
6461
controller!: DeviceSwitchController;
6562

6663
override connectedCallback() {
6764
super.connectedCallback();
6865

6966
this.controller = new DeviceSwitchController(
70-
this,
7167
this.coreContext,
7268
this.navigation,
7369
this.destinations,
7470
);
75-
76-
this.controller.hostConnected().finally(() => {
77-
this.isLoading = false;
78-
this.requestUpdate();
79-
});
8071
}
8172

82-
handleDeviceItemClick = (e: CustomEvent<DeviceItemClickEventDetail>) => {
83-
this.controller.connectToDevice({
84-
title: e.detail.title,
85-
connectionType: e.detail.connectionType,
86-
timestamp: e.detail.timestamp,
87-
});
88-
};
73+
handleConnectionItemClick = (
74+
e: CustomEvent<ConnectionItemClickEventDetail>,
75+
) => {
76+
const connectionType = e.detail.connectionType;
8977

90-
handleAddNewDevice = () => {
91-
this.controller.addNewDevice();
78+
if (connectionType) {
79+
this.controller.connectNewDevice(connectionType);
80+
}
9281
};
9382

94-
private renderDeviceList() {
95-
const devices = this.controller?.getDevices() || [];
96-
97-
if (this.isLoading) {
98-
return html`
99-
<div class="lb-flex lb-items-center lb-justify-center lb-p-24">
100-
<div
101-
class="lb-border-primary lb-h-32 lb-w-32 lb-animate-spin lb-rounded-full lb-border-b-2"
102-
></div>
103-
</div>
104-
`;
105-
}
83+
private renderConnectedDevice() {
84+
const connectedDevice = this.coreContext.getConnectedDevice();
10685

107-
if (devices.length === 0) {
108-
return html`
109-
<div
110-
class="lb-flex lb-flex-col lb-items-center lb-gap-16 lb-p-24 lb-text-center"
111-
>
112-
<div class="lb-text-muted lb-body-2">
113-
${this.languageContext.currentTranslation.deviceSwitch.noDevices}
114-
</div>
115-
</div>
116-
`;
86+
if (!connectedDevice) {
87+
return "";
11788
}
11889

90+
const deviceModelId = this.controller.mapDeviceModelId(
91+
connectedDevice.modelId,
92+
);
93+
const lang = this.languageContext.currentTranslation;
94+
11995
return html`
12096
<div class="lb-flex lb-flex-col lb-gap-12 lb-p-24 lb-pt-0">
121-
${devices.map((device) => {
122-
const connectionType = this.controller.getConnectionTypeFromTransport(
123-
device.transport,
124-
);
125-
const connectedDevice = this.coreContext.getConnectedDevice();
126-
const isConnected =
127-
connectedDevice && connectedDevice.name === device.name;
128-
const status = isConnected ? "connected" : "available";
129-
const deviceModelId = this.controller.mapDeviceModelId(
130-
device.deviceModel?.model,
131-
);
132-
133-
const lang = this.languageContext.currentTranslation;
134-
135-
return html`
136-
<ledger-device-item
137-
device-id=${device.id}
138-
title=${device.name}
139-
connection-type=${connectionType}
140-
device-model-id=${deviceModelId}
141-
status=${status}
142-
connected-text=${lang.deviceSwitch.status.connected}
143-
available-text=${lang.deviceSwitch.status.available}
144-
@device-item-click=${this.handleDeviceItemClick}
145-
></ledger-device-item>
146-
`;
147-
})}
97+
<ledger-device-item
98+
title=${connectedDevice.name}
99+
device-model-id=${deviceModelId}
100+
status="connected"
101+
.clickable=${false}
102+
connected-text=${lang.deviceSwitch.status.connected}
103+
available-text=${lang.deviceSwitch.status.available}
104+
></ledger-device-item>
148105
</div>
149106
`;
150107
}
@@ -163,19 +120,21 @@ export class DeviceSwitchScreen extends LitElement {
163120
}
164121

165122
private renderAddNewDeviceSection() {
123+
const lang = this.languageContext.currentTranslation;
124+
166125
return html`
167126
<div class="lb-flex lb-flex-col lb-gap-12 lb-p-24">
168127
<ledger-connection-item
169-
title="${this.languageContext.currentTranslation.deviceSwitch
170-
.connectBluetooth}"
128+
title="${lang.deviceSwitch.connectBluetooth}"
129+
hint="${lang.deviceSwitch.connectBluetoothHint}"
171130
connection-type="bluetooth"
172-
@connection-item-click=${this.handleAddNewDevice}
131+
@connection-item-click=${this.handleConnectionItemClick}
173132
></ledger-connection-item>
174133
<ledger-connection-item
175-
title="${this.languageContext.currentTranslation.deviceSwitch
176-
.connectUsb}"
134+
title="${lang.deviceSwitch.connectUsb}"
135+
hint="${lang.deviceSwitch.connectUsbHint}"
177136
connection-type="usb"
178-
@connection-item-click=${this.handleAddNewDevice}
137+
@connection-item-click=${this.handleConnectionItemClick}
179138
></ledger-connection-item>
180139
</div>
181140
`;
@@ -184,7 +143,7 @@ export class DeviceSwitchScreen extends LitElement {
184143
override render() {
185144
return html`
186145
<div class="lb-flex lb-flex-col">
187-
${this.renderDeviceList()} ${this.renderSeparator()}
146+
${this.renderConnectedDevice()} ${this.renderSeparator()}
188147
${this.renderAddNewDeviceSection()}
189148
</div>
190149
`;

packages/ledger-button/src/domain/onboarding/select-device/select-device-controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,5 @@ export class SelectDeviceController implements ReactiveController {
131131
this.mapErrors(error);
132132
}
133133
}
134+
134135
}

packages/ledger-button/src/i18n/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@
6767
"noDevices": "No previously connected devices found",
6868
"connectAnother": "Or connect another Ledger",
6969
"connectBluetooth": "Connect a Bluetooth",
70+
"connectBluetoothHint": "Power on and unlock your device",
7071
"connectUsb": "Connect with USB",
72+
"connectUsbHint": "Plug in and unlock your device",
7173
"status": {
7274
"connected": "Connected",
7375
"available": "Available"

0 commit comments

Comments
 (0)