Merge pull request #7577 from BlueWallet/notifre

FIX: Alert the user if notifications deregister failed
This commit is contained in:
GLaDOS 2025-02-10 19:06:25 +00:00 committed by GitHub
commit ae80cb9118
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 98 additions and 47 deletions

View file

@ -8,7 +8,7 @@ import loc from '../../loc';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { startAndDecrypt } from '../../blue_modules/start-and-decrypt';
import { majorTomToGroundControl } from '../../blue_modules/notifications';
import { isNotificationsEnabled, majorTomToGroundControl, unsubscribe } from '../../blue_modules/notifications';
const BlueApp = BlueAppClass.getInstance();
@ -49,6 +49,7 @@ interface StorageContextType {
cachedPassword: typeof BlueApp.cachedPassword;
getItem: typeof BlueApp.getItem;
setItem: typeof BlueApp.setItem;
handleWalletDeletion: (walletID: string, forceDelete?: boolean) => Promise<void>;
}
export enum WalletTransactionsStatus {
@ -99,6 +100,57 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
setWallets([...BlueApp.getWallets()]);
}, []);
const handleWalletDeletion = useCallback(
async (walletID: string, forceDelete = false) => {
const wallet = wallets.find(w => w.getID() === walletID);
if (!wallet) return;
try {
const isNotificationsSettingsEnabled = await isNotificationsEnabled();
if (isNotificationsSettingsEnabled) {
const externalAddresses = wallet.getAllExternalAddresses();
if (externalAddresses.length > 0) {
await unsubscribe(externalAddresses, [], []);
}
}
deleteWallet(wallet);
saveToDisk(true);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
} catch (e: unknown) {
console.error(e);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
if (forceDelete) {
deleteWallet(wallet);
saveToDisk(true);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
} else {
presentAlert({
title: loc.errors.error,
message: loc.wallets.details_delete_wallet_error_message,
buttons: [
{
text: loc.wallets.details_delete_anyway,
onPress: () => handleWalletDeletion(walletID, true),
style: 'destructive',
},
{
text: loc.wallets.list_tryagain,
onPress: () => handleWalletDeletion(walletID),
},
{
text: loc._.cancel,
onPress: () => {},
style: 'cancel',
},
],
options: { cancelable: false },
});
}
}
},
[deleteWallet, saveToDisk, wallets],
);
const resetWallets = useCallback(() => {
setWallets(BlueApp.getWallets());
}, []);
@ -274,6 +326,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
isPasswordInUse: BlueApp.isPasswordInUse,
walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus,
handleWalletDeletion,
}),
[
wallets,
@ -292,6 +345,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
resetWallets,
walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus,
handleWalletDeletion,
],
);

View file

