Merge pull request #7315 from BlueWallet/qa

REF: useDeviceQUickactions
This commit is contained in:
GLaDOS 2024-11-16 01:22:42 +00:00 committed by GitHub
commit 95ab408605
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 145 additions and 159 deletions

View File

@ -1,7 +1,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 { useCallback, useEffect, useRef } from 'react';
import { AppState, AppStateStatus, Linking } from 'react-native';
import A from '../blue_modules/analytics';
import { getClipboardContent } from '../blue_modules/clipboard';
@ -25,12 +25,10 @@ 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 DeviceQuickActions = lazy(() => import('../components/DeviceQuickActions'));
const HandOffComponentListener = lazy(() => import('../components/HandOffComponentListener'));
import useDeviceQuickActions from '../hooks/useDeviceQuickActions';
import useHandoffListener from '../hooks/useHandoffListener';
const ClipboardContentType = Object.freeze({
BITCOIN: 'BITCOIN',
@ -40,12 +38,13 @@ 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();
useDeviceQuickActions();
useHandoffListener();
const processPushNotifications = useCallback(async () => {
await new Promise(resolve => setTimeout(resolve, 200));
@ -311,12 +310,7 @@ const CompanionDelegates = () => {
};
}, [addListeners]);
return (
<Suspense fallback={null}>
{isQuickActionsEnabled && <DeviceQuickActions />}
{isHandOffUseEnabled && <HandOffComponentListener />}
</Suspense>
);
return null;
};
export default CompanionDelegates;

View File

@ -6,7 +6,10 @@ import { clearUseURv1, isURv1Enabled, setUseURv1 } from '../../blue_modules/ur';
import { BlueApp } from '../../class';
import { saveLanguage, STORAGE_KEY } from '../../loc';
import { FiatUnit, TFiatUnit } from '../../models/fiatUnit';
import { getEnabled as getIsDeviceQuickActionsEnabled, setEnabled as setIsDeviceQuickActionsEnabled } from '../DeviceQuickActions';
import {
getEnabled as getIsDeviceQuickActionsEnabled,
setEnabled as setIsDeviceQuickActionsEnabled,
} from '../../hooks/useDeviceQuickActions';
import { getIsHandOffUseEnabled, setIsHandOffUseEnabled } from '../HandOffComponent';
import { useStorage } from '../../hooks/context/useStorage';
import { BitcoinUnit } from '../../models/bitcoinUnits';

View File

@ -1,21 +0,0 @@
import React from 'react';
export const DeviceQuickActionsStorageKey = 'DeviceQuickActionsEnabled';
interface DeviceQuickActionsFunctions {
popInitialAction: () => void;
}
export const setEnabled = (): void => {};
export const getEnabled = async (): Promise<boolean> => {
return false;
};
const DeviceQuickActions: React.FC & DeviceQuickActionsFunctions = () => {
return null;
};
DeviceQuickActions.popInitialAction = (): void => {};
export default DeviceQuickActions;

View File

@ -1,6 +1,6 @@
import React from 'react';
import DefaultPreference from 'react-native-default-preference';
// @ts-ignore: react-native-handoff is not in the type definition
// @ts-ignore: Handoff is not typed
import Handoff from 'react-native-handoff';
import { useSettings } from '../hooks/context/useSettings';
import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
@ -9,32 +9,32 @@ import { HandOffComponentProps } from './types';
const HandOffComponent: React.FC<HandOffComponentProps> = props => {
const { isHandOffUseEnabled } = useSettings();
if (process.env.NODE_ENV === 'development') {
console.debug('HandOffComponent: render');
}
if (isHandOffUseEnabled) {
return <Handoff {...props} />;
}
return null;
console.debug('HandOffComponent is rendering.');
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
};
const MemoizedHandOffComponent = React.memo(HandOffComponent);
export const setIsHandOffUseEnabled = async (value: boolean) => {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BlueApp.HANDOFF_STORAGE_KEY, value.toString());
console.debug('setIsHandOffUseEnabledAsyncStorage', value);
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BlueApp.HANDOFF_STORAGE_KEY, value.toString());
console.debug('setIsHandOffUseEnabled', value);
} catch (error) {
console.error('Error setting handoff enabled status:', error);
throw error; // Propagate error to caller
}
};
export const getIsHandOffUseEnabled = async (): Promise<boolean> => {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const isEnabledValue = (await DefaultPreference.get(BlueApp.HANDOFF_STORAGE_KEY)) ?? false;
console.debug('getIsHandOffUseEnabled', isEnabledValue);
return isEnabledValue === 'true';
} catch (e) {
console.debug('getIsHandOffUseEnabled error', e);
const isEnabledValue = await DefaultPreference.get(BlueApp.HANDOFF_STORAGE_KEY);
const result = isEnabledValue === 'true';
console.debug('getIsHandOffUseEnabled', result);
return result;
} catch (error) {
console.error('Error getting handoff enabled status:', error);
return false;
}
};

