mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-26 08:55:56 +01:00
commit
dffd02b4c1
8 changed files with 336 additions and 319 deletions
294
App.js
294
App.js
|
@ -1,294 +0,0 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import {
|
||||
AppState,
|
||||
NativeModules,
|
||||
NativeEventEmitter,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
UIManager,
|
||||
useColorScheme,
|
||||
View,
|
||||
LogBox,
|
||||
} from 'react-native';
|
||||
import { NavigationContainer, CommonActions } from '@react-navigation/native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { navigationRef } from './NavigationService';
|
||||
import * as NavigationService from './NavigationService';
|
||||
import { Chain } from './models/bitcoinUnits';
|
||||
import DeeplinkSchemaMatch from './class/deeplink-schema-match';
|
||||
import loc from './loc';
|
||||
import { BlueDefaultTheme, BlueDarkTheme } from './components/themes';
|
||||
import BlueClipboard from './blue_modules/clipboard';
|
||||
import { BlueStorageContext } from './blue_modules/storage-context';
|
||||
import WatchConnectivity from './WatchConnectivity';
|
||||
import Notifications from './blue_modules/notifications';
|
||||
import Biometric from './class/biometrics';
|
||||
import WidgetCommunication from './components/WidgetCommunication';
|
||||
import ActionSheet from './screen/ActionSheet';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from './blue_modules/hapticFeedback';
|
||||
import MenuElements from './components/MenuElements';
|
||||
import { updateExchangeRate } from './blue_modules/currency';
|
||||
import { NavigationProvider } from './components/NavigationProvider';
|
||||
import A from './blue_modules/analytics';
|
||||
import HandOffComponentListener from './components/HandOffComponentListener';
|
||||
import DeviceQuickActions from './components/DeviceQuickActions';
|
||||
import MainRoot from './Navigation';
|
||||
|
||||
const eventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined;
|
||||
const { SplashScreen } = NativeModules;
|
||||
|
||||
LogBox.ignoreLogs(['Require cycle:', 'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.']);
|
||||
|
||||
const ClipboardContentType = Object.freeze({
|
||||
BITCOIN: 'BITCOIN',
|
||||
LIGHTNING: 'LIGHTNING',
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
walletsInitialized,
|
||||
wallets,
|
||||
addWallet,
|
||||
saveToDisk,
|
||||
fetchAndSaveWalletTransactions,
|
||||
refreshAllWalletTransactions,
|
||||
setSharedCosigner,
|
||||
} = useContext(BlueStorageContext);
|
||||
const appState = useRef(AppState.currentState);
|
||||
const clipboardContent = useRef();
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
const onNotificationReceived = async notification => {
|
||||
const payload = Object.assign({}, notification, notification.data);
|
||||
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
|
||||
payload.foreground = true;
|
||||
|
||||
await Notifications.addNotification(payload);
|
||||
// if user is staring at the app when he receives the notification we process it instantly
|
||||
// so app refetches related wallet
|
||||
if (payload.foreground) await processPushNotifications();
|
||||
};
|
||||
|
||||
const addListeners = () => {
|
||||
const urlSubscription = Linking.addEventListener('url', handleOpenURL);
|
||||
const appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
|
||||
|
||||
const notificationSubscription = eventEmitter?.addListener('onNotificationReceived', onNotificationReceived);
|
||||
|
||||
// Store subscriptions in a ref or state to remove them later
|
||||
return {
|
||||
urlSubscription,
|
||||
appStateSubscription,
|
||||
notificationSubscription,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized) {
|
||||
const subscriptions = addListeners();
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
subscriptions.urlSubscription?.remove();
|
||||
subscriptions.appStateSubscription?.remove();
|
||||
subscriptions.notificationSubscription?.remove();
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized]); // Re-run when walletsInitialized changes
|
||||
|
||||
/**
|
||||
* Processes push notifications stored in AsyncStorage. Might navigate to some screen.
|
||||
*
|
||||
* @returns {Promise<boolean>} returns TRUE if notification was processed _and acted_ upon, i.e. navigation happened
|
||||
* @private
|
||||
*/
|
||||
const processPushNotifications = async () => {
|
||||
if (!walletsInitialized) {
|
||||
console.log('not processing push notifications because wallets are not initialized');
|
||||
return;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
// sleep needed as sometimes unsuspend is faster than notification module actually saves notifications to async storage
|
||||
const notifications2process = await Notifications.getStoredNotifications();
|
||||
|
||||
await Notifications.clearStoredNotifications();
|
||||
Notifications.setApplicationIconBadgeNumber(0);
|
||||
const deliveredNotifications = await Notifications.getDeliveredNotifications();
|
||||
setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000); // so notification bubble wont disappear too fast
|
||||
|
||||
for (const payload of notifications2process) {
|
||||
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction);
|
||||
|
||||
console.log('processing push notification:', payload);
|
||||
let wallet;
|
||||
switch (+payload.type) {
|
||||
case 2:
|
||||
case 3:
|
||||
wallet = wallets.find(w => w.weOwnAddress(payload.address));
|
||||
break;
|
||||
case 1:
|
||||
case 4:
|
||||
wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash));
|
||||
break;
|
||||
}
|
||||
|
||||
if (wallet) {
|
||||
const walletID = wallet.getID();
|
||||
fetchAndSaveWalletTransactions(walletID);
|
||||
if (wasTapped) {
|
||||
if (payload.type !== 3 || wallet.chain === Chain.OFFCHAIN) {
|
||||
NavigationService.dispatch(
|
||||
CommonActions.navigate({
|
||||
name: 'WalletTransactions',
|
||||
params: {
|
||||
walletID,
|
||||
walletType: wallet.type,
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
NavigationService.navigate('ReceiveDetailsRoot', {
|
||||
screen: 'ReceiveDetails',
|
||||
params: {
|
||||
walletID,
|
||||
address: payload.address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
console.log('could not find wallet while processing push notification, NOP');
|
||||
}
|
||||
} // end foreach notifications loop
|
||||
|
||||
if (deliveredNotifications.length > 0) {
|
||||
// notification object is missing userInfo. We know we received a notification but don't have sufficient
|
||||
// data to refresh 1 wallet. let's refresh all.
|
||||
refreshAllWalletTransactions();
|
||||
}
|
||||
|
||||
// if we are here - we did not act upon any push
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleAppStateChange = async nextAppState => {
|
||||
if (wallets.length === 0) return;
|
||||
if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) {
|
||||
setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000);
|
||||
updateExchangeRate();
|
||||
const processed = await processPushNotifications();
|
||||
if (processed) return;
|
||||
const clipboard = await BlueClipboard().getClipboardContent();
|
||||
const isAddressFromStoredWallet = wallets.some(wallet => {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
// checking address validity is faster than unwrapping hierarchy only to compare it to garbage
|
||||
return wallet.isAddressValid && wallet.isAddressValid(clipboard) && wallet.weOwnAddress(clipboard);
|
||||
} else {
|
||||
return wallet.isInvoiceGeneratedByWallet(clipboard) || wallet.weOwnAddress(clipboard);
|
||||
}
|
||||
});
|
||||
const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(clipboard);
|
||||
const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard);
|
||||
const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard);
|
||||
const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard);
|
||||
if (
|
||||
!isAddressFromStoredWallet &&
|
||||
clipboardContent.current !== clipboard &&
|
||||
(isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning)
|
||||
) {
|
||||
let contentType;
|
||||
if (isBitcoinAddress) {
|
||||
contentType = ClipboardContentType.BITCOIN;
|
||||
} else if (isLightningInvoice || isLNURL) {
|
||||
contentType = ClipboardContentType.LIGHTNING;
|
||||
} else if (isBothBitcoinAndLightning) {
|
||||
contentType = ClipboardContentType.BITCOIN;
|
||||
}
|
||||
showClipboardAlert({ contentType });
|
||||
}
|
||||
clipboardContent.current = clipboard;
|
||||
}
|
||||
if (nextAppState) {
|
||||
appState.current = nextAppState;
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenURL = event => {
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), {
|
||||
wallets,
|
||||
addWallet,
|
||||
saveToDisk,
|
||||
setSharedCosigner,
|
||||
});
|
||||
};
|
||||
|
||||
const showClipboardAlert = ({ contentType }) => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
BlueClipboard()
|
||||
.getClipboardContent()
|
||||
.then(clipboard => {
|
||||
ActionSheet.showActionSheetWithOptions(
|
||||
{
|
||||
title: loc._.clipboard,
|
||||
message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning,
|
||||
options: [loc._.cancel, loc._.continue],
|
||||
cancelButtonIndex: 0,
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0: // Cancel
|
||||
break;
|
||||
case 1:
|
||||
handleOpenURL({ url: clipboard });
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
// Call hide to setup the listener on the native side
|
||||
SplashScreen?.addObserver();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
|
||||
<NavigationProvider>
|
||||
<SafeAreaProvider>
|
||||
<View style={styles.root}>
|
||||
<MainRoot />
|
||||
<Notifications onProcessNotifications={processPushNotifications} />
|
||||
<MenuElements />
|
||||
<DeviceQuickActions />
|
||||
<Biometric />
|
||||
<HandOffComponentListener />
|
||||
</View>
|
||||
<WatchConnectivity />
|
||||
<WidgetCommunication />
|
||||
</SafeAreaProvider>
|
||||
</NavigationProvider>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
39
App.tsx
Normal file
39
App.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { useEffect } from 'react';
|
||||
import { NativeModules, Platform, useColorScheme } from 'react-native';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { navigationRef } from './NavigationService';
|
||||
import { BlueDefaultTheme, BlueDarkTheme } from './components/themes';
|
||||
import { NavigationProvider } from './components/NavigationProvider';
|
||||
import { BlueStorageProvider } from './blue_modules/storage-context';
|
||||
import MasterView from './MasterView';
|
||||
import { SettingsProvider } from './components/Context/SettingsContext';
|
||||
const { SplashScreen } = NativeModules;
|
||||
|
||||
const App = () => {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
// Call hide to setup the listener on the native side
|
||||
SplashScreen?.addObserver();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
|
||||
<NavigationProvider>
|
||||
<SafeAreaProvider>
|
||||
<BlueStorageProvider>
|
||||
<SettingsProvider>
|
||||
<MasterView />
|
||||
</SettingsProvider>
|
||||
</BlueStorageProvider>
|
||||
</SafeAreaProvider>
|
||||
</NavigationProvider>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
33
MasterView.tsx
Normal file
33
MasterView.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { Suspense, lazy, useEffect } from 'react';
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
import MainRoot from './navigation';
|
||||
import { useStorage } from './blue_modules/storage-context';
|
||||
import Biometric from './class/biometrics';
|
||||
const CompanionDelegates = lazy(() => import('./components/CompanionDelegates'));
|
||||
const { SplashScreen } = NativeModules;
|
||||
|
||||
const MasterView = () => {
|
||||
const { walletsInitialized } = useStorage();
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
// Call hide to setup the listener on the native side
|
||||
SplashScreen?.addObserver();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Biometric />
|
||||
<MainRoot />
|
||||
{walletsInitialized && (
|
||||
<Suspense>
|
||||
<CompanionDelegates />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MasterView;
|
245
components/CompanionDelegates.tsx
Normal file
245
components/CompanionDelegates.tsx
Normal file
|
@ -0,0 +1,245 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { useEffect, useRef, useCallback, lazy, Suspense } from 'react';
|
||||
import { AppState, NativeModules, NativeEventEmitter, Linking, Platform, UIManager, AppStateStatus } from 'react-native';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import { navigationRef } from '../NavigationService';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import DeeplinkSchemaMatch from '../class/deeplink-schema-match';
|
||||
import loc from '../loc';
|
||||
import BlueClipboard from '../blue_modules/clipboard';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
import { updateExchangeRate } from '../blue_modules/currency';
|
||||
import A from '../blue_modules/analytics';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
import { LightningCustodianWallet } from '../class';
|
||||
import ActionSheet from '../screen/ActionSheet';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
|
||||
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',
|
||||
LIGHTNING: 'LIGHTNING',
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
const CompanionDelegates = () => {
|
||||
const { wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions, setSharedCosigner } = useStorage();
|
||||
const appState = useRef<AppStateStatus>(AppState.currentState);
|
||||
const clipboardContent = useRef<undefined | string>();
|
||||
|
||||
const processPushNotifications = useCallback(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
const notifications2process = await Notifications.getStoredNotifications();
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
await Notifications.clearStoredNotifications();
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
Notifications.setApplicationIconBadgeNumber(0);
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
const deliveredNotifications = await Notifications.getDeliveredNotifications();
|
||||
// @ts-ignore: Notifications type is not defined
|
||||
setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000);
|
||||
|
||||
for (const payload of notifications2process) {
|
||||
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction);
|
||||
|
||||
console.log('processing push notification:', payload);
|
||||
let wallet;
|
||||
switch (+payload.type) {
|
||||
case 2:
|
||||
case 3:
|
||||
wallet = wallets.find(w => w.weOwnAddress(payload.address));
|
||||
break;
|
||||
case 1:
|
||||
case 4:
|
||||
wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash));
|
||||
break;
|
||||
}
|
||||
|
||||
if (wallet) {
|
||||
const walletID = wallet.getID();
|
||||
fetchAndSaveWalletTransactions(walletID);
|
||||
if (wasTapped) {
|
||||
if (payload.type !== 3 || wallet.chain === Chain.OFFCHAIN) {
|
||||
navigationRef.dispatch(
|
||||
CommonActions.navigate({
|
||||
name: 'WalletTransactions',
|
||||
params: {
|
||||
walletID,
|
||||
walletType: wallet.type,
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
navigationRef.navigate('ReceiveDetailsRoot', {
|
||||
screen: 'ReceiveDetails',
|
||||
params: {
|
||||
walletID,
|
||||
address: payload.address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
console.log('could not find wallet while processing push notification, NOP');
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveredNotifications.length > 0) {
|
||||
refreshAllWalletTransactions();
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [fetchAndSaveWalletTransactions, refreshAllWalletTransactions, wallets]);
|
||||
|
||||
const handleOpenURL = useCallback(
|
||||
(event: { url: string }) => {
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, value => navigationRef.navigate(...value), {
|
||||
wallets,
|
||||
addWallet,
|
||||
saveToDisk,
|
||||
setSharedCosigner,
|
||||
});
|
||||
},
|
||||
[addWallet, saveToDisk, setSharedCosigner, wallets],
|
||||
);
|
||||
|
||||
const showClipboardAlert = useCallback(
|
||||
({ contentType }: { contentType: undefined | string }) => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
BlueClipboard()
|
||||
.getClipboardContent()
|
||||
.then(clipboard => {
|
||||
ActionSheet.showActionSheetWithOptions(
|
||||
{
|
||||
title: loc._.clipboard,
|
||||
message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning,
|
||||
options: [loc._.cancel, loc._.continue],
|
||||
cancelButtonIndex: 0,
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
handleOpenURL({ url: clipboard });
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
[handleOpenURL],
|
||||
);
|
||||
|
||||
const handleAppStateChange = useCallback(
|
||||
async (nextAppState: AppStateStatus | undefined) => {
|
||||
if (wallets.length === 0) return;
|
||||
if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) {
|
||||
setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000);
|
||||
updateExchangeRate();
|
||||
const processed = await processPushNotifications();
|
||||
if (processed) return;
|
||||
const clipboard = await BlueClipboard().getClipboardContent();
|
||||
const isAddressFromStoredWallet = wallets.some(wallet => {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
return wallet.isAddressValid && wallet.isAddressValid(clipboard) && wallet.weOwnAddress(clipboard);
|
||||
} else {
|
||||
return (wallet as LightningCustodianWallet).isInvoiceGeneratedByWallet(clipboard) || wallet.weOwnAddress(clipboard);
|
||||
}
|
||||
});
|
||||
const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(clipboard);
|
||||
const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard);
|
||||
const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard);
|
||||
const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard);
|
||||
if (
|
||||
!isAddressFromStoredWallet &&
|
||||
clipboardContent.current !== clipboard &&
|
||||
(isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning)
|
||||
) {
|
||||
let contentType;
|
||||
if (isBitcoinAddress) {
|
||||
contentType = ClipboardContentType.BITCOIN;
|
||||
} else if (isLightningInvoice || isLNURL) {
|
||||
contentType = ClipboardContentType.LIGHTNING;
|
||||
} else if (isBothBitcoinAndLightning) {
|
||||
contentType = ClipboardContentType.BITCOIN;
|
||||
}
|
||||
showClipboardAlert({ contentType });
|
||||
}
|
||||
clipboardContent.current = clipboard;
|
||||
}
|
||||
if (nextAppState) {
|
||||
appState.current = nextAppState;
|
||||
}
|
||||
},
|
||||
[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]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = addListeners();
|
||||
|
||||
return () => {
|
||||
subscriptions.urlSubscription?.remove();
|
||||
subscriptions.appStateSubscription?.remove();
|
||||
subscriptions.notificationSubscription?.remove();
|
||||
};
|
||||
}, [addListeners]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Notifications onProcessNotifications={processPushNotifications} />
|
||||
<Suspense fallback={null}>
|
||||
<MenuElements />
|
||||
<DeviceQuickActions />
|
||||
<HandOffComponentListener />
|
||||
<WidgetCommunication />
|
||||
<WatchConnectivity />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanionDelegates;
|
21
index.js
21
index.js
|
@ -1,12 +1,9 @@
|
|||
import './shim.js';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { AppRegistry } from 'react-native';
|
||||
|
||||
import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
|
||||
import App from './App';
|
||||
import { BlueStorageProvider } from './blue_modules/storage-context';
|
||||
import A from './blue_modules/analytics';
|
||||
import { SettingsProvider } from './components/Context/SettingsContext';
|
||||
import { restoreSavedPreferredFiatCurrencyAndExchangeFromStorage } from './blue_modules/currency';
|
||||
|
||||
if (!Error.captureStackTrace) {
|
||||
|
@ -14,19 +11,21 @@ if (!Error.captureStackTrace) {
|
|||
Error.captureStackTrace = () => {};
|
||||
}
|
||||
|
||||
LogBox.ignoreLogs(['Require cycle:', 'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.']);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
const BlueAppComponent = () => {
|
||||
useEffect(() => {
|
||||
restoreSavedPreferredFiatCurrencyAndExchangeFromStorage();
|
||||
A(A.ENUM.INIT);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BlueStorageProvider>
|
||||
<SettingsProvider>
|
||||
<App />
|
||||
</SettingsProvider>
|
||||
</BlueStorageProvider>
|
||||
);
|
||||
return <App />;
|
||||
};
|
||||
|
||||
AppRegistry.registerComponent('BlueWallet', () => BlueAppComponent);
|
||||
|
|
|
@ -70,12 +70,7 @@ import {
|
|||
import { Icon } from 'react-native-elements';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { I18nManager, Platform, TouchableOpacity } from 'react-native';
|
||||
import {
|
||||
NavigationDefaultOptions,
|
||||
NavigationDefaultOptionsForDesktop,
|
||||
NavigationFormModalOptions,
|
||||
StatusBarLightOptions,
|
||||
} from '../Navigation';
|
||||
import { NavigationDefaultOptions, NavigationDefaultOptionsForDesktop, NavigationFormModalOptions, StatusBarLightOptions } from './';
|
||||
|
||||
const DetailViewRoot = createNativeStackNavigator();
|
||||
const DetailViewStackScreensStack = () => {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { Suspense, lazy } from 'react';
|
||||
import { NativeStackNavigationOptions, createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { useStorage } from './blue_modules/storage-context';
|
||||
import UnlockWith from './screen/UnlockWith';
|
||||
import { LazyLoadingIndicator } from './navigation/LazyLoadingIndicator';
|
||||
import { isHandset } from './blue_modules/environment';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
import UnlockWith from '../screen/UnlockWith';
|
||||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
import { isHandset } from '../blue_modules/environment';
|
||||
|
||||
const DetailViewScreensStack = lazy(() => import('./navigation/DetailViewScreensStack'));
|
||||
const DrawerRoot = lazy(() => import('./navigation/DrawerRoot'));
|
||||
const DetailViewScreensStack = lazy(() => import('./DetailViewScreensStack'));
|
||||
const DrawerRoot = lazy(() => import('./DrawerRoot'));
|
||||
|
||||
export const NavigationDefaultOptions: NativeStackNavigationOptions = {
|
||||
headerShown: false,
|
|
@ -3,7 +3,7 @@ const path = require('path');
|
|||
|
||||
const mainLocFile = './loc/en.json';
|
||||
const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers', 'navigation'];
|
||||
const addFiles = ['BlueComponents.js', 'App.js', 'Navigation.tsx'];
|
||||
const addFiles = ['BlueComponents.js', 'App.tsx', 'navigation/index.tsx'];
|
||||
const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units'];
|
||||
|
||||
const allLocKeysHashmap = {}; // loc key -> used or not
|
||||
|
@ -32,7 +32,7 @@ for (const dir of dirsToInterate) {
|
|||
for (const filename of addFiles) {
|
||||
allDirFiles.push(path.resolve(filename));
|
||||
}
|
||||
allDirFiles.push(path.resolve('App.js'));
|
||||
allDirFiles.push(path.resolve('App.tsx'));
|
||||
|
||||
// got all source files
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue