mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
Merge branch 'master' into search
This commit is contained in:
commit
fde4bc4f4b
2
.github/workflows/build-release-apk.yml
vendored
2
.github/workflows/build-release-apk.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Install node_modules
|
||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -66,9 +66,7 @@ jobs:
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
tool-cache: true
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
@ -141,7 +139,7 @@ jobs:
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047
|
||||
arch: x86_64
|
||||
script: npm run e2e:release-test -- --record-videos all --record-logs all --take-screenshots all --headless -d 200000 -R 3 --artifacts-location /mnt/artifacts
|
||||
script: npm run e2e:release-test -- --record-videos all --record-logs all --take-screenshots all --headless -d 200000 -R 5 --artifacts-location /mnt/artifacts
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
|
18
Gemfile.lock
18
Gemfile.lock
@ -18,19 +18,19 @@ GEM
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.948.0)
|
||||
aws-sdk-core (3.199.0)
|
||||
aws-partitions (1.950.0)
|
||||
aws-sdk-core (3.201.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.87.0)
|
||||
aws-sdk-core (~> 3, >= 3.199.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.154.0)
|
||||
aws-sdk-core (~> 3, >= 3.199.0)
|
||||
aws-sdk-kms (1.88.0)
|
||||
aws-sdk-core (~> 3, >= 3.201.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.156.0)
|
||||
aws-sdk-core (~> 3, >= 3.201.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
@ -210,7 +210,7 @@ GEM
|
||||
base64
|
||||
mini_magick (4.13.1)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.24.0)
|
||||
minitest (5.24.1)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
|
@ -2,9 +2,9 @@ import { createNavigationContainerRef, NavigationAction, ParamListBase, StackAct
|
||||
|
||||
export const navigationRef = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
export function navigate(name: string, params?: ParamListBase) {
|
||||
export function navigate(name: string, params?: ParamListBase, options?: { merge: boolean }) {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.current?.navigate(name, params);
|
||||
navigationRef.current?.navigate({ name, params, merge: options?.merge });
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,10 @@ export function dispatch(action: NavigationAction) {
|
||||
}
|
||||
}
|
||||
|
||||
export function navigateToWalletsList() {
|
||||
navigate('WalletsList');
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.current?.reset({
|
||||
|
@ -84,6 +84,11 @@ android {
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
assets.srcDirs = ['src/main/assets', 'src/main/res/assets']
|
||||
@ -125,7 +130,6 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
}
|
||||
apply plugin: 'com.google.gms.google-services' // Google Services plugin
|
||||
|
||||
|
@ -4,9 +4,9 @@ buildscript {
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
supportLibVersion = "28.0.0"
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
buildToolsVersion = "33.0.0"
|
||||
compileSdkVersion = 34
|
||||
targetSdkVersion = 34
|
||||
googlePlayServicesVersion = "16.+"
|
||||
googlePlayServicesIidVersion = "16.0.1"
|
||||
firebaseVersion = "17.3.4"
|
||||
@ -15,7 +15,7 @@ buildscript {
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
kotlin_version = '1.9.25'
|
||||
kotlinVersion = '1.9.23'
|
||||
kotlinVersion = '1.9.25'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
@ -23,11 +23,11 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
classpath("com.bugsnag:bugsnag-android-gradle-plugin:5.+")
|
||||
classpath 'com.google.gms:google-services:4.4.2' // Google Services plugin
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,14 +56,19 @@ allprojects {
|
||||
google()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'androidx.activity:activity:1.5.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
afterEvaluate {project ->
|
||||
if (project.hasProperty("android")) {
|
||||
android {
|
||||
buildToolsVersion "34.0.0"
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
}
|
||||
@ -78,4 +83,4 @@ subprojects {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
@ -89,7 +89,7 @@ const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' };
|
||||
export const hardcodedPeers: Peer[] = [
|
||||
{ host: 'mainnet.foundationdevices.com', ssl: '50002' },
|
||||
{ host: 'bitcoin.lukechilds.co', ssl: '50002' },
|
||||
{ host: 'electrum.jochen-hoenicke.de', ssl: '50006' },
|
||||
// { host: 'electrum.jochen-hoenicke.de', ssl: '50006' },
|
||||
{ host: 'electrum1.bluewallet.io', ssl: '443' },
|
||||
{ host: 'electrum.acinq.co', ssl: '50002' },
|
||||
{ host: 'electrum.bitaroo.net', ssl: '50002' },
|
||||
@ -887,7 +887,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
|
||||
const tx = ret[txid];
|
||||
// dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain
|
||||
// strings txhex
|
||||
if (verbose && typeof tx !== 'string' && (!tx.confirmations || tx.confirmations < 7)) {
|
||||
if (verbose && typeof tx !== 'string' && (!tx?.confirmations || tx.confirmations < 7)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Alert as RNAlert } from 'react-native';
|
||||
|
||||
import { Alert as RNAlert, Platform, ToastAndroid } from 'react-native';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
import loc from '../loc';
|
||||
|
||||
@ -21,7 +20,14 @@ const presentAlert = ({
|
||||
if (hapticFeedback) {
|
||||
triggerHapticFeedback(hapticFeedback);
|
||||
}
|
||||
|
||||
if (Platform.OS !== 'android') {
|
||||
type = AlertType.Alert;
|
||||
}
|
||||
switch (type) {
|
||||
case AlertType.Toast:
|
||||
ToastAndroid.showWithGravity(message, ToastAndroid.LONG, ToastAndroid.BOTTOM);
|
||||
break;
|
||||
default:
|
||||
RNAlert.alert(title ?? loc.alert.default, message);
|
||||
break;
|
||||
|
@ -1,86 +1,130 @@
|
||||
import React from 'react';
|
||||
import { Platform, StyleSheet, useWindowDimensions, View } from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, ReactElement, ComponentType, JSXElementConstructor, ReactNode } from 'react';
|
||||
import { SheetSize, SizeInfo, TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet';
|
||||
import { Keyboard, StyleSheet, View, TouchableOpacity, Platform, Image } from 'react-native';
|
||||
|
||||
import { BlueSpacing10 } from '../BlueComponents';
|
||||
import loc from '../loc';
|
||||
import Button from './Button';
|
||||
import { useTheme } from './themes';
|
||||
interface BottomModalProps extends TrueSheetProps {
|
||||
children?: React.ReactNode;
|
||||
onClose?: () => void;
|
||||
name?: string;
|
||||
isGrabberVisible?: boolean;
|
||||
sizes?: SheetSize[] | undefined;
|
||||
footer?: ReactElement | ComponentType<any>;
|
||||
footerDefaultMargins?: boolean | number;
|
||||
onPresent?: () => void;
|
||||
onSizeChange?: (size: SizeInfo) => void;
|
||||
showCloseButton?: boolean;
|
||||
}
|
||||
|
||||
export interface BottomModalHandle {
|
||||
present: () => Promise<void>;
|
||||
dismiss: () => Promise<void>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
justifyContent: 'flex-end',
|
||||
margin: 0,
|
||||
footerContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
hasDoneButton: {
|
||||
padding: 16,
|
||||
paddingBottom: 24,
|
||||
buttonContainer: {
|
||||
position: 'absolute',
|
||||
backgroundColor: 'lightgray',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
top: 20,
|
||||
right: 20,
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
|
||||
interface BottomModalProps {
|
||||
children?: React.ReactNode;
|
||||
onBackButtonPress?: () => void;
|
||||
onBackdropPress?: () => void;
|
||||
onClose: () => void;
|
||||
windowHeight?: number;
|
||||
windowWidth?: number;
|
||||
doneButton?: boolean;
|
||||
avoidKeyboard?: boolean;
|
||||
allowBackdropPress?: boolean;
|
||||
isVisible: boolean;
|
||||
coverScreen?: boolean;
|
||||
propagateSwipe?: boolean;
|
||||
}
|
||||
|
||||
const BottomModal: React.FC<BottomModalProps> = ({
|
||||
onBackButtonPress,
|
||||
onBackdropPress,
|
||||
onClose,
|
||||
windowHeight,
|
||||
windowWidth,
|
||||
doneButton,
|
||||
isVisible,
|
||||
avoidKeyboard = false,
|
||||
allowBackdropPress = true,
|
||||
coverScreen = true,
|
||||
propagateSwipe,
|
||||
...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,
|
||||
const BottomModal = forwardRef<BottomModalHandle, BottomModalProps>(
|
||||
(
|
||||
{
|
||||
name,
|
||||
onClose,
|
||||
onPresent,
|
||||
onSizeChange,
|
||||
showCloseButton = true,
|
||||
isGrabberVisible = true,
|
||||
sizes = ['auto'],
|
||||
footer,
|
||||
footerDefaultMargins,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
});
|
||||
ref,
|
||||
) => {
|
||||
const trueSheetRef = useRef<TrueSheet>(null);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
propagateSwipe={propagateSwipe}
|
||||
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>
|
||||
);
|
||||
};
|
||||
useImperativeHandle(ref, () => ({
|
||||
present: async () => {
|
||||
Keyboard.dismiss();
|
||||
if (trueSheetRef.current?.present) {
|
||||
await trueSheetRef.current.present();
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
dismiss: async () => {
|
||||
Keyboard.dismiss();
|
||||
if (trueSheetRef.current?.dismiss) {
|
||||
await trueSheetRef.current.dismiss();
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const dismiss = () => {
|
||||
trueSheetRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const renderTopRightButton = () =>
|
||||
showCloseButton ? (
|
||||
<TouchableOpacity style={styles.buttonContainer} onPress={dismiss} testID="ModalDoneButton">
|
||||
<Image source={require('../img/close.png')} width={20} height={20} />
|
||||
</TouchableOpacity>
|
||||
) : null;
|
||||
|
||||
const renderFooter = (): ReactElement<any, string | JSXElementConstructor<any>> | ComponentType<unknown> | undefined => {
|
||||
// Footer is not working correctly on Android yet.
|
||||
if (!footer) return undefined;
|
||||
|
||||
if (React.isValidElement(footer)) {
|
||||
return footerDefaultMargins ? <View style={styles.footerContainer}>{footer}</View> : footer;
|
||||
} else if (typeof footer === 'function') {
|
||||
// Render the footer component dynamically
|
||||
const FooterComponent = footer as ComponentType<any>;
|
||||
return <FooterComponent />;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const FooterComponent = Platform.OS !== 'android' && renderFooter();
|
||||
|
||||
return (
|
||||
<TrueSheet
|
||||
ref={trueSheetRef}
|
||||
cornerRadius={24}
|
||||
sizes={sizes}
|
||||
onDismiss={onClose}
|
||||
onPresent={onPresent}
|
||||
onSizeChange={onSizeChange}
|
||||
grabber={isGrabberVisible}
|
||||
// Footer is not working correctly on Android yet.
|
||||
FooterComponent={FooterComponent as ReactElement}
|
||||
{...props}
|
||||
blurTint="regular"
|
||||
>
|
||||
{children}
|
||||
{renderTopRightButton()}
|
||||
{Platform.OS === 'android' && (renderFooter() as ReactNode)}
|
||||
</TrueSheet>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default BottomModal;
|
||||
|
@ -231,7 +231,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
||||
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit}>
|
||||
<Text style={styles.walletPreferredUnitText}>
|
||||
{wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY
|
||||
? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD
|
||||
? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD)
|
||||
: wallet.getPreferredBalanceUnit()}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -23,6 +23,8 @@ import { useIsLargeScreen } from '../hooks/useIsLargeScreen';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
|
||||
import { BlurredBalanceView } from './BlurredBalanceView';
|
||||
import { useTheme } from './themes';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import { WalletTransactionsStatus } from './Context/StorageProvider';
|
||||
import { Transaction, TWallet } from '../class/wallets/types';
|
||||
|
||||
interface NewWalletPanelProps {
|
||||
@ -103,128 +105,9 @@ interface WalletCarouselItemProps {
|
||||
customStyle?: ViewStyle;
|
||||
horizontal?: boolean;
|
||||
isActive?: boolean;
|
||||
searchQuery?: string;
|
||||
renderHighlightedText?: (text: string, query: string) => JSX.Element;
|
||||
allowOnPressAnimation?: boolean;
|
||||
}
|
||||
|
||||
export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = ({
|
||||
item,
|
||||
onPress,
|
||||
handleLongPress,
|
||||
isSelectedWallet,
|
||||
customStyle,
|
||||
horizontal,
|
||||
isActive,
|
||||
searchQuery,
|
||||
renderHighlightedText,
|
||||
}) => {
|
||||
const scaleValue = useRef(new Animated.Value(1.0)).current;
|
||||
const { colors } = useTheme();
|
||||
const { width } = useWindowDimensions();
|
||||
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
|
||||
const onPressedIn = useCallback(() => {
|
||||
Animated.spring(scaleValue, {
|
||||
toValue: 0.95,
|
||||
useNativeDriver: true,
|
||||
friction: 3,
|
||||
tension: 100,
|
||||
}).start();
|
||||
}, [scaleValue]);
|
||||
|
||||
const onPressedOut = useCallback(() => {
|
||||
Animated.spring(scaleValue, {
|
||||
toValue: 1.0,
|
||||
useNativeDriver: true,
|
||||
friction: 3,
|
||||
tension: 100,
|
||||
}).start();
|
||||
}, [scaleValue]);
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
onPressedOut();
|
||||
onPress(item);
|
||||
}, [item, onPress, onPressedOut]);
|
||||
|
||||
const opacity = isSelectedWallet === false ? 0.5 : 1.0;
|
||||
let image;
|
||||
switch (item.type) {
|
||||
case LightningLdkWallet.type:
|
||||
case LightningCustodianWallet.type:
|
||||
image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
|
||||
break;
|
||||
case MultisigHDWallet.type:
|
||||
image = I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png');
|
||||
break;
|
||||
default:
|
||||
image = I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png');
|
||||
}
|
||||
|
||||
const latestTransactionText =
|
||||
item.getBalance() !== 0 && item.getLatestTransactionTime() === 0
|
||||
? loc.wallets.pull_to_refresh
|
||||
: item.getTransactions().find((tx: Transaction) => tx.confirmations === 0)
|
||||
? loc.transactions.pending
|
||||
: transactionTimeToReadable(item.getLatestTransactionTime());
|
||||
|
||||
const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
isLargeScreen || !horizontal ? [iStyles.rootLargeDevice, customStyle] : (customStyle ?? { ...iStyles.root, width: itemWidth }),
|
||||
{ opacity, transform: [{ scale: scaleValue }] },
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
testID={item.getLabel()}
|
||||
onPressIn={onPressedIn}
|
||||
onPressOut={onPressedOut}
|
||||
onLongPress={() => {
|
||||
if (handleLongPress) handleLongPress();
|
||||
}}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<View style={[iStyles.shadowContainer, { backgroundColor: colors.background, shadowColor: colors.shadowColor }]}>
|
||||
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}>
|
||||
<Image source={image} style={iStyles.image} />
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
|
||||
{renderHighlightedText && searchQuery ? renderHighlightedText(item.getLabel(), searchQuery) : item.getLabel()}
|
||||
</Text>
|
||||
<View style={iStyles.balanceContainer}>
|
||||
{item.hideBalance ? (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<BlurredBalanceView />
|
||||
</>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
key={`${balance}`} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
|
||||
>
|
||||
{`${balance} `}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
|
||||
{loc.wallets.list_latest_transaction}
|
||||
</Text>
|
||||
<Text numberOfLines={1} style={[iStyles.latestTxTime, { color: colors.inverseForegroundColor }]}>
|
||||
{latestTransactionText}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
const iStyles = StyleSheet.create({
|
||||
root: { paddingRight: 20 },
|
||||
rootLargeDevice: { marginVertical: 20 },
|
||||
@ -284,6 +167,119 @@ const iStyles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
||||
({ item, onPress, handleLongPress, isSelectedWallet, customStyle, horizontal, allowOnPressAnimation = true }) => {
|
||||
const scaleValue = useRef(new Animated.Value(1.0)).current;
|
||||
const { colors } = useTheme();
|
||||
const { walletTransactionUpdateStatus } = useStorage();
|
||||
const { width } = useWindowDimensions();
|
||||
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
|
||||
const onPressedIn = useCallback(() => {
|
||||
Animated.spring(scaleValue, {
|
||||
toValue: 0.95,
|
||||
useNativeDriver: true,
|
||||
friction: 3,
|
||||
tension: 100,
|
||||
}).start();
|
||||
}, [scaleValue]);
|
||||
|
||||
const onPressedOut = useCallback(() => {
|
||||
Animated.spring(scaleValue, {
|
||||
toValue: 1.0,
|
||||
useNativeDriver: true,
|
||||
friction: 3,
|
||||
tension: 100,
|
||||
}).start();
|
||||
}, [scaleValue]);
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
onPressedOut();
|
||||
onPress(item);
|
||||
}, [item, onPress, onPressedOut]);
|
||||
|
||||
const opacity = isSelectedWallet === false ? 0.5 : 1.0;
|
||||
let image;
|
||||
switch (item.type) {
|
||||
case LightningLdkWallet.type:
|
||||
case LightningCustodianWallet.type:
|
||||
image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
|
||||
break;
|
||||
case MultisigHDWallet.type:
|
||||
image = I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png');
|
||||
break;
|
||||
default:
|
||||
image = I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png');
|
||||
}
|
||||
|
||||
const latestTransactionText =
|
||||
walletTransactionUpdateStatus === WalletTransactionsStatus.ALL || walletTransactionUpdateStatus === item.getID()
|
||||
? loc.transactions.updating
|
||||
: item.getBalance() !== 0 && item.getLatestTransactionTime() === 0
|
||||
? loc.wallets.pull_to_refresh
|
||||
: item.getTransactions().find((tx: Transaction) => tx.confirmations === 0)
|
||||
? loc.transactions.pending
|
||||
: transactionTimeToReadable(item.getLatestTransactionTime());
|
||||
|
||||
const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
isLargeScreen || !horizontal ? [iStyles.rootLargeDevice, customStyle] : (customStyle ?? { ...iStyles.root, width: itemWidth }),
|
||||
{ opacity, transform: [{ scale: scaleValue }] },
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
testID={item.getLabel()}
|
||||
onPressIn={allowOnPressAnimation ? onPressedIn : undefined}
|
||||
onPressOut={allowOnPressAnimation ? onPressedOut : undefined}
|
||||
onLongPress={() => {
|
||||
if (handleLongPress) handleLongPress();
|
||||
}}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<View style={[iStyles.shadowContainer, { backgroundColor: colors.background, shadowColor: colors.shadowColor }]}>
|
||||
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}>
|
||||
<Image source={image} style={iStyles.image} />
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
|
||||
{item.getLabel()}
|
||||
</Text>
|
||||
<View style={iStyles.balanceContainer}>
|
||||
{item.hideBalance ? (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<BlurredBalanceView />
|
||||
</>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
key={`${balance}`} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
|
||||
>
|
||||
{`${balance} `}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
|
||||
{loc.wallets.list_latest_transaction}
|
||||
</Text>
|
||||
<Text numberOfLines={1} style={[iStyles.latestTxTime, { color: colors.inverseForegroundColor }]}>
|
||||
{latestTransactionText}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
|
||||
horizontal?: boolean;
|
||||
selectedWallet?: string;
|
||||
@ -292,8 +288,6 @@ interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
|
||||
handleLongPress?: () => void;
|
||||
data: TWallet[];
|
||||
scrollEnabled?: boolean;
|
||||
renderHighlightedText?: (text: string, query: string) => JSX.Element;
|
||||
searchQuery?: string;
|
||||
}
|
||||
|
||||
type FlatListRefType = FlatList<any> & {
|
||||
@ -322,17 +316,7 @@ const cStyles = StyleSheet.create({
|
||||
const ListHeaderComponent: React.FC = () => <View style={cStyles.separatorStyle} />;
|
||||
|
||||
const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props, ref) => {
|
||||
const {
|
||||
horizontal,
|
||||
data,
|
||||
handleLongPress,
|
||||
onPress,
|
||||
selectedWallet,
|
||||
scrollEnabled,
|
||||
onNewWalletPress,
|
||||
searchQuery,
|
||||
renderHighlightedText,
|
||||
} = props;
|
||||
const { horizontal, data, handleLongPress, onPress, selectedWallet, scrollEnabled, onNewWalletPress } = props;
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: ListRenderItemInfo<TWallet>) =>
|
||||
item ? (
|
||||
@ -342,11 +326,9 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
handleLongPress={handleLongPress}
|
||||
onPress={onPress}
|
||||
horizontal={horizontal}
|
||||
searchQuery={searchQuery}
|
||||
renderHighlightedText={renderHighlightedText}
|
||||
/>
|
||||
) : null,
|
||||
[horizontal, selectedWallet, handleLongPress, onPress, searchQuery, renderHighlightedText],
|
||||
[horizontal, selectedWallet, handleLongPress, onPress],
|
||||
);
|
||||
|
||||
const flatListRef = useRef<FlatList<any>>(null);
|
||||
@ -420,8 +402,6 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
handleLongPress={handleLongPress}
|
||||
onPress={onPress}
|
||||
key={index}
|
||||
searchQuery={props.searchQuery}
|
||||
renderHighlightedText={props.renderHighlightedText}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||
import { navigationRef } from '../NavigationService';
|
||||
|
||||
/**
|
||||
* Helper function that navigates to ScanQR screen, and returns promise that will resolve with the result of a scan,
|
||||
* and then navigates back. If QRCode scan was closed, promise resolves to null.
|
||||
@ -37,9 +38,13 @@ function scanQrHelper(
|
||||
params = { launchedBy: currentScreenName, showFileImportButton: Boolean(showFileImportButton) };
|
||||
}
|
||||
|
||||
navigationRef.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params,
|
||||
navigationRef.navigate({
|
||||
name: 'ScanQRCodeRoot',
|
||||
params: {
|
||||
screen: 'ScanQRCode',
|
||||
params,
|
||||
},
|
||||
merge: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,11 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
|
||||
const { wallets, saveToDisk } = useStorage();
|
||||
const { isBiometricUseEnabled } = useBiometrics();
|
||||
|
||||
const enhancedNavigate: NavigationProp<ParamListBase>['navigate'] = (screenOrOptions: any, params?: any) => {
|
||||
const enhancedNavigate: NavigationProp<ParamListBase>['navigate'] = (
|
||||
screenOrOptions: any,
|
||||
params?: any,
|
||||
options?: { merge?: boolean },
|
||||
) => {
|
||||
let screenName: string;
|
||||
if (typeof screenOrOptions === 'string') {
|
||||
screenName = screenOrOptions;
|
||||
@ -32,9 +36,11 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
|
||||
const proceedWithNavigation = () => {
|
||||
console.log('Proceeding with navigation to', screenName);
|
||||
if (navigationRef.current?.isReady()) {
|
||||
typeof screenOrOptions === 'string'
|
||||
? originalNavigation.navigate(screenOrOptions, params)
|
||||
: originalNavigation.navigate(screenName, params); // Fixed to use screenName and params
|
||||
if (typeof screenOrOptions === 'string') {
|
||||
originalNavigation.navigate({ name: screenOrOptions, params, merge: options?.merge });
|
||||
} else {
|
||||
originalNavigation.navigate({ ...screenOrOptions, params, merge: options?.merge });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -88,9 +94,14 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
|
||||
})();
|
||||
};
|
||||
|
||||
const navigateToWalletsList = () => {
|
||||
enhancedNavigate('WalletsList');
|
||||
}
|
||||
|
||||
return {
|
||||
...originalNavigation,
|
||||
navigate: enhancedNavigate,
|
||||
navigateToWalletsList,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -464,7 +464,7 @@ PODS:
|
||||
- React-perflogger (= 0.72.14)
|
||||
- ReactNativeCameraKit (13.0.0):
|
||||
- React-Core
|
||||
- RealmJS (12.11.1):
|
||||
- RealmJS (12.12.1):
|
||||
- React
|
||||
- rn-ldk (0.8.4):
|
||||
- React-Core
|
||||
@ -517,6 +517,9 @@ PODS:
|
||||
- RNWatch (1.1.0):
|
||||
- React
|
||||
- SocketRocket (0.6.1)
|
||||
- TrueSheet (0.12.4):
|
||||
- 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"
|
||||
|
||||
@ -830,7 +836,7 @@ SPEC CHECKSUMS:
|
||||
React-utils: 22a77b05da25ce49c744faa82e73856dcae1734e
|
||||
ReactCommon: ff94462e007c568d8cdebc32e3c97af86ec93bb5
|
||||
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
|
||||
RealmJS: ecd23895a68689ce2c048d28df3d699cf76cedb7
|
||||
RealmJS: f4f6d27b325b1c1b402eb0aa5ee79e9d5f603293
|
||||
rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643
|
||||
RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a
|
||||
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
|
||||
@ -854,8 +860,9 @@ SPEC CHECKSUMS:
|
||||
RNVectorIcons: 32462e7c7e58fe457474fc79c4d7de3f0ef08d70
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
|
||||
TrueSheet: 3ba036b4f20ea78a2953fcf7949e8a60dd63d3f6
|
||||
Yoga: c32e0be1a17f8f1f0e633a3122f7666441f52c82
|
||||
|
||||
PODFILE CHECKSUM: f19eea438501edfe85fb2fa51d40ba1b57540758
|
||||
|
||||
COCOAPODS: 1.14.3
|
||||
COCOAPODS: 1.15.2
|
||||
|
21629
package-lock.json
generated
21629
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -51,7 +51,6 @@
|
||||
"clean:ios": "rm -fr node_modules && rm -fr ios/Pods && npm i && cd ios && pod update && cd ..; npm start -- --reset-cache",
|
||||
"releasenotes2json": "./scripts/release-notes.sh > release-notes.txt; node -e 'console.log(JSON.stringify(require(\"fs\").readFileSync(\"release-notes.txt\", \"utf8\")));' > release-notes.json",
|
||||
"branch2json": "./scripts/current-branch.sh > current-branch.json",
|
||||
"prestart": "watchman watch-del $PWD && watchman watch-project $PWD",
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"android": "react-native run-android",
|
||||
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
|
||||
@ -108,6 +107,7 @@
|
||||
"@bugsnag/react-native": "7.25.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#fbcb1af",
|
||||
"@ngraveio/bc-ur": "1.1.12",
|
||||
"@noble/secp256k1": "1.6.3",
|
||||
"@react-native-async-storage/async-storage": "1.24.0",
|
||||
@ -174,7 +174,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",
|
||||
@ -194,7 +193,7 @@
|
||||
"react-native-vector-icons": "10.1.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"readable-stream": "3.6.2",
|
||||
"realm": "12.11.1",
|
||||
"realm": "12.12.1",
|
||||
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
|
||||
"rn-nodeify": "10.3.0",
|
||||
"scryptsy": "2.1.0",
|
||||
|
@ -6,7 +6,6 @@ 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 Button from '../../components/Button';
|
||||
import LNNodeBar from '../../components/LNNodeBar';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
@ -18,6 +17,7 @@ import selectWallet from '../../helpers/select-wallet';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
|
||||
|
||||
const LdkNodeInfoChannelStatus = { ACTIVE: 'Active', INACTIVE: 'Inactive', PENDING: 'PENDING', STATUS: 'status' };
|
||||
|
||||
@ -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,52 +224,50 @@ const LdkInfo = () => {
|
||||
const status = selectedChannelIndex?.status;
|
||||
const channelData = selectedChannelIndex?.channel.item;
|
||||
return (
|
||||
<BottomModal isVisible={selectedChannelIndex !== undefined} onClose={closeModal} avoidKeyboard>
|
||||
<View style={[styles.modalContent, stylesHook.modalContent]}>
|
||||
<Text style={stylesHook.detailsText}>{loc.lnd.node_alias}</Text>
|
||||
<BlueSpacing10 />
|
||||
{channelData && (
|
||||
<Text style={stylesHook.detailsText}>
|
||||
{LightningLdkWallet.pubkeyToAlias(channelData.remote_node_id) +
|
||||
' (' +
|
||||
channelData.remote_node_id.substr(0, 10) +
|
||||
'...' +
|
||||
channelData.remote_node_id.substr(-6) +
|
||||
')'}
|
||||
</Text>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<LNNodeBar
|
||||
disabled={
|
||||
status === LdkNodeInfoChannelStatus.ACTIVE || status === LdkNodeInfoChannelStatus.INACTIVE ? !channelData?.is_usable : true
|
||||
}
|
||||
canSend={Number(channelData?.outbound_capacity_msat / 1000)}
|
||||
canReceive={Number(channelData?.inbound_capacity_msat / 1000)}
|
||||
itemPriceUnit={wallet.getPreferredBalanceUnit()}
|
||||
/>
|
||||
|
||||
<BottomModal ref={bottomModalRef} contentContainerStyle={[styles.modalContent, stylesHook.modalContent]}>
|
||||
<Text style={stylesHook.detailsText}>{loc.lnd.node_alias}</Text>
|
||||
<BlueSpacing10 />
|
||||
{channelData && (
|
||||
<Text style={stylesHook.detailsText}>
|
||||
{status === LdkNodeInfoChannelStatus.PENDING
|
||||
? loc.transactions.pending
|
||||
: channelData?.is_usable
|
||||
? loc.lnd.active
|
||||
: loc.lnd.inactive}
|
||||
{LightningLdkWallet.pubkeyToAlias(channelData.remote_node_id) +
|
||||
' (' +
|
||||
channelData.remote_node_id.substr(0, 10) +
|
||||
'...' +
|
||||
channelData.remote_node_id.substr(-6) +
|
||||
')'}
|
||||
</Text>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<LNNodeBar
|
||||
disabled={
|
||||
status === LdkNodeInfoChannelStatus.ACTIVE || status === LdkNodeInfoChannelStatus.INACTIVE ? !channelData?.is_usable : true
|
||||
}
|
||||
canSend={Number(channelData?.outbound_capacity_msat / 1000)}
|
||||
canReceive={Number(channelData?.inbound_capacity_msat / 1000)}
|
||||
itemPriceUnit={wallet.getPreferredBalanceUnit()}
|
||||
/>
|
||||
|
||||
{status === LdkNodeInfoChannelStatus.INACTIVE && (
|
||||
<>
|
||||
<StyledButton
|
||||
onPress={() => handleOnConnectPeerTapped(channelData)}
|
||||
text={loc.lnd.reconnect_peer}
|
||||
buttonStyle={StyledButtonType.grey}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
<Text style={stylesHook.detailsText}>
|
||||
{status === LdkNodeInfoChannelStatus.PENDING
|
||||
? loc.transactions.pending
|
||||
: channelData?.is_usable
|
||||
? loc.lnd.active
|
||||
: loc.lnd.inactive}
|
||||
</Text>
|
||||
|
||||
<StyledButton onPress={() => closeChannel(channelData)} text={loc.lnd.close_channel} buttonStyle={StyledButtonType.destroy} />
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
{status === LdkNodeInfoChannelStatus.INACTIVE && (
|
||||
<>
|
||||
<StyledButton
|
||||
onPress={() => handleOnConnectPeerTapped(channelData)}
|
||||
text={loc.lnd.reconnect_peer}
|
||||
buttonStyle={StyledButtonType.grey}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
|
||||
<StyledButton onPress={() => closeChannel(channelData)} text={loc.lnd.close_channel} buttonStyle={StyledButtonType.destroy} />
|
||||
<BlueSpacing20 />
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
@ -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, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import Share from 'react-native-share';
|
||||
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
@ -47,7 +36,6 @@ const ReceiveDetails = () => {
|
||||
const [customUnit, setCustomUnit] = useState(BitcoinUnit.BTC);
|
||||
const [bip21encoded, setBip21encoded] = useState('');
|
||||
const [isCustom, setIsCustom] = useState(false);
|
||||
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
|
||||
const [tempCustomLabel, setTempCustomLabel] = useState('');
|
||||
const [tempCustomAmount, setTempCustomAmount] = useState('');
|
||||
const [tempCustomUnit, setTempCustomUnit] = useState(BitcoinUnit.BTC);
|
||||
@ -56,6 +44,7 @@ const ReceiveDetails = () => {
|
||||
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('');
|
||||
@ -65,11 +54,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,
|
||||
@ -253,7 +237,7 @@ const ReceiveDetails = () => {
|
||||
{isCustom && (
|
||||
<>
|
||||
{getDisplayAmount() && (
|
||||
<BlueText testID="CustomAmountText" style={[styles.amount, stylesHook.amount]} numberOfLines={1}>
|
||||
<BlueText testID="BitcoinAmountText" style={[styles.amount, stylesHook.amount]} numberOfLines={1}>
|
||||
{getDisplayAmount()}
|
||||
</BlueText>
|
||||
)}
|
||||
@ -325,21 +309,16 @@ const ReceiveDetails = () => {
|
||||
}, [wallet, address, obtainWalletAddress, setAddressBIP21Encoded]),
|
||||
);
|
||||
|
||||
const dismissCustomAmountModal = () => {
|
||||
Keyboard.dismiss();
|
||||
setIsCustomModalVisible(false);
|
||||
};
|
||||
|
||||
const showCustomAmountModal = () => {
|
||||
setTempCustomLabel(customLabel);
|
||||
setTempCustomAmount(customAmount);
|
||||
setTempCustomUnit(customUnit);
|
||||
setIsCustomModalVisible(true);
|
||||
bottomModalRef.current.present();
|
||||
};
|
||||
|
||||
const createCustomAmountAddress = () => {
|
||||
bottomModalRef.current.dismiss();
|
||||
setIsCustom(true);
|
||||
setIsCustomModalVisible(false);
|
||||
let amount = tempCustomAmount;
|
||||
switch (tempCustomUnit) {
|
||||
case BitcoinUnit.BTC:
|
||||
@ -373,52 +352,7 @@ const ReceiveDetails = () => {
|
||||
setCustomUnit(wallet.getPreferredBalanceUnit());
|
||||
setBip21encoded(DeeplinkSchemaMatch.bip21encode(address));
|
||||
setShowAddress(true);
|
||||
setIsCustomModalVisible(false);
|
||||
};
|
||||
|
||||
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={tempCustomUnit}
|
||||
amount={tempCustomAmount || ''}
|
||||
onChangeText={setTempCustomAmount}
|
||||
onAmountUnitChange={setTempCustomUnit}
|
||||
/>
|
||||
<View style={[styles.customAmount, stylesHook.customAmount]}>
|
||||
<TextInput
|
||||
onChangeText={setTempCustomLabel}
|
||||
placeholderTextColor="#81868e"
|
||||
placeholder={loc.receive.details_label}
|
||||
value={tempCustomLabel || ''}
|
||||
numberOfLines={1}
|
||||
style={[styles.customAmountText, stylesHook.customAmountText]}
|
||||
testID="CustomAmountDescription"
|
||||
/>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.modalButtonContainer}>
|
||||
<Button
|
||||
testID="CustomAmountResetButton"
|
||||
style={[styles.modalButton, stylesHook.modalButton]}
|
||||
title={loc.receive.reset}
|
||||
onPress={resetCustomAmount}
|
||||
/>
|
||||
<View style={styles.modalButtonSpacing} />
|
||||
<Button
|
||||
testID="CustomAmountSaveButton"
|
||||
style={[styles.modalButton, stylesHook.modalButton]}
|
||||
title={loc.receive.details_create}
|
||||
onPress={createCustomAmountAddress}
|
||||
/>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BottomModal>
|
||||
);
|
||||
bottomModalRef.current.dismiss();
|
||||
};
|
||||
|
||||
const handleShareButtonPressed = () => {
|
||||
@ -470,52 +404,93 @@ const ReceiveDetails = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={[styles.root, stylesHook.root]} keyboardShouldPersistTaps="always">
|
||||
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
|
||||
<View style={styles.tabsContainer}>
|
||||
<SegmentedControl
|
||||
values={segmentControlValues}
|
||||
selectedIndex={segmentControlValues.findIndex(tab => tab === currentTab)}
|
||||
onChange={index => {
|
||||
setCurrentTab(segmentControlValues[index]);
|
||||
}}
|
||||
<>
|
||||
<ScrollView contentContainerStyle={[styles.root, stylesHook.root]} keyboardShouldPersistTaps="always">
|
||||
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
|
||||
<View style={styles.tabsContainer}>
|
||||
<SegmentedControl
|
||||
values={segmentControlValues}
|
||||
selectedIndex={segmentControlValues.findIndex(tab => tab === currentTab)}
|
||||
onChange={index => {
|
||||
setCurrentTab(segmentControlValues[index]);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{showAddress && renderTabContent()}
|
||||
{address !== undefined && showAddress && (
|
||||
<HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />
|
||||
)}
|
||||
{showConfirmedBalance ? renderConfirmedBalance() : null}
|
||||
{showPendingBalance ? renderPendingBalance() : null}
|
||||
{!showAddress && !showPendingBalance && !showConfirmedBalance ? <BlueLoading /> : null}
|
||||
<View style={styles.share}>
|
||||
<BlueCard>
|
||||
{showAddress && currentTab === loc.wallets.details_address && (
|
||||
<BlueButtonLink
|
||||
style={styles.link}
|
||||
testID="SetCustomAmountButton"
|
||||
title={loc.receive.details_setAmount}
|
||||
onPress={showCustomAmountModal}
|
||||
/>
|
||||
)}
|
||||
<Button onPress={handleShareButtonPressed} title={loc.receive.details_share} />
|
||||
</BlueCard>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<BottomModal
|
||||
ref={bottomModalRef}
|
||||
contentContainerStyle={styles.modalContainerJustify}
|
||||
backgroundColor={colors.modal}
|
||||
footer={
|
||||
<View style={styles.modalButtonContainer}>
|
||||
<Button
|
||||
testID="CustomAmountResetButton"
|
||||
style={[styles.modalButton, stylesHook.modalButton]}
|
||||
title={loc.receive.reset}
|
||||
onPress={resetCustomAmount}
|
||||
/>
|
||||
<View style={styles.modalButtonSpacing} />
|
||||
<Button
|
||||
testID="CustomAmountSaveButton"
|
||||
style={[styles.modalButton, stylesHook.modalButton]}
|
||||
title={loc.receive.details_create}
|
||||
onPress={createCustomAmountAddress}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<AmountInput
|
||||
unit={tempCustomUnit}
|
||||
amount={tempCustomAmount || ''}
|
||||
onChangeText={setTempCustomAmount}
|
||||
onAmountUnitChange={setTempCustomUnit}
|
||||
/>
|
||||
<View style={[styles.customAmount, stylesHook.customAmount]}>
|
||||
<TextInput
|
||||
onChangeText={setTempCustomLabel}
|
||||
placeholderTextColor="#81868e"
|
||||
placeholder={loc.receive.details_label}
|
||||
value={tempCustomLabel || ''}
|
||||
numberOfLines={1}
|
||||
style={[styles.customAmountText, stylesHook.customAmountText]}
|
||||
testID="CustomAmountDescription"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{showAddress && renderTabContent()}
|
||||
{renderCustomAmountModal()}
|
||||
{address !== undefined && showAddress && (
|
||||
<HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />
|
||||
)}
|
||||
{showConfirmedBalance ? renderConfirmedBalance() : null}
|
||||
{showPendingBalance ? renderPendingBalance() : null}
|
||||
{!showAddress && !showPendingBalance && !showConfirmedBalance ? <BlueLoading /> : null}
|
||||
<View style={styles.share}>
|
||||
<BlueCard>
|
||||
{showAddress && currentTab === loc.wallets.details_address && (
|
||||
<BlueButtonLink
|
||||
style={styles.link}
|
||||
testID="SetCustomAmountButton"
|
||||
title={loc.receive.details_setAmount}
|
||||
onPress={showCustomAmountModal}
|
||||
/>
|
||||
)}
|
||||
<Button onPress={handleShareButtonPressed} title={loc.receive.details_share} />
|
||||
</BlueCard>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueSpacing20 />
|
||||
</BottomModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContent: {
|
||||
modalContainerJustify: {
|
||||
alignContent: 'center',
|
||||
padding: 22,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
minHeight: 350,
|
||||
height: 350,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
customAmount: {
|
||||
flexDirection: 'row',
|
||||
@ -570,6 +545,8 @@ const styles = StyleSheet.create({
|
||||
modalButtonContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 34,
|
||||
},
|
||||
modalButtonSpacing: {
|
||||
width: 16,
|
||||
|
@ -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[]>([]);
|
||||
@ -112,7 +112,7 @@ const SendDetails = () => {
|
||||
const [dumb, setDumb] = useState(false);
|
||||
const { isEditable } = routeParams;
|
||||
// if utxo is limited we use it to calculate available balance
|
||||
const balance: number = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : wallet?.getBalance() ?? 0;
|
||||
const balance: number = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : (wallet?.getBalance() ?? 0);
|
||||
const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true);
|
||||
|
||||
// if cutomFee is not set, we need to choose highest possible fee for wallet balance
|
||||
@ -388,6 +388,10 @@ const SendDetails = () => {
|
||||
useCallback(() => {
|
||||
setIsLoading(false);
|
||||
setDumb(v => !v);
|
||||
return () => {
|
||||
feeModalRef.current?.dismiss();
|
||||
optionsModalRef.current?.dismiss();
|
||||
};
|
||||
}, []),
|
||||
);
|
||||
|
||||
@ -609,11 +613,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 +637,8 @@ const SendDetails = () => {
|
||||
}
|
||||
|
||||
if (wallet?.type === MultisigHDWallet.type) {
|
||||
feeModalRef.current?.dismiss();
|
||||
optionsModalRef.current?.dismiss();
|
||||
navigation.navigate('PsbtMultisig', {
|
||||
memo: transactionMemo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
@ -652,6 +663,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 +698,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 +721,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 +747,7 @@ const SendDetails = () => {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const importTransaction = async () => {
|
||||
await 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 +768,7 @@ const SendDetails = () => {
|
||||
const txhex = psbt.extractTransaction().toHex();
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, txhex });
|
||||
setIsLoading(false);
|
||||
setOptionsVisible(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -762,7 +779,7 @@ const SendDetails = () => {
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, psbt });
|
||||
setIsLoading(false);
|
||||
setOptionsVisible(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -771,7 +788,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,13 +822,13 @@ const SendDetails = () => {
|
||||
};
|
||||
|
||||
const _importTransactionMultisig = async (base64arg: string | false) => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
try {
|
||||
const base64 = base64arg || (await fs.openSignedTransaction());
|
||||
if (!base64) return;
|
||||
const psbt = bitcoin.Psbt.fromBase64(base64); // if it doesnt throw - all good, its valid
|
||||
|
||||
if ((wallet as MultisigHDWallet)?.howManySignaturesCanWeMake() > 0 && (await askCosignThisTransaction())) {
|
||||
hideOptions();
|
||||
setIsLoading(true);
|
||||
await sleep(100);
|
||||
(wallet as MultisigHDWallet).cosignPsbt(psbt);
|
||||
@ -830,7 +847,6 @@ const SendDetails = () => {
|
||||
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
||||
}
|
||||
setIsLoading(false);
|
||||
setOptionsVisible(false);
|
||||
};
|
||||
|
||||
const importTransactionMultisig = () => {
|
||||
@ -851,8 +867,8 @@ const SendDetails = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const importTransactionMultisigScanQr = () => {
|
||||
setOptionsVisible(false);
|
||||
const importTransactionMultisigScanQr = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
requestCameraAuthorization().then(() => {
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
@ -865,9 +881,10 @@ const SendDetails = () => {
|
||||
};
|
||||
|
||||
const handleAddRecipient = async () => {
|
||||
console.log('handleAddRecipient');
|
||||
console.debug('handleAddRecipient');
|
||||
await optionsModalRef.current?.dismiss();
|
||||
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;
|
||||
@ -875,39 +892,41 @@ const SendDetails = () => {
|
||||
};
|
||||
|
||||
const handleRemoveRecipient = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
const last = scrollIndex.current === addresses.length - 1;
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setAddresses(addrs => {
|
||||
addrs.splice(scrollIndex.current, 1);
|
||||
return [...addrs];
|
||||
});
|
||||
setOptionsVisible(false);
|
||||
|
||||
if (addresses.length === 0) return;
|
||||
await sleep(200); // wait for animation
|
||||
scrollView.current?.flashScrollIndicators();
|
||||
if (last && Platform.OS === 'android') scrollView.current?.scrollToEnd(); // fix white screen on android
|
||||
};
|
||||
|
||||
const handleCoinControl = () => {
|
||||
const handleCoinControl = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
if (!wallet) return;
|
||||
setOptionsVisible(false);
|
||||
navigation.navigate('CoinControl', {
|
||||
walletID: wallet?.getID(),
|
||||
onUTXOChoose: (u: CreateTransactionUtxo[]) => setUtxo(u),
|
||||
});
|
||||
};
|
||||
|
||||
const handleInsertContact = () => {
|
||||
const handleInsertContact = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
if (!wallet) return;
|
||||
setOptionsVisible(false);
|
||||
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
|
||||
};
|
||||
|
||||
const handlePsbtSign = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
setIsLoading(true);
|
||||
setOptionsVisible(false);
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
|
||||
const scannedData = await scanQrHelper(name);
|
||||
|
||||
const scannedData = await scanQrHelper(name, true, undefined);
|
||||
if (!scannedData) return setIsLoading(false);
|
||||
|
||||
let tx;
|
||||
@ -945,11 +964,6 @@ const SendDetails = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const hideOptions = () => {
|
||||
Keyboard.dismiss();
|
||||
setOptionsVisible(false);
|
||||
};
|
||||
|
||||
// Header Right Button
|
||||
|
||||
const headerRightOnPress = (id: string) => {
|
||||
@ -1070,8 +1084,7 @@ const SendDetails = () => {
|
||||
disabled={isLoading}
|
||||
style={styles.advancedOptions}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss();
|
||||
setOptionsVisible(true);
|
||||
optionsModalRef.current?.present();
|
||||
}}
|
||||
testID="advancedOptionsMenuButton"
|
||||
>
|
||||
@ -1106,7 +1119,8 @@ const SendDetails = () => {
|
||||
scrollIndex.current = index;
|
||||
};
|
||||
|
||||
const onUseAllPressed = () => {
|
||||
const onUseAllPressed = async () => {
|
||||
await optionsModalRef.current?.dismiss();
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
|
||||
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure;
|
||||
Alert.alert(
|
||||
@ -1126,12 +1140,16 @@ const SendDetails = () => {
|
||||
u[scrollIndex.current] = BitcoinUnit.BTC;
|
||||
return [...u];
|
||||
});
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setOptionsVisible(false);
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {
|
||||
optionsModalRef.current?.present();
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
@ -1146,16 +1164,6 @@ const SendDetails = () => {
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.modal,
|
||||
borderTopColor: colors.borderTopColor,
|
||||
borderWidth: colors.borderWidth,
|
||||
},
|
||||
optionsContent: {
|
||||
backgroundColor: colors.modal,
|
||||
borderTopColor: colors.borderTopColor,
|
||||
borderWidth: colors.borderWidth,
|
||||
},
|
||||
feeModalItemActive: {
|
||||
backgroundColor: colors.feeActive,
|
||||
},
|
||||
@ -1237,42 +1245,18 @@ 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}
|
||||
backgroundColor={colors.modal}
|
||||
contentContainerStyle={[styles.modalContent, styles.modalContentMinHeight]}
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<View style={styles.feeModalFooter}>
|
||||
<TouchableOpacity
|
||||
testID="feeCustom"
|
||||
accessibilityRole="button"
|
||||
style={styles.feeModalCustom}
|
||||
onPress={async () => {
|
||||
await feeModalRef.current?.dismiss();
|
||||
let error = loc.send.fee_satvbyte;
|
||||
while (true) {
|
||||
let fee: number | string;
|
||||
@ -1291,7 +1275,6 @@ const SendDetails = () => {
|
||||
if (Number(fee) < 1) fee = '1';
|
||||
fee = Number(fee).toString(); // this will remove leading zeros if any
|
||||
setCustomFee(fee);
|
||||
setIsFeeSelectionModalVisible(false);
|
||||
return;
|
||||
}
|
||||
}}
|
||||
@ -1299,7 +1282,38 @@ const SendDetails = () => {
|
||||
<Text style={[styles.feeModalCustomText, stylesHook.feeModalCustomText]}>{loc.send.fee_custom}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
}
|
||||
>
|
||||
<View style={styles.paddingTop80}>
|
||||
{options.map(({ label, time, fee, rate, active, disabled }) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
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>
|
||||
))}
|
||||
</View>
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
@ -1308,56 +1322,52 @@ 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} backgroundColor={colors.modal} contentContainerStyle={styles.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 +1410,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 +1424,9 @@ const SendDetails = () => {
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
style={styles.selectTouch}
|
||||
onPress={() => navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN })}
|
||||
onPress={() => {
|
||||
navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN });
|
||||
}}
|
||||
disabled={!isEditable || isLoading}
|
||||
>
|
||||
<Text style={[styles.selectLabel, stylesHook.selectLabel]}>{wallet?.getLabel()}</Text>
|
||||
@ -1558,7 +1574,7 @@ const SendDetails = () => {
|
||||
<TouchableOpacity
|
||||
testID="chooseFee"
|
||||
accessibilityRole="button"
|
||||
onPress={() => setIsFeeSelectionModalVisible(true)}
|
||||
onPress={() => feeModalRef.current?.present()}
|
||||
disabled={isLoading}
|
||||
style={styles.fee}
|
||||
>
|
||||
@ -1640,16 +1656,12 @@ const styles = StyleSheet.create({
|
||||
right: 8,
|
||||
},
|
||||
modalContent: {
|
||||
padding: 22,
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
minHeight: 200,
|
||||
margin: 22,
|
||||
},
|
||||
modalContentMinHeight: Platform.OS === 'android' ? { minHeight: 400 } : {},
|
||||
paddingTop80: { paddingTop: 80 },
|
||||
optionsContent: {
|
||||
padding: 22,
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
minHeight: 130,
|
||||
},
|
||||
feeModalItem: {
|
||||
paddingHorizontal: 16,
|
||||
@ -1673,11 +1685,6 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
feeModalCustom: {
|
||||
height: 60,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
feeModalCustomText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
@ -1715,6 +1722,9 @@ const styles = StyleSheet.create({
|
||||
marginRight: 18,
|
||||
marginVertical: 8,
|
||||
},
|
||||
feeModalFooter: {
|
||||
paddingVertical: 50,
|
||||
},
|
||||
memo: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
LayoutAnimation,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
@ -29,6 +28,7 @@ import { useTheme } from '../../components/themes';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const FrozenBadge = () => {
|
||||
const { colors } = useTheme();
|
||||
@ -132,7 +132,7 @@ const OutputModal = ({ item: { address, txid, value, vout, confirmations = 0 },
|
||||
const amount = formatBalance(value, balanceUnit, true);
|
||||
|
||||
const oStyles = StyleSheet.create({
|
||||
container: { paddingHorizontal: 0, borderBottomColor: colors.lightBorder, backgroundColor: colors.elevated },
|
||||
container: { paddingHorizontal: 0, borderBottomColor: colors.lightBorder, backgroundColor: 'transparent' },
|
||||
avatar: { borderColor: 'white', borderWidth: 1, backgroundColor: color },
|
||||
amount: { fontWeight: 'bold', color: colors.foregroundColor },
|
||||
tranContainer: { paddingLeft: 20 },
|
||||
@ -194,9 +194,12 @@ const mStyles = StyleSheet.create({
|
||||
},
|
||||
buttonContainer: {
|
||||
height: 45,
|
||||
marginBottom: 36,
|
||||
marginHorizontal: 24,
|
||||
},
|
||||
});
|
||||
|
||||
const transparentBackground = { backgroundColor: 'transparent' };
|
||||
const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) => {
|
||||
const { colors } = useTheme();
|
||||
const { txMetadata, saveToDisk } = useStorage();
|
||||
@ -234,11 +237,12 @@ const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) =>
|
||||
]}
|
||||
onChangeText={onMemoChange}
|
||||
/>
|
||||
<ListItem title={loc.cc.freezeLabel} Component={TouchableWithoutFeedback} switch={switchValue} />
|
||||
<BlueSpacing20 />
|
||||
<View style={mStyles.buttonContainer}>
|
||||
<Button testID="UseCoin" title={loc.cc.use_coin} onPress={() => onUseCoin([output])} />
|
||||
</View>
|
||||
<ListItem
|
||||
title={loc.cc.freezeLabel}
|
||||
containerStyle={transparentBackground}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={switchValue}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
);
|
||||
@ -254,8 +258,9 @@ OutputModalContent.propTypes = {
|
||||
|
||||
const CoinControl = () => {
|
||||
const { colors } = useTheme();
|
||||
const navigation = useNavigation();
|
||||
const navigation = useExtendedNavigation();
|
||||
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);
|
||||
@ -323,7 +328,8 @@ const CoinControl = () => {
|
||||
|
||||
const handleChoose = item => setOutput(item);
|
||||
|
||||
const handleUseCoin = u => {
|
||||
const handleUseCoin = async u => {
|
||||
await bottomModalRef.current?.dismiss();
|
||||
setOutput(null);
|
||||
navigation.pop();
|
||||
onUTXOChoose(u);
|
||||
@ -386,6 +392,12 @@ const CoinControl = () => {
|
||||
return <OutputModalContent output={output} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (output) {
|
||||
bottomModalRef.current?.present();
|
||||
}
|
||||
}, [output]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}>
|
||||
@ -403,17 +415,22 @@ const CoinControl = () => {
|
||||
)}
|
||||
|
||||
<BottomModal
|
||||
isVisible={Boolean(output)}
|
||||
ref={bottomModalRef}
|
||||
onClose={() => {
|
||||
Keyboard.dismiss();
|
||||
setOutput(false);
|
||||
}}
|
||||
backgroundColor={colors.elevated}
|
||||
footer={
|
||||
<View style={mStyles.buttonContainer}>
|
||||
<Button testID="UseCoin" title={loc.cc.use_coin} onPress={() => handleUseCoin([output])} />
|
||||
</View>
|
||||
}
|
||||
footerDefaultMargins
|
||||
contentContainerStyle={[styles.modalContent, styles.modalMinHeight]}
|
||||
>
|
||||
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
|
||||
<View style={[styles.modalContent, { backgroundColor: colors.elevated }]}>{output && renderOutputModalContent()}</View>
|
||||
</KeyboardAvoidingView>
|
||||
{output && renderOutputModalContent()}
|
||||
</BottomModal>
|
||||
|
||||
<FlatList
|
||||
ListHeaderComponent={tipCoins}
|
||||
data={utxo}
|
||||
@ -454,11 +471,8 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
modalContent: {
|
||||
padding: 22,
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
modalMinHeight: Platform.OS === 'android' ? { minHeight: 500 } : {},
|
||||
empty: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
|
@ -60,7 +60,7 @@ const PsbtMultisigQRCode = () => {
|
||||
};
|
||||
|
||||
const openScanner = async () => {
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
const scanned = await scanQrHelper(name, true, undefined);
|
||||
onBarScanned({ data: scanned });
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
BlueText,
|
||||
} from '../../BlueComponents';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import presentAlert, { AlertType } from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
@ -205,7 +205,7 @@ export default class ElectrumSettings extends Component {
|
||||
}
|
||||
} catch (error) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: error });
|
||||
presentAlert({ message: error, type: AlertType.Toast });
|
||||
}
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueCard, BlueSpacing, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import presentAlert, { AlertType } from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
@ -101,7 +101,7 @@ export default class CPFP extends Component {
|
||||
} catch (error) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
this.setState({ isLoading: false });
|
||||
presentAlert({ message: error.message });
|
||||
presentAlert({ message: error.message, type: AlertType.Toast });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -143,7 +143,7 @@ const TransactionDetails = () => {
|
||||
// okay, this txid _was_ with someone using payment codes, so we show the label edit dialog
|
||||
// and load user-defined alias for the pc if any
|
||||
|
||||
setCounterpartyLabel(counterpartyMetadata ? counterpartyMetadata[foundPaymentCode]?.label ?? '' : '');
|
||||
setCounterpartyLabel(counterpartyMetadata ? (counterpartyMetadata[foundPaymentCode]?.label ?? '') : '');
|
||||
setIsCounterpartyLabelVisible(true);
|
||||
setPaymentCode(foundPaymentCode);
|
||||
}
|
||||
|
@ -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
|
||||
@ -92,9 +92,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
textDestination: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
exportButton: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
@ -164,10 +161,11 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
);
|
||||
|
||||
const saveFileButtonAfterOnPress = () => {
|
||||
setIsShareModalVisible(false);
|
||||
shareModalRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const onSave = async () => {
|
||||
dismissAllModals();
|
||||
if (!wallet) {
|
||||
throw new Error('Wallet is undefined');
|
||||
}
|
||||
@ -224,65 +222,63 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
}, [walletID]),
|
||||
);
|
||||
|
||||
const hideMnemonicsModal = () => {
|
||||
Keyboard.dismiss();
|
||||
setIsMnemonicsModalVisible(false);
|
||||
};
|
||||
|
||||
const renderMnemonicsModal = () => {
|
||||
return (
|
||||
<BottomModal isVisible={isMnemonicsModalVisible} onClose={hideMnemonicsModal} coverScreen={false}>
|
||||
<View style={[styles.newKeyModalContent, stylesHook.modalContent]}>
|
||||
<View style={styles.itemKeyUnprovidedWrapper}>
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextWrapper}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex })}
|
||||
</Text>
|
||||
</View>
|
||||
<BottomModal
|
||||
ref={mnemonicsModalRef}
|
||||
footerDefaultMargins
|
||||
backgroundColor={colors.elevated}
|
||||
contentContainerStyle={styles.newKeyModalContent}
|
||||
footer={
|
||||
<>
|
||||
<Button
|
||||
title={loc.multisig.share}
|
||||
onPress={() => {
|
||||
shareModalRef.current?.present();
|
||||
}}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<View style={styles.itemKeyUnprovidedWrapper}>
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextWrapper}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex })}
|
||||
</Text>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
{vaultKeyData.xpub.length > 1 && (
|
||||
<>
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.wallet_key}</Text>
|
||||
<BlueSpacing10 />
|
||||
<SquareEnumeratedWords
|
||||
contentAlign={SquareEnumeratedWordsContentAlign.left}
|
||||
entries={[vaultKeyData.xpub, vaultKeyData.fp, vaultKeyData.path]}
|
||||
appendNumber={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{vaultKeyData.seed.length > 1 && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.seed}</Text>
|
||||
<BlueSpacing10 />
|
||||
<SquareEnumeratedWords
|
||||
contentAlign={SquareEnumeratedWordsContentAlign.left}
|
||||
entries={vaultKeyData.seed.split(' ')}
|
||||
appendNumber
|
||||
/>
|
||||
{vaultKeyData.passphrase.length > 1 && (
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{vaultKeyData.passphrase}</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<Button
|
||||
title={loc.multisig.share}
|
||||
onPress={() => {
|
||||
setIsMnemonicsModalVisible(false);
|
||||
setTimeout(() => {
|
||||
setIsShareModalVisible(true);
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<Button title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
{vaultKeyData.xpub.length > 1 && (
|
||||
<>
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.wallet_key}</Text>
|
||||
<BlueSpacing10 />
|
||||
<SquareEnumeratedWords
|
||||
contentAlign={SquareEnumeratedWordsContentAlign.left}
|
||||
entries={[vaultKeyData.xpub, vaultKeyData.fp, vaultKeyData.path]}
|
||||
appendNumber={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{vaultKeyData.seed.length > 1 && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.seed}</Text>
|
||||
<BlueSpacing10 />
|
||||
<SquareEnumeratedWords
|
||||
contentAlign={SquareEnumeratedWordsContentAlign.left}
|
||||
entries={vaultKeyData.seed.split(' ')}
|
||||
appendNumber
|
||||
/>
|
||||
{vaultKeyData.passphrase.length > 1 && (
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{vaultKeyData.passphrase}</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{renderShareModal()}
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
@ -346,7 +342,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 +356,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 +385,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) {
|
||||
@ -407,7 +403,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
)}
|
||||
|
||||
<MultipleStepsListItem
|
||||
useActionSheet
|
||||
actionSheetOptions={{
|
||||
options: [loc._.cancel, loc.multisig.confirm],
|
||||
title: loc._.seed,
|
||||
@ -451,6 +446,11 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const dismissAllModals = () => {
|
||||
provideMnemonicsModalRef.current?.dismiss();
|
||||
shareModalRef.current?.dismiss();
|
||||
mnemonicsModalRef.current?.dismiss();
|
||||
};
|
||||
const handleUseMnemonicPhrase = async () => {
|
||||
let passphrase;
|
||||
if (askPassphrase) {
|
||||
@ -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,83 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
};
|
||||
|
||||
const scanOrOpenFile = async () => {
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
const scanned = await scanQrHelper(route.name, true);
|
||||
await provideMnemonicsModalRef.current?.dismiss();
|
||||
const scanned = await scanQrHelper(route.name, true, undefined);
|
||||
setImportText(String(scanned));
|
||||
setIsProvideMnemonicsModalVisible(true);
|
||||
provideMnemonicsModalRef.current?.present();
|
||||
};
|
||||
|
||||
const hideProvideMnemonicsModal = () => {
|
||||
Keyboard.dismiss();
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
provideMnemonicsModalRef.current?.dismiss();
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
};
|
||||
|
||||
const hideShareModal = () => {
|
||||
setIsShareModalVisible(false);
|
||||
};
|
||||
const hideShareModal = () => {};
|
||||
|
||||
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 />
|
||||
<BottomModal
|
||||
onClose={hideProvideMnemonicsModal}
|
||||
ref={provideMnemonicsModalRef}
|
||||
contentContainerStyle={styles.newKeyModalContent}
|
||||
backgroundColor={colors.elevated}
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<>
|
||||
{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>
|
||||
<>
|
||||
<BlueButtonLink ref={openScannerButtonRef} disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
<BottomModal
|
||||
ref={shareModalRef}
|
||||
onClose={hideShareModal}
|
||||
contentContainerStyle={[styles.modalContent, styles.alignItemsCenter, styles.shareModalHeight]}
|
||||
backgroundColor={colors.elevated}
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<SaveFileButton
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
fileContent={exportString}
|
||||
fileName={exportFilename}
|
||||
afterOnPress={saveFileButtonAfterOnPress}
|
||||
>
|
||||
<SquareButton title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
}
|
||||
>
|
||||
<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 />
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
@ -643,8 +651,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
|
||||
{renderProvideMnemonicsModal()}
|
||||
|
||||
{renderShareModal()}
|
||||
|
||||
{renderMnemonicsModal()}
|
||||
</View>
|
||||
);
|
||||
@ -661,20 +667,12 @@ const styles = StyleSheet.create({
|
||||
vaultKeyTextWrapper: { justifyContent: 'center', alignItems: 'center', paddingLeft: 16 },
|
||||
newKeyModalContent: {
|
||||
paddingHorizontal: 22,
|
||||
paddingVertical: 32,
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||
paddingTop: 32,
|
||||
minHeight: 370,
|
||||
},
|
||||
modalContent: {
|
||||
padding: 22,
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||
backgroundColor: 'white',
|
||||
minHeight: 400,
|
||||
},
|
||||
vaultKeyCircleSuccess: {
|
||||
width: 42,
|
||||
@ -686,14 +684,14 @@ const styles = StyleSheet.create({
|
||||
exportButton: {
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 32,
|
||||
},
|
||||
headerText: { fontSize: 15, color: '#13244D' },
|
||||
mainBlock: { marginHorizontal: 16 },
|
||||
alignItemsCenter: { alignItems: 'center' },
|
||||
squareButtonWrapper: { height: 50, width: 250 },
|
||||
alignItemsCenter: { alignItems: 'center', justifyContent: 'space-between' },
|
||||
shareModalHeight: { minHeight: 450 },
|
||||
tipKeys: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useRoute, RouteProp } from '@react-navigation/native';
|
||||
import LottieView from 'lottie-react-native';
|
||||
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';
|
||||
import { MultisigHDWallet } from '../../class';
|
||||
import BottomModal from '../../components/BottomModal';
|
||||
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
|
||||
import Button from '../../components/Button';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
@ -23,9 +23,9 @@ const WalletsAddMultisig: React.FC = () => {
|
||||
const { colors } = useTheme();
|
||||
const { navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const { walletLabel } = useRoute<RouteProps>().params;
|
||||
const bottomModalRef = useRef<BottomModalHandle>(null);
|
||||
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();
|
||||
|
||||
@ -38,16 +38,15 @@ const WalletsAddMultisig: React.FC = () => {
|
||||
textdesc: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
modalContentShort: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
textSubtitle: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
selectedItem: {
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
deSelectedItem: {
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
textHeader: {
|
||||
@ -56,6 +55,7 @@ const WalletsAddMultisig: React.FC = () => {
|
||||
});
|
||||
|
||||
const onLetsStartPress = () => {
|
||||
bottomModalRef.current?.dismiss();
|
||||
navigate('WalletsAddMultisigStep2', { m, n, format, walletLabel });
|
||||
};
|
||||
|
||||
@ -90,88 +90,78 @@ const WalletsAddMultisig: React.FC = () => {
|
||||
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
|
||||
sizes={['auto', 'large']}
|
||||
ref={bottomModalRef}
|
||||
contentContainerStyle={styles.modalContentShort}
|
||||
backgroundColor={colors.elevated}
|
||||
>
|
||||
<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: string) => {
|
||||
@ -246,13 +236,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,
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
I18nManager,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
LayoutAnimation,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@ -16,7 +14,6 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Icon } from '@rneui/themed';
|
||||
|
||||
import A from '../../blue_modules/analytics';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { encodeUR } from '../../blue_modules/ur';
|
||||
@ -35,11 +32,12 @@ import { SquareButton } from '../../components/SquareButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import confirm from '../../helpers/confirm';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const staticCache = {};
|
||||
|
||||
@ -48,15 +46,15 @@ const WalletsAddMultisigStep2 = () => {
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const navigation = useNavigation();
|
||||
const { navigate, navigateToWalletsList } = useExtendedNavigation();
|
||||
const { m, n, format, walletLabel } = useRoute().params;
|
||||
const { name } = useRoute();
|
||||
|
||||
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 +80,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('');
|
||||
}
|
||||
})();
|
||||
@ -90,8 +88,20 @@ const WalletsAddMultisigStep2 = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentSharedCosigner]);
|
||||
|
||||
const handleOnHelpPress = () => {
|
||||
navigation.navigate('WalletsAddMultisigHelp');
|
||||
const handleOnHelpPress = async () => {
|
||||
await dismissAllModals();
|
||||
navigate('WalletsAddMultisigHelp');
|
||||
};
|
||||
|
||||
const dismissAllModals = async () => {
|
||||
try {
|
||||
await mnemonicsModalRef.current?.dismiss();
|
||||
await provideMnemonicsModalRef.current?.dismiss();
|
||||
await renderCosignersXpubModalRef.current?.dismiss();
|
||||
} catch (e) {
|
||||
// in rare occasions trying to dismiss non visible modals can error out
|
||||
console.debug('dismissAllModals error', e);
|
||||
}
|
||||
};
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
@ -101,9 +111,6 @@ const WalletsAddMultisigStep2 = () => {
|
||||
textDestination: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.modal,
|
||||
},
|
||||
exportButton: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
@ -157,6 +164,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
w.setDerivationPath(MultisigHDWallet.PATH_LEGACY);
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
for (const cc of cosigners) {
|
||||
@ -172,7 +180,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
await saveToDisk();
|
||||
A(A.ENUM.CREATED_WALLET);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
navigation.getParent().goBack();
|
||||
navigateToWalletsList();
|
||||
};
|
||||
|
||||
const generateNewKey = () => {
|
||||
@ -184,8 +192,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());
|
||||
@ -209,6 +216,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
path = MultisigHDWallet.PATH_LEGACY;
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
return path;
|
||||
@ -219,7 +227,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 +236,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 +263,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 +302,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
}
|
||||
}
|
||||
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
@ -350,7 +358,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,8 +387,10 @@ 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);
|
||||
setImportText(ret.data);
|
||||
setTimeout(() => {
|
||||
provideMnemonicsModalRef.current.present().then(() => {});
|
||||
}, 100);
|
||||
} else {
|
||||
if (MultisigHDWallet.isXpubValid(ret.data) && !MultisigHDWallet.isXpubForMultisig(ret.data)) {
|
||||
return presentAlert({ message: loc.multisig.not_a_multisignature_xpub });
|
||||
@ -390,7 +400,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
|
||||
@ -416,6 +426,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
}
|
||||
@ -446,6 +457,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
|
||||
@ -458,12 +470,10 @@ const WalletsAddMultisigStep2 = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const scanOrOpenFile = () => {
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
onBarScanned({ data: scanned });
|
||||
});
|
||||
const scanOrOpenFile = async () => {
|
||||
await provideMnemonicsModalRef.current.dismiss();
|
||||
const scanned = await scanQrHelper(name, true, undefined);
|
||||
onBarScanned({ data: scanned });
|
||||
};
|
||||
|
||||
const dashType = ({ index, lastIndex, isChecked, isFocus }) => {
|
||||
@ -562,75 +572,94 @@ const WalletsAddMultisigStep2 = () => {
|
||||
|
||||
const renderMnemonicsModal = () => {
|
||||
return (
|
||||
<BottomModal isVisible={isMnemonicsModalVisible} onClose={Keyboard.dismiss}>
|
||||
<View style={[styles.newKeyModalContent, stylesHook.modalContent]}>
|
||||
<View style={styles.itemKeyUnprovidedWrapper}>
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextWrapper}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex })}
|
||||
</Text>
|
||||
</View>
|
||||
<BottomModal
|
||||
ref={mnemonicsModalRef}
|
||||
isGrabberVisible={false}
|
||||
dismissible={false}
|
||||
showCloseButton={!isLoading}
|
||||
footerDefaultMargins
|
||||
backgroundColor={colors.modal}
|
||||
contentContainerStyle={styles.newKeyModalContent}
|
||||
footer={
|
||||
<View style={styles.modalFooterBottomPadding}>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Button title={loc.send.success_done} onPress={() => mnemonicsModalRef.current.dismiss()} />
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View style={styles.itemKeyUnprovidedWrapper}>
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextWrapper}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex })}
|
||||
</Text>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.headerText, stylesHook.textDestination]}>{loc.multisig.wallet_key_created}</Text>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.seed}</Text>
|
||||
<BlueSpacing10 />
|
||||
<View style={styles.secretContainer}>{renderSecret(vaultKeyData.seed.split(' '))}</View>
|
||||
<BlueSpacing20 />
|
||||
{isLoading ? <ActivityIndicator /> : <Button title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />}
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.headerText, stylesHook.textDestination]}>{loc.multisig.wallet_key_created}</Text>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.textDestination, stylesHook.textDestination]}>{loc._.seed}</Text>
|
||||
<BlueSpacing10 />
|
||||
<View style={styles.secretContainer}>{renderSecret(vaultKeyData.seed.split(' '))}</View>
|
||||
<BlueSpacing20 />
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
||||
const hideProvideMnemonicsModal = () => {
|
||||
Keyboard.dismiss();
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
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 />
|
||||
<BottomModal
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<View style={styles.modalFooterBottomPadding}>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Button
|
||||
testID="DoImportKeyButton"
|
||||
disabled={importText.trim().length === 0}
|
||||
title={loc.wallets.import_do_import}
|
||||
onPress={useMnemonicPhrase}
|
||||
/>
|
||||
<>
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<BlueButtonLink
|
||||
testID="ScanOrOpenFile"
|
||||
ref={openScannerButton}
|
||||
disabled={isLoading}
|
||||
onPress={scanOrOpenFile}
|
||||
title={loc.wallets.import_scan_qr}
|
||||
/>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
}
|
||||
ref={provideMnemonicsModalRef}
|
||||
backgroundColor={colors.modal}
|
||||
isGrabberVisible={false}
|
||||
contentContainerStyle={styles.modalContent}
|
||||
onDismiss={() => {
|
||||
Keyboard.dismiss();
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
@ -645,37 +674,41 @@ 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}
|
||||
backgroundColor={colors.modal}
|
||||
footerDefaultMargins
|
||||
contentContainerStyle={[styles.modalContent, styles.alignItemsCenter]}
|
||||
footer={
|
||||
<View style={styles.modalFooterBottomPadding}>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<SaveFileButton
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
fileName={cosignerXpubFilename}
|
||||
fileContent={cosignerXpub}
|
||||
beforeOnPress={exportCosignerBeforeOnPress}
|
||||
afterOnPress={exportCosignerAfterOnPress}
|
||||
>
|
||||
<SquareButton title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
)}
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
}
|
||||
>
|
||||
<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 />
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
@ -740,20 +773,14 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 22,
|
||||
paddingVertical: 32,
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||
minHeight: 400,
|
||||
minHeight: 450,
|
||||
},
|
||||
newKeyModalContent: {
|
||||
paddingHorizontal: 22,
|
||||
paddingBottom: 60,
|
||||
paddingTop: 50,
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||
minHeight: 450,
|
||||
},
|
||||
modalFooterBottomPadding: { paddingBottom: 26 },
|
||||
vaultKeyCircleSuccess: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
@ -782,13 +809,11 @@ const styles = StyleSheet.create({
|
||||
exportButton: {
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
headerText: { fontSize: 15, color: '#13244D' },
|
||||
alignItemsCenter: { alignItems: 'center' },
|
||||
squareButtonWrapper: { height: 50, width: 250 },
|
||||
helpButtonWrapper: {
|
||||
alignItems: 'flex-end',
|
||||
flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
|
||||
|
@ -26,7 +26,7 @@ import * as fs from '../../blue_modules/fs';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
|
||||
import WalletGradient from '../../class/wallet-gradient';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import presentAlert, { AlertType } from '../../components/Alert';
|
||||
import { FButton, FContainer } from '../../components/FloatButtons';
|
||||
import LNNodeBar from '../../components/LNNodeBar';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
@ -241,7 +241,7 @@ const WalletTransactions = ({ navigation }) => {
|
||||
console.log(wallet.getLabel(), 'fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
presentAlert({ message: err.message });
|
||||
presentAlert({ message: err.message, type: AlertType.Toast });
|
||||
setIsLoading(false);
|
||||
setTimeElapsed(prev => prev + 1);
|
||||
}
|
||||
@ -303,7 +303,7 @@ const WalletTransactions = ({ navigation }) => {
|
||||
await wallet.fetchBtcAddress();
|
||||
toAddress = wallet.refill_addressess[0];
|
||||
} catch (Err) {
|
||||
return presentAlert({ message: Err.message });
|
||||
return presentAlert({ message: Err.message, type: AlertType.Toast });
|
||||
}
|
||||
}
|
||||
navigate('SendDetailsRoot', {
|
||||
|
@ -206,9 +206,11 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
await element(by.id('SetCustomAmountButton')).tap();
|
||||
await element(by.id('BitcoinAmountInput')).replaceText('1');
|
||||
await element(by.id('CustomAmountDescription')).typeText('test');
|
||||
await element(by.id('CustomAmountDescription')).tapReturnKey();
|
||||
await element(by.id('CustomAmountSaveButton')).tap();
|
||||
await sup('1 BTC');
|
||||
await sup('test');
|
||||
await expect(element(by.id('CustomAmountDescriptionText'))).toHaveText('test');
|
||||
await expect(element(by.id('BitcoinAmountText'))).toHaveText('1 BTC');
|
||||
|
||||
await yo('BitcoinAddressQRCodeContainer');
|
||||
await yo('CopyTextToClipboard');
|
||||
await device.pressBack();
|
||||
@ -575,6 +577,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('BitcoinAmountInput')).typeText('0.0005\n');
|
||||
await element(by.id('BitcoinAmountInput')).tapReturnKey();
|
||||
|
||||
// setting fee rate:
|
||||
const feeRate = 3;
|
||||
|
@ -501,7 +501,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
||||
await element(by.text('test1')).atIndex(0).tap();
|
||||
await element(by.id('OutputMemo')).replaceText('test2');
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // freeze switch
|
||||
await device.pressBack(); // closing modal
|
||||
await element(by.id('ModalDoneButton')).tap();
|
||||
await expect(element(by.text('test2')).atIndex(0)).toBeVisible();
|
||||
await expect(element(by.text('Freeze')).atIndex(0)).toBeVisible();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { hashIt, helperDeleteWallet, helperImportWallet, sleep, sup, yo } from './helperz';
|
||||
import { hashIt, helperDeleteWallet, helperImportWallet, sleep, yo } from './helperz';
|
||||
|
||||
beforeAll(async () => {
|
||||
// reinstalling the app just for any case to clean up app's storage
|
||||
@ -42,9 +42,11 @@ describe('BlueWallet UI Tests - import Watch-only wallet (zpub)', () => {
|
||||
await element(by.id('SetCustomAmountButton')).tap();
|
||||
await element(by.id('BitcoinAmountInput')).replaceText('1');
|
||||
await element(by.id('CustomAmountDescription')).typeText('Test');
|
||||
await element(by.id('CustomAmountDescription')).tapReturnKey();
|
||||
await element(by.id('CustomAmountSaveButton')).tap();
|
||||
await sup('1 BTC');
|
||||
await sup('Test');
|
||||
await expect(element(by.id('CustomAmountDescriptionText'))).toHaveText('Test');
|
||||
await expect(element(by.id('BitcoinAmountText'))).toHaveText('1 BTC');
|
||||
|
||||
await expect(element(by.id('BitcoinAddressQRCodeContainer'))).toBeVisible();
|
||||
|
||||
await expect(element(by.text('bitcoin:bc1qc8wun6lf9vcajpddtgdpd2pdrp0kwp29j6upgv?amount=1&label=Test'))).toBeVisible();
|
||||
|
@ -78,6 +78,8 @@ jest.mock('react-native-default-preference', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@lodev09/react-native-true-sheet');
|
||||
|
||||
jest.mock('react-native-fs', () => {
|
||||
return {
|
||||
mkdir: jest.fn(),
|
||||
|
Loading…
Reference in New Issue
Block a user