Merge pull request #7672 from BlueWallet/cc

REF: CompanionDelegate to hook
This commit is contained in:
GLaDOS 2025-03-05 22:54:21 +00:00 committed by GitHub
commit d375bd9780
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 44 deletions

View file

@ -19,25 +19,42 @@ import loc from '../loc';
import { Chain } from '../models/bitcoinUnits'; import { Chain } from '../models/bitcoinUnits';
import { navigationRef } from '../NavigationService'; import { navigationRef } from '../NavigationService';
import ActionSheet from '../screen/ActionSheet'; import ActionSheet from '../screen/ActionSheet';
import { useStorage } from '../hooks/context/useStorage'; import { useStorage } from './context/useStorage';
import RNQRGenerator from 'rn-qr-generator'; import RNQRGenerator from 'rn-qr-generator';
import presentAlert from './Alert'; import presentAlert from '../components/Alert';
import useMenuElements from '../hooks/useMenuElements'; import useMenuElements from './useMenuElements';
import useWidgetCommunication from '../hooks/useWidgetCommunication'; import useWidgetCommunication from './useWidgetCommunication';
import useWatchConnectivity from '../hooks/useWatchConnectivity'; import useWatchConnectivity from './useWatchConnectivity';
import useDeviceQuickActions from '../hooks/useDeviceQuickActions'; import useDeviceQuickActions from './useDeviceQuickActions';
import useHandoffListener from '../hooks/useHandoffListener'; import useHandoffListener from './useHandoffListener';
const ClipboardContentType = Object.freeze({ const ClipboardContentType = Object.freeze({
BITCOIN: 'BITCOIN', BITCOIN: 'BITCOIN',
LIGHTNING: 'LIGHTNING', LIGHTNING: 'LIGHTNING',
}); });
const CompanionDelegates = () => { /**
const { wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions, setSharedCosigner } = useStorage(); * Hook that initializes all companion listeners and functionality without rendering a component
*/
const useCompanionListeners = (skipIfNotInitialized = true) => {
const {
wallets,
addWallet,
saveToDisk,
fetchAndSaveWalletTransactions,
refreshAllWalletTransactions,
setSharedCosigner,
walletsInitialized,
} = useStorage();
const appState = useRef<AppStateStatus>(AppState.currentState); const appState = useRef<AppStateStatus>(AppState.currentState);
const clipboardContent = useRef<undefined | string>(); const clipboardContent = useRef<undefined | string>();
// We need to call hooks unconditionally before any conditional logic
// We'll use this check inside the effects to conditionally run logic
const shouldActivateListeners = !skipIfNotInitialized || walletsInitialized;
// Initialize other hooks regardless of activation status
// They'll handle their own conditional logic internally
useWatchConnectivity(); useWatchConnectivity();
useWidgetCommunication(); useWidgetCommunication();
useMenuElements(); useMenuElements();
@ -45,6 +62,8 @@ const CompanionDelegates = () => {
useHandoffListener(); useHandoffListener();
const processPushNotifications = useCallback(async () => { const processPushNotifications = useCallback(async () => {
if (!shouldActivateListeners) return false;
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
try { try {
const notifications2process = await getStoredNotifications(); const notifications2process = await getStoredNotifications();
@ -164,15 +183,19 @@ const CompanionDelegates = () => {
console.error('Failed to process push notifications:', error); console.error('Failed to process push notifications:', error);
} }
return false; return false;
}, [fetchAndSaveWalletTransactions, refreshAllWalletTransactions, wallets]); }, [fetchAndSaveWalletTransactions, refreshAllWalletTransactions, wallets, shouldActivateListeners]);
useEffect(() => { useEffect(() => {
if (!shouldActivateListeners) return;
initializeNotifications(processPushNotifications); initializeNotifications(processPushNotifications);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [shouldActivateListeners]);
const handleOpenURL = useCallback( const handleOpenURL = useCallback(
async (event: { url: string }): Promise<void> => { async (event: { url: string }): Promise<void> => {
if (!shouldActivateListeners) return;
try { try {
if (!event.url) return; if (!event.url) return;
let decodedUrl: string; let decodedUrl: string;
@ -227,11 +250,13 @@ const CompanionDelegates = () => {
presentAlert({ message: err.message || loc.send.qr_error_no_qrcode }); presentAlert({ message: err.message || loc.send.qr_error_no_qrcode });
} }
}, },
[wallets, addWallet, saveToDisk, setSharedCosigner], [wallets, addWallet, saveToDisk, setSharedCosigner, shouldActivateListeners],
); );
const showClipboardAlert = useCallback( const showClipboardAlert = useCallback(
({ contentType }: { contentType: undefined | string }) => { ({ contentType }: { contentType: undefined | string }) => {
if (!shouldActivateListeners) return;
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight); triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
getClipboardContent().then(clipboard => { getClipboardContent().then(clipboard => {
if (!clipboard) return; if (!clipboard) return;
@ -254,12 +279,13 @@ const CompanionDelegates = () => {
); );
}); });
}, },
[handleOpenURL], [handleOpenURL, shouldActivateListeners],
); );
const handleAppStateChange = useCallback( const handleAppStateChange = useCallback(
async (nextAppState: AppStateStatus | undefined) => { async (nextAppState: AppStateStatus | undefined) => {
if (wallets.length === 0) return; if (!shouldActivateListeners || wallets.length === 0) return;
if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) { if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) {
setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000); setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000);
updateExchangeRate(); updateExchangeRate();
@ -299,10 +325,12 @@ const CompanionDelegates = () => {
appState.current = nextAppState; appState.current = nextAppState;
} }
}, },
[processPushNotifications, showClipboardAlert, wallets], [processPushNotifications, showClipboardAlert, wallets, shouldActivateListeners],
); );
const addListeners = useCallback(() => { const addListeners = useCallback(() => {
if (!shouldActivateListeners) return { urlSubscription: null, appStateSubscription: null };
const urlSubscription = Linking.addEventListener('url', handleOpenURL); const urlSubscription = Linking.addEventListener('url', handleOpenURL);
const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); const appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
@ -310,18 +338,16 @@ const CompanionDelegates = () => {
urlSubscription, urlSubscription,
appStateSubscription, appStateSubscription,
}; };
}, [handleOpenURL, handleAppStateChange]); }, [handleOpenURL, handleAppStateChange, shouldActivateListeners]);
useEffect(() => { useEffect(() => {
const subscriptions = addListeners(); const subscriptions = addListeners();
return () => { return () => {
subscriptions.urlSubscription?.remove(); subscriptions.urlSubscription?.remove?.();
subscriptions.appStateSubscription?.remove(); subscriptions.appStateSubscription?.remove?.();
}; };
}, [addListeners]); }, [addListeners]);
return null;
}; };
export default CompanionDelegates; export default useCompanionListeners;

