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