View File

@ -1,73 +0,0 @@
import React, { useEffect, useCallback } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { useStorage } from '../hooks/context/useStorage';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { HandOffActivityType } from './types';
interface UserActivityData {
activityType: HandOffActivityType;
userInfo: {
address?: string;
xpub?: string;
};
}
const { EventEmitter } = NativeModules;
const eventEmitter = new NativeEventEmitter(EventEmitter);
const HandOffComponentListener: React.FC = React.memo(() => {
const { walletsInitialized } = useStorage();
const { navigate } = useExtendedNavigation();
const onUserActivityOpen = useCallback((data: UserActivityData) => {
switch (data.activityType) {
case HandOffActivityType.ReceiveOnchain:
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: {
address: data.userInfo.address,
},
});
break;
case HandOffActivityType.Xpub:
navigate('WalletXpubRoot', {
screen: 'WalletXpub',
params: {
xpub: data.userInfo.xpub,
},
});
break;
default:
console.log(`Unhandled activity type: ${data.activityType}`);
break;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!walletsInitialized) {
return;
}
const addListeners = () => {
const activitySubscription = eventEmitter.addListener('onUserActivityOpen', onUserActivityOpen);
// Attempt to fetch the most recent user activity
EventEmitter.getMostRecentUserActivity?.()
.then(onUserActivityOpen)
.catch(() => console.log('No userActivity object sent'));
return { activitySubscription };
};
const subscriptions = addListeners();
return () => {
subscriptions.activitySubscription?.remove();
};
}, [walletsInitialized, onUserActivityOpen]);
return null;
});
export default HandOffComponentListener;

View File

@ -1,7 +0,0 @@
import React from 'react';
const HandOffComponentListener: React.FC = () => {
return null;
};
export default HandOffComponentListener;

View File

