REF: Replace abandoned package

This commit is contained in:
Marcos Rodriguez Velez 2024-06-30 13:17:55 -04:00
parent c2287f2df6
commit 2d663ac8e2
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
13 changed files with 486 additions and 532 deletions

14
App.tsx
View file

@ -10,13 +10,25 @@ import { BlueDarkTheme, BlueDefaultTheme } from './components/themes';
import MasterView from './navigation/MasterView';
import { navigationRef } from './NavigationService';
import { StorageProvider } from './components/Context/StorageProvider';
import { TrueSheet } from '@lodev09/react-native-true-sheet';
const App = () => {
const colorScheme = useColorScheme();
const onReady = () => {
// @ts-ignore: fix later
navigationRef.current?.addListener('beforeRemove', async (e: any) => {
if (e.data.action.type === 'NAVIGATE') {
e.preventDefault();
await TrueSheet.dismiss('BottomModal');
navigationRef.current?.dispatch(e.data.action);
}
});
};
return (
<LargeScreenProvider>
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme} onReady={onReady}>
<SafeAreaProvider>
<StorageProvider>
<SettingsProvider>

View file

@ -5,8 +5,8 @@ buildscript {
minSdkVersion = 24
supportLibVersion = "28.0.0"
buildToolsVersion = "34.0.0"
compileSdkVersion = 33
targetSdkVersion = 33
compileSdkVersion = 34
targetSdkVersion = 34
googlePlayServicesVersion = "16.+"
googlePlayServicesIidVersion = "16.0.1"
firebaseVersion = "17.3.4"
@ -63,7 +63,7 @@ subprojects {
if (project.hasProperty("android")) {
android {
buildToolsVersion "34.0.0"
compileSdkVersion 33
compileSdkVersion 34
defaultConfig {
minSdkVersion 24
}

View file

@ -1,83 +1,56 @@
import React from 'react';
import { Platform, StyleSheet, useWindowDimensions, View } from 'react-native';
import Modal from 'react-native-modal';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import { TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet';
import { BlueSpacing10 } from '../BlueComponents';
import loc from '../loc';
import Button from './Button';
import { useTheme } from './themes';
const styles = StyleSheet.create({
root: {
justifyContent: 'flex-end',
margin: 0,
},
hasDoneButton: {
padding: 16,
paddingBottom: 24,
},
});
interface BottomModalProps {
interface BottomModalProps extends TrueSheetProps {
children?: React.ReactNode;
onBackButtonPress?: () => void;
onBackdropPress?: () => void;
onClose: () => void;
windowHeight?: number;
windowWidth?: number;
doneButton?: boolean;
avoidKeyboard?: boolean;
allowBackdropPress?: boolean;
isVisible: boolean;
coverScreen?: boolean;
onClose?: () => void;
name?: string;
isGrabberVisible?: boolean;
}
const BottomModal: React.FC<BottomModalProps> = ({
onBackButtonPress,
onBackdropPress,
onClose,
windowHeight,
windowWidth,
doneButton,
isVisible,
avoidKeyboard = false,
allowBackdropPress = true,
coverScreen = true,
...props
}) => {
const { height: valueWindowHeight, width: valueWindowWidth } = useWindowDimensions();
const handleBackButtonPress = onBackButtonPress ?? onClose;
const handleBackdropPress = allowBackdropPress ? onBackdropPress ?? onClose : undefined;
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
hasDoneButton: {
backgroundColor: colors.elevated,
},
});
export interface BottomModalHandle {
present: () => Promise<void>;
dismiss: () => Promise<void>;
}
return (
<Modal
style={styles.root}
deviceHeight={windowHeight ?? valueWindowHeight}
deviceWidth={windowWidth ?? valueWindowWidth}
onBackButtonPress={handleBackButtonPress}
onBackdropPress={handleBackdropPress}
isVisible={isVisible}
coverScreen={coverScreen}
{...props}
accessibilityViewIsModal
avoidKeyboard={avoidKeyboard}
useNativeDriverForBackdrop={Platform.OS === 'android'}
>
{props.children}
{doneButton && (
<View style={[styles.hasDoneButton, stylesHook.hasDoneButton]}>
<Button title={loc.send.input_done} onPress={onClose} testID="ModalDoneButton" />
<BlueSpacing10 />
</View>
)}
</Modal>
);
};
const BottomModal = forwardRef<BottomModalHandle, BottomModalProps>(
({ name, onClose, onPresent, onSizeChange, isGrabberVisible = true, children, ...props }, ref) => {
const trueSheetRef = useRef<TrueSheet>(null);
useImperativeHandle(ref, () => ({
present: async () => {
if (trueSheetRef.current?.present) {
await trueSheetRef.current.present();
} else {
return Promise.resolve();
}
},
dismiss: async () => {
if (trueSheetRef.current?.dismiss) {
await trueSheetRef.current.dismiss();
} else {
return Promise.resolve();
}
},
}));
return (
<TrueSheet
name={name ?? 'BottomModal'}
ref={trueSheetRef}
cornerRadius={24}
sizes={['auto']}
blurTint="regular"
onDismiss={onClose}
onPresent={onPresent}
onSizeChange={onSizeChange}
grabber={isGrabberVisible}
{...props}
>
{children}
</TrueSheet>
);
},
);
export default BottomModal;

View file

@ -517,6 +517,9 @@ PODS:
- RNWatch (1.1.0):
- React
- SocketRocket (0.6.1)
- TrueSheet (0.12.2):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- Yoga (1.14.0)
DEPENDENCIES:
@ -600,6 +603,7 @@ DEPENDENCIES:
- RNSVG (from `../node_modules/react-native-svg`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- RNWatch (from `../node_modules/react-native-watch-connectivity`)
- "TrueSheet (from `../node_modules/@lodev09/react-native-true-sheet`)"
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@ -767,6 +771,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-vector-icons"
RNWatch:
:path: "../node_modules/react-native-watch-connectivity"
TrueSheet:
:path: "../node_modules/@lodev09/react-native-true-sheet"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
@ -854,6 +860,7 @@ SPEC CHECKSUMS:
RNVectorIcons: 32462e7c7e58fe457474fc79c4d7de3f0ef08d70
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
TrueSheet: 8235baf14119d4f9d8a904ed1de26111f73b733c
Yoga: c32e0be1a17f8f1f0e633a3122f7666441f52c82
PODFILE CHECKSUM: f19eea438501edfe85fb2fa51d40ba1b57540758

57
package-lock.json generated
View file

@ -14,6 +14,7 @@
"@bugsnag/react-native": "7.24.0",
"@bugsnag/source-maps": "2.3.3",
"@keystonehq/bc-ur-registry": "0.6.4",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#329026d",
"@ngraveio/bc-ur": "1.1.12",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "1.23.1",
@ -80,7 +81,6 @@
"react-native-keychain": "8.2.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.2.0",
"react-native-modal": "13.0.1",
"react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
"react-native-permissions": "4.1.5",
"react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783",
@ -4234,6 +4234,19 @@
"tslib": "^2.3.0"
}
},
"node_modules/@lodev09/react-native-true-sheet": {
"version": "0.12.2",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#329026d62e8c077aa1ae55e09d6d57dcc5fd6efd",
"license": "MIT",
"workspaces": [
"example",
"docs"
],
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@ngraveio/bc-ur": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@ngraveio/bc-ur/-/bc-ur-1.1.12.tgz",
@ -19558,14 +19571,6 @@
"react": "18.2.0"
}
},
"node_modules/react-native-animatable": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.3.tgz",
"integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==",
"dependencies": {
"prop-types": "^15.7.2"
}
},
"node_modules/react-native-biometrics": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-biometrics/-/react-native-biometrics-3.0.1.tgz",
@ -19786,19 +19791,6 @@
}
}
},
"node_modules/react-native-modal": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.1.tgz",
"integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==",
"dependencies": {
"prop-types": "^15.6.2",
"react-native-animatable": "1.3.3"
},
"peerDependencies": {
"react": "*",
"react-native": ">=0.65.0"
}
},
"node_modules/react-native-obscure": {
"name": "@talaikis/react-native-obscure",
"version": "0.0.3",
@ -25761,6 +25753,10 @@
"tslib": "^2.3.0"
}
},
"@lodev09/react-native-true-sheet": {
"version": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#329026d62e8c077aa1ae55e09d6d57dcc5fd6efd",
"from": "@lodev09/react-native-true-sheet@github:BlueWallet/react-native-true-sheet#329026d"
},
"@ngraveio/bc-ur": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@ngraveio/bc-ur/-/bc-ur-1.1.12.tgz",
@ -37350,14 +37346,6 @@
}
}
},
"react-native-animatable": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.3.tgz",
"integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-native-biometrics": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-biometrics/-/react-native-biometrics-3.0.1.tgz",
@ -37489,15 +37477,6 @@
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.2.0.tgz",
"integrity": "sha512-Wi486WdDoBcDKUNxzr2/8f64ZwkWExlIvn0lcfFhsRNP2CVXGkjLyXhr3h6jf3Utkv5EmoFKiNqcmZn9kjYDxQ=="
},
"react-native-modal": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.1.tgz",
"integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==",
"requires": {
"prop-types": "^15.6.2",
"react-native-animatable": "1.3.3"
}
},
"react-native-obscure": {
"version": "git+ssh://git@github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
"integrity": "sha512-bmzbnlXII8hW7steqwouzQW9cJ+mdA8HJ8pWkb0KD60jC5dYgJ0e66wgUiSTDNFPrvlEKriE4eEanoJFj7Q9pg==",

View file

@ -98,6 +98,7 @@
"@bugsnag/react-native": "7.24.0",
"@bugsnag/source-maps": "2.3.3",
"@keystonehq/bc-ur-registry": "0.6.4",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#329026d",
"@ngraveio/bc-ur": "1.1.12",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "1.23.1",
@ -164,7 +165,6 @@
"react-native-keychain": "8.2.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.2.0",
"react-native-modal": "13.0.1",
"react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
"react-native-permissions": "4.1.5",
"react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783",

View file

@ -6,7 +6,7 @@ import { BlueLoading, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../
import { LightningLdkWallet } from '../../class';
import { TWallet } from '../../class/wallets/types';
import presentAlert from '../../components/Alert';
import BottomModal from '../../components/BottomModal';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
import Button from '../../components/Button';
import LNNodeBar from '../../components/LNNodeBar';
import navigationStyle from '../../components/navigationStyle';
@ -46,6 +46,7 @@ const LdkInfo = () => {
const [pendingChannels, setPendingChannels] = useState<any[]>([]);
const [wBalance, setWalletBalance] = useState<{ confirmedBalance?: number }>({});
const [maturingBalance, setMaturingBalance] = useState(0);
const bottomModalRef = useRef<BottomModalHandle>(null);
const [maturingEta, setMaturingEta] = useState('');
const centerContent = channels.length === 0 && pendingChannels.length === 0 && inactiveChannels.length === 0;
const allChannelsAmount = useRef(0);
@ -210,6 +211,7 @@ const LdkInfo = () => {
const closeModal = () => {
Keyboard.dismiss();
setSelectedChannelIndex(undefined);
bottomModalRef.current?.dismiss();
};
const handleOnConnectPeerTapped = async (channelData: any) => {
@ -222,7 +224,7 @@ const LdkInfo = () => {
const status = selectedChannelIndex?.status;
const channelData = selectedChannelIndex?.channel.item;
return (
<BottomModal isVisible={selectedChannelIndex !== undefined} onClose={closeModal} avoidKeyboard>
<BottomModal ref={bottomModalRef}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<Text style={stylesHook.detailsText}>{loc.lnd.node_alias}</Text>
<BlueSpacing10 />

View file

@ -1,17 +1,6 @@
import { useFocusEffect, useRoute } from '@react-navigation/native';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardAvoidingView,
Platform,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { BackHandler, InteractionManager, Keyboard, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
import Share from 'react-native-share';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
@ -47,12 +36,12 @@ const ReceiveDetails = () => {
const [customUnit, setCustomUnit] = useState(BitcoinUnit.BTC);
const [bip21encoded, setBip21encoded] = useState();
const [isCustom, setIsCustom] = useState(false);
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
const [showPendingBalance, setShowPendingBalance] = useState(false);
const [showConfirmedBalance, setShowConfirmedBalance] = useState(false);
const [showAddress, setShowAddress] = useState(false);
const [currentTab, setCurrentTab] = useState(segmentControlValues[0]);
const { goBack, setParams } = useExtendedNavigation();
const bottomModalRef = useRef(null);
const { colors } = useTheme();
const [intervalMs, setIntervalMs] = useState(5000);
const [eta, setEta] = useState('');
@ -62,11 +51,6 @@ const ReceiveDetails = () => {
const fetchAddressInterval = useRef();
const receiveAddressButton = useRef();
const stylesHook = StyleSheet.create({
modalContent: {
backgroundColor: colors.modal,
borderTopColor: colors.foregroundColor,
borderWidth: colors.borderWidth,
},
customAmount: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
@ -324,16 +308,16 @@ const ReceiveDetails = () => {
const dismissCustomAmountModal = () => {
Keyboard.dismiss();
setIsCustomModalVisible(false);
bottomModalRef.current.dismiss();
};
const showCustomAmountModal = () => {
setIsCustomModalVisible(true);
bottomModalRef.current.present();
};
const createCustomAmountAddress = () => {
setIsCustom(true);
setIsCustomModalVisible(false);
bottomModalRef.current.dismiss();
let amount = customAmount;
switch (customUnit) {
case BitcoinUnit.BTC:
@ -357,34 +341,35 @@ const ReceiveDetails = () => {
const renderCustomAmountModal = () => {
return (
<BottomModal isVisible={isCustomModalVisible} onClose={dismissCustomAmountModal}>
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<AmountInput unit={customUnit} amount={customAmount || ''} onChangeText={setCustomAmount} onAmountUnitChange={setCustomUnit} />
<View style={[styles.customAmount, stylesHook.customAmount]}>
<TextInput
onChangeText={setCustomLabel}
placeholderTextColor="#81868e"
placeholder={loc.receive.details_label}
value={customLabel || ''}
numberOfLines={1}
style={[styles.customAmountText, stylesHook.customAmountText]}
testID="CustomAmountDescription"
/>
</View>
<BlueSpacing20 />
<View>
<Button
testID="CustomAmountSaveButton"
style={[styles.modalButton, stylesHook.modalButton]}
title={loc.receive.details_create}
onPress={createCustomAmountAddress}
/>
<BlueSpacing20 />
</View>
<BlueSpacing20 />
</View>
</KeyboardAvoidingView>
<BottomModal
ref={bottomModalRef}
onClose={dismissCustomAmountModal}
backgroundColor={colors.modal}
contentContainerStyle={styles.modalContent}
>
<AmountInput unit={customUnit} amount={customAmount || ''} onChangeText={setCustomAmount} onAmountUnitChange={setCustomUnit} />
<View style={[styles.customAmount, stylesHook.customAmount]}>
<TextInput
onChangeText={setCustomLabel}
placeholderTextColor="#81868e"
placeholder={loc.receive.details_label}
value={customLabel || ''}
numberOfLines={1}
style={[styles.customAmountText, stylesHook.customAmountText]}
testID="CustomAmountDescription"
/>
</View>
<BlueSpacing20 />
<View>
<Button
testID="CustomAmountSaveButton"
style={[styles.modalButton, stylesHook.modalButton]}
title={loc.receive.details_create}
onPress={createCustomAmountAddress}
/>
<BlueSpacing20 />
</View>
<BlueSpacing20 />
</BottomModal>
);
};
@ -480,10 +465,6 @@ const styles = StyleSheet.create({
padding: 22,
justifyContent: 'center',
alignItems: 'center',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
minHeight: 350,
height: 350,
},
customAmount: {
flexDirection: 'row',

View file

@ -36,7 +36,7 @@ import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electr
import AddressInput from '../../components/AddressInput';
import presentAlert from '../../components/Alert';
import AmountInput from '../../components/AmountInput';
import BottomModal from '../../components/BottomModal';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
import Button from '../../components/Button';
import CoinsSelected from '../../components/CoinsSelected';
import InputAccessoryAllFunds from '../../components/InputAccessoryAllFunds';
@ -91,10 +91,10 @@ const SendDetails = () => {
const [width, setWidth] = useState(Dimensions.get('window').width);
const [isLoading, setIsLoading] = useState(false);
const [wallet, setWallet] = useState<TWallet | null>(null);
const feeModalRef = useRef<BottomModalHandle>(null);
const optionsModalRef = useRef<BottomModalHandle>(null);
const [walletSelectionOrCoinsSelectedHidden, setWalletSelectionOrCoinsSelectedHidden] = useState(false);
const [isAmountToolbarVisibleForAndroid, setIsAmountToolbarVisibleForAndroid] = useState(false);
const [isFeeSelectionModalVisible, setIsFeeSelectionModalVisible] = useState(false);
const [optionsVisible, setOptionsVisible] = useState(false);
const [isTransactionReplaceable, setIsTransactionReplaceable] = useState<boolean>(false);
const [addresses, setAddresses] = useState<IPaymentDestinations[]>([]);
const [units, setUnits] = useState<BitcoinUnit[]>([]);
@ -609,11 +609,16 @@ const SendDetails = () => {
if (tx && routeParams.launchedBy && psbt) {
console.warn('navigating back to ', routeParams.launchedBy);
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
// @ts-ignore idk how to fix FIXME?
navigation.navigate(routeParams.launchedBy, { psbt });
}
if (wallet?.type === WatchOnlyWallet.type) {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
// user whether he wants to broadcast it
@ -628,6 +633,8 @@ const SendDetails = () => {
}
if (wallet?.type === MultisigHDWallet.type) {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('PsbtMultisig', {
memo: transactionMemo,
psbtBase64: psbt.toBase64(),
@ -652,6 +659,8 @@ const SendDetails = () => {
// (ez can be the case for single-address wallet when doing self-payment for consolidation)
recipients = outputs;
}
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('Confirm', {
fee: new BigNumber(fee).dividedBy(100000000).toNumber(),
@ -685,8 +694,9 @@ const SendDetails = () => {
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
}
setOptionsVisible(false);
requestCameraAuthorization().then(() => {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
@ -707,18 +717,20 @@ const SendDetails = () => {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
// psbt base64?
// we construct PSBT object and pass to next screen
// so user can do smth with it:
const psbt = bitcoin.Psbt.fromBase64(ret.data);
navigation.navigate('PsbtWithHardwareWallet', {
memo: transactionMemo,
fromWallet: wallet,
psbt,
});
setIsLoading(false);
setOptionsVisible(false);
}
};
@ -731,6 +743,8 @@ const SendDetails = () => {
* @returns {Promise<void>}
*/
const importTransaction = async () => {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
if (wallet?.type !== WatchOnlyWallet.type) {
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
}
@ -751,7 +765,7 @@ const SendDetails = () => {
const txhex = psbt.extractTransaction().toHex();
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, txhex });
setIsLoading(false);
setOptionsVisible(false);
return;
}
@ -762,7 +776,7 @@ const SendDetails = () => {
const psbt = bitcoin.Psbt.fromBase64(file);
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, psbt });
setIsLoading(false);
setOptionsVisible(false);
return;
}
@ -771,7 +785,7 @@ const SendDetails = () => {
const file = (await RNFS.readFile(res.uri, 'ascii')).replace('\n', '').replace('\r', '');
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, txhex: file });
setIsLoading(false);
setOptionsVisible(false);
return;
}
@ -805,6 +819,8 @@ const SendDetails = () => {
};
const _importTransactionMultisig = async (base64arg: string | false) => {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
try {
const base64 = base64arg || (await fs.openSignedTransaction());
if (!base64) return;
@ -830,7 +846,6 @@ const SendDetails = () => {
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
}
setIsLoading(false);
setOptionsVisible(false);
};
const importTransactionMultisig = () => {
@ -852,7 +867,8 @@ const SendDetails = () => {
};
const importTransactionMultisigScanQr = () => {
setOptionsVisible(false);
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
requestCameraAuthorization().then(() => {
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
@ -867,7 +883,7 @@ const SendDetails = () => {
const handleAddRecipient = async () => {
console.log('handleAddRecipient');
setAddresses(addrs => [...addrs, { address: '', key: String(Math.random()) } as IPaymentDestinations]);
setOptionsVisible(false);
await sleep(200); // wait for animation
scrollView.current?.scrollToEnd();
if (addresses.length === 0) return;
@ -881,7 +897,7 @@ const SendDetails = () => {
addrs.splice(scrollIndex.current, 1);
return [...addrs];
});
setOptionsVisible(false);
if (addresses.length === 0) return;
await sleep(200); // wait for animation
scrollView.current?.flashScrollIndicators();
@ -890,7 +906,8 @@ const SendDetails = () => {
const handleCoinControl = () => {
if (!wallet) return;
setOptionsVisible(false);
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('CoinControl', {
walletID: wallet?.getID(),
onUTXOChoose: (u: CreateTransactionUtxo[]) => setUtxo(u),
@ -899,13 +916,16 @@ const SendDetails = () => {
const handleInsertContact = () => {
if (!wallet) return;
setOptionsVisible(false);
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
};
const handlePsbtSign = async () => {
setIsLoading(true);
setOptionsVisible(false);
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
const scannedData = await scanQrHelper(name);
if (!scannedData) return setIsLoading(false);
@ -947,7 +967,6 @@ const SendDetails = () => {
const hideOptions = () => {
Keyboard.dismiss();
setOptionsVisible(false);
};
// Header Right Button
@ -1071,7 +1090,8 @@ const SendDetails = () => {
style={styles.advancedOptions}
onPress={() => {
Keyboard.dismiss();
setOptionsVisible(true);
feeModalRef.current?.dismiss();
optionsModalRef.current?.present();
}}
testID="advancedOptionsMenuButton"
>
@ -1127,7 +1147,6 @@ const SendDetails = () => {
return [...u];
});
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setOptionsVisible(false);
},
style: 'default',
},
@ -1237,69 +1256,67 @@ const SendDetails = () => {
];
return (
<BottomModal isVisible={isFeeSelectionModalVisible} onClose={() => setIsFeeSelectionModalVisible(false)}>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
{options.map(({ label, time, fee, rate, active, disabled }, index) => (
<TouchableOpacity
accessibilityRole="button"
key={label}
disabled={disabled}
onPress={() => {
setFeePrecalc(fp => ({ ...fp, current: fee }));
setIsFeeSelectionModalVisible(false);
setCustomFee(rate.toString());
}}
style={[styles.feeModalItem, active && styles.feeModalItemActive, active && !disabled && stylesHook.feeModalItemActive]}
>
<View style={styles.feeModalRow}>
<Text style={[styles.feeModalLabel, disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalLabel]}>
{label}
</Text>
<View style={[styles.feeModalTime, disabled ? stylesHook.feeModalItemDisabled : stylesHook.feeModalTime]}>
<Text style={stylesHook.feeModalTimeText}>~{time}</Text>
</View>
</View>
<View style={styles.feeModalRow}>
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>{fee && formatFee(fee)}</Text>
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>
{rate} {loc.units.sat_vbyte}
</Text>
</View>
</TouchableOpacity>
))}
<BottomModal ref={feeModalRef}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
{options.map(({ label, time, fee, rate, active, disabled }, index) => (
<TouchableOpacity
testID="feeCustom"
accessibilityRole="button"
style={styles.feeModalCustom}
onPress={async () => {
let error = loc.send.fee_satvbyte;
while (true) {
let fee: number | string;
key={label}
disabled={disabled}
onPress={() => {
setFeePrecalc(fp => ({ ...fp, current: fee }));
feeModalRef.current?.dismiss();
setCustomFee(rate.toString());
}}
style={[styles.feeModalItem, active && styles.feeModalItemActive, active && !disabled && stylesHook.feeModalItemActive]}
>
<View style={styles.feeModalRow}>
<Text style={[styles.feeModalLabel, disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalLabel]}>
{label}
</Text>
<View style={[styles.feeModalTime, disabled ? stylesHook.feeModalItemDisabled : stylesHook.feeModalTime]}>
<Text style={stylesHook.feeModalTimeText}>~{time}</Text>
</View>
</View>
<View style={styles.feeModalRow}>
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>{fee && formatFee(fee)}</Text>
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>
{rate} {loc.units.sat_vbyte}
</Text>
</View>
</TouchableOpacity>
))}
<TouchableOpacity
testID="feeCustom"
accessibilityRole="button"
style={styles.feeModalCustom}
onPress={async () => {
let error = loc.send.fee_satvbyte;
while (true) {
let fee: number | string;
try {
fee = await prompt(loc.send.create_fee, error, true, 'numeric');
} catch (_) {
return;
}
if (!/^\d+$/.test(fee)) {
error = loc.send.details_fee_field_is_not_valid;
continue;
}
if (Number(fee) < 1) fee = '1';
fee = Number(fee).toString(); // this will remove leading zeros if any
setCustomFee(fee);
setIsFeeSelectionModalVisible(false);
try {
fee = await prompt(loc.send.create_fee, error, true, 'numeric');
} catch (_) {
return;
}
}}
>
<Text style={[styles.feeModalCustomText, stylesHook.feeModalCustomText]}>{loc.send.fee_custom}</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
if (!/^\d+$/.test(fee)) {
error = loc.send.details_fee_field_is_not_valid;
continue;
}
if (Number(fee) < 1) fee = '1';
fee = Number(fee).toString(); // this will remove leading zeros if any
setCustomFee(fee);
feeModalRef.current?.dismiss();
return;
}
}}
>
<Text style={[styles.feeModalCustomText, stylesHook.feeModalCustomText]}>{loc.send.fee_custom}</Text>
</TouchableOpacity>
</View>
</BottomModal>
);
};
@ -1308,56 +1325,57 @@ const SendDetails = () => {
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
return (
<BottomModal isVisible={optionsVisible} onClose={hideOptions}>
<KeyboardAvoidingView enabled={!isTablet} behavior={undefined}>
<View style={[styles.optionsContent, stylesHook.optionsContent]}>
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
<ListItem testID="InsertContactButton" title={loc.send.details_insert_contact} onPress={handleInsertContact} />
)}
{isEditable && (
<ListItem
testID="sendMaxButton"
disabled={balance === 0 || isSendMaxUsed}
title={loc.send.details_adv_full}
onPress={onUseAllPressed}
/>
)}
{wallet?.type === HDSegwitBech32Wallet.type && isEditable && (
<ListItem
title={loc.send.details_adv_fee_bump}
Component={TouchableWithoutFeedback}
switch={{ value: isTransactionReplaceable, onValueChange: onReplaceableFeeSwitchValueChanged }}
/>
)}
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
<ListItem title={loc.send.details_adv_import} onPress={importTransaction} />
)}
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
<ListItem testID="ImportQrTransactionButton" title={loc.send.details_adv_import_qr} onPress={importQrTransaction} />
)}
{wallet?.type === MultisigHDWallet.type && isEditable && (
<ListItem title={loc.send.details_adv_import} onPress={importTransactionMultisig} />
)}
{wallet?.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0 && isEditable && (
<ListItem title={loc.multisig.co_sign_transaction} onPress={importTransactionMultisigScanQr} />
)}
{isEditable && (
<>
<ListItem testID="AddRecipient" title={loc.send.details_add_rec_add} onPress={handleAddRecipient} />
<ListItem
testID="RemoveRecipient"
title={loc.send.details_add_rec_rem}
disabled={addresses.length < 2}
onPress={handleRemoveRecipient}
/>
</>
)}
<ListItem testID="CoinControl" title={loc.cc.header} onPress={handleCoinControl} />
{(wallet as MultisigHDWallet)?.allowCosignPsbt() && isEditable && (
<ListItem testID="PsbtSign" title={loc.send.psbt_sign} onPress={handlePsbtSign} />
)}
</View>
</KeyboardAvoidingView>
<BottomModal
ref={optionsModalRef}
onClose={hideOptions}
backgroundColor={colors.modal}
contentContainerStyle={[styles.optionsContent, stylesHook.optionsContent]}
>
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
<ListItem testID="InsertContactButton" title={loc.send.details_insert_contact} onPress={handleInsertContact} />
)}
{isEditable && (
<ListItem
testID="sendMaxButton"
disabled={balance === 0 || isSendMaxUsed}
title={loc.send.details_adv_full}
onPress={onUseAllPressed}
/>
)}
{wallet?.type === HDSegwitBech32Wallet.type && isEditable && (
<ListItem
title={loc.send.details_adv_fee_bump}
Component={TouchableWithoutFeedback}
switch={{ value: isTransactionReplaceable, onValueChange: onReplaceableFeeSwitchValueChanged }}
/>
)}
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
<ListItem title={loc.send.details_adv_import} onPress={importTransaction} />
)}
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
<ListItem testID="ImportQrTransactionButton" title={loc.send.details_adv_import_qr} onPress={importQrTransaction} />
)}
{wallet?.type === MultisigHDWallet.type && isEditable && (
<ListItem title={loc.send.details_adv_import} onPress={importTransactionMultisig} />
)}
{wallet?.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0 && isEditable && (
<ListItem title={loc.multisig.co_sign_transaction} onPress={importTransactionMultisigScanQr} />
)}
{isEditable && (
<>
<ListItem testID="AddRecipient" title={loc.send.details_add_rec_add} onPress={handleAddRecipient} />
<ListItem
testID="RemoveRecipient"
title={loc.send.details_add_rec_rem}
disabled={addresses.length < 2}
onPress={handleRemoveRecipient}
/>
</>
)}
<ListItem testID="CoinControl" title={loc.cc.header} onPress={handleCoinControl} />
{(wallet as MultisigHDWallet)?.allowCosignPsbt() && isEditable && (
<ListItem testID="PsbtSign" title={loc.send.psbt_sign} onPress={handlePsbtSign} />
)}
</BottomModal>
);
};
@ -1400,7 +1418,11 @@ const SendDetails = () => {
<TouchableOpacity
accessibilityRole="button"
style={styles.selectTouch}
onPress={() => navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN })}
onPress={() => {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN });
}}
>
<Text style={styles.selectText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
<Icon name={I18nManager.isRTL ? 'angle-left' : 'angle-right'} size={18} type="font-awesome" color="#9aa0aa" />
@ -1410,7 +1432,11 @@ const SendDetails = () => {
<TouchableOpacity
accessibilityRole="button"
style={styles.selectTouch}
onPress={() => navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN })}
onPress={() => {
feeModalRef.current?.dismiss();
optionsModalRef.current?.dismiss();
navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN });
}}
disabled={!isEditable || isLoading}
>
<Text style={[styles.selectLabel, stylesHook.selectLabel]}>{wallet?.getLabel()}</Text>
@ -1558,7 +1584,7 @@ const SendDetails = () => {
<TouchableOpacity
testID="chooseFee"
accessibilityRole="button"
onPress={() => setIsFeeSelectionModalVisible(true)}
onPress={() => feeModalRef.current?.present()}
disabled={isLoading}
style={styles.fee}
>
@ -1641,15 +1667,9 @@ const styles = StyleSheet.create({
},
modalContent: {
padding: 22,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
minHeight: 200,
},
optionsContent: {
padding: 22,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
minHeight: 130,
},
feeModalItem: {
paddingHorizontal: 16,

View file

@ -256,6 +256,7 @@ const CoinControl = () => {
const { colors } = useTheme();
const navigation = useNavigation();
const { width } = useWindowDimensions();
const bottomModalRef = useRef(null);
const { walletID, onUTXOChoose } = useRoute().params;
const { wallets, saveToDisk, sleep } = useStorage();
const wallet = wallets.find(w => w.getID() === walletID);
@ -386,6 +387,14 @@ const CoinControl = () => {
return <OutputModalContent output={output} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />;
};
useEffect(() => {
if (output) {
bottomModalRef.current?.present();
} else {
bottomModalRef.current?.dismiss();
}
}, [output]);
if (loading) {
return (
<SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}>
@ -403,7 +412,7 @@ const CoinControl = () => {
)}
<BottomModal
isVisible={Boolean(output)}
ref={bottomModalRef}
onClose={() => {
Keyboard.dismiss();
setOutput(false);

View file

@ -31,7 +31,7 @@ import {
} from '../../BlueComponents';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import presentAlert from '../../components/Alert';
import BottomModal from '../../components/BottomModal';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
import Button from '../../components/Button';
import MultipleStepsListItem, {
MultipleStepsListItemButtohType,
@ -68,9 +68,9 @@ const ViewEditMultisigCosigners: React.FC = () => {
const [isLoading, setIsLoading] = useState(true);
const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(true);
const [currentlyEditingCosignerNum, setCurrentlyEditingCosignerNum] = useState<number | false>(false);
const [isProvideMnemonicsModalVisible, setIsProvideMnemonicsModalVisible] = useState(false);
const [isMnemonicsModalVisible, setIsMnemonicsModalVisible] = useState(false);
const [isShareModalVisible, setIsShareModalVisible] = useState(false);
const shareModalRef = useRef<BottomModalHandle>(null);
const provideMnemonicsModalRef = useRef<BottomModalHandle>(null);
const mnemonicsModalRef = useRef<BottomModalHandle>(null);
const [importText, setImportText] = useState('');
const [exportString, setExportString] = useState('{}'); // used in exportCosigner()
const [exportStringURv2, setExportStringURv2] = useState(''); // used in QR
@ -164,7 +164,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
);
const saveFileButtonAfterOnPress = () => {
setIsShareModalVisible(false);
shareModalRef.current?.dismiss();
};
const onSave = async () => {
@ -226,12 +226,12 @@ const ViewEditMultisigCosigners: React.FC = () => {
const hideMnemonicsModal = () => {
Keyboard.dismiss();
setIsMnemonicsModalVisible(false);
mnemonicsModalRef.current?.dismiss();
};
const renderMnemonicsModal = () => {
return (
<BottomModal isVisible={isMnemonicsModalVisible} onClose={hideMnemonicsModal} coverScreen={false}>
<BottomModal ref={mnemonicsModalRef} onClose={hideMnemonicsModal}>
<View style={[styles.newKeyModalContent, stylesHook.modalContent]}>
<View style={styles.itemKeyUnprovidedWrapper}>
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
@ -274,14 +274,14 @@ const ViewEditMultisigCosigners: React.FC = () => {
<Button
title={loc.multisig.share}
onPress={() => {
setIsMnemonicsModalVisible(false);
mnemonicsModalRef.current?.dismiss();
setTimeout(() => {
setIsShareModalVisible(true);
shareModalRef.current?.present();
}, 1000);
}}
/>
<BlueSpacing20 />
<Button title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />
<Button title={loc.send.success_done} onPress={() => mnemonicsModalRef.current?.dismiss()} />
</View>
</BottomModal>
);
@ -346,7 +346,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
setExportFilename('bw-cosigner-' + fp + '.json');
setIsMnemonicsModalVisible(true);
mnemonicsModalRef.current?.present();
},
}}
dashes={MultipleStepsListItemDashType.topAndBottom}
@ -360,7 +360,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
disabled: vaultKeyData.isLoading,
onPress: () => {
setCurrentlyEditingCosignerNum(el.index + 1);
setIsProvideMnemonicsModalVisible(true);
provideMnemonicsModalRef.current?.present();
},
}}
dashes={el.index === length - 1 ? MultipleStepsListItemDashType.top : MultipleStepsListItemDashType.topAndBottom}
@ -389,7 +389,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
passphrase: passphrase ?? '',
isLoading: false,
});
setIsMnemonicsModalVisible(true);
mnemonicsModalRef.current?.present();
const fp = wallet.getFingerprint(keyIndex);
const path = wallet.getCustomDerivationPathForCosigner(keyIndex);
if (!path) {
@ -485,7 +485,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setWallet(wallet);
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current?.dismiss();
setIsSaveButtonDisabled(false);
setImportText('');
setAskPassphrase(false);
@ -509,75 +509,71 @@ const ViewEditMultisigCosigners: React.FC = () => {
};
const scanOrOpenFile = async () => {
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current?.dismiss();
const scanned = await scanQrHelper(route.name, true);
setImportText(String(scanned));
setIsProvideMnemonicsModalVisible(true);
provideMnemonicsModalRef.current?.present();
};
const hideProvideMnemonicsModal = () => {
Keyboard.dismiss();
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current?.dismiss();
setImportText('');
setAskPassphrase(false);
};
const hideShareModal = () => {
setIsShareModalVisible(false);
shareModalRef.current?.dismiss();
};
const renderProvideMnemonicsModal = () => {
return (
<BottomModal avoidKeyboard isVisible={isProvideMnemonicsModalVisible} onClose={hideProvideMnemonicsModal} coverScreen={false}>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : 'padding'} keyboardVerticalOffset={120}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 />
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
{isAdvancedModeEnabled && (
<>
<BlueSpacing10 />
<View style={styles.row}>
<BlueText>{loc.wallets.import_passphrase}</BlueText>
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
</View>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<Button disabled={importText.trim().length === 0} title={loc.wallets.import_do_import} onPress={handleUseMnemonicPhrase} />
)}
<BlueButtonLink ref={openScannerButtonRef} disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</KeyboardAvoidingView>
<BottomModal onClose={hideProvideMnemonicsModal} ref={provideMnemonicsModalRef}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 />
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
{isAdvancedModeEnabled && (
<>
<BlueSpacing10 />
<View style={styles.row}>
<BlueText>{loc.wallets.import_passphrase}</BlueText>
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
</View>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<Button disabled={importText.trim().length === 0} title={loc.wallets.import_do_import} onPress={handleUseMnemonicPhrase} />
)}
<BlueButtonLink ref={openScannerButtonRef} disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</BottomModal>
);
};
const renderShareModal = () => {
return (
<BottomModal isVisible={isShareModalVisible} onClose={hideShareModal} doneButton coverScreen={false}>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
<Text style={[styles.headerText, stylesHook.textDestination]}>
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
</Text>
<QRCodeComponent value={exportStringURv2} size={260} isLogoRendered={false} />
<BlueSpacing20 />
<View style={styles.squareButtonWrapper}>
<SaveFileButton
style={[styles.exportButton, stylesHook.exportButton]}
fileContent={exportString}
fileName={exportFilename}
afterOnPress={saveFileButtonAfterOnPress}
>
<SquareButton title={loc.multisig.share} />
</SaveFileButton>
</View>
<BottomModal ref={shareModalRef} onClose={hideShareModal}>
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
<Text style={[styles.headerText, stylesHook.textDestination]}>
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
</Text>
<QRCodeComponent value={exportStringURv2} size={260} isLogoRendered={false} />
<BlueSpacing20 />
<View style={styles.squareButtonWrapper}>
<SaveFileButton
style={[styles.exportButton, stylesHook.exportButton]}
fileContent={exportString}
fileName={exportFilename}
afterOnPress={saveFileButtonAfterOnPress}
>
<SquareButton title={loc.multisig.share} />
</SaveFileButton>
</View>
</KeyboardAvoidingView>
</View>
</BottomModal>
);
};

View file

@ -1,7 +1,7 @@
import { useNavigation, useRoute } from '@react-navigation/native';
import LottieView from 'lottie-react-native';
import React, { useEffect, useRef, useState } from 'react';
import { Keyboard, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Icon } from '@rneui/themed';
import { BlueSpacing20 } from '../../BlueComponents';
@ -18,10 +18,10 @@ const WalletsAddMultisig = () => {
const { colors } = useTheme();
const { navigate } = useNavigation();
const loadingAnimation = useRef();
const bottomModalRef = useRef();
const { walletLabel } = useRoute().params;
const [m, setM] = useState(2);
const [n, setN] = useState(3);
const [isModalVisible, setIsModalVisible] = useState(false);
const [format, setFormat] = useState(MultisigHDWallet.FORMAT_P2WSH);
const { isAdvancedModeEnabled } = useSettings();
@ -41,9 +41,11 @@ const WalletsAddMultisig = () => {
color: colors.alternativeTextColor,
},
selectedItem: {
paddingHorizontal: 8,
backgroundColor: colors.elevated,
},
deSelectedItem: {
paddingHorizontal: 8,
backgroundColor: 'transparent',
},
textHeader: {
@ -99,88 +101,73 @@ const WalletsAddMultisig = () => {
setN(n - 1);
};
const closeModal = () => {
Keyboard.dismiss();
setIsModalVisible(false);
};
const renderModal = () => {
return (
<BottomModal isVisible={isModalVisible} onClose={closeModal} doneButton propagateSwipe>
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
<ScrollView>
<Text style={[styles.textHeader, stylesHook.textHeader]}>{loc.multisig.quorum_header}</Text>
<Text style={[styles.textSubtitle, stylesHook.textSubtitle]}>{loc.multisig.required_keys_out_of_total}</Text>
<View style={styles.rowCenter}>
<View style={styles.column}>
<TouchableOpacity accessibilityRole="button" onPress={increaseM} disabled={n === m || m === 7} style={styles.chevron}>
<Icon
name="chevron-up"
size={22}
type="font-awesome-5"
color={n === m || m === 7 ? colors.buttonDisabledTextColor : '#007AFF'}
/>
</TouchableOpacity>
<Text style={[styles.textM, stylesHook.textHeader]}>{m}</Text>
<TouchableOpacity accessibilityRole="button" onPress={decreaseM} disabled={m === 2} style={styles.chevron}>
<Icon name="chevron-down" size={22} type="font-awesome-5" color={m === 2 ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
</View>
<BottomModal ref={bottomModalRef} contentContainerStyle={styles.modalContentShort} backgroundColor={stylesHook.modalContentShort}>
<Text style={[styles.textHeader, stylesHook.textHeader]}>{loc.multisig.quorum_header}</Text>
<Text style={[styles.textSubtitle, stylesHook.textSubtitle]}>{loc.multisig.required_keys_out_of_total}</Text>
<View style={styles.rowCenter}>
<View style={styles.column}>
<TouchableOpacity accessibilityRole="button" onPress={increaseM} disabled={n === m || m === 7} style={styles.chevron}>
<Icon
name="chevron-up"
size={22}
type="font-awesome-5"
color={n === m || m === 7 ? colors.buttonDisabledTextColor : '#007AFF'}
/>
</TouchableOpacity>
<Text style={[styles.textM, stylesHook.textHeader]}>{m}</Text>
<TouchableOpacity accessibilityRole="button" onPress={decreaseM} disabled={m === 2} style={styles.chevron}>
<Icon name="chevron-down" size={22} type="font-awesome-5" color={m === 2 ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
</View>
<View style={styles.columnOf}>
<Text style={styles.textOf}>{loc.multisig.of}</Text>
</View>
<View style={styles.columnOf}>
<Text style={styles.textOf}>{loc.multisig.of}</Text>
</View>
<View style={styles.column}>
<TouchableOpacity accessibilityRole="button" disabled={n === 7} onPress={increaseN} style={styles.chevron}>
<Icon name="chevron-up" size={22} type="font-awesome-5" color={n === 7 ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
<Text style={[styles.textM, stylesHook.textHeader]}>{n}</Text>
<TouchableOpacity
accessibilityRole="button"
onPress={decreaseN}
disabled={n === m}
style={styles.chevron}
testID="DecreaseN"
>
<Icon name="chevron-down" size={22} type="font-awesome-5" color={n === m ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
</View>
</View>
<BlueSpacing20 />
<Text style={[styles.textHeader, stylesHook.textHeader]}>{loc.multisig.wallet_type}</Text>
<BlueSpacing20 />
<ListItem
bottomDivider={false}
onPress={setFormatP2wsh}
title={`${loc.multisig.native_segwit_title} (${MultisigHDWallet.FORMAT_P2WSH})`}
checkmark={isP2wsh()}
containerStyle={[styles.borderRadius6, styles.item, isP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
<ListItem
bottomDivider={false}
onPress={setFormatP2shP2wsh}
title={`${loc.multisig.wrapped_segwit_title} (${MultisigHDWallet.FORMAT_P2SH_P2WSH})`}
checkmark={isP2shP2wsh()}
containerStyle={[styles.borderRadius6, styles.item, isP2shP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
<ListItem
bottomDivider={false}
onPress={setFormatP2sh}
title={`${loc.multisig.legacy_title} (${MultisigHDWallet.FORMAT_P2SH})`}
checkmark={isP2sh()}
containerStyle={[styles.borderRadius6, styles.item, isP2sh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
</ScrollView>
<View style={styles.column}>
<TouchableOpacity accessibilityRole="button" disabled={n === 7} onPress={increaseN} style={styles.chevron}>
<Icon name="chevron-up" size={22} type="font-awesome-5" color={n === 7 ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
<Text style={[styles.textM, stylesHook.textHeader]}>{n}</Text>
<TouchableOpacity accessibilityRole="button" onPress={decreaseN} disabled={n === m} style={styles.chevron} testID="DecreaseN">
<Icon name="chevron-down" size={22} type="font-awesome-5" color={n === m ? colors.buttonDisabledTextColor : '#007AFF'} />
</TouchableOpacity>
</View>
</View>
<BlueSpacing20 />
<Text style={[styles.textHeader, stylesHook.textHeader]}>{loc.multisig.wallet_type}</Text>
<BlueSpacing20 />
<ListItem
bottomDivider={false}
onPress={setFormatP2wsh}
title={`${loc.multisig.native_segwit_title} (${MultisigHDWallet.FORMAT_P2WSH})`}
checkmark={isP2wsh()}
containerStyle={[styles.borderRadius6, styles.item, isP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
<ListItem
bottomDivider={false}
onPress={setFormatP2shP2wsh}
title={`${loc.multisig.wrapped_segwit_title} (${MultisigHDWallet.FORMAT_P2SH_P2WSH})`}
checkmark={isP2shP2wsh()}
containerStyle={[styles.borderRadius6, styles.item, isP2shP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
<ListItem
bottomDivider={false}
onPress={setFormatP2sh}
title={`${loc.multisig.legacy_title} (${MultisigHDWallet.FORMAT_P2SH})`}
checkmark={isP2sh()}
containerStyle={[styles.borderRadius6, styles.item, isP2sh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/>
</BottomModal>
);
};
const showAdvancedOptionsModal = () => {
setIsModalVisible(true);
bottomModalRef.current.present();
};
const getCurrentlySelectedFormat = code => {
@ -255,13 +242,7 @@ const styles = StyleSheet.create({
flex: 0.8,
},
modalContentShort: {
paddingHorizontal: 24,
paddingTop: 24,
justifyContent: 'center',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
borderColor: 'rgba(0, 0, 0, 0.1)',
minHeight: 350,
padding: 24,
},
borderRadius6: {
borderRadius: 6,

View file

@ -6,7 +6,6 @@ import {
I18nManager,
InteractionManager,
Keyboard,
KeyboardAvoidingView,
LayoutAnimation,
Platform,
StyleSheet,
@ -54,9 +53,9 @@ const WalletsAddMultisigStep2 = () => {
const [cosigners, setCosigners] = useState([]); // array of cosigners user provided. if format [cosigner, fp, path]
const [isLoading, setIsLoading] = useState(false);
const [isMnemonicsModalVisible, setIsMnemonicsModalVisible] = useState(false);
const [isProvideMnemonicsModalVisible, setIsProvideMnemonicsModalVisible] = useState(false);
const [isRenderCosignersXpubModalVisible, setIsRenderCosignersXpubModalVisible] = useState(false);
const mnemonicsModalRef = useRef(null);
const provideMnemonicsModalRef = useRef(null);
const renderCosignersXpubModalRef = useRef(null);
const [cosignerXpub, setCosignerXpub] = useState(''); // string used in exportCosigner()
const [cosignerXpubURv2, setCosignerXpubURv2] = useState(''); // string displayed in renderCosignersXpubModal()
const [cosignerXpubFilename, setCosignerXpubFilename] = useState('bw-cosigner.bwcosigner');
@ -82,7 +81,7 @@ const WalletsAddMultisigStep2 = () => {
(async function () {
if (await confirm(loc.multisig.shared_key_detected, loc.multisig.shared_key_detected_question)) {
setImportText(currentSharedCosigner);
setIsProvideMnemonicsModalVisible(true);
provideMnemonicsModalRef.current.present();
setSharedCosigner('');
}
})();
@ -184,8 +183,7 @@ const WalletsAddMultisigStep2 = () => {
setCosigners(cosignersCopy);
setVaultKeyData({ keyIndex: cosignersCopy.length, seed: w.getSecret(), xpub: w.getXpub(), isLoading: false });
setIsLoading(true);
setIsMnemonicsModalVisible(true);
mnemonicsModalRef.current.present();
setTimeout(() => {
// filling cache
setXpubCacheForMnemonics(w.getSecret());
@ -219,7 +217,7 @@ const WalletsAddMultisigStep2 = () => {
setCosignerXpub(MultisigCosigner.exportToJson(cosigner[1], cosigner[0], cosigner[2]));
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(cosigner[1], cosigner[0], cosigner[2]))[0]);
setCosignerXpubFilename('bw-cosigner-' + cosigner[1] + '.bwcosigner');
setIsRenderCosignersXpubModalVisible(true);
renderCosignersXpubModalRef.current.present();
} else {
const path = getPath();
@ -228,7 +226,7 @@ const WalletsAddMultisigStep2 = () => {
setCosignerXpub(MultisigCosigner.exportToJson(fp, xpub, path));
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
setCosignerXpubFilename('bw-cosigner-' + fp + '.bwcosigner');
setIsRenderCosignersXpubModalVisible(true);
renderCosignersXpubModalRef.current.present();
}
};
@ -255,12 +253,12 @@ const WalletsAddMultisigStep2 = () => {
};
const iHaveMnemonics = () => {
setIsProvideMnemonicsModalVisible(true);
provideMnemonicsModalRef.current.present();
};
const tryUsingXpub = async (xpub, fp, path) => {
if (!MultisigHDWallet.isXpubForMultisig(xpub)) {
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current.dismiss();
setIsLoading(false);
setImportText('');
setAskPassphrase(false);
@ -294,7 +292,7 @@ const WalletsAddMultisigStep2 = () => {
}
}
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current.dismiss();
setIsLoading(false);
setImportText('');
setAskPassphrase(false);
@ -350,7 +348,7 @@ const WalletsAddMultisigStep2 = () => {
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setCosigners(cosignersCopy);
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current.dismiss();
setIsLoading(false);
setImportText('');
setAskPassphrase(false);
@ -379,7 +377,7 @@ const WalletsAddMultisigStep2 = () => {
if (ret.data.toUpperCase().startsWith('UR')) {
presentAlert({ message: 'BC-UR not decoded. This should never happen' });
} else if (isValidMnemonicSeed(ret.data)) {
setIsProvideMnemonicsModalVisible(true);
provideMnemonicsModalRef.current.present();
setImportText(ret.data);
} else {
if (MultisigHDWallet.isXpubValid(ret.data) && !MultisigHDWallet.isXpubForMultisig(ret.data)) {
@ -390,7 +388,7 @@ const WalletsAddMultisigStep2 = () => {
}
let cosigner = new MultisigCosigner(ret.data);
if (!cosigner.isValid()) return presentAlert({ message: loc.multisig.invalid_cosigner });
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current.dismiss();
if (cosigner.howManyCosignersWeHave() > 1) {
// lets look for the correct cosigner. thats probably gona be the one with specific corresponding path,
// for example m/48'/0'/0'/2' if user chose to setup native segwit in BW
@ -459,7 +457,6 @@ const WalletsAddMultisigStep2 = () => {
};
const scanOrOpenFile = () => {
setIsProvideMnemonicsModalVisible(false);
InteractionManager.runAfterInteractions(async () => {
const scanned = await scanQrHelper(name, true);
onBarScanned({ data: scanned });
@ -562,7 +559,7 @@ const WalletsAddMultisigStep2 = () => {
const renderMnemonicsModal = () => {
return (
<BottomModal isVisible={isMnemonicsModalVisible} onClose={Keyboard.dismiss}>
<BottomModal onClose={Keyboard.dismiss} ref={mnemonicsModalRef} isGrabberVisible={false}>
<View style={[styles.newKeyModalContent, stylesHook.modalContent]}>
<View style={styles.itemKeyUnprovidedWrapper}>
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
@ -581,7 +578,7 @@ const WalletsAddMultisigStep2 = () => {
<BlueSpacing10 />
<View style={styles.secretContainer}>{renderSecret(vaultKeyData.seed.split(' '))}</View>
<BlueSpacing20 />
{isLoading ? <ActivityIndicator /> : <Button title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />}
{isLoading ? <ActivityIndicator /> : <Button title={loc.send.success_done} onPress={() => mnemonicsModalRef.current.dismiss()} />}
</View>
</BottomModal>
);
@ -589,48 +586,47 @@ const WalletsAddMultisigStep2 = () => {
const hideProvideMnemonicsModal = () => {
Keyboard.dismiss();
setIsProvideMnemonicsModalVisible(false);
provideMnemonicsModalRef.current.dismiss();
setImportText('');
setAskPassphrase(false);
};
const renderProvideMnemonicsModal = () => {
return (
<BottomModal isVisible={isProvideMnemonicsModalVisible} onClose={hideProvideMnemonicsModal}>
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 />
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
{isAdvancedModeEnabled && (
<>
<BlueSpacing10 />
<View style={styles.row}>
<BlueText>{loc.wallets.import_passphrase}</BlueText>
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
</View>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<Button
testID="DoImportKeyButton"
disabled={importText.trim().length === 0}
title={loc.wallets.import_do_import}
onPress={useMnemonicPhrase}
/>
)}
<BlueButtonLink
testID="ScanOrOpenFile"
ref={openScannerButton}
disabled={isLoading}
onPress={scanOrOpenFile}
title={loc.wallets.import_scan_qr}
<BottomModal onClose={hideProvideMnemonicsModal} ref={provideMnemonicsModalRef} isGrabberVisible={false}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 />
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
{isAdvancedModeEnabled && (
<>
<BlueSpacing10 />
<View style={styles.row}>
<BlueText>{loc.wallets.import_passphrase}</BlueText>
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
</View>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<Button
testID="DoImportKeyButton"
disabled={importText.trim().length === 0}
title={loc.wallets.import_do_import}
onPress={useMnemonicPhrase}
/>
</View>
</KeyboardAvoidingView>
)}
<BlueButtonLink
testID="ScanOrOpenFile"
ref={openScannerButton}
disabled={isLoading}
onPress={scanOrOpenFile}
title={loc.wallets.import_scan_qr}
/>
</View>
</BottomModal>
);
};
@ -645,37 +641,35 @@ const WalletsAddMultisigStep2 = () => {
const hideCosignersXpubModal = () => {
Keyboard.dismiss();
setIsRenderCosignersXpubModalVisible(false);
renderCosignersXpubModalRef.current.dismiss();
};
const renderCosignersXpubModal = () => {
return (
<BottomModal isVisible={isRenderCosignersXpubModalVisible} onClose={hideCosignersXpubModal}>
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
<Text style={[styles.headerText, stylesHook.textDestination]}>
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
</Text>
<BlueSpacing20 />
<QRCodeComponent value={cosignerXpubURv2} size={260} />
<BlueSpacing20 />
<View style={styles.squareButtonWrapper}>
{isLoading ? (
<ActivityIndicator />
) : (
<SaveFileButton
style={[styles.exportButton, stylesHook.exportButton]}
fileName={cosignerXpubFilename}
fileContent={cosignerXpub}
beforeOnPress={exportCosignerBeforeOnPress}
afterOnPress={exportCosignerAfterOnPress}
>
<SquareButton title={loc.multisig.share} />
</SaveFileButton>
)}
</View>
<BottomModal onClose={hideCosignersXpubModal} ref={renderCosignersXpubModalRef} isGrabberVisible={false}>
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
<Text style={[styles.headerText, stylesHook.textDestination]}>
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
</Text>
<BlueSpacing20 />
<QRCodeComponent value={cosignerXpubURv2} size={260} />
<BlueSpacing20 />
<View style={styles.squareButtonWrapper}>
{isLoading ? (
<ActivityIndicator />
) : (
<SaveFileButton
style={[styles.exportButton, stylesHook.exportButton]}
fileName={cosignerXpubFilename}
fileContent={cosignerXpub}
beforeOnPress={exportCosignerBeforeOnPress}
afterOnPress={exportCosignerAfterOnPress}
>
<SquareButton title={loc.multisig.share} />
</SaveFileButton>
)}
</View>
</KeyboardAvoidingView>
</View>
</BottomModal>
);
};