View file

@ -1,20 +1,16 @@
import React, { lazy, Suspense } from 'react'; import React from 'react';
import { useStorage } from '../hooks/context/useStorage';
import DevMenu from '../components/DevMenu'; import DevMenu from '../components/DevMenu';
import MainRoot from './index'; import MainRoot from './index';
const CompanionDelegates = lazy(() => import('../components/CompanionDelegates')); import useCompanionListeners from '../hooks/useCompanionListeners';
const MasterView = () => { const MasterView = () => {
const { walletsInitialized } = useStorage(); // Initialize companion listeners only when wallets are initialized
// The hook checks walletsInitialized internally, so it won't run until ready
useCompanionListeners();
return ( return (
<> <>
<MainRoot /> <MainRoot />
{walletsInitialized && (
<Suspense>
<CompanionDelegates />
</Suspense>
)}
{__DEV__ && <DevMenu />} {__DEV__ && <DevMenu />}
</> </>
); );

View file

@ -1,7 +1,7 @@
import { RouteProp, StackActions, useFocusEffect, useIsFocused, useRoute } from '@react-navigation/native'; import { RouteProp, StackActions, useIsFocused, useRoute } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib'; import * as bitcoin from 'bitcoinjs-lib';
import createHash from 'create-hash'; import createHash from 'create-hash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; import { Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import Base43 from '../../blue_modules/base43'; import Base43 from '../../blue_modules/base43';
import * as fs from '../../blue_modules/fs'; import * as fs from '../../blue_modules/fs';
@ -12,7 +12,6 @@ import Button from '../../components/Button';
import { useTheme } from '../../components/themes'; import { useTheme } from '../../components/themes';
import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr'; import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr';
import loc from '../../loc'; import loc from '../../loc';
import { useSettings } from '../../hooks/context/useSettings';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import CameraScreen from '../../components/CameraScreen'; import CameraScreen from '../../components/CameraScreen';
import SafeArea from '../../components/SafeArea'; import SafeArea from '../../components/SafeArea';
@ -57,7 +56,6 @@ const styles = StyleSheet.create({
const ScanQRCode = () => { const ScanQRCode = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { setIsDrawerShouldHide } = useSettings();
const navigation = useExtendedNavigation(); const navigation = useExtendedNavigation();
const route = useRoute<RouteProps>(); const route = useRoute<RouteProps>();
const navigationState = navigation.getState(); const navigationState = navigation.getState();
@ -96,16 +94,6 @@ const ScanQRCode = () => {
return createHash('sha256').update(s).digest().toString('hex'); return createHash('sha256').update(s).digest().toString('hex');
}; };
useFocusEffect(
useCallback(() => {
setIsDrawerShouldHide(true);
return () => {
setIsDrawerShouldHide(false);
};
}, [setIsDrawerShouldHide]),
);
const _onReadUniformResourceV2 = (part: string) => { const _onReadUniformResourceV2 = (part: string) => {
if (!decoder) decoder = new BlueURDecoder(); if (!decoder) decoder = new BlueURDecoder();
try { try {