@ -1,8 +1,8 @@
import { useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { CommonActions } from '@react-navigation/native';
import { useEffect } from 'react';
import { DeviceEventEmitter, Linking, Platform } from 'react-native';
import QuickActions from 'react-native-quick-actions';
import QuickActions, { ShortcutItem } from 'react-native-quick-actions';
import DeeplinkSchemaMatch from '../class/deeplink-schema-match';
import { TWallet } from '../class/wallets/types';
import useOnAppLaunch from '../hooks/useOnAppLaunch';
@ -30,16 +30,15 @@ export async function getEnabled(): Promise<boolean> {
}
}
function DeviceQuickActions() {
const useDeviceQuickActions = () => {
const { wallets, walletsInitialized, isStorageEncrypted, addWallet, saveToDisk, setSharedCosigner } = useStorage();
const { preferredFiatCurrency, isQuickActionsEnabled } = useSettings();
const { isViewAllWalletsEnabled, getSelectedDefaultWallet } = useOnAppLaunch();
useEffect(() => {
if (walletsInitialized) {
isStorageEncrypted()
.then((value: boolean | undefined | null) => {
.then(value => {
if (value) {
removeShortcuts();
} else {
@ -48,7 +47,7 @@ function DeviceQuickActions() {
})
.catch(() => removeShortcuts());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallets, walletsInitialized, preferredFiatCurrency, isStorageEncrypted]);
useEffect(() => {
@ -57,7 +56,7 @@ function DeviceQuickActions() {
popInitialShortcutAction().then(popInitialAction);
return () => DeviceEventEmitter.removeAllListeners('quickActionShortcut');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletsInitialized]);
useEffect(() => {
@ -68,7 +67,7 @@ function DeviceQuickActions() {
removeShortcuts();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isQuickActionsEnabled, walletsInitialized]);
const popInitialShortcutAction = async (): Promise<any> => {
@ -78,7 +77,7 @@ function DeviceQuickActions() {
const popInitialAction = async (data: any): Promise<void> => {
if (data) {
const wallet = wallets.find((w: { getID: () => any }) => w.getID() === data.userInfo.url.split('wallet/')[1]);
const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]);
if (wallet) {
NavigationService.dispatch(
CommonActions.navigate({
@ -126,7 +125,7 @@ function DeviceQuickActions() {
};
const walletQuickActions = (data: any): void => {
const wallet = wallets.find((w: { getID: () => any }) => w.getID() === data.userInfo.url.split('wallet/')[1]);
const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]);
if (wallet) {
NavigationService.dispatch(
CommonActions.navigate({
@ -153,22 +152,21 @@ function DeviceQuickActions() {
if (await getEnabled()) {
QuickActions.isSupported((error: null, _supported: any) => {
if (error === null) {
const shortcutItems = [];
for (const wallet of wallets.slice(0, 4)) {
shortcutItems.push({
type: 'Wallets', // Required
title: wallet.getLabel(), // Optional, if empty, `type` will be used instead
subtitle:
wallet.hideBalance || wallet.getBalance() <= 0
? ''
: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
userInfo: {
url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL
},
icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }),
});
}
// @ts-ignore: Fix later
const shortcutItems: ShortcutItem[] = wallets.slice(0, 4).map((wallet, index) => ({
type: 'Wallets',
title: wallet.getLabel(),
subtitle:
wallet.hideBalance || wallet.getBalance() <= 0
? ''
: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
userInfo: {
url: `bluewallet://wallet/${wallet.getID()}`,
},
icon: Platform.select({
android: 'quickactions',
ios: index === 0 ? 'Favorite' : 'Bookmark',
}) || 'quickactions',
}));
QuickActions.setShortcutItems(shortcutItems);
}
});
@ -177,7 +175,7 @@ function DeviceQuickActions() {
}
};
return null;
return { popInitialAction };
}
export default DeviceQuickActions;
export default useDeviceQuickActions;

View File

@ -0,0 +1,17 @@
export const DeviceQuickActionsStorageKey = 'DeviceQuickActionsEnabled';
export const setEnabled = (): void => {};
export const getEnabled = async (): Promise<boolean> => {
return false;
};
const useDeviceQuickActions = () => {
const popInitialAction = (): void => {};
return { popInitialAction };
};
export default useDeviceQuickActions;

View File

@ -0,0 +1,63 @@
import { useEffect, useCallback } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { useStorage } from '../hooks/context/useStorage';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { HandOffActivityType } from '../components/types';
import { useSettings } from './context/useSettings';
interface UserActivityData {
activityType: HandOffActivityType;
userInfo: {
address?: string;
xpub?: string;
};
}
const EventEmitter = NativeModules.EventEmitter;
const eventEmitter = EventEmitter ? new NativeEventEmitter(EventEmitter) : null;
const useHandoffListener = () => {
const { walletsInitialized } = useStorage();
const { isHandOffUseEnabled } = useSettings();
const { navigate } = useExtendedNavigation();
const handleUserActivity = useCallback(
(data: UserActivityData) => {
const { activityType, userInfo } = data;
try {
if (activityType === HandOffActivityType.ReceiveOnchain) {
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: { address: userInfo.address },
});
} else if (activityType === HandOffActivityType.Xpub) {
navigate('WalletXpubRoot', {
screen: 'WalletXpub',
params: { xpub: userInfo.xpub },
});
} else {
console.debug(`Unhandled activity type: ${activityType}`);
}
} catch (error) {
console.error('Error handling user activity:', error);
}
},
[navigate],
);
useEffect(() => {
if (!walletsInitialized || !isHandOffUseEnabled) return;
const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', handleUserActivity);
EventEmitter.getMostRecentUserActivity?.()
.then(handleUserActivity)
.catch(() => console.debug('No userActivity object sent'));
return () => {
activitySubscription?.remove();
};
}, [walletsInitialized, isHandOffUseEnabled, handleUserActivity]);
};
export default useHandoffListener;

View File

@ -0,0 +1,3 @@
const useHandoffListener = () => {};
export default useHandoffListener;

View File

@ -44,6 +44,8 @@ import assert from 'assert';
import useMenuElements from '../../hooks/useMenuElements';
import { useSettings } from '../../hooks/context/useSettings';
import { getClipboardContent } from '../../blue_modules/clipboard';
import HandOffComponent from '../../components/HandOffComponent';
import { HandOffActivityType } from '../../components/types';
const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
@ -496,6 +498,13 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
/>
)}
</FContainer>
{wallet?.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type && wallet.getXpub && wallet.getXpub() ? (
<HandOffComponent
title={wallet.getLabel()}
type={HandOffActivityType.Xpub}
url={`https://www.blockonomics.co/#/search?q=${wallet.getXpub()}`}
/>
) : null}
</View>
);
};