Skip to content

Commit 13c1915

Browse files
thisconnectshonsirsha
authored andcommitted
Merge branch 'frontend-standalone-accountselector'
2 parents 52f02f0 + 56ce8ae commit 13c1915

File tree

10 files changed

+114
-29
lines changed

10 files changed

+114
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- Add icons for CTA and action buttons in account page
1717
- Restructure "Manage device" tab in settings
1818
- Responsive account selector (Marketplace)
19+
- iOS: show different messages when Bluetooth is off vs. when Bluetooth permission for BitBoxApp is disabled.
1920

2021
## v4.49.0
2122
- Bundle BitBox02 Nova firmware version v9.24.0

backend/backend.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ var fixedURLWhitelist = []string{
9999
"https://bitcoincore.org/en/2016/01/26/segwit-benefits/",
100100
"https://en.bitcoin.it/wiki/Bech32_adoption",
101101
"https://github.com/bitcoin/bips/",
102+
// iOS app settings
103+
"app-settings:",
102104
// Others
103105
"https://cointracking.info/import/bitbox/",
104106
}

backend/devices/bluetooth/bluetooth.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ type Peripheral struct {
5757
type State struct {
5858
// BluetoothAvailable is false if bluetooth is powered off or otherwise unavailable.
5959
BluetoothAvailable bool `json:"bluetoothAvailable"`
60+
// BluetoothUnauthorized is true if the app does not have permission to use Bluetooth.
61+
BluetoothUnauthorized bool `json:"bluetoothUnauthorized"`
6062
// Scanning is true if we are currently scanning for peripherals.
6163
Scanning bool `json:"scanning"`
6264
Peripherals []*Peripheral `json:"peripherals"`

frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,29 @@ class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol, UIDoc
117117

118118
func systemOpen(_ urlString: String?) throws {
119119
guard let urlString = urlString else { return }
120-
// Check if it's a local file path (not a URL)
121-
var url: URL
122-
if urlString.hasPrefix("/") {
123-
// This is a local file path, construct a file URL
124-
url = URL(fileURLWithPath: urlString)
125-
} else if let potentialURL = URL(string: urlString), potentialURL.scheme != nil {
126-
// This is already a valid URL with a scheme
127-
url = potentialURL
128-
} else {
129-
// Invalid URL or path
130-
return
131-
}
132-
// Ensure we run on the main thread
120+
133121
DispatchQueue.main.async {
122+
if urlString == "app-settings:" {
123+
if let url = URL(string: UIApplication.openSettingsURLString) {
124+
// opens app settings page in the system settings
125+
UIApplication.shared.open(url)
126+
}
127+
return
128+
}
129+
130+
// Check if it's a local file path (not a URL)
131+
var url: URL
132+
if urlString.hasPrefix("/") {
133+
// This is a local file path, construct a file URL
134+
url = URL(fileURLWithPath: urlString)
135+
} else if let potentialURL = URL(string: urlString), potentialURL.scheme != nil {
136+
// This is already a valid URL with a scheme
137+
url = potentialURL
138+
} else {
139+
// Invalid URL or path
140+
return
141+
}
142+
134143
if url.isFileURL {
135144
// Local file path, use UIDocumentInteractionController
136145
if let rootViewController = self.getRootViewController() {

frontends/ios/BitBoxApp/BitBoxApp/Bluetooth.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ enum ConnectionState: String, Codable {
3030

3131
struct State {
3232
var bluetoothAvailable: Bool
33+
var bluetoothUnauthorized: Bool
3334
var scanning: Bool
3435
var discoveredPeripherals: [UUID: PeripheralMetadata]
3536
}
@@ -61,6 +62,7 @@ class BLEConnectionContext {
6162
class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
6263
private var state: State = State(
6364
bluetoothAvailable: false,
65+
bluetoothUnauthorized: false,
6466
scanning: false,
6567
discoveredPeripherals: [:]
6668
)
@@ -124,12 +126,25 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
124126

125127
func centralManagerDidUpdateState(_ central: CBCentralManager) {
126128
state.bluetoothAvailable = centralManager.state == .poweredOn
129+
130+
if #available(iOS 13.0, *) {
131+
// user denied BT permission,
132+
// or restricted by device policy
133+
let authorization = CBManager.authorization
134+
state.bluetoothUnauthorized = (authorization == .denied || authorization == .restricted)
135+
}
136+
127137
updateBackendState()
128138

129139
switch central.state {
130140
case .poweredOn:
131-
print("BLE: on")
132-
restartScan()
141+
if state.bluetoothUnauthorized {
142+
print("BLE: on but permission denied")
143+
handleDisconnect()
144+
} else {
145+
print("BLE: on")
146+
restartScan()
147+
}
133148
case .poweredOff, .unauthorized, .unsupported, .resetting, .unknown:
134149
print("BLE: unavailable or not supported")
135150
handleDisconnect()
@@ -409,6 +424,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
409424

410425
struct StateJSON: Codable {
411426
let bluetoothAvailable: Bool
427+
let bluetoothUnauthorized: Bool
412428
let scanning: Bool
413429
let peripherals: [PeripheralJSON]
414430
}
@@ -425,6 +441,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
425441

426442
let state = StateJSON(
427443
bluetoothAvailable: state.bluetoothAvailable,
444+
bluetoothUnauthorized: state.bluetoothUnauthorized,
428445
scanning: state.scanning,
429446
peripherals: peripherals
430447
)

frontends/web/src/api/bluetooth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ export type TPeripheral = {
1717

1818
type TBluetoothState = {
1919
bluetoothAvailable: boolean;
20+
bluetoothUnauthorized: boolean;
2021
scanning: boolean;
2122
peripherals: TPeripheral[];
2223
};
2324

2425
export const getState = (): Promise<TBluetoothState> => {
26+
2527
return apiGet('bluetooth/state');
2628
};
2729

frontends/web/src/components/bluetooth/bluetooth.module.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,20 @@
2424
text-align: center;
2525
}
2626
}
27+
28+
.bluetoothDisabledContainer {
29+
align-items: start;
30+
display: flex;
31+
flex-direction: column;
32+
gap: 6px;
33+
}
34+
35+
.bluetoothDisabledTitle {
36+
font-weight: 600;
37+
}
38+
39+
.link {
40+
font-weight: 600;
41+
margin-top: var(--space-quarter);
42+
text-decoration: none;
43+
}

frontends/web/src/components/bluetooth/bluetooth.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSync } from '@/hooks/api';
66
import { connect, getState, syncState, TPeripheral } from '@/api/bluetooth';
77
import { runningInIOS } from '@/utils/env';
88
import { Message } from '@/components/message/message';
9+
import { A } from '@/components/anchor/anchor';
910
import { ActionableItem } from '@/components/actionable-item/actionable-item';
1011
import { Badge } from '@/components/badge/badge';
1112
import { HorizontallyCenteredSpinner, SpinnerRingAnimated } from '@/components/spinner/SpinnerAnimation';
@@ -54,10 +55,36 @@ const BluetoothInner = ({ peripheralContainerClassName }: Props) => {
5455
if (!state) {
5556
return null;
5657
}
58+
59+
if (state.bluetoothUnauthorized) {
60+
return (
61+
<Message type="warning">
62+
<div className={styles.bluetoothDisabledContainer}>
63+
<span className={styles.bluetoothDisabledTitle}>
64+
{t('bluetooth.disabledPermissionTitle')}
65+
</span>
66+
<span >
67+
{t('bluetooth.disabledPermissionDescription')}
68+
</span>
69+
<A className={styles.link} href="app-settings:">
70+
{t('generic.enable')}
71+
</A>
72+
</div>
73+
</Message>
74+
);
75+
}
76+
5777
if (!state.bluetoothAvailable) {
5878
return (
5979
<Message type="warning">
60-
{t('bluetooth.enable')}
80+
<div className={styles.bluetoothDisabledContainer}>
81+
<span className={styles.bluetoothDisabledTitle}>
82+
{t('bluetooth.disabledGloballyTitle')}
83+
</span>
84+
<span >
85+
{t('bluetooth.disabledGloballyDescription')}
86+
</span>
87+
</div>
6188
</Message>
6289
);
6390
}

frontends/web/src/components/groupedaccountselector/groupedaccountselector.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export type TOption = TDropdownOption<AccountCode> & TOptionAccountSelector;
3030
export type TGroupedOption = TDropdownGroupedOption<AccountCode, TGroupAccountSelector, TOptionAccountSelector>;
3131

3232
type TAccountSelector = {
33-
title: string;
33+
title?: string;
3434
disabled?: boolean;
3535
selected?: string;
3636
onChange: (value: string) => void;
37-
onProceed: () => void;
37+
onProceed?: () => void;
3838
accounts: TAccount[];
3939
};
4040

@@ -120,7 +120,9 @@ export const GroupedAccountSelector = ({ title, disabled, selected, onChange, on
120120

121121
return (
122122
<>
123-
<h1 className="title text-center">{title}</h1>
123+
{title && (
124+
<h1 className="title text-center">{title}</h1>
125+
)}
124126
<Dropdown<AccountCode, false, TGroupAccountSelector, TOptionAccountSelector>
125127
className={styles.select}
126128
classNamePrefix="react-select"
@@ -139,14 +141,16 @@ export const GroupedAccountSelector = ({ title, disabled, selected, onChange, on
139141
onOpenChange={setIsOpen}
140142
mobileTriggerComponent={mobileTriggerComponent}
141143
/>
142-
<div className="buttons text-center">
143-
<Button
144-
primary
145-
onClick={onProceed}
146-
disabled={!selected || disabled}>
147-
{t('buy.info.next')}
148-
</Button>
149-
</div>
144+
{onProceed && (
145+
<div className="buttons text-center">
146+
<Button
147+
primary
148+
onClick={onProceed}
149+
disabled={!selected || disabled}>
150+
{t('buy.info.next')}
151+
</Button>
152+
</div>
153+
)}
150154
</>
151155
);
152-
};
156+
};

frontends/web/src/locales/en/app.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,10 @@
383383
"connected": "connected",
384384
"connectionFailed": "failed",
385385
"connectionIssues": "Having connection issues?",
386-
"enable": "Please turn on Bluetooth",
386+
"disabledGloballyDescription": "Please turn on Bluetooth to connect to your BitBox.",
387+
"disabledGloballyTitle": "Bluetooth access is disabled.",
388+
"disabledPermissionDescription": "Please allow Bluetooth to connect to your BitBox.",
389+
"disabledPermissionTitle": "Bluetooth access is disabled for BitBoxApp.",
387390
"select": "Select your BitBox"
388391
},
389392
"bootloader": {
@@ -891,6 +894,7 @@
891894
"buy_bitcoin": "Buy Bitcoin",
892895
"buy_crypto": "Buy crypto",
893896
"close": "Close",
897+
"enable": "Enable",
894898
"enabled_false": "Disabled",
895899
"enabled_true": "Enabled",
896900
"noOptionOnIos": "Not available on iOS",

0 commit comments

Comments
 (0)