Merge branch 'master' into headr

This commit is contained in:
Marcos Rodriguez Velez 2025-02-26 00:48:33 -04:00
commit 2ee13dcf4f
24 changed files with 215 additions and 192 deletions

View file

@ -29,6 +29,20 @@ jobs:
with:
fetch-depth: 0 # Ensures the full Git history is available
- name: Clear All Caches
if: github.ref == 'refs/heads/master'
run: |
echo "Clearing Xcode DerivedData..."
rm -rf ~/Library/Developer/Xcode/DerivedData
echo "Clearing CocoaPods Cache..."
rm -rf ~/Library/Caches/CocoaPods
echo "Clearing npm Cache..."
npm cache clean --force
echo "Clearing Ruby Gems Cache..."
rm -rf ~/.gem
echo "Clearing Bundler Cache..."
rm -rf ~/.bundle/cache
- name: Ensure Correct Branch
if: github.ref != 'refs/heads/master'
run: |

View file

@ -83,7 +83,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "7.1.1"
versionName "7.1.3"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

View file

@ -10,16 +10,22 @@ import RNQRGenerator from 'rn-qr-generator';
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import { useSettings } from '../hooks/context/useSettings';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { isDesktop } from '../blue_modules/environment';
interface AddressInputScanButtonProps {
isLoading: boolean;
isLoading?: boolean;
onChangeText: (text: string) => void;
type?: 'default' | 'link';
testID?: string;
beforePress?: () => Promise<void> | void;
}
export const AddressInputScanButton = ({
isLoading,
onChangeText,
type = 'default',
testID = 'BlueAddressInputScanQrButton',
beforePress,
}: AddressInputScanButtonProps) => {
const { colors } = useTheme();
const { isClipboardGetContentEnabled } = useSettings();
@ -35,15 +41,17 @@ export const AddressInputScanButton = ({
});
const toolTipOnPress = useCallback(async () => {
if (beforePress) {
await beforePress();
}
Keyboard.dismiss();
navigation.navigate('ScanQRCode', {
showFileImportButton: true,
});
}, [navigation]);
}, [navigation, beforePress]);
const actions = useMemo(() => {
const availableActions = [
CommonToolTipActions.ScanQR,
CommonToolTipActions.ChoosePhoto,
CommonToolTipActions.ImportFile,
{
@ -135,21 +143,30 @@ export const AddressInputScanButton = ({
actions={actions}
isButton
onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton"
testID={testID}
disabled={isLoading}
onPress={toolTipOnPress}
buttonStyle={buttonStyle}
isMenuPrimaryAction={isDesktop}
buttonStyle={type === 'default' ? buttonStyle : undefined}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}
>
<Image source={require('../img/scan-white.png')} accessible={false} />
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
{type === 'default' ? (
<>
<Image source={require('../img/scan-white.png')} accessible={false} />
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
</>
) : (
<Text style={[styles.linkText, { color: colors.foregroundColor }]}>{loc.wallets.import_scan_qr}</Text>
)}
</ToolTipMenu>
);
};
AddressInputScanButton.displayName = 'AddressInputScanButton';
const styles = StyleSheet.create({
scan: {
height: 36,
@ -164,4 +181,8 @@ const styles = StyleSheet.create({
scanText: {
marginLeft: 4,
},
linkText: {
textAlign: 'center',
fontSize: 16,
},
});

View file

@ -246,8 +246,10 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
console.debug('[refreshAllWalletTransactions] Refresh already in progress');
return;
}
console.debug('[refreshAllWalletTransactions] Starting refreshAllWalletTransactions');
refreshingRef.current = true;
await new Promise<void>(resolve => InteractionManager.runAfterInteractions(() => resolve()));
const TIMEOUT_DURATION = 30000;
const timeoutPromise = new Promise<never>((_resolve, reject) =>
setTimeout(() => {
@ -292,12 +294,11 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
console.debug('[refreshAllWalletTransactions] Saving data to disk');
await saveToDisk();
})(),
timeoutPromise,
]);
console.debug('[refreshAllWalletTransactions] Refresh completed successfully');
} catch (error) {
console.error('[refreshAllWalletTransactions] Error in refreshAllWalletTransactions:', error);
console.error('[refreshAllWalletTransactions] Error:', error);
} finally {
console.debug('[refreshAllWalletTransactions] Resetting wallet transaction status and refresh lock');
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);

View file

@ -1,11 +1,11 @@
import React, { Ref, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { Platform, Pressable, TouchableOpacity } from 'react-native';
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
import { ContextMenuView, RenderItem, OnPressMenuItemEventObject, IconConfig, MenuElementConfig } from 'react-native-ios-context-menu';
import { ToolTipMenuProps, Action } from './types';
import { useSettings } from '../hooks/context/useSettings';
const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
const ToolTipMenu = (props: ToolTipMenuProps) => {
const {
title = '',
isMenuPrimaryAction = false,
@ -199,6 +199,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
};
return props.actions.length > 0 ? (Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView()) : null;
});
};
export default ToolTipMenu;

View file

@ -366,4 +366,4 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
</ToolTipMenu>
);
},
);
);

