BlueWallet/screen/wallets/details.js

566 lines
20 KiB
JavaScript
Raw Normal View History

/* global alert */
import React, { useEffect, useState, useCallback, useContext, useRef } from 'react';
2019-09-27 16:49:56 +02:00
import {
View,
Text,
TextInput,
Alert,
2020-01-02 23:06:51 +01:00
KeyboardAvoidingView,
2019-09-27 16:49:56 +02:00
TouchableOpacity,
Keyboard,
TouchableWithoutFeedback,
Switch,
Platform,
Linking,
StyleSheet,
StatusBar,
PermissionsAndroid,
2019-09-27 16:49:56 +02:00
} from 'react-native';
2020-12-25 17:09:53 +01:00
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueText, BlueLoading } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import { HDLegacyBreadwalletWallet } from '../../class/wallets/hd-legacy-breadwallet-wallet';
import { HDLegacyP2PKHWallet } from '../../class/wallets/hd-legacy-p2pkh-wallet';
import { HDSegwitP2SHWallet } from '../../class/wallets/hd-segwit-p2sh-wallet';
2018-12-13 17:50:18 +01:00
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import Biometric from '../../class/biometrics';
import {
HDSegwitBech32Wallet,
SegwitP2SHWallet,
LegacyWallet,
SegwitBech32Wallet,
WatchOnlyWallet,
MultisigHDWallet,
HDAezeedWallet,
} from '../../class';
2020-01-01 04:31:04 +01:00
import { ScrollView } from 'react-native-gesture-handler';
2020-07-20 15:38:46 +02:00
import loc from '../../loc';
2020-08-09 03:04:11 +02:00
import { useTheme, useRoute, useNavigation } from '@react-navigation/native';
import RNFS from 'react-native-fs';
import Share from 'react-native-share';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Notifications from '../../blue_modules/notifications';
import isCatalyst from 'react-native-is-catalyst';
const prompt = require('../../blue_modules/prompt');
2018-01-30 23:42:38 +01:00
const styles = StyleSheet.create({
root: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1,
},
save: {
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'center',
},
address: {
alignItems: 'center',
flex: 1,
},
textLabel1: {
fontWeight: '500',
fontSize: 14,
marginVertical: 12,
},
textLabel2: {
fontWeight: '500',
fontSize: 14,
marginVertical: 16,
},
textValue: {
fontWeight: '500',
fontSize: 14,
},
input: {
flexDirection: 'row',
borderWidth: 1,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
alignItems: 'center',
borderRadius: 4,
},
inputText: {
flex: 1,
marginHorizontal: 8,
minHeight: 33,
2020-06-09 14:23:21 +02:00
color: '#81868e',
},
hardware: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
delete: {
color: '#d0021b',
fontSize: 15,
fontWeight: '500',
2020-10-29 10:26:39 +01:00
textAlign: 'center',
},
});
2020-08-09 03:04:11 +02:00
const WalletDetails = () => {
const { saveToDisk, wallets, deleteWallet, setSelectedWallet } = useContext(BlueStorageContext);
const { walletID } = useRoute().params;
2020-08-09 03:04:11 +02:00
const [isLoading, setIsLoading] = useState(true);
const [backdoorPressed, setBackdoorPressed] = useState(0);
const wallet = useRef(wallets.find(w => w.getID() === walletID)).current;
2020-08-09 03:04:11 +02:00
const [walletName, setWalletName] = useState(wallet.getLabel());
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
const { goBack, navigate, setOptions, popToTop } = useNavigation();
2020-08-09 03:04:11 +02:00
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
textLabel1: {
color: colors.feeText,
},
textLabel2: {
color: colors.feeText,
},
saveText: {
color: colors.outputValue,
},
textValue: {
color: colors.outputValue,
},
input: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
2018-01-30 23:42:38 +01:00
2020-08-09 03:04:11 +02:00
backgroundColor: colors.inputBackgroundColor,
},
});
2018-01-30 23:42:38 +01:00
const setLabel = async () => {
2020-08-09 03:04:11 +02:00
if (walletName.trim().length > 0) {
wallet.setLabel(walletName);
if (wallet.type === WatchOnlyWallet.type && wallet.getSecret().startsWith('zpub')) {
wallet.setUseWithHardwareWalletEnabled(useWithHardwareWallet);
}
2020-08-09 03:04:11 +02:00
wallet.setHideTransactionsInWalletsList(!hideTransactionsInWalletsList);
}
await saveToDisk();
2020-08-09 03:04:11 +02:00
alert(loc.wallets.details_wallet_updated);
goBack();
};
2018-01-30 23:42:38 +01:00
2020-08-09 03:04:11 +02:00
useEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
setOptions({
headerRight: () => (
<TouchableOpacity disabled={isLoading} style={styles.save} onPress={setLabel}>
<Text style={stylesHook.saveText}>{loc.wallets.details_save}</Text>
</TouchableOpacity>
),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading, colors.outputValue, walletName, useWithHardwareWallet, hideTransactionsInWalletsList]);
2020-08-09 03:04:11 +02:00
useEffect(() => {
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
2020-08-09 03:04:11 +02:00
}, []);
useEffect(() => {
setSelectedWallet(walletID);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
2020-08-09 03:04:11 +02:00
const presentWalletHasBalanceAlert = useCallback(async () => {
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
try {
const walletBalanceConfirmation = await prompt(
loc.wallets.details_del_wb,
loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }),
true,
'plain-text',
);
if (Number(walletBalanceConfirmation) === wallet.getBalance()) {
setIsLoading(true);
Notifications.unsubscribe(wallet.getAllExternalAddresses(), [], []);
deleteWallet(wallet);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
await saveToDisk();
popToTop();
} else {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
setIsLoading(false);
alert(loc.wallets.details_del_wb_err);
}
} catch (_) {}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [popToTop, wallet]);
2020-08-09 03:04:11 +02:00
const navigateToWalletExport = () => {
navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID: wallet.getID(),
},
});
};
2020-10-05 23:25:14 +02:00
const navigateToMultisigCoordinationSetup = () => {
navigate('ExportMultisigCoordinationSetupRoot', {
screen: 'ExportMultisigCoordinationSetup',
params: {
walletId: wallet.getID(),
},
2020-10-05 23:25:14 +02:00
});
};
const navigateToViewEditCosigners = () => {
2020-12-12 01:27:43 +01:00
navigate('ViewEditMultisigCosignersRoot', {
screen: 'ViewEditMultisigCosigners',
params: {
walletId: wallet.getID(),
},
});
};
2020-08-09 03:04:11 +02:00
const navigateToXPub = () =>
navigate('WalletXpubRoot', {
screen: 'WalletXpub',
params: {
secret: wallet.getSecret(),
},
2020-08-09 03:04:11 +02:00
});
2020-08-09 03:04:11 +02:00
const renderMarketplaceButton = () => {
return Platform.select({
android: (
2020-07-15 19:32:59 +02:00
<SecondButton
onPress={() =>
2020-08-09 03:04:11 +02:00
navigate('Marketplace', {
fromWallet: wallet,
})
}
2020-07-20 15:38:46 +02:00
title={loc.wallets.details_marketplace}
/>
),
2020-04-30 17:31:29 +02:00
ios: (
2020-07-15 19:32:59 +02:00
<SecondButton
2020-04-30 17:31:29 +02:00
onPress={async () => {
Linking.openURL('https://bluewallet.io/marketplace-btc/');
}}
2020-07-20 15:38:46 +02:00
title={loc.wallets.details_marketplace}
2020-04-30 17:31:29 +02:00
/>
),
});
};
const exportInternals = async () => {
if (backdoorPressed < 10) return setBackdoorPressed(backdoorPressed + 1);
setBackdoorPressed(0);
if (wallet.type !== HDSegwitBech32Wallet.type) return;
const fileName = 'wallet-externals.json';
const contents = JSON.stringify(
{
_balances_by_external_index: wallet._balances_by_external_index,
_balances_by_internal_index: wallet._balances_by_internal_index,
_txs_by_external_index: wallet._txs_by_external_index,
_txs_by_internal_index: wallet._txs_by_internal_index,
_utxo: wallet._utxo,
next_free_address_index: wallet.next_free_address_index,
next_free_change_address_index: wallet.next_free_change_address_index,
internal_addresses_cache: wallet.internal_addresses_cache,
external_addresses_cache: wallet.external_addresses_cache,
_xpub: wallet._xpub,
gap_limit: wallet.gap_limit,
label: wallet.label,
_lastTxFetch: wallet._lastTxFetch,
_lastBalanceFetch: wallet._lastBalanceFetch,
},
null,
2,
);
if (Platform.OS === 'ios') {
const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`;
await RNFS.writeFile(filePath, contents);
Share.open({
url: 'file://' + filePath,
saveToFiles: isCatalyst,
})
.catch(error => {
console.log(error);
alert(error.message);
})
.finally(() => {
RNFS.unlink(filePath);
});
} else if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
title: loc.send.permission_storage_title,
message: loc.send.permission_storage_message,
buttonNeutral: loc.send.permission_storage_later,
buttonNegative: loc._.cancel,
buttonPositive: loc._.ok,
});
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('Storage Permission: Granted');
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
try {
await RNFS.writeFile(filePath, contents);
alert(loc.formatString(loc.send.txSaved, { filePath: fileName }));
} catch (e) {
console.log(e);
alert(e.message);
}
} else {
console.log('Storage Permission: Denied');
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [
{
text: loc.send.open_settings,
onPress: () => {
Linking.openSettings();
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
]);
}
}
};
const purgeTransactions = async () => {
if (backdoorPressed < 10) return setBackdoorPressed(backdoorPressed + 1);
setBackdoorPressed(0);
const msg = 'Transactions purged. Pls go to main screen and back to rerender screen';
if (wallet.type === HDSegwitBech32Wallet.type) {
wallet._txs_by_external_index = {};
wallet._txs_by_internal_index = {};
alert(msg);
}
if (wallet._hdWalletInstance) {
wallet._hdWalletInstance._txs_by_external_index = {};
wallet._hdWalletInstance._txs_by_internal_index = {};
alert(msg);
}
};
2020-08-09 03:04:11 +02:00
const navigateToBroadcast = () => {
navigate('Broadcast');
};
const navigateToIsItMyAddress = () => {
navigate('IsItMyAddress');
};
2020-08-09 03:04:11 +02:00
const walletNameTextInputOnBlur = () => {
if (walletName.trim().length === 0) {
const walletLabel = wallet.getLabel();
setWalletName(walletLabel);
2018-01-30 23:42:38 +01:00
}
2020-08-09 03:04:11 +02:00
};
2020-07-20 15:38:46 +02:00
2020-08-09 03:04:11 +02:00
const handleDeleteButtonTapped = () => {
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
Alert.alert(
loc.wallets.details_delete_wallet,
loc.wallets.details_are_you_sure,
[
{
text: loc.wallets.details_yes_delete,
onPress: async () => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
return;
}
}
if (wallet.getBalance() > 0 && wallet.allowSend()) {
presentWalletHasBalanceAlert();
} else {
setIsLoading(true);
Notifications.unsubscribe(wallet.getAllExternalAddresses(), [], []);
deleteWallet(wallet);
2020-08-09 03:04:11 +02:00
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
await saveToDisk();
2020-08-09 03:04:11 +02:00
popToTop();
}
},
style: 'destructive',
},
{ text: loc.wallets.details_no_cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
};
return isLoading ? (
<View style={styles.root}>
2020-11-30 05:18:54 +01:00
<BlueLoading />
2020-08-09 03:04:11 +02:00
</View>
) : (
<SafeBlueArea style={styles.root}>
<StatusBar barStyle="default" />
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<ScrollView contentContainerStyle={styles.scrollViewContent}>
<BlueCard style={styles.address}>
{(() => {
if (
[LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type) ||
(wallet.type === WatchOnlyWallet.type && !wallet.isHd())
) {
return (
<>
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_address.toLowerCase()}</Text>
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.getAddress()}</Text>
</>
);
}
})()}
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.add_wallet_name.toLowerCase()}</Text>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.input, stylesHook.input]}>
<TextInput
placeholder={loc.send.details_note_placeholder}
value={walletName}
onChangeText={setWalletName}
onBlur={walletNameTextInputOnBlur}
numberOfLines={1}
placeholderTextColor="#81868e"
style={styles.inputText}
editable={!isLoading}
underlineColorAndroid="transparent"
/>
</View>
</KeyboardAvoidingView>
<BlueSpacing20 />
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text>
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.typeReadable}</Text>
2020-10-05 23:25:14 +02:00
{wallet.type === MultisigHDWallet.type && (
<>
2020-12-21 19:27:49 +01:00
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_multisig_type}</Text>
2020-10-05 23:25:14 +02:00
<BlueText>
2020-12-21 19:27:49 +01:00
{loc.formatString(loc.wallets[`details_ms_${wallet.isNativeSegwit() ? 'ns' : wallet.isWrappedSegwit() ? 'ws' : 'l'}`], {
m: wallet.getM(),
n: wallet.getN(),
})}
2020-10-05 23:25:14 +02:00
</BlueText>
</>
)}
{wallet.type === MultisigHDWallet.type && (
2020-10-05 23:25:14 +02:00
<>
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.multisig.how_many_signatures_can_bluewallet_make}</Text>
<BlueText>{wallet.howManySignaturesCanWeMake()}</BlueText>
</>
)}
{wallet.type === MultisigHDWallet.type && !!wallet.getDerivationPath() && (
<>
2020-12-21 19:27:49 +01:00
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_derivation_path}</Text>
2020-10-05 23:25:14 +02:00
<BlueText>{wallet.getDerivationPath()}</BlueText>
</>
)}
2020-08-09 03:04:11 +02:00
{wallet.type === LightningCustodianWallet.type && (
2020-08-03 04:03:15 +02:00
<>
2020-08-09 03:04:11 +02:00
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_connected_to.toLowerCase()}</Text>
<BlueText>{wallet.getBaseURI()}</BlueText>
2020-08-03 04:03:15 +02:00
</>
2020-08-09 03:04:11 +02:00
)}
{wallet.type === HDAezeedWallet.type && (
<>
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.identity_pubkey.toLowerCase()}</Text>
<BlueText>{wallet.getIdentityPubkey()}</BlueText>
</>
)}
2020-08-09 03:04:11 +02:00
<>
<Text onPress={exportInternals} style={[styles.textLabel2, stylesHook.textLabel2]}>
{loc.transactions.list_title.toLowerCase()}
</Text>
2020-08-09 03:04:11 +02:00
<View style={styles.hardware}>
<BlueText>{loc.wallets.details_display}</BlueText>
<Switch value={hideTransactionsInWalletsList} onValueChange={setHideTransactionsInWalletsList} />
</View>
</>
<>
<Text onPress={purgeTransactions} style={[styles.textLabel2, stylesHook.textLabel2]}>
{loc.transactions.transactions_count.toLowerCase()}
</Text>
2020-08-09 03:04:11 +02:00
<BlueText>{wallet.getTransactions().length}</BlueText>
</>
<View>
{wallet.type === WatchOnlyWallet.type && wallet.getSecret().startsWith('zpub') && (
<>
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_advanced.toLowerCase()}</Text>
<View style={styles.hardware}>
<BlueText>{loc.wallets.details_use_with_hardware_wallet}</BlueText>
<Switch value={useWithHardwareWallet} onValueChange={setUseWithHardwareWallet} />
</View>
2020-08-03 04:03:15 +02:00
<>
2020-08-09 03:04:11 +02:00
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_master_fingerprint.toLowerCase()}</Text>
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.getMasterFingerprintHex()}</Text>
2020-08-03 04:03:15 +02:00
</>
2020-08-09 03:04:11 +02:00
<BlueSpacing20 />
</>
)}
<BlueSpacing20 />
2020-08-09 03:04:11 +02:00
<SecondButton onPress={navigateToWalletExport} title={loc.wallets.details_export_backup} />
2020-08-09 03:04:11 +02:00
<BlueSpacing20 />
2020-01-02 23:06:51 +01:00
2020-10-05 23:25:14 +02:00
{wallet.type === MultisigHDWallet.type && (
<>
<SecondButton
onPress={navigateToMultisigCoordinationSetup}
title={loc.multisig.export_coordination_setup.replace(/^\w/, c => c.toUpperCase())}
/>
</>
)}
{wallet.type === MultisigHDWallet.type && (
<>
<BlueSpacing20 />
<SecondButton onPress={navigateToViewEditCosigners} title={loc.multisig.view_edit_cosigners} />
</>
)}
2020-08-09 03:04:11 +02:00
{(wallet.type === HDLegacyBreadwalletWallet.type ||
wallet.type === HDLegacyP2PKHWallet.type ||
wallet.type === HDSegwitBech32Wallet.type ||
wallet.type === HDSegwitP2SHWallet.type) && (
<>
<SecondButton onPress={navigateToXPub} title={loc.wallets.details_show_xpub} />
2020-01-02 23:06:51 +01:00
2020-08-09 03:04:11 +02:00
<BlueSpacing20 />
{renderMarketplaceButton()}
</>
)}
{wallet.type !== LightningCustodianWallet.type && (
<>
<BlueSpacing20 />
<SecondButton onPress={navigateToBroadcast} title={loc.settings.network_broadcast} />
</>
)}
<>
<BlueSpacing20 />
<SecondButton onPress={navigateToIsItMyAddress} title={loc.is_it_my_address.title} />
</>
2020-08-09 03:04:11 +02:00
<BlueSpacing20 />
<BlueSpacing20 />
2020-10-29 10:26:39 +01:00
<TouchableOpacity onPress={handleDeleteButtonTapped}>
2020-11-11 05:05:00 +01:00
<Text textBreakStrategy="simple" style={styles.delete}>{`${loc.wallets.details_delete}${' '}`}</Text>
2020-08-09 03:04:11 +02:00
</TouchableOpacity>
</View>
</BlueCard>
</ScrollView>
</TouchableWithoutFeedback>
</SafeBlueArea>
);
};
2018-03-18 03:48:23 +01:00
2020-12-25 17:09:53 +01:00
WalletDetails.navigationOptions = navigationStyle({
2020-08-09 03:04:11 +02:00
headerTitle: loc.wallets.details_title,
2020-07-15 19:32:59 +02:00
});
2020-08-09 03:04:11 +02:00
export default WalletDetails;