diff --git a/suite-native/app/app.config.ts b/suite-native/app/app.config.ts
index ee615d02c001..59ef0dc55cc9 100644
--- a/suite-native/app/app.config.ts
+++ b/suite-native/app/app.config.ts
@@ -126,6 +126,8 @@ const getPlugins = (): ExpoPlugins => {
},
],
]),
+
+ ['react-native-ble-plx', {}],
// These should come last
'./plugins/withRemoveXcodeLocalEnv.js',
['./plugins/withEnvFile.js', { buildType }],
diff --git a/suite-native/app/package.json b/suite-native/app/package.json
index 6650c4e594f5..36d530e8307d 100644
--- a/suite-native/app/package.json
+++ b/suite-native/app/package.json
@@ -90,10 +90,12 @@
"react": "18.2.0",
"react-intl": "^6.6.2",
"react-native": "0.73.6",
+ "react-native-ble-plx": "^3.1.2",
"react-native-flipper": "^0.212.0",
"react-native-gesture-handler": "2.15.0",
"react-native-keyboard-aware-scroll-view": "0.9.5",
"react-native-mmkv": "2.11.0",
+ "react-native-permissions": "^4.1.5",
"react-native-reanimated": "3.8.1",
"react-native-restart": "0.0.27",
"react-native-safe-area-context": "4.9.0",
diff --git a/suite-native/bluetooth/package.json b/suite-native/bluetooth/package.json
new file mode 100644
index 000000000000..687117c99a3a
--- /dev/null
+++ b/suite-native/bluetooth/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@suite-native/bluetooth",
+ "version": "1.0.0",
+ "private": true,
+ "license": "See LICENSE.md in repo root",
+ "sideEffects": false,
+ "main": "src/index",
+ "scripts": {
+ "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'",
+ "test:unit": "jest -c ../../jest.config.base.js",
+ "type-check": "yarn g:tsc --build"
+ },
+ "dependencies": {
+ "@suite-native/atoms": "workspace:*",
+ "@suite-native/intl": "workspace:*",
+ "@trezor/styles": "workspace:*",
+ "@trezor/transport-native-ble": "workspace:*",
+ "expo-constants": "15.4.5",
+ "react": "18.2.0",
+ "react-native": "0.73.6",
+ "react-native-ble-plx": "^3.1.2",
+ "react-native-permissions": "^4.1.5"
+ }
+}
diff --git a/suite-native/bluetooth/src/components/BluetoothAdapterStateManager.tsx b/suite-native/bluetooth/src/components/BluetoothAdapterStateManager.tsx
new file mode 100644
index 000000000000..adb2d4ba04fb
--- /dev/null
+++ b/suite-native/bluetooth/src/components/BluetoothAdapterStateManager.tsx
@@ -0,0 +1,49 @@
+import { State as AdapterState } from 'react-native-ble-plx';
+
+import { AlertBox, Button, Loader, VStack } from '@suite-native/atoms';
+
+import { useBluetoothAdapterState } from '../hooks/useBluetoothAdapterState';
+import { BluetoothPermissionErrors } from '../hooks/useBluetoothPermissions';
+import { BluetoothPermissionError } from './BluetoothPermissionError';
+
+export const BluetoothAdapterStateManager = () => {
+ const { bluetoothState, turnOnBluetooth } = useBluetoothAdapterState();
+
+ if (bluetoothState === AdapterState.PoweredOn) {
+ // We are good to go
+ return null;
+ }
+
+ if (bluetoothState === AdapterState.Unknown || bluetoothState === AdapterState.Resetting) {
+ return ;
+ }
+
+ if (bluetoothState === AdapterState.Unsupported) {
+ return ;
+ }
+
+ if (bluetoothState === AdapterState.Unauthorized) {
+ return (
+
+ );
+ }
+
+ if (bluetoothState === AdapterState.PoweredOff) {
+ return (
+
+
+
+
+ );
+ }
+
+ // Exhaustive check - this should never happen
+ const _exhaustiveCheck: never = bluetoothState;
+
+ return _exhaustiveCheck;
+};
diff --git a/suite-native/bluetooth/src/components/BluetoothPermissionError.tsx b/suite-native/bluetooth/src/components/BluetoothPermissionError.tsx
new file mode 100644
index 000000000000..4479f2f352d0
--- /dev/null
+++ b/suite-native/bluetooth/src/components/BluetoothPermissionError.tsx
@@ -0,0 +1,33 @@
+import { openSettings } from 'react-native-permissions';
+
+import { AlertBox, Button, VStack } from '@suite-native/atoms';
+
+import { BluetoothPermissionErrors } from '../hooks/useBluetoothPermissions';
+
+type BluetoothPermissionErrorProps = {
+ error: BluetoothPermissionErrors;
+};
+
+const ERROR_MESSAGES: Record = {
+ [BluetoothPermissionErrors.BluetoothAccessBlocked]:
+ 'Please enable Bluetooth permission for the app in your phone settings.',
+ [BluetoothPermissionErrors.LocationAccessBlocked]: 'Please enable Bluetooth on your phone',
+ [BluetoothPermissionErrors.NearbyDevicesAccessBlocked]:
+ 'Please enable Nearby Devices permission for the app in your phone settings.',
+};
+
+export const BluetoothPermissionError = ({ error }: BluetoothPermissionErrorProps) => {
+ const handleOpenSettings = async () => {
+ await openSettings();
+ };
+
+ return (
+
+
+
+
+ );
+};
diff --git a/suite-native/bluetooth/src/components/DevicesScanner.tsx b/suite-native/bluetooth/src/components/DevicesScanner.tsx
new file mode 100644
index 000000000000..c3d77aa25aed
--- /dev/null
+++ b/suite-native/bluetooth/src/components/DevicesScanner.tsx
@@ -0,0 +1,126 @@
+import { useEffect, useState } from 'react';
+import { State as AdapterState, BleError } from 'react-native-ble-plx';
+
+import { AlertBox, Box, Button, Loader, Text, VStack } from '@suite-native/atoms';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { BLEScannedDevice, nativeBleManager } from '@trezor/transport-native-ble';
+
+import { useBluetoothAdapterState } from '../hooks/useBluetoothAdapterState';
+import { useBluetoothPermissions } from '../hooks/useBluetoothPermissions';
+import { BluetoothAdapterStateManager } from './BluetoothAdapterStateManager';
+import { BluetoothPermissionError } from './BluetoothPermissionError';
+import { ScannedDeviceItem } from './ScannedDeviceItem';
+
+const containerStyle = prepareNativeStyle(utils => ({
+ flex: 1,
+ width: '100%',
+ paddingHorizontal: utils.spacings.medium,
+}));
+
+export const DevicesScanner = () => {
+ const { applyStyle } = useNativeStyles();
+
+ const [scannedDevices, setScannedDevices] = useState([]);
+ const [scanError, setScanError] = useState();
+ const [isScanRunning, setIsScanRunning] = useState(false);
+ const { hasBluetoothPermissions, requestBluetoothPermissions, bluetoothPermissionError } =
+ useBluetoothPermissions();
+ const { bluetoothState } = useBluetoothAdapterState();
+
+ const stopScanning = async () => {
+ nativeBleManager.stopDeviceScan();
+ setIsScanRunning(false);
+ setScannedDevices([]);
+ };
+
+ const scanDevices = () => {
+ setScanError(null);
+ setIsScanRunning(true);
+ setScannedDevices([]);
+
+ nativeBleManager.scanDevices(
+ newlyScannedDevices => {
+ setScannedDevices(newlyScannedDevices);
+ },
+ error => {
+ setScanError(error);
+ stopScanning();
+ setScannedDevices([]);
+ },
+ );
+ };
+
+ const requestPermissions = () => {
+ return requestBluetoothPermissions();
+ };
+
+ useEffect(() => {
+ return () => {
+ stopScanning();
+ };
+ }, []);
+
+ const shouldShowRequestPermission = !hasBluetoothPermissions;
+ const shouldShowScanDevicesButton =
+ !isScanRunning && hasBluetoothPermissions && bluetoothState === AdapterState.PoweredOn;
+ const shouldShowBluetoothAdapterManager =
+ bluetoothState !== AdapterState.PoweredOn && !isScanRunning && hasBluetoothPermissions;
+ const shouldShowBluetoothPermissionError = !!bluetoothPermissionError;
+
+ return (
+
+
+ hasBluetoothPermissions: {hasBluetoothPermissions ? 'true' : 'false'} {'\n'}
+ bluetoothPermissionError: {bluetoothPermissionError} {'\n'}
+ bluetoothState: {bluetoothState} {'\n'}
+ isScanRunning: {isScanRunning ? 'true' : 'false'} {'\n'}
+ shouldShowRequestPermission: {shouldShowRequestPermission ? 'true' : 'false'} {'\n'}
+
+ {shouldShowRequestPermission && (
+
+
+
+
+ )}
+ {shouldShowBluetoothAdapterManager && }
+
+ {shouldShowBluetoothPermissionError && (
+
+ )}
+ {shouldShowScanDevicesButton && }
+ {isScanRunning && (
+
+
+ Scanning for devices...
+
+
+
+
+ )}
+ {scanError && (
+
+ )}
+ {isScanRunning && (
+
+ {scannedDevices.map(scannedDevice => (
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/suite-native/bluetooth/src/components/ScannedDeviceItem.tsx b/suite-native/bluetooth/src/components/ScannedDeviceItem.tsx
new file mode 100644
index 000000000000..3d5d643afd62
--- /dev/null
+++ b/suite-native/bluetooth/src/components/ScannedDeviceItem.tsx
@@ -0,0 +1,111 @@
+import { useEffect, useState } from 'react';
+import { BleError, State as AdapterState } from 'react-native-ble-plx';
+import { Alert } from 'react-native';
+
+import { AlertBox, Box, Button, HStack, Loader, Text, VStack } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { BLEScannedDevice, nativeBleManager } from '@trezor/transport-native-ble';
+import { Icon, IconName } from '@suite-common/icons';
+import { useActiveColorScheme } from '@suite-native/theme';
+
+import {
+ BluetoothPermissionErrors,
+ useBluetoothPermissions,
+} from '../hooks/useBluetoothPermissions';
+import { BluetoothPermissionError } from './BluetoothPermissionError';
+import { useBluetoothAdapterState } from '../hooks/useBluetoothAdapterState';
+import { BluetoothAdapterStateManager } from './BluetoothAdapterStateManager';
+
+type ContainerStylePayload = {
+ seenQuiteLongAgo: boolean;
+};
+const containerStyle = prepareNativeStyle((_, { seenQuiteLongAgo }) => ({
+ flexDirection: 'row',
+ borderWidth: 1,
+ width: '100%',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ extend: {
+ condition: seenQuiteLongAgo,
+ style: {
+ opacity: 0.5,
+ },
+ },
+}));
+
+const deviceItemStyle = prepareNativeStyle(utils => ({
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: utils.spacings.extraSmall,
+ backgroundColor: utils.colors.backgroundSurfaceElevation2,
+ width: 50,
+ height: 50,
+ borderRadius: 25,
+ justifyContent: 'center',
+}));
+
+const DeviceIcon = () => {
+ const activeColorScheme = useActiveColorScheme();
+ const { applyStyle } = useNativeStyles();
+
+ const connectedDeviceIcon: IconName =
+ activeColorScheme === 'standard' ? 'trezorConnectedLight' : 'trezorConnectedDark';
+
+ return (
+
+
+
+ );
+};
+
+export const ScannedDeviceItem = ({ device }: { device: BLEScannedDevice }) => {
+ const { bleDevice } = device;
+ const { applyStyle } = useNativeStyles();
+ const [isConnecting, setIsConnecting] = useState(false);
+
+ const connectDevice = async () => {
+ setIsConnecting(true);
+ try {
+ await nativeBleManager.connectDevice({
+ deviceOrId: bleDevice,
+ });
+ } catch (error) {
+ alert('Error connecting to device');
+ Alert.alert('Error connecting to device', error?.message, [{ text: 'OK' }]);
+ }
+ setIsConnecting(false);
+ };
+
+ const lastSeenInSec = Math.floor((Date.now() - device.lastSeenTimestamp) / 1000);
+ const seenQuiteLongAgo = lastSeenInSec > 10;
+
+ if (lastSeenInSec > 30) {
+ // This device is probably not in range anymore or it's not advertising anymore
+ return null;
+ }
+
+ return (
+
+
+
+
+ {bleDevice.name}
+ {bleDevice.id}
+ {seenQuiteLongAgo && (
+ Last seen: {lastSeenInSec}s ago
+ )}
+
+
+ {isConnecting ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/suite-native/bluetooth/src/hooks/useBluetoothAdapterState.ts b/suite-native/bluetooth/src/hooks/useBluetoothAdapterState.ts
new file mode 100644
index 000000000000..f5a6820a9a0e
--- /dev/null
+++ b/suite-native/bluetooth/src/hooks/useBluetoothAdapterState.ts
@@ -0,0 +1,33 @@
+import { useEffect, useState } from 'react';
+import { State as BluetoothAdapterState } from 'react-native-ble-plx';
+
+import { nativeBleManager } from '@trezor/transport-native-ble';
+
+export const useBluetoothAdapterState = () => {
+ const [bluetoothState, setState] = useState(
+ BluetoothAdapterState.Unknown,
+ );
+
+ useEffect(() => {
+ nativeBleManager.bleManager.state().then(newState => {
+ setState(newState);
+ });
+
+ const subscription = nativeBleManager.bleManager.onStateChange(newState => {
+ setState(newState);
+ });
+
+ return () => {
+ subscription.remove();
+ };
+ }, []);
+
+ const turnOnBluetooth = async () => {
+ await nativeBleManager.bleManager.enable();
+ };
+
+ return {
+ bluetoothState,
+ turnOnBluetooth,
+ };
+};
diff --git a/suite-native/bluetooth/src/hooks/useBluetoothPermissions.ts b/suite-native/bluetooth/src/hooks/useBluetoothPermissions.ts
new file mode 100644
index 000000000000..4187bcfd204c
--- /dev/null
+++ b/suite-native/bluetooth/src/hooks/useBluetoothPermissions.ts
@@ -0,0 +1,128 @@
+import { useState, useRef, useLayoutEffect } from 'react';
+import { AppState, AppStateStatus, Platform } from 'react-native';
+import {
+ PERMISSIONS,
+ RESULTS,
+ request,
+ requestMultiple,
+ check,
+ PermissionStatus,
+} from 'react-native-permissions';
+
+import Constants from 'expo-constants';
+
+export enum BluetoothPermissionErrors {
+ BluetoothAccessBlocked = 'BluetoothAccessBlocked',
+ LocationAccessBlocked = 'LocationAccessBlocked',
+ NearbyDevicesAccessBlocked = 'NearbyDevicesAccessBlocked',
+}
+
+export const useBluetoothPermissions = () => {
+ const appState = useRef(AppState.currentState);
+ const [hasBluetoothPermissions, setHasBluetoothPermissions] = useState(false);
+ const [bluetoothPermissionError, setBluetoothPermissionError] =
+ useState();
+ const deviceOSVersion = Constants.systemVersion || 0;
+
+ const requestIosPermission = async ({ checkOnly = false }: { checkOnly?: boolean } = {}) => {
+ const permissionFn = checkOnly ? check : request;
+ const bluetoothPermissionStatus = await permissionFn(PERMISSIONS.IOS.BLUETOOTH);
+ const bluetoothAllowed = bluetoothPermissionStatus === RESULTS.GRANTED;
+
+ if (bluetoothAllowed) {
+ setHasBluetoothPermissions(true);
+ setBluetoothPermissionError(undefined);
+ } else {
+ setBluetoothPermissionError(BluetoothPermissionErrors.BluetoothAccessBlocked);
+ }
+ };
+
+ const requestAndroidPermission = async ({
+ checkOnly = false,
+ }: { checkOnly?: boolean } = {}) => {
+ let hasError = false;
+
+ if (deviceOSVersion >= 12) {
+ let result: {
+ [PERMISSIONS.ANDROID.BLUETOOTH_CONNECT]: PermissionStatus;
+ [PERMISSIONS.ANDROID.BLUETOOTH_SCAN]: PermissionStatus;
+ } = {
+ [PERMISSIONS.ANDROID.BLUETOOTH_CONNECT]: RESULTS.DENIED,
+ [PERMISSIONS.ANDROID.BLUETOOTH_SCAN]: RESULTS.DENIED,
+ };
+ if (checkOnly) {
+ result[PERMISSIONS.ANDROID.BLUETOOTH_CONNECT] = await check(
+ PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
+ );
+ result[PERMISSIONS.ANDROID.BLUETOOTH_SCAN] = await check(
+ PERMISSIONS.ANDROID.BLUETOOTH_SCAN,
+ );
+ } else {
+ result = await requestMultiple([
+ PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
+ PERMISSIONS.ANDROID.BLUETOOTH_SCAN,
+ ]);
+ }
+
+ if (
+ result[PERMISSIONS.ANDROID.BLUETOOTH_CONNECT] !== RESULTS.GRANTED ||
+ result[PERMISSIONS.ANDROID.BLUETOOTH_SCAN] !== RESULTS.GRANTED
+ ) {
+ setBluetoothPermissionError(BluetoothPermissionErrors.NearbyDevicesAccessBlocked);
+ hasError = true;
+ }
+ } else {
+ const permissionFn = checkOnly ? check : request;
+ const bluetoothPermissionStatus = await permissionFn(
+ PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
+ );
+
+ if (bluetoothPermissionStatus !== RESULTS.GRANTED) {
+ setBluetoothPermissionError(BluetoothPermissionErrors.LocationAccessBlocked);
+ hasError = true;
+ }
+ }
+
+ if (!hasError) {
+ setHasBluetoothPermissions(true);
+ setBluetoothPermissionError(undefined);
+ }
+ };
+
+ // Checking if app has required permissions every time the app becomes active
+ const requestPermissions = async ({ checkOnly = false }: { checkOnly?: boolean } = {}) => {
+ if (Platform.OS === 'ios') {
+ await requestIosPermission({ checkOnly });
+ }
+
+ if (Platform.OS === 'android') {
+ await requestAndroidPermission({ checkOnly });
+ }
+ };
+
+ // External permission changes must be picked up by the app by tracking the app state
+ useLayoutEffect(() => {
+ const handleAppStateChange = (nextAppState: AppStateStatus) => {
+ if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
+ setBluetoothPermissionError(undefined);
+ requestPermissions({ checkOnly: true });
+ }
+
+ appState.current = nextAppState;
+ };
+
+ const subscription = AppState.addEventListener('change', handleAppStateChange);
+ requestPermissions({ checkOnly: true });
+
+ return () => {
+ subscription.remove();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return {
+ hasBluetoothPermissions,
+ bluetoothPermissionError,
+ requestBluetoothPermissions: requestPermissions,
+ };
+};
diff --git a/suite-native/bluetooth/src/index.ts b/suite-native/bluetooth/src/index.ts
new file mode 100644
index 000000000000..c17c100fba0a
--- /dev/null
+++ b/suite-native/bluetooth/src/index.ts
@@ -0,0 +1 @@
+export { DevicesScanner } from './components/DevicesScanner';
diff --git a/suite-native/bluetooth/tsconfig.json b/suite-native/bluetooth/tsconfig.json
new file mode 100644
index 000000000000..c7ebe855e214
--- /dev/null
+++ b/suite-native/bluetooth/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": { "outDir": "libDev" },
+ "references": []
+}
diff --git a/suite-native/discovery/src/discoveryConfigSlice.ts b/suite-native/discovery/src/discoveryConfigSlice.ts
index a228aa77383d..b6df73bd4a69 100644
--- a/suite-native/discovery/src/discoveryConfigSlice.ts
+++ b/suite-native/discovery/src/discoveryConfigSlice.ts
@@ -76,6 +76,11 @@ export const selectDiscoverySupportedNetworks = memoizeWithArgs(
(state: DeviceRootState, areTestnetsEnabled: boolean) =>
pipe(
selectDeviceSupportedNetworks(state),
+ symbols => {
+ console.log('selectDeviceSupportedNetworks', symbols);
+
+ return symbols;
+ },
networkSymbols => filterTestnetNetworks(networkSymbols, areTestnetsEnabled),
filterUnavailableNetworks,
filterBlacklistedNetworks,
diff --git a/suite-native/discovery/src/discoveryMiddleware.ts b/suite-native/discovery/src/discoveryMiddleware.ts
index b7024340ed04..e7304463ddfb 100644
--- a/suite-native/discovery/src/discoveryMiddleware.ts
+++ b/suite-native/discovery/src/discoveryMiddleware.ts
@@ -4,6 +4,7 @@ import {
discoveryActions,
selectDeviceModel,
selectDeviceFirmwareVersion,
+ DISCOVERY_MODULE_PREFIX,
} from '@suite-common/wallet-core';
import { createMiddlewareWithExtraDeps } from '@suite-common/redux-utils';
import { isFirmwareVersionSupported } from '@suite-native/device';
@@ -17,6 +18,10 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps(
dispatch(discoveryActions.removeDiscovery(action.payload.state));
}
+ if (action.type.startsWith(DISCOVERY_MODULE_PREFIX)) {
+ console.log(action.name, JSON.stringify(action, null, 2));
+ }
+
const device = selectDevice(getState());
const deviceModel = selectDeviceModel(getState());
const deviceFwVersion = selectDeviceFirmwareVersion(getState());
diff --git a/suite-native/discovery/src/discoveryThunks.ts b/suite-native/discovery/src/discoveryThunks.ts
index 549c1ea30633..0aa755c27986 100644
--- a/suite-native/discovery/src/discoveryThunks.ts
+++ b/suite-native/discovery/src/discoveryThunks.ts
@@ -441,6 +441,8 @@ export const createDescriptorPreloadedDiscoveryThunk = createThunk(
return;
}
+ console.log('createDescriptorPreloadedDiscoveryThunk', deviceState, supportedNetworks);
+
const supportedNetworksSymbols = supportedNetworks.map(network => network.symbol);
const discoveryNetworksTotalCount = supportedNetworksSymbols.length;
diff --git a/suite-native/module-connect-device/package.json b/suite-native/module-connect-device/package.json
index 2873c97a391d..0ce2b3892679 100644
--- a/suite-native/module-connect-device/package.json
+++ b/suite-native/module-connect-device/package.json
@@ -19,6 +19,7 @@
"@suite-common/wallet-core": "workspace:*",
"@suite-native/alerts": "workspace:*",
"@suite-native/atoms": "workspace:*",
+ "@suite-native/bluetooth": "workspace:*",
"@suite-native/device": "workspace:*",
"@suite-native/device-mutex": "workspace:*",
"@suite-native/forms": "workspace:*",
diff --git a/suite-native/module-connect-device/src/components/ConnectDeviceSreenView.tsx b/suite-native/module-connect-device/src/components/ConnectDeviceSreenView.tsx
index 25f5d50da069..c785bd5ee4c1 100644
--- a/suite-native/module-connect-device/src/components/ConnectDeviceSreenView.tsx
+++ b/suite-native/module-connect-device/src/components/ConnectDeviceSreenView.tsx
@@ -2,7 +2,7 @@ import { ReactNode } from 'react';
import { Box } from '@suite-native/atoms';
import { Screen } from '@suite-native/navigation';
-import { prepareNativeStyle, useNativeStyles, NativeStyleObject } from '@trezor/styles';
+import { NativeStyleObject, prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { ConnectDeviceScreenHeader } from './ConnectDeviceScreenHeader';
diff --git a/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx b/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx
index dd09a981e9a5..eb93734987b7 100644
--- a/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx
+++ b/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx
@@ -1,16 +1,16 @@
import { useEffect } from 'react';
import { Dimensions } from 'react-native';
-import { useSelector, useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { useIsFocused } from '@react-navigation/native';
+import { authorizeDevice, selectDevice, selectIsDeviceAuthorized } from '@suite-common/wallet-core';
import { Text, VStack } from '@suite-native/atoms';
+import { DevicesScanner } from '@suite-native/bluetooth';
+import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { Translation } from '@suite-native/intl';
-import { ConnectDeviceAnimation } from '@suite-native/device';
-import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { Screen } from '@suite-native/navigation';
-import { selectDevice, selectIsDeviceAuthorized, authorizeDevice } from '@suite-common/wallet-core';
-import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { ConnectDeviceScreenHeader } from '../components/ConnectDeviceScreenHeader';
@@ -56,7 +56,7 @@ export const ConnectAndUnlockDeviceScreen = () => {
-
+
);
diff --git a/suite-native/receive/src/components/DeviceScreenContent.tsx b/suite-native/receive/src/components/DeviceScreenContent.tsx
index af7c9a85175b..2cf608ca7dfd 100644
--- a/suite-native/receive/src/components/DeviceScreenContent.tsx
+++ b/suite-native/receive/src/components/DeviceScreenContent.tsx
@@ -55,7 +55,14 @@ const deviceToContentStyles = {
lineHeight: 25,
pagerOffset: 40,
},
-} as const satisfies Record;
+ T3W1: {
+ fontSource: require('../../../../packages/theme/fonts/RobotoMono-Regular.ttf'),
+ fontSize: 20,
+ lineWidth: 230,
+ lineHeight: 25,
+ pagerOffset: 60,
+ },
+} as const satisfies Record;
type ContentCanvasStyleProps = {
lineWidth: number;
diff --git a/suite-native/state/package.json b/suite-native/state/package.json
index c4d57a4a5d96..28a3afe7dc67 100644
--- a/suite-native/state/package.json
+++ b/suite-native/state/package.json
@@ -31,6 +31,7 @@
"@suite-native/toasts": "workspace:*",
"@trezor/connect": "workspace:*",
"@trezor/transport-native": "workspace:*",
+ "@trezor/transport-native-ble": "workspace:*",
"@trezor/utils": "workspace:*",
"expo-device": "5.9.3",
"react": "18.2.0",
diff --git a/suite-native/state/src/extraDependencies.ts b/suite-native/state/src/extraDependencies.ts
index b7d607f1207b..70ddd7f41605 100644
--- a/suite-native/state/src/extraDependencies.ts
+++ b/suite-native/state/src/extraDependencies.ts
@@ -9,19 +9,21 @@ import { selectDevices } from '@suite-common/wallet-core';
import { selectFiatCurrencyCode, setFiatCurrency } from '@suite-native/module-settings';
import { PROTO } from '@trezor/connect';
import { mergeDeepObject } from '@trezor/utils';
-import { NativeUsbTransport } from '@trezor/transport-native';
+import { NativeTransportBLE } from '@trezor/transport-native-ble';
const deviceType = Device.isDevice ? 'device' : 'emulator';
-const transportsPerDeviceType = {
- device: Platform.select({
- ios: ['BridgeTransport', 'UdpTransport'],
- android: [new NativeUsbTransport()],
- }),
- emulator: ['BridgeTransport', 'UdpTransport'],
-} as const;
+// const transportsPerDeviceType = {
+// device: Platform.select({
+// ios: ['BridgeTransport', 'UdpTransport'],
+// android: [new NativeUsbTransport()],
+// }),
+// emulator: ['BridgeTransport', 'UdpTransport'],
+// } as const;
-const transports = transportsPerDeviceType[deviceType];
+// const transports = transportsPerDeviceType[deviceType];
+
+const transports = [new NativeTransportBLE()];
export const extraDependencies: ExtraDependencies = mergeDeepObject(extraDependenciesMock, {
selectors: {
diff --git a/yarn.lock b/yarn.lock
index f6f87b822e2b..fe7c4d6ed73c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3463,7 +3463,7 @@ __metadata:
languageName: node
linkType: hard
-"@expo/config-plugins@npm:7.8.4, @expo/config-plugins@npm:~7.8.0, @expo/config-plugins@npm:~7.8.2":
+"@expo/config-plugins@npm:7.8.4, @expo/config-plugins@npm:^7.2.5, @expo/config-plugins@npm:~7.8.0, @expo/config-plugins@npm:~7.8.2":
version: 7.8.4
resolution: "@expo/config-plugins@npm:7.8.4"
dependencies:
@@ -8821,10 +8821,12 @@ __metadata:
react: "npm:18.2.0"
react-intl: "npm:^6.6.2"
react-native: "npm:0.73.6"
+ react-native-ble-plx: "npm:^3.1.2"
react-native-flipper: "npm:^0.212.0"
react-native-gesture-handler: "npm:2.15.0"
react-native-keyboard-aware-scroll-view: "npm:0.9.5"
react-native-mmkv: "npm:2.11.0"
+ react-native-permissions: "npm:^4.1.5"
react-native-reanimated: "npm:3.8.1"
react-native-restart: "npm:0.0.27"
react-native-safe-area-context: "npm:4.9.0"
@@ -8927,6 +8929,22 @@ __metadata:
languageName: unknown
linkType: soft
+"@suite-native/bluetooth@workspace:*, @suite-native/bluetooth@workspace:suite-native/bluetooth":
+ version: 0.0.0-use.local
+ resolution: "@suite-native/bluetooth@workspace:suite-native/bluetooth"
+ dependencies:
+ "@suite-native/atoms": "workspace:*"
+ "@suite-native/intl": "workspace:*"
+ "@trezor/styles": "workspace:*"
+ "@trezor/transport-native-ble": "workspace:*"
+ expo-constants: "npm:15.4.5"
+ react: "npm:18.2.0"
+ react-native: "npm:0.73.6"
+ react-native-ble-plx: "npm:^3.1.2"
+ react-native-permissions: "npm:^4.1.5"
+ languageName: unknown
+ linkType: soft
+
"@suite-native/config@workspace:*, @suite-native/config@workspace:suite-native/config":
version: 0.0.0-use.local
resolution: "@suite-native/config@workspace:suite-native/config"
@@ -9332,6 +9350,7 @@ __metadata:
"@suite-common/wallet-core": "workspace:*"
"@suite-native/alerts": "workspace:*"
"@suite-native/atoms": "workspace:*"
+ "@suite-native/bluetooth": "workspace:*"
"@suite-native/device": "workspace:*"
"@suite-native/device-mutex": "workspace:*"
"@suite-native/forms": "workspace:*"
@@ -9708,6 +9727,7 @@ __metadata:
"@suite-native/toasts": "workspace:*"
"@trezor/connect": "workspace:*"
"@trezor/transport-native": "workspace:*"
+ "@trezor/transport-native-ble": "workspace:*"
"@trezor/utils": "workspace:*"
expo-device: "npm:5.9.3"
react: "npm:18.2.0"
@@ -11230,6 +11250,15 @@ __metadata:
languageName: unknown
linkType: soft
+"@trezor/transport-native-ble@workspace:*, @trezor/transport-native-ble@workspace:packages/transport-native-ble":
+ version: 0.0.0-use.local
+ resolution: "@trezor/transport-native-ble@workspace:packages/transport-native-ble"
+ dependencies:
+ "@trezor/transport": "workspace:*"
+ react-native-ble-plx: "npm:^3.1.2"
+ languageName: unknown
+ linkType: soft
+
"@trezor/transport-native@workspace:*, @trezor/transport-native@workspace:packages/transport-native":
version: 0.0.0-use.local
resolution: "@trezor/transport-native@workspace:packages/transport-native"
@@ -33669,6 +33698,18 @@ __metadata:
languageName: node
linkType: hard
+"react-native-ble-plx@npm:^3.1.2":
+ version: 3.1.2
+ resolution: "react-native-ble-plx@npm:3.1.2"
+ dependencies:
+ "@expo/config-plugins": "npm:^7.2.5"
+ peerDependencies:
+ react: "*"
+ react-native: "*"
+ checksum: 10/75e3960fcc8c236d0e39fe07a5abd10f617ae8e1cd4114e2179d0e33876aad0932ce229a39e768914f859b5029445b4e44e21e03d5f031cb680f9eee060e9f43
+ languageName: node
+ linkType: hard
+
"react-native-flipper@npm:^0.212.0":
version: 0.212.0
resolution: "react-native-flipper@npm:0.212.0"
@@ -33726,6 +33767,20 @@ __metadata:
languageName: node
linkType: hard
+"react-native-permissions@npm:^4.1.5":
+ version: 4.1.5
+ resolution: "react-native-permissions@npm:4.1.5"
+ peerDependencies:
+ react: ">=18.1.0"
+ react-native: ">=0.70.0"
+ react-native-windows: ">=0.70.0"
+ peerDependenciesMeta:
+ react-native-windows:
+ optional: true
+ checksum: 10/2b1eaa879e4b0c3455488c3dfa1d8b203bdbf2cd550b8ee5691d26e03c086d33f9bc495a70f8e1e85d0c16131374e11d12f7927168179e339f158eea9fb04830
+ languageName: node
+ linkType: hard
+
"react-native-reanimated@npm:3.8.1":
version: 3.8.1
resolution: "react-native-reanimated@npm:3.8.1"