Merge branch 'master' into wallrtd
|
@ -34,7 +34,7 @@ jobs:
|
|||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 15.4
|
||||
xcode-version: 16.0
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
@ -127,14 +127,14 @@ jobs:
|
|||
|
||||
- name: Expected IPA file name
|
||||
run: |
|
||||
echo "IPA file name: BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa"
|
||||
echo "IPA file name: BlueWallet_${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa"
|
||||
- name: Build App
|
||||
run: bundle exec fastlane ios build_app_lane
|
||||
- name: Upload IPA as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa
|
||||
path: ./build/BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa
|
||||
name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
|
||||
path: ./build/BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
|
||||
|
||||
testflight-upload:
|
||||
needs: build
|
||||
|
@ -167,7 +167,7 @@ jobs:
|
|||
- name: Download IPA from Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: BlueWallet.${{ needs.build.outputs.project_version }}(${{ needs.build.outputs.new_build_number }}).ipa
|
||||
name: BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa
|
||||
path: ./
|
||||
- name: Create App Store Connect API Key JSON
|
||||
run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import { Alert } from 'react-native';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import RNFS from 'react-native-fs';
|
||||
import Realm from 'realm';
|
||||
|
@ -299,13 +298,13 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
);
|
||||
return;
|
||||
}
|
||||
Alert.alert(
|
||||
loc.errors.network,
|
||||
loc.formatString(
|
||||
presentAlert({
|
||||
title: loc.errors.network,
|
||||
message: loc.formatString(
|
||||
usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect,
|
||||
usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {},
|
||||
),
|
||||
[
|
||||
buttons: [
|
||||
{
|
||||
text: loc.wallets.list_tryagain,
|
||||
onPress: () => {
|
||||
|
@ -318,10 +317,10 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
{
|
||||
text: loc.settings.electrum_reset,
|
||||
onPress: () => {
|
||||
Alert.alert(
|
||||
loc.settings.electrum_reset,
|
||||
loc.settings.electrum_reset_to_default,
|
||||
[
|
||||
presentAlert({
|
||||
title: loc.settings.electrum_reset,
|
||||
message: loc.settings.electrum_reset_to_default,
|
||||
buttons: [
|
||||
{
|
||||
text: loc._.cancel,
|
||||
style: 'cancel',
|
||||
|
@ -340,16 +339,15 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||
} catch (e) {
|
||||
// Must be running on Android
|
||||
console.log(e);
|
||||
console.log(e); // Must be running on Android
|
||||
}
|
||||
presentAlert({ message: loc.settings.electrum_saved });
|
||||
setTimeout(connectMain, 500);
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
options: { cancelable: true },
|
||||
});
|
||||
connectionAttempt = 0;
|
||||
mainClient.close() && mainClient.close();
|
||||
},
|
||||
|
@ -364,8 +362,8 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
options: { cancelable: false },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,8 +31,6 @@ function Notifications(props) {
|
|||
return false;
|
||||
};
|
||||
|
||||
Notifications.isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
|
||||
|
||||
/**
|
||||
* Calls `configure`, which tries to obtain push token, save it, and registers all associated with
|
||||
* notifications callbacks
|
||||
|
@ -131,7 +129,7 @@ function Notifications(props) {
|
|||
* @returns {Promise<boolean>} TRUE if permissions were obtained, FALSE otherwise
|
||||
*/
|
||||
Notifications.tryToObtainPermissions = async function (anchor) {
|
||||
if (!Notifications.isNotificationsCapable) return false;
|
||||
if (!isNotificationsCapable) return false;
|
||||
if (await Notifications.getPushToken()) {
|
||||
// we already have a token, no sense asking again, just configure pushes to register callbacks and we are done
|
||||
if (!alreadyConfigured) configureNotifications(); // no await so it executes in background while we return TRUE and use token
|
||||
|
@ -441,4 +439,6 @@ function Notifications(props) {
|
|||
return null;
|
||||
}
|
||||
|
||||
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
|
||||
|
||||
export default Notifications;
|
||||
|
|
|
@ -72,7 +72,7 @@ const AddressInput = ({
|
|||
const toolTipOnPress = useCallback(async () => {
|
||||
await scanButtonTapped();
|
||||
Keyboard.dismiss();
|
||||
if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value }));
|
||||
if (launchedBy) scanQrHelper(launchedBy, true).then(value => onBarScanned({ data: value }));
|
||||
}, [launchedBy, onBarScanned, scanButtonTapped]);
|
||||
|
||||
const onMenuItemPressed = useCallback(
|
||||
|
|
|
@ -1,35 +1,92 @@
|
|||
import { Alert as RNAlert, Platform, ToastAndroid } from 'react-native';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
import loc from '../loc';
|
||||
|
||||
export enum AlertType {
|
||||
Alert,
|
||||
Toast,
|
||||
}
|
||||
const presentAlert = ({
|
||||
title,
|
||||
message,
|
||||
type = AlertType.Alert,
|
||||
hapticFeedback,
|
||||
}: {
|
||||
title?: string;
|
||||
message: string;
|
||||
type?: AlertType;
|
||||
hapticFeedback?: HapticFeedbackTypes;
|
||||
}) => {
|
||||
if (hapticFeedback) {
|
||||
triggerHapticFeedback(hapticFeedback);
|
||||
}
|
||||
|
||||
if (Platform.OS !== 'android') {
|
||||
type = AlertType.Alert;
|
||||
}
|
||||
switch (type) {
|
||||
case AlertType.Toast:
|
||||
ToastAndroid.show(message, ToastAndroid.LONG);
|
||||
break;
|
||||
default:
|
||||
RNAlert.alert(title ?? message, title && message ? message : undefined);
|
||||
break;
|
||||
}
|
||||
};
|
||||
interface AlertButton {
|
||||
text: string;
|
||||
onPress?: () => void;
|
||||
style?: 'default' | 'cancel' | 'destructive';
|
||||
}
|
||||
|
||||
interface AlertOptions {
|
||||
cancelable?: boolean;
|
||||
}
|
||||
|
||||
const presentAlert = (() => {
|
||||
let lastAlertParams: {
|
||||
title?: string;
|
||||
message: string;
|
||||
type?: AlertType;
|
||||
hapticFeedback?: HapticFeedbackTypes;
|
||||
buttons?: AlertButton[];
|
||||
options?: AlertOptions;
|
||||
} | null = null;
|
||||
|
||||
const clearCache = () => {
|
||||
lastAlertParams = null;
|
||||
};
|
||||
|
||||
return ({
|
||||
title,
|
||||
message,
|
||||
type = AlertType.Alert,
|
||||
hapticFeedback,
|
||||
buttons = [],
|
||||
options = { cancelable: false },
|
||||
}: {
|
||||
title?: string;
|
||||
message: string;
|
||||
type?: AlertType;
|
||||
hapticFeedback?: HapticFeedbackTypes;
|
||||
buttons?: AlertButton[];
|
||||
options?: AlertOptions;
|
||||
}) => {
|
||||
if (
|
||||
lastAlertParams &&
|
||||
lastAlertParams.title === title &&
|
||||
lastAlertParams.message === message &&
|
||||
lastAlertParams.type === type &&
|
||||
lastAlertParams.hapticFeedback === hapticFeedback &&
|
||||
JSON.stringify(lastAlertParams.buttons) === JSON.stringify(buttons) &&
|
||||
JSON.stringify(lastAlertParams.options) === JSON.stringify(options)
|
||||
) {
|
||||
return; // Skip showing the alert if the content is the same as the last one
|
||||
}
|
||||
|
||||
lastAlertParams = { title, message, type, hapticFeedback, buttons, options };
|
||||
|
||||
if (hapticFeedback) {
|
||||
triggerHapticFeedback(hapticFeedback);
|
||||
}
|
||||
|
||||
// Ensure that there's at least one button (required for both iOS and Android)
|
||||
const wrappedButtons =
|
||||
buttons.length > 0
|
||||
? buttons
|
||||
: [
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
switch (type) {
|
||||
case AlertType.Toast:
|
||||
if (Platform.OS === 'android') {
|
||||
ToastAndroid.show(message, ToastAndroid.LONG);
|
||||
clearCache();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RNAlert.alert(title ?? message, title && message ? message : undefined, wrappedButtons, options);
|
||||
break;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
export default presentAlert;
|
||||
|
|
|
@ -362,7 +362,7 @@ const styles = StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
margin: 16,
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useStorage } from '../../hooks/context/useStorage';
|
|||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { TotalWalletsBalanceKey, TotalWalletsBalancePreferredUnit } from '../TotalWalletsBalance';
|
||||
import { LayoutAnimation } from 'react-native';
|
||||
import { BLOCK_EXPLORERS, getBlockExplorerUrl, saveBlockExplorer, BlockExplorer, normalizeUrl } from '../../models/blockExplorer';
|
||||
|
||||
// DefaultPreference and AsyncStorage get/set
|
||||
|
||||
|
@ -85,6 +86,8 @@ interface SettingsContextType {
|
|||
setTotalBalancePreferredUnitStorage: (unit: BitcoinUnit) => Promise<void>;
|
||||
isDrawerShouldHide: boolean;
|
||||
setIsDrawerShouldHide: (value: boolean) => void;
|
||||
selectedBlockExplorer: BlockExplorer;
|
||||
setBlockExplorerStorage: (explorer: BlockExplorer) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const defaultSettingsContext: SettingsContextType = {
|
||||
|
@ -112,6 +115,8 @@ const defaultSettingsContext: SettingsContextType = {
|
|||
setTotalBalancePreferredUnitStorage: async (unit: BitcoinUnit) => {},
|
||||
isDrawerShouldHide: false,
|
||||
setIsDrawerShouldHide: () => {},
|
||||
selectedBlockExplorer: BLOCK_EXPLORERS.default,
|
||||
setBlockExplorerStorage: async (explorer: BlockExplorer) => false,
|
||||
};
|
||||
|
||||
export const SettingsContext = createContext<SettingsContextType>(defaultSettingsContext);
|
||||
|
@ -142,6 +147,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
// Toggle Drawer (for screens like Manage Wallets or ScanQRCode)
|
||||
const [isDrawerShouldHide, setIsDrawerShouldHide] = useState<boolean>(false);
|
||||
|
||||
const [selectedBlockExplorer, setSelectedBlockExplorer] = useState<BlockExplorer>(BLOCK_EXPLORERS.default);
|
||||
|
||||
const languageStorage = useAsyncStorage(STORAGE_KEY);
|
||||
const { walletsInitialized } = useStorage();
|
||||
|
||||
|
@ -211,6 +218,18 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setTotalBalancePreferredUnitState(unit);
|
||||
})
|
||||
.catch(error => console.error('Error fetching total balance preferred unit:', error));
|
||||
getBlockExplorerUrl()
|
||||
.then(url => {
|
||||
console.debug('SettingsContext blockExplorer:', url);
|
||||
const predefinedExplorer = Object.values(BLOCK_EXPLORERS).find(explorer => normalizeUrl(explorer.url) === normalizeUrl(url));
|
||||
if (predefinedExplorer) {
|
||||
setSelectedBlockExplorer(predefinedExplorer);
|
||||
} else {
|
||||
setSelectedBlockExplorer({ key: 'custom', name: 'Custom', url });
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching block explorer settings:', error));
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
@ -295,6 +314,13 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setTotalBalancePreferredUnitState(unit);
|
||||
}, []);
|
||||
|
||||
const setBlockExplorerStorage = useCallback(async (explorer: BlockExplorer): Promise<boolean> => {
|
||||
const success = await saveBlockExplorer(explorer.url);
|
||||
if (success) {
|
||||
setSelectedBlockExplorer(explorer);
|
||||
}
|
||||
return success;
|
||||
}, []);
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
preferredFiatCurrency,
|
||||
|
@ -321,6 +347,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setTotalBalancePreferredUnitStorage,
|
||||
isDrawerShouldHide,
|
||||
setIsDrawerShouldHide,
|
||||
selectedBlockExplorer,
|
||||
setBlockExplorerStorage,
|
||||
}),
|
||||
[
|
||||
preferredFiatCurrency,
|
||||
|
@ -347,6 +375,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setTotalBalancePreferredUnitStorage,
|
||||
isDrawerShouldHide,
|
||||
setIsDrawerShouldHide,
|
||||
selectedBlockExplorer,
|
||||
setBlockExplorerStorage,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -59,28 +59,16 @@ export enum WalletTransactionsStatus {
|
|||
NONE = 'NONE',
|
||||
ALL = 'ALL',
|
||||
}
|
||||
// @ts-ignore defaut value does not match the type
|
||||
|
||||
// @ts-ignore default value does not match the type
|
||||
export const StorageContext = createContext<StorageContextType>(undefined);
|
||||
|
||||
export const StorageProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const txMetadata = useRef<TTXMetadata>(BlueApp.tx_metadata);
|
||||
const counterpartyMetadata = useRef<TCounterpartyMetadata>(BlueApp.counterparty_metadata || {}); // init
|
||||
const getTransactions = BlueApp.getTransactions;
|
||||
const fetchWalletBalances = BlueApp.fetchWalletBalances;
|
||||
const fetchWalletTransactions = BlueApp.fetchWalletTransactions;
|
||||
const getBalance = BlueApp.getBalance;
|
||||
const isStorageEncrypted = BlueApp.storageIsEncrypted;
|
||||
const encryptStorage = BlueApp.encryptStorage;
|
||||
const sleep = BlueApp.sleep;
|
||||
const createFakeStorage = BlueApp.createFakeStorage;
|
||||
const decryptStorage = BlueApp.decryptStorage;
|
||||
const isPasswordInUse = BlueApp.isPasswordInUse;
|
||||
const cachedPassword = BlueApp.cachedPassword;
|
||||
|
||||
const getItem = BlueApp.getItem;
|
||||
const setItem = BlueApp.setItem;
|
||||
|
||||
const [wallets, setWallets] = useState<TWallet[]>([]);
|
||||
const [selectedWalletID, setSelectedWalletID] = useState<undefined | string>();
|
||||
const [selectedWalletID, setSelectedWalletID] = useState<string | undefined>();
|
||||
const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState<WalletTransactionsStatus | string>(
|
||||
WalletTransactionsStatus.NONE,
|
||||
);
|
||||
|
@ -89,6 +77,47 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
const [currentSharedCosigner, setCurrentSharedCosigner] = useState<string>('');
|
||||
const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState<() => void>(() => {});
|
||||
|
||||
const saveToDisk = useCallback(
|
||||
async (force: boolean = false) => {
|
||||
if (!force && BlueApp.getWallets().length === 0) {
|
||||
console.debug('Not saving empty wallets array');
|
||||
return;
|
||||
}
|
||||
await InteractionManager.runAfterInteractions(async () => {
|
||||
BlueApp.tx_metadata = txMetadata.current;
|
||||
BlueApp.counterparty_metadata = counterpartyMetadata.current;
|
||||
await BlueApp.saveToDisk();
|
||||
const w: TWallet[] = [...BlueApp.getWallets()];
|
||||
setWallets(w);
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[txMetadata.current, counterpartyMetadata.current],
|
||||
);
|
||||
|
||||
const addWallet = useCallback((wallet: TWallet) => {
|
||||
BlueApp.wallets.push(wallet);
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
}, []);
|
||||
|
||||
const deleteWallet = useCallback((wallet: TWallet) => {
|
||||
BlueApp.deleteWallet(wallet);
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
}, []);
|
||||
|
||||
const resetWallets = useCallback(() => {
|
||||
setWallets(BlueApp.getWallets());
|
||||
}, []);
|
||||
|
||||
const setWalletsWithNewOrder = useCallback(
|
||||
(wlts: TWallet[]) => {
|
||||
BlueApp.wallets = wlts;
|
||||
saveToDisk();
|
||||
},
|
||||
[saveToDisk],
|
||||
);
|
||||
|
||||
// Initialize wallets and connect to Electrum
|
||||
useEffect(() => {
|
||||
BlueElectrum.isDisabled().then(setIsElectrumDisabled);
|
||||
if (walletsInitialized) {
|
||||
|
@ -99,33 +128,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
}
|
||||
}, [walletsInitialized]);
|
||||
|
||||
const saveToDisk = useCallback(async (force: boolean = false) => {
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
if (BlueApp.getWallets().length === 0 && !force) {
|
||||
console.log('not saving empty wallets array');
|
||||
return;
|
||||
}
|
||||
BlueApp.tx_metadata = txMetadata.current;
|
||||
BlueApp.counterparty_metadata = counterpartyMetadata.current;
|
||||
await BlueApp.saveToDisk();
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
txMetadata.current = BlueApp.tx_metadata;
|
||||
counterpartyMetadata.current = BlueApp.counterparty_metadata;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resetWallets = () => {
|
||||
setWallets(BlueApp.getWallets());
|
||||
};
|
||||
|
||||
const setWalletsWithNewOrder = useCallback(
|
||||
(wlts: TWallet[]) => {
|
||||
BlueApp.wallets = wlts;
|
||||
saveToDisk();
|
||||
},
|
||||
[saveToDisk],
|
||||
);
|
||||
|
||||
const refreshAllWalletTransactions = useCallback(
|
||||
async (lastSnappedTo?: number, showUpdateStatusIndicator: boolean = true) => {
|
||||
const TIMEOUT_DURATION = 30000;
|
||||
|
@ -137,41 +139,37 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
);
|
||||
|
||||
const mainLogicPromise = new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
let noErr = true;
|
||||
try {
|
||||
await BlueElectrum.waitTillConnected();
|
||||
if (showUpdateStatusIndicator) {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
|
||||
}
|
||||
const paymentCodesStart = Date.now();
|
||||
await BlueApp.fetchSenderPaymentCodes(lastSnappedTo);
|
||||
const paymentCodesEnd = Date.now();
|
||||
console.log('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec');
|
||||
const balanceStart = +new Date();
|
||||
await fetchWalletBalances(lastSnappedTo);
|
||||
const balanceEnd = +new Date();
|
||||
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
const start = +new Date();
|
||||
await fetchWalletTransactions(lastSnappedTo);
|
||||
const end = +new Date();
|
||||
console.log('fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
console.warn(err);
|
||||
reject(err);
|
||||
} finally {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
let noErr = true;
|
||||
try {
|
||||
await BlueElectrum.waitTillConnected();
|
||||
if (showUpdateStatusIndicator) {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
|
||||
}
|
||||
if (noErr) await saveToDisk(); // caching
|
||||
resolve();
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
|
||||
}
|
||||
const paymentCodesStart = Date.now();
|
||||
await BlueApp.fetchSenderPaymentCodes(lastSnappedTo);
|
||||
const paymentCodesEnd = Date.now();
|
||||
console.debug('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec');
|
||||
|
||||
const balanceStart = Date.now();
|
||||
await BlueApp.fetchWalletBalances(lastSnappedTo);
|
||||
const balanceEnd = Date.now();
|
||||
console.debug('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
|
||||
const start = Date.now();
|
||||
await BlueApp.fetchWalletTransactions(lastSnappedTo);
|
||||
const end = Date.now();
|
||||
console.debug('fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
console.error(err);
|
||||
reject(err);
|
||||
} finally {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
|
||||
}
|
||||
if (noErr) await saveToDisk();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -182,53 +180,43 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
|
||||
}
|
||||
},
|
||||
[fetchWalletBalances, fetchWalletTransactions, saveToDisk],
|
||||
[saveToDisk],
|
||||
);
|
||||
|
||||
const fetchAndSaveWalletTransactions = useCallback(
|
||||
async (walletID: string) => {
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
await InteractionManager.runAfterInteractions(async () => {
|
||||
const index = wallets.findIndex(wallet => wallet.getID() === walletID);
|
||||
let noErr = true;
|
||||
try {
|
||||
// 5sec debounce:
|
||||
if (+new Date() - _lastTimeTriedToRefetchWallet[walletID] < 5000) {
|
||||
console.log('re-fetch wallet happens too fast; NOP');
|
||||
if (Date.now() - (_lastTimeTriedToRefetchWallet[walletID] || 0) < 5000) {
|
||||
console.debug('Re-fetch wallet happens too fast; NOP');
|
||||
return;
|
||||
}
|
||||
_lastTimeTriedToRefetchWallet[walletID] = +new Date();
|
||||
_lastTimeTriedToRefetchWallet[walletID] = Date.now();
|
||||
|
||||
await BlueElectrum.waitTillConnected();
|
||||
setWalletTransactionUpdateStatus(walletID);
|
||||
const balanceStart = +new Date();
|
||||
await fetchWalletBalances(index);
|
||||
const balanceEnd = +new Date();
|
||||
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
const start = +new Date();
|
||||
await fetchWalletTransactions(index);
|
||||
const end = +new Date();
|
||||
console.log('fetch tx took', (end - start) / 1000, 'sec');
|
||||
const balanceStart = Date.now();
|
||||
await BlueApp.fetchWalletBalances(index);
|
||||
const balanceEnd = Date.now();
|
||||
console.debug('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
const start = Date.now();
|
||||
await BlueApp.fetchWalletTransactions(index);
|
||||
const end = Date.now();
|
||||
console.debug('fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
console.warn(err);
|
||||
console.error(err);
|
||||
} finally {
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
|
||||
}
|
||||
if (noErr) await saveToDisk(); // caching
|
||||
if (noErr) await saveToDisk();
|
||||
});
|
||||
},
|
||||
[fetchWalletBalances, fetchWalletTransactions, saveToDisk, wallets],
|
||||
[saveToDisk, wallets],
|
||||
);
|
||||
|
||||
const addWallet = useCallback((wallet: TWallet) => {
|
||||
BlueApp.wallets.push(wallet);
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
}, []);
|
||||
|
||||
const deleteWallet = useCallback((wallet: TWallet) => {
|
||||
BlueApp.deleteWallet(wallet);
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
}, []);
|
||||
|
||||
const addAndSaveWallet = useCallback(
|
||||
async (w: TWallet) => {
|
||||
if (wallets.some(i => i.getID() === w.getID())) {
|
||||
|
@ -247,13 +235,12 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
hapticFeedback: HapticFeedbackTypes.ImpactHeavy,
|
||||
message: w.type === WatchOnlyWallet.type ? loc.wallets.import_success_watchonly : loc.wallets.import_success,
|
||||
});
|
||||
// @ts-ignore need to type notifications first
|
||||
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []);
|
||||
// start balance fetching at the background
|
||||
await w.fetchBalance();
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
},
|
||||
[addWallet, saveToDisk, wallets],
|
||||
[wallets, addWallet, saveToDisk],
|
||||
);
|
||||
|
||||
const value: StorageContextType = useMemo(
|
||||
|
@ -263,7 +250,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
txMetadata: txMetadata.current,
|
||||
counterpartyMetadata: counterpartyMetadata.current,
|
||||
saveToDisk,
|
||||
getTransactions,
|
||||
getTransactions: BlueApp.getTransactions,
|
||||
selectedWalletID,
|
||||
setSelectedWalletID,
|
||||
addWallet,
|
||||
|
@ -271,24 +258,24 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
currentSharedCosigner,
|
||||
setSharedCosigner: setCurrentSharedCosigner,
|
||||
addAndSaveWallet,
|
||||
setItem,
|
||||
getItem,
|
||||
fetchWalletBalances,
|
||||
fetchWalletTransactions,
|
||||
setItem: BlueApp.setItem,
|
||||
getItem: BlueApp.getItem,
|
||||
fetchWalletBalances: BlueApp.fetchWalletBalances,
|
||||
fetchWalletTransactions: BlueApp.fetchWalletTransactions,
|
||||
fetchAndSaveWalletTransactions,
|
||||
isStorageEncrypted,
|
||||
encryptStorage,
|
||||
isStorageEncrypted: BlueApp.storageIsEncrypted,
|
||||
encryptStorage: BlueApp.encryptStorage,
|
||||
startAndDecrypt,
|
||||
cachedPassword,
|
||||
getBalance,
|
||||
cachedPassword: BlueApp.cachedPassword,
|
||||
getBalance: BlueApp.getBalance,
|
||||
walletsInitialized,
|
||||
setWalletsInitialized,
|
||||
refreshAllWalletTransactions,
|
||||
sleep,
|
||||
createFakeStorage,
|
||||
sleep: BlueApp.sleep,
|
||||
createFakeStorage: BlueApp.createFakeStorage,
|
||||
resetWallets,
|
||||
decryptStorage,
|
||||
isPasswordInUse,
|
||||
decryptStorage: BlueApp.decryptStorage,
|
||||
isPasswordInUse: BlueApp.isPasswordInUse,
|
||||
walletTransactionUpdateStatus,
|
||||
setWalletTransactionUpdateStatus,
|
||||
isElectrumDisabled,
|
||||
|
@ -300,30 +287,23 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
|||
wallets,
|
||||
setWalletsWithNewOrder,
|
||||
saveToDisk,
|
||||
getTransactions,
|
||||
selectedWalletID,
|
||||
setSelectedWalletID,
|
||||
addWallet,
|
||||
deleteWallet,
|
||||
currentSharedCosigner,
|
||||
addAndSaveWallet,
|
||||
setItem,
|
||||
getItem,
|
||||
fetchWalletBalances,
|
||||
fetchWalletTransactions,
|
||||
fetchAndSaveWalletTransactions,
|
||||
isStorageEncrypted,
|
||||
encryptStorage,
|
||||
cachedPassword,
|
||||
getBalance,
|
||||
walletsInitialized,
|
||||
setWalletsInitialized,
|
||||
refreshAllWalletTransactions,
|
||||
sleep,
|
||||
createFakeStorage,
|
||||
decryptStorage,
|
||||
isPasswordInUse,
|
||||
resetWallets,
|
||||
walletTransactionUpdateStatus,
|
||||
setWalletTransactionUpdateStatus,
|
||||
isElectrumDisabled,
|
||||
setIsElectrumDisabled,
|
||||
reloadTransactionsMenuActionFunction,
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
31
components/HeaderMenuButton.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import { useTheme } from './themes';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { Platform } from 'react-native';
|
||||
import { Action } from './types';
|
||||
|
||||
interface HeaderMenuButtonProps {
|
||||
onPressMenuItem: (id: string) => void;
|
||||
actions: Action[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const HeaderMenuButton: React.FC<HeaderMenuButtonProps> = ({ onPressMenuItem, actions, disabled }) => {
|
||||
const { colors } = useTheme();
|
||||
const styleProps = Platform.OS === 'android' ? { iconStyle: { transform: [{ rotate: '90deg' }] } } : {};
|
||||
return (
|
||||
<ToolTipMenu
|
||||
testID="HeaderMenuButton"
|
||||
disabled={disabled}
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={onPressMenuItem}
|
||||
actions={actions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} {...styleProps} />
|
||||
</ToolTipMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMenuButton;
|
|
@ -98,6 +98,8 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
|||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
color: colors.alternativeTextColor,
|
||||
fontWeight: '400',
|
||||
paddingVertical: switchProps ? 8 : 0,
|
||||
lineHeight: 20,
|
||||
fontSize: 14,
|
||||
},
|
||||
|
||||
|
@ -130,7 +132,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
|||
</RNElementsListItem.Title>
|
||||
{subtitle && (
|
||||
<RNElementsListItem.Subtitle
|
||||
numberOfLines={subtitleNumberOfLines ?? 1}
|
||||
numberOfLines={switchProps ? 0 : (subtitleNumberOfLines ?? 1)}
|
||||
accessible={switchProps === undefined}
|
||||
style={stylesHook.subtitle}
|
||||
>
|
||||
|
@ -152,7 +154,9 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
|||
<>
|
||||
{chevron && <RNElementsListItem.Chevron iconStyle={{ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }} />}
|
||||
{rightIcon && <Avatar icon={rightIcon} />}
|
||||
{switchProps && <Switch {...memoizedSwitchProps} accessibilityLabel={title} accessible accessibilityRole="switch" />}
|
||||
{switchProps && (
|
||||
<Switch {...memoizedSwitchProps} accessibilityLabel={title} style={styles.margin16} accessible accessibilityRole="switch" />
|
||||
)}
|
||||
{checkmark && (
|
||||
<RNElementsListItem.CheckBox
|
||||
iconRight
|
||||
|
@ -216,5 +220,8 @@ const styles = StyleSheet.create({
|
|||
margin8: {
|
||||
margin: 8,
|
||||
},
|
||||
margin16: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
width16: { width: 16 },
|
||||
});
|
||||
|
|
|
@ -101,6 +101,8 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
|||
<View
|
||||
style={styles.qrCodeContainer}
|
||||
testID="BitcoinAddressQRCodeContainer"
|
||||
accessibilityIgnoresInvertColors
|
||||
importantForAccessibility="no-hide-descendants"
|
||||
accessibilityRole="image"
|
||||
accessibilityLabel={loc.receive.qrcode_for_the_address}
|
||||
>
|
||||
|
|
89
components/SettingsBlockExplorerCustomUrlListItem.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, TextInput, View, Switch } from 'react-native';
|
||||
import { ListItem } from '@rneui/themed';
|
||||
import { useTheme } from './themes';
|
||||
import loc from '../loc';
|
||||
|
||||
interface SettingsBlockExplorerCustomUrlItemProps {
|
||||
isCustomEnabled: boolean;
|
||||
onSwitchToggle: (value: boolean) => void;
|
||||
customUrl: string;
|
||||
onCustomUrlChange: (url: string) => void;
|
||||
onSubmitCustomUrl: () => void;
|
||||
inputRef?: React.RefObject<TextInput>;
|
||||
}
|
||||
|
||||
const SettingsBlockExplorerCustomUrlItem: React.FC<SettingsBlockExplorerCustomUrlItemProps> = ({
|
||||
isCustomEnabled,
|
||||
onSwitchToggle,
|
||||
customUrl,
|
||||
onCustomUrlChange,
|
||||
onSubmitCustomUrl,
|
||||
inputRef,
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem containerStyle={[styles.container, { backgroundColor: colors.background }]} bottomDivider>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title style={[styles.title, { color: colors.text }]}>{loc.settings.block_explorer_preferred}</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
<Switch
|
||||
accessible
|
||||
accessibilityRole="switch"
|
||||
accessibilityState={{ checked: isCustomEnabled }}
|
||||
onValueChange={onSwitchToggle}
|
||||
value={isCustomEnabled}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
{isCustomEnabled && (
|
||||
<View style={[styles.uriContainer, { borderColor: colors.formBorder, backgroundColor: colors.inputBackgroundColor }]}>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={customUrl}
|
||||
placeholder={loc._.enter_url}
|
||||
onChangeText={onCustomUrlChange}
|
||||
numberOfLines={1}
|
||||
style={[styles.uriText, { color: colors.text }]}
|
||||
placeholderTextColor={colors.placeholderTextColor}
|
||||
textContentType="URL"
|
||||
clearButtonMode="while-editing"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
underlineColorAndroid="transparent"
|
||||
onSubmitEditing={onSubmitCustomUrl}
|
||||
editable={isCustomEnabled}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsBlockExplorerCustomUrlItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
minHeight: 60,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
uriContainer: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 15,
|
||||
marginVertical: 10,
|
||||
paddingHorizontal: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
uriText: {
|
||||
flex: 1,
|
||||
minHeight: 36,
|
||||
},
|
||||
});
|
|
@ -43,7 +43,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
const { navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const menuRef = useRef<ToolTipMenuProps>();
|
||||
const { txMetadata, counterpartyMetadata, wallets } = useStorage();
|
||||
const { language } = useSettings();
|
||||
const { language, selectedBlockExplorer } = useSettings();
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
backgroundColor: 'transparent',
|
||||
|
@ -253,16 +253,16 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
|
||||
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle ?? ''), [subtitle]);
|
||||
const handleOnViewOnBlockExplorer = useCallback(() => {
|
||||
const url = `https://mempool.space/tx/${item.hash}`;
|
||||
const url = `${selectedBlockExplorer}/tx/${item.hash}`;
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
});
|
||||
}, [item.hash]);
|
||||
}, [item.hash, selectedBlockExplorer]);
|
||||
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
|
||||
Clipboard.setString(`https://mempool.space/tx/${item.hash}`);
|
||||
}, [item.hash]);
|
||||
Clipboard.setString(`${selectedBlockExplorer}/tx/${item.hash}`);
|
||||
}, [item.hash, selectedBlockExplorer]);
|
||||
|
||||
const onToolTipPress = useCallback(
|
||||
(id: any) => {
|
||||
|
|
|
@ -31,6 +31,7 @@ export const BlueDefaultTheme = {
|
|||
outgoingForegroundColor: '#d0021b',
|
||||
successColor: '#37c0a1',
|
||||
failedColor: '#ff0000',
|
||||
placeholderTextColor: '#81868e',
|
||||
shadowColor: '#000000',
|
||||
inverseForegroundColor: '#ffffff',
|
||||
hdborderColor: '#68BBE1',
|
||||
|
|
|
@ -331,7 +331,7 @@ platform :ios do
|
|||
xcargs: "GCC_PREPROCESSOR_DEFINITIONS='$(inherited) VERBOSE_LOGGING=1'",
|
||||
output_directory: "./build", # Directory where the IPA file will be stored
|
||||
|
||||
output_name: "BlueWallet.#{ENV['PROJECT_VERSION']}(#{ENV['NEW_BUILD_NUMBER']}).ipa",
|
||||
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa",
|
||||
buildlog_path: "./build_logs"
|
||||
)
|
||||
end
|
||||
|
@ -343,10 +343,11 @@ platform :ios do
|
|||
begin
|
||||
UI.message("Uploading to TestFlight without processing wait...")
|
||||
changelog = ENV["LATEST_COMMIT_MESSAGE"]
|
||||
|
||||
ipa_path = "./BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
|
||||
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: "./BlueWallet.#{ENV['PROJECT_VERSION']}(#{ENV['NEW_BUILD_NUMBER']}).ipa",
|
||||
ipa: ipa_path,
|
||||
skip_waiting_for_build_processing: true, # Do not wait for processing
|
||||
changelog: changelog
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ function scanQrHelper(
|
|||
if (useMerge) {
|
||||
const onBarScanned = function (data: any) {
|
||||
setTimeout(() => resolve(data.data || data), 1);
|
||||
navigationRef.navigate({ name: currentScreenName, params: {}, merge: true });
|
||||
navigationRef.navigate({ name: currentScreenName, params: data, merge: true });
|
||||
};
|
||||
|
||||
params = {
|
||||
|
|
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 1.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 2.png
Normal file
After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128pt@1x.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128pt@2x.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 767 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16pt@1x.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16pt@2x.png
Normal file
After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 29 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256pt@1x.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256pt@2x.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32pt@1x.png
Normal file
After Width: | Height: | Size: 614 B |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32pt@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 75 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512pt@1x.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512pt@2x.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 20 KiB |
|
@ -1,172 +1,100 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "BlueWallet-20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-60@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-20.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-20@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-29@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-40@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "BlueWallet-1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "1024 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "16pt@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "16pt@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32pt@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "32pt@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "128pt@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "128pt@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256pt@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "256pt@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512pt@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "512pt@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "1024 2.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "watchos",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
|
536
ios/Podfile.lock
|
@ -10,6 +10,7 @@
|
|||
"never": "Never",
|
||||
"of": "{number} of {total}",
|
||||
"ok": "OK",
|
||||
"enter_url": "Enter URL",
|
||||
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it.",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
|
@ -25,7 +26,8 @@
|
|||
"pick_file": "Choose a file",
|
||||
"enter_amount": "Enter amount",
|
||||
"qr_custom_input_button": "Tap 10 times to enter custom input",
|
||||
"unlock": "Unlock"
|
||||
"unlock": "Unlock",
|
||||
"suggested": "Suggested"
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Your voucher code is",
|
||||
|
@ -206,8 +208,10 @@
|
|||
"performance_score": "Performance score: {num}",
|
||||
"run_performance_test": "Test performance",
|
||||
"about_selftest": "Run self-test",
|
||||
"block_explorer_invalid_custom_url": "The URL provided is invalid. Please enter a valid URL starting with http:// or https://.",
|
||||
"about_selftest_electrum_disabled": "Self-testing is not available with Electrum Offline Mode. Please disable offline mode and try again.",
|
||||
"about_selftest_ok": "All internal tests have passed successfully. The wallet works well.",
|
||||
|
||||
"about_sm_github": "GitHub",
|
||||
"about_sm_discord": "Discord Server",
|
||||
"about_sm_telegram": "Telegram channel",
|
||||
|
@ -259,6 +263,9 @@
|
|||
"encrypt_storage_explanation_description_line1": "Enabling Storage Encryption adds an extra layer of protection to your app by securing the way your data is stored on your device. This makes it harder for anyone to access your information without permission.",
|
||||
"encrypt_storage_explanation_description_line2": "However, it's important to know that this encryption only protects the access to your wallets stored on the device's keychain. It doesn't put a password or any extra protection on the wallets themselves.",
|
||||
"i_understand": "I understand",
|
||||
"block_explorer": "Block Explorer",
|
||||
"block_explorer_preferred": "Use preferred block explorer",
|
||||
"block_explorer_error_saving_custom": "Error saving preferred block explorer",
|
||||
"encrypt_title": "Security",
|
||||
"encrypt_tstorage": "Storage",
|
||||
"encrypt_use": "Use {type}",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"never": "Nunca",
|
||||
"of": "{number} de {total}",
|
||||
"ok": "OK",
|
||||
"enter_url": "Introducir URL",
|
||||
"storage_is_encrypted": "Tu almacenamiento está encriptado. Se requiere contraseña para descifrarlo.",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
|
@ -25,7 +26,8 @@
|
|||
"pick_file": "Escoge un archivo",
|
||||
"enter_amount": "Ingresa la cantidad",
|
||||
"qr_custom_input_button": "Pulsa 10 veces para ingresar una entrada personalizada",
|
||||
"unlock": "Desbloquear"
|
||||
"unlock": "Desbloquear",
|
||||
"suggested": "Sugerido"
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Tu código de cupón es",
|
||||
|
@ -206,8 +208,10 @@
|
|||
"performance_score": "Puntuación de rendimiento: {num}",
|
||||
"run_performance_test": "Prueba de rendimiento",
|
||||
"about_selftest": "Ejecutar auto-prueba",
|
||||
"block_explorer_invalid_custom_url": "La URL proporcionada no es válida. Ingresa una URL válida que comience con http:// o https://.",
|
||||
"about_selftest_electrum_disabled": "La autocomprobación no está disponible con el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo. ",
|
||||
"about_selftest_ok": "Todas las pruebas internas han pasado satisfactoriamente. La billetera funciona bien.",
|
||||
|
||||
"about_sm_github": "GitHub",
|
||||
"about_sm_discord": "Servidor Discord",
|
||||
"about_sm_telegram": "Chat de Telegram ",
|
||||
|
@ -259,6 +263,9 @@
|
|||
"encrypt_storage_explanation_description_line1": "Habilitar el cifrado de almacenamiento agrega una capa adicional de protección a tu aplicación al proteger la forma en que se almacenan tus datos en tu dispositivo. Esto hace que sea más difícil para cualquier persona acceder a tu información sin permiso.",
|
||||
"encrypt_storage_explanation_description_line2": "Sin embargo, es importante saber que este cifrado sólo protege el acceso a tus billeteras almacenadas en el llavero del dispositivo. No pone una contraseña ni ninguna protección adicional en las billeteras.",
|
||||
"i_understand": "Entiendo",
|
||||
"block_explorer": "Explorador de bloques",
|
||||
"block_explorer_preferred": "Utiliza el explorador de bloques preferido",
|
||||
"block_explorer_error_saving_custom": "Error al guardar el explorador de bloques preferido",
|
||||
"encrypt_title": "Seguridad",
|
||||
"encrypt_tstorage": "Almacenamiento",
|
||||
"encrypt_use": "Usar {type}",
|
||||
|
|
79
models/blockExplorer.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
// blockExplorer.ts
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
|
||||
export interface BlockExplorer {
|
||||
key: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const BLOCK_EXPLORERS: { [key: string]: BlockExplorer } = {
|
||||
default: { key: 'default', name: 'Mempool.space', url: 'https://mempool.space' },
|
||||
blockchair: { key: 'blockchair', name: 'Blockchair', url: 'https://blockchair.com/bitcoin' },
|
||||
blockstream: { key: 'blockstream', name: 'Blockstream.info', url: 'https://blockstream.info' },
|
||||
custom: { key: 'custom', name: 'Custom', url: '' }, // Custom URL will be handled separately
|
||||
};
|
||||
|
||||
export const getBlockExplorersList = (): BlockExplorer[] => {
|
||||
return Object.values(BLOCK_EXPLORERS);
|
||||
};
|
||||
|
||||
export const normalizeUrl = (url: string): string => {
|
||||
return url.replace(/\/+$/, '');
|
||||
};
|
||||
|
||||
export const isValidUrl = (url: string): boolean => {
|
||||
const pattern = /^(https?:\/\/)/;
|
||||
return pattern.test(url);
|
||||
};
|
||||
|
||||
export const findMatchingExplorerByDomain = (url: string): BlockExplorer | null => {
|
||||
const domain = getDomain(url);
|
||||
for (const explorer of Object.values(BLOCK_EXPLORERS)) {
|
||||
if (getDomain(explorer.url) === domain) {
|
||||
return explorer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getDomain = (url: string): string => {
|
||||
try {
|
||||
const hostname = new URL(url).hostname;
|
||||
return hostname.replace(/^www\./, '');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const BLOCK_EXPLORER_STORAGE_KEY = 'blockExplorer';
|
||||
|
||||
export const saveBlockExplorer = async (url: string): Promise<boolean> => {
|
||||
try {
|
||||
await DefaultPreference.set(BLOCK_EXPLORER_STORAGE_KEY, url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error saving block explorer:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeBlockExplorer = async (): Promise<boolean> => {
|
||||
try {
|
||||
await DefaultPreference.clear(BLOCK_EXPLORER_STORAGE_KEY);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error removing block explorer:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getBlockExplorerUrl = async (): Promise<string> => {
|
||||
try {
|
||||
const url = await DefaultPreference.get(BLOCK_EXPLORER_STORAGE_KEY);
|
||||
return url ?? BLOCK_EXPLORERS.default.url;
|
||||
} catch (error) {
|
||||
console.error('Error getting block explorer:', error);
|
||||
return BLOCK_EXPLORERS.default.url;
|
||||
}
|
||||
};
|
|
@ -21,9 +21,16 @@ import {
|
|||
export type AddWalletStackParamList = {
|
||||
AddWallet: undefined;
|
||||
ImportWallet: undefined;
|
||||
ImportWalletDiscovery: undefined;
|
||||
ImportWalletDiscovery: {
|
||||
importText: string;
|
||||
askPassphrase: boolean;
|
||||
searchAccounts: boolean;
|
||||
};
|
||||
ImportSpeed: undefined;
|
||||
ImportCustomDerivationPath: undefined;
|
||||
ImportCustomDerivationPath: {
|
||||
importText: string;
|
||||
password: string | undefined;
|
||||
};
|
||||
PleaseBackup: undefined;
|
||||
PleaseBackupLNDHub: undefined;
|
||||
ProvideEntropy: undefined;
|
||||
|
|
|
@ -31,6 +31,7 @@ import AddWalletStack from './AddWalletStack';
|
|||
import AztecoRedeemStackRoot from './AztecoRedeemStack';
|
||||
import {
|
||||
AboutComponent,
|
||||
BlockExplorerSettingsComponent,
|
||||
CurrencyComponent,
|
||||
DefaultViewComponent,
|
||||
ElectrumSettingsComponent,
|
||||
|
@ -304,6 +305,12 @@ const DetailViewStackScreensStack = () => {
|
|||
component={NetworkSettingsComponent}
|
||||
options={navigationStyle({ title: loc.settings.network })(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
name="SettingsBlockExplorer"
|
||||
component={BlockExplorerSettingsComponent}
|
||||
options={navigationStyle({ title: loc.settings.block_explorer })(theme)}
|
||||
/>
|
||||
|
||||
<DetailViewStack.Screen name="About" component={AboutComponent} options={navigationStyle({ title: loc.settings.about })(theme)} />
|
||||
<DetailViewStack.Screen
|
||||
name="DefaultView"
|
||||
|
|
|
@ -54,6 +54,7 @@ export type DetailViewStackParamList = {
|
|||
About: undefined;
|
||||
DefaultView: undefined;
|
||||
ElectrumSettings: undefined;
|
||||
SettingsBlockExplorer: undefined;
|
||||
EncryptStorage: undefined;
|
||||
Language: undefined;
|
||||
LightningSettings: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
|||
// Define lazy imports
|
||||
const WalletsAdd = lazy(() => import('../screen/wallets/Add'));
|
||||
const ImportCustomDerivationPath = lazy(() => import('../screen/wallets/importCustomDerivationPath'));
|
||||
const ImportWalletDiscovery = lazy(() => import('../screen/wallets/importDiscovery'));
|
||||
const ImportWalletDiscovery = lazy(() => import('../screen/wallets/ImportWalletDiscovery'));
|
||||
const ImportSpeed = lazy(() => import('../screen/wallets/importSpeed'));
|
||||
const ImportWallet = lazy(() => import('../screen/wallets/import'));
|
||||
const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup'));
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
import Currency from '../screen/settings/Currency';
|
||||
import Language from '../screen/settings/Language';
|
||||
import { LazyLoadingIndicator } from './LazyLoadingIndicator'; // Assume you have this component for loading indication
|
||||
import SettingsBlockExplorer from '../screen/settings/SettingsBlockExplorer';
|
||||
|
||||
const Settings = lazy(() => import('../screen/settings/Settings'));
|
||||
const GeneralSettings = lazy(() => import('../screen/settings/GeneralSettings'));
|
||||
|
@ -46,6 +47,12 @@ export const NetworkSettingsComponent = () => (
|
|||
</Suspense>
|
||||
);
|
||||
|
||||
export const BlockExplorerSettingsComponent = () => (
|
||||
<Suspense fallback={<LazyLoadingIndicator />}>
|
||||
<SettingsBlockExplorer />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export const AboutComponent = () => (
|
||||
<Suspense fallback={<LazyLoadingIndicator />}>
|
||||
<About />
|
||||
|
|
|
@ -35,7 +35,7 @@ export type SendDetailsStackParamList = {
|
|||
};
|
||||
PsbtWithHardwareWallet: {
|
||||
memo?: string;
|
||||
fromWallet: TWallet;
|
||||
walletID: string;
|
||||
launchedBy?: string;
|
||||
psbt?: Psbt;
|
||||
txhex?: string;
|
||||
|
@ -79,18 +79,4 @@ export type SendDetailsStackParamList = {
|
|||
PaymentCodeList: {
|
||||
walletID: string;
|
||||
};
|
||||
ScanQRCodeRoot: {
|
||||
screen: string;
|
||||
params: {
|
||||
isLoading?: boolean;
|
||||
cameraStatusGranted?: boolean;
|
||||
backdoorPressed?: boolean;
|
||||
launchedBy?: string;
|
||||
urTotal?: number;
|
||||
urHave?: number;
|
||||
backdoorText?: string;
|
||||
showFileImportButton?: boolean;
|
||||
onBarScanned: (data: string) => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
174
package-lock.json
generated
|
@ -21,8 +21,8 @@
|
|||
"@react-native-clipboard/clipboard": "1.14.2",
|
||||
"@react-native-community/push-notification-ios": "1.11.0",
|
||||
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#8c6004b",
|
||||
"@react-native/gradle-plugin": "^0.75.3",
|
||||
"@react-native/metro-config": "0.75.3",
|
||||
"@react-native/gradle-plugin": "^0.75.4",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-navigation/drawer": "6.7.2",
|
||||
"@react-navigation/native": "6.1.18",
|
||||
"@react-navigation/native-stack": "6.11.0",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"coinselect": "3.1.13",
|
||||
"crypto-js": "4.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
"detox": "20.27.2",
|
||||
"detox": "20.27.3",
|
||||
"ecpair": "2.0.1",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-localization": "github:BlueWallet/react-localization#ae7969a",
|
||||
"react-native": "0.75.3",
|
||||
"react-native": "0.75.4",
|
||||
"react-native-biometrics": "3.0.1",
|
||||
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
|
||||
"react-native-camera-kit": "13.0.0",
|
||||
|
@ -89,7 +89,7 @@
|
|||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-randombytes": "3.6.1",
|
||||
"react-native-rate": "1.2.12",
|
||||
"react-native-reanimated": "3.15.3",
|
||||
"react-native-reanimated": "3.15.4",
|
||||
"react-native-safe-area-context": "4.11.0",
|
||||
"react-native-screens": "3.34.0",
|
||||
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
|
||||
|
@ -112,11 +112,11 @@
|
|||
"@babel/core": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@jest/reporters": "^27.5.1",
|
||||
"@react-native/babel-preset": "^0.75.3",
|
||||
"@react-native/eslint-config": "^0.75.3",
|
||||
"@react-native/js-polyfills": "^0.75.3",
|
||||
"@react-native/metro-babel-transformer": "^0.75.3",
|
||||
"@react-native/typescript-config": "^0.75.3",
|
||||
"@react-native/babel-preset": "^0.75.4",
|
||||
"@react-native/eslint-config": "^0.75.4",
|
||||
"@react-native/js-polyfills": "^0.75.4",
|
||||
"@react-native/metro-babel-transformer": "^0.75.4",
|
||||
"@react-native/typescript-config": "^0.75.4",
|
||||
"@types/bip38": "^3.1.2",
|
||||
"@types/bs58check": "^2.1.0",
|
||||
"@types/create-hash": "^1.2.2",
|
||||
|
@ -5610,30 +5610,30 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.3.tgz",
|
||||
"integrity": "sha512-i7MaRbYR06WdpJWv3a0PQ2ScFBUeevwcJ0tVopnFwTg0tBWp3NFEMDIcU8lyXVy9Y59WmrP1V2ROaRDaPiESgg==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.4.tgz",
|
||||
"integrity": "sha512-WX6/LNHwyjislSFM+h3qQjBiPaXXPJW5ZV4TdgNKb6QOPO0g1KGYRQj44cI2xSpZ3fcWrvQFZfQgSMbVK9Sg7A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-plugin-codegen": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.3.tgz",
|
||||
"integrity": "sha512-8JmXEKq+Efb9AffsV48l8gmKe/ZQ2PbBygZjHdIf8DNZZhO/z5mt27J4B43MWNdp5Ww1l59T0mEaf8l/uywQUg==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-gu5ZRIdr7+ufi09DJROhfDtbF4biTnCDJqtqcmtsku4cXOXPHE36QbC/vAmKEZ0PMPURBI8lwF2wfaeHLn7gig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/codegen": "0.75.3"
|
||||
"@react-native/codegen": "0.75.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-preset": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.3.tgz",
|
||||
"integrity": "sha512-VZQkQEj36DKEGApXFYdVcFtqdglbnoVr7aOZpjffURSgPcIA9vWTm1b+OL4ayOaRZXTZKiDBNQCXvBX5E5AgQg==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.4.tgz",
|
||||
"integrity": "sha512-UtyYCDJ3rZIeggyFEfh/q5t/FZ5a1h9F8EI37Nbrwyk/OKPH+1XS4PbHROHJzBARlJwOAfmT75+ovYUO0eakJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
@ -5678,7 +5678,7 @@
|
|||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"@react-native/babel-plugin-codegen": "0.75.3",
|
||||
"@react-native/babel-plugin-codegen": "0.75.4",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
|
@ -5690,9 +5690,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/codegen": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.3.tgz",
|
||||
"integrity": "sha512-I0bz5jwOkiR7vnhYLGoV22RGmesErUg03tjsCiQgmsMpbyCYumudEtLNN5+DplHGK56bu8KyzBqKkWXGSKSCZQ==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-0FplNAD/S5FUvm8YIn6uyarOcP4jdJPqWz17K4a/Gp2KSsG/JJKEskX3aj5wpePzVfNQl3WyvBJ0whODdCocIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.0",
|
||||
|
@ -5712,15 +5712,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/community-cli-plugin": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.3.tgz",
|
||||
"integrity": "sha512-njsYm+jBWzfLcJcxavAY5QFzYTrmPtjbxq/64GSqwcQYzy9qAkI7LNTK/Wprq1I/4HOuHJO7Km+EddCXB+ByRQ==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-k/hevYPjEpW0MNVVyb3v9PJosOP+FzenS7+oqYNLXdEmgTnGHrAtYX9ABrJJgzeJt7I6g8g+RDvm8PSE+tnM5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native-community/cli-server-api": "14.1.0",
|
||||
"@react-native-community/cli-tools": "14.1.0",
|
||||
"@react-native/dev-middleware": "0.75.3",
|
||||
"@react-native/metro-babel-transformer": "0.75.3",
|
||||
"@react-native/dev-middleware": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"metro": "^0.80.3",
|
||||
|
@ -5804,22 +5804,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/debugger-frontend": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.3.tgz",
|
||||
"integrity": "sha512-99bLQsUwsxUMNR7Wa9eV2uyR38yfd6mOEqfN+JIm8/L9sKA926oh+CZkjDy1M8RmCB6spB5N9fVFVkrVdf2yFA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.4.tgz",
|
||||
"integrity": "sha512-QfGurR5hV6bhMPn/6VxS2RomYrPRFGwA03jJr+zKyWHnxDAu5jOqYVyKAktIIbhYe5sPp78QVl1ZYuhcnsRbEw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/dev-middleware": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.3.tgz",
|
||||
"integrity": "sha512-h2/6+UGmeMWjnT43axy27jNqoDRsE1C1qpjRC3sYpD4g0bI0jSTkY1kAgj8uqGGXLnHXiHOtjLDGdbAgZrsPaA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.4.tgz",
|
||||
"integrity": "sha512-UhyBeQOG2wNcvrUGw3+IBrHBk/lIu7hHGmWt4j8W9Aqv9BwktHKkPyko+5A1yoUeO1O/VDnHWYqWeOejcA9wpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.75.3",
|
||||
"@react-native/debugger-frontend": "0.75.4",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
|
@ -5876,15 +5876,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/eslint-config": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.75.3.tgz",
|
||||
"integrity": "sha512-BUkgQ3+irVZFfikIX3JgQQut/LABcumMZD3lzWRpf1hpbsEsXmYLo3u9qpfyYsavnqVGtWg8bLZDDGG6lmQBhA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.75.4.tgz",
|
||||
"integrity": "sha512-3KBHYwp4HnBdaCFx9KDPvQY+sGrv5fHX2qDkXGKmN3uYBz+zfnMQXTiht6OuBbWULUF0y0o8m+uH1yYAn/V9mw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/eslint-parser": "^7.20.0",
|
||||
"@react-native/eslint-plugin": "0.75.3",
|
||||
"@react-native/eslint-plugin": "0.75.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
|
@ -6074,9 +6074,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/eslint-plugin": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.75.3.tgz",
|
||||
"integrity": "sha512-p0BwuqflumZtUJ8VUt5YQPsYnxrwfHJZCTQ0N1X+4B8aoBNIKxtfzeIqJsSz/GvM86HGgGnd/nyxEo8IwkPo7Q==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-1kEZzC8UKi3baHnH7tBVCNpF4aoAmT7g7hEa5/rtZ+Z7vcpaxeY6wjNYt3j02Z9n310yX0NKDJox30CqvzEvsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -6084,31 +6084,31 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/gradle-plugin": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.3.tgz",
|
||||
"integrity": "sha512-mSfa/Mq/AsALuG/kvXz5ECrc6HdY5waMHal2sSfa8KA0Gt3JqYQVXF9Pdwd4yR5ClPZDI2HRa1tdE8GVlhMvPA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-kKTmw7cF7p1raT30DC0L6N+xiVXN7dlRy0J+hYPiCRRVHplwgvyS7pszjxfzwXmHFqOxwpxQVI3du8opsma1Mg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/js-polyfills": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.3.tgz",
|
||||
"integrity": "sha512-+JVFJ351GSJT3V7LuXscMqfnpR/UxzsAjbBjfAHBR3kqTbVqrAmBccqPCA3NLzgb/RY8khLJklwMUVlWrn8iFg==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.4.tgz",
|
||||
"integrity": "sha512-NF5ID5FjcVHBYk1LQ4JMRjPmxBWEo4yoqW1m6vGOQZPT8D5Qs9afgx3f7gQatxbn3ivMh0FVbLW0zBx6LyxEzA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-babel-transformer": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.3.tgz",
|
||||
"integrity": "sha512-gDlEl6C2mwQPLxFOR+yla5MpJpDPNOFD6J5Hd9JM9+lOdUq6MNujh1Xn4ZMvglW7rfViq3nMjg4xPQeGUhDG+w==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.4.tgz",
|
||||
"integrity": "sha512-O0WMW/K8Ny/MAAeRebqGEQhrbzcioxcPHZtos+EH2hWeBTEKHQV8fMYYxfYDabpr392qdhSBwg3LlXUD4U3PXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@react-native/babel-preset": "0.75.3",
|
||||
"@react-native/babel-preset": "0.75.4",
|
||||
"hermes-parser": "0.22.0",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
|
@ -6120,13 +6120,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-config": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.3.tgz",
|
||||
"integrity": "sha512-BKvJGCzKMtGPfKU4v8+16xtv6iXPbnSeeD6ONvzW0Hm9amPxAP9GGweiUTwnOXUqt9XcfRhytZosiOmGa39Wqg==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.4.tgz",
|
||||
"integrity": "sha512-gIIVlPUtZ1UKCxMJRtG88FoWS5REbI4YUmiyoM8eBUA85Zvk27b67iBX5Lkuxg8FGc7t9tjWQRpVGs2IK5uZpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "0.75.3",
|
||||
"@react-native/metro-babel-transformer": "0.75.3",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"metro-config": "^0.80.3",
|
||||
"metro-runtime": "^0.80.3"
|
||||
},
|
||||
|
@ -6135,22 +6135,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@react-native/normalize-colors": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.3.tgz",
|
||||
"integrity": "sha512-3mhF8AJFfIN0E5bEs/DQ4U2LzMJYm+FPSwY5bJ1DZhrxW1PFAh24bAPrSd8PwS0iarQ7biLdr1lWf/8LFv8pDA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.4.tgz",
|
||||
"integrity": "sha512-90QrQDLg0/k9xqYesaKuIkayOSjD+FKa0hsHollbwT5h3kuGMY+lU7UZxnb8tU55Y1PKdvjYxqQsYWI/ql79zA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/typescript-config": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.75.3.tgz",
|
||||
"integrity": "sha512-yekwhjG9lDvib+3jYwagEXcyTJ4mEryDLCoQINWCwy+7GtMx2BrKweHc4Phcc1dqmK1U/XMzVUXF3X3hc+FVPA==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.75.4.tgz",
|
||||
"integrity": "sha512-0849BqSIDGYltqMbniZg1MvDSFO5KMQsmIKpGzioTm3+SF73Ec1LihA1CtpJyZFonEclKXEIawRTNh2bxGgJJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/virtualized-lists": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.3.tgz",
|
||||
"integrity": "sha512-cTLm7k7Y//SvV8UK8esrDHEw5OrwwSJ4Fqc3x52Imi6ROuhshfGIPFwhtn4pmAg9nWHzHwwqiJ+9hCSVnXXX+g==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.4.tgz",
|
||||
"integrity": "sha512-iEauRiXjvWG/iOH8bV+9MfepCS+72cuL5rhkrenYZS0NUnDcNjF+wtaoS9+Gx5z1UJOfEXxSmyXRtQJZne8SnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
|
@ -9772,9 +9772,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/detox": {
|
||||
"version": "20.27.2",
|
||||
"resolved": "https://registry.npmjs.org/detox/-/detox-20.27.2.tgz",
|
||||
"integrity": "sha512-cC6S40v7ix+uA5jYzG8eazSs7YtOWgc2aCwWLZIIzfE5Kvo0gfHgtqeRhrYWCMZaj/irKKs39h2B070oNQOIrA==",
|
||||
"version": "20.27.3",
|
||||
"resolved": "https://registry.npmjs.org/detox/-/detox-20.27.3.tgz",
|
||||
"integrity": "sha512-bMAjL+6dQLF75DWbJb0gTp6/cOnpFqu5CU+VcE48ZIRLBQp5KD/ppk2zvx/B8k+CeQJjWFGs8exfpVuOBJUTpw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -9784,7 +9784,7 @@
|
|||
"caf": "^15.0.1",
|
||||
"chalk": "^4.0.0",
|
||||
"child-process-promise": "^2.2.0",
|
||||
"detox-copilot": "^0.0.9",
|
||||
"detox-copilot": "^0.0.14",
|
||||
"execa": "^5.1.1",
|
||||
"find-up": "^5.0.0",
|
||||
"fs-extra": "^11.0.0",
|
||||
|
@ -9831,9 +9831,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/detox-copilot": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/detox-copilot/-/detox-copilot-0.0.9.tgz",
|
||||
"integrity": "sha512-Wk2fuisD8EH+349b0ysNWvZ7UEsThAChbYFlLqOR1jWkDaonEvgf6IOUlmxjvyTl9ENtl8ckd1U7k94yCBYwqw==",
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/detox-copilot/-/detox-copilot-0.0.14.tgz",
|
||||
"integrity": "sha512-XzruvuEVnX/sGkkDLDDLTGuwyoS0qLk+g5psDWgpLrnFd9w4QA0I65G5dwPzcK68lYaOGb0FThXv3fmD37/Mlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detox/node_modules/ansi-styles": {
|
||||
|
@ -20467,22 +20467,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.75.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.3.tgz",
|
||||
"integrity": "sha512-+Ne6u5H+tPo36sme19SCd1u2UID2uo0J/XzAJarxmrDj4Nsdi44eyUDKtQHmhgxjRGsuVJqAYrMK0abLSq8AHw==",
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz",
|
||||
"integrity": "sha512-Jehg4AMNIAXu9cn0/1jbTCoNg3tc+t6EekwucCalN8YoRmxGd/PY6osQTI/5fSAM40JQ4O8uv8Qg09Ycpb5sxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@react-native-community/cli": "14.1.0",
|
||||
"@react-native-community/cli-platform-android": "14.1.0",
|
||||
"@react-native-community/cli-platform-ios": "14.1.0",
|
||||
"@react-native/assets-registry": "0.75.3",
|
||||
"@react-native/codegen": "0.75.3",
|
||||
"@react-native/community-cli-plugin": "0.75.3",
|
||||
"@react-native/gradle-plugin": "0.75.3",
|
||||
"@react-native/js-polyfills": "0.75.3",
|
||||
"@react-native/normalize-colors": "0.75.3",
|
||||
"@react-native/virtualized-lists": "0.75.3",
|
||||
"@react-native/assets-registry": "0.75.4",
|
||||
"@react-native/codegen": "0.75.4",
|
||||
"@react-native/community-cli-plugin": "0.75.4",
|
||||
"@react-native/gradle-plugin": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/normalize-colors": "0.75.4",
|
||||
"@react-native/virtualized-lists": "0.75.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"ansi-regex": "^5.0.0",
|
||||
|
@ -20882,9 +20882,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated": {
|
||||
"version": "3.15.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.15.3.tgz",
|
||||
"integrity": "sha512-5QBk/7PZvZ98Adxm4MRyglwzsRzReTQIe4Hd2wbBBAZ68IC4OYKvsc8cPEjgx3/1mG8HgHFYhbcDe5U2RjeFqw==",
|
||||
"version": "3.15.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.15.4.tgz",
|
||||
"integrity": "sha512-jcpHE+MnsvSbClhHgAFoken7SnaHrUJ5gVA8BUw8S1j6rkrw2VzRpht6cxn14NlqYx5ytjfG9IXJDOzq8tFvfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
|
||||
|
|
20
package.json
|
@ -10,11 +10,11 @@
|
|||
"@babel/core": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@jest/reporters": "^27.5.1",
|
||||
"@react-native/babel-preset": "^0.75.3",
|
||||
"@react-native/eslint-config": "^0.75.3",
|
||||
"@react-native/js-polyfills": "^0.75.3",
|
||||
"@react-native/metro-babel-transformer": "^0.75.3",
|
||||
"@react-native/typescript-config": "^0.75.3",
|
||||
"@react-native/babel-preset": "^0.75.4",
|
||||
"@react-native/eslint-config": "^0.75.4",
|
||||
"@react-native/js-polyfills": "^0.75.4",
|
||||
"@react-native/metro-babel-transformer": "^0.75.4",
|
||||
"@react-native/typescript-config": "^0.75.4",
|
||||
"@types/bip38": "^3.1.2",
|
||||
"@types/bs58check": "^2.1.0",
|
||||
"@types/create-hash": "^1.2.2",
|
||||
|
@ -85,8 +85,8 @@
|
|||
"@react-native-clipboard/clipboard": "1.14.2",
|
||||
"@react-native-community/push-notification-ios": "1.11.0",
|
||||
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#8c6004b",
|
||||
"@react-native/gradle-plugin": "^0.75.3",
|
||||
"@react-native/metro-config": "0.75.3",
|
||||
"@react-native/gradle-plugin": "^0.75.4",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-navigation/drawer": "6.7.2",
|
||||
"@react-navigation/native": "6.1.18",
|
||||
"@react-navigation/native-stack": "6.11.0",
|
||||
|
@ -111,7 +111,7 @@
|
|||
"coinselect": "3.1.13",
|
||||
"crypto-js": "4.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
"detox": "20.27.2",
|
||||
"detox": "20.27.3",
|
||||
"ecpair": "2.0.1",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc",
|
||||
|
@ -125,7 +125,7 @@
|
|||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-localization": "github:BlueWallet/react-localization#ae7969a",
|
||||
"react-native": "0.75.3",
|
||||
"react-native": "0.75.4",
|
||||
"react-native-biometrics": "3.0.1",
|
||||
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
|
||||
"react-native-camera-kit": "13.0.0",
|
||||
|
@ -153,7 +153,7 @@
|
|||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-randombytes": "3.6.1",
|
||||
"react-native-rate": "1.2.12",
|
||||
"react-native-reanimated": "3.15.3",
|
||||
"react-native-reanimated": "3.15.4",
|
||||
"react-native-safe-area-context": "4.11.0",
|
||||
"react-native-screens": "3.34.0",
|
||||
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
|
||||
|
|
|
@ -25,7 +25,7 @@ import AmountInput from '../../components/AmountInput';
|
|||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc, { formatBalance, formatBalancePlain, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import * as NavigationService from '../../NavigationService';
|
||||
|
@ -325,16 +325,8 @@ const LNDCreateInvoice = () => {
|
|||
};
|
||||
|
||||
const navigateToScanQRCode = () => {
|
||||
requestCameraAuthorization().then(() => {
|
||||
NavigationService.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned: processLnurl,
|
||||
launchedBy: name,
|
||||
},
|
||||
});
|
||||
Keyboard.dismiss();
|
||||
});
|
||||
scanQrHelper(name, true, processLnurl);
|
||||
Keyboard.dismiss();
|
||||
};
|
||||
|
||||
const renderScanClickable = () => {
|
||||
|
|
|
@ -35,9 +35,8 @@ import { SuccessView } from '../send/success';
|
|||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import SegmentedControl from '../../components/SegmentControl';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
|
||||
const segmentControlValues = [loc.wallets.details_address, loc.bip47.payment_code];
|
||||
|
||||
|
@ -167,12 +166,9 @@ const ReceiveDetails = () => {
|
|||
}, [onEnablePaymentsCodeSwitchValue]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu isButton isMenuPrimaryAction onPressMenuItem={onPressMenuItem} actions={[toolTipActions]}>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
[colors.foregroundColor, onPressMenuItem, toolTipActions],
|
||||
() => <HeaderMenuButton actions={toolTipActions} onPressMenuItem={onPressMenuItem} />,
|
||||
|
||||
[onPressMenuItem, toolTipActions],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useTheme } from '../../components/themes';
|
|||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const BROADCAST_RESULT = Object.freeze({
|
||||
none: 'Input transaction hex',
|
||||
|
@ -39,6 +40,7 @@ const Broadcast: React.FC = () => {
|
|||
const [txHex, setTxHex] = useState<string | undefined>();
|
||||
const { colors } = useTheme();
|
||||
const [broadcastResult, setBroadcastResult] = useState<string>(BROADCAST_RESULT.none);
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
|
||||
const stylesHooks = StyleSheet.create({
|
||||
input: {
|
||||
|
@ -158,13 +160,13 @@ const Broadcast: React.FC = () => {
|
|||
<BlueSpacing20 />
|
||||
</BlueCard>
|
||||
)}
|
||||
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} />}
|
||||
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} url={`${selectedBlockExplorer}/tx/${tx}`} />}
|
||||
</View>
|
||||
</SafeArea>
|
||||
);
|
||||
};
|
||||
|
||||
const SuccessScreen: React.FC<{ tx: string }> = ({ tx }) => {
|
||||
const SuccessScreen: React.FC<{ tx: string; url: string }> = ({ tx, url }) => {
|
||||
if (!tx) {
|
||||
return null;
|
||||
}
|
||||
|
@ -177,7 +179,7 @@ const SuccessScreen: React.FC<{ tx: string }> = ({ tx }) => {
|
|||
<BlueSpacing20 />
|
||||
<BlueTextCentered>{loc.settings.success_transaction_broadcasted}</BlueTextCentered>
|
||||
<BlueSpacing10 />
|
||||
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(`https://mempool.space/tx/${tx}`)} />
|
||||
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(url)} />
|
||||
</View>
|
||||
</BlueCard>
|
||||
</View>
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
import DocumentPicker from 'react-native-document-picker';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import RNFS from 'react-native-fs';
|
||||
|
||||
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
|
||||
import * as fs from '../../blue_modules/fs';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
|
@ -40,8 +39,7 @@ import Button from '../../components/Button';
|
|||
import CoinsSelected from '../../components/CoinsSelected';
|
||||
import InputAccessoryAllFunds, { InputAccessoryAllFundsAccessoryViewID } from '../../components/InputAccessoryAllFunds';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { requestCameraAuthorization, scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
|
||||
|
@ -58,6 +56,7 @@ import SelectFeeModal from '../../components/SelectFeeModal';
|
|||
import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
|
||||
interface IPaymentDestinations {
|
||||
address: string; // btc address or payment code
|
||||
|
@ -601,7 +600,6 @@ const SendDetails = () => {
|
|||
|
||||
if (tx && routeParams.launchedBy && psbt) {
|
||||
console.warn('navigating back to ', routeParams.launchedBy);
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
// @ts-ignore idk how to fix FIXME?
|
||||
|
||||
|
@ -609,14 +607,12 @@ const SendDetails = () => {
|
|||
}
|
||||
|
||||
if (wallet?.type === WatchOnlyWallet.type) {
|
||||
feeModalRef.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
|
||||
navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: transactionMemo,
|
||||
fromWallet: wallet,
|
||||
walletID: wallet.getID(),
|
||||
psbt,
|
||||
launchedBy: routeParams.launchedBy,
|
||||
});
|
||||
|
@ -625,8 +621,6 @@ const SendDetails = () => {
|
|||
}
|
||||
|
||||
if (wallet?.type === MultisigHDWallet.type) {
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
navigation.navigate('PsbtMultisig', {
|
||||
memo: transactionMemo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
|
@ -651,7 +645,6 @@ const SendDetails = () => {
|
|||
// (ez can be the case for single-address wallet when doing self-payment for consolidation)
|
||||
recipients = outputs;
|
||||
}
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
navigation.navigate('Confirm', {
|
||||
fee: new BigNumber(fee).dividedBy(100000000).toNumber(),
|
||||
|
@ -680,22 +673,13 @@ const SendDetails = () => {
|
|||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const importQrTransaction = () => {
|
||||
const importQrTransaction = async () => {
|
||||
if (wallet?.type !== WatchOnlyWallet.type) {
|
||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||
}
|
||||
|
||||
requestCameraAuthorization().then(() => {
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned: importQrTransactionOnBarScanned,
|
||||
showFileImportButton: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
importQrTransactionOnBarScanned(data);
|
||||
};
|
||||
|
||||
const importQrTransactionOnBarScanned = (ret: any) => {
|
||||
|
@ -708,8 +692,6 @@ const SendDetails = () => {
|
|||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
// we dont support it in this flow
|
||||
} else {
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
// psbt base64?
|
||||
|
||||
// we construct PSBT object and pass to next screen
|
||||
|
@ -718,7 +700,7 @@ const SendDetails = () => {
|
|||
|
||||
navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: transactionMemo,
|
||||
fromWallet: wallet,
|
||||
walletID: wallet.getID(),
|
||||
psbt,
|
||||
});
|
||||
setIsLoading(false);
|
||||
|
@ -752,7 +734,7 @@ const SendDetails = () => {
|
|||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
const txhex = psbt.extractTransaction().toHex();
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, txhex });
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, walletID: wallet.getID(), txhex });
|
||||
setIsLoading(false);
|
||||
|
||||
return;
|
||||
|
@ -763,7 +745,7 @@ const SendDetails = () => {
|
|||
// so user can do smth with it:
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, psbt });
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, walletID: wallet.getID(), psbt });
|
||||
setIsLoading(false);
|
||||
|
||||
return;
|
||||
|
@ -772,7 +754,7 @@ const SendDetails = () => {
|
|||
if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
|
||||
// plain text file with txhex ready to broadcast
|
||||
const file = (await RNFS.readFile(res.uri, 'ascii')).replace('\n', '').replace('\r', '');
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, fromWallet: wallet, txhex: file });
|
||||
navigation.navigate('PsbtWithHardwareWallet', { memo: transactionMemo, walletID: wallet.getID(), txhex: file });
|
||||
setIsLoading(false);
|
||||
|
||||
return;
|
||||
|
@ -853,15 +835,8 @@ const SendDetails = () => {
|
|||
};
|
||||
|
||||
const importTransactionMultisigScanQr = async () => {
|
||||
await requestCameraAuthorization().then(() => {
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
onBarScanned(data);
|
||||
};
|
||||
|
||||
const handleAddRecipient = () => {
|
||||
|
@ -1055,18 +1030,7 @@ const SendDetails = () => {
|
|||
const setHeaderRightOptions = () => {
|
||||
navigation.setOptions({
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
headerRight: () => (
|
||||
<ToolTipMenu
|
||||
disabled={isLoading}
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={headerRightOnPress}
|
||||
actions={headerRightActions()}
|
||||
testID="advancedOptionsMenuButton"
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} style={styles.advancedOptions} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
headerRight: () => <HeaderMenuButton disabled={isLoading} onPressMenuItem={headerRightOnPress} actions={headerRightActions()} />,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1185,8 +1149,6 @@ const SendDetails = () => {
|
|||
accessibilityRole="button"
|
||||
style={styles.selectTouch}
|
||||
onPress={() => {
|
||||
feeModalRef.current?.dismiss();
|
||||
|
||||
navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN });
|
||||
}}
|
||||
>
|
||||
|
@ -1499,11 +1461,6 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
advancedOptions: {
|
||||
minWidth: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
frozenContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
|
|
|
@ -5,7 +5,7 @@ import { BlueButtonLink, BlueCard, BlueSpacing10, BlueSpacing20, BlueText } from
|
|||
import Button from '../../components/Button';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
@ -57,16 +57,7 @@ const IsItMyAddress = () => {
|
|||
};
|
||||
|
||||
const importScan = () => {
|
||||
requestCameraAuthorization().then(() => {
|
||||
navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: name,
|
||||
onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
scanQrHelper(name, true, onBarScanned);
|
||||
};
|
||||
|
||||
const clearAddressInput = () => {
|
||||
|
|
|
@ -60,7 +60,7 @@ const PsbtMultisigQRCode = () => {
|
|||
};
|
||||
|
||||
const openScanner = async () => {
|
||||
const scanned = await scanQrHelper(name, true, undefined);
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
onBarScanned({ data: scanned });
|
||||
};
|
||||
|
||||
|
|
|
@ -15,18 +15,19 @@ import { DynamicQRCode } from '../../components/DynamicQRCode';
|
|||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import { SecondButton } from '../../components/SecondButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useBiometrics, unlockWithBiometrics } from '../../hooks/useBiometrics';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const PsbtWithHardwareWallet = () => {
|
||||
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useStorage();
|
||||
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled, wallets } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const navigation = useExtendedNavigation();
|
||||
const route = useRoute();
|
||||
const { fromWallet, memo, psbt, deepLinkPSBT, launchedBy } = route.params;
|
||||
const { walletID, memo, psbt, deepLinkPSBT, launchedBy } = route.params;
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const routeParamsPSBT = useRef(route.params.psbt);
|
||||
const routeParamsTXHex = route.params.txhex;
|
||||
const { colors } = useTheme();
|
||||
|
@ -60,7 +61,7 @@ const PsbtWithHardwareWallet = () => {
|
|||
});
|
||||
|
||||
const _combinePSBT = receivedPSBT => {
|
||||
return fromWallet.combinePsbt(psbt, receivedPSBT);
|
||||
return wallet.combinePsbt(psbt, receivedPSBT);
|
||||
};
|
||||
|
||||
const onBarScanned = ret => {
|
||||
|
@ -104,7 +105,7 @@ const PsbtWithHardwareWallet = () => {
|
|||
if (deepLinkPSBT) {
|
||||
const newPsbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
|
||||
try {
|
||||
const Tx = fromWallet.combinePsbt(routeParamsPSBT.current, newPsbt);
|
||||
const Tx = wallet.combinePsbt(routeParamsPSBT.current, newPsbt);
|
||||
setTxHex(Tx.toHex());
|
||||
} catch (Err) {
|
||||
presentAlert({ message: Err });
|
||||
|
@ -128,7 +129,7 @@ const PsbtWithHardwareWallet = () => {
|
|||
try {
|
||||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const result = await fromWallet.broadcastTx(txHex);
|
||||
const result = await wallet.broadcastTx(txHex);
|
||||
if (result) {
|
||||
setIsLoading(false);
|
||||
const txDecoded = bitcoin.Transaction.fromHex(txHex);
|
||||
|
@ -139,7 +140,7 @@ const PsbtWithHardwareWallet = () => {
|
|||
}
|
||||
navigation.navigate('Success', { amount: undefined });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep to make sure network propagates
|
||||
fetchAndSaveWalletTransactions(fromWallet.getID());
|
||||
fetchAndSaveWalletTransactions(wallet.getID());
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
setIsLoading(false);
|
||||
|
@ -214,17 +215,11 @@ const PsbtWithHardwareWallet = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const openScanner = () => {
|
||||
requestCameraAuthorization().then(() => {
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: route.name,
|
||||
showFileImportButton: false,
|
||||
onBarScanned,
|
||||
},
|
||||
});
|
||||
});
|
||||
const openScanner = async () => {
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
}
|
||||
};
|
||||
|
||||
if (txHex) return _renderBroadcastHex();
|
||||
|
|
|
@ -13,12 +13,14 @@ import loc from '../../loc';
|
|||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import HandOffComponent from '../../components/HandOffComponent';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const Success = () => {
|
||||
const pop = () => {
|
||||
getParent().pop();
|
||||
};
|
||||
const { colors } = useTheme();
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
const { getParent } = useNavigation();
|
||||
const { amount, fee, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop, txid } = useRoute().params;
|
||||
const stylesHook = StyleSheet.create({
|
||||
|
@ -52,7 +54,7 @@ const Success = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`https://mempool.space/tx/${txid}`}
|
||||
url={`${selectedBlockExplorer}/tx/${txid}`}
|
||||
/>
|
||||
)}
|
||||
</SafeArea>
|
||||
|
|
|
@ -2,7 +2,6 @@ import { useNavigation } from '@react-navigation/native';
|
|||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import React, { useEffect, useReducer } from 'react';
|
||||
import { ScrollView, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { BlueCard, BlueText } from '../../BlueComponents';
|
||||
import { TWallet } from '../../class/wallets/types';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import useOnAppLaunch from '../../hooks/useOnAppLaunch';
|
||||
|
@ -105,10 +104,9 @@ const DefaultView: React.FC = () => {
|
|||
value: state.isViewAllWalletsSwitchEnabled,
|
||||
disabled: wallets.length <= 0,
|
||||
}}
|
||||
subtitle={loc.settings.default_desc}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.default_desc}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
{!state.isViewAllWalletsSwitchEnabled && (
|
||||
<ListItem
|
||||
title={loc.settings.default_info}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
|
||||
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import ListItem, { TouchableOpacityWrapper } from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
@ -13,6 +11,9 @@ import PromptPasswordConfirmationModal, {
|
|||
PromptPasswordConfirmationModalHandle,
|
||||
} from '../../components/PromptPasswordConfirmationModal';
|
||||
import { popToTop } from '../../NavigationService';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { Header } from '../../components/Header';
|
||||
import { BlueSpacing20 } from '../../BlueComponents';
|
||||
|
||||
enum ActionType {
|
||||
SetLoading = 'SET_LOADING',
|
||||
|
@ -72,9 +73,6 @@ const EncryptStorage = () => {
|
|||
root: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
headerText: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
const initializeState = useCallback(async () => {
|
||||
|
@ -139,15 +137,6 @@ const EncryptStorage = () => {
|
|||
navigate('PlausibleDeniability');
|
||||
};
|
||||
|
||||
const renderPasscodeExplanation = () => {
|
||||
return Platform.OS === 'android' && Platform.Version >= 30 ? (
|
||||
<>
|
||||
<BlueText />
|
||||
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })}</BlueText>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={[styles.root, styleHooks.root]}
|
||||
|
@ -157,26 +146,30 @@ const EncryptStorage = () => {
|
|||
<View style={styles.paddingTop} />
|
||||
{state.deviceBiometricCapable && (
|
||||
<>
|
||||
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
|
||||
{loc.settings.biometrics}
|
||||
</Text>
|
||||
<Header leftText={loc.settings.biometrics} />
|
||||
<ListItem
|
||||
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType! })}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch, disabled: state.currentLoadingSwitch !== null }}
|
||||
switch={{
|
||||
value: biometricEnabled,
|
||||
onValueChange: onUseBiometricSwitch,
|
||||
disabled: state.currentLoadingSwitch !== null,
|
||||
}}
|
||||
isLoading={state.currentLoadingSwitch === 'biometric' && state.isLoading}
|
||||
containerStyle={[styles.row, styleHooks.root]}
|
||||
subtitle={
|
||||
<>
|
||||
<Text style={styles.subtitleText}>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType! })}</Text>
|
||||
{Platform.OS === 'android' && Platform.Version >= 30 && (
|
||||
<Text style={styles.subtitleText}>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })}</Text>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType! })}</BlueText>
|
||||
{renderPasscodeExplanation()}
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
|
||||
{loc.settings.encrypt_tstorage}
|
||||
</Text>
|
||||
<BlueSpacing20 />
|
||||
<Header leftText={loc.settings.encrypt_tstorage} />
|
||||
<ListItem
|
||||
testID="EncyptedAndPasswordProtected"
|
||||
title={loc.settings.encrypt_enc_and_pass}
|
||||
|
@ -211,7 +204,7 @@ const EncryptStorage = () => {
|
|||
dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.SUCCESS });
|
||||
success = true;
|
||||
} catch (error) {
|
||||
presentAlert({ title: loc.errors.error, message: (error as Error).message });
|
||||
presentAlert({ message: (error as Error).message });
|
||||
success = false;
|
||||
}
|
||||
} else if (state.modalType === MODAL_TYPES.ENTER_PASSWORD) {
|
||||
|
@ -243,12 +236,11 @@ const styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
},
|
||||
paddingTop: { paddingTop: 19 },
|
||||
headerText: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 30,
|
||||
marginLeft: 17,
|
||||
},
|
||||
row: { minHeight: 60 },
|
||||
subtitleText: {
|
||||
fontSize: 14,
|
||||
marginTop: 5,
|
||||
},
|
||||
});
|
||||
|
||||
export default EncryptStorage;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useNavigation } from '@react-navigation/native';
|
||||
import React from 'react';
|
||||
import { Platform, ScrollView, StyleSheet } from 'react-native';
|
||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { BlueSpacing20 } from '../../BlueComponents';
|
||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
|
@ -50,14 +50,10 @@ const GeneralSettings: React.FC = () => {
|
|||
title={loc.settings.general_continuity}
|
||||
Component={PressableWrapper}
|
||||
switch={{ onValueChange: onHandOffUseEnabledChange, value: isHandOffUseEnabled }}
|
||||
subtitle={loc.settings.general_continuity_e}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.general_continuity_e}</BlueText>
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
) : null}
|
||||
<BlueSpacing20 />
|
||||
<ListItem
|
||||
Component={PressableWrapper}
|
||||
title="Legacy URv1 QR"
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
import { useNavigation } from '@react-navigation/native';
|
||||
import React from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { isNotificationsCapable } from '../../blue_modules/notifications';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import loc from '../../loc';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const NetworkSettings = () => {
|
||||
const { navigate } = useNavigation();
|
||||
const NetworkSettings: React.FC = () => {
|
||||
const navigation = useExtendedNavigation();
|
||||
|
||||
const navigateToElectrumSettings = () => {
|
||||
navigate('ElectrumSettings');
|
||||
navigation.navigate('ElectrumSettings');
|
||||
};
|
||||
|
||||
const navigateToLightningSettings = () => {
|
||||
navigate('LightningSettings');
|
||||
navigation.navigate('LightningSettings');
|
||||
};
|
||||
|
||||
const navigateToBlockExplorerSettings = () => {
|
||||
navigation.navigate('SettingsBlockExplorer');
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets>
|
||||
<ListItem title={loc.settings.block_explorer} onPress={navigateToBlockExplorerSettings} testID="BlockExplorerSettings" chevron />
|
||||
<ListItem title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} testID="ElectrumSettings" chevron />
|
||||
<ListItem title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} testID="LightningSettings" chevron />
|
||||
{Notifications.isNotificationsCapable && (
|
||||
{isNotificationsCapable && (
|
||||
<ListItem
|
||||
title={loc.settings.notifications}
|
||||
onPress={() => navigate('NotificationSettings')}
|
||||
onPress={() => navigation.navigate('NotificationSettings')}
|
||||
testID="NotificationSettings"
|
||||
chevron
|
||||
/>
|
219
screen/settings/SettingsBlockExplorer.tsx
Normal file
|
@ -0,0 +1,219 @@
|
|||
import React, { useRef, useCallback, useState, useEffect } from 'react';
|
||||
import { SectionList, StyleSheet, TextInput, SectionListRenderItemInfo, SectionListData, View, LayoutAnimation } from 'react-native';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import loc from '../../loc';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import {
|
||||
getBlockExplorersList,
|
||||
BlockExplorer,
|
||||
isValidUrl,
|
||||
normalizeUrl,
|
||||
BLOCK_EXPLORERS,
|
||||
removeBlockExplorer,
|
||||
} from '../../models/blockExplorer';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import SettingsBlockExplorerCustomUrlItem from '../../components/SettingsBlockExplorerCustomUrlListItem';
|
||||
import { Header } from '../../components/Header';
|
||||
|
||||
type BlockExplorerItem = BlockExplorer | string;
|
||||
|
||||
interface SectionData extends SectionListData<BlockExplorerItem> {
|
||||
title?: string;
|
||||
data: BlockExplorerItem[];
|
||||
}
|
||||
|
||||
const SettingsBlockExplorer: React.FC = () => {
|
||||
const { colors } = useTheme();
|
||||
const { selectedBlockExplorer, setBlockExplorerStorage } = useSettings();
|
||||
const customUrlInputRef = useRef<TextInput>(null);
|
||||
const [customUrl, setCustomUrl] = useState<string>(selectedBlockExplorer.key === 'custom' ? selectedBlockExplorer.url : '');
|
||||
const [isCustomEnabled, setIsCustomEnabled] = useState<boolean>(selectedBlockExplorer.key === 'custom');
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
const predefinedExplorers = getBlockExplorersList().filter(explorer => explorer.key !== 'custom');
|
||||
|
||||
const sections: SectionData[] = [
|
||||
{
|
||||
title: loc._.suggested,
|
||||
data: predefinedExplorers,
|
||||
},
|
||||
{
|
||||
title: loc.wallets.details_advanced,
|
||||
data: ['custom'],
|
||||
},
|
||||
];
|
||||
|
||||
const handleExplorerPress = useCallback(
|
||||
async (explorer: BlockExplorer) => {
|
||||
const success = await setBlockExplorerStorage(explorer);
|
||||
if (success) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
setIsCustomEnabled(false);
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.settings.block_explorer_error_saving_custom });
|
||||
}
|
||||
},
|
||||
[setBlockExplorerStorage],
|
||||
);
|
||||
|
||||
const handleCustomUrlChange = useCallback((url: string) => {
|
||||
setCustomUrl(url);
|
||||
}, []);
|
||||
|
||||
const handleSubmitCustomUrl = useCallback(async () => {
|
||||
if (isSubmitting) return;
|
||||
setIsSubmitting(true);
|
||||
const customUrlNormalized = normalizeUrl(customUrl);
|
||||
|
||||
if (!isValidUrl(customUrlNormalized)) {
|
||||
presentAlert({ message: loc.settings.block_explorer_invalid_custom_url });
|
||||
customUrlInputRef.current?.focus();
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const customExplorer: BlockExplorer = {
|
||||
key: 'custom',
|
||||
name: 'Custom',
|
||||
url: customUrlNormalized,
|
||||
};
|
||||
|
||||
const success = await setBlockExplorerStorage(customExplorer);
|
||||
|
||||
if (success) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.settings.block_explorer_error_saving_custom });
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
}, [customUrl, setBlockExplorerStorage, isSubmitting]);
|
||||
|
||||
const handleCustomSwitchToggle = useCallback(
|
||||
async (value: boolean) => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setIsCustomEnabled(value);
|
||||
if (value) {
|
||||
await removeBlockExplorer();
|
||||
customUrlInputRef.current?.focus();
|
||||
} else {
|
||||
const defaultExplorer = BLOCK_EXPLORERS.default;
|
||||
const success = await setBlockExplorerStorage(defaultExplorer);
|
||||
if (success) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
if (!isSubmitting) {
|
||||
presentAlert({ message: loc.settings.block_explorer_error_saving_custom });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[setBlockExplorerStorage, isSubmitting],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (isCustomEnabled) {
|
||||
const customUrlNormalized = normalizeUrl(customUrl);
|
||||
if (!isValidUrl(customUrlNormalized)) {
|
||||
(async () => {
|
||||
const success = await setBlockExplorerStorage(BLOCK_EXPLORERS.default);
|
||||
if (!success) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.settings.block_explorer_error_saving_custom });
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [customUrl, isCustomEnabled, setBlockExplorerStorage]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, section }: SectionListRenderItemInfo<BlockExplorerItem, SectionData>) => {
|
||||
if (section.title === loc._.suggested) {
|
||||
const explorer = item as BlockExplorer;
|
||||
const isSelected = !isCustomEnabled && normalizeUrl(selectedBlockExplorer.url || '') === normalizeUrl(explorer.url || '');
|
||||
return (
|
||||
<ListItem
|
||||
title={explorer.name}
|
||||
onPress={() => handleExplorerPress(explorer)}
|
||||
checkmark={isSelected}
|
||||
disabled={isCustomEnabled}
|
||||
containerStyle={[{ backgroundColor: colors.background }, styles.rowHeight]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<SettingsBlockExplorerCustomUrlItem
|
||||
isCustomEnabled={isCustomEnabled}
|
||||
onSwitchToggle={handleCustomSwitchToggle}
|
||||
customUrl={customUrl}
|
||||
onCustomUrlChange={handleCustomUrlChange}
|
||||
onSubmitCustomUrl={handleSubmitCustomUrl}
|
||||
inputRef={customUrlInputRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
selectedBlockExplorer,
|
||||
isCustomEnabled,
|
||||
handleExplorerPress,
|
||||
colors.background,
|
||||
handleCustomSwitchToggle,
|
||||
customUrl,
|
||||
handleCustomUrlChange,
|
||||
handleSubmitCustomUrl,
|
||||
],
|
||||
);
|
||||
|
||||
// @ts-ignore: renderSectionHeader type is not correct
|
||||
const renderSectionHeader = useCallback(({ section }) => {
|
||||
const { title } = section;
|
||||
if (title) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Header leftText={title} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SectionList<BlockExplorerItem, SectionData>
|
||||
sections={sections}
|
||||
keyExtractor={(item, index) => {
|
||||
if (typeof item === 'string') {
|
||||
return `custom-${index}`;
|
||||
} else {
|
||||
return item.key;
|
||||
}
|
||||
}}
|
||||
renderItem={renderItem}
|
||||
renderSectionHeader={renderSectionHeader}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
automaticallyAdjustContentInsets
|
||||
style={[styles.root, { backgroundColor: colors.background }]}
|
||||
stickySectionHeadersEnabled={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsBlockExplorer;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
paddingTop: 24,
|
||||
},
|
||||
rowHeight: {
|
||||
minHeight: 60,
|
||||
},
|
||||
});
|
|
@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
|||
import { Platform, Pressable, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { openSettings } from 'react-native-permissions';
|
||||
import A from '../../blue_modules/analytics';
|
||||
import { BlueCard, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
|
||||
import { Header } from '../../components/Header';
|
||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
|
@ -10,6 +9,7 @@ import { setBalanceDisplayAllowed } from '../../components/WidgetCommunication';
|
|||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { BlueSpacing20 } from '../../BlueComponents';
|
||||
|
||||
enum SettingsPrivacySection {
|
||||
None,
|
||||
|
@ -45,9 +45,6 @@ const SettingsPrivacy: React.FC = () => {
|
|||
root: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
widgetsHeader: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -130,13 +127,13 @@ const SettingsPrivacy: React.FC = () => {
|
|||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
testID: 'ClipboardSwitch',
|
||||
}}
|
||||
subtitle={
|
||||
<Pressable accessibilityRole="button">
|
||||
<Text style={styles.subtitleText}>{loc.settings.privacy_clipboard_explanation}</Text>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
<BlueCard>
|
||||
<Pressable accessibilityRole="button">
|
||||
<BlueText>{loc.settings.privacy_clipboard_explanation}</BlueText>
|
||||
</Pressable>
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<ListItem
|
||||
title={loc.settings.privacy_quickactions}
|
||||
Component={TouchableWithoutFeedback}
|
||||
|
@ -146,12 +143,14 @@ const SettingsPrivacy: React.FC = () => {
|
|||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||
testID: 'QuickActionsSwitch',
|
||||
}}
|
||||
subtitle={
|
||||
<>
|
||||
<Text style={styles.subtitleText}>{loc.settings.privacy_quickactions_explanation}</Text>
|
||||
{storageIsEncrypted && <Text style={styles.subtitleText}>{loc.settings.encrypted_feature_disabled}</Text>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_quickactions_explanation}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
||||
</BlueCard>
|
||||
|
||||
<ListItem
|
||||
title={loc.total_balance_view.title}
|
||||
Component={PressableWrapper}
|
||||
|
@ -161,11 +160,9 @@ const SettingsPrivacy: React.FC = () => {
|
|||
disabled: isLoading === SettingsPrivacySection.All || wallets.length < 2,
|
||||
testID: 'TotalBalanceSwitch',
|
||||
}}
|
||||
subtitle={<Text style={styles.subtitleText}>{loc.total_balance_view.explanation}</Text>}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.total_balance_view.explanation}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
</BlueCard>
|
||||
|
||||
<ListItem
|
||||
title={loc.settings.privacy_temporary_screenshots}
|
||||
Component={TouchableWithoutFeedback}
|
||||
|
@ -174,24 +171,24 @@ const SettingsPrivacy: React.FC = () => {
|
|||
value: !isPrivacyBlurEnabled,
|
||||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
}}
|
||||
subtitle={<Text style={styles.subtitleText}>{loc.settings.privacy_temporary_screenshots_instructions}</Text>}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_temporary_screenshots_instructions}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
<ListItem
|
||||
title={loc.settings.privacy_do_not_track}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ onValueChange: onDoNotTrackValueChange, value: isDoNotTrackEnabled, disabled: isLoading === SettingsPrivacySection.All }}
|
||||
switch={{
|
||||
onValueChange: onDoNotTrackValueChange,
|
||||
value: isDoNotTrackEnabled,
|
||||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
}}
|
||||
subtitle={<Text style={styles.subtitleText}>{loc.settings.privacy_do_not_track_explanation}</Text>}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_do_not_track_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
{Platform.OS === 'ios' && (
|
||||
<>
|
||||
<BlueSpacing40 />
|
||||
<Text adjustsFontSizeToFit style={[styles.widgetsHeader, styleHooks.widgetsHeader]}>
|
||||
{loc.settings.widgets}
|
||||
</Text>
|
||||
<BlueSpacing20 />
|
||||
<Header leftText={loc.settings.widgets} />
|
||||
<ListItem
|
||||
title={loc.settings.total_balance}
|
||||
Component={TouchableWithoutFeedback}
|
||||
|
@ -200,18 +197,17 @@ const SettingsPrivacy: React.FC = () => {
|
|||
value: storageIsEncrypted ? false : isWidgetBalanceDisplayAllowed,
|
||||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||
}}
|
||||
subtitle={
|
||||
<>
|
||||
<Text style={styles.subtitleText}>{loc.settings.total_balance_explanation}</Text>
|
||||
{storageIsEncrypted && <Text style={styles.subtitleText}>{loc.settings.encrypted_feature_disabled}</Text>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.total_balance_explanation}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
||||
</BlueCard>
|
||||
</>
|
||||
)}
|
||||
|
||||
<BlueSpacing20 />
|
||||
<ListItem title={loc.settings.privacy_system_settings} chevron onPress={openApplicationSettings} testID="PrivacySystemSettings" />
|
||||
<BlueSpacing20 />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
@ -220,14 +216,14 @@ const styles = StyleSheet.create({
|
|||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
widgetsHeader: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 30,
|
||||
marginLeft: 17,
|
||||
},
|
||||
|
||||
headerContainer: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
subtitleText: {
|
||||
fontSize: 14,
|
||||
marginTop: 5,
|
||||
},
|
||||
});
|
||||
|
||||
export default SettingsPrivacy;
|
||||
|
|
|
@ -416,10 +416,9 @@ export default class ElectrumSettings extends Component {
|
|||
value: this.state.isOfflineMode,
|
||||
testID: 'ElectrumConnectionEnabledSwitch',
|
||||
}}
|
||||
subtitle={loc.settings.electrum_offline_description}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.electrum_offline_description}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
{!this.state.isOfflineMode && this.renderElectrumSettings()}
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
|
@ -105,14 +105,11 @@ const NotificationSettings = () => {
|
|||
<ListItem
|
||||
Component={TouchableWithoutFeedback}
|
||||
title={loc.settings.push_notifications}
|
||||
subtitle={loc.settings.groundcontrol_explanation}
|
||||
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.groundcontrol_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
<ButtonRNElements
|
||||
icon={{
|
||||
name: 'github',
|
||||
|
|
|
@ -19,6 +19,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
|||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
|
@ -63,6 +64,7 @@ const TransactionDetails = () => {
|
|||
const { setOptions, navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const { hash, walletID } = useRoute<RouteProps>().params;
|
||||
const { saveToDisk, txMetadata, counterpartyMetadata, wallets, getTransactions } = useStorage();
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
const [from, setFrom] = useState<string[]>([]);
|
||||
const [to, setTo] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
@ -159,7 +161,7 @@ const TransactionDetails = () => {
|
|||
);
|
||||
|
||||
const handleOnOpenTransactionOnBlockExplorerTapped = () => {
|
||||
const url = `https://mempool.space/tx/${tx?.hash}`;
|
||||
const url = `${selectedBlockExplorer}/tx/${tx?.hash}`;
|
||||
Linking.canOpenURL(url)
|
||||
.then(supported => {
|
||||
if (supported) {
|
||||
|
@ -184,7 +186,7 @@ const TransactionDetails = () => {
|
|||
};
|
||||
|
||||
const handleCopyPress = (stringToCopy: string) => {
|
||||
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`);
|
||||
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `${selectedBlockExplorer}/tx/${tx?.hash}`);
|
||||
};
|
||||
|
||||
if (isLoading || !tx) {
|
||||
|
@ -255,7 +257,7 @@ const TransactionDetails = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`https://mempool.space/tx/${tx.hash}`}
|
||||
url={`${selectedBlockExplorer}/tx/${tx.hash}`}
|
||||
/>
|
||||
<BlueCard>
|
||||
<View>
|
||||
|
|
|
@ -21,6 +21,7 @@ import { useStorage } from '../../hooks/context/useStorage';
|
|||
import { HandOffActivityType } from '../../components/types';
|
||||
import HeaderRightButton from '../../components/HeaderRightButton';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
enum ButtonStatus {
|
||||
Possible,
|
||||
|
@ -97,6 +98,7 @@ const TransactionStatus = () => {
|
|||
const { navigate, setOptions, goBack } = useNavigation<TransactionStatusProps['navigation']>();
|
||||
const { colors } = useTheme();
|
||||
const wallet = useRef(wallets.find(w => w.getID() === walletID));
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
const fetchTxInterval = useRef<NodeJS.Timeout>();
|
||||
const stylesHook = StyleSheet.create({
|
||||
value: {
|
||||
|
@ -481,7 +483,7 @@ const TransactionStatus = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`https://mempool.space/tx/${tx.hash}`}
|
||||
url={`${selectedBlockExplorer}/tx/${tx.hash}`}
|
||||
/>
|
||||
|
||||
<View style={styles.container}>
|
||||
|
|
|
@ -25,11 +25,10 @@ import WalletButton from '../../components/WalletButton';
|
|||
import loc from '../../loc';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { Action } from '../../components/types';
|
||||
import { getLNDHub } from '../../helpers/lndHub';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
|
||||
enum ButtonSelected {
|
||||
// @ts-ignore: Return later to update
|
||||
|
@ -228,9 +227,7 @@ const WalletsAdd: React.FC = () => {
|
|||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
<HeaderMenuButton
|
||||
onPressMenuItem={(id: string) => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
if (id === HDSegwitBech32Wallet.type) {
|
||||
|
@ -246,11 +243,9 @@ const WalletsAdd: React.FC = () => {
|
|||
}
|
||||
}}
|
||||
actions={toolTipActions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
/>
|
||||
),
|
||||
[colors.foregroundColor, handleOnLightningButtonPressed, navigateToEntropy, toolTipActions],
|
||||
[handleOnLightningButtonPressed, navigateToEntropy, toolTipActions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||
import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native';
|
||||
import IdleTimerManager from 'react-native-idle-timer';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20 } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet } from '../../class';
|
||||
import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||
import startImport from '../../class/wallet-import';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
|
@ -15,20 +15,44 @@ import prompt from '../../helpers/prompt';
|
|||
import loc from '../../loc';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types';
|
||||
import { navigate } from '../../NavigationService';
|
||||
|
||||
const ImportWalletDiscovery = () => {
|
||||
const navigation = useExtendedNavigation();
|
||||
type RouteProps = RouteProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||
type NavigationProp = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||
|
||||
type TReturn = {
|
||||
cancelled?: boolean;
|
||||
stopped?: boolean;
|
||||
wallets: TWallet[];
|
||||
};
|
||||
|
||||
type ImportTask = {
|
||||
promise: Promise<TReturn>;
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
type WalletEntry = {
|
||||
wallet: TWallet | THDWalletForWatchOnly;
|
||||
subtitle: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const ImportWalletDiscovery: React.FC = () => {
|
||||
const navigation = useExtendedNavigation<NavigationProp>();
|
||||
const { colors } = useTheme();
|
||||
const route = useRoute();
|
||||
const route = useRoute<RouteProps>();
|
||||
const { importText, askPassphrase, searchAccounts } = route.params;
|
||||
const task = useRef();
|
||||
const task = useRef<ImportTask | null>(null);
|
||||
const { addAndSaveWallet } = useStorage();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [wallets, setWallets] = useState([]);
|
||||
const [password, setPassword] = useState();
|
||||
const [selected, setSelected] = useState(0);
|
||||
const [progress, setProgress] = useState();
|
||||
const importing = useRef(false);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [wallets, setWallets] = useState<WalletEntry[]>([]);
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const [selected, setSelected] = useState<number>(0);
|
||||
const [progress, setProgress] = useState<string | undefined>();
|
||||
const importing = useRef<boolean>(false);
|
||||
const bip39 = useMemo(() => {
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(importText);
|
||||
|
@ -44,32 +68,46 @@ const ImportWalletDiscovery = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const saveWallet = wallet => {
|
||||
if (importing.current) return;
|
||||
importing.current = true;
|
||||
addAndSaveWallet(wallet);
|
||||
navigation.getParent().pop();
|
||||
};
|
||||
const saveWallet = useCallback(
|
||||
(wallet: TWallet | THDWalletForWatchOnly) => {
|
||||
if (importing.current) return;
|
||||
importing.current = true;
|
||||
addAndSaveWallet(wallet);
|
||||
navigate('WalletsList');
|
||||
},
|
||||
[addAndSaveWallet],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onProgress = data => setProgress(data);
|
||||
const onProgress = (data: string) => setProgress(data);
|
||||
|
||||
const onWallet = wallet => {
|
||||
const onWallet = (wallet: TWallet | THDWalletForWatchOnly) => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
const id = wallet.getID();
|
||||
let subtitle;
|
||||
let subtitle: string | undefined;
|
||||
|
||||
try {
|
||||
subtitle = wallet.getDerivationPath?.();
|
||||
// For watch-only wallets, display the descriptor or xpub
|
||||
if (wallet.type === WatchOnlyWallet.type) {
|
||||
if (wallet.isHd() && wallet.getSecret()) {
|
||||
subtitle = wallet.getSecret(); // Display descriptor
|
||||
} else {
|
||||
subtitle = wallet.getAddress(); // Display address
|
||||
}
|
||||
} else {
|
||||
subtitle = (wallet as THDWalletForWatchOnly).getDerivationPath?.();
|
||||
}
|
||||
} catch (e) {}
|
||||
setWallets(w => [...w, { wallet, subtitle, id }]);
|
||||
|
||||
setWallets(w => [...w, { wallet, subtitle: subtitle || '', id }]);
|
||||
};
|
||||
|
||||
const onPassword = async (title, subtitle) => {
|
||||
const onPassword = async (title: string, subtitle: string) => {
|
||||
try {
|
||||
const pass = await prompt(title, subtitle);
|
||||
setPassword(pass);
|
||||
return pass;
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Cancel Pressed') {
|
||||
navigation.goBack();
|
||||
}
|
||||
|
@ -84,7 +122,7 @@ const ImportWalletDiscovery = () => {
|
|||
task.current.promise
|
||||
.then(({ cancelled, wallets: w }) => {
|
||||
if (cancelled) return;
|
||||
if (w.length === 1) saveWallet(w[0]); // instantly save wallet if only one has been discovered
|
||||
if (w.length === 1) saveWallet(w[0]); // Instantly save wallet if only one has been discovered
|
||||
if (w.length === 0) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
}
|
||||
|
@ -99,16 +137,19 @@ const ImportWalletDiscovery = () => {
|
|||
IdleTimerManager.setIdleTimerDisabled(false);
|
||||
});
|
||||
|
||||
return () => task.current.stop();
|
||||
return () => {
|
||||
task.current?.stop();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleCustomDerivation = () => {
|
||||
task.current.stop();
|
||||
task.current?.stop();
|
||||
|
||||
navigation.navigate('ImportCustomDerivationPath', { importText, password });
|
||||
};
|
||||
|
||||
const renderItem = ({ item, index }) => (
|
||||
const renderItem = ({ item, index }: { item: WalletEntry; index: number }) => (
|
||||
<WalletToImport
|
||||
key={item.id}
|
||||
title={item.wallet.typeReadable}
|
||||
|
@ -121,22 +162,45 @@ const ImportWalletDiscovery = () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const keyExtractor = w => w.id;
|
||||
const keyExtractor = (w: WalletEntry) => w.id;
|
||||
|
||||
const ListHeaderComponent = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{wallets && wallets.length > 0 ? (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormLabel>{loc.wallets.import_discovery_subtitle}</BlueFormLabel>
|
||||
<BlueSpacing10 />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
[wallets],
|
||||
);
|
||||
|
||||
const ListEmptyComponent = useMemo(
|
||||
() => (
|
||||
<View style={styles.noWallets}>
|
||||
<BlueText style={styles.center}>{loc.wallets.import_discovery_no_wallets}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeArea style={[styles.root, stylesHook.root]}>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormLabel>{loc.wallets.import_discovery_subtitle}</BlueFormLabel>
|
||||
<BlueSpacing20 />
|
||||
|
||||
{!loading && wallets.length === 0 ? (
|
||||
<View style={styles.noWallets}>
|
||||
<BlueFormLabel>{loc.wallets.import_discovery_no_wallets}</BlueFormLabel>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList contentContainerStyle={styles.flatListContainer} data={wallets} keyExtractor={keyExtractor} renderItem={renderItem} />
|
||||
)}
|
||||
|
||||
<FlatList
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
contentContainerStyle={styles.flatListContainer}
|
||||
data={wallets}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
automaticallyAdjustContentInsets
|
||||
contentInsetAdjustmentBehavior="always"
|
||||
/>
|
||||
<View style={[styles.center, stylesHook.center]}>
|
||||
{loading && (
|
||||
<>
|
||||
|
@ -157,9 +221,12 @@ const ImportWalletDiscovery = () => {
|
|||
<BlueSpacing10 />
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
disabled={wallets.length === 0}
|
||||
disabled={wallets?.length === 0}
|
||||
title={loc.wallets.import_do_import}
|
||||
onPress={() => saveWallet(wallets[selected].wallet)}
|
||||
onPress={() => {
|
||||
if (wallets.length === 0) return;
|
||||
saveWallet(wallets[selected].wallet);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -169,7 +236,6 @@ const ImportWalletDiscovery = () => {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
paddingTop: 40,
|
||||
flex: 1,
|
||||
},
|
||||
flatListContainer: {
|
|
@ -499,7 +499,7 @@ const ManageWallets: React.FC = () => {
|
|||
|
||||
return (
|
||||
<GestureHandlerRootView style={[{ backgroundColor: colors.background }, styles.root]}>
|
||||
<NestableScrollContainer contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets>
|
||||
<NestableScrollContainer contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets scrollEnabled>
|
||||
{renderHeader}
|
||||
<NestableDraggableFlatList
|
||||
data={state.tempOrder.filter((item): item is WalletItem => item.type === ItemType.WalletSection)}
|
||||
|
|
|
@ -128,17 +128,23 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
}
|
||||
}, [wallet, isElectrumDisabled, isLoading, saveToDisk, pageSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet && wallet.getLastTxFetch() === 0) {
|
||||
refreshTransactions();
|
||||
}
|
||||
}, [wallet, refreshTransactions]);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
if (wallet && wallet.getLastTxFetch() === 0) {
|
||||
refreshTransactions();
|
||||
}
|
||||
});
|
||||
|
||||
return () => task.cancel();
|
||||
}, [refreshTransactions, wallet]),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet) {
|
||||
setSelectedWalletID(wallet.getID());
|
||||
setSelectedWalletID(walletID);
|
||||
}
|
||||
}, [wallet, setSelectedWalletID]);
|
||||
}, [wallet, setSelectedWalletID, walletID]);
|
||||
|
||||
const isLightning = (): boolean => wallet?.chain === Chain.OFFCHAIN || false;
|
||||
|
||||
|
@ -173,7 +179,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
navigate('SendDetailsRoot', {
|
||||
screen: 'SendDetails',
|
||||
params: {
|
||||
walletID: wallet?.getID(),
|
||||
walletID,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -182,8 +188,8 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
assert(wallet?.type === LightningCustodianWallet.type, `internal error, wallet is not ${LightningCustodianWallet.type}`);
|
||||
navigate('WalletTransactions', {
|
||||
walletType: wallet?.type,
|
||||
walletID: wallet?.getID(),
|
||||
key: `WalletTransactions-${wallet?.getID()}`,
|
||||
walletID,
|
||||
key: `WalletTransactions-${walletID}`,
|
||||
}); // navigating back to ln wallet screen
|
||||
|
||||
// getting refill address, either cached or from the server:
|
||||
|
@ -252,7 +258,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
const params = {
|
||||
walletID: wallet?.getID(),
|
||||
walletID,
|
||||
uri: ret?.data ? ret.data : ret,
|
||||
};
|
||||
if (wallet?.chain === Chain.ONCHAIN) {
|
||||
|
@ -263,7 +269,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[wallet, navigate, isLoading],
|
||||
[isLoading, walletID, wallet?.chain, navigate],
|
||||
);
|
||||
|
||||
const choosePhoto = () => {
|
||||
|
@ -288,7 +294,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
|
||||
const sendButtonPress = () => {
|
||||
if (wallet?.chain === Chain.OFFCHAIN) {
|
||||
return navigate('ScanLndInvoiceRoot', { screen: 'ScanLndInvoice', params: { walletID: wallet.getID() } });
|
||||
return navigate('ScanLndInvoiceRoot', { screen: 'ScanLndInvoice', params: { walletID } });
|
||||
}
|
||||
|
||||
if (wallet?.type === WatchOnlyWallet.type && wallet.isHd() && !wallet.useWithHardwareWalletEnabled()) {
|
||||
|
@ -407,7 +413,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
navigate('WalletExportRoot', {
|
||||
screen: 'WalletExport',
|
||||
params: {
|
||||
walletID: wallet!.getID(),
|
||||
walletID,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -460,9 +466,9 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
|||
text={loc.receive.header}
|
||||
onPress={() => {
|
||||
if (wallet.chain === Chain.OFFCHAIN) {
|
||||
navigate('LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID: wallet.getID() } });
|
||||
navigate('LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID } });
|
||||
} else {
|
||||
navigate('ReceiveDetailsRoot', { screen: 'ReceiveDetails', params: { walletID: wallet.getID() } });
|
||||
navigate('ReceiveDetailsRoot', { screen: 'ReceiveDetails', params: { walletID } });
|
||||
}
|
||||
}}
|
||||
icon={
|
||||
|
|
|
@ -469,8 +469,10 @@ const WalletsAddMultisigStep2 = () => {
|
|||
|
||||
const scanOrOpenFile = async () => {
|
||||
await provideMnemonicsModalRef.current.dismiss();
|
||||
const scanned = await scanQrHelper(name, true, undefined);
|
||||
onBarScanned({ data: scanned });
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
if (scanned) {
|
||||
onBarScanned(scanned);
|
||||
}
|
||||
};
|
||||
|
||||
const dashType = ({ index, lastIndex, isChecked, isFocus }) => {
|
||||
|
|
|
@ -4,18 +4,17 @@ import { Keyboard, Platform, StyleSheet, TouchableWithoutFeedback, View, ScrollV
|
|||
import { BlueButtonLink, BlueFormLabel, BlueFormMultiInput, BlueSpacing20 } from '../../BlueComponents';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import loc from '../../loc';
|
||||
import {
|
||||
DoneAndDismissKeyboardInputAccessory,
|
||||
DoneAndDismissKeyboardInputAccessoryViewID,
|
||||
} from '../../components/DoneAndDismissKeyboardInputAccessory';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
|
||||
const WalletsImport = () => {
|
||||
const navigation = useExtendedNavigation();
|
||||
|
@ -97,16 +96,11 @@ const WalletsImport = () => {
|
|||
setTimeout(() => importMnemonic(value), 500);
|
||||
};
|
||||
|
||||
const importScan = () => {
|
||||
requestCameraAuthorization().then(() =>
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: route.name,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const importScan = async () => {
|
||||
const data = await scanQrHelper(navigation, true);
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
}
|
||||
};
|
||||
|
||||
const speedBackdoorTap = () => {
|
||||
|
@ -140,18 +134,8 @@ const WalletsImport = () => {
|
|||
}, [askPassphrase, searchAccounts]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
testID="HeaderRightButton"
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={toolTipOnPressMenuItem}
|
||||
actions={toolTipActions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
[toolTipOnPressMenuItem, toolTipActions, colors.foregroundColor],
|
||||
() => <HeaderMenuButton onPressMenuItem={toolTipOnPressMenuItem} actions={toolTipActions} />,
|
||||
[toolTipOnPressMenuItem, toolTipActions],
|
||||
);
|
||||
|
||||
// Adding the ToolTipMenu to the header
|
||||
|
|
|
@ -178,6 +178,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await yo('WalletsList');
|
||||
await expect(element(by.id('cr34t3d'))).toBeVisible();
|
||||
await element(by.id('cr34t3d')).tap();
|
||||
await yo('ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
await element(by.text('Yes, I have.')).tap();
|
||||
try {
|
||||
|
@ -506,7 +507,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await element(by.id('CreateButton')).tap();
|
||||
await yo('Multisig Vault');
|
||||
await element(by.id('Multisig Vault')).tap(); // go inside the wallet
|
||||
|
||||
await yo('ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
await element(by.text('Yes, I have.')).tap();
|
||||
try {
|
||||
|
@ -571,6 +572,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
|
||||
// sending...
|
||||
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
|
@ -677,9 +679,9 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await element(by.id('MnemonicInput')).replaceText(
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
);
|
||||
await element(by.id('HeaderRightButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Passphrase')).tap();
|
||||
await element(by.id('HeaderRightButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Search accounts')).tap();
|
||||
await element(by.id('DoImport')).tap();
|
||||
await sleep(1000);
|
||||
|
|
|
@ -40,6 +40,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
|
||||
// lets create real transaction:
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
|
||||
|
@ -188,6 +189,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
|
||||
// go inside the wallet
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
|
||||
// lets create real transaction:
|
||||
|
@ -202,23 +204,23 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await element(by.text('OK')).tap();
|
||||
|
||||
// lest add another two outputs
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction1'); // adding a recipient autoscrolls it to the last one
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).replaceText('0.0002\n');
|
||||
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
|
||||
|
||||
// remove last output, check if second output is shown
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Remove Recipient')).tap();
|
||||
await yo('Transaction1');
|
||||
|
||||
// adding it again
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction2'))).replaceText('bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7');
|
||||
|
@ -227,7 +229,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
// remove second output
|
||||
await element(by.id('Transaction2')).swipe('right', 'fast', NaN, 0.2);
|
||||
await sleep(5000);
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Remove Recipient')).tap();
|
||||
|
||||
// creating and verifying. tx should have 3 outputs
|
||||
|
@ -262,7 +264,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
|
||||
// go inside the wallet
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
|
||||
// set fee rate
|
||||
|
@ -275,7 +277,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
// first send MAX output
|
||||
await element(by.id('AddressInput')).replaceText('bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
|
||||
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Use Full Balance')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
|
@ -294,7 +296,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
// add second output with amount
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction1');
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
|
@ -332,9 +334,10 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
|
||||
// go inside the wallet
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Sign a transaction')).tap();
|
||||
|
||||
// tapping 5 times invisible button is a backdoor:
|
||||
|
@ -391,6 +394,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
|
||||
// go to receive screen and check that payment code is there
|
||||
|
||||
await yo('ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
|
||||
try {
|
||||
|
@ -451,15 +455,17 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
await yo('SendButton');
|
||||
|
||||
await element(by.id('SendButton')).tap();
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Insert Contact')).tap();
|
||||
await element(by.id('ContactListItem0')).tap();
|
||||
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
|
||||
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Insert Contact')).tap();
|
||||
await element(by.id('ContactListItem1')).tap();
|
||||
await element(by.id('BitcoinAmountInput')).atIndex(1).typeText('0.0002\n');
|
||||
|
@ -600,8 +606,9 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await device.launchApp({ newInstance: true });
|
||||
await yo('WalletsList');
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Coin Control')).tap();
|
||||
await waitFor(element(by.id('Loading'))) // wait for outputs to be loaded
|
||||
.not.toExist()
|
||||
|
@ -620,7 +627,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await element(by.text('test2')).atIndex(0).tap();
|
||||
await element(by.id('UseCoin')).tap();
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Use Full Balance')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
// setting fee rate:
|
||||
|
@ -647,9 +654,10 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await device.pressBack();
|
||||
|
||||
// create tx with unfrozen input
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Use Full Balance')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
// setting fee rate:
|
||||
|
|
|
@ -29,7 +29,6 @@ describe('BlueWallet UI Tests - import Watch-only wallet (zpub)', () => {
|
|||
'0.0001',
|
||||
);
|
||||
await sleep(15000);
|
||||
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
try {
|
||||
// in case emulator has no google services and doesnt support pushes
|
||||
|
@ -51,11 +50,10 @@ describe('BlueWallet UI Tests - import Watch-only wallet (zpub)', () => {
|
|||
|
||||
await expect(element(by.text('bitcoin:bc1qc8wun6lf9vcajpddtgdpd2pdrp0kwp29j6upgv?amount=1&label=Test'))).toBeVisible();
|
||||
await device.pressBack();
|
||||
|
||||
await element(by.id('SendButton')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
await element(by.id('advancedOptionsMenuButton')).tap();
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Import Transaction (QR)')).tap(); // opens camera
|
||||
|
||||
// produced by real Keystone device using MNEMONICS_KEYSTONE
|
||||
|
|