mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
commit
e51433105b
@ -27,8 +27,11 @@ function Notifications(props) {
|
||||
let token = await AsyncStorage.getItem(PUSH_TOKEN);
|
||||
token = JSON.parse(token);
|
||||
return token;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
AsyncStorage.removeItem(PUSH_TOKEN);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -287,7 +290,21 @@ function Notifications(props) {
|
||||
};
|
||||
|
||||
Notifications.getSavedUri = async function () {
|
||||
return AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
|
||||
try {
|
||||
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
|
||||
if (baseUriStored) {
|
||||
baseURI = baseUriStored;
|
||||
}
|
||||
return baseUriStored;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
try {
|
||||
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri);
|
||||
} catch (storageError) {
|
||||
console.error('Failed to reset URI:', storageError);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -385,7 +402,16 @@ function Notifications(props) {
|
||||
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
|
||||
notifications = JSON.parse(stringified);
|
||||
if (!Array.isArray(notifications)) notifications = [];
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.error('Invalid notifications format:', e);
|
||||
notifications = [];
|
||||
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, '[]');
|
||||
} else {
|
||||
console.error('Error accessing notifications:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return notifications;
|
||||
};
|
||||
@ -396,7 +422,11 @@ function Notifications(props) {
|
||||
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
|
||||
notifications = JSON.parse(stringified);
|
||||
if (!Array.isArray(notifications)) notifications = [];
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Start fresh with just the new notification
|
||||
notifications = [];
|
||||
}
|
||||
|
||||
notifications.push(notification);
|
||||
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications));
|
||||
@ -420,7 +450,11 @@ function Notifications(props) {
|
||||
app_version: appVersion,
|
||||
}),
|
||||
});
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await AsyncStorage.setItem('lang', 'en');
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
Notifications.clearStoredNotifications = async function () {
|
||||
@ -455,7 +489,13 @@ function Notifications(props) {
|
||||
if (baseUriStored) {
|
||||
baseURI = baseUriStored;
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.warn('Failed to load custom URI, falling back to default');
|
||||
baseURI = groundControlUri;
|
||||
// Attempt to reset in background
|
||||
AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri).catch(err => console.error('Failed to reset URI:', err));
|
||||
}
|
||||
|
||||
// every launch should clear badges:
|
||||
Notifications.setApplicationIconBadgeNumber(0);
|
||||
|
@ -2,8 +2,7 @@ import 'react-native-gesture-handler'; // should be on top
|
||||
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import React, { lazy, Suspense, useCallback, useEffect, useRef } from 'react';
|
||||
import { AppState, AppStateStatus, Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
||||
|
||||
import { AppState, AppStateStatus, Linking } from 'react-native';
|
||||
import A from '../blue_modules/analytics';
|
||||
import BlueClipboard from '../blue_modules/clipboard';
|
||||
import { updateExchangeRate } from '../blue_modules/currency';
|
||||
@ -18,15 +17,13 @@ import ActionSheet from '../screen/ActionSheet';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import RNQRGenerator from 'rn-qr-generator';
|
||||
import presentAlert from './Alert';
|
||||
import useMenuElements from '../hooks/useMenuElements';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import useWidgetCommunication from '../hooks/useWidgetCommunication';
|
||||
import useWatchConnectivity from '../hooks/useWatchConnectivity';
|
||||
|
||||
const MenuElements = lazy(() => import('../components/MenuElements'));
|
||||
const DeviceQuickActions = lazy(() => import('../components/DeviceQuickActions'));
|
||||
const HandOffComponentListener = lazy(() => import('../components/HandOffComponentListener'));
|
||||
const WidgetCommunication = lazy(() => import('../components/WidgetCommunication'));
|
||||
const WatchConnectivity = lazy(() => import('./WatchConnectivity'));
|
||||
|
||||
// @ts-ignore: NativeModules.EventEmitter is not typed
|
||||
const eventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined;
|
||||
|
||||
const ClipboardContentType = Object.freeze({
|
||||
BITCOIN: 'BITCOIN',
|
||||
@ -36,8 +33,14 @@ const ClipboardContentType = Object.freeze({
|
||||
const CompanionDelegates = () => {
|
||||
const { wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions, setSharedCosigner } = useStorage();
|
||||
const appState = useRef<AppStateStatus>(AppState.currentState);
|
||||
const { isHandOffUseEnabled, isQuickActionsEnabled } = useSettings();
|
||||
const clipboardContent = useRef<undefined | string>();
|
||||
|
||||
useWatchConnectivity();
|
||||
|
||||
useWidgetCommunication();
|
||||
useMenuElements();
|
||||
|
||||
const processPushNotifications = useCallback(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
@ -223,32 +226,15 @@ const CompanionDelegates = () => {
|
||||
[processPushNotifications, showClipboardAlert, wallets],
|
||||
);
|
||||
|
||||
const onNotificationReceived = useCallback(
|
||||
async (notification: { data: { data: any } }) => {
|
||||
const payload = Object.assign({}, notification, notification.data);
|
||||
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
payload.foreground = true;
|
||||
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
await Notifications.addNotification(payload);
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
if (payload.foreground) await processPushNotifications();
|
||||
},
|
||||
[processPushNotifications],
|
||||
);
|
||||
|
||||
const addListeners = useCallback(() => {
|
||||
const urlSubscription = Linking.addEventListener('url', handleOpenURL);
|
||||
const appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
|
||||
const notificationSubscription = eventEmitter?.addListener('onNotificationReceived', onNotificationReceived);
|
||||
|
||||
return {
|
||||
urlSubscription,
|
||||
appStateSubscription,
|
||||
notificationSubscription,
|
||||
};
|
||||
}, [handleOpenURL, handleAppStateChange, onNotificationReceived]);
|
||||
}, [handleOpenURL, handleAppStateChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = addListeners();
|
||||
@ -256,7 +242,6 @@ const CompanionDelegates = () => {
|
||||
return () => {
|
||||
subscriptions.urlSubscription?.remove();
|
||||
subscriptions.appStateSubscription?.remove();
|
||||
subscriptions.notificationSubscription?.remove();
|
||||
};
|
||||
}, [addListeners]);
|
||||
|
||||
@ -264,11 +249,8 @@ const CompanionDelegates = () => {
|
||||
<>
|
||||
<Notifications onProcessNotifications={processPushNotifications} />
|
||||
<Suspense fallback={null}>
|
||||
<MenuElements />
|
||||
<DeviceQuickActions />
|
||||
<HandOffComponentListener />
|
||||
<WidgetCommunication />
|
||||
<WatchConnectivity />
|
||||
{isQuickActionsEnabled && <DeviceQuickActions />}
|
||||
{isHandOffUseEnabled && <HandOffComponentListener />}
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
@ -9,12 +9,12 @@ import { saveLanguage, STORAGE_KEY } from '../../loc';
|
||||
import { FiatUnit, TFiatUnit } from '../../models/fiatUnit';
|
||||
import { getEnabled as getIsDeviceQuickActionsEnabled, setEnabled as setIsDeviceQuickActionsEnabled } from '../DeviceQuickActions';
|
||||
import { getIsHandOffUseEnabled, setIsHandOffUseEnabled } from '../HandOffComponent';
|
||||
import { isBalanceDisplayAllowed, setBalanceDisplayAllowed } from '../WidgetCommunication';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { TotalWalletsBalanceKey, TotalWalletsBalancePreferredUnit } from '../TotalWalletsBalance';
|
||||
import { BLOCK_EXPLORERS, getBlockExplorerUrl, saveBlockExplorer, BlockExplorer, normalizeUrl } from '../../models/blockExplorer';
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import { isBalanceDisplayAllowed, setBalanceDisplayAllowed } from '../../hooks/useWidgetCommunication';
|
||||
|
||||
const getDoNotTrackStorage = async (): Promise<boolean> => {
|
||||
try {
|
||||
@ -77,7 +77,7 @@ interface SettingsContextType {
|
||||
isHandOffUseEnabled: boolean;
|
||||
setIsHandOffUseEnabledAsyncStorage: (value: boolean) => Promise<void>;
|
||||
isPrivacyBlurEnabled: boolean;
|
||||
setIsPrivacyBlurEnabledState: (value: boolean) => void;
|
||||
setIsPrivacyBlurEnabled: (value: boolean) => void;
|
||||
isDoNotTrackEnabled: boolean;
|
||||
setDoNotTrackStorage: (value: boolean) => Promise<void>;
|
||||
isWidgetBalanceDisplayAllowed: boolean;
|
||||
@ -108,7 +108,7 @@ const defaultSettingsContext: SettingsContextType = {
|
||||
isHandOffUseEnabled: false,
|
||||
setIsHandOffUseEnabledAsyncStorage: async () => {},
|
||||
isPrivacyBlurEnabled: true,
|
||||
setIsPrivacyBlurEnabledState: () => {},
|
||||
setIsPrivacyBlurEnabled: () => {},
|
||||
isDoNotTrackEnabled: false,
|
||||
setDoNotTrackStorage: async () => {},
|
||||
isWidgetBalanceDisplayAllowed: true,
|
||||
@ -314,16 +314,6 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.m
|
||||
console.error('Error setting isQuickActionsEnabled:', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setIsPrivacyBlurEnabledState = useCallback((value: boolean): void => {
|
||||
try {
|
||||
setIsPrivacyBlurEnabled(value);
|
||||
console.debug(`Privacy blur: ${value}`);
|
||||
} catch (e) {
|
||||
console.error('Error setting isPrivacyBlurEnabled:', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setIsTotalBalanceEnabledStorage = useCallback(async (value: boolean): Promise<void> => {
|
||||
try {
|
||||
await setTotalBalanceViewEnabledStorage(value);
|
||||
@ -364,7 +354,7 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.m
|
||||
isHandOffUseEnabled,
|
||||
setIsHandOffUseEnabledAsyncStorage,
|
||||
isPrivacyBlurEnabled,
|
||||
setIsPrivacyBlurEnabledState,
|
||||
setIsPrivacyBlurEnabled,
|
||||
isDoNotTrackEnabled,
|
||||
setDoNotTrackStorage,
|
||||
isWidgetBalanceDisplayAllowed,
|
||||
@ -394,7 +384,7 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.m
|
||||
isHandOffUseEnabled,
|
||||
setIsHandOffUseEnabledAsyncStorage,
|
||||
isPrivacyBlurEnabled,
|
||||
setIsPrivacyBlurEnabledState,
|
||||
setIsPrivacyBlurEnabled,
|
||||
isDoNotTrackEnabled,
|
||||
setDoNotTrackStorage,
|
||||
isWidgetBalanceDisplayAllowed,
|
||||
|
@ -35,8 +35,6 @@ interface StorageContextType {
|
||||
resetWallets: () => void;
|
||||
walletTransactionUpdateStatus: WalletTransactionsStatus | string;
|
||||
setWalletTransactionUpdateStatus: (status: WalletTransactionsStatus | string) => void;
|
||||
reloadTransactionsMenuActionFunction: () => void;
|
||||
setReloadTransactionsMenuActionFunction: (func: () => void) => void;
|
||||
getTransactions: typeof BlueApp.getTransactions;
|
||||
fetchWalletBalances: typeof BlueApp.fetchWalletBalances;
|
||||
fetchWalletTransactions: typeof BlueApp.fetchWalletTransactions;
|
||||
@ -72,7 +70,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
);
|
||||
const [walletsInitialized, setWalletsInitialized] = useState<boolean>(false);
|
||||
const [currentSharedCosigner, setCurrentSharedCosigner] = useState<string>('');
|
||||
const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState<() => void>(() => {});
|
||||
|
||||
const saveToDisk = useCallback(
|
||||
async (force: boolean = false) => {
|
||||
@ -273,8 +270,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
isPasswordInUse: BlueApp.isPasswordInUse,
|
||||
walletTransactionUpdateStatus,
|
||||
setWalletTransactionUpdateStatus,
|
||||
reloadTransactionsMenuActionFunction,
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
}),
|
||||
[
|
||||
wallets,
|
||||
@ -293,8 +288,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
resetWallets,
|
||||
walletTransactionUpdateStatus,
|
||||
setWalletTransactionUpdateStatus,
|
||||
reloadTransactionsMenuActionFunction,
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -1,69 +0,0 @@
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
|
||||
/*
|
||||
Component for iPadOS and macOS menu items with keyboard shortcuts.
|
||||
EventEmitter on the native side should receive a payload and rebuild menus.
|
||||
*/
|
||||
|
||||
const eventEmitter = Platform.OS === 'ios' || Platform.OS === 'macos' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined;
|
||||
const MenuElements = () => {
|
||||
const { walletsInitialized, reloadTransactionsMenuActionFunction } = useStorage();
|
||||
|
||||
// BlueWallet -> Settings
|
||||
const openSettings = useCallback(() => {
|
||||
dispatchNavigate('Settings');
|
||||
}, []);
|
||||
|
||||
// File -> Add Wallet
|
||||
const addWalletMenuAction = useCallback(() => {
|
||||
dispatchNavigate('AddWalletRoot');
|
||||
}, []);
|
||||
|
||||
// File -> Add Wallet
|
||||
const importWalletMenuAction = useCallback(() => {
|
||||
dispatchNavigate('AddWalletRoot', 'ImportWallet');
|
||||
}, []);
|
||||
|
||||
const dispatchNavigate = (routeName: string, screen?: string) => {
|
||||
const action = screen
|
||||
? CommonActions.navigate({
|
||||
name: routeName,
|
||||
params: { screen },
|
||||
})
|
||||
: CommonActions.navigate({
|
||||
name: routeName,
|
||||
});
|
||||
|
||||
NavigationService.dispatch(action);
|
||||
};
|
||||
|
||||
const reloadTransactionsMenuElementsFunction = useCallback(() => {
|
||||
if (reloadTransactionsMenuActionFunction && typeof reloadTransactionsMenuActionFunction === 'function') {
|
||||
reloadTransactionsMenuActionFunction();
|
||||
}
|
||||
}, [reloadTransactionsMenuActionFunction]);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug('MenuElements: useEffect');
|
||||
if (walletsInitialized) {
|
||||
eventEmitter?.addListener('openSettings', openSettings);
|
||||
eventEmitter?.addListener('addWalletMenuAction', addWalletMenuAction);
|
||||
eventEmitter?.addListener('importWalletMenuAction', importWalletMenuAction);
|
||||
eventEmitter?.addListener('reloadTransactionsMenuAction', reloadTransactionsMenuElementsFunction);
|
||||
}
|
||||
return () => {
|
||||
eventEmitter?.removeAllListeners('openSettings');
|
||||
eventEmitter?.removeAllListeners('addWalletMenuAction');
|
||||
eventEmitter?.removeAllListeners('importWalletMenuAction');
|
||||
eventEmitter?.removeAllListeners('reloadTransactionsMenuAction');
|
||||
};
|
||||
}, [addWalletMenuAction, importWalletMenuAction, openSettings, reloadTransactionsMenuElementsFunction, walletsInitialized]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MenuElements;
|
@ -1,5 +0,0 @@
|
||||
const MenuElements = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MenuElements;
|
@ -1,246 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import {
|
||||
transferCurrentComplicationUserInfo,
|
||||
transferUserInfo,
|
||||
updateApplicationContext,
|
||||
useInstalled,
|
||||
useReachability,
|
||||
watchEvents,
|
||||
} from 'react-native-watch-connectivity';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
import { MultisigHDWallet } from '../class';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import { FiatUnit } from '../models/fiatUnit';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
|
||||
function WatchConnectivity() {
|
||||
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useStorage();
|
||||
const { preferredFiatCurrency } = useSettings();
|
||||
const isReachable = useReachability();
|
||||
const isInstalled = useInstalled(); // true | false
|
||||
const messagesListenerActive = useRef(false);
|
||||
const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey);
|
||||
|
||||
useEffect(() => {
|
||||
let messagesListener = () => {};
|
||||
if (isInstalled && isReachable && walletsInitialized && messagesListenerActive.current === false) {
|
||||
messagesListener = watchEvents.addListener('message', handleMessages);
|
||||
messagesListenerActive.current = true;
|
||||
} else {
|
||||
messagesListener();
|
||||
messagesListenerActive.current = false;
|
||||
}
|
||||
return () => {
|
||||
messagesListener();
|
||||
messagesListenerActive.current = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, isReachable, isInstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`Apple Watch: isInstalled: ${isInstalled}, isReachable: ${isReachable}, walletsInitialized: ${walletsInitialized}`);
|
||||
if (isInstalled && walletsInitialized) {
|
||||
constructWalletsToSendToWatch().then(walletsToProcess => {
|
||||
if (walletsToProcess) {
|
||||
if (isReachable) {
|
||||
transferUserInfo(walletsToProcess);
|
||||
console.log('Apple Watch: sent info to watch transferUserInfo');
|
||||
} else {
|
||||
updateApplicationContext(walletsToProcess);
|
||||
console.log('Apple Watch: sent info to watch context');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, wallets, isReachable, isInstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized && isReachable && isInstalled) {
|
||||
updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) });
|
||||
}
|
||||
}, [isInstalled, isReachable, walletsInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInstalled && isReachable && walletsInitialized && preferredFiatCurrency) {
|
||||
const preferredFiatCurrencyParsed = preferredFiatCurrency ?? FiatUnit.USD;
|
||||
try {
|
||||
if (lastPreferredCurrency.current !== preferredFiatCurrencyParsed.endPointKey) {
|
||||
transferCurrentComplicationUserInfo({
|
||||
preferredFiatCurrency: preferredFiatCurrencyParsed.endPointKey,
|
||||
});
|
||||
lastPreferredCurrency.current = preferredFiatCurrency.endPointKey;
|
||||
} else {
|
||||
console.log('WatchConnectivity lastPreferredCurrency has not changed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('WatchConnectivity useEffect preferredFiatCurrency error');
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled]);
|
||||
|
||||
const handleMessages = (message, reply) => {
|
||||
if (message.request === 'createInvoice') {
|
||||
handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description)
|
||||
.then(createInvoiceRequest => reply({ invoicePaymentRequest: createInvoiceRequest }))
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
reply({});
|
||||
});
|
||||
} else if (message.message === 'sendApplicationContext') {
|
||||
constructWalletsToSendToWatch().then(walletsToProcess => {
|
||||
if (walletsToProcess) {
|
||||
updateApplicationContext(walletsToProcess);
|
||||
}
|
||||
});
|
||||
} else if (message.message === 'fetchTransactions') {
|
||||
fetchWalletTransactions()
|
||||
.then(() => saveToDisk())
|
||||
.finally(() => reply({}));
|
||||
} else if (message.message === 'hideBalance') {
|
||||
const walletIndex = message.walletIndex;
|
||||
const wallet = wallets[walletIndex];
|
||||
wallet.hideBalance = message.hideBalance;
|
||||
saveToDisk().finally(() => reply({}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLightningInvoiceCreateRequest = async (walletIndex, amount, description = loc.lnd.placeholder) => {
|
||||
const wallet = wallets[walletIndex];
|
||||
if (wallet.allowReceive() && amount > 0) {
|
||||
try {
|
||||
const invoiceRequest = await wallet.addInvoice(amount, description);
|
||||
|
||||
// lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid
|
||||
try {
|
||||
// Let's verify if notifications are already configured. Otherwise the watch app will freeze waiting for user approval in iOS app
|
||||
if (await Notifications.isNotificationsEnabled()) {
|
||||
const decoded = await wallet.decodeInvoice(invoiceRequest);
|
||||
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('WatchConnectivity - Running in Simulator');
|
||||
console.log(e);
|
||||
}
|
||||
return invoiceRequest;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const constructWalletsToSendToWatch = async () => {
|
||||
if (!Array.isArray(wallets)) {
|
||||
console.log('No Wallets set to sync with Watch app. Exiting...');
|
||||
return;
|
||||
}
|
||||
if (!walletsInitialized) {
|
||||
console.log('Wallets not initialized. Exiting...');
|
||||
return;
|
||||
}
|
||||
const walletsToProcess = [];
|
||||
|
||||
for (const wallet of wallets) {
|
||||
let receiveAddress;
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
try {
|
||||
receiveAddress = await wallet.getAddressAsync();
|
||||
} catch (_) {}
|
||||
if (!receiveAddress) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
receiveAddress = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
|
||||
}
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
try {
|
||||
await wallet.getAddressAsync();
|
||||
receiveAddress = wallet.getAddress();
|
||||
} catch (_) {}
|
||||
if (!receiveAddress) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
receiveAddress = wallet.getAddress();
|
||||
}
|
||||
}
|
||||
const transactions = wallet.getTransactions(10);
|
||||
const watchTransactions = [];
|
||||
for (const transaction of transactions) {
|
||||
let type = 'pendingConfirmation';
|
||||
let memo = '';
|
||||
let amount = 0;
|
||||
|
||||
if ('confirmations' in transaction && !(transaction.confirmations > 0)) {
|
||||
type = 'pendingConfirmation';
|
||||
} else if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = transaction.timestamp + transaction.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
type = 'pendingConfirmation';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (transaction.ispaid) {
|
||||
type = 'received';
|
||||
} else {
|
||||
type = 'sent';
|
||||
}
|
||||
}
|
||||
} else if (transaction.value / 100000000 < 0) {
|
||||
type = 'sent';
|
||||
} else {
|
||||
type = 'received';
|
||||
}
|
||||
if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
|
||||
amount = isNaN(transaction.value) ? '0' : amount;
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = transaction.timestamp + transaction.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (transaction.ispaid) {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else {
|
||||
amount = loc.lnd.expired;
|
||||
}
|
||||
} else {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
}
|
||||
} else {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
}
|
||||
if (txMetadata[transaction.hash] && txMetadata[transaction.hash].memo) {
|
||||
memo = txMetadata[transaction.hash].memo;
|
||||
} else if (transaction.memo) {
|
||||
memo = transaction.memo;
|
||||
}
|
||||
const watchTX = { type, amount, memo, time: transactionTimeToReadable(transaction.received) };
|
||||
watchTransactions.push(watchTX);
|
||||
}
|
||||
|
||||
const walletInformation = {
|
||||
label: wallet.getLabel(),
|
||||
balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
|
||||
type: wallet.type,
|
||||
preferredBalanceUnit: wallet.getPreferredBalanceUnit(),
|
||||
receiveAddress,
|
||||
transactions: watchTransactions,
|
||||
hideBalance: wallet.hideBalance,
|
||||
};
|
||||
if (wallet.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type) {
|
||||
walletInformation.xpub = wallet.getXpub() ? wallet.getXpub() : wallet.getSecret();
|
||||
}
|
||||
if (wallet.allowBIP47() && wallet.isBIP47Enabled()) {
|
||||
walletInformation.paymentCode = wallet.getBIP47PaymentCode();
|
||||
}
|
||||
walletsToProcess.push(walletInformation);
|
||||
}
|
||||
return { wallets: walletsToProcess, randomID: Math.floor(Math.random() * 11) };
|
||||
};
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default WatchConnectivity;
|
@ -1,4 +0,0 @@
|
||||
const WatchConnectivity = () => {
|
||||
return null;
|
||||
};
|
||||
export default WatchConnectivity;
|
@ -1,91 +0,0 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import { TWallet, Transaction } from '../class/wallets/types';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
|
||||
|
||||
enum WidgetCommunicationKeys {
|
||||
AllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance',
|
||||
AllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime',
|
||||
DisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed',
|
||||
LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed',
|
||||
}
|
||||
|
||||
export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
const displayBalance = await DefaultPreference.get(WidgetCommunicationKeys.DisplayBalanceAllowed);
|
||||
return displayBalance === '1';
|
||||
} catch {
|
||||
await setBalanceDisplayAllowed(true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export const setBalanceDisplayAllowed = async (value: boolean): Promise<void> => {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
if (value) {
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, '1');
|
||||
} else {
|
||||
await DefaultPreference.clear(WidgetCommunicationKeys.DisplayBalanceAllowed);
|
||||
}
|
||||
};
|
||||
|
||||
const allWalletsBalanceAndTransactionTime = async (
|
||||
wallets: TWallet[],
|
||||
walletsInitialized: boolean,
|
||||
): Promise<{ allWalletsBalance: number; latestTransactionTime: number | string }> => {
|
||||
if (!walletsInitialized || !(await isBalanceDisplayAllowed())) {
|
||||
return { allWalletsBalance: 0, latestTransactionTime: 0 };
|
||||
}
|
||||
let balance = 0;
|
||||
let latestTransactionTime: number | string = 0;
|
||||
|
||||
for (const wallet of wallets) {
|
||||
if (wallet.hideBalance) continue;
|
||||
balance += await wallet.getBalance();
|
||||
|
||||
const transactions: Transaction[] = await wallet.getTransactions();
|
||||
for (const transaction of transactions) {
|
||||
const transactionTime = await wallet.getLatestTransactionTimeEpoch();
|
||||
if (transaction.confirmations > 0 && transactionTime > Number(latestTransactionTime)) {
|
||||
latestTransactionTime = transactionTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (latestTransactionTime === 0 && transactions[0]?.confirmations === 0) {
|
||||
latestTransactionTime = WidgetCommunicationKeys.LatestTransactionIsUnconfirmed;
|
||||
}
|
||||
}
|
||||
|
||||
return { allWalletsBalance: balance, latestTransactionTime };
|
||||
};
|
||||
|
||||
const WidgetCommunication: React.FC = () => {
|
||||
const { wallets, walletsInitialized } = useStorage();
|
||||
const { isWidgetBalanceDisplayAllowed } = useSettings();
|
||||
|
||||
const syncWidgetBalanceWithWallets = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
const { allWalletsBalance, latestTransactionTime } = await allWalletsBalanceAndTransactionTime(wallets, walletsInitialized);
|
||||
await Promise.all([
|
||||
DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, String(allWalletsBalance)),
|
||||
DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, String(latestTransactionTime)),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Failed to sync widget balance with wallets:', error);
|
||||
}
|
||||
}, [wallets, walletsInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized) {
|
||||
syncWidgetBalanceWithWallets();
|
||||
}
|
||||
}, [wallets, walletsInitialized, isWidgetBalanceDisplayAllowed, syncWidgetBalanceWithWallets]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default WidgetCommunication;
|
@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const setBalanceDisplayAllowed = async (value: boolean): Promise<void> => {};
|
||||
|
||||
const WidgetCommunication: React.FC = () => {
|
||||
return null; // This component does not render anything.
|
||||
};
|
||||
|
||||
export default WidgetCommunication;
|
65
hooks/useMenuElements.ios.ts
Normal file
65
hooks/useMenuElements.ios.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import { useStorage } from './context/useStorage';
|
||||
|
||||
/*
|
||||
Hook for managing iPadOS and macOS menu actions with keyboard shortcuts.
|
||||
Uses MenuElementsEmitter for event handling.
|
||||
*/
|
||||
|
||||
const { MenuElementsEmitter } = NativeModules;
|
||||
const eventEmitter =
|
||||
(Platform.OS === 'ios' || Platform.OS === 'macos') && MenuElementsEmitter ? new NativeEventEmitter(MenuElementsEmitter) : null;
|
||||
|
||||
const useMenuElements = () => {
|
||||
const { walletsInitialized } = useStorage();
|
||||
const reloadTransactionsMenuActionRef = useRef<() => void>(() => {});
|
||||
|
||||
const setReloadTransactionsMenuActionFunction = useCallback((newFunction: () => void) => {
|
||||
console.debug('Setting reloadTransactionsMenuActionFunction.');
|
||||
reloadTransactionsMenuActionRef.current = newFunction;
|
||||
}, []);
|
||||
|
||||
const dispatchNavigate = useCallback((routeName: string, screen?: string) => {
|
||||
NavigationService.dispatch(CommonActions.navigate({ name: routeName, params: screen ? { screen } : undefined }));
|
||||
}, []);
|
||||
|
||||
const eventActions = useMemo(
|
||||
() => ({
|
||||
openSettings: () => dispatchNavigate('Settings'),
|
||||
addWallet: () => dispatchNavigate('AddWalletRoot'),
|
||||
importWallet: () => dispatchNavigate('AddWalletRoot', 'ImportWallet'),
|
||||
reloadTransactions: () => {
|
||||
console.debug('Calling reloadTransactionsMenuActionFunction');
|
||||
reloadTransactionsMenuActionRef.current?.();
|
||||
},
|
||||
}),
|
||||
[dispatchNavigate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!walletsInitialized || !eventEmitter) return;
|
||||
|
||||
console.debug('Setting up menu event listeners');
|
||||
|
||||
const listeners = [
|
||||
eventEmitter.addListener('openSettings', eventActions.openSettings),
|
||||
eventEmitter.addListener('addWalletMenuAction', eventActions.addWallet),
|
||||
eventEmitter.addListener('importWalletMenuAction', eventActions.importWallet),
|
||||
eventEmitter.addListener('reloadTransactionsMenuAction', eventActions.reloadTransactions),
|
||||
];
|
||||
|
||||
return () => {
|
||||
console.debug('Removing menu event listeners');
|
||||
listeners.forEach(listener => listener.remove());
|
||||
};
|
||||
}, [walletsInitialized, eventActions]);
|
||||
|
||||
return {
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
};
|
||||
};
|
||||
|
||||
export default useMenuElements;
|
9
hooks/useMenuElements.ts
Normal file
9
hooks/useMenuElements.ts
Normal file
@ -0,0 +1,9 @@
|
||||
const useMenuElements = () => {
|
||||
const setReloadTransactionsMenuActionFunction = (_: () => void) => {};
|
||||
|
||||
return {
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
};
|
||||
};
|
||||
|
||||
export default useMenuElements;
|
241
hooks/useWatchConnectivity.ios.ts
Normal file
241
hooks/useWatchConnectivity.ios.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
transferCurrentComplicationUserInfo,
|
||||
transferUserInfo,
|
||||
updateApplicationContext,
|
||||
useInstalled,
|
||||
usePaired,
|
||||
useReachability,
|
||||
watchEvents,
|
||||
} from 'react-native-watch-connectivity';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
import { MultisigHDWallet } from '../class';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import { FiatUnit } from '../models/fiatUnit';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
|
||||
interface Message {
|
||||
request?: string;
|
||||
message?: string;
|
||||
walletIndex?: number;
|
||||
amount?: number;
|
||||
description?: string;
|
||||
hideBalance?: boolean;
|
||||
}
|
||||
|
||||
interface Reply {
|
||||
(response: Record<string, any>): void;
|
||||
}
|
||||
|
||||
interface LightningInvoiceCreateRequest {
|
||||
walletIndex: number;
|
||||
amount: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
type: string;
|
||||
amount: string;
|
||||
memo: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export function useWatchConnectivity() {
|
||||
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useStorage();
|
||||
const { preferredFiatCurrency } = useSettings();
|
||||
const isReachable = useReachability();
|
||||
const isInstalled = useInstalled();
|
||||
const isPaired = usePaired();
|
||||
const messagesListenerActive = useRef(false);
|
||||
const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey);
|
||||
|
||||
// Set up message listener only when conditions are met
|
||||
useEffect(() => {
|
||||
if (!isInstalled || !isPaired || !walletsInitialized || !isReachable) {
|
||||
console.debug('Apple Watch not installed, not paired, or other conditions not met. Exiting message listener setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
const messagesListener = watchEvents.addListener('message', (message: any) => handleMessages(message, () => {}));
|
||||
messagesListenerActive.current = true;
|
||||
|
||||
return () => {
|
||||
messagesListener();
|
||||
messagesListenerActive.current = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, isReachable, isInstalled, isPaired]);
|
||||
|
||||
// Send wallet data to Apple Watch
|
||||
useEffect(() => {
|
||||
if (!isInstalled || !isPaired || !walletsInitialized) return;
|
||||
|
||||
const sendWalletData = async () => {
|
||||
try {
|
||||
const walletsToProcess = await constructWalletsToSendToWatch();
|
||||
if (walletsToProcess) {
|
||||
if (isReachable) {
|
||||
transferUserInfo(walletsToProcess);
|
||||
console.debug('Apple Watch: sent info to watch transferUserInfo');
|
||||
} else {
|
||||
updateApplicationContext(walletsToProcess);
|
||||
console.debug('Apple Watch: sent info to watch context');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Failed to send wallets to watch:', error);
|
||||
}
|
||||
};
|
||||
sendWalletData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, isReachable, isInstalled, isPaired]);
|
||||
|
||||
// Update application context with wallet status
|
||||
useEffect(() => {
|
||||
if (!isInstalled || !isPaired || !walletsInitialized || !isReachable) return;
|
||||
|
||||
updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) });
|
||||
}, [isReachable, walletsInitialized, isInstalled, isPaired]);
|
||||
|
||||
// Update preferred fiat currency to Apple Watch if it changes
|
||||
useEffect(() => {
|
||||
if (!isInstalled || !isPaired || !walletsInitialized || !isReachable || !preferredFiatCurrency) return;
|
||||
|
||||
if (lastPreferredCurrency.current !== preferredFiatCurrency.endPointKey) {
|
||||
try {
|
||||
transferCurrentComplicationUserInfo({ preferredFiatCurrency: preferredFiatCurrency.endPointKey });
|
||||
lastPreferredCurrency.current = preferredFiatCurrency.endPointKey;
|
||||
console.debug('Apple Watch: updated preferred fiat currency');
|
||||
} catch (error) {
|
||||
console.debug('Error updating preferredFiatCurrency on watch:', error);
|
||||
}
|
||||
} else {
|
||||
console.debug('WatchConnectivity lastPreferredCurrency has not changed');
|
||||
}
|
||||
}, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled, isPaired]);
|
||||
|
||||
const handleMessages = useCallback(
|
||||
async (message: Message, reply: Reply) => {
|
||||
try {
|
||||
if (message.request === 'createInvoice') {
|
||||
const createInvoiceRequest = await handleLightningInvoiceCreateRequest({
|
||||
walletIndex: message.walletIndex!,
|
||||
amount: message.amount!,
|
||||
description: message.description,
|
||||
});
|
||||
reply({ invoicePaymentRequest: createInvoiceRequest });
|
||||
} else if (message.message === 'sendApplicationContext') {
|
||||
const walletsToProcess = await constructWalletsToSendToWatch();
|
||||
if (walletsToProcess) updateApplicationContext(walletsToProcess);
|
||||
} else if (message.message === 'fetchTransactions') {
|
||||
await fetchWalletTransactions();
|
||||
await saveToDisk();
|
||||
reply({});
|
||||
} else if (message.message === 'hideBalance') {
|
||||
wallets[message.walletIndex!].hideBalance = message.hideBalance!;
|
||||
await saveToDisk();
|
||||
reply({});
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Error handling message:', error);
|
||||
reply({});
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[fetchWalletTransactions, saveToDisk, wallets],
|
||||
);
|
||||
|
||||
const handleLightningInvoiceCreateRequest = useCallback(
|
||||
async ({ walletIndex, amount, description = loc.lnd.placeholder }: LightningInvoiceCreateRequest): Promise<string | undefined> => {
|
||||
const wallet = wallets[walletIndex];
|
||||
if (wallet.allowReceive() && amount > 0) {
|
||||
try {
|
||||
if ('addInvoice' in wallet) {
|
||||
const invoiceRequest = await wallet.addInvoice(amount, description);
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
if (await Notifications.isNotificationsEnabled()) {
|
||||
const decoded = await wallet.decodeInvoice(invoiceRequest);
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
|
||||
return invoiceRequest;
|
||||
}
|
||||
return invoiceRequest;
|
||||
}
|
||||
} catch (invoiceError) {
|
||||
console.debug('Error creating invoice:', invoiceError);
|
||||
}
|
||||
}
|
||||
},
|
||||
[wallets],
|
||||
);
|
||||
|
||||
// Construct wallet data to send to the watch, including transaction details
|
||||
const constructWalletsToSendToWatch = useCallback(async () => {
|
||||
if (!Array.isArray(wallets) || !walletsInitialized) return;
|
||||
|
||||
const walletsToProcess = await Promise.allSettled(
|
||||
wallets.map(async wallet => {
|
||||
try {
|
||||
let receiveAddress;
|
||||
try {
|
||||
receiveAddress = wallet.chain === Chain.ONCHAIN ? await wallet.getAddressAsync() : wallet.getAddress();
|
||||
} catch {
|
||||
receiveAddress =
|
||||
wallet.chain === Chain.ONCHAIN
|
||||
? 'next_free_address_index' in wallet && '_getExternalAddressByIndex' in wallet
|
||||
? wallet._getExternalAddressByIndex(wallet.next_free_address_index)
|
||||
: wallet.getAddress()
|
||||
: wallet.getAddress();
|
||||
}
|
||||
|
||||
const transactions: Transaction[] = wallet
|
||||
.getTransactions()
|
||||
.slice(0, 10)
|
||||
.map((transaction: any) => ({
|
||||
type: transaction.confirmations ? 'pendingConfirmation' : 'received',
|
||||
amount: formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString(),
|
||||
memo: txMetadata[transaction.hash]?.memo || transaction.memo || '',
|
||||
time: transactionTimeToReadable(transaction.received),
|
||||
}));
|
||||
|
||||
return {
|
||||
label: wallet.getLabel(),
|
||||
balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
|
||||
type: wallet.type,
|
||||
preferredBalanceUnit: wallet.getPreferredBalanceUnit(),
|
||||
receiveAddress,
|
||||
transactions,
|
||||
hideBalance: wallet.hideBalance,
|
||||
...(wallet.chain === Chain.ONCHAIN &&
|
||||
wallet.type !== MultisigHDWallet.type && {
|
||||
xpub: wallet.getXpub() || wallet.getSecret(),
|
||||
}),
|
||||
...(wallet.allowBIP47() &&
|
||||
wallet.isBIP47Enabled() &&
|
||||
'getBIP47PaymentCode' in wallet && { paymentCode: wallet.getBIP47PaymentCode() }),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to construct wallet:', {
|
||||
walletLabel: wallet.getLabel(),
|
||||
walletType: wallet.type,
|
||||
error,
|
||||
});
|
||||
return null; // Ensure failed wallet returns null so it's excluded from final results
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const processedWallets = walletsToProcess
|
||||
.filter(result => result.status === 'fulfilled' && result.value !== null)
|
||||
.map(result => (result as PromiseFulfilledResult<any>).value);
|
||||
|
||||
console.debug('Constructed wallets to process for Apple Watch');
|
||||
return { wallets: processedWallets, randomID: Math.floor(Math.random() * 11) };
|
||||
}, [wallets, walletsInitialized, txMetadata]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default useWatchConnectivity;
|
2
hooks/useWatchConnectivity.ts
Normal file
2
hooks/useWatchConnectivity.ts
Normal file
@ -0,0 +1,2 @@
|
||||
const useWatchConnectivity = () => {};
|
||||
export default useWatchConnectivity;
|
141
hooks/useWidgetCommunication.ios.ts
Normal file
141
hooks/useWidgetCommunication.ios.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import { Transaction, TWallet } from '../class/wallets/types';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
|
||||
import debounce from '../blue_modules/debounce';
|
||||
|
||||
enum WidgetCommunicationKeys {
|
||||
AllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance',
|
||||
AllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime',
|
||||
DisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed',
|
||||
LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed',
|
||||
}
|
||||
|
||||
DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
|
||||
export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
||||
try {
|
||||
const displayBalance = await DefaultPreference.get(WidgetCommunicationKeys.DisplayBalanceAllowed);
|
||||
if (displayBalance === '1') {
|
||||
return true;
|
||||
} else if (displayBalance === '0') {
|
||||
return false;
|
||||
} else {
|
||||
// Preference not set, initialize it to '1' (allowed) and return true
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, '1');
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get DisplayBalanceAllowed:', error);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export const setBalanceDisplayAllowed = async (allowed: boolean): Promise<void> => {
|
||||
try {
|
||||
if (allowed) {
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, '1');
|
||||
} else {
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, '0');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to set DisplayBalanceAllowed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const calculateBalanceAndTransactionTime = async (
|
||||
wallets: TWallet[],
|
||||
walletsInitialized: boolean,
|
||||
): Promise<{
|
||||
allWalletsBalance: number;
|
||||
latestTransactionTime: number | string;
|
||||
}> => {
|
||||
if (!walletsInitialized || !(await isBalanceDisplayAllowed())) {
|
||||
return { allWalletsBalance: 0, latestTransactionTime: 0 };
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
wallets.map(async wallet => {
|
||||
if (wallet.hideBalance) return { balance: 0, latestTransactionTime: 0 };
|
||||
|
||||
const balance = await wallet.getBalance();
|
||||
const transactions: Transaction[] = await wallet.getTransactions();
|
||||
const confirmedTransactions = transactions.filter(t => t.confirmations > 0);
|
||||
const latestTransactionTime =
|
||||
confirmedTransactions.length > 0
|
||||
? Math.max(...confirmedTransactions.map(t => t.received || t.time || 0))
|
||||
: WidgetCommunicationKeys.LatestTransactionIsUnconfirmed;
|
||||
|
||||
return { balance, latestTransactionTime };
|
||||
}),
|
||||
);
|
||||
|
||||
const allWalletsBalance = results.reduce((acc, result) => acc + (result.status === 'fulfilled' ? result.value.balance : 0), 0);
|
||||
const latestTransactionTime = results.reduce(
|
||||
(max, result) =>
|
||||
result.status === 'fulfilled' && typeof result.value.latestTransactionTime === 'number' && result.value.latestTransactionTime > max
|
||||
? result.value.latestTransactionTime
|
||||
: max,
|
||||
0,
|
||||
);
|
||||
|
||||
return { allWalletsBalance, latestTransactionTime };
|
||||
};
|
||||
|
||||
export const syncWidgetBalanceWithWallets = async (
|
||||
wallets: TWallet[],
|
||||
walletsInitialized: boolean,
|
||||
cachedBalance: { current: number },
|
||||
cachedLatestTransactionTime: { current: number | string },
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { allWalletsBalance, latestTransactionTime } = await calculateBalanceAndTransactionTime(wallets, walletsInitialized);
|
||||
|
||||
if (cachedBalance.current !== allWalletsBalance || cachedLatestTransactionTime.current !== latestTransactionTime) {
|
||||
await Promise.all([
|
||||
DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, String(allWalletsBalance)),
|
||||
DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, String(latestTransactionTime)),
|
||||
]);
|
||||
|
||||
cachedBalance.current = allWalletsBalance;
|
||||
cachedLatestTransactionTime.current = latestTransactionTime;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to sync widget balance with wallets:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedSyncWidgetBalanceWithWallets = debounce(
|
||||
async (
|
||||
wallets: TWallet[],
|
||||
walletsInitialized: boolean,
|
||||
cachedBalance: { current: number },
|
||||
cachedLatestTransactionTime: { current: number | string },
|
||||
) => {
|
||||
await syncWidgetBalanceWithWallets(wallets, walletsInitialized, cachedBalance, cachedLatestTransactionTime);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const useWidgetCommunication = (): void => {
|
||||
const { wallets, walletsInitialized } = useStorage();
|
||||
const { isWidgetBalanceDisplayAllowed } = useSettings();
|
||||
const cachedBalance = useRef<number>(0);
|
||||
const cachedLatestTransactionTime = useRef<number | string>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized) {
|
||||
debouncedSyncWidgetBalanceWithWallets(wallets, walletsInitialized, cachedBalance, cachedLatestTransactionTime);
|
||||
}
|
||||
}, [wallets, walletsInitialized, isWidgetBalanceDisplayAllowed]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedSyncWidgetBalanceWithWallets.cancel();
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default useWidgetCommunication;
|
9
hooks/useWidgetCommunication.ts
Normal file
9
hooks/useWidgetCommunication.ts
Normal file
@ -0,0 +1,9 @@
|
||||
const useWidgetCommunication = (): void => {};
|
||||
|
||||
export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const setBalanceDisplayAllowed = async (_allowed: boolean): Promise<void> => {};
|
||||
|
||||
export default useWidgetCommunication;
|
@ -118,6 +118,7 @@
|
||||
B450109F2C0FCDA500619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
|
||||
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
|
||||
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
|
||||
B45942C42CDECF2400B3DC2E /* MenuElementsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */; };
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
|
||||
B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
|
||||
B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
|
||||
@ -384,6 +385,8 @@
|
||||
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; };
|
||||
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; };
|
||||
B4B31A352C77BBA000663334 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = "<group>"; };
|
||||
B4C075282CDDB3BE00322A84 /* MenuElementsEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuElementsEmitter.h; sourceTree = "<group>"; };
|
||||
B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuElementsEmitter.m; sourceTree = "<group>"; };
|
||||
B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceivePageInterfaceController.swift; sourceTree = "<group>"; };
|
||||
B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveType.swift; sourceTree = "<group>"; };
|
||||
B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceMode.swift; sourceTree = "<group>"; };
|
||||
@ -476,6 +479,7 @@
|
||||
13B07FAE1A68108700A75B9A /* BlueWallet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4C0752B2CDDB3CC00322A84 /* MenuElementsEmitter */,
|
||||
B461B850299599F800E431AA /* AppDelegate.h */,
|
||||
B461B851299599F800E431AA /* AppDelegate.mm */,
|
||||
32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */,
|
||||
@ -811,6 +815,15 @@
|
||||
path = BlueWalletUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B4C0752B2CDDB3CC00322A84 /* MenuElementsEmitter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */,
|
||||
B4C075282CDDB3BE00322A84 /* MenuElementsEmitter.h */,
|
||||
);
|
||||
path = MenuElementsEmitter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FAA856B639C61E61D2CF90A8 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1225,6 +1238,7 @@
|
||||
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */,
|
||||
B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
B45942C42CDECF2400B3DC2E /* MenuElementsEmitter.m in Sources */,
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
|
||||
B44033F42BCC377F00162242 /* WidgetData.swift in Sources */,
|
||||
B49A28C52CD1A894006B08E4 /* MarketData.swift in Sources */,
|
||||
|
@ -7,6 +7,7 @@
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <RNCPushNotificationIOS.h>
|
||||
#import "EventEmitter.h"
|
||||
#import "MenuElementsEmitter.h"
|
||||
#import <React/RCTRootView.h>
|
||||
#import <Bugsnag/Bugsnag.h>
|
||||
#import "BlueWallet-Swift.h"
|
||||
@ -152,18 +153,27 @@
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
|
||||
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
|
||||
{
|
||||
[self.userDefaultsGroup setValue:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo} forKey:@"onUserActivityOpen"];
|
||||
NSDictionary *userActivityData = @{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo};
|
||||
[self.userDefaultsGroup setValue:userActivityData forKey:@"onUserActivityOpen"];
|
||||
|
||||
// Check if the activity type matches the allowed types
|
||||
if ([userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.receiveonchain"] ||
|
||||
[userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.xpub"] ||
|
||||
[userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.blockexplorer"]) {
|
||||
|
||||
[EventEmitter.sharedInstance sendUserActivity:userActivityData];
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
|
||||
return [RCTLinkingManager application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
else {
|
||||
[EventEmitter.sharedInstance sendUserActivity:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo}];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// If activity type does not match any of the specified types, do nothing
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [RCTLinkingManager application:app openURL:url options:options];
|
||||
}
|
||||
@ -184,7 +194,6 @@
|
||||
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
|
||||
{
|
||||
NSDictionary *userInfo = notification.request.content.userInfo;
|
||||
[EventEmitter.sharedInstance sendNotification:userInfo];
|
||||
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
|
||||
}
|
||||
|
||||
@ -234,24 +243,24 @@
|
||||
}
|
||||
|
||||
- (void)openSettings:(UIKeyCommand *)keyCommand {
|
||||
[EventEmitter.sharedInstance openSettings];
|
||||
[MenuElementsEmitter.sharedInstance openSettings];
|
||||
}
|
||||
|
||||
- (void)addWalletAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance addWalletMenuAction];
|
||||
[MenuElementsEmitter.sharedInstance addWalletMenuAction];
|
||||
NSLog(@"Add Wallet action performed");
|
||||
}
|
||||
|
||||
- (void)importWalletAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance importWalletMenuAction];
|
||||
[MenuElementsEmitter.sharedInstance importWalletMenuAction];
|
||||
NSLog(@"Import Wallet action performed");
|
||||
}
|
||||
|
||||
- (void)reloadTransactionsAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance reloadTransactionsMenuAction];
|
||||
[MenuElementsEmitter.sharedInstance reloadTransactionsMenuAction];
|
||||
NSLog(@"Reload Transactions action performed");
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,6 @@
|
||||
@interface EventEmitter : RCTEventEmitter <RCTBridgeModule>
|
||||
|
||||
+ (EventEmitter *)sharedInstance;
|
||||
- (void)sendNotification:(NSDictionary *)userInfo;
|
||||
- (void)openSettings;
|
||||
- (void)addWalletMenuAction;
|
||||
- (void)importWalletMenuAction;
|
||||
- (void)reloadTransactionsMenuAction;
|
||||
- (void)sendUserActivity:(NSDictionary *)userInfo;
|
||||
|
||||
@end
|
||||
|
@ -18,7 +18,11 @@ RCT_EXPORT_MODULE();
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (EventEmitter *)sharedInstance {
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
@ -27,47 +31,24 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
sharedInstance = [super init];
|
||||
return sharedInstance;
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[@"onNotificationReceived",@"openSettings",@"onUserActivityOpen",@"addWalletMenuAction", @"importWalletMenuAction", @"reloadTransactionsMenuAction"];
|
||||
}
|
||||
|
||||
- (void)sendNotification:(NSDictionary *)userInfo
|
||||
{
|
||||
[sharedInstance sendEventWithName:@"onNotificationReceived" body:userInfo];
|
||||
return @[@"onUserActivityOpen"];
|
||||
}
|
||||
|
||||
- (void)sendUserActivity:(NSDictionary *)userInfo
|
||||
{
|
||||
[sharedInstance sendEventWithName:@"onUserActivityOpen" body:userInfo];
|
||||
[self sendEventWithName:@"onUserActivityOpen" body:userInfo];
|
||||
}
|
||||
|
||||
RCT_REMAP_METHOD(getMostRecentUserActivity, resolve: (RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXPORT_METHOD(getMostRecentUserActivity:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
resolve([defaults valueForKey:@"onUserActivityOpen"]);
|
||||
}
|
||||
|
||||
|
||||
- (void)openSettings
|
||||
{
|
||||
[sharedInstance sendEventWithName:@"openSettings" body:nil];
|
||||
}
|
||||
|
||||
- (void)addWalletMenuAction {
|
||||
[sharedInstance sendEventWithName:@"addWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)importWalletMenuAction {
|
||||
[sharedInstance sendEventWithName:@"importWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)reloadTransactionsMenuAction {
|
||||
[sharedInstance sendEventWithName:@"reloadTransactionsMenuAction" body:nil];
|
||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
resolve([defaults valueForKey:@"onUserActivityOpen"]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -48,6 +48,9 @@
|
||||
},
|
||||
"China (Chinese Yuan)" : {
|
||||
|
||||
},
|
||||
"Choose your preferred currency." : {
|
||||
|
||||
},
|
||||
"Choose your preferred fiat currency." : {
|
||||
|
||||
@ -262,6 +265,9 @@
|
||||
},
|
||||
"Venezuela (Venezuelan Bolívar Soberano)" : {
|
||||
|
||||
},
|
||||
"View the current Bitcoin market rate in your preferred currency." : {
|
||||
|
||||
},
|
||||
"View the current Bitcoin market rate in your preferred fiat currency." : {
|
||||
|
||||
|
17
ios/MenuElementsEmitter.h
Normal file
17
ios/MenuElementsEmitter.h
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// MenuElementsEmitter.h
|
||||
// BlueWallet
|
||||
//
|
||||
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface MenuElementsEmitter : RCTEventEmitter
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)openSettings;
|
||||
- (void)addWalletMenuAction;
|
||||
- (void)importWalletMenuAction;
|
||||
- (void)reloadTransactionsMenuAction;
|
||||
|
||||
@end
|
58
ios/MenuElementsEmitter.m
Normal file
58
ios/MenuElementsEmitter.m
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// MenuElementsEmitter.m
|
||||
// BlueWallet
|
||||
//
|
||||
|
||||
#import "MenuElementsEmitter.h"
|
||||
|
||||
static MenuElementsEmitter *sharedInstance;
|
||||
|
||||
@implementation MenuElementsEmitter
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
if (!sharedInstance) {
|
||||
sharedInstance = [[self alloc] init];
|
||||
}
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
sharedInstance = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
@"openSettings",
|
||||
@"addWalletMenuAction",
|
||||
@"importWalletMenuAction",
|
||||
@"reloadTransactionsMenuAction"
|
||||
];
|
||||
}
|
||||
|
||||
- (void)openSettings {
|
||||
[self sendEventWithName:@"openSettings" body:nil];
|
||||
}
|
||||
|
||||
- (void)addWalletMenuAction {
|
||||
[self sendEventWithName:@"addWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)importWalletMenuAction {
|
||||
[self sendEventWithName:@"importWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)reloadTransactionsMenuAction {
|
||||
[self sendEventWithName:@"reloadTransactionsMenuAction" body:nil];
|
||||
}
|
||||
|
||||
@end
|
27
ios/MenuElementsEmitter/MenuElementsEmitter.h
Normal file
27
ios/MenuElementsEmitter/MenuElementsEmitter.h
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// MenuElementsEmitter.h
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 11/7/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// MenuElementsEmitter.h
|
||||
// BlueWallet
|
||||
//
|
||||
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface MenuElementsEmitter : RCTEventEmitter
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)openSettings;
|
||||
- (void)addWalletMenuAction;
|
||||
- (void)importWalletMenuAction;
|
||||
- (void)reloadTransactionsMenuAction;
|
||||
- (NSArray<NSString *> *)supportedEvents;
|
||||
|
||||
@end
|
62
ios/MenuElementsEmitter/MenuElementsEmitter.m
Normal file
62
ios/MenuElementsEmitter/MenuElementsEmitter.m
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// MenuElementsEmitter.m
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 11/7/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MenuElementsEmitter.h"
|
||||
|
||||
static MenuElementsEmitter *sharedInstance;
|
||||
|
||||
@implementation MenuElementsEmitter
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
sharedInstance = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
@"openSettings",
|
||||
@"addWalletMenuAction",
|
||||
@"importWalletMenuAction",
|
||||
@"reloadTransactionsMenuAction"
|
||||
];
|
||||
}
|
||||
|
||||
- (void)openSettings {
|
||||
[self sendEventWithName:@"openSettings" body:nil];
|
||||
}
|
||||
|
||||
- (void)addWalletMenuAction {
|
||||
[self sendEventWithName:@"addWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)importWalletMenuAction {
|
||||
[self sendEventWithName:@"importWalletMenuAction" body:nil];
|
||||
}
|
||||
|
||||
- (void)reloadTransactionsMenuAction {
|
||||
[self sendEventWithName:@"reloadTransactionsMenuAction" body:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -19,11 +19,13 @@ import { useTheme } from '../../components/themes';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const SendCreate = () => {
|
||||
const { fee, recipients, memo = '', satoshiPerByte, psbt, showAnimatedQr, tx } = useRoute().params;
|
||||
const transaction = bitcoin.Transaction.fromHex(tx);
|
||||
const size = transaction.virtualSize();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const { colors } = useTheme();
|
||||
const { setOptions } = useNavigation();
|
||||
|
||||
@ -47,11 +49,11 @@ const SendCreate = () => {
|
||||
|
||||
useEffect(() => {
|
||||
console.log('send/create - useEffect');
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
return () => {
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, []);
|
||||
}, [isPrivacyBlurEnabled]);
|
||||
|
||||
const exportTXN = useCallback(async () => {
|
||||
const fileName = `${Date.now()}.txn`;
|
||||
|
@ -5,7 +5,6 @@ import A from '../../blue_modules/analytics';
|
||||
import { Header } from '../../components/Header';
|
||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { setBalanceDisplayAllowed } from '../../components/WidgetCommunication';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
@ -28,7 +27,7 @@ const SettingsPrivacy: React.FC = () => {
|
||||
isDoNotTrackEnabled,
|
||||
setDoNotTrackStorage,
|
||||
isPrivacyBlurEnabled,
|
||||
setIsPrivacyBlurEnabledState,
|
||||
setIsPrivacyBlurEnabled,
|
||||
isWidgetBalanceDisplayAllowed,
|
||||
setIsWidgetBalanceDisplayAllowedStorage,
|
||||
isClipboardGetContentEnabled,
|
||||
@ -82,7 +81,6 @@ const SettingsPrivacy: React.FC = () => {
|
||||
const onWidgetsTotalBalanceValueChange = async (value: boolean) => {
|
||||
setIsLoading(SettingsPrivacySection.Widget);
|
||||
try {
|
||||
await setBalanceDisplayAllowed(value);
|
||||
setIsWidgetBalanceDisplayAllowedStorage(value);
|
||||
} catch (e) {
|
||||
console.debug('onWidgetsTotalBalanceValueChange catch', e);
|
||||
@ -102,7 +100,7 @@ const SettingsPrivacy: React.FC = () => {
|
||||
|
||||
const onTemporaryScreenshotsValueChange = (value: boolean) => {
|
||||
setIsLoading(SettingsPrivacySection.TemporaryScreenshots);
|
||||
setIsPrivacyBlurEnabledState(!value);
|
||||
setIsPrivacyBlurEnabled(!value);
|
||||
setIsLoading(SettingsPrivacySection.None);
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { ExportMultisigCoordinationSetupStackRootParamList } from '../../navigation/ExportMultisigCoordinationSetupStack';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const enum ActionType {
|
||||
SET_LOADING = 'SET_LOADING',
|
||||
@ -72,6 +73,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
||||
const { params } = useRoute<RouteProp<ExportMultisigCoordinationSetupStackRootParamList, 'ExportMultisigCoordinationSetup'>>();
|
||||
const walletID = params.walletID;
|
||||
const { wallets } = useStorage();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const wallet: TWallet | undefined = wallets.find(w => w.getID() === walletID);
|
||||
const dynamicQRCode = useRef<any>();
|
||||
const { colors } = useTheme();
|
||||
@ -99,7 +101,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
||||
dispatch({ type: ActionType.SET_LOADING, isLoading: true });
|
||||
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
if (wallet) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
|
@ -137,6 +137,7 @@ const ImportWalletDiscovery: React.FC = () => {
|
||||
});
|
||||
|
||||
return () => {
|
||||
keepAwake(false);
|
||||
task.current?.stop();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -6,12 +6,14 @@ import { useTheme } from '../../components/themes';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const PleaseBackup: React.FC = () => {
|
||||
const { wallets } = useStorage();
|
||||
const { walletID } = useRoute().params as { walletID: string };
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const navigation = useNavigation();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
@ -37,7 +39,7 @@ const PleaseBackup: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
return () => {
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
|
||||
disallowScreenshot(false);
|
||||
|
@ -54,7 +54,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const { colors } = useTheme();
|
||||
const { wallets, setWalletsWithNewOrder } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { isElectrumDisabled } = useSettings();
|
||||
const { isElectrumDisabled, isPrivacyBlurEnabled } = useSettings();
|
||||
const { navigate, dispatch, addListener } = useExtendedNavigation();
|
||||
const openScannerButtonRef = useRef();
|
||||
const route = useRoute();
|
||||
@ -191,7 +191,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
if (hasLoaded.current) return;
|
||||
setIsLoading(true);
|
||||
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (!w.current) {
|
||||
|
@ -13,6 +13,7 @@ import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import { WalletExportStackParamList } from '../../navigation/WalletExportStack';
|
||||
import useAppState from '../../hooks/useAppState';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
type RouteProps = RouteProp<WalletExportStackParamList, 'WalletExport'>;
|
||||
|
||||
@ -21,6 +22,7 @@ const WalletExport: React.FC = () => {
|
||||
const { walletID } = useRoute<RouteProps>().params;
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { goBack } = useNavigation();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const { colors } = useTheme();
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const [qrCodeSize, setQRCodeSize] = useState(90);
|
||||
@ -43,7 +45,7 @@ const WalletExport: React.FC = () => {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (wallet) {
|
||||
if (!wallet.getUserHasSavedExport()) {
|
||||
@ -54,10 +56,10 @@ const WalletExport: React.FC = () => {
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
task.cancel();
|
||||
disallowScreenshot(false);
|
||||
task.cancel();
|
||||
};
|
||||
}, [wallet, saveToDisk]),
|
||||
}, [isPrivacyBlurEnabled, wallet, saveToDisk]),
|
||||
);
|
||||
|
||||
const secrets: string[] = (() => {
|
||||
|
@ -42,6 +42,7 @@ import getWalletTransactionsOptions from '../../navigation/helpers/getWalletTran
|
||||
import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder';
|
||||
import selectWallet from '../../helpers/select-wallet';
|
||||
import assert from 'assert';
|
||||
import useMenuElements from '../../hooks/useMenuElements';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const buttonFontSize =
|
||||
@ -52,8 +53,8 @@ const buttonFontSize =
|
||||
type WalletTransactionsProps = NativeStackScreenProps<DetailViewStackParamList, 'WalletTransactions'>;
|
||||
|
||||
const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
const { wallets, saveToDisk, setSelectedWalletID, setReloadTransactionsMenuActionFunction } = useStorage();
|
||||
const { isElectrumDisabled } = useSettings();
|
||||
const { wallets, saveToDisk, setSelectedWalletID } = useStorage();
|
||||
const { setReloadTransactionsMenuActionFunction } = useMenuElements();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { walletID } = route.params;
|
||||
@ -64,6 +65,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
const navigation = useExtendedNavigation();
|
||||
const { setOptions, navigate } = navigation;
|
||||
const { colors } = useTheme();
|
||||
const { isElectrumDisabled } = useSettings();
|
||||
const walletActionButtonsRef = useRef<View>(null);
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
@ -372,7 +374,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
task.cancel();
|
||||
setReloadTransactionsMenuActionFunction(() => {});
|
||||
};
|
||||
}, [setReloadTransactionsMenuActionFunction, refreshTransactions]),
|
||||
}, [refreshTransactions, setReloadTransactionsMenuActionFunction]),
|
||||
);
|
||||
|
||||
const refreshProps = isDesktop || isElectrumDisabled ? {} : { refreshing: isLoading, onRefresh: refreshTransactions };
|
||||
|
@ -23,6 +23,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import TotalWalletsBalance from '../../components/TotalWalletsBalance';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import useMenuElements from '../../hooks/useMenuElements';
|
||||
|
||||
const WalletsListSections = { CAROUSEL: 'CAROUSEL', TRANSACTIONS: 'TRANSACTIONS' };
|
||||
|
||||
@ -98,14 +99,8 @@ const WalletsList: React.FC = () => {
|
||||
const { isLargeScreen } = useIsLargeScreen();
|
||||
const walletsCarousel = useRef<any>();
|
||||
const currentWalletIndex = useRef<number>(0);
|
||||
const {
|
||||
wallets,
|
||||
getTransactions,
|
||||
getBalance,
|
||||
refreshAllWalletTransactions,
|
||||
setSelectedWalletID,
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
} = useStorage();
|
||||
const { setReloadTransactionsMenuActionFunction } = useMenuElements();
|
||||
const { wallets, getTransactions, getBalance, refreshAllWalletTransactions, setSelectedWalletID } = useStorage();
|
||||
const { isTotalBalanceEnabled, isElectrumDisabled } = useSettings();
|
||||
const { width } = useWindowDimensions();
|
||||
const { colors, scanImage } = useTheme();
|
||||
@ -129,6 +124,38 @@ const WalletsList: React.FC = () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Forcefully fetches TXs and balance for ALL wallets.
|
||||
* Triggered manually by user on pull-to-refresh.
|
||||
*/
|
||||
const refreshTransactions = useCallback(
|
||||
async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
|
||||
if (isElectrumDisabled) {
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
|
||||
return;
|
||||
}
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: showLoadingIndicator });
|
||||
refreshAllWalletTransactions(undefined, showUpdateStatusIndicator).finally(() => {
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
|
||||
});
|
||||
},
|
||||
[isElectrumDisabled, refreshAllWalletTransactions],
|
||||
);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
console.debug('WalletsList onRefresh');
|
||||
refreshTransactions(true, false);
|
||||
// Optimized for Mac option doesn't like RN Refresh component. Menu Elements now handles it for macOS
|
||||
}, [refreshTransactions]);
|
||||
|
||||
const verifyBalance = useCallback(() => {
|
||||
if (getBalance() !== 0) {
|
||||
A(A.ENUM.GOT_NONZERO_BALANCE);
|
||||
} else {
|
||||
A(A.ENUM.GOT_ZERO_BALANCE);
|
||||
}
|
||||
}, [getBalance]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
@ -140,8 +167,7 @@ const WalletsList: React.FC = () => {
|
||||
task.cancel();
|
||||
setReloadTransactionsMenuActionFunction(() => {});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []),
|
||||
}, [onRefresh, setReloadTransactionsMenuActionFunction, verifyBalance, setSelectedWalletID]),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -161,32 +187,6 @@ const WalletsList: React.FC = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [route.params?.scannedData]);
|
||||
|
||||
const verifyBalance = useCallback(() => {
|
||||
if (getBalance() !== 0) {
|
||||
A(A.ENUM.GOT_NONZERO_BALANCE);
|
||||
} else {
|
||||
A(A.ENUM.GOT_ZERO_BALANCE);
|
||||
}
|
||||
}, [getBalance]);
|
||||
|
||||
/**
|
||||
* Forcefully fetches TXs and balance for ALL wallets.
|
||||
* Triggered manually by user on pull-to-refresh.
|
||||
*/
|
||||
const refreshTransactions = useCallback(
|
||||
async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
|
||||
if (isElectrumDisabled) {
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
|
||||
return;
|
||||
}
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: showLoadingIndicator });
|
||||
refreshAllWalletTransactions(undefined, showUpdateStatusIndicator).finally(() => {
|
||||
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
|
||||
});
|
||||
},
|
||||
[isElectrumDisabled, refreshAllWalletTransactions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
refreshTransactions(false, true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -408,12 +408,6 @@ const WalletsList: React.FC = () => {
|
||||
});
|
||||
}, [pasteFromClipboard, onBarScanned, routeName]);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
console.debug('WalletsList onRefresh');
|
||||
refreshTransactions(true, false);
|
||||
// Optimized for Mac option doesn't like RN Refresh component. Menu Elements now handles it for macOS
|
||||
}, [refreshTransactions]);
|
||||
|
||||
const refreshProps = isDesktop || isElectrumDisabled ? {} : { refreshing: isLoading, onRefresh };
|
||||
|
||||
const sections: SectionData[] = [
|
||||
|
@ -36,6 +36,7 @@ import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const staticCache = {};
|
||||
|
||||
@ -59,15 +60,16 @@ const WalletsAddMultisigStep2 = () => {
|
||||
const [importText, setImportText] = useState('');
|
||||
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||
const openScannerButton = useRef();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const data = useRef(new Array(n));
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
return () => {
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, []),
|
||||
}, [isPrivacyBlurEnabled]),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -16,6 +16,7 @@ import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const WalletsImport = () => {
|
||||
const navigation = useExtendedNavigation();
|
||||
@ -30,7 +31,7 @@ const WalletsImport = () => {
|
||||
const [searchAccountsMenuState, setSearchAccountsMenuState] = useState(false);
|
||||
const [askPassphraseMenuState, setAskPassphraseMenuState] = useState(false);
|
||||
const [clearClipboardMenuState, setClearClipboardMenuState] = useState(true);
|
||||
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
@ -61,11 +62,11 @@ const WalletsImport = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
return () => {
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, []);
|
||||
}, [isPrivacyBlurEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerImport) importButtonPressed();
|
||||
|
@ -10,6 +10,7 @@ import { useTheme } from '../../components/themes';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
const PleaseBackupLNDHub = () => {
|
||||
const { wallets } = useStorage();
|
||||
@ -18,6 +19,7 @@ const PleaseBackupLNDHub = () => {
|
||||
const navigation = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const [qrCodeSize, setQRCodeSize] = useState(90);
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
|
||||
const handleBackButton = useCallback(() => {
|
||||
navigation.getParent().pop();
|
||||
@ -38,13 +40,13 @@ const PleaseBackupLNDHub = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
|
||||
return () => {
|
||||
disallowScreenshot(false);
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
|
||||
};
|
||||
}, [handleBackButton]);
|
||||
}, [handleBackButton, isPrivacyBlurEnabled]);
|
||||
|
||||
const pop = () => navigation.getParent().pop();
|
||||
|
||||
|
@ -13,6 +13,7 @@ import loc from '../../loc';
|
||||
import { styles, useDynamicStyles } from './xpub.styles';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
|
||||
type WalletXpubRouteProp = RouteProp<{ params: { walletID: string; xpub: string } }, 'params'>;
|
||||
export type RootStackParamList = {
|
||||
@ -27,6 +28,7 @@ const WalletXpub: React.FC = () => {
|
||||
const route = useRoute<WalletXpubRouteProp>();
|
||||
const { walletID, xpub } = route.params;
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [xPubText, setXPubText] = useState<string | undefined>(undefined);
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList, 'WalletXpub'>>();
|
||||
@ -36,7 +38,7 @@ const WalletXpub: React.FC = () => {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
disallowScreenshot(true);
|
||||
disallowScreenshot(isPrivacyBlurEnabled);
|
||||
// Skip execution if walletID hasn't changed
|
||||
if (lastWalletIdRef.current === walletID) {
|
||||
return;
|
||||
@ -55,10 +57,10 @@ const WalletXpub: React.FC = () => {
|
||||
});
|
||||
lastWalletIdRef.current = walletID;
|
||||
return () => {
|
||||
task.cancel();
|
||||
disallowScreenshot(false);
|
||||
task.cancel();
|
||||
};
|
||||
}, [walletID, wallet, xpub, navigation]),
|
||||
}, [isPrivacyBlurEnabled, walletID, wallet, xpub, navigation]),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user