@ -422,7 +422,6 @@
"details_export_history": "Export History to CSV",
"details_master_fingerprint": "Master Fingerprint",
"details_multisig_type": "multisig",
"details_no_cancel": "No, cancel",
"details_show_xpub": "Show Wallet XPUB",
"details_show_addresses": "Show addresses",
"details_title": "Wallet",
@ -493,7 +492,9 @@
"identity_pubkey": "Identity Pubkey",
"xpub_title": "Wallet XPUB",
"manage_wallets_search_placeholder": "Search wallets, memos",
"more_info": "More Info"
"more_info": "More Info",
"details_delete_wallet_error_message": "There was an issue confirming if this wallet was removed from notifications—this could be due to a network issue or poor connection. If you continue, you might still receive notifications for transactions related to this wallet, even after it is deleted.",
"details_delete_anyway": "Delete anyway"
},
"total_balance_view": {
"display_in_bitcoin": "Display in Bitcoin",

View file

@ -16,7 +16,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useTheme } from '../../components/themes';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc from '../../loc';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import useDebounce from '../../hooks/useDebounce';
import { TTXMetadata } from '../../class';
@ -28,6 +28,7 @@ import prompt from '../../helpers/prompt';
import HeaderRightButton from '../../components/HeaderRightButton';
import { useSettings } from '../../hooks/context/useSettings';
import DragList, { DragListRenderItemInfo } from 'react-native-draglist';
import { BitcoinUnit } from '../../models/bitcoinUnits';
const ManageWalletsListItem = lazy(() => import('../../components/ManageWalletsListItem'));
@ -193,7 +194,7 @@ const reducer = (state: State, action: Action): State => {
const ManageWallets: React.FC = () => {
const { colors, closeImage } = useTheme();
const { wallets: storedWallets, setWalletsWithNewOrder, txMetadata } = useStorage();
const { wallets: storedWallets, setWalletsWithNewOrder, txMetadata, handleWalletDeletion } = useStorage();
const { setIsDrawerShouldHide } = useSettings();
const walletsRef = useRef<TWallet[]>(deepCopyWallets(storedWallets)); // Create a deep copy of wallets for the DraggableFlatList
const { navigate, setOptions, goBack } = useExtendedNavigation();
@ -242,6 +243,12 @@ const ManageWallets: React.FC = () => {
walletsRef.current = deepCopyWallets(newWalletOrder);
state.tempOrder.forEach(item => {
if (item.type === ItemType.WalletSection && !newWalletOrder.some(wallet => wallet.getID() === item.data.getID())) {
handleWalletDeletion(item.data.getID());
}
});
if (beforeRemoveListenerRef.current) {
navigation.removeListener('beforeRemove', beforeRemoveListenerRef.current);
}
@ -251,7 +258,7 @@ const ManageWallets: React.FC = () => {
dispatch({ type: SET_SEARCH_QUERY, payload: '' });
dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: false });
}
}, [goBack, setWalletsWithNewOrder, state.searchQuery, state.isSearchFocused, state.tempOrder, navigation]);
}, [goBack, setWalletsWithNewOrder, state.searchQuery, state.isSearchFocused, state.tempOrder, navigation, handleWalletDeletion]);
const hasUnsavedChanges = useMemo(() => {
return JSON.stringify(walletsRef.current) !== JSON.stringify(state.tempOrder.map(item => item.data));
@ -361,15 +368,20 @@ const ManageWallets: React.FC = () => {
const presentWalletHasBalanceAlert = useCallback(async (wallet: TWallet) => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
try {
const balance = formatBalanceWithoutSuffix(wallet.getBalance(), BitcoinUnit.SATS, true);
const walletBalanceConfirmation = await prompt(
loc.wallets.details_delete_wallet,
loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }),
loc.formatString(loc.wallets.details_del_wb_q, { balance }),
true,
'plain-text',
'numeric',
true,
loc.wallets.details_delete,
);
if (Number(walletBalanceConfirmation) === wallet.getBalance()) {
const cleanedConfirmation = (walletBalanceConfirmation || '').replace(/[^0-9]/g, '');
if (Number(cleanedConfirmation) === wallet.getBalance()) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
dispatch({ type: REMOVE_WALLET, payload: wallet.getID() });
} else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
@ -381,10 +393,10 @@ const ManageWallets: React.FC = () => {
const handleDeleteWallet = useCallback(
async (wallet: TWallet) => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
Alert.alert(
loc.wallets.details_delete_wallet,
loc.wallets.details_are_you_sure,
[
presentAlert({
title: loc.wallets.details_delete_wallet,
message: loc.wallets.details_are_you_sure,
buttons: [
{
text: loc.wallets.details_yes_delete,
onPress: async () => {
@ -395,7 +407,7 @@ const ManageWallets: React.FC = () => {
return;
}
}
if (wallet.getBalance() > 0 && wallet.allowSend()) {
if (wallet.getBalance && wallet.getBalance() > 0 && wallet.allowSend && wallet.allowSend()) {
presentWalletHasBalanceAlert(wallet);
} else {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
@ -404,10 +416,10 @@ const ManageWallets: React.FC = () => {
},
style: 'destructive',
},
{ text: loc.wallets.details_no_cancel, onPress: () => {}, style: 'cancel' },
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
options: { cancelable: false },
});
},
[isBiometricUseCapableAndEnabled, presentWalletHasBalanceAlert],
);

View file

@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
ActivityIndicator,
Alert,
I18nManager,
InteractionManager,
LayoutAnimation,
@ -38,18 +37,17 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { useStorage } from '../../hooks/context/useStorage';
import { popToTop } from '../../NavigationService';
import { useFocusEffect, useRoute, RouteProp } from '@react-navigation/native';
import { LightningTransaction, Transaction, TWallet } from '../../class/wallets/types';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { unsubscribe } from '../../blue_modules/notifications';
import HeaderMenuButton from '../../components/HeaderMenuButton';
import { Action } from '../../components/types';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { popToTop } from '../../NavigationService';
type RouteProps = RouteProp<DetailViewStackParamList, 'WalletDetails'>;
const WalletDetails: React.FC = () => {
const { saveToDisk, wallets, deleteWallet, setSelectedWalletID, txMetadata } = useStorage();
const { saveToDisk, wallets, setSelectedWalletID, txMetadata, handleWalletDeletion } = useStorage();
const { isBiometricUseCapableAndEnabled } = useBiometrics();
const { walletID } = useRoute<RouteProps>().params;
const [isLoading, setIsLoading] = useState<boolean>(false);
@ -89,25 +87,11 @@ const WalletDetails: React.FC = () => {
}, [wallet]);
const [isMasterFingerPrintVisible, setIsMasterFingerPrintVisible] = useState<boolean>(false);
const navigateToOverviewAndDeleteWallet = useCallback(async () => {
const navigateToOverviewAndDeleteWallet = useCallback(() => {
setIsLoading(true);
try {
const externalAddresses = wallet.getAllExternalAddresses();
if (externalAddresses.length > 0) {
await unsubscribe(externalAddresses, [], []);
}
deleteWallet(wallet);
saveToDisk(true);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
popToTop();
} catch (e: unknown) {
console.error(e);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: (e as Error).message });
setIsLoading(false);
}
}, [deleteWallet, saveToDisk, wallet]);
handleWalletDeletion(wallet.getID());
popToTop();
}, [handleWalletDeletion, wallet]);
const presentWalletHasBalanceAlert = useCallback(async () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
@ -125,7 +109,7 @@ const WalletDetails: React.FC = () => {
const cleanedConfirmation = (walletBalanceConfirmation || '').replace(/[^0-9]/g, '');
if (Number(cleanedConfirmation) === wallet.getBalance()) {
await navigateToOverviewAndDeleteWallet();
navigateToOverviewAndDeleteWallet();
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
} else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
@ -137,10 +121,10 @@ const WalletDetails: React.FC = () => {
const handleDeleteButtonTapped = useCallback(() => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
Alert.alert(
loc.wallets.details_delete_wallet,
loc.wallets.details_are_you_sure,
[
presentAlert({
title: loc.wallets.details_delete_wallet,
message: loc.wallets.details_are_you_sure,
buttons: [
{
text: loc.wallets.details_yes_delete,
onPress: async () => {
@ -159,10 +143,10 @@ const WalletDetails: React.FC = () => {
},
style: 'destructive',
},
{ text: loc.wallets.details_no_cancel, onPress: () => {}, style: 'cancel' },
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
options: { cancelable: false },
});
}, [isBiometricUseCapableAndEnabled, navigateToOverviewAndDeleteWallet, presentWalletHasBalanceAlert, wallet]);
const exportHistoryContent = useCallback(() => {