View file

@ -1,5 +1,6 @@
import { Platform } from 'react-native';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { navigationRef } from '../NavigationService.ts';
const isCameraAuthorizationStatusGranted = async () => {
const status = await check(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
@ -10,4 +11,17 @@ const requestCameraAuthorization = () => {
return request(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
};
export { isCameraAuthorizationStatusGranted, requestCameraAuthorization };
const scanQrHelper = async (): Promise<string> => {
await requestCameraAuthorization();
return new Promise(resolve => {
if (navigationRef.isReady()) {
navigationRef.current?.navigate('ScanQRCode', {
onBarScanned: (data: string) => {
resolve(data);
},
});
}
});
};
export { isCameraAuthorizationStatusGranted, requestCameraAuthorization, scanQrHelper };

View file

@ -55,9 +55,13 @@ const useHandoffListener = () => {
const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', handleUserActivity);
EventEmitter.getMostRecentUserActivity?.()
.then(handleUserActivity)
.catch(() => console.debug('No valid user activity object received'));
if (EventEmitter && EventEmitter.getMostRecentUserActivity) {
EventEmitter.getMostRecentUserActivity()
.then(handleUserActivity)
.catch(() => console.debug('No valid user activity object received'));
} else {
console.debug('EventEmitter native module is not available.');
}
return () => {
activitySubscription?.remove();

View file

@ -17,6 +17,7 @@ LogBox.ignoreLogs([
'Require cycle:',
'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.',
'Open debugger to view warnings.',
'Non-serializable values were found in the navigation state',
]);
const BlueAppComponent = () => {

View file

@ -1471,7 +1471,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1529,7 +1529,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1578,7 +1578,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1621,7 +1621,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
@ -1671,7 +1671,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1727,7 +1727,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1915,7 +1915,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1968,7 +1968,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -2014,7 +2014,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -2063,7 +2063,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 7.1.1;
MARKETING_VERSION = 7.1.3;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;

View file

@ -1652,7 +1652,7 @@ PODS:
- React-Core
- RNDefaultPreference (1.5.1):
- React-Core
- RNDeviceInfo (14.0.2):
- RNDeviceInfo (14.0.4):
- React-Core
- RNFS (2.20.0):
- React-Core
@ -1819,7 +1819,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (4.7.0):
- RNScreens (4.9.0):
- DoubleConversion
- glog
- hermes-engine
@ -1862,7 +1862,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNSVG (15.11.1):
- RNSVG (15.11.2):
- React-Core
- RNVectorIcons (10.2.0):
- DoubleConversion
@ -2300,7 +2300,7 @@ SPEC CHECKSUMS:
RNCClipboard: ee059e6006b137e369caed5eb852b4aad9f5d886
RNCPushNotificationIOS: 6c4ca3388c7434e4a662b92e4dfeeee858e6f440
RNDefaultPreference: 8a089ee8ce829a66c5453e3c5434f0785499d1c3
RNDeviceInfo: 801c18d0525e86580900e7b5f562dca0c8c05021
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
RNGestureHandler: 79e731e77dbec11fb5508e646d13897f1ee45912
RNHandoff: bc8af5a86853ff13b033e7ba1114c3c5b38e6385
@ -2312,9 +2312,9 @@ SPEC CHECKSUMS:
RNRate: 7641919330e0d6688ad885a985b4bd697ed7d14c
RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad
RNReanimated: 66cf0f600a26d2b5e74c6e0b1c77c1ab1f62fc05
RNScreens: 9a7346d6ce564a948e9d61cf9ec10950093e34df
RNScreens: ee069f569efb54804334321c916643f8cc9debaf
RNShare: 6204e6a1987ba3e7c47071ef703e5449a0e3548a
RNSVG: 86fecdfc637614ba9def63f7f3f2e7795e018356
RNSVG: a07e14363aa208062c6483bad24a438d5986d490
RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748

View file

@ -243,6 +243,7 @@
"electrum_connected": "Connected",
"electrum_connected_not": "Not Connected",
"electrum_error_connect": "Cannot connect to the provided Electrum server",
"electrum_error_connect_tor": "Cannot connect to the provided Electrum server. Please make sure Orbot app is connected and try again.",
"lndhub_uri": "E.g., {example}",
"electrum_host": "E.g., {example}",
"electrum_offline_mode": "Offline Mode",
@ -289,6 +290,7 @@
"language_isRTL": "Restarting BlueWallet is required for the language orientation to take effect.",
"license": "License",
"lightning_error_lndhub_uri": "Invalid LNDhub URI",
"lightning_error_lndhub_uri_tor": "Invalid LNDhub URI. Please make sure Orbot app is connected and try again.",
"lightning_saved": "Your changes have been saved successfully.",
"lightning_settings": "Lightning Settings",
"lightning_settings_explain": "To connect to your own LND node, please install LNDhub and put its URL here in settings. Please note that only wallets created after saving changes will connect to the specified LNDhub.",

View file

@ -32,8 +32,8 @@ export type DetailViewStackParamList = {
LNDViewInvoice: { invoice: LightningTransaction; walletID: string };
LNDViewAdditionalInvoiceInformation: { invoiceId: string };
LNDViewAdditionalInvoicePreImage: { invoiceId: string };
Broadcast: { onBarScanned?: string };
IsItMyAddress: { address?: string; onBarScanned?: string };
Broadcast: object;
IsItMyAddress: object;
GenerateWord: undefined;
LnurlPay: undefined;
LnurlPaySuccess: {

28
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "bluewallet",
"version": "7.1.1",
"version": "7.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bluewallet",
"version": "7.1.1",
"version": "7.1.3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@ -14,7 +14,7 @@
"@bugsnag/react-native": "8.2.0",
"@bugsnag/source-maps": "2.3.3",
"@keystonehq/bc-ur-registry": "0.7.0",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#0fefdd1aca07fcc3c204f6f1511f9fc0a2c59111",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37",
"@ngraveio/bc-ur": "1.1.13",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "2.1.0",
@ -92,10 +92,10 @@
"react-native-reanimated": "3.16.7",
"react-native-safe-area-context": "5.2.0",
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.7.0",
"react-native-screens": "4.9.1",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "11.1.0",
"react-native-svg": "15.11.1",
"react-native-svg": "15.11.2",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",
"react-native-watch-connectivity": "1.1.0",
@ -4699,9 +4699,9 @@
}
},
"node_modules/@lodev09/react-native-true-sheet": {
"version": "1.1.1",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#0fefdd1aca07fcc3c204f6f1511f9fc0a2c59111",
"integrity": "sha512-44sy92Jofy5dmKYpu9CYAHEHnEUnZPvy5bCSCvlg/2W3HlIUY2IKp81VzrFLf7XvDO7ZREqNsTt4QsaOH+GUnQ==",
"version": "2.0.0",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37",
"integrity": "sha512-KmqW5bBFowvbZ0d3u960PUx+avPZ80IaRPHszvarI5leeYrkTSUGqEeVVAQnaBHMfl5u6rrOVA4NoJHPsqU0Vg==",
"license": "MIT",
"workspaces": [
"example",
@ -22101,9 +22101,9 @@
}
},
"node_modules/react-native-screens": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.7.0.tgz",
"integrity": "sha512-PKBwBIKasBuaR6otU7GsUb9t5pb2eG1G9uHMHOivst/Iw1tXK+DDz1HSDQFjwcj2pUjrKSkXmwUtbY/oAvsCUA==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.9.1.tgz",
"integrity": "sha512-3pIOu1YXTDClQfkgk2t7rIr7unrV6bviaXRJsOq1APNHLeCPrJ6m5Gy3pW5Ty+XBm9jRAbMqhpjiXZIjesOR6A==",
"license": "MIT",
"dependencies": {
"react-freeze": "^1.0.0",
@ -22139,9 +22139,9 @@
}
},
"node_modules/react-native-svg": {
"version": "15.11.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.1.tgz",
"integrity": "sha512-Qmwx/yJKt+AHUr4zjxx/Q69qwKtRfr1+uIfFMQoq3WFRhqU76aL9db1DyvPiY632DAsVGba1pHf92OZPkpjrdQ==",
"version": "15.11.2",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz",
"integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==",
"license": "MIT",
"dependencies": {
"css-select": "^5.1.0",

View file

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "7.1.1",
"version": "7.1.3",
"license": "MIT",
"repository": {
"type": "git",
@ -82,7 +82,7 @@
"@bugsnag/react-native": "8.2.0",
"@bugsnag/source-maps": "2.3.3",
"@keystonehq/bc-ur-registry": "0.7.0",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#0fefdd1aca07fcc3c204f6f1511f9fc0a2c59111",
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37",
"@ngraveio/bc-ur": "1.1.13",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "2.1.0",
@ -160,10 +160,10 @@
"@react-navigation/devtools": "7.0.15",
"react-native-safe-area-context": "5.2.0",
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.7.0",
"react-native-screens": "4.9.1",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "11.1.0",
"react-native-svg": "15.11.1",
"react-native-svg": "15.11.2",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",
"react-native-watch-connectivity": "1.1.0",

View file

@ -1,5 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useRoute, RouteProp } from '@react-navigation/native';
import React, { useCallback, useState } from 'react';
import * as bitcoin from 'bitcoinjs-lib';
import { ActivityIndicator, Keyboard, Linking, StyleSheet, TextInput, View } from 'react-native';
@ -20,11 +19,9 @@ import Button from '../../components/Button';
import SafeArea from '../../components/SafeArea';
import { useTheme } from '../../components/themes';
import loc from '../../loc';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useSettings } from '../../hooks/context/useSettings';
import { majorTomToGroundControl } from '../../blue_modules/notifications';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { scanQrHelper } from '../../helpers/scan-qr.ts';
const BROADCAST_RESULT = Object.freeze({
none: 'Input transaction hex',
@ -33,17 +30,12 @@ const BROADCAST_RESULT = Object.freeze({
error: 'error',
});
type RouteProps = RouteProp<DetailViewStackParamList, 'Broadcast'>;
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList, 'Broadcast'>;
const Broadcast: React.FC = () => {
const { params } = useRoute<RouteProps>();
const [tx, setTx] = useState<string | undefined>();
const [txHex, setTxHex] = useState<string | undefined>();
const { colors } = useTheme();
const [broadcastResult, setBroadcastResult] = useState<string>(BROADCAST_RESULT.none);
const { selectedBlockExplorer } = useSettings();
const { setParams, navigate } = useExtendedNavigation<NavigationProps>();
const stylesHooks = StyleSheet.create({
input: {
@ -66,14 +58,6 @@ const Broadcast: React.FC = () => {
} catch (e) {}
}, []);
useEffect(() => {
const scannedData = params?.onBarScanned;
if (scannedData) {
handleScannedData(scannedData);
setParams({ onBarScanned: undefined });
}
}, [handleScannedData, params?.onBarScanned, setParams]);
const handleUpdateTxHex = (nextValue: string) => setTxHex(nextValue.trim());
const handleBroadcast = async () => {
@ -104,10 +88,11 @@ const Broadcast: React.FC = () => {
}
};
const handleQRScan = () => {
navigate('ScanQRCode', {
showFileImportButton: true,
});
const handleQRScan = async () => {
const scannedData = await scanQrHelper();
if (scannedData) {
handleScannedData(scannedData);
}
};
let status;

View file

@ -64,7 +64,7 @@ const ScanQRCode = () => {
const previousRoute = navigationState.routes[navigationState.routes.length - 2];
const defaultLaunchedBy = previousRoute ? previousRoute.name : undefined;
const { launchedBy = defaultLaunchedBy, showFileImportButton } = route.params || {};
const { launchedBy = defaultLaunchedBy, showFileImportButton, onBarScanned } = route.params || {};
const scannedCache: Record<string, number> = {};
const { colors } = useTheme();
const isFocused = useIsFocused();
@ -116,6 +116,9 @@ const ScanQRCode = () => {
if (launchedBy) {
const merge = true;
const popToAction = StackActions.popTo(launchedBy, { onBarScanned: data }, merge);
if (onBarScanned) {
onBarScanned(data);
}
navigation.dispatch(popToAction);
}
@ -156,6 +159,9 @@ const ScanQRCode = () => {
if (launchedBy) {
const merge = true;
const popToAction = StackActions.popTo(launchedBy, { onBarScanned: data }, merge);
if (onBarScanned) {
onBarScanned(data);
}
navigation.dispatch(popToAction);
}
@ -212,6 +218,9 @@ const ScanQRCode = () => {
if (launchedBy) {
const merge = true;
const popToAction = StackActions.popTo(launchedBy, { onBarScanned: data }, merge);
if (onBarScanned) {
onBarScanned(data);
}
navigation.dispatch(popToAction);
}
return;
@ -222,6 +231,9 @@ const ScanQRCode = () => {
const merge = true;
const popToAction = StackActions.popTo(launchedBy, { onBarScanned: ret.data }, merge);
if (onBarScanned) {
onBarScanned(ret.data);
}
navigation.dispatch(popToAction);
} catch (e) {
@ -286,7 +298,7 @@ const ScanQRCode = () => {
<BlueSpacing40 />
{showFileImportButton && <Button title={loc.wallets.import_file} onPress={showFilePicker} />}
<BlueSpacing40 />
<Button title={loc.wallets.list_long_choose} onPress={showFilePicker} />
<Button title={loc.wallets.list_long_choose} onPress={onShowImagePickerButtonPress} />
<BlueSpacing40 />
<Button title={loc._.cancel} onPress={dismiss} />
</View>

View file

@ -52,6 +52,11 @@ const ElectrumSettings: React.FC = () => {
const [isAndroidNumericKeyboardFocused, setIsAndroidNumericKeyboardFocused] = useState(false);
const [isAndroidAddressKeyboardVisible, setIsAndroidAddressKeyboardVisible] = useState(false);
const { setIsElectrumDisabled, isElectrumDisabled } = useSettings();
const [savedServer, setSavedServer] = useState<{ host: string; tcp: string; ssl: string }>({
host: '',
tcp: '',
ssl: '',
});
const stylesHook = StyleSheet.create({
inputWrap: {
@ -127,6 +132,12 @@ const ElectrumSettings: React.FC = () => {
setConfig(await BlueElectrum.getConfig());
}, 500);
setSavedServer({
host: savedHost || '',
tcp: savedPort ? savedPort.toString() : '',
ssl: savedSslPort ? savedSslPort.toString() : '',
});
setIsLoading(false);
return () => {
@ -174,7 +185,11 @@ const ElectrumSettings: React.FC = () => {
if (serverHost && (serverPort || serverSslPort)) {
const testConnect = await BlueElectrum.testConnection(serverHost, Number(serverPort), Number(serverSslPort));
if (!testConnect) return;
if (!testConnect) {
return presentAlert({
message: serverHost.endsWith('.onion') ? loc.settings.electrum_error_connect_tor : loc.settings.electrum_error_connect,
});
}
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
// Clear current data for the preferred host
@ -454,6 +469,11 @@ const ElectrumSettings: React.FC = () => {
};
const preferredServerIsEmpty = !host || (!port && !sslPort);
const saveDisabled: boolean =
preferredServerIsEmpty ||
(host === savedServer.host &&
((savedServer.tcp !== '' && port?.toString() === savedServer.tcp) ||
(savedServer.ssl !== '' && sslPort?.toString() === savedServer.ssl)));
const renderElectrumSettings = () => {
return (
@ -541,13 +561,7 @@ const ElectrumSettings: React.FC = () => {
</BlueCard>
<BlueCard>
<BlueSpacing20 />
<Button
showActivityIndicator={isLoading}
disabled={isLoading || preferredServerIsEmpty}
testID="Save"
onPress={save}
title={loc.settings.save}
/>
<Button disabled={saveDisabled} testID="Save" onPress={save} title={loc.settings.save} />
</BlueCard>
{Platform.select({
@ -597,6 +611,7 @@ const ElectrumSettings: React.FC = () => {
onValueChange: onElectrumConnectionEnabledSwitchChange,
value: isElectrumDisabled,
testID: 'ElectrumConnectionEnabledSwitch',
disabled: isLoading,
}}
disabled={isLoading}
bottomDivider={false}

View file

@ -1,5 +1,4 @@
import React, { useRef, useState, useEffect } from 'react';
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
import { Keyboard, StyleSheet, TextInput, View, ScrollView, TouchableOpacity, Text } from 'react-native';
import { BlueButtonLink, BlueCard, BlueSpacing10, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
import Button from '../../components/Button';
@ -8,23 +7,17 @@ import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import { TWallet } from '../../class/wallets/types';
import { WalletCarouselItem } from '../../components/WalletsCarousel';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { Divider } from '@rneui/themed';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import presentAlert from '../../components/Alert';
import { navigate } from '../../NavigationService';
type RouteProps = RouteProp<DetailViewStackParamList, 'IsItMyAddress'>;
type NavigationProp = NativeStackNavigationProp<DetailViewStackParamList, 'IsItMyAddress'>;
import { scanQrHelper } from '../../helpers/scan-qr.ts';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation.ts';
const IsItMyAddress: React.FC = () => {
const { navigate } = useExtendedNavigation();
const { wallets } = useStorage();
const navigation = useNavigation<NavigationProp>();
const route = useRoute<RouteProps>();
const { colors } = useTheme();
const scanButtonRef = useRef<any>();
const scrollViewRef = useRef<ScrollView>(null);
const firstWalletRef = useRef<View>(null);
@ -40,20 +33,6 @@ const IsItMyAddress: React.FC = () => {
},
});
useEffect(() => {
if (route.params?.address && route.params.address !== address) {
setAddress(route.params.address);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [route.params?.address]);
useEffect(() => {
const currentAddress = route.params?.address;
if (currentAddress !== address) {
navigation.setParams({ address });
}
}, [address, navigation, route.params?.address]);
const handleUpdateAddress = (nextValue: string) => setAddress(nextValue);
const clearAddressInput = () => {
@ -102,27 +81,16 @@ const IsItMyAddress: React.FC = () => {
}
};
const onBarScanned = (value: string) => {
const importScan = async () => {
const value = await scanQrHelper();
const cleanAddress = value.replace(/^bitcoin(:|=)/i, '').split('?')[0];
setAddress(value);
setResultCleanAddress(cleanAddress);
};
const importScan = async () => {
navigate('ScanQRCode');
};
useEffect(() => {
const data = route.params?.onBarScanned;
if (data) {
onBarScanned(data);
navigation.setParams({ onBarScanned: undefined });
}
}, [navigation, route.name, route.params?.onBarScanned]);
const viewQRCode = () => {
if (!resultCleanAddress) return;
navigation.navigate('ReceiveDetailsRoot', {
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: {
address: resultCleanAddress,
@ -195,7 +163,7 @@ const IsItMyAddress: React.FC = () => {
</View>
<BlueSpacing10 />
<BlueButtonLink ref={scanButtonRef} title={loc.wallets.import_scan_qr} onPress={importScan} />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} />
<BlueSpacing20 />
{resultCleanAddress && (
<>
@ -227,7 +195,7 @@ const IsItMyAddress: React.FC = () => {
<WalletCarouselItem
item={wallet}
onPress={item => {
navigation.navigate('WalletTransactions', {
navigate('WalletTransactions', {
walletID: item.getID(),
walletType: item.type,
});

View file

@ -80,10 +80,11 @@ const LightningSettings: React.FC = () => {
};
const save = useCallback(async () => {
setIsLoading(true);
let normalizedURI;
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
if (URI) {
const normalizedURI = new URL(URI.replace(/([^:]\/)\/+/g, '$1')).toString();
normalizedURI = new URL(URI.replace(/([^:]\/)\/+/g, '$1')).toString();
await LightningCustodianWallet.isValidNodeAddress(normalizedURI);
await setLNDHub(normalizedURI);
@ -95,7 +96,9 @@ const LightningSettings: React.FC = () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.settings.lightning_error_lndhub_uri });
presentAlert({
message: normalizedURI?.endsWith('.onion') ? loc.settings.lightning_error_lndhub_uri_tor : loc.settings.lightning_error_lndhub_uri,
});
console.log(error);
}
setIsLoading(false);

View file

@ -3,7 +3,7 @@ import { RouteProp, useRoute } from '@react-navigation/native';
import Clipboard from '@react-native-clipboard/clipboard';
import { Keyboard, Platform, ScrollView, StyleSheet, TouchableWithoutFeedback, View, TouchableOpacity, Image } from 'react-native';
import { disallowScreenshot } from 'react-native-screen-capture';
import { BlueButtonLink, BlueFormLabel, BlueFormMultiInput, BlueSpacing20 } from '../../BlueComponents';
import { BlueFormLabel, BlueFormMultiInput, BlueSpacing20 } from '../../BlueComponents';
import Button from '../../components/Button';
import {
DoneAndDismissKeyboardInputAccessory,
@ -19,6 +19,7 @@ import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { isDesktop } from '../../blue_modules/environment';
import { AddressInputScanButton } from '../../components/AddressInputScanButton';
type RouteProps = RouteProp<AddWalletStackParamList, 'ImportWallet'>;
type NavigationProps = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWallet'>;
@ -109,12 +110,6 @@ const ImportWallet = () => {
[importMnemonic],
);
const importScan = useCallback(async () => {
navigation.navigate('ScanQRCode', {
showFileImportButton: true,
});
}, [navigation]);
useEffect(() => {
const data = route.params?.onBarScanned;
if (data) {
@ -199,7 +194,7 @@ const ImportWallet = () => {
<>
<Button disabled={importText.trim().length === 0} title={loc.wallets.import_do_import} testID="DoImport" onPress={handleImport} />
<BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} testID="ScanImport" />
<AddressInputScanButton type="link" onChangeText={setImportText} testID="ScanImport" />
</>
</View>
</>

View file

@ -18,15 +18,7 @@ import {
import { Badge, Icon } from '@rneui/themed';
import { isDesktop } from '../../blue_modules/environment';
import { encodeUR } from '../../blue_modules/ur';
import {
BlueButtonLink,
BlueCard,
BlueFormMultiInput,
BlueLoading,
BlueSpacing10,
BlueSpacing20,
BlueTextCentered,
} from '../../BlueComponents';
import { BlueCard, BlueFormMultiInput, BlueLoading, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import presentAlert from '../../components/Alert';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
@ -52,6 +44,7 @@ import { ViewEditMultisigCosignersStackParamList } from '../../navigation/ViewEd
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import SafeArea from '../../components/SafeArea';
import { TWallet } from '../../class/wallets/types';
import { AddressInputScanButton } from '../../components/AddressInputScanButton';
type RouteParams = RouteProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>;
type NavigationProp = NativeStackNavigationProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>;
@ -62,8 +55,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
const { wallets, setWalletsWithNewOrder } = useStorage();
const { isBiometricUseCapableAndEnabled } = useBiometrics();
const { isElectrumDisabled, isPrivacyBlurEnabled } = useSettings();
const { navigate, dispatch, setParams, setOptions } = useExtendedNavigation<NavigationProp>();
const openScannerButtonRef = useRef();
const { dispatch, setParams, setOptions } = useExtendedNavigation<NavigationProp>();
const route = useRoute<RouteParams>();
const { walletID } = route.params;
const w = useRef(wallets.find(wallet => wallet.getID() === walletID));
@ -516,11 +508,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
});
};
const scanOrOpenFile = async () => {
await provideMnemonicsModalRef.current?.dismiss();
navigate('ScanQRCode', { showFileImportButton: true });
};
useEffect(() => {
const scannedData = route.params.onBarScanned;
if (scannedData) {
@ -573,11 +560,13 @@ const ViewEditMultisigCosigners: React.FC = () => {
{!isLoading && (
<>
<BlueButtonLink
ref={openScannerButtonRef}
disabled={isLoading}
onPress={scanOrOpenFile}
title={loc.wallets.import_scan_qr}
<AddressInputScanButton
beforePress={async () => {
await provideMnemonicsModalRef.current?.dismiss();
}}
isLoading={isLoading}
type="link"
onChangeText={setImportText}
/>
<BlueSpacing20 />
</>

View file

@ -122,27 +122,32 @@ const WalletsList: React.FC = () => {
},
});
/**
* Forcefully fetches TXs and balance for ALL wallets.
* Triggered manually by user on pull-to-refresh.
*/
const refreshTransactions = useCallback(
async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
if (isElectrumDisabled) {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
return;
}
const refreshWallets = useCallback(
async (index: number | undefined, showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
if (isElectrumDisabled) return;
dispatch({ type: ActionTypes.SET_LOADING, payload: showLoadingIndicator });
refreshAllWalletTransactions(undefined, showUpdateStatusIndicator).finally(() => {
try {
await refreshAllWalletTransactions(index, showUpdateStatusIndicator);
} catch (error) {
console.error(error);
} finally {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
});
}
},
[isElectrumDisabled, refreshAllWalletTransactions],
);
/**
* Forcefully fetches TXs and balance for ALL wallets.
* Triggered manually by user on pull-to-refresh.
*/
const refreshTransactions = useCallback(() => {
refreshWallets(undefined, true, false);
}, [refreshWallets]);
const onRefresh = useCallback(() => {
console.debug('WalletsList onRefresh');
refreshTransactions(true, false);
refreshTransactions();
// Optimized for Mac option doesn't like RN Refresh component. Menu Elements now handles it for macOS
}, [refreshTransactions]);
@ -200,7 +205,7 @@ const WalletsList: React.FC = () => {
}, [navigation, onBarScanned, route.params?.onBarScanned]);
useEffect(() => {
refreshTransactions(false, true);
refreshTransactions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -219,10 +224,6 @@ const WalletsList: React.FC = () => {
[navigation],
);
const setIsLoading = useCallback((value: boolean) => {
dispatch({ type: ActionTypes.SET_LOADING, payload: value });
}, []);
const onSnapToItem = useCallback(
(e: { nativeEvent: { contentOffset: any } }) => {
if (!isFocused) return;
@ -233,12 +234,12 @@ const WalletsList: React.FC = () => {
if (currentWalletIndex.current !== index) {
console.debug('onSnapToItem', wallets.length === index ? 'NewWallet/Importing card' : index);
if (wallets[index] && (wallets[index].timeToRefreshBalance() || wallets[index].timeToRefreshTransaction())) {
refreshAllWalletTransactions(index, false).finally(() => setIsLoading(false));
refreshWallets(index, false, false);
}
currentWalletIndex.current = index;
}
},
[isFocused, refreshAllWalletTransactions, setIsLoading, wallets, width],
[isFocused, refreshWallets, wallets, width],
);
const renderListHeaderComponent = useCallback(() => {

View file

@ -17,7 +17,7 @@ import { Icon } from '@rneui/themed';
import A from '../../blue_modules/analytics';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { encodeUR } from '../../blue_modules/ur';
import { BlueButtonLink, BlueFormMultiInput, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import { BlueFormMultiInput, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import presentAlert from '../../components/Alert';
import BottomModal from '../../components/BottomModal';
@ -44,6 +44,7 @@ import MultipleStepsListItem, {
MultipleStepsListItemButtonType,
MultipleStepsListItemDashType,
} from '../../components/MultipleStepsListItem';
import { AddressInputScanButton } from '../../components/AddressInputScanButton';
const staticCache = {};
@ -65,7 +66,6 @@ const WalletsAddMultisigStep2 = () => {
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', isLoading: false }); // string rendered in modal
const [importText, setImportText] = useState('');
const [askPassphrase, setAskPassphrase] = useState(false);
const openScannerButton = useRef();
const { isPrivacyBlurEnabled } = useSettings();
const data = useRef(new Array(n));
const { isVisible } = useKeyboard();
@ -433,11 +433,6 @@ const WalletsAddMultisigStep2 = () => {
[cosigners, format, getXpubCacheForMnemonics, tryUsingXpub],
);
const scanOrOpenFile = async () => {
await provideMnemonicsModalRef.current.dismiss();
navigation.navigate('ScanQRCode', { showFileImportButton: true });
};
const utilizeMnemonicPhrase = useCallback(async () => {
try {
await provideMnemonicsModalRef.current.dismiss();
@ -659,12 +654,15 @@ const WalletsAddMultisigStep2 = () => {
onPress={utilizeMnemonicPhrase}
/>
<View style={styles.height16} />
<BlueButtonLink
<AddressInputScanButton
beforePress={async () => {
await provideMnemonicsModalRef.current.dismiss();
}}
onBarScanned={onBarScanned}
testID="ScanOrOpenFile"
ref={openScannerButton}
type="link"
disabled={isLoading}
onPress={scanOrOpenFile}
title={loc.wallets.import_scan_qr}
/>
</>
)}