Merge branch 'master' into clip
|
@ -1,7 +1,6 @@
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import { Alert } from 'react-native';
|
|
||||||
import DefaultPreference from 'react-native-default-preference';
|
import DefaultPreference from 'react-native-default-preference';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import Realm from 'realm';
|
import Realm from 'realm';
|
||||||
|
@ -299,13 +298,13 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Alert.alert(
|
presentAlert({
|
||||||
loc.errors.network,
|
title: loc.errors.network,
|
||||||
loc.formatString(
|
message: loc.formatString(
|
||||||
usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect,
|
usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect,
|
||||||
usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {},
|
usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {},
|
||||||
),
|
),
|
||||||
[
|
buttons: [
|
||||||
{
|
{
|
||||||
text: loc.wallets.list_tryagain,
|
text: loc.wallets.list_tryagain,
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
|
@ -318,10 +317,10 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
||||||
{
|
{
|
||||||
text: loc.settings.electrum_reset,
|
text: loc.settings.electrum_reset,
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
Alert.alert(
|
presentAlert({
|
||||||
loc.settings.electrum_reset,
|
title: loc.settings.electrum_reset,
|
||||||
loc.settings.electrum_reset_to_default,
|
message: loc.settings.electrum_reset_to_default,
|
||||||
[
|
buttons: [
|
||||||
{
|
{
|
||||||
text: loc._.cancel,
|
text: loc._.cancel,
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
|
@ -340,16 +339,15 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
||||||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Must be running on Android
|
console.log(e); // Must be running on Android
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
presentAlert({ message: loc.settings.electrum_saved });
|
presentAlert({ message: loc.settings.electrum_saved });
|
||||||
setTimeout(connectMain, 500);
|
setTimeout(connectMain, 500);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: true },
|
options: { cancelable: true },
|
||||||
);
|
});
|
||||||
connectionAttempt = 0;
|
connectionAttempt = 0;
|
||||||
mainClient.close() && mainClient.close();
|
mainClient.close() && mainClient.close();
|
||||||
},
|
},
|
||||||
|
@ -364,8 +362,8 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: false },
|
options: { cancelable: false },
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,35 +1,92 @@
|
||||||
import { Alert as RNAlert, Platform, ToastAndroid } from 'react-native';
|
import { Alert as RNAlert, Platform, ToastAndroid } from 'react-native';
|
||||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||||
|
import loc from '../loc';
|
||||||
|
|
||||||
export enum AlertType {
|
export enum AlertType {
|
||||||
Alert,
|
Alert,
|
||||||
Toast,
|
Toast,
|
||||||
}
|
}
|
||||||
const presentAlert = ({
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
type = AlertType.Alert,
|
|
||||||
hapticFeedback,
|
|
||||||
}: {
|
|
||||||
title?: string;
|
|
||||||
message: string;
|
|
||||||
type?: AlertType;
|
|
||||||
hapticFeedback?: HapticFeedbackTypes;
|
|
||||||
}) => {
|
|
||||||
if (hapticFeedback) {
|
|
||||||
triggerHapticFeedback(hapticFeedback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Platform.OS !== 'android') {
|
interface AlertButton {
|
||||||
type = AlertType.Alert;
|
text: string;
|
||||||
}
|
onPress?: () => void;
|
||||||
switch (type) {
|
style?: 'default' | 'cancel' | 'destructive';
|
||||||
case AlertType.Toast:
|
}
|
||||||
ToastAndroid.show(message, ToastAndroid.LONG);
|
|
||||||
break;
|
interface AlertOptions {
|
||||||
default:
|
cancelable?: boolean;
|
||||||
RNAlert.alert(title ?? message, title && message ? message : undefined);
|
}
|
||||||
break;
|
|
||||||
}
|
const presentAlert = (() => {
|
||||||
};
|
let lastAlertParams: {
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
type?: AlertType;
|
||||||
|
hapticFeedback?: HapticFeedbackTypes;
|
||||||
|
buttons?: AlertButton[];
|
||||||
|
options?: AlertOptions;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
const clearCache = () => {
|
||||||
|
lastAlertParams = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type = AlertType.Alert,
|
||||||
|
hapticFeedback,
|
||||||
|
buttons = [],
|
||||||
|
options = { cancelable: false },
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
type?: AlertType;
|
||||||
|
hapticFeedback?: HapticFeedbackTypes;
|
||||||
|
buttons?: AlertButton[];
|
||||||
|
options?: AlertOptions;
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
lastAlertParams &&
|
||||||
|
lastAlertParams.title === title &&
|
||||||
|
lastAlertParams.message === message &&
|
||||||
|
lastAlertParams.type === type &&
|
||||||
|
lastAlertParams.hapticFeedback === hapticFeedback &&
|
||||||
|
JSON.stringify(lastAlertParams.buttons) === JSON.stringify(buttons) &&
|
||||||
|
JSON.stringify(lastAlertParams.options) === JSON.stringify(options)
|
||||||
|
) {
|
||||||
|
return; // Skip showing the alert if the content is the same as the last one
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAlertParams = { title, message, type, hapticFeedback, buttons, options };
|
||||||
|
|
||||||
|
if (hapticFeedback) {
|
||||||
|
triggerHapticFeedback(hapticFeedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that there's at least one button (required for both iOS and Android)
|
||||||
|
const wrappedButtons =
|
||||||
|
buttons.length > 0
|
||||||
|
? buttons
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
text: loc._.ok,
|
||||||
|
onPress: () => {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case AlertType.Toast:
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
ToastAndroid.show(message, ToastAndroid.LONG);
|
||||||
|
clearCache();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RNAlert.alert(title ?? message, title && message ? message : undefined, wrappedButtons, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
export default presentAlert;
|
export default presentAlert;
|
||||||
|
|
|
@ -98,6 +98,8 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||||
color: colors.alternativeTextColor,
|
color: colors.alternativeTextColor,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
|
paddingVertical: switchProps ? 8 : 0,
|
||||||
|
lineHeight: 20,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -130,7 +132,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||||
</RNElementsListItem.Title>
|
</RNElementsListItem.Title>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<RNElementsListItem.Subtitle
|
<RNElementsListItem.Subtitle
|
||||||
numberOfLines={subtitleNumberOfLines ?? 1}
|
numberOfLines={switchProps ? 0 : (subtitleNumberOfLines ?? 1)}
|
||||||
accessible={switchProps === undefined}
|
accessible={switchProps === undefined}
|
||||||
style={stylesHook.subtitle}
|
style={stylesHook.subtitle}
|
||||||
>
|
>
|
||||||
|
@ -152,7 +154,9 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||||
<>
|
<>
|
||||||
{chevron && <RNElementsListItem.Chevron iconStyle={{ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }} />}
|
{chevron && <RNElementsListItem.Chevron iconStyle={{ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }} />}
|
||||||
{rightIcon && <Avatar icon={rightIcon} />}
|
{rightIcon && <Avatar icon={rightIcon} />}
|
||||||
{switchProps && <Switch {...memoizedSwitchProps} accessibilityLabel={title} accessible accessibilityRole="switch" />}
|
{switchProps && (
|
||||||
|
<Switch {...memoizedSwitchProps} accessibilityLabel={title} style={styles.margin16} accessible accessibilityRole="switch" />
|
||||||
|
)}
|
||||||
{checkmark && (
|
{checkmark && (
|
||||||
<RNElementsListItem.CheckBox
|
<RNElementsListItem.CheckBox
|
||||||
iconRight
|
iconRight
|
||||||
|
@ -216,5 +220,8 @@ const styles = StyleSheet.create({
|
||||||
margin8: {
|
margin8: {
|
||||||
margin: 8,
|
margin: 8,
|
||||||
},
|
},
|
||||||
|
margin16: {
|
||||||
|
marginLeft: 16,
|
||||||
|
},
|
||||||
width16: { width: 16 },
|
width16: { width: 16 },
|
||||||
});
|
});
|
||||||
|
|
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 2.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128pt@1x.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128pt@2x.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16pt@1x.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16pt@2x.png
Normal file
After Width: | Height: | Size: 614 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256pt@1x.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256pt@2x.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32pt@1x.png
Normal file
After Width: | Height: | Size: 614 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32pt@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512pt@1x.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512pt@2x.png
Normal file
After Width: | Height: | Size: 26 KiB |
|
@ -29,6 +29,72 @@
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "16pt@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "16pt@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "32pt@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "32pt@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "128pt@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "128pt@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "256pt@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "256pt@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "512pt@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "512pt@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "1024 2.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "watchos",
|
||||||
|
"size" : "1024x1024"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|
|
@ -21,9 +21,16 @@ import {
|
||||||
export type AddWalletStackParamList = {
|
export type AddWalletStackParamList = {
|
||||||
AddWallet: undefined;
|
AddWallet: undefined;
|
||||||
ImportWallet: undefined;
|
ImportWallet: undefined;
|
||||||
ImportWalletDiscovery: undefined;
|
ImportWalletDiscovery: {
|
||||||
|
importText: string;
|
||||||
|
askPassphrase: boolean;
|
||||||
|
searchAccounts: boolean;
|
||||||
|
};
|
||||||
ImportSpeed: undefined;
|
ImportSpeed: undefined;
|
||||||
ImportCustomDerivationPath: undefined;
|
ImportCustomDerivationPath: {
|
||||||
|
importText: string;
|
||||||
|
password: string | undefined;
|
||||||
|
};
|
||||||
PleaseBackup: undefined;
|
PleaseBackup: undefined;
|
||||||
PleaseBackupLNDHub: undefined;
|
PleaseBackupLNDHub: undefined;
|
||||||
ProvideEntropy: undefined;
|
ProvideEntropy: undefined;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||||
// Define lazy imports
|
// Define lazy imports
|
||||||
const WalletsAdd = lazy(() => import('../screen/wallets/Add'));
|
const WalletsAdd = lazy(() => import('../screen/wallets/Add'));
|
||||||
const ImportCustomDerivationPath = lazy(() => import('../screen/wallets/importCustomDerivationPath'));
|
const ImportCustomDerivationPath = lazy(() => import('../screen/wallets/importCustomDerivationPath'));
|
||||||
const ImportWalletDiscovery = lazy(() => import('../screen/wallets/importDiscovery'));
|
const ImportWalletDiscovery = lazy(() => import('../screen/wallets/ImportWalletDiscovery'));
|
||||||
const ImportSpeed = lazy(() => import('../screen/wallets/importSpeed'));
|
const ImportSpeed = lazy(() => import('../screen/wallets/importSpeed'));
|
||||||
const ImportWallet = lazy(() => import('../screen/wallets/import'));
|
const ImportWallet = lazy(() => import('../screen/wallets/import'));
|
||||||
const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup'));
|
const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup'));
|
||||||
|
|
16
package-lock.json
generated
|
@ -47,7 +47,7 @@
|
||||||
"coinselect": "3.1.13",
|
"coinselect": "3.1.13",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"detox": "20.27.2",
|
"detox": "20.27.3",
|
||||||
"ecpair": "2.0.1",
|
"ecpair": "2.0.1",
|
||||||
"ecurve": "1.0.6",
|
"ecurve": "1.0.6",
|
||||||
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
||||||
|
@ -9772,9 +9772,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detox": {
|
"node_modules/detox": {
|
||||||
"version": "20.27.2",
|
"version": "20.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/detox/-/detox-20.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/detox/-/detox-20.27.3.tgz",
|
||||||
"integrity": "sha512-cC6S40v7ix+uA5jYzG8eazSs7YtOWgc2aCwWLZIIzfE5Kvo0gfHgtqeRhrYWCMZaj/irKKs39h2B070oNQOIrA==",
|
"integrity": "sha512-bMAjL+6dQLF75DWbJb0gTp6/cOnpFqu5CU+VcE48ZIRLBQp5KD/ppk2zvx/B8k+CeQJjWFGs8exfpVuOBJUTpw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -9784,7 +9784,7 @@
|
||||||
"caf": "^15.0.1",
|
"caf": "^15.0.1",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"child-process-promise": "^2.2.0",
|
"child-process-promise": "^2.2.0",
|
||||||
"detox-copilot": "^0.0.9",
|
"detox-copilot": "^0.0.14",
|
||||||
"execa": "^5.1.1",
|
"execa": "^5.1.1",
|
||||||
"find-up": "^5.0.0",
|
"find-up": "^5.0.0",
|
||||||
"fs-extra": "^11.0.0",
|
"fs-extra": "^11.0.0",
|
||||||
|
@ -9831,9 +9831,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detox-copilot": {
|
"node_modules/detox-copilot": {
|
||||||
"version": "0.0.9",
|
"version": "0.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/detox-copilot/-/detox-copilot-0.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/detox-copilot/-/detox-copilot-0.0.14.tgz",
|
||||||
"integrity": "sha512-Wk2fuisD8EH+349b0ysNWvZ7UEsThAChbYFlLqOR1jWkDaonEvgf6IOUlmxjvyTl9ENtl8ckd1U7k94yCBYwqw==",
|
"integrity": "sha512-XzruvuEVnX/sGkkDLDDLTGuwyoS0qLk+g5psDWgpLrnFd9w4QA0I65G5dwPzcK68lYaOGb0FThXv3fmD37/Mlg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/detox/node_modules/ansi-styles": {
|
"node_modules/detox/node_modules/ansi-styles": {
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
"coinselect": "3.1.13",
|
"coinselect": "3.1.13",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"detox": "20.27.2",
|
"detox": "20.27.3",
|
||||||
"ecpair": "2.0.1",
|
"ecpair": "2.0.1",
|
||||||
"ecurve": "1.0.6",
|
"ecurve": "1.0.6",
|
||||||
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import React, { useEffect, useReducer } from 'react';
|
import React, { useEffect, useReducer } from 'react';
|
||||||
import { ScrollView, TouchableWithoutFeedback, View } from 'react-native';
|
import { ScrollView, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { BlueCard, BlueText } from '../../BlueComponents';
|
|
||||||
import { TWallet } from '../../class/wallets/types';
|
import { TWallet } from '../../class/wallets/types';
|
||||||
import ListItem from '../../components/ListItem';
|
import ListItem from '../../components/ListItem';
|
||||||
import useOnAppLaunch from '../../hooks/useOnAppLaunch';
|
import useOnAppLaunch from '../../hooks/useOnAppLaunch';
|
||||||
|
@ -105,10 +104,9 @@ const DefaultView: React.FC = () => {
|
||||||
value: state.isViewAllWalletsSwitchEnabled,
|
value: state.isViewAllWalletsSwitchEnabled,
|
||||||
disabled: wallets.length <= 0,
|
disabled: wallets.length <= 0,
|
||||||
}}
|
}}
|
||||||
|
subtitle={loc.settings.default_desc}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.default_desc}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
{!state.isViewAllWalletsSwitchEnabled && (
|
{!state.isViewAllWalletsSwitchEnabled && (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.settings.default_info}
|
title={loc.settings.default_info}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
|
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
|
||||||
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
|
||||||
import presentAlert from '../../components/Alert';
|
|
||||||
import ListItem, { TouchableOpacityWrapper } from '../../components/ListItem';
|
import ListItem, { TouchableOpacityWrapper } from '../../components/ListItem';
|
||||||
import { useTheme } from '../../components/themes';
|
import { useTheme } from '../../components/themes';
|
||||||
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
||||||
|
@ -13,6 +11,9 @@ import PromptPasswordConfirmationModal, {
|
||||||
PromptPasswordConfirmationModalHandle,
|
PromptPasswordConfirmationModalHandle,
|
||||||
} from '../../components/PromptPasswordConfirmationModal';
|
} from '../../components/PromptPasswordConfirmationModal';
|
||||||
import { popToTop } from '../../NavigationService';
|
import { popToTop } from '../../NavigationService';
|
||||||
|
import presentAlert from '../../components/Alert';
|
||||||
|
import { Header } from '../../components/Header';
|
||||||
|
import { BlueSpacing20 } from '../../BlueComponents';
|
||||||
|
|
||||||
enum ActionType {
|
enum ActionType {
|
||||||
SetLoading = 'SET_LOADING',
|
SetLoading = 'SET_LOADING',
|
||||||
|
@ -72,9 +73,6 @@ const EncryptStorage = () => {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
headerText: {
|
|
||||||
color: colors.foregroundColor,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const initializeState = useCallback(async () => {
|
const initializeState = useCallback(async () => {
|
||||||
|
@ -139,15 +137,6 @@ const EncryptStorage = () => {
|
||||||
navigate('PlausibleDeniability');
|
navigate('PlausibleDeniability');
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPasscodeExplanation = () => {
|
|
||||||
return Platform.OS === 'android' && Platform.Version >= 30 ? (
|
|
||||||
<>
|
|
||||||
<BlueText />
|
|
||||||
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })}</BlueText>
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
contentContainerStyle={[styles.root, styleHooks.root]}
|
contentContainerStyle={[styles.root, styleHooks.root]}
|
||||||
|
@ -157,26 +146,30 @@ const EncryptStorage = () => {
|
||||||
<View style={styles.paddingTop} />
|
<View style={styles.paddingTop} />
|
||||||
{state.deviceBiometricCapable && (
|
{state.deviceBiometricCapable && (
|
||||||
<>
|
<>
|
||||||
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
|
<Header leftText={loc.settings.biometrics} />
|
||||||
{loc.settings.biometrics}
|
|
||||||
</Text>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType! })}
|
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType! })}
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch, disabled: state.currentLoadingSwitch !== null }}
|
switch={{
|
||||||
|
value: biometricEnabled,
|
||||||
|
onValueChange: onUseBiometricSwitch,
|
||||||
|
disabled: state.currentLoadingSwitch !== null,
|
||||||
|
}}
|
||||||
isLoading={state.currentLoadingSwitch === 'biometric' && state.isLoading}
|
isLoading={state.currentLoadingSwitch === 'biometric' && state.isLoading}
|
||||||
containerStyle={[styles.row, styleHooks.root]}
|
containerStyle={[styles.row, styleHooks.root]}
|
||||||
|
subtitle={
|
||||||
|
<>
|
||||||
|
<Text style={styles.subtitleText}>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType! })}</Text>
|
||||||
|
{Platform.OS === 'android' && Platform.Version >= 30 && (
|
||||||
|
<Text style={styles.subtitleText}>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })}</Text>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType! })}</BlueText>
|
|
||||||
{renderPasscodeExplanation()}
|
|
||||||
</BlueCard>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
|
<BlueSpacing20 />
|
||||||
{loc.settings.encrypt_tstorage}
|
<Header leftText={loc.settings.encrypt_tstorage} />
|
||||||
</Text>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
testID="EncyptedAndPasswordProtected"
|
testID="EncyptedAndPasswordProtected"
|
||||||
title={loc.settings.encrypt_enc_and_pass}
|
title={loc.settings.encrypt_enc_and_pass}
|
||||||
|
@ -211,7 +204,7 @@ const EncryptStorage = () => {
|
||||||
dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.SUCCESS });
|
dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.SUCCESS });
|
||||||
success = true;
|
success = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
presentAlert({ title: loc.errors.error, message: (error as Error).message });
|
presentAlert({ message: (error as Error).message });
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
} else if (state.modalType === MODAL_TYPES.ENTER_PASSWORD) {
|
} else if (state.modalType === MODAL_TYPES.ENTER_PASSWORD) {
|
||||||
|
@ -243,12 +236,11 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
paddingTop: { paddingTop: 19 },
|
paddingTop: { paddingTop: 19 },
|
||||||
headerText: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 30,
|
|
||||||
marginLeft: 17,
|
|
||||||
},
|
|
||||||
row: { minHeight: 60 },
|
row: { minHeight: 60 },
|
||||||
|
subtitleText: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 5,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default EncryptStorage;
|
export default EncryptStorage;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, ScrollView, StyleSheet } from 'react-native';
|
import { Platform, ScrollView, StyleSheet } from 'react-native';
|
||||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
import { BlueSpacing20 } from '../../BlueComponents';
|
||||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||||
import { useTheme } from '../../components/themes';
|
import { useTheme } from '../../components/themes';
|
||||||
import loc from '../../loc';
|
import loc from '../../loc';
|
||||||
|
@ -50,14 +50,10 @@ const GeneralSettings: React.FC = () => {
|
||||||
title={loc.settings.general_continuity}
|
title={loc.settings.general_continuity}
|
||||||
Component={PressableWrapper}
|
Component={PressableWrapper}
|
||||||
switch={{ onValueChange: onHandOffUseEnabledChange, value: isHandOffUseEnabled }}
|
switch={{ onValueChange: onHandOffUseEnabledChange, value: isHandOffUseEnabled }}
|
||||||
|
subtitle={loc.settings.general_continuity_e}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.general_continuity_e}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<BlueSpacing20 />
|
|
||||||
<ListItem
|
<ListItem
|
||||||
Component={PressableWrapper}
|
Component={PressableWrapper}
|
||||||
title="Legacy URv1 QR"
|
title="Legacy URv1 QR"
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { Platform, Pressable, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
import { Platform, Pressable, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { openSettings } from 'react-native-permissions';
|
import { openSettings } from 'react-native-permissions';
|
||||||
import A from '../../blue_modules/analytics';
|
import A from '../../blue_modules/analytics';
|
||||||
import { BlueCard, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
|
|
||||||
import { Header } from '../../components/Header';
|
import { Header } from '../../components/Header';
|
||||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||||
import { useTheme } from '../../components/themes';
|
import { useTheme } from '../../components/themes';
|
||||||
|
@ -10,6 +9,7 @@ import { setBalanceDisplayAllowed } from '../../components/WidgetCommunication';
|
||||||
import loc from '../../loc';
|
import loc from '../../loc';
|
||||||
import { useStorage } from '../../hooks/context/useStorage';
|
import { useStorage } from '../../hooks/context/useStorage';
|
||||||
import { useSettings } from '../../hooks/context/useSettings';
|
import { useSettings } from '../../hooks/context/useSettings';
|
||||||
|
import { BlueSpacing20 } from '../../BlueComponents';
|
||||||
|
|
||||||
enum SettingsPrivacySection {
|
enum SettingsPrivacySection {
|
||||||
None,
|
None,
|
||||||
|
@ -45,9 +45,6 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
widgetsHeader: {
|
|
||||||
color: colors.foregroundColor,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -130,13 +127,13 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
disabled: isLoading === SettingsPrivacySection.All,
|
disabled: isLoading === SettingsPrivacySection.All,
|
||||||
testID: 'ClipboardSwitch',
|
testID: 'ClipboardSwitch',
|
||||||
}}
|
}}
|
||||||
|
subtitle={
|
||||||
|
<Pressable accessibilityRole="button">
|
||||||
|
<Text style={styles.subtitleText}>{loc.settings.privacy_clipboard_explanation}</Text>
|
||||||
|
</Pressable>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<Pressable accessibilityRole="button">
|
|
||||||
<BlueText>{loc.settings.privacy_clipboard_explanation}</BlueText>
|
|
||||||
</Pressable>
|
|
||||||
</BlueCard>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.settings.privacy_quickactions}
|
title={loc.settings.privacy_quickactions}
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
|
@ -146,12 +143,14 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||||
testID: 'QuickActionsSwitch',
|
testID: 'QuickActionsSwitch',
|
||||||
}}
|
}}
|
||||||
|
subtitle={
|
||||||
|
<>
|
||||||
|
<Text style={styles.subtitleText}>{loc.settings.privacy_quickactions_explanation}</Text>
|
||||||
|
{storageIsEncrypted && <Text style={styles.subtitleText}>{loc.settings.encrypted_feature_disabled}</Text>}
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.privacy_quickactions_explanation}</BlueText>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
|
||||||
</BlueCard>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.total_balance_view.title}
|
title={loc.total_balance_view.title}
|
||||||
Component={PressableWrapper}
|
Component={PressableWrapper}
|
||||||
|
@ -161,11 +160,9 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
disabled: isLoading === SettingsPrivacySection.All || wallets.length < 2,
|
disabled: isLoading === SettingsPrivacySection.All || wallets.length < 2,
|
||||||
testID: 'TotalBalanceSwitch',
|
testID: 'TotalBalanceSwitch',
|
||||||
}}
|
}}
|
||||||
|
subtitle={<Text style={styles.subtitleText}>{loc.total_balance_view.explanation}</Text>}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.total_balance_view.explanation}</BlueText>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
</BlueCard>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.settings.privacy_temporary_screenshots}
|
title={loc.settings.privacy_temporary_screenshots}
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
|
@ -174,24 +171,24 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
value: !isPrivacyBlurEnabled,
|
value: !isPrivacyBlurEnabled,
|
||||||
disabled: isLoading === SettingsPrivacySection.All,
|
disabled: isLoading === SettingsPrivacySection.All,
|
||||||
}}
|
}}
|
||||||
|
subtitle={<Text style={styles.subtitleText}>{loc.settings.privacy_temporary_screenshots_instructions}</Text>}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.privacy_temporary_screenshots_instructions}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.settings.privacy_do_not_track}
|
title={loc.settings.privacy_do_not_track}
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
switch={{ onValueChange: onDoNotTrackValueChange, value: isDoNotTrackEnabled, disabled: isLoading === SettingsPrivacySection.All }}
|
switch={{
|
||||||
|
onValueChange: onDoNotTrackValueChange,
|
||||||
|
value: isDoNotTrackEnabled,
|
||||||
|
disabled: isLoading === SettingsPrivacySection.All,
|
||||||
|
}}
|
||||||
|
subtitle={<Text style={styles.subtitleText}>{loc.settings.privacy_do_not_track_explanation}</Text>}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.privacy_do_not_track_explanation}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
{Platform.OS === 'ios' && (
|
{Platform.OS === 'ios' && (
|
||||||
<>
|
<>
|
||||||
<BlueSpacing40 />
|
<BlueSpacing20 />
|
||||||
<Text adjustsFontSizeToFit style={[styles.widgetsHeader, styleHooks.widgetsHeader]}>
|
<Header leftText={loc.settings.widgets} />
|
||||||
{loc.settings.widgets}
|
|
||||||
</Text>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={loc.settings.total_balance}
|
title={loc.settings.total_balance}
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
|
@ -200,18 +197,17 @@ const SettingsPrivacy: React.FC = () => {
|
||||||
value: storageIsEncrypted ? false : isWidgetBalanceDisplayAllowed,
|
value: storageIsEncrypted ? false : isWidgetBalanceDisplayAllowed,
|
||||||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||||
}}
|
}}
|
||||||
|
subtitle={
|
||||||
|
<>
|
||||||
|
<Text style={styles.subtitleText}>{loc.settings.total_balance_explanation}</Text>
|
||||||
|
{storageIsEncrypted && <Text style={styles.subtitleText}>{loc.settings.encrypted_feature_disabled}</Text>}
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.total_balance_explanation}</BlueText>
|
|
||||||
<BlueSpacing20 />
|
|
||||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
|
||||||
</BlueCard>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<BlueSpacing20 />
|
|
||||||
<ListItem title={loc.settings.privacy_system_settings} chevron onPress={openApplicationSettings} testID="PrivacySystemSettings" />
|
<ListItem title={loc.settings.privacy_system_settings} chevron onPress={openApplicationSettings} testID="PrivacySystemSettings" />
|
||||||
<BlueSpacing20 />
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -220,14 +216,14 @@ const styles = StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
widgetsHeader: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 30,
|
|
||||||
marginLeft: 17,
|
|
||||||
},
|
|
||||||
headerContainer: {
|
headerContainer: {
|
||||||
paddingVertical: 16,
|
paddingVertical: 16,
|
||||||
},
|
},
|
||||||
|
subtitleText: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 5,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SettingsPrivacy;
|
export default SettingsPrivacy;
|
||||||
|
|
|
@ -416,10 +416,9 @@ export default class ElectrumSettings extends Component {
|
||||||
value: this.state.isOfflineMode,
|
value: this.state.isOfflineMode,
|
||||||
testID: 'ElectrumConnectionEnabledSwitch',
|
testID: 'ElectrumConnectionEnabledSwitch',
|
||||||
}}
|
}}
|
||||||
|
subtitle={loc.settings.electrum_offline_description}
|
||||||
/>
|
/>
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.electrum_offline_description}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
{!this.state.isOfflineMode && this.renderElectrumSettings()}
|
{!this.state.isOfflineMode && this.renderElectrumSettings()}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,14 +105,11 @@ const NotificationSettings = () => {
|
||||||
<ListItem
|
<ListItem
|
||||||
Component={TouchableWithoutFeedback}
|
Component={TouchableWithoutFeedback}
|
||||||
title={loc.settings.push_notifications}
|
title={loc.settings.push_notifications}
|
||||||
|
subtitle={loc.settings.groundcontrol_explanation}
|
||||||
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
|
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
|
||||||
/>
|
/>
|
||||||
<BlueSpacing20 />
|
<BlueSpacing20 />
|
||||||
|
|
||||||
<BlueCard>
|
|
||||||
<BlueText>{loc.settings.groundcontrol_explanation}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
|
|
||||||
<ButtonRNElements
|
<ButtonRNElements
|
||||||
icon={{
|
icon={{
|
||||||
name: 'github',
|
name: 'github',
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useRoute } from '@react-navigation/native';
|
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||||
import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native';
|
import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native';
|
||||||
import IdleTimerManager from 'react-native-idle-timer';
|
import IdleTimerManager from 'react-native-idle-timer';
|
||||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||||
import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20 } from '../../BlueComponents';
|
import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||||
import { HDSegwitBech32Wallet } from '../../class';
|
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||||
import startImport from '../../class/wallet-import';
|
import startImport from '../../class/wallet-import';
|
||||||
import presentAlert from '../../components/Alert';
|
import presentAlert from '../../components/Alert';
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
|
@ -15,20 +15,44 @@ import prompt from '../../helpers/prompt';
|
||||||
import loc from '../../loc';
|
import loc from '../../loc';
|
||||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||||
import { useStorage } from '../../hooks/context/useStorage';
|
import { useStorage } from '../../hooks/context/useStorage';
|
||||||
|
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types';
|
||||||
|
import { navigate } from '../../NavigationService';
|
||||||
|
|
||||||
const ImportWalletDiscovery = () => {
|
type RouteProps = RouteProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||||
const navigation = useExtendedNavigation();
|
type NavigationProp = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||||
|
|
||||||
|
type TReturn = {
|
||||||
|
cancelled?: boolean;
|
||||||
|
stopped?: boolean;
|
||||||
|
wallets: TWallet[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ImportTask = {
|
||||||
|
promise: Promise<TReturn>;
|
||||||
|
stop: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WalletEntry = {
|
||||||
|
wallet: TWallet | THDWalletForWatchOnly;
|
||||||
|
subtitle: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImportWalletDiscovery: React.FC = () => {
|
||||||
|
const navigation = useExtendedNavigation<NavigationProp>();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const route = useRoute();
|
const route = useRoute<RouteProps>();
|
||||||
const { importText, askPassphrase, searchAccounts } = route.params;
|
const { importText, askPassphrase, searchAccounts } = route.params;
|
||||||
const task = useRef();
|
const task = useRef<ImportTask | null>(null);
|
||||||
const { addAndSaveWallet } = useStorage();
|
const { addAndSaveWallet } = useStorage();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [wallets, setWallets] = useState([]);
|
const [wallets, setWallets] = useState<WalletEntry[]>([]);
|
||||||
const [password, setPassword] = useState();
|
const [password, setPassword] = useState<string | undefined>();
|
||||||
const [selected, setSelected] = useState(0);
|
const [selected, setSelected] = useState<number>(0);
|
||||||
const [progress, setProgress] = useState();
|
const [progress, setProgress] = useState<string | undefined>();
|
||||||
const importing = useRef(false);
|
const importing = useRef<boolean>(false);
|
||||||
const bip39 = useMemo(() => {
|
const bip39 = useMemo(() => {
|
||||||
const hd = new HDSegwitBech32Wallet();
|
const hd = new HDSegwitBech32Wallet();
|
||||||
hd.setSecret(importText);
|
hd.setSecret(importText);
|
||||||
|
@ -44,32 +68,46 @@ const ImportWalletDiscovery = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveWallet = wallet => {
|
const saveWallet = useCallback(
|
||||||
if (importing.current) return;
|
(wallet: TWallet | THDWalletForWatchOnly) => {
|
||||||
importing.current = true;
|
if (importing.current) return;
|
||||||
addAndSaveWallet(wallet);
|
importing.current = true;
|
||||||
navigation.getParent().pop();
|
addAndSaveWallet(wallet);
|
||||||
};
|
navigate('WalletsList');
|
||||||
|
},
|
||||||
|
[addAndSaveWallet],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onProgress = data => setProgress(data);
|
const onProgress = (data: string) => setProgress(data);
|
||||||
|
|
||||||
const onWallet = wallet => {
|
const onWallet = (wallet: TWallet | THDWalletForWatchOnly) => {
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
const id = wallet.getID();
|
const id = wallet.getID();
|
||||||
let subtitle;
|
let subtitle: string | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
subtitle = wallet.getDerivationPath?.();
|
// For watch-only wallets, display the descriptor or xpub
|
||||||
|
if (wallet.type === WatchOnlyWallet.type) {
|
||||||
|
if (wallet.isHd() && wallet.getSecret()) {
|
||||||
|
subtitle = wallet.getSecret(); // Display descriptor
|
||||||
|
} else {
|
||||||
|
subtitle = wallet.getAddress(); // Display address
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtitle = (wallet as THDWalletForWatchOnly).getDerivationPath?.();
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
setWallets(w => [...w, { wallet, subtitle, id }]);
|
|
||||||
|
setWallets(w => [...w, { wallet, subtitle: subtitle || '', id }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPassword = async (title, subtitle) => {
|
const onPassword = async (title: string, subtitle: string) => {
|
||||||
try {
|
try {
|
||||||
const pass = await prompt(title, subtitle);
|
const pass = await prompt(title, subtitle);
|
||||||
setPassword(pass);
|
setPassword(pass);
|
||||||
return pass;
|
return pass;
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
if (e.message === 'Cancel Pressed') {
|
if (e.message === 'Cancel Pressed') {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
}
|
}
|
||||||
|
@ -84,7 +122,7 @@ const ImportWalletDiscovery = () => {
|
||||||
task.current.promise
|
task.current.promise
|
||||||
.then(({ cancelled, wallets: w }) => {
|
.then(({ cancelled, wallets: w }) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (w.length === 1) saveWallet(w[0]); // instantly save wallet if only one has been discovered
|
if (w.length === 1) saveWallet(w[0]); // Instantly save wallet if only one has been discovered
|
||||||
if (w.length === 0) {
|
if (w.length === 0) {
|
||||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||||
}
|
}
|
||||||
|
@ -99,16 +137,19 @@ const ImportWalletDiscovery = () => {
|
||||||
IdleTimerManager.setIdleTimerDisabled(false);
|
IdleTimerManager.setIdleTimerDisabled(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => task.current.stop();
|
return () => {
|
||||||
|
task.current?.stop();
|
||||||
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCustomDerivation = () => {
|
const handleCustomDerivation = () => {
|
||||||
task.current.stop();
|
task.current?.stop();
|
||||||
|
|
||||||
navigation.navigate('ImportCustomDerivationPath', { importText, password });
|
navigation.navigate('ImportCustomDerivationPath', { importText, password });
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item, index }) => (
|
const renderItem = ({ item, index }: { item: WalletEntry; index: number }) => (
|
||||||
<WalletToImport
|
<WalletToImport
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.wallet.typeReadable}
|
title={item.wallet.typeReadable}
|
||||||
|
@ -121,22 +162,45 @@ const ImportWalletDiscovery = () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const keyExtractor = w => w.id;
|
const keyExtractor = (w: WalletEntry) => w.id;
|
||||||
|
|
||||||
|
const ListHeaderComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
{wallets && wallets.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<BlueSpacing20 />
|
||||||
|
<BlueFormLabel>{loc.wallets.import_discovery_subtitle}</BlueFormLabel>
|
||||||
|
<BlueSpacing10 />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[wallets],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ListEmptyComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<View style={styles.noWallets}>
|
||||||
|
<BlueText style={styles.center}>{loc.wallets.import_discovery_no_wallets}</BlueText>
|
||||||
|
<BlueSpacing20 />
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeArea style={[styles.root, stylesHook.root]}>
|
<SafeArea style={[styles.root, stylesHook.root]}>
|
||||||
<BlueSpacing20 />
|
<FlatList
|
||||||
<BlueFormLabel>{loc.wallets.import_discovery_subtitle}</BlueFormLabel>
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
<BlueSpacing20 />
|
contentContainerStyle={styles.flatListContainer}
|
||||||
|
data={wallets}
|
||||||
{!loading && wallets.length === 0 ? (
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
<View style={styles.noWallets}>
|
keyExtractor={keyExtractor}
|
||||||
<BlueFormLabel>{loc.wallets.import_discovery_no_wallets}</BlueFormLabel>
|
renderItem={renderItem}
|
||||||
</View>
|
automaticallyAdjustContentInsets
|
||||||
) : (
|
contentInsetAdjustmentBehavior="always"
|
||||||
<FlatList contentContainerStyle={styles.flatListContainer} data={wallets} keyExtractor={keyExtractor} renderItem={renderItem} />
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={[styles.center, stylesHook.center]}>
|
<View style={[styles.center, stylesHook.center]}>
|
||||||
{loading && (
|
{loading && (
|
||||||
<>
|
<>
|
||||||
|
@ -157,9 +221,12 @@ const ImportWalletDiscovery = () => {
|
||||||
<BlueSpacing10 />
|
<BlueSpacing10 />
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
disabled={wallets.length === 0}
|
disabled={wallets?.length === 0}
|
||||||
title={loc.wallets.import_do_import}
|
title={loc.wallets.import_do_import}
|
||||||
onPress={() => saveWallet(wallets[selected].wallet)}
|
onPress={() => {
|
||||||
|
if (wallets.length === 0) return;
|
||||||
|
saveWallet(wallets[selected].wallet);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -169,7 +236,6 @@ const ImportWalletDiscovery = () => {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
paddingTop: 40,
|
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
flatListContainer: {
|
flatListContainer: {
|