mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-21 14:34:55 +01:00
Merge branch 'master' into storageuse
This commit is contained in:
commit
6612360b1e
82 changed files with 1842 additions and 1568 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;
|
34
App.tsx
Normal file
34
App.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React from 'react';
|
||||
import { 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 { SettingsProvider } from './components/Context/SettingsContext';
|
||||
import { LargeScreenProvider } from './components/Context/LargeScreenProvider';
|
||||
import MasterView from './navigation/MasterView';
|
||||
|
||||
const App = () => {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<LargeScreenProvider>
|
||||
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
|
||||
<NavigationProvider>
|
||||
<SafeAreaProvider>
|
||||
<BlueStorageProvider>
|
||||
<SettingsProvider>
|
||||
<MasterView />
|
||||
</SettingsProvider>
|
||||
</BlueStorageProvider>
|
||||
</SafeAreaProvider>
|
||||
</NavigationProvider>
|
||||
</NavigationContainer>
|
||||
</LargeScreenProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
22
MasterView.tsx
Normal file
22
MasterView.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import MainRoot from './navigation';
|
||||
import { useStorage } from './blue_modules/storage-context';
|
||||
const CompanionDelegates = lazy(() => import('./components/CompanionDelegates'));
|
||||
|
||||
const MasterView = () => {
|
||||
const { walletsInitialized } = useStorage();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainRoot />
|
||||
{walletsInitialized && (
|
||||
<Suspense>
|
||||
<CompanionDelegates />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MasterView;
|
|
@ -79,7 +79,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "6.6.6"
|
||||
versionName "6.6.7"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
|
|
@ -7,50 +7,52 @@
|
|||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||
<uses-permission android:name="android.permission.ACTION_GET_CONTENT" />
|
||||
<uses-permission android:name="android.permission.ACTION_CREATE_DOCUMENT" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:allowBackup="false"
|
||||
android:largeHeap="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:allowBackup="false"
|
||||
android:largeHeap="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_name"
|
||||
android:value="BlueWallet notifications"/> <!-- YOUR NOTIFICATION CHANNEL NAME -->
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_description"
|
||||
android:value="Notifications about incoming payments"/> <!-- YOUR NOTIFICATION CHANNEL DESCRIPTION -->
|
||||
|
||||
<!-- Change the value to true to enable pop-up for in foreground (remote-only, for local use ignoreInForeground) -->
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground"
|
||||
android:value="true"/>
|
||||
<!-- Change the value to false if you don't want the creation of the default channel -->
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.channel_create_default"
|
||||
android:value="true"/>
|
||||
<!-- Change the resource name to your App's accent color - or any other color you want -->
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.notification_color"
|
||||
android:resource="@color/white"/> <!-- or @android:color/{name} to use a standard color -->
|
||||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.notification_channel_name"
|
||||
android:value="BlueWallet notifications" />
|
||||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.notification_channel_description"
|
||||
android:value="Notifications about incoming payments" />
|
||||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.notification_foreground"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.channel_create_default"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.notification_color"
|
||||
android:resource="@color/white" />
|
||||
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"
|
||||
<receiver
|
||||
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
@ -58,71 +60,65 @@
|
|||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
|
||||
android:exported="false" >
|
||||
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<activity android:name=".SplashActivity"
|
||||
android:exported="true"
|
||||
>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="bitcoin" />
|
||||
<data android:scheme="lightning" />
|
||||
<data android:scheme="bluewallet" />
|
||||
<data android:scheme="lapp" />
|
||||
<data android:scheme="blue" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="application/octet-stream"
|
||||
android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="text/plain"
|
||||
android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="bitcoin" />
|
||||
<data android:scheme="lightning" />
|
||||
<data android:scheme="bluewallet" />
|
||||
<data android:scheme="lapp" />
|
||||
<data android:scheme="blue" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="application/octet-stream"
|
||||
android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="text/plain"
|
||||
android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<meta-data android:name="com.bugsnag.android.API_KEY" android:value="17ba9059f676f1cc4f45d98182388b01" />
|
||||
<meta-data android:name="com.bugsnag.android.API_KEY" android:value="17ba9059f676f1cc4f45d98182388b01" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package io.bluewallet.bluewallet; // Replace with your package name
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class SplashActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.splash_screen); // Replace with your layout name
|
||||
|
||||
int SPLASH_DISPLAY_LENGTH = 1000; // Splash screen duration in milliseconds
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent mainIntent = new Intent(SplashActivity.this, MainActivity.class);
|
||||
SplashActivity.this.startActivity(mainIntent);
|
||||
SplashActivity.this.finish();
|
||||
}
|
||||
}, SPLASH_DISPLAY_LENGTH);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_color">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="75dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/splash_icon" />
|
||||
</RelativeLayout>
|
||||
|
|
@ -83,9 +83,3 @@ subprojects {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
subprojects { subproject ->
|
||||
if(project['name'] == 'react-native-widget-center') {
|
||||
project.configurations { compile { } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import Realm from 'realm';
|
|||
import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class';
|
||||
import presentAlert from '../components/Alert';
|
||||
import loc from '../loc';
|
||||
import { reloadAllTimelines } from '../components/WidgetCommunication';
|
||||
import RNFS from 'react-native-fs';
|
||||
|
||||
const ElectrumClient = require('electrum-client');
|
||||
|
@ -52,7 +51,7 @@ type ElectrumTransaction = {
|
|||
};
|
||||
}[];
|
||||
blockhash: string;
|
||||
confirmations?: number;
|
||||
confirmations: number;
|
||||
time: number;
|
||||
blocktime: number;
|
||||
};
|
||||
|
@ -212,8 +211,6 @@ export async function connectMain(): Promise<void> {
|
|||
await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp ?? '');
|
||||
await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl ?? '');
|
||||
}
|
||||
|
||||
reloadAllTimelines();
|
||||
} catch (e) {
|
||||
// Must be running on Android
|
||||
console.log(e);
|
||||
|
@ -340,7 +337,6 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
await DefaultPreference.clear(ELECTRUM_HOST);
|
||||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||
reloadAllTimelines();
|
||||
} catch (e) {
|
||||
// Must be running on Android
|
||||
console.log(e);
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
function WidgetCommunication() {
|
||||
return null;
|
||||
}
|
||||
|
||||
WidgetCommunication.isBalanceDisplayAllowed = () => {};
|
||||
WidgetCommunication.setBalanceDisplayAllowed = () => {};
|
||||
WidgetCommunication.reloadAllTimelines = () => {};
|
||||
|
||||
export default WidgetCommunication;
|
|
@ -3,7 +3,6 @@ import DefaultPreference from 'react-native-default-preference';
|
|||
import * as RNLocalize from 'react-native-localize';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { FiatUnit, FiatUnitType, getFiatRate } from '../models/fiatUnit';
|
||||
import { reloadAllTimelines } from '../components/WidgetCommunication';
|
||||
|
||||
const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency';
|
||||
const PREFERRED_CURRENCY_LOCALE_STORAGE_KEY = 'preferredCurrencyLocale';
|
||||
|
@ -32,8 +31,6 @@ async function setPreferredCurrency(item: FiatUnitType): Promise<void> {
|
|||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, item.endPointKey);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, item.locale.replace('-', '_'));
|
||||
// @ts-ignore: Convert to TSX later
|
||||
reloadAllTimelines();
|
||||
}
|
||||
|
||||
async function getPreferredCurrency(): Promise<FiatUnitType> {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { isTablet, getDeviceType } from 'react-native-device-info';
|
||||
import { isTablet as checkIsTablet, getDeviceType } from 'react-native-device-info';
|
||||
|
||||
const isTablet: boolean = checkIsTablet();
|
||||
const isDesktop: boolean = getDeviceType() === 'Desktop';
|
||||
const isHandset: boolean = getDeviceType() === 'Handset';
|
||||
|
||||
export const isHandset: boolean = getDeviceType() === 'Handset';
|
||||
export { isDesktop, isTablet };
|
||||
export { isDesktop, isTablet, isHandset };
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Platform } from 'react-native';
|
||||
import Biometric from '../class/biometrics';
|
||||
import prompt from '../helpers/prompt';
|
||||
import loc from '../loc';
|
||||
import { BlueApp as BlueAppClass } from '../class/';
|
||||
import { showKeychainWipeAlert } from '../hooks/useBiometrics';
|
||||
|
||||
const BlueApp = BlueAppClass.getInstance();
|
||||
// If attempt reaches 10, a wipe keychain option will be provided to the user.
|
||||
|
@ -55,7 +55,7 @@ export const startAndDecrypt = async (retry?: boolean): Promise<boolean> => {
|
|||
return startAndDecrypt(true);
|
||||
} else {
|
||||
unlockAttempt = 0;
|
||||
Biometric.showKeychainWipeAlert();
|
||||
showKeychainWipeAlert();
|
||||
// We want to return false to let the UnlockWith screen that it is NOT ok to proceed.
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
import { useContext } from 'react';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics';
|
||||
import PasscodeAuth from 'react-native-passcode-auth';
|
||||
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
|
||||
import loc from '../loc';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import presentAlert from '../components/Alert';
|
||||
|
||||
const STORAGEKEY = 'Biometrics';
|
||||
|
||||
const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
|
||||
|
||||
// Define a function type with properties
|
||||
type DescribableFunction = {
|
||||
(): null; // Call signature
|
||||
FaceID: 'Face ID';
|
||||
TouchID: 'Touch ID';
|
||||
Biometrics: 'Biometrics';
|
||||
isBiometricUseCapableAndEnabled: () => Promise<boolean>;
|
||||
isDeviceBiometricCapable: () => Promise<boolean>;
|
||||
setBiometricUseEnabled: (arg: boolean) => Promise<void>;
|
||||
biometricType: () => Promise<keyof typeof RNBiometryTypes | undefined>;
|
||||
isBiometricUseEnabled: () => Promise<boolean>;
|
||||
unlockWithBiometrics: () => Promise<boolean>;
|
||||
showKeychainWipeAlert: () => void;
|
||||
};
|
||||
|
||||
// Bastard component/module. All properties are added in runtime
|
||||
const Biometric = function () {
|
||||
const { getItem, setItem } = useContext(BlueStorageContext);
|
||||
Biometric.FaceID = 'Face ID';
|
||||
Biometric.TouchID = 'Touch ID';
|
||||
Biometric.Biometrics = 'Biometrics';
|
||||
|
||||
Biometric.isDeviceBiometricCapable = async () => {
|
||||
try {
|
||||
const { available } = await rnBiometrics.isSensorAvailable();
|
||||
return available;
|
||||
} catch (e) {
|
||||
console.log('Biometrics isDeviceBiometricCapable failed');
|
||||
console.log(e);
|
||||
Biometric.setBiometricUseEnabled(false);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Biometric.biometricType = async () => {
|
||||
try {
|
||||
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
|
||||
if (!available) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return biometryType;
|
||||
} catch (e) {
|
||||
console.log('Biometrics biometricType failed');
|
||||
console.log(e);
|
||||
return undefined; // Explicitly return false in case of an error
|
||||
}
|
||||
};
|
||||
|
||||
Biometric.isBiometricUseEnabled = async () => {
|
||||
try {
|
||||
const enabledBiometrics = await getItem(STORAGEKEY);
|
||||
return !!enabledBiometrics;
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Biometric.isBiometricUseCapableAndEnabled = async () => {
|
||||
const isBiometricUseEnabled = await Biometric.isBiometricUseEnabled();
|
||||
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
|
||||
return isBiometricUseEnabled && isDeviceBiometricCapable;
|
||||
};
|
||||
|
||||
Biometric.setBiometricUseEnabled = async value => {
|
||||
await setItem(STORAGEKEY, value === true ? '1' : '');
|
||||
};
|
||||
|
||||
Biometric.unlockWithBiometrics = async () => {
|
||||
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
|
||||
if (isDeviceBiometricCapable) {
|
||||
return new Promise(resolve => {
|
||||
rnBiometrics
|
||||
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
|
||||
.then((result: { success: any }) => {
|
||||
if (result.success) {
|
||||
resolve(true);
|
||||
} else {
|
||||
console.log('Biometrics authentication failed');
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.log('Biometrics authentication error');
|
||||
presentAlert({ message: error.message });
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const clearKeychain = async () => {
|
||||
try {
|
||||
console.log('Wiping keychain');
|
||||
console.log('Wiping key: data');
|
||||
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
|
||||
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
||||
});
|
||||
console.log('Wiped key: data');
|
||||
console.log('Wiping key: data_encrypted');
|
||||
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
||||
console.log('Wiped key: data_encrypted');
|
||||
console.log('Wiping key: STORAGEKEY');
|
||||
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
||||
console.log('Wiped key: STORAGEKEY');
|
||||
NavigationService.reset();
|
||||
} catch (error: any) {
|
||||
console.warn(error);
|
||||
presentAlert({ message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
const requestDevicePasscode = async () => {
|
||||
let isDevicePasscodeSupported: boolean | undefined = false;
|
||||
try {
|
||||
isDevicePasscodeSupported = await PasscodeAuth.isSupported();
|
||||
if (isDevicePasscodeSupported) {
|
||||
const isAuthenticated = await PasscodeAuth.authenticate();
|
||||
if (isAuthenticated) {
|
||||
Alert.alert(
|
||||
loc.settings.encrypt_tstorage,
|
||||
loc.settings.biom_remove_decrypt,
|
||||
[
|
||||
{ text: loc._.cancel, style: 'cancel' },
|
||||
{
|
||||
text: loc._.ok,
|
||||
style: 'destructive',
|
||||
onPress: async () => await clearKeychain(),
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
isDevicePasscodeSupported = undefined;
|
||||
}
|
||||
if (isDevicePasscodeSupported === false) {
|
||||
presentAlert({ message: loc.settings.biom_no_passcode });
|
||||
}
|
||||
};
|
||||
|
||||
Biometric.showKeychainWipeAlert = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Alert.alert(
|
||||
loc.settings.encrypt_tstorage,
|
||||
loc.settings.biom_10times,
|
||||
[
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {
|
||||
console.log('Cancel Pressed');
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => requestDevicePasscode(),
|
||||
style: 'default',
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return null;
|
||||
} as DescribableFunction;
|
||||
|
||||
export default Biometric;
|
||||
export { RNBiometryTypes as BiometricType };
|
23
class/contact-list.ts
Normal file
23
class/contact-list.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import BIP47Factory from '@spsina/bip47';
|
||||
import { TWallet } from './wallets/types';
|
||||
import ecc from '../blue_modules/noble_ecc';
|
||||
|
||||
export class ContactList {
|
||||
private _wallet: TWallet;
|
||||
|
||||
constructor(wallet: TWallet) {
|
||||
if (!wallet.allowBIP47()) throw new Error('BIP47 is not allowed for the wallet');
|
||||
if (!wallet.isBIP47Enabled()) throw new Error('BIP47 is not enabled');
|
||||
|
||||
this._wallet = wallet;
|
||||
}
|
||||
|
||||
isPaymentCodeValid(pc: string): boolean {
|
||||
try {
|
||||
BIP47Factory(ecc).fromPaymentCode(pc);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1542,22 +1542,21 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
}
|
||||
|
||||
/**
|
||||
* this method goes over all our txs and checks if we sent a notification tx in the past to the given PC
|
||||
* find and return _existing_ notification transaction for the given payment code
|
||||
* (i.e. if it exists - we notified in the past and dont need to notify again)
|
||||
*/
|
||||
needToNotifyBIP47(receiverPaymentCode: string): boolean {
|
||||
getBIP47NotificationTransaction(receiverPaymentCode: string): Transaction | undefined {
|
||||
const publicBip47 = BIP47Factory(ecc).fromPaymentCode(receiverPaymentCode);
|
||||
const remoteNotificationAddress = publicBip47.getNotificationAddress();
|
||||
|
||||
for (const tx of this.getTransactions()) {
|
||||
for (const output of tx.outputs) {
|
||||
if (output.scriptPubKey?.addresses?.includes(remoteNotificationAddress)) return false;
|
||||
if (output.scriptPubKey?.addresses?.includes(remoteNotificationAddress)) return tx;
|
||||
// ^^^ if in the past we sent a tx to his notification address - most likely that was a proper notification
|
||||
// transaction with OP_RETURN.
|
||||
// but not gona verify it here, will just trust it
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1694,6 +1693,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
return bip47Local.getNotificationAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* check our notification address, and decypher all payment codes people notified us
|
||||
* about (so they can pay us)
|
||||
*/
|
||||
async fetchBIP47SenderPaymentCodes(): Promise<void> {
|
||||
const bip47_instance = this.getBIP47FromSeed();
|
||||
const address = bip47_instance.getNotificationAddress();
|
||||
|
@ -1748,10 +1751,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* payment codes of people who can pay us
|
||||
*/
|
||||
getBIP47SenderPaymentCodes(): string[] {
|
||||
return this._receive_payment_codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* payment codes of people whom we can pay
|
||||
*/
|
||||
getBIP47ReceiverPaymentCodes(): string[] {
|
||||
return this._send_payment_codes;
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ export type Transaction = {
|
|||
inputs: TransactionInput[];
|
||||
outputs: TransactionOutput[];
|
||||
blockhash: string;
|
||||
confirmations?: number;
|
||||
confirmations: number;
|
||||
time: number;
|
||||
blocktime: number;
|
||||
received?: number;
|
||||
|
|
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;
|
41
components/Context/LargeScreenProvider.tsx
Normal file
41
components/Context/LargeScreenProvider.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React, { createContext, useState, useEffect, useMemo, ReactNode } from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import { isDesktop, isTablet } from '../../blue_modules/environment';
|
||||
|
||||
interface ILargeScreenContext {
|
||||
isLargeScreen: boolean;
|
||||
}
|
||||
|
||||
export const LargeScreenContext = createContext<ILargeScreenContext | undefined>(undefined);
|
||||
|
||||
interface LargeScreenProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const LargeScreenProvider: React.FC<LargeScreenProviderProps> = ({ children }) => {
|
||||
const [windowWidth, setWindowWidth] = useState<number>(Dimensions.get('window').width);
|
||||
const screenWidth: number = useMemo(() => Dimensions.get('screen').width, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updateScreenUsage = (): void => {
|
||||
const newWindowWidth = Dimensions.get('window').width;
|
||||
if (newWindowWidth !== windowWidth) {
|
||||
setWindowWidth(newWindowWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const subscription = Dimensions.addEventListener('change', updateScreenUsage);
|
||||
return () => subscription.remove();
|
||||
}, [windowWidth]);
|
||||
|
||||
const isLargeScreen: boolean = useMemo(() => {
|
||||
const halfScreenWidth = windowWidth >= screenWidth / 2;
|
||||
const condition = (isTablet && halfScreenWidth) || isDesktop;
|
||||
console.debug(
|
||||
`LargeScreenProvider.isLargeScreen: width: ${windowWidth}, Screen width: ${screenWidth}, Is tablet: ${isTablet}, Is large screen: ${condition}, isDesktkop: ${isDesktop}`,
|
||||
);
|
||||
return condition;
|
||||
}, [windowWidth, screenWidth]);
|
||||
|
||||
return <LargeScreenContext.Provider value={{ isLargeScreen }}>{children}</LargeScreenContext.Provider>;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import { useCallback, useContext, useEffect } from 'react';
|
||||
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
|
@ -65,7 +65,7 @@ const MenuElements = () => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized]);
|
||||
|
||||
return <></>;
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MenuElements;
|
||||
|
|
|
@ -7,6 +7,7 @@ import loc from '../loc';
|
|||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useTheme } from './themes';
|
||||
import { ActionIcons } from '../typings/ActionIcons';
|
||||
import { Action } from './types';
|
||||
|
||||
interface QRCodeComponentProps {
|
||||
value: string;
|
||||
|
@ -18,22 +19,6 @@ interface QRCodeComponentProps {
|
|||
onError?: () => void;
|
||||
}
|
||||
|
||||
interface ActionType {
|
||||
Share: 'share';
|
||||
Copy: 'copy';
|
||||
}
|
||||
|
||||
interface Action {
|
||||
id: string;
|
||||
text: string;
|
||||
icon: ActionIcons;
|
||||
}
|
||||
|
||||
const actionKeys: ActionType = {
|
||||
Share: 'share',
|
||||
Copy: 'copy',
|
||||
};
|
||||
|
||||
const actionIcons: { [key: string]: ActionIcons } = {
|
||||
Share: {
|
||||
iconType: 'SYSTEM',
|
||||
|
@ -45,6 +30,22 @@ const actionIcons: { [key: string]: ActionIcons } = {
|
|||
},
|
||||
};
|
||||
|
||||
const actionKeys = {
|
||||
Share: 'share',
|
||||
Copy: 'copy',
|
||||
};
|
||||
|
||||
const menuActions: Action[] =
|
||||
Platform.OS === 'ios' || Platform.OS === 'macos'
|
||||
? [
|
||||
{
|
||||
id: actionKeys.Copy,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: actionIcons.Copy,
|
||||
},
|
||||
]
|
||||
: [{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }];
|
||||
|
||||
const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
||||
value = '',
|
||||
isLogoRendered = true,
|
||||
|
@ -75,23 +76,6 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const menuActions = (): Action[] => {
|
||||
const actions: Action[] = [];
|
||||
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
|
||||
actions.push({
|
||||
id: actionKeys.Copy,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: actionIcons.Copy,
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
id: actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: actionIcons.Share,
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
|
||||
const renderQRCode = (
|
||||
<QRCode
|
||||
value={value}
|
||||
|
@ -115,7 +99,7 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
|||
accessibilityLabel={loc.receive.qrcode_for_the_address}
|
||||
>
|
||||
{isMenuAvailable ? (
|
||||
<ToolTipMenu actions={menuActions()} onPressMenuItem={onPressMenuItem}>
|
||||
<ToolTipMenu actions={menuActions} onPressMenuItem={onPressMenuItem}>
|
||||
{renderQRCode}
|
||||
</ToolTipMenu>
|
||||
) : (
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import loc from '../loc';
|
||||
import { ActionIcons } from '../typings/ActionIcons';
|
||||
import * as fs from '../blue_modules/fs';
|
||||
import { Action } from './types';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
|
||||
interface SaveFileButtonProps {
|
||||
fileName: string;
|
||||
|
@ -11,7 +12,7 @@ interface SaveFileButtonProps {
|
|||
children?: ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
afterOnPress?: () => void;
|
||||
beforeOnPress?: () => Promise<void>; // Changed this line
|
||||
beforeOnPress?: () => Promise<void>;
|
||||
onMenuWillHide?: () => void;
|
||||
onMenuWillShow?: () => void;
|
||||
}
|
||||
|
@ -26,30 +27,24 @@ const SaveFileButton: React.FC<SaveFileButtonProps> = ({
|
|||
onMenuWillHide,
|
||||
onMenuWillShow,
|
||||
}) => {
|
||||
const actions = [
|
||||
{ id: 'save', text: loc._.save, icon: actionIcons.Save },
|
||||
{ id: 'share', text: loc.receive.details_share, icon: actionIcons.Share },
|
||||
];
|
||||
|
||||
const handlePressMenuItem = async (actionId: string) => {
|
||||
if (beforeOnPress) {
|
||||
await beforeOnPress(); // Now properly awaiting a function that returns a promise
|
||||
await beforeOnPress();
|
||||
}
|
||||
const action = actions.find(a => a.id === actionId);
|
||||
|
||||
if (action?.id === 'save') {
|
||||
await fs.writeFileAndExport(fileName, fileContent, false).finally(() => {
|
||||
afterOnPress?.(); // Safely call afterOnPress if it exists
|
||||
afterOnPress?.();
|
||||
});
|
||||
} else if (action?.id === 'share') {
|
||||
await fs.writeFileAndExport(fileName, fileContent, true).finally(() => {
|
||||
afterOnPress?.(); // Safely call afterOnPress if it exists
|
||||
afterOnPress?.();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// @ts-ignore: Tooltip must be refactored to use TSX}
|
||||
<ToolTipMenu
|
||||
onMenuWillHide={onMenuWillHide}
|
||||
onMenuWillShow={onMenuWillShow}
|
||||
|
@ -57,7 +52,7 @@ const SaveFileButton: React.FC<SaveFileButtonProps> = ({
|
|||
isMenuPrimaryAction
|
||||
actions={actions}
|
||||
onPressMenuItem={handlePressMenuItem}
|
||||
buttonStyle={style}
|
||||
buttonStyle={style as ViewStyle} // Type assertion to match ViewStyle
|
||||
>
|
||||
{children}
|
||||
</ToolTipMenu>
|
||||
|
@ -76,3 +71,7 @@ const actionIcons: { [key: string]: ActionIcons } = {
|
|||
iconValue: 'square.and.arrow.down',
|
||||
},
|
||||
};
|
||||
const actions: Action[] = [
|
||||
{ id: 'save', text: loc._.save, icon: actionIcons.Save },
|
||||
{ id: 'share', text: loc.receive.details_share, icon: actionIcons.Share },
|
||||
];
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import React, { useRef, useEffect, forwardRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Pressable } from 'react-native';
|
||||
import showPopupMenu from '../blue_modules/showPopupMenu';
|
||||
|
||||
const BaseToolTipMenu = (props, ref) => {
|
||||
const menuRef = useRef();
|
||||
const disabled = props.disabled ?? false;
|
||||
const isMenuPrimaryAction = props.isMenuPrimaryAction ?? false;
|
||||
const enableAndroidRipple = props.enableAndroidRipple ?? true;
|
||||
const buttonStyle = props.buttonStyle ?? {};
|
||||
const handleToolTipSelection = selection => {
|
||||
props.onPressMenuItem(selection.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (ref && ref.current) {
|
||||
ref.current.dismissMenu = dismissMenu;
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
const dismissMenu = () => {
|
||||
console.log('dismissMenu Not implemented');
|
||||
};
|
||||
|
||||
const showMenu = () => {
|
||||
const menu = [];
|
||||
for (const actions of props.actions) {
|
||||
if (Array.isArray(actions)) {
|
||||
for (const actionToMap of actions) {
|
||||
menu.push({ id: actionToMap.id, label: actionToMap.text });
|
||||
}
|
||||
} else {
|
||||
menu.push({ id: actions.id, label: actions.text });
|
||||
}
|
||||
}
|
||||
|
||||
showPopupMenu(menu, handleToolTipSelection, menuRef.current);
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
{...(enableAndroidRipple ? { android_ripple: { color: 'lightgrey' } } : {})}
|
||||
ref={menuRef}
|
||||
disabled={disabled}
|
||||
style={buttonStyle}
|
||||
{...(isMenuPrimaryAction ? { onPress: showMenu } : { onPress: props.onPress, onLongPress: showMenu })}
|
||||
>
|
||||
{props.children}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
||||
ToolTipMenu.propTypes = {
|
||||
actions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
|
||||
children: PropTypes.node,
|
||||
onPressMenuItem: PropTypes.func.isRequired,
|
||||
isMenuPrimaryAction: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
76
components/TooltipMenu.android.tsx
Normal file
76
components/TooltipMenu.android.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import React, { useRef, useEffect, forwardRef, Ref, useMemo, useCallback } from 'react';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import showPopupMenu, { OnPopupMenuItemSelect, PopupMenuItem } from '../blue_modules/showPopupMenu.android';
|
||||
import { ToolTipMenuProps } from './types';
|
||||
|
||||
const dismissMenu = () => {
|
||||
console.log('dismissMenu Not implemented');
|
||||
};
|
||||
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => void }>) => {
|
||||
const menuRef = useRef<View>(null);
|
||||
const {
|
||||
actions,
|
||||
children,
|
||||
onPressMenuItem,
|
||||
isMenuPrimaryAction = false,
|
||||
buttonStyle = {},
|
||||
enableAndroidRipple = true,
|
||||
disabled = false,
|
||||
onPress,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const handleToolTipSelection = useCallback<OnPopupMenuItemSelect>(
|
||||
(selection: PopupMenuItem) => {
|
||||
if (selection.id) {
|
||||
onPressMenuItem(selection.id);
|
||||
}
|
||||
},
|
||||
[onPressMenuItem],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore: fix later
|
||||
if (ref && ref.current) {
|
||||
// @ts-ignore: fix later
|
||||
ref.current.dismissMenu = dismissMenu;
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
const menuItems = useMemo(() => {
|
||||
const menu: { id: string; label: string }[] = [];
|
||||
actions.forEach(action => {
|
||||
if (Array.isArray(action)) {
|
||||
action.forEach(actionToMap => {
|
||||
menu.push({ id: actionToMap.id.toString(), label: actionToMap.text });
|
||||
});
|
||||
} else {
|
||||
menu.push({ id: action.id.toString(), label: action.text });
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
}, [actions]);
|
||||
|
||||
const showMenu = useCallback(() => {
|
||||
if (menuRef.current) {
|
||||
showPopupMenu(menuItems, handleToolTipSelection, menuRef.current);
|
||||
}
|
||||
}, [menuItems, handleToolTipSelection]);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
{...(enableAndroidRipple ? { android_ripple: { color: 'lightgrey' } } : {})}
|
||||
ref={menuRef}
|
||||
disabled={disabled}
|
||||
style={buttonStyle}
|
||||
{...(isMenuPrimaryAction ? { onPress: showMenu } : { onPress, onLongPress: showMenu })}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
|
@ -1,140 +0,0 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
|
||||
const BaseToolTipMenu = (props, ref) => {
|
||||
const menuItemMapped = ({ action, menuOptions }) => {
|
||||
const item = {
|
||||
actionKey: action.id,
|
||||
actionTitle: action.text,
|
||||
icon: action.icon,
|
||||
menuOptions,
|
||||
menuTitle: action.menuTitle,
|
||||
};
|
||||
item.menuState = action.menuStateOn ? 'on' : 'off';
|
||||
|
||||
if (action.disabled) {
|
||||
item.menuAttributes = ['disabled'];
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
const menuItems = props.actions.map(action => {
|
||||
if (Array.isArray(action)) {
|
||||
const mapped = [];
|
||||
for (const actionToMap of action) {
|
||||
mapped.push(menuItemMapped({ action: actionToMap }));
|
||||
}
|
||||
const submenu = {
|
||||
menuOptions: ['displayInline'],
|
||||
menuItems: mapped,
|
||||
menuTitle: '',
|
||||
};
|
||||
return submenu;
|
||||
} else {
|
||||
return menuItemMapped({ action });
|
||||
}
|
||||
});
|
||||
const menuTitle = props.title ?? '';
|
||||
const isButton = !!props.isButton;
|
||||
const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false;
|
||||
const renderPreview = props.renderPreview ?? undefined;
|
||||
const disabled = props.disabled ?? false;
|
||||
const onPress = props.onPress ?? undefined;
|
||||
const onMenuWillShow = props.onMenuWillShow ?? undefined;
|
||||
const onMenuWillHide = props.onMenuWillHide ?? undefined;
|
||||
|
||||
const buttonStyle = props.buttonStyle;
|
||||
return isButton ? (
|
||||
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" style={buttonStyle}>
|
||||
<ContextMenuButton
|
||||
ref={ref}
|
||||
onMenuWillShow={onMenuWillShow}
|
||||
onMenuWillHide={onMenuWillHide}
|
||||
useActionSheetFallback={false}
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
isMenuPrimaryAction={isMenuPrimaryAction}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenuButton>
|
||||
</TouchableOpacity>
|
||||
) : props.onPress ? (
|
||||
<ContextMenuView
|
||||
ref={ref}
|
||||
lazyPreview
|
||||
shouldEnableAggressiveCleanup
|
||||
shouldCleanupOnComponentWillUnmountForMenuPreview
|
||||
internalCleanupMode="automatic"
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
useActionSheetFallback={false}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
{...(renderPreview
|
||||
? {
|
||||
previewConfig: {
|
||||
previewType: 'CUSTOM',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
renderPreview,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
|
||||
{props.children}
|
||||
</TouchableOpacity>
|
||||
</ContextMenuView>
|
||||
) : (
|
||||
<ContextMenuView
|
||||
ref={ref}
|
||||
internalCleanupMode="viewController"
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
lazyPreview
|
||||
shouldEnableAggressiveCleanup
|
||||
useActionSheetFallback={false}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
{...(renderPreview
|
||||
? {
|
||||
previewConfig: {
|
||||
previewType: 'CUSTOM',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
renderPreview,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
||||
ToolTipMenu.propTypes = {
|
||||
actions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
onPressMenuItem: PropTypes.func.isRequired,
|
||||
isMenuPrimaryAction: PropTypes.bool,
|
||||
isButton: PropTypes.bool,
|
||||
renderPreview: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
previewValue: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
122
components/TooltipMenu.ios.tsx
Normal file
122
components/TooltipMenu.ios.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
import React, { forwardRef, Ref, useMemo, useCallback } from 'react';
|
||||
import { ContextMenuView, ContextMenuButton, RenderItem } from 'react-native-ios-context-menu';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { ToolTipMenuProps, Action } from './types';
|
||||
|
||||
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
|
||||
const {
|
||||
title = '',
|
||||
isButton = false,
|
||||
isMenuPrimaryAction = false,
|
||||
renderPreview,
|
||||
disabled = false,
|
||||
onPress,
|
||||
onMenuWillShow,
|
||||
onMenuWillHide,
|
||||
buttonStyle,
|
||||
onPressMenuItem,
|
||||
} = props;
|
||||
|
||||
const menuItemMapped = useCallback(({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => {
|
||||
const item: any = {
|
||||
actionKey: action.id.toString(),
|
||||
actionTitle: action.text,
|
||||
icon: action.icon,
|
||||
menuOptions,
|
||||
menuTitle: action.menuTitle,
|
||||
};
|
||||
item.menuState = action.menuStateOn ? 'on' : 'off';
|
||||
|
||||
if (action.disabled) {
|
||||
item.menuAttributes = ['disabled'];
|
||||
}
|
||||
return item;
|
||||
}, []);
|
||||
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
props.actions.map(action => {
|
||||
if (Array.isArray(action)) {
|
||||
const mapped = action.map(actionToMap => menuItemMapped({ action: actionToMap }));
|
||||
return {
|
||||
menuOptions: ['displayInline'],
|
||||
menuItems: mapped,
|
||||
menuTitle: '',
|
||||
};
|
||||
} else {
|
||||
return menuItemMapped({ action });
|
||||
}
|
||||
}),
|
||||
[props.actions, menuItemMapped],
|
||||
);
|
||||
|
||||
const handlePressMenuItem = useCallback(
|
||||
({ nativeEvent }: { nativeEvent: { actionKey: string } }) => {
|
||||
onPressMenuItem(nativeEvent.actionKey);
|
||||
},
|
||||
[onPressMenuItem],
|
||||
);
|
||||
|
||||
const renderContextMenuButton = () => (
|
||||
<ContextMenuButton
|
||||
ref={ref}
|
||||
onMenuWillShow={onMenuWillShow}
|
||||
onMenuWillHide={onMenuWillHide}
|
||||
useActionSheetFallback={false}
|
||||
onPressMenuItem={handlePressMenuItem}
|
||||
isMenuPrimaryAction={isMenuPrimaryAction}
|
||||
menuConfig={{
|
||||
menuTitle: title,
|
||||
menuItems,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenuButton>
|
||||
);
|
||||
|
||||
const renderContextMenuView = () => (
|
||||
<ContextMenuView
|
||||
ref={ref}
|
||||
lazyPreview
|
||||
shouldEnableAggressiveCleanup
|
||||
internalCleanupMode="automatic"
|
||||
onPressMenuItem={handlePressMenuItem}
|
||||
useActionSheetFallback={false}
|
||||
menuConfig={{
|
||||
menuTitle: title,
|
||||
menuItems,
|
||||
}}
|
||||
{...(renderPreview
|
||||
? {
|
||||
previewConfig: {
|
||||
previewType: 'CUSTOM',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
renderPreview: renderPreview as RenderItem,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{onPress ? (
|
||||
<TouchableOpacity accessibilityRole="button" onPress={onPress}>
|
||||
{props.children}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</ContextMenuView>
|
||||
);
|
||||
|
||||
return isMenuPrimaryAction && onPress ? (
|
||||
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" style={buttonStyle}>
|
||||
{renderContextMenuButton()}
|
||||
</TouchableOpacity>
|
||||
) : isButton ? (
|
||||
renderContextMenuButton()
|
||||
) : (
|
||||
renderContextMenuView()
|
||||
);
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
|
@ -1,9 +0,0 @@
|
|||
import { forwardRef } from 'react';
|
||||
|
||||
const BaseToolTipMenu = (props, _ref) => {
|
||||
return props.children;
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
11
components/TooltipMenu.tsx
Normal file
11
components/TooltipMenu.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { forwardRef, Ref } from 'react';
|
||||
import { ToolTipMenuProps } from './types';
|
||||
|
||||
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
|
||||
console.debug('ToolTipMenu.tsx ref:', ref);
|
||||
return props.children;
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
|
@ -20,6 +20,7 @@ import { useTheme } from './themes';
|
|||
import ListItem from './ListItem';
|
||||
import { useSettings } from './Context/SettingsContext';
|
||||
import { LightningTransaction, Transaction } from '../class/wallets/types';
|
||||
import { Action } from './types';
|
||||
|
||||
interface TransactionListItemProps {
|
||||
itemPriceUnit: BitcoinUnit;
|
||||
|
@ -287,9 +288,9 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
handleOnViewOnBlockExplorer,
|
||||
],
|
||||
);
|
||||
const toolTipActions = useMemo((): Action[] | Action[][] => {
|
||||
const actions: (Action | Action[])[] = [];
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const actions = [];
|
||||
if (rowTitle !== loc.lnd.expired) {
|
||||
actions.push({
|
||||
id: actionKeys.CopyAmount,
|
||||
|
@ -305,6 +306,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
icon: actionIcons.Clipboard,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.hash) {
|
||||
actions.push(
|
||||
{
|
||||
|
@ -337,10 +339,9 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
]);
|
||||
}
|
||||
|
||||
return actions;
|
||||
return actions as Action[] | Action[][];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ToolTipMenu ref={menuRef} actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
|
||||
|
|
|
@ -154,7 +154,7 @@ const iStyles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelectedWallet, customStyle }) => {
|
||||
export const WalletCarouselItem = React.memo(({ item, _, onPress, handleLongPress, isSelectedWallet, customStyle }) => {
|
||||
const scaleValue = new Animated.Value(1.0);
|
||||
const { colors } = useTheme();
|
||||
const { walletTransactionUpdateStatus } = useContext(BlueStorageContext);
|
||||
|
@ -251,7 +251,7 @@ export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelect
|
|||
</Pressable>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
WalletCarouselItem.propTypes = {
|
||||
item: PropTypes.any,
|
||||
|
@ -292,7 +292,7 @@ const WalletsCarousel = forwardRef((props, ref) => {
|
|||
<NewWalletPanel onPress={onPress} />
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[horizontal, selectedWallet, handleLongPress, onPress, preferredFiatCurrency, language],
|
||||
[horizontal, selectedWallet, preferredFiatCurrency, language],
|
||||
);
|
||||
const flatListRef = useRef();
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ import {
|
|||
transferCurrentComplicationUserInfo,
|
||||
transferUserInfo,
|
||||
} from 'react-native-watch-connectivity';
|
||||
import { Chain } from './models/bitcoinUnits';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from './loc';
|
||||
import { BlueStorageContext } from './blue_modules/storage-context';
|
||||
import Notifications from './blue_modules/notifications';
|
||||
import { FiatUnit } from './models/fiatUnit';
|
||||
import { MultisigHDWallet } from './class';
|
||||
import { useSettings } from './components/Context/SettingsContext';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
import { FiatUnit } from '../models/fiatUnit';
|
||||
import { MultisigHDWallet } from '../class';
|
||||
import { useSettings } from './Context/SettingsContext';
|
||||
|
||||
function WatchConnectivity() {
|
||||
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useContext(BlueStorageContext);
|
|
@ -1,7 +1,5 @@
|
|||
import React, { useContext, useEffect } from 'react';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
// @ts-ignore: no type definitions
|
||||
import RNWidgetCenter from 'react-native-widget-center';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import { TWallet } from '../class/wallets/types';
|
||||
import { useSettings } from './Context/SettingsContext';
|
||||
|
@ -13,10 +11,6 @@ enum WidgetCommunicationKeys {
|
|||
LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed',
|
||||
}
|
||||
|
||||
export const reloadAllTimelines = (): void => {
|
||||
RNWidgetCenter.reloadAllTimelines();
|
||||
};
|
||||
|
||||
export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
||||
try {
|
||||
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
|
||||
|
@ -34,7 +28,6 @@ export const setBalanceDisplayAllowed = async (value: boolean): Promise<void> =>
|
|||
} else {
|
||||
await DefaultPreference.clear(WidgetCommunicationKeys.DisplayBalanceAllowed);
|
||||
}
|
||||
reloadAllTimelines();
|
||||
};
|
||||
|
||||
export const syncWidgetBalanceWithWallets = async (wallets: TWallet[], walletsInitialized: boolean): Promise<void> => {
|
||||
|
@ -42,7 +35,6 @@ export const syncWidgetBalanceWithWallets = async (wallets: TWallet[], walletsIn
|
|||
const { allWalletsBalance, latestTransactionTime } = await allWalletsBalanceAndTransactionTime(wallets, walletsInitialized);
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, String(allWalletsBalance));
|
||||
await DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, String(latestTransactionTime));
|
||||
reloadAllTimelines();
|
||||
};
|
||||
|
||||
const allWalletsBalanceAndTransactionTime = async (
|
||||
|
|
|
@ -7,8 +7,6 @@ export const isBalanceDisplayAllowed = async (): Promise<boolean> => {
|
|||
|
||||
export const setBalanceDisplayAllowed = async (value: boolean): Promise<void> => {};
|
||||
|
||||
export const reloadAllTimelines = (): void => {};
|
||||
|
||||
export const syncWidgetBalanceWithWallets = async (_wallets: TWallet[], _walletsInitialized: boolean): Promise<void> => {};
|
||||
|
||||
const WidgetCommunication: React.FC = () => {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useContext, useRef } from 'react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import PropTypes from 'prop-types';
|
||||
import { AddressTypeBadge } from './AddressTypeBadge';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import TooltipMenu from '../TooltipMenu';
|
||||
|
@ -10,12 +9,13 @@ import Clipboard from '@react-native-clipboard/clipboard';
|
|||
import Share from 'react-native-share';
|
||||
import { useTheme } from '../themes';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../Alert';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import QRCodeComponent from '../QRCodeComponent';
|
||||
import confirm from '../../helpers/confirm';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
import { Action } from '../types';
|
||||
|
||||
interface AddressItemProps {
|
||||
// todo: fix `any` after addresses.js is converted to the church of holy typescript
|
||||
|
@ -26,8 +26,9 @@ interface AddressItemProps {
|
|||
}
|
||||
|
||||
const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: AddressItemProps) => {
|
||||
const { wallets } = useContext(BlueStorageContext);
|
||||
const { wallets } = useStorage();
|
||||
const { colors } = useTheme();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
|
||||
const hasTransactions = item.transactions > 0;
|
||||
|
||||
|
@ -79,6 +80,8 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
});
|
||||
};
|
||||
|
||||
const menuActions = useMemo(() => getAvailableActions({ allowSignVerifyMessage }), [allowSignVerifyMessage]);
|
||||
|
||||
const balance = formatBalance(item.balance, balanceUnit, true);
|
||||
|
||||
const handleCopyPress = () => {
|
||||
|
@ -118,8 +121,8 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
navigateToSignVerify();
|
||||
} else if (id === AddressItem.actionKeys.ExportPrivateKey) {
|
||||
if (await confirm(loc.addresses.sensitive_private_key)) {
|
||||
if (await Biometric.isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -129,39 +132,6 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
}
|
||||
};
|
||||
|
||||
const getAvailableActions = () => {
|
||||
const actions = [
|
||||
{
|
||||
id: AddressItem.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: AddressItem.actionIcons.Clipboard,
|
||||
},
|
||||
{
|
||||
id: AddressItem.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: AddressItem.actionIcons.Share,
|
||||
},
|
||||
];
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: AddressItem.actionKeys.SignVerify,
|
||||
text: loc.addresses.sign_title,
|
||||
icon: AddressItem.actionIcons.Signature,
|
||||
});
|
||||
}
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: AddressItem.actionKeys.ExportPrivateKey,
|
||||
text: loc.addresses.copy_private_key,
|
||||
icon: AddressItem.actionIcons.ExportPrivateKey,
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
const renderPreview = () => {
|
||||
return <QRCodeComponent value={item.address} isMenuAvailable={false} />;
|
||||
};
|
||||
|
@ -171,7 +141,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
<TooltipMenu
|
||||
title={item.address}
|
||||
ref={menuRef}
|
||||
actions={getAvailableActions()}
|
||||
actions={menuActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
renderPreview={renderPreview}
|
||||
onPress={navigateToReceive}
|
||||
|
@ -246,15 +216,37 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
AddressItem.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
index: PropTypes.number,
|
||||
address: PropTypes.string,
|
||||
isInternal: PropTypes.bool,
|
||||
transactions: PropTypes.number,
|
||||
balance: PropTypes.number,
|
||||
}),
|
||||
balanceUnit: PropTypes.string,
|
||||
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] | Action[][] => {
|
||||
const actions = [
|
||||
{
|
||||
id: AddressItem.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: AddressItem.actionIcons.Clipboard,
|
||||
},
|
||||
{
|
||||
id: AddressItem.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: AddressItem.actionIcons.Share,
|
||||
},
|
||||
];
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: AddressItem.actionKeys.SignVerify,
|
||||
text: loc.addresses.sign_title,
|
||||
icon: AddressItem.actionIcons.Signature,
|
||||
});
|
||||
}
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: AddressItem.actionKeys.ExportPrivateKey,
|
||||
text: loc.addresses.copy_private_key,
|
||||
icon: AddressItem.actionIcons.ExportPrivateKey,
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
export { AddressItem };
|
||||
|
|
30
components/types.ts
Normal file
30
components/types.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { ViewStyle } from 'react-native';
|
||||
|
||||
export interface Action {
|
||||
id: string | number;
|
||||
text: string;
|
||||
icon: {
|
||||
iconType: string;
|
||||
iconValue: string;
|
||||
};
|
||||
menuTitle?: string;
|
||||
menuStateOn?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolTipMenuProps {
|
||||
actions: Action[] | Action[][];
|
||||
children: React.ReactNode;
|
||||
enableAndroidRipple?: boolean;
|
||||
onPressMenuItem: (id: string) => void;
|
||||
title?: string;
|
||||
isMenuPrimaryAction?: boolean;
|
||||
isButton?: boolean;
|
||||
renderPreview?: () => React.ReactNode;
|
||||
onPress?: () => void;
|
||||
previewValue?: string;
|
||||
disabled?: boolean;
|
||||
buttonStyle?: ViewStyle;
|
||||
onMenuWillShow?: () => void;
|
||||
onMenuWillHide?: () => void;
|
||||
}
|
195
hooks/useBiometrics.ts
Normal file
195
hooks/useBiometrics.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics';
|
||||
import PasscodeAuth from 'react-native-passcode-auth';
|
||||
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
|
||||
import loc from '../loc';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
import presentAlert from '../components/Alert';
|
||||
|
||||
const STORAGEKEY = 'Biometrics';
|
||||
const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
|
||||
|
||||
const FaceID = 'Face ID';
|
||||
const TouchID = 'Touch ID';
|
||||
const Biometrics = 'Biometrics';
|
||||
|
||||
const clearKeychain = async () => {
|
||||
try {
|
||||
console.log('Wiping keychain');
|
||||
console.log('Wiping key: data');
|
||||
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
|
||||
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
||||
});
|
||||
console.log('Wiped key: data');
|
||||
console.log('Wiping key: data_encrypted');
|
||||
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
||||
console.log('Wiped key: data_encrypted');
|
||||
console.log('Wiping key: STORAGEKEY');
|
||||
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
||||
console.log('Wiped key: STORAGEKEY');
|
||||
NavigationService.reset();
|
||||
} catch (error: any) {
|
||||
console.warn(error);
|
||||
presentAlert({ message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
const requestDevicePasscode = async () => {
|
||||
let isDevicePasscodeSupported: boolean | undefined = false;
|
||||
try {
|
||||
isDevicePasscodeSupported = await PasscodeAuth.isSupported();
|
||||
if (isDevicePasscodeSupported) {
|
||||
const isAuthenticated = await PasscodeAuth.authenticate();
|
||||
if (isAuthenticated) {
|
||||
Alert.alert(
|
||||
loc.settings.encrypt_tstorage,
|
||||
loc.settings.biom_remove_decrypt,
|
||||
[
|
||||
{ text: loc._.cancel, style: 'cancel' },
|
||||
{
|
||||
text: loc._.ok,
|
||||
style: 'destructive',
|
||||
onPress: async () => await clearKeychain(),
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
isDevicePasscodeSupported = undefined;
|
||||
}
|
||||
if (isDevicePasscodeSupported === false) {
|
||||
presentAlert({ message: loc.settings.biom_no_passcode });
|
||||
}
|
||||
};
|
||||
|
||||
const showKeychainWipeAlert = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Alert.alert(
|
||||
loc.settings.encrypt_tstorage,
|
||||
loc.settings.biom_10times,
|
||||
[
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {
|
||||
console.log('Cancel Pressed');
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => requestDevicePasscode(),
|
||||
style: 'default',
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const useBiometrics = () => {
|
||||
const { getItem, setItem } = useStorage();
|
||||
const [biometricEnabled, setBiometricEnabled] = useState(false);
|
||||
const [deviceBiometricType, setDeviceBiometricType] = useState<'TouchID' | 'FaceID' | 'Biometrics' | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBiometricEnabledStatus = async () => {
|
||||
const enabled = await isBiometricUseEnabled();
|
||||
setBiometricEnabled(enabled);
|
||||
|
||||
const biometricType = await type();
|
||||
setDeviceBiometricType(biometricType);
|
||||
};
|
||||
|
||||
fetchBiometricEnabledStatus();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const isDeviceBiometricCapable = async () => {
|
||||
try {
|
||||
const { available } = await rnBiometrics.isSensorAvailable();
|
||||
return available;
|
||||
} catch (e) {
|
||||
console.log('Biometrics isDeviceBiometricCapable failed');
|
||||
console.log(e);
|
||||
setBiometricUseEnabled(false);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const type = async () => {
|
||||
try {
|
||||
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
|
||||
if (!available) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return biometryType;
|
||||
} catch (e) {
|
||||
console.log('Biometrics biometricType failed');
|
||||
console.log(e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const isBiometricUseEnabled = async () => {
|
||||
try {
|
||||
const enabledBiometrics = await getItem(STORAGEKEY);
|
||||
return !!enabledBiometrics;
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isBiometricUseCapableAndEnabled = async () => {
|
||||
const isEnabled = await isBiometricUseEnabled();
|
||||
const isCapable = await isDeviceBiometricCapable();
|
||||
return isEnabled && isCapable;
|
||||
};
|
||||
|
||||
const setBiometricUseEnabled = async (value: boolean) => {
|
||||
await setItem(STORAGEKEY, value === true ? '1' : '');
|
||||
setBiometricEnabled(value);
|
||||
};
|
||||
|
||||
const unlockWithBiometrics = async () => {
|
||||
const isCapable = await isDeviceBiometricCapable();
|
||||
if (isCapable) {
|
||||
return new Promise(resolve => {
|
||||
rnBiometrics
|
||||
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
|
||||
.then((result: { success: any }) => {
|
||||
if (result.success) {
|
||||
resolve(true);
|
||||
} else {
|
||||
console.log('Biometrics authentication failed');
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.log('Biometrics authentication error');
|
||||
presentAlert({ message: error.message });
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
isDeviceBiometricCapable,
|
||||
deviceBiometricType,
|
||||
isBiometricUseEnabled,
|
||||
isBiometricUseCapableAndEnabled,
|
||||
setBiometricUseEnabled,
|
||||
unlockWithBiometrics,
|
||||
clearKeychain,
|
||||
requestDevicePasscode,
|
||||
biometricEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
export { FaceID, TouchID, Biometrics, RNBiometryTypes as BiometricType, useBiometrics, showKeychainWipeAlert };
|
|
@ -1,9 +1,8 @@
|
|||
import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native';
|
||||
import Biometric from '../class/biometrics';
|
||||
import { navigationRef } from '../NavigationService';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import { useContext } from 'react';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder';
|
||||
import { useBiometrics } from './useBiometrics';
|
||||
|
||||
// List of screens that require biometrics
|
||||
|
||||
|
@ -15,7 +14,8 @@ const requiresWalletExportIsSaved = ['ReceiveDetailsRoot', 'WalletAddresses'];
|
|||
|
||||
export const useExtendedNavigation = (): NavigationProp<ParamListBase> => {
|
||||
const originalNavigation = useNavigation<NavigationProp<ParamListBase>>();
|
||||
const { wallets, saveToDisk } = useContext(BlueStorageContext);
|
||||
const { wallets, saveToDisk } = useStorage();
|
||||
const { isBiometricUseEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
|
||||
const enhancedNavigate: NavigationProp<ParamListBase>['navigate'] = (screenOrOptions: any, params?: any) => {
|
||||
let screenName: string;
|
||||
|
@ -42,12 +42,12 @@ export const useExtendedNavigation = (): NavigationProp<ParamListBase> => {
|
|||
|
||||
(async () => {
|
||||
if (isRequiresBiometrics) {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseEnabled();
|
||||
if (isBiometricsEnabled) {
|
||||
const isAuthenticated = await Biometric.unlockWithBiometrics();
|
||||
const isAuthenticated = await unlockWithBiometrics();
|
||||
if (isAuthenticated) {
|
||||
proceedWithNavigation();
|
||||
return; // Ensure the function exits if this path is taken
|
||||
return;
|
||||
} else {
|
||||
console.error('Biometric authentication failed');
|
||||
// Decide if navigation should proceed or not after failed authentication
|
||||
|
|
|
@ -1,41 +1,10 @@
|
|||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import { isTablet } from 'react-native-device-info';
|
||||
import { isDesktop } from '../blue_modules/environment';
|
||||
import { useContext } from 'react';
|
||||
import { LargeScreenContext } from '../components/Context/LargeScreenProvider';
|
||||
|
||||
// Custom hook to determine if the screen is large
|
||||
export const useIsLargeScreen = () => {
|
||||
const [windowWidth, setWindowWidth] = useState(Dimensions.get('window').width);
|
||||
const screenWidth = useMemo(() => Dimensions.get('screen').width, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updateScreenUsage = () => {
|
||||
const newWindowWidth = Dimensions.get('window').width;
|
||||
if (newWindowWidth !== windowWidth) {
|
||||
console.debug(`Window width changed: ${newWindowWidth}`);
|
||||
setWindowWidth(newWindowWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener for dimension changes
|
||||
const subscription = Dimensions.addEventListener('change', updateScreenUsage);
|
||||
|
||||
// Cleanup function to remove the event listener
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, [windowWidth]);
|
||||
|
||||
// Determine if the window width is at least half of the screen width
|
||||
const isLargeScreen = useMemo(() => {
|
||||
const isRunningOnTablet = isTablet();
|
||||
const halfScreenWidth = windowWidth >= screenWidth / 2;
|
||||
const condition = (isRunningOnTablet && halfScreenWidth) || isDesktop;
|
||||
console.debug(
|
||||
`Window width: ${windowWidth}, Screen width: ${screenWidth}, Is tablet: ${isTablet()}, Is large screen: ${condition}, isDesktkop: ${isDesktop}`,
|
||||
);
|
||||
return condition;
|
||||
}, [windowWidth, screenWidth]);
|
||||
|
||||
return isLargeScreen;
|
||||
export const useIsLargeScreen = (): boolean => {
|
||||
const context = useContext(LargeScreenContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useIsLargeScreen must be used within a LargeScreenProvider');
|
||||
}
|
||||
return context.isLargeScreen;
|
||||
};
|
||||
|
|
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);
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
|
|
@ -151,13 +151,14 @@
|
|||
B4A29A3A2B55C990002A67DF /* BlueWalletWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B40D4E30225841EC00428FCC /* BlueWalletWatch.app */; platformFilter = ios; };
|
||||
B4A29A3C2B55C990002A67DF /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
B4A29A3D2B55C990002A67DF /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
B4AB21072B61D8CA0080440C /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB21062B61D8CA0080440C /* SplashScreen.swift */; };
|
||||
B4AB21092B61DC3F0080440C /* SplashScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = B4AB21082B61DC3F0080440C /* SplashScreen.m */; };
|
||||
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
|
||||
B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
|
||||
B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
|
||||
B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
|
||||
B4B1A4652BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
|
||||
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
|
||||
C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; };
|
||||
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
|
||||
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -445,9 +446,8 @@
|
|||
B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = "<group>"; };
|
||||
B4A29A452B55C990002A67DF /* BlueWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B4A29A462B55C990002A67DF /* BlueWallet-NoLDK.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BlueWallet-NoLDK.plist"; sourceTree = "<absolute>"; };
|
||||
B4AB21062B61D8CA0080440C /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = "<group>"; };
|
||||
B4AB21082B61DC3F0080440C /* SplashScreen.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SplashScreen.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = "<group>"; };
|
||||
B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = "<group>"; };
|
||||
B68F8552DD4428F64B11DCFB /* Pods-BlueWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.debug.xcconfig"; path = "Target Support Files/Pods-BlueWallet/Pods-BlueWallet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
@ -474,7 +474,7 @@
|
|||
files = (
|
||||
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
|
||||
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
|
||||
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
|
||||
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */,
|
||||
773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -709,6 +709,7 @@
|
|||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */,
|
||||
B44033C82BCC34AC00162242 /* Shared */,
|
||||
B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */,
|
||||
B4549F2E2B80FEA1002E3153 /* ci_scripts */,
|
||||
|
@ -865,8 +866,6 @@
|
|||
B4AB21052B61D8890080440C /* SplashScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4AB21062B61D8CA0080440C /* SplashScreen.swift */,
|
||||
B4AB21082B61DC3F0080440C /* SplashScreen.m */,
|
||||
);
|
||||
name = SplashScreen;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1116,7 +1115,7 @@
|
|||
);
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
packageReferences = (
|
||||
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */,
|
||||
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */,
|
||||
B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */,
|
||||
);
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
|
@ -1502,9 +1501,7 @@
|
|||
files = (
|
||||
B44033E92BCC371A00162242 /* MarketData.swift in Sources */,
|
||||
B44033CA2BCC350A00162242 /* Currency.swift in Sources */,
|
||||
B4AB21092B61DC3F0080440C /* SplashScreen.m in Sources */,
|
||||
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
|
||||
B4AB21072B61D8CA0080440C /* SplashScreen.swift in Sources */,
|
||||
B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */,
|
||||
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
|
||||
B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */,
|
||||
|
@ -1516,6 +1513,7 @@
|
|||
B44034072BCC38A000162242 /* FiatUnit.swift in Sources */,
|
||||
B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */,
|
||||
B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */,
|
||||
B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */,
|
||||
B44033DA2BCC369A00162242 /* Colors.swift in Sources */,
|
||||
B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
|
||||
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */,
|
||||
|
@ -1536,6 +1534,7 @@
|
|||
B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */,
|
||||
6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */,
|
||||
6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */,
|
||||
B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */,
|
||||
B44033D52BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
|
||||
6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */,
|
||||
B44034022BCC37F800162242 /* Bundle+decode.swift in Sources */,
|
||||
|
@ -1632,6 +1631,7 @@
|
|||
B44033C72BCC332400162242 /* Balance.swift in Sources */,
|
||||
B44033FC2BCC379200162242 /* WidgetDataStore.swift in Sources */,
|
||||
B44033C22BCC32F800162242 /* BitcoinUnit.swift in Sources */,
|
||||
B4B1A4652BFA73110072E3BB /* WidgetHelper.swift in Sources */,
|
||||
B44033F12BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
|
||||
B44034032BCC37F800162242 /* Bundle+decode.swift in Sources */,
|
||||
B44033CD2BCC350A00162242 /* Currency.swift in Sources */,
|
||||
|
@ -1744,7 +1744,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
|
@ -1769,7 +1769,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -1804,7 +1804,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
|
@ -1824,7 +1824,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -1860,7 +1860,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -1873,7 +1873,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
|
@ -1903,7 +1903,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -1916,7 +1916,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
|
||||
|
@ -1947,7 +1947,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -1966,7 +1966,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
|
@ -2003,7 +2003,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -2022,7 +2022,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
|
||||
|
@ -2175,7 +2175,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -2192,7 +2192,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
|
@ -2225,7 +2225,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -2242,7 +2242,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
|
||||
|
@ -2274,7 +2274,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -2287,7 +2287,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
|
@ -2322,7 +2322,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -2335,7 +2335,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
|
||||
|
@ -2366,7 +2366,7 @@
|
|||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
|
@ -2417,7 +2417,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
|
@ -2456,7 +2456,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -2480,7 +2480,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -2510,7 +2510,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWalletRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1703136666;
|
||||
CURRENT_PROJECT_VERSION = 1703136669;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -2529,7 +2529,7 @@
|
|||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
"$(inherited)",
|
||||
);
|
||||
MARKETING_VERSION = 6.6.6;
|
||||
MARKETING_VERSION = 6.6.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -2627,7 +2627,7 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */ = {
|
||||
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/EFPrefix/EFQRCode.git";
|
||||
requirement = {
|
||||
|
@ -2648,7 +2648,7 @@
|
|||
/* Begin XCSwiftPackageProductDependency section */
|
||||
6DFC806F24EA0B6C007B8700 /* EFQRCode */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */;
|
||||
package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */;
|
||||
productName = EFQRCode;
|
||||
};
|
||||
B41B76842B66B2FF002C48D5 /* Bugsnag */ = {
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
#import "EventEmitter.h"
|
||||
#import <React/RCTRootView.h>
|
||||
#import <Bugsnag/Bugsnag.h>
|
||||
#import "BlueWallet-Swift.h"
|
||||
|
||||
@interface AppDelegate() <UNUserNotificationCenterDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIView *launchScreenView;
|
||||
@property (nonatomic, strong) NSUserDefaults *userDefaultsGroup;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -20,9 +21,10 @@
|
|||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
|
||||
NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
NSString *isDoNotTrackEnabled = [group stringForKey:@"donottrack"];
|
||||
[self clearFilesIfNeeded];
|
||||
self.userDefaultsGroup = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
|
||||
NSString *isDoNotTrackEnabled = [self.userDefaultsGroup stringForKey:@"donottrack"];
|
||||
if (![isDoNotTrackEnabled isEqualToString:@"1"]) {
|
||||
// Set the appType based on the current platform
|
||||
#if TARGET_OS_MACCATALYST
|
||||
|
@ -41,8 +43,6 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
[NSUserDefaults.standardUserDefaults setValue:@"" forKey:@"deviceUIDCopy"];
|
||||
}
|
||||
|
||||
[self addSplashScreenView];
|
||||
|
||||
self.moduleName = @"BlueWallet";
|
||||
// You can add your custom initial props in the dictionary below.
|
||||
// They will be passed down to the ViewController used by React Native.
|
||||
|
@ -52,26 +52,12 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
center.delegate = self;
|
||||
|
||||
|
||||
[self setupUserDefaultsListener];
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
- (void)addSplashScreenView
|
||||
{
|
||||
// Get the rootView
|
||||
RCTRootView *rootView = (RCTRootView *)self.window.rootViewController.view;
|
||||
|
||||
// Capture the launch screen view
|
||||
UIStoryboard *launchScreenStoryboard = [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil];
|
||||
UIViewController *launchScreenVC = [launchScreenStoryboard instantiateInitialViewController];
|
||||
UIView *launchScreenView = launchScreenVC.view;
|
||||
launchScreenView.frame = self.window.bounds;
|
||||
[self.window addSubview:launchScreenView];
|
||||
|
||||
// Keep a reference to the launch screen view to remove it later
|
||||
rootView.loadingView = launchScreenView;
|
||||
}
|
||||
|
||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
||||
{
|
||||
#if DEBUG
|
||||
|
@ -81,34 +67,66 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
#endif
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
|
||||
{
|
||||
if([keyPath isEqual:@"deviceUID"] || [keyPath isEqual:@"deviceUIDCopy"])
|
||||
{
|
||||
[self copyDeviceUID];
|
||||
if ([keyPath isEqualToString:@"deviceUID"] || [keyPath isEqualToString:@"deviceUIDCopy"]) {
|
||||
[self copyDeviceUID];
|
||||
}
|
||||
|
||||
NSArray *keys = @[
|
||||
@"WidgetCommunicationAllWalletsSatoshiBalance",
|
||||
@"WidgetCommunicationAllWalletsLatestTransactionTime",
|
||||
@"WidgetCommunicationDisplayBalanceAllowed",
|
||||
@"WidgetCommunicationLatestTransactionIsUnconfirmed",
|
||||
@"preferredCurrency",
|
||||
@"preferredCurrencyLocale",
|
||||
@"electrum_host",
|
||||
@"electrum_tcp_port",
|
||||
@"electrum_ssl_port"
|
||||
];
|
||||
|
||||
if ([keys containsObject:keyPath]) {
|
||||
[WidgetHelper reloadAllWidgets];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copyDeviceUID {
|
||||
[[NSUserDefaults standardUserDefaults] addObserver:self
|
||||
[NSUserDefaults.standardUserDefaults addObserver:self
|
||||
forKeyPath:@"deviceUID"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
[[NSUserDefaults standardUserDefaults] addObserver:self
|
||||
[NSUserDefaults.standardUserDefaults addObserver:self
|
||||
forKeyPath:@"deviceUIDCopy"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
NSString *deviceUID = [[NSUserDefaults standardUserDefaults] stringForKey:@"deviceUID"];
|
||||
if (deviceUID && deviceUID.length > 0) {
|
||||
[NSUserDefaults.standardUserDefaults setValue:deviceUID forKey:@"deviceUIDCopy"];
|
||||
}
|
||||
NSString *deviceUID = [NSUserDefaults.standardUserDefaults stringForKey:@"deviceUID"];
|
||||
if (deviceUID && deviceUID.length > 0) {
|
||||
[NSUserDefaults.standardUserDefaults setValue:deviceUID forKey:@"deviceUIDCopy"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupUserDefaultsListener {
|
||||
NSArray *keys = @[
|
||||
@"WidgetCommunicationAllWalletsSatoshiBalance",
|
||||
@"WidgetCommunicationAllWalletsLatestTransactionTime",
|
||||
@"WidgetCommunicationDisplayBalanceAllowed",
|
||||
@"WidgetCommunicationLatestTransactionIsUnconfirmed",
|
||||
@"preferredCurrency",
|
||||
@"preferredCurrencyLocale",
|
||||
@"electrum_host",
|
||||
@"electrum_tcp_port",
|
||||
@"electrum_ssl_port"
|
||||
];
|
||||
|
||||
for (NSString *key in keys) {
|
||||
[self.userDefaultsGroup addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:NULL];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
|
||||
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
|
||||
{
|
||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
[defaults setValue:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo} forKey:@"onUserActivityOpen"];
|
||||
[self.userDefaultsGroup setValue:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo} forKey:@"onUserActivityOpen"];
|
||||
if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
|
||||
return [RCTLinkingManager application:application
|
||||
continueUserActivity:userActivity
|
||||
|
@ -129,8 +147,7 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
[defaults removeObjectForKey:@"onUserActivityOpen"];
|
||||
[self.userDefaultsGroup removeObjectForKey:@"onUserActivityOpen"];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler {
|
||||
|
@ -152,8 +169,8 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
[builder removeMenuForIdentifier:UIMenuToolbar];
|
||||
|
||||
// File -> Add Wallet (Command + A)
|
||||
UIKeyCommand *addWalletCommand = [UIKeyCommand keyCommandWithInput:@"A"
|
||||
modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
|
||||
UIKeyCommand *addWalletCommand = [UIKeyCommand keyCommandWithInput:@"A"
|
||||
modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
|
||||
action:@selector(addWalletAction:)];
|
||||
[addWalletCommand setTitle:@"Add Wallet"];
|
||||
|
||||
|
@ -184,35 +201,29 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu
|
|||
[builder insertSiblingMenu:settings afterMenuForIdentifier:UIMenuAbout];
|
||||
}
|
||||
|
||||
|
||||
- (void)openSettings:(UIKeyCommand *)keyCommand {
|
||||
[EventEmitter.sharedInstance openSettings];
|
||||
}
|
||||
|
||||
- (void)addWalletAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance addWalletMenuAction];
|
||||
|
||||
[EventEmitter.sharedInstance addWalletMenuAction];
|
||||
NSLog(@"Add Wallet action performed");
|
||||
}
|
||||
|
||||
- (void)importWalletAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance importWalletMenuAction];
|
||||
|
||||
[EventEmitter.sharedInstance importWalletMenuAction];
|
||||
NSLog(@"Import Wallet action performed");
|
||||
}
|
||||
|
||||
- (void)reloadTransactionsAction:(UIKeyCommand *)keyCommand {
|
||||
// Implement the functionality for adding a wallet
|
||||
[EventEmitter.sharedInstance reloadTransactionsMenuAction];
|
||||
|
||||
[EventEmitter.sharedInstance reloadTransactionsMenuAction];
|
||||
NSLog(@"Reload Transactions action performed");
|
||||
}
|
||||
|
||||
|
||||
|
||||
-(void)showHelp:(id)sender {
|
||||
- (void)showHelp:(id)sender {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://bluewallet.io/docs"] options:@{} completionHandler:nil];
|
||||
}
|
||||
|
||||
|
@ -248,4 +259,63 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
|
||||
}
|
||||
|
||||
// Clear cache on app launch
|
||||
- (void)clearFilesIfNeeded {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
BOOL shouldClearFiles = [defaults boolForKey:@"clearFilesOnLaunch"];
|
||||
|
||||
if (shouldClearFiles) {
|
||||
[self clearDocumentDirectory];
|
||||
[self clearCacheDirectory];
|
||||
[self clearTempDirectory];
|
||||
|
||||
// Reset the switch
|
||||
[defaults setBool:NO forKey:@"clearFilesOnLaunch"];
|
||||
[defaults synchronize];
|
||||
|
||||
// Show an alert
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Cache Cleared"
|
||||
message:@"The document, cache, and temp directories have been cleared."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
|
||||
[alert addAction:okAction];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearDocumentDirectory {
|
||||
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
[self clearDirectoryAtURL:documentsDirectory];
|
||||
}
|
||||
|
||||
- (void)clearCacheDirectory {
|
||||
NSURL *cacheDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
[self clearDirectoryAtURL:cacheDirectory];
|
||||
}
|
||||
|
||||
- (void)clearTempDirectory {
|
||||
NSURL *tempDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
|
||||
[self clearDirectoryAtURL:tempDirectory];
|
||||
}
|
||||
|
||||
- (void)clearDirectoryAtURL:(NSURL *)directoryURL {
|
||||
NSError *error;
|
||||
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:directoryURL includingPropertiesForKeys:nil options:0 error:&error];
|
||||
|
||||
if (error) {
|
||||
NSLog(@"Error reading contents of directory: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSURL *fileURL in contents) {
|
||||
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error removing file: %@", error.localizedDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
PODS:
|
||||
- boost (1.76.0)
|
||||
- BugsnagReactNative (7.22.7):
|
||||
- BugsnagReactNative (7.23.0):
|
||||
- React-Core
|
||||
- BVLinearGradient (2.8.3):
|
||||
- React-Core
|
||||
|
@ -334,7 +334,7 @@ PODS:
|
|||
- React-Core
|
||||
- react-native-document-picker (9.1.1):
|
||||
- React-Core
|
||||
- react-native-idle-timer (2.1.6):
|
||||
- react-native-idle-timer (2.2.2):
|
||||
- React-Core
|
||||
- react-native-image-picker (7.1.2):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
|
@ -352,8 +352,6 @@ PODS:
|
|||
- react-native-tcp-socket (6.0.6):
|
||||
- CocoaAsyncSocket
|
||||
- React-Core
|
||||
- react-native-widget-center (0.0.9):
|
||||
- React
|
||||
- React-NativeModulesApple (0.72.14):
|
||||
- hermes-engine
|
||||
- React-callinvoker
|
||||
|
@ -466,7 +464,7 @@ PODS:
|
|||
- React-perflogger (= 0.72.14)
|
||||
- ReactNativeCameraKit (13.0.0):
|
||||
- React-Core
|
||||
- RealmJS (12.8.0):
|
||||
- RealmJS (12.8.1):
|
||||
- React
|
||||
- rn-ldk (0.8.4):
|
||||
- React-Core
|
||||
|
@ -478,7 +476,7 @@ PODS:
|
|||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (10.13.2):
|
||||
- RNDeviceInfo (10.14.0):
|
||||
- React-Core
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
|
@ -509,9 +507,9 @@ PODS:
|
|||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNShare (10.2.0):
|
||||
- RNShare (10.2.1):
|
||||
- React-Core
|
||||
- RNSVG (13.14.0):
|
||||
- RNSVG (13.14.1):
|
||||
- React-Core
|
||||
- RNVectorIcons (10.1.0):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
|
@ -561,7 +559,6 @@ DEPENDENCIES:
|
|||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`)
|
||||
- react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`)
|
||||
- react-native-widget-center (from `../node_modules/react-native-widget-center`)
|
||||
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
|
||||
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||
|
@ -688,8 +685,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-secure-key-store"
|
||||
react-native-tcp-socket:
|
||||
:path: "../node_modules/react-native-tcp-socket"
|
||||
react-native-widget-center:
|
||||
:path: "../node_modules/react-native-widget-center"
|
||||
React-NativeModulesApple:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
|
||||
React-perflogger:
|
||||
|
@ -777,7 +772,7 @@ EXTERNAL SOURCES:
|
|||
|
||||
SPEC CHECKSUMS:
|
||||
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
|
||||
BugsnagReactNative: 7cc5c927f6a0b00a8e3cc7157dab4cc94a4bc575
|
||||
BugsnagReactNative: 079e8ede687b76bd8b661acd55bc5c888af56dc7
|
||||
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
|
@ -809,7 +804,7 @@ SPEC CHECKSUMS:
|
|||
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
|
||||
react-native-bw-file-access: b232fd1d902521ca046f3fc5990ab1465e1878d7
|
||||
react-native-document-picker: 3599b238843369026201d2ef466df53f77ae0452
|
||||
react-native-idle-timer: f7f651542b39dce9b9473e4578cb64a255075f17
|
||||
react-native-idle-timer: ee2053f2cd458f6fef1db7bebe5098ca281cce07
|
||||
react-native-image-picker: 1889c342e6a4ba089ff11ae0c3bf5cc30a3134d0
|
||||
react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5
|
||||
react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc
|
||||
|
@ -817,7 +812,6 @@ SPEC CHECKSUMS:
|
|||
react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d
|
||||
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
|
||||
react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7
|
||||
react-native-widget-center: 12dfba20a4fa995850b52cf0afecf734397f4b9c
|
||||
React-NativeModulesApple: 3107f777453f953906d9ba9dc5f8cbd91a6ef913
|
||||
React-perflogger: daabc494c6328efc1784a4b49b8b74fca305d11c
|
||||
React-RCTActionSheet: 0e0e64a7cf6c07f1de73d1f0a92d26a70262b256
|
||||
|
@ -836,13 +830,13 @@ SPEC CHECKSUMS:
|
|||
React-utils: 22a77b05da25ce49c744faa82e73856dcae1734e
|
||||
ReactCommon: ff94462e007c568d8cdebc32e3c97af86ec93bb5
|
||||
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
|
||||
RealmJS: 3e6010ae878227830e947f40f996e13ccab4c8ba
|
||||
RealmJS: 2c7fdb3991d7655fba5f88eb288f75eaf5cb9980
|
||||
rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643
|
||||
RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
|
||||
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
|
||||
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 42aadf1282ffa0a88dc38a504a7be145eb010dfa
|
||||
RNDeviceInfo: 59344c19152c4b2b32283005f9737c5c64b42fba
|
||||
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
||||
RNGestureHandler: 982741f345785f2927e7b28f67dc83679cf3bfc8
|
||||
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
|
||||
|
@ -855,8 +849,8 @@ SPEC CHECKSUMS:
|
|||
RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9
|
||||
RNReanimated: d4f25b2a931c4f0b2bb12173a3096f02ea4cfb05
|
||||
RNScreens: b8d370282cdeae9df85dd5eab20c88eb5181243b
|
||||
RNShare: 554a91f5cfbe4adac4cfe3654826ee8b299fe365
|
||||
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
|
||||
RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c
|
||||
RNSVG: af3907ac5d4fa26a862b75a16d8f15bc74f2ceda
|
||||
RNVectorIcons: 32462e7c7e58fe457474fc79c4d7de3f0ef08d70
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
|
||||
|
|
|
@ -12,16 +12,32 @@
|
|||
<key>FooterText</key>
|
||||
<string>Provide this Unique ID when reporting an issue</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<string>Report Issue</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSTextFieldSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Unique ID</string>
|
||||
<key>Key</key>
|
||||
<key>Key</key>
|
||||
<string>deviceUIDCopy</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Cache</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Clear Cache on Next Launch</string>
|
||||
<key>Key</key>
|
||||
<string>clearFilesOnLaunch</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// SplashScreen.m
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 1/24/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(SplashScreen, NSObject)
|
||||
RCT_EXTERN_METHOD(addObserver)
|
||||
RCT_EXTERN_METHOD(dismissSplashScreen)
|
||||
@end
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// SplashScreen.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 1/24/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import React
|
||||
|
||||
@objc(SplashScreen)
|
||||
class SplashScreen: NSObject, RCTBridgeModule {
|
||||
static func moduleName() -> String! {
|
||||
return "SplashScreen"
|
||||
}
|
||||
|
||||
static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
func addObserver() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(dismissSplashScreen), name: NSNotification.Name("HideSplashScreen"), object: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
func dismissSplashScreen() {
|
||||
DispatchQueue.main.async {
|
||||
if let rootView = UIApplication.shared.delegate?.window??.rootViewController?.view as? RCTRootView {
|
||||
rootView.loadingView?.removeFromSuperview()
|
||||
rootView.loadingView = nil
|
||||
}
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name("HideSplashScreen"), object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
11
ios/WidgetHelper.swift
Normal file
11
ios/WidgetHelper.swift
Normal file
|
@ -0,0 +1,11 @@
|
|||
import WidgetKit
|
||||
|
||||
@objc class WidgetHelper: NSObject {
|
||||
@objc static func reloadAllWidgets() {
|
||||
if #available(iOS 14.0, *) {
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
}
|
19
loc/en.json
19
loc/en.json
|
@ -277,6 +277,7 @@
|
|||
"encrypt_title": "Security",
|
||||
"encrypt_tstorage": "Storage",
|
||||
"encrypt_use": "Use {type}",
|
||||
"encrypted_feature_disabled": "This feature cannot be used with encrypted storage enabled.",
|
||||
"encrypt_use_expl": "{type} will be used to confirm your identity before making a transaction, unlocking, exporting, or deleting a wallet. {type} will not be used to unlock encrypted storage.",
|
||||
"biometrics_fail": "If {type} is not enabled, or fails to unlock, you can use your device passcode as an alternative.",
|
||||
"general": "General",
|
||||
|
@ -615,10 +616,22 @@
|
|||
},
|
||||
"bip47": {
|
||||
"payment_code": "Payment Code",
|
||||
"payment_codes_list": "Payment Codes List",
|
||||
"who_can_pay_me": "Who can pay me:",
|
||||
"whom_can_i_pay": "Whom can I pay:",
|
||||
"contacts": "Contacts",
|
||||
"purpose": "Reusable and shareable code (BIP47)",
|
||||
"pay_this_contact": "Pay this contact",
|
||||
"rename_contact": "Rename contact",
|
||||
"copy_payment_code": "Copy Payment Code",
|
||||
"copied": "Copied",
|
||||
"rename": "Rename",
|
||||
"provide_name": "Provide new name for this contact",
|
||||
"add_contact": "Add Contact",
|
||||
"provide_payment_code": "Provide Payment Code",
|
||||
"invalid_pc": "Invalid Payment Code",
|
||||
"notification_tx_unconfirmed": "Notification transaction is not confirmed yet, please wait",
|
||||
"failed_create_notif_tx": "Failed to create on-chain transaction",
|
||||
"onchain_tx_needed": "On-chain transaction needed",
|
||||
"notif_tx_sent" : "Notification transaction sent. Please wait for it to confirm",
|
||||
"notif_tx": "Notification transaction",
|
||||
"not_found": "Payment code not found"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,8 @@
|
|||
"maxSats": "La cantidad máxima es {max} sats",
|
||||
"maxSatsFull": "La cantidad máxima es {max} sats o {currency}",
|
||||
"minSats": "La cantidad mínima es {min} sats",
|
||||
"minSatsFull": "La cantidad mínima es {min} sats o {currency}"
|
||||
"minSatsFull": "La cantidad mínima es {min} sats o {currency}",
|
||||
"qrcode_for_the_address": "Código QR para la dirección"
|
||||
},
|
||||
"send": {
|
||||
"provided_address_is_invoice": "Esta dirección parece ser para una factura Lightning. Por favor, ve a tu billetera Lightning para realizar el pago de esta factura.",
|
||||
|
@ -166,6 +167,7 @@
|
|||
"details_next": "Siguiente",
|
||||
"details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.",
|
||||
"details_note_placeholder": "Nota personal",
|
||||
"counterparty_label_placeholder": "Editar nombre de contacto",
|
||||
"details_scan": "Escanear",
|
||||
"details_scan_hint": "Toca dos veces para escanear o importar un destino",
|
||||
"details_total_exceeds_balance": "La cantidad de envío excede el saldo disponible.",
|
||||
|
@ -275,6 +277,7 @@
|
|||
"encrypt_title": "Seguridad",
|
||||
"encrypt_tstorage": "Almacenamiento",
|
||||
"encrypt_use": "Usar {type}",
|
||||
"encrypted_feature_disabled": "Esta función no se puede utilizar con el almacenamiento cifrado habilitado.",
|
||||
"encrypt_use_expl": "{type} se utilizará para confirmar tu identidad antes de realizar una transacción, desbloquear, exportar o eliminar una billetera. {type} no se utilizará para desbloquear el almacenamiento encriptado.",
|
||||
"biometrics_fail": "Si {type} no está activado o no se desbloquea, puedes utilizar el código de acceso de tu dispositivo como alternativa.",
|
||||
"general": "General",
|
||||
|
@ -340,7 +343,6 @@
|
|||
"cpfp_title": "Aumentar Comisión (CPFP)",
|
||||
"details_balance_hide": "Ocultar Balance",
|
||||
"details_balance_show": "Mostrar Balance",
|
||||
"details_block": "Altura del Bloque",
|
||||
"details_copy": "Copiar",
|
||||
"details_copy_amount": "Importe de la copia",
|
||||
"details_copy_block_explorer_link": "Copiar el enlace del explorador de bloques",
|
||||
|
@ -351,7 +353,7 @@
|
|||
"details_outputs": "Salidas",
|
||||
"date": "Fecha",
|
||||
"details_received": "Recibido",
|
||||
"transaction_note_saved": "La nota de transacción se ha guardado correctamente.",
|
||||
"transaction_saved": "Guardado",
|
||||
"details_show_in_block_explorer": "Ver en el Explorador de Bloques",
|
||||
"details_title": "Transacción",
|
||||
"details_to": "Salida",
|
||||
|
@ -372,6 +374,8 @@
|
|||
"status_cancel": "Cancelar Transacción",
|
||||
"transactions_count": "Número de Transacciones",
|
||||
"txid": "ID de Transacción",
|
||||
"from": "De: {counterparty}",
|
||||
"to": "A: {counterparty}",
|
||||
"updating": "Actualizando..."
|
||||
},
|
||||
"wallets": {
|
||||
|
@ -612,9 +616,22 @@
|
|||
},
|
||||
"bip47": {
|
||||
"payment_code": "Código de pago",
|
||||
"payment_codes_list": "Lista de códigos de pago",
|
||||
"who_can_pay_me": "Quién puede pagarme:",
|
||||
"contacts": "Contactos",
|
||||
"purpose": "Código reutilizable y compartible (BIP47)",
|
||||
"pay_this_contact": "Paga a este contacto",
|
||||
"rename_contact": "Renombrar contacto",
|
||||
"copy_payment_code": "Copiar código de pago",
|
||||
"copied": "Copiado",
|
||||
"rename": "Cambiar nombre",
|
||||
"provide_name": "Proporciona un nuevo nombre para este contacto",
|
||||
"add_contact": "Agregar contacto",
|
||||
"provide_payment_code": "Proporciona código de pago",
|
||||
"invalid_pc": "Código de pago no válido",
|
||||
"notification_tx_unconfirmed": "La transacción de notificación aún no está confirmada, espera",
|
||||
"failed_create_notif_tx": "No se pudo crear una transacción en cadena",
|
||||
"onchain_tx_needed": "Se necesita transacción en cadena",
|
||||
"notif_tx_sent" : "Transacción de notificación enviada. Espera a que se confirme",
|
||||
"notif_tx": "Transacción de notificación",
|
||||
"not_found": "Código de pago no encontrado"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = () => {
|
||||
|
@ -104,7 +99,7 @@ const DetailViewStackScreensStack = () => {
|
|||
title: '',
|
||||
headerBackTitle: loc.wallets.list_title,
|
||||
navigationBarColor: theme.colors.navigationBarColor,
|
||||
headerShown: true,
|
||||
headerShown: !isDesktop,
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.customHeader,
|
||||
},
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
|
||||
const PaymentCode = lazy(() => import('../screen/wallets/paymentCode'));
|
||||
const PaymentCodesList = lazy(() => import('../screen/wallets/paymentCodesList'));
|
||||
const PaymentCode = lazy(() => import('../screen/wallets/PaymentCode'));
|
||||
const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList'));
|
||||
|
||||
export const PaymentCodeComponent = () => (
|
||||
<Suspense fallback={<LazyLoadingIndicator />}>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
|
||||
const ReorderWallets = lazy(() => import('../screen/wallets/reorderWallets'));
|
||||
|
||||
export const ReorderWalletsComponent = () => (
|
||||
<Suspense fallback={<LazyLoadingIndicator />}>
|
||||
<ReorderWallets />
|
||||
</Suspense>
|
||||
);
|
22
navigation/MasterView.tsx
Normal file
22
navigation/MasterView.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import 'react-native-gesture-handler'; // should be on top
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import MainRoot from '../navigation';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
const CompanionDelegates = lazy(() => import('../components/CompanionDelegates'));
|
||||
|
||||
const MasterView = () => {
|
||||
const { walletsInitialized } = useStorage();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainRoot />
|
||||
{walletsInitialized && (
|
||||
<Suspense>
|
||||
<CompanionDelegates />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MasterView;
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { PaymentCodeComponent, PaymentCodesListComponent } from './LazyLoadPaymentCodeStack';
|
||||
import loc from '../loc'; // Assuming 'loc' is used for localization
|
||||
import navigationStyle from '../components/navigationStyle';
|
||||
import { useTheme } from '../components/themes';
|
||||
|
||||
export type PaymentCodeStackParamList = {
|
||||
PaymentCode: { paymentCode: string };
|
||||
|
@ -11,10 +13,20 @@ export type PaymentCodeStackParamList = {
|
|||
const Stack = createNativeStackNavigator<PaymentCodeStackParamList>();
|
||||
|
||||
const PaymentCodeStackRoot = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }} initialRouteName="PaymentCode">
|
||||
<Stack.Screen name="PaymentCode" component={PaymentCodeComponent} options={{ headerTitle: loc.bip47.payment_code }} />
|
||||
<Stack.Screen name="PaymentCodesList" component={PaymentCodesListComponent} options={{ headerTitle: loc.bip47.payment_codes_list }} />
|
||||
<Stack.Screen
|
||||
name="PaymentCode"
|
||||
component={PaymentCodeComponent}
|
||||
options={navigationStyle({ title: loc.bip47.payment_code, closeButton: true })(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PaymentCodesList"
|
||||
component={PaymentCodesListComponent}
|
||||
options={navigationStyle({ title: loc.bip47.contacts, closeButton: true })(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { ReorderWalletsComponent } from './LazyLoadReorderWalletsStack';
|
||||
import { useTheme } from '../components/themes';
|
||||
import navigationStyle from '../components/navigationStyle';
|
||||
import loc from '../loc';
|
||||
import ReorderWallets from '../screen/wallets/reorderWallets';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
|
@ -14,7 +14,7 @@ const ReorderWalletsStackRoot = () => {
|
|||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name="ReorderWalletsScreen"
|
||||
component={ReorderWalletsComponent}
|
||||
component={ReorderWallets}
|
||||
options={navigationStyle({
|
||||
headerBackVisible: false,
|
||||
headerLargeTitle: true,
|
||||
|
|
|
@ -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,
|
124
package-lock.json
generated
124
package-lock.json
generated
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"name": "bluewallet",
|
||||
"version": "6.6.6",
|
||||
"version": "6.6.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bluewallet",
|
||||
"version": "6.6.6",
|
||||
"version": "6.6.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@bugsnag/react-native": "7.22.7",
|
||||
"@bugsnag/react-native": "7.23.0",
|
||||
"@bugsnag/source-maps": "2.3.3",
|
||||
"@keystonehq/bc-ur-registry": "0.6.4",
|
||||
"@ngraveio/bc-ur": "1.1.12",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"react-native-camera-kit": "13.0.0",
|
||||
"react-native-crypto": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.13.2",
|
||||
"react-native-device-info": "10.14.0",
|
||||
"react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4",
|
||||
"react-native-elements": "3.4.3",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"react-native-gesture-handler": "2.16.2",
|
||||
"react-native-handoff": "https://github.com/BlueWallet/react-native-handoff#31d005f93d31099d0e564590a3bbd052b8a02b39",
|
||||
"react-native-haptic-feedback": "2.2.0",
|
||||
"react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b",
|
||||
"react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#7300b637c465c86e8db874c442e687950111da40",
|
||||
"react-native-image-picker": "7.1.2",
|
||||
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3",
|
||||
"react-native-keychain": "8.2.0",
|
||||
|
@ -92,14 +92,13 @@
|
|||
"react-native-safe-area-context": "4.10.1",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48",
|
||||
"react-native-share": "10.2.0",
|
||||
"react-native-svg": "13.14.0",
|
||||
"react-native-share": "10.2.1",
|
||||
"react-native-svg": "13.14.1",
|
||||
"react-native-tcp-socket": "6.0.6",
|
||||
"react-native-vector-icons": "10.1.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
|
||||
"readable-stream": "3.6.2",
|
||||
"realm": "12.8.0",
|
||||
"realm": "12.8.1",
|
||||
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
|
||||
"rn-nodeify": "10.3.0",
|
||||
"scryptsy": "2.1.0",
|
||||
|
@ -2250,17 +2249,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@bugsnag/plugin-react-native-unhandled-rejection": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.22.7.tgz",
|
||||
"integrity": "sha512-xmFpUPYrQxwsr9RJ1HTu9lfNUbAHM+hIyUEshg+/Wfj/1Zvnkr0AnkqRWbQFqkOBklzYI4s7maJvm4S2go/KOQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.23.0.tgz",
|
||||
"integrity": "sha512-z0Nlqir3nnBcXVffw8uau12SS7vVUu0yS65SS5uWUn9cNIwNSTqZ/40pHVGh1VQBbpXlYrw7RVbPtufWmS20EA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@bugsnag/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bugsnag/react-native": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.7.tgz",
|
||||
"integrity": "sha512-vHmynQj7rzPW+1v8aK41G9T5HSaXipgFkkCmczOiFG9YYNzVKeaPcbwcS6Z6+tLZ55ZQeJdupfezcmj4rnAZVw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.23.0.tgz",
|
||||
"integrity": "sha512-4xw0BDUBYPYxxBM0rbRr+uI+8IA/22p2JdzW+DMzLjue9e5PY+dTGZgREllGLuE27NtMBvnaA0rlWSYfxE04cQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bugsnag/core": "^7.22.7",
|
||||
"@bugsnag/delivery-react-native": "^7.22.7",
|
||||
|
@ -2272,7 +2273,7 @@
|
|||
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-hermes": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-session": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.23.0",
|
||||
"iserror": "^0.0.2"
|
||||
}
|
||||
},
|
||||
|
@ -19386,9 +19387,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-device-info": {
|
||||
"version": "10.13.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.13.2.tgz",
|
||||
"integrity": "sha512-5EAls7uvGdZkVfp1KWHsR5BfJJHp/ux64+ZPj1865IcaUyrNQIWYFmrTHwTH8L/NGJUTBrzv+y6WODnN17LSbw==",
|
||||
"version": "10.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.14.0.tgz",
|
||||
"integrity": "sha512-9NnTGfhEU4UgQtz4p6COk2Gbqly0dpSWrJtp+dw5rNAi96KtYbaNnO5yoOHDlJ1SVIzh8+hFu3WxVbnWkFU9gA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
|
@ -19507,9 +19509,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-idle-timer": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#8587876d68ab5920e79619726aeca9e672beaf2b",
|
||||
"integrity": "sha512-d24QXgHUkwu+BTp676Pb7ng7u01iiw3YBBY+DYvurkMAHRoB9c+wj32oB4sLq07W+LbETyMEuDlLUxTOasqd3Q==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#7300b637c465c86e8db874c442e687950111da40",
|
||||
"integrity": "sha512-izpVaEvAXucoKpn/pooJ+vY7h2/sp6LHb+tPVCYv8WYvXbha2v6cW8R762y1ErNyJbEMj7u7IfFtSTxjeIcT9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-image-picker": {
|
||||
|
@ -19739,9 +19741,10 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/react-native-share": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.2.0.tgz",
|
||||
"integrity": "sha512-dn6FNoEADHdeAkBihIN4ewyxc5PO0MmvFzaSsTsLCwOs7VrX8rBNbGDE5iMP3Anz+SY7YInW2UuxVttBzii6wg==",
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.2.1.tgz",
|
||||
"integrity": "sha512-Z2LWGYWH7raM4H6Oauttv1tEhaB43XSWJAN8iS6oaSG9CnyrUBeYFF4QpU1AH5RgNeylXQdN8CtbizCHHt6coQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
|
@ -19755,9 +19758,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-svg": {
|
||||
"version": "13.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.14.0.tgz",
|
||||
"integrity": "sha512-27ZnxUkHgWICimhuj6MuqBkISN53lVvgWJB7pIypjXysAyM+nqgQBPh4vXg+7MbqLBoYvR4PiBgKfwwGAqVxHg==",
|
||||
"version": "13.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.14.1.tgz",
|
||||
"integrity": "sha512-0DSa0EOySzV0J9utmRFVE5Vr4mKRXA7GtH1Ga1B6fzR967HGFW1ytH6hmnOf36316hjYpXMMB7s4oVe1FqghlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^1.1.3"
|
||||
|
@ -19868,15 +19872,6 @@
|
|||
"react-native": ">=0.40"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-widget-center": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-widget-center.git#a128c389526d55afdd67937494f2fec224dd0009",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.1",
|
||||
"react-native": ">=0.60.0-rc.0 <1.0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
|
@ -20110,10 +20105,11 @@
|
|||
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
|
||||
},
|
||||
"node_modules/realm": {
|
||||
"version": "12.8.0",
|
||||
"resolved": "https://registry.npmjs.org/realm/-/realm-12.8.0.tgz",
|
||||
"integrity": "sha512-U1w5+ncyURQFQTrshoGn3KV+pzR1rQlPT7s3Sw6HPIPVBH80EWU3mirwvqp6RQ+Qi32ctRrBMTNeGd5mzAyiSw==",
|
||||
"version": "12.8.1",
|
||||
"resolved": "https://registry.npmjs.org/realm/-/realm-12.8.1.tgz",
|
||||
"integrity": "sha512-+zj2bvU0EACXvPBdvRsp2TybHPqPtftciTXsAlhrTWMiaoqC8FO6lriPbUs/JwsXz1w9otJXl5kXRBghPQHgLQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "apache-2.0",
|
||||
"dependencies": {
|
||||
"@realm/fetch": "^0.1.1",
|
||||
"bson": "^4.7.2",
|
||||
|
@ -24003,14 +23999,14 @@
|
|||
"integrity": "sha512-p3C7m6GXh9ICnGt+m1FwWpBCiGNGdQvoTzzN0LAxT6YQdB3t2nmhqE3QIpHmXpJK1PveTCIOO2DbeSerWtUEsg=="
|
||||
},
|
||||
"@bugsnag/plugin-react-native-unhandled-rejection": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.22.7.tgz",
|
||||
"integrity": "sha512-xmFpUPYrQxwsr9RJ1HTu9lfNUbAHM+hIyUEshg+/Wfj/1Zvnkr0AnkqRWbQFqkOBklzYI4s7maJvm4S2go/KOQ=="
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.23.0.tgz",
|
||||
"integrity": "sha512-z0Nlqir3nnBcXVffw8uau12SS7vVUu0yS65SS5uWUn9cNIwNSTqZ/40pHVGh1VQBbpXlYrw7RVbPtufWmS20EA=="
|
||||
},
|
||||
"@bugsnag/react-native": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.7.tgz",
|
||||
"integrity": "sha512-vHmynQj7rzPW+1v8aK41G9T5HSaXipgFkkCmczOiFG9YYNzVKeaPcbwcS6Z6+tLZ55ZQeJdupfezcmj4rnAZVw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.23.0.tgz",
|
||||
"integrity": "sha512-4xw0BDUBYPYxxBM0rbRr+uI+8IA/22p2JdzW+DMzLjue9e5PY+dTGZgREllGLuE27NtMBvnaA0rlWSYfxE04cQ==",
|
||||
"requires": {
|
||||
"@bugsnag/core": "^7.22.7",
|
||||
"@bugsnag/delivery-react-native": "^7.22.7",
|
||||
|
@ -24022,7 +24018,7 @@
|
|||
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-hermes": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-session": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.22.7",
|
||||
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.23.0",
|
||||
"iserror": "^0.0.2"
|
||||
}
|
||||
},
|
||||
|
@ -37100,9 +37096,9 @@
|
|||
"integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg=="
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "10.13.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.13.2.tgz",
|
||||
"integrity": "sha512-5EAls7uvGdZkVfp1KWHsR5BfJJHp/ux64+ZPj1865IcaUyrNQIWYFmrTHwTH8L/NGJUTBrzv+y6WODnN17LSbw=="
|
||||
"version": "10.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.14.0.tgz",
|
||||
"integrity": "sha512-9NnTGfhEU4UgQtz4p6COk2Gbqly0dpSWrJtp+dw5rNAi96KtYbaNnO5yoOHDlJ1SVIzh8+hFu3WxVbnWkFU9gA=="
|
||||
},
|
||||
"react-native-document-picker": {
|
||||
"version": "git+ssh://git@github.com/BlueWallet/react-native-document-picker.git#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
|
@ -37177,9 +37173,9 @@
|
|||
"integrity": "sha512-3tqJOjCguWhIrX0nkURn4yw6kXdsSDjjrvZCRjKXYGlL28hdQmoW2okAHduDTD9FWj9lA+lHgwFWgGs4aFNN7A=="
|
||||
},
|
||||
"react-native-idle-timer": {
|
||||
"version": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#8587876d68ab5920e79619726aeca9e672beaf2b",
|
||||
"integrity": "sha512-d24QXgHUkwu+BTp676Pb7ng7u01iiw3YBBY+DYvurkMAHRoB9c+wj32oB4sLq07W+LbETyMEuDlLUxTOasqd3Q==",
|
||||
"from": "react-native-idle-timer@https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b"
|
||||
"version": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#7300b637c465c86e8db874c442e687950111da40",
|
||||
"integrity": "sha512-izpVaEvAXucoKpn/pooJ+vY7h2/sp6LHb+tPVCYv8WYvXbha2v6cW8R762y1ErNyJbEMj7u7IfFtSTxjeIcT9g==",
|
||||
"from": "react-native-idle-timer@https://github.com/BlueWallet/react-native-idle-timer#7300b637c465c86e8db874c442e687950111da40"
|
||||
},
|
||||
"react-native-image-picker": {
|
||||
"version": "7.1.2",
|
||||
|
@ -37334,9 +37330,9 @@
|
|||
"from": "react-native-secure-key-store@https://github.com/BlueWallet/react-native-secure-key-store#2076b48"
|
||||
},
|
||||
"react-native-share": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.2.0.tgz",
|
||||
"integrity": "sha512-dn6FNoEADHdeAkBihIN4ewyxc5PO0MmvFzaSsTsLCwOs7VrX8rBNbGDE5iMP3Anz+SY7YInW2UuxVttBzii6wg=="
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.2.1.tgz",
|
||||
"integrity": "sha512-Z2LWGYWH7raM4H6Oauttv1tEhaB43XSWJAN8iS6oaSG9CnyrUBeYFF4QpU1AH5RgNeylXQdN8CtbizCHHt6coQ=="
|
||||
},
|
||||
"react-native-size-matters": {
|
||||
"version": "0.3.1",
|
||||
|
@ -37344,9 +37340,9 @@
|
|||
"integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw=="
|
||||
},
|
||||
"react-native-svg": {
|
||||
"version": "13.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.14.0.tgz",
|
||||
"integrity": "sha512-27ZnxUkHgWICimhuj6MuqBkISN53lVvgWJB7pIypjXysAyM+nqgQBPh4vXg+7MbqLBoYvR4PiBgKfwwGAqVxHg==",
|
||||
"version": "13.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.14.1.tgz",
|
||||
"integrity": "sha512-0DSa0EOySzV0J9utmRFVE5Vr4mKRXA7GtH1Ga1B6fzR967HGFW1ytH6hmnOf36316hjYpXMMB7s4oVe1FqghlQ==",
|
||||
"requires": {
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^1.1.3"
|
||||
|
@ -37420,10 +37416,6 @@
|
|||
"lodash.sortby": "^4.7.0"
|
||||
}
|
||||
},
|
||||
"react-native-widget-center": {
|
||||
"version": "git+ssh://git@github.com/BlueWallet/react-native-widget-center.git#a128c389526d55afdd67937494f2fec224dd0009",
|
||||
"from": "react-native-widget-center@https://github.com/BlueWallet/react-native-widget-center#a128c38"
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
|
@ -37517,9 +37509,9 @@
|
|||
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
|
||||
},
|
||||
"realm": {
|
||||
"version": "12.8.0",
|
||||
"resolved": "https://registry.npmjs.org/realm/-/realm-12.8.0.tgz",
|
||||
"integrity": "sha512-U1w5+ncyURQFQTrshoGn3KV+pzR1rQlPT7s3Sw6HPIPVBH80EWU3mirwvqp6RQ+Qi32ctRrBMTNeGd5mzAyiSw==",
|
||||
"version": "12.8.1",
|
||||
"resolved": "https://registry.npmjs.org/realm/-/realm-12.8.1.tgz",
|
||||
"integrity": "sha512-+zj2bvU0EACXvPBdvRsp2TybHPqPtftciTXsAlhrTWMiaoqC8FO6lriPbUs/JwsXz1w9otJXl5kXRBghPQHgLQ==",
|
||||
"requires": {
|
||||
"@realm/fetch": "^0.1.1",
|
||||
"bson": "^4.7.2",
|
||||
|
|
15
package.json
15
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bluewallet",
|
||||
"version": "6.6.6",
|
||||
"version": "6.6.7",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
|
||||
"ios": "react-native run-ios",
|
||||
"postinstall": "rn-nodeify --install buffer,events,process,stream,inherits,path,assert,crypto --hack; npm run releasenotes2json; npm run branch2json; npm run patches",
|
||||
"patches": "patch -p1 < scripts/rn-ldk.patch; patch -p1 < scripts/react-native-camera-kit.patch; patch -p1 < scripts/react-native-widget-center.patch",
|
||||
"patches": "patch -p1 < scripts/rn-ldk.patch; patch -p1 < scripts/react-native-camera-kit.patch;",
|
||||
"test": "npm run tslint && npm run lint && npm run unit && npm run jest",
|
||||
"jest": "jest -b tests/integration/*",
|
||||
"e2e:debug-build": "detox build -c android.debug",
|
||||
|
@ -148,7 +148,7 @@
|
|||
"react-native-camera-kit": "13.0.0",
|
||||
"react-native-crypto": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.13.2",
|
||||
"react-native-device-info": "10.14.0",
|
||||
"react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4",
|
||||
"react-native-elements": "3.4.3",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"react-native-gesture-handler": "2.16.2",
|
||||
"react-native-handoff": "https://github.com/BlueWallet/react-native-handoff#31d005f93d31099d0e564590a3bbd052b8a02b39",
|
||||
"react-native-haptic-feedback": "2.2.0",
|
||||
"react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b",
|
||||
"react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#7300b637c465c86e8db874c442e687950111da40",
|
||||
"react-native-image-picker": "7.1.2",
|
||||
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3",
|
||||
"react-native-keychain": "8.2.0",
|
||||
|
@ -177,14 +177,13 @@
|
|||
"react-native-safe-area-context": "4.10.1",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48",
|
||||
"react-native-share": "10.2.0",
|
||||
"react-native-svg": "13.14.0",
|
||||
"react-native-share": "10.2.1",
|
||||
"react-native-svg": "13.14.1",
|
||||
"react-native-tcp-socket": "6.0.6",
|
||||
"react-native-vector-icons": "10.1.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
|
||||
"readable-stream": "3.6.2",
|
||||
"realm": "12.8.0",
|
||||
"realm": "12.8.1",
|
||||
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
|
||||
"rn-nodeify": "10.3.0",
|
||||
"scryptsy": "2.1.0",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useCallback, useContext, useEffect, useReducer, useRef } from 'react';
|
||||
import { View, Image, ActivityIndicator, NativeModules, StyleSheet } from 'react-native';
|
||||
import Biometric, { BiometricType } from '../class/biometrics';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
|
||||
import { View, Image, ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import { useStorage } from '../blue_modules/storage-context';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
import SafeArea from '../components/SafeArea';
|
||||
import { BlueTextCentered } from '../BlueComponents';
|
||||
import loc from '../loc';
|
||||
import Button from '../components/Button';
|
||||
import { BiometricType, useBiometrics } from '../hooks/useBiometrics';
|
||||
|
||||
enum AuthType {
|
||||
Encrypted,
|
||||
|
@ -49,12 +49,11 @@ function reducer(state: State, action: Action): State {
|
|||
}
|
||||
}
|
||||
|
||||
const { SplashScreen } = NativeModules;
|
||||
|
||||
const UnlockWith: React.FC = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const isUnlockingWallets = useRef(false);
|
||||
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useContext(BlueStorageContext);
|
||||
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useStorage();
|
||||
const { deviceBiometricType, unlockWithBiometrics, isBiometricUseCapableAndEnabled, isBiometricUseEnabled } = useBiometrics();
|
||||
|
||||
const successfullyAuthenticated = useCallback(() => {
|
||||
setWalletsInitialized(true);
|
||||
|
@ -62,19 +61,20 @@ const UnlockWith: React.FC = () => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const unlockWithBiometrics = useCallback(async () => {
|
||||
const unlockUsingBiometrics = useCallback(async () => {
|
||||
if (isUnlockingWallets.current || state.isAuthenticating) return;
|
||||
isUnlockingWallets.current = true;
|
||||
dispatch({ type: SET_IS_AUTHENTICATING, payload: true });
|
||||
|
||||
if (await Biometric.unlockWithBiometrics()) {
|
||||
if (await unlockWithBiometrics()) {
|
||||
await startAndDecrypt();
|
||||
successfullyAuthenticated();
|
||||
}
|
||||
|
||||
dispatch({ type: SET_IS_AUTHENTICATING, payload: false });
|
||||
isUnlockingWallets.current = false;
|
||||
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.isAuthenticating]);
|
||||
|
||||
const unlockWithKey = useCallback(async () => {
|
||||
if (isUnlockingWallets.current || state.isAuthenticating) return;
|
||||
|
@ -91,18 +91,16 @@ const UnlockWith: React.FC = () => {
|
|||
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
SplashScreen?.dismissSplashScreen();
|
||||
|
||||
const startUnlock = async () => {
|
||||
const storageIsEncrypted = await isStorageEncrypted();
|
||||
const isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const biometricType = isBiometricUseCapableAndEnabled ? await Biometric.biometricType() : undefined;
|
||||
const biometricsUseEnabled = await Biometric.isBiometricUseEnabled();
|
||||
const biometricUseCapableAndEnabled = await isBiometricUseCapableAndEnabled();
|
||||
const biometricsUseEnabled = await isBiometricUseEnabled();
|
||||
const biometricType = biometricUseCapableAndEnabled ? deviceBiometricType : undefined;
|
||||
|
||||
if (storageIsEncrypted) {
|
||||
dispatch({ type: SET_AUTH, payload: { type: AuthType.Encrypted, detail: undefined } });
|
||||
unlockWithKey();
|
||||
} else if (isBiometricUseCapableAndEnabled) {
|
||||
} else if (biometricUseCapableAndEnabled) {
|
||||
dispatch({ type: SET_AUTH, payload: { type: AuthType.Biometrics, detail: biometricType } });
|
||||
unlockWithBiometrics();
|
||||
} else if (biometricsUseEnabled && biometricType === undefined) {
|
||||
|
@ -120,7 +118,7 @@ const UnlockWith: React.FC = () => {
|
|||
|
||||
const onUnlockPressed = () => {
|
||||
if (state.auth.type === AuthType.Biometrics) {
|
||||
unlockWithBiometrics();
|
||||
unlockUsingBiometrics();
|
||||
} else {
|
||||
unlockWithKey();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { BlueLoading, BlueDismissKeyboardInputAccessory, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import AddressInput from '../../components/AddressInput';
|
||||
import AmountInput from '../../components/AmountInput';
|
||||
|
@ -11,13 +11,13 @@ import loc from '../../loc';
|
|||
import { HDSegwitBech32Wallet, LightningLdkWallet } from '../../class';
|
||||
import { ArrowPicker } from '../../components/ArrowPicker';
|
||||
import { Psbt } from 'bitcoinjs-lib';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import Button from '../../components/Button';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
type LdkOpenChannelProps = RouteProp<
|
||||
{
|
||||
|
@ -33,8 +33,8 @@ type LdkOpenChannelProps = RouteProp<
|
|||
>;
|
||||
|
||||
const LdkOpenChannel = (props: any) => {
|
||||
const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
|
||||
const [isBiometricUseCapableAndEnabled, setIsBiometricUseCapableAndEnabled] = useState(false);
|
||||
const { wallets, fetchAndSaveWalletTransactions } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const { colors }: { colors: any } = useTheme();
|
||||
const { navigate, setParams } = useNavigation();
|
||||
const {
|
||||
|
@ -75,14 +75,10 @@ const LdkOpenChannel = (props: any) => {
|
|||
})();
|
||||
}, [psbt]);
|
||||
|
||||
useEffect(() => {
|
||||
Biometric.isBiometricUseCapableAndEnabled().then(setIsBiometricUseCapableAndEnabled);
|
||||
}, []);
|
||||
|
||||
const finalizeOpenChannel = async () => {
|
||||
setIsLoading(true);
|
||||
if (isBiometricUseCapableAndEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { I18nManager, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
|
@ -8,8 +8,7 @@ import AmountInput from '../../components/AmountInput';
|
|||
import Lnurl from '../../class/lnurl';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import loc, { formatBalanceWithoutSuffix, formatBalance } from '../../loc';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import Button from '../../components/Button';
|
||||
|
@ -17,6 +16,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
|
|||
import SafeArea from '../../components/SafeArea';
|
||||
import { btcToSatoshi, fiatToBTC, satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
/**
|
||||
* if user has default currency - fiat, attempting to pay will trigger conversion from entered in input field fiat value
|
||||
|
@ -26,7 +26,8 @@ import prompt from '../../helpers/prompt';
|
|||
const _cacheFiatToSat = {};
|
||||
|
||||
const LnurlPay = () => {
|
||||
const { wallets } = useContext(BlueStorageContext);
|
||||
const { wallets } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const { walletID, lnurl } = useRoute().params;
|
||||
/** @type {LightningCustodianWallet} */
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
|
@ -105,9 +106,9 @@ const LnurlPay = () => {
|
|||
/** @type {Lnurl} */
|
||||
const LN = _LN;
|
||||
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Text,
|
||||
ActivityIndicator,
|
||||
|
@ -12,24 +12,24 @@ import {
|
|||
} from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
||||
|
||||
import { BlueCard, BlueDismissKeyboardInputAccessory, BlueLoading } from '../../BlueComponents';
|
||||
import AddressInput from '../../components/AddressInput';
|
||||
import AmountInput from '../../components/AmountInput';
|
||||
import Lnurl from '../../class/lnurl';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import loc, { formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import Button from '../../components/Button';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const ScanLndInvoice = () => {
|
||||
const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
|
||||
const { wallets, fetchAndSaveWalletTransactions } = useStorage();
|
||||
const { unlockWithBiometrics, isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { colors } = useTheme();
|
||||
const { walletID, uri, invoice } = useRoute().params;
|
||||
const name = useRoute().name;
|
||||
|
@ -167,10 +167,10 @@ const ScanLndInvoice = () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
|
|||
import SafeArea from '../../components/SafeArea';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { isTablet } from 'react-native-device-info';
|
||||
import { isTablet } from '../../blue_modules/environment';
|
||||
|
||||
const BROADCAST_RESULT = Object.freeze({
|
||||
none: 'Input transaction hex',
|
||||
|
@ -117,7 +117,7 @@ const Broadcast: React.FC = () => {
|
|||
|
||||
return (
|
||||
<SafeArea>
|
||||
<KeyboardAvoidingView enabled={!isTablet()} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
|
||||
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
|
||||
<View style={styles.wrapper} testID="BroadcastView">
|
||||
{BROADCAST_RESULT.success !== broadcastResult && (
|
||||
<BlueCard style={styles.mainCard}>
|
||||
|
|
|
@ -5,11 +5,9 @@ import { PayjoinClient } from 'payjoin-client';
|
|||
import PropTypes from 'prop-types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
|
||||
import PayjoinTransaction from '../../class/payjoin-transaction';
|
||||
import { BlueText, BlueCard } from '../../BlueComponents';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
|
@ -21,10 +19,11 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
|
|||
import SafeArea from '../../components/SafeArea';
|
||||
import { satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency';
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const Confirm = () => {
|
||||
const { wallets, fetchAndSaveWalletTransactions, isElectrumDisabled } = useContext(BlueStorageContext);
|
||||
const [isBiometricUseCapableAndEnabled, setIsBiometricUseCapableAndEnabled] = useState(false);
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const { params } = useRoute();
|
||||
const { recipients = [], walletID, fee, memo, tx, satoshiPerByte, psbt } = params;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
@ -64,7 +63,6 @@ const Confirm = () => {
|
|||
useEffect(() => {
|
||||
console.log('send/confirm - useEffect');
|
||||
console.log('address = ', recipients);
|
||||
Biometric.isBiometricUseCapableAndEnabled().then(setIsBiometricUseCapableAndEnabled);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
@ -77,8 +75,8 @@ const Confirm = () => {
|
|||
testID="TransactionDetailsButton"
|
||||
style={[styles.txDetails, stylesHook.txDetails]}
|
||||
onPress={async () => {
|
||||
if (isBiometricUseCapableAndEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +97,7 @@ const Confirm = () => {
|
|||
),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [colors, fee, feeSatoshi, isBiometricUseCapableAndEnabled, memo, recipients, satoshiPerByte, tx, wallet]);
|
||||
}, [colors, fee, feeSatoshi, memo, recipients, satoshiPerByte, tx, wallet]);
|
||||
|
||||
/**
|
||||
* we need to look into `recipients`, find destination address and return its outputScript
|
||||
|
@ -163,8 +161,8 @@ const Confirm = () => {
|
|||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
|
||||
if (isBiometricUseCapableAndEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ const SendDetails = () => {
|
|||
// we need to re-calculate fees if user opens-closes coin control
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setIsLoading(false);
|
||||
setDumb(v => !v);
|
||||
}, []),
|
||||
);
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useIsFocused, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, Linking, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
|
||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import { DynamicQRCode } from '../../components/DynamicQRCode';
|
||||
|
@ -20,9 +18,12 @@ import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
|||
import loc from '../../loc';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const PsbtWithHardwareWallet = () => {
|
||||
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useContext(BlueStorageContext);
|
||||
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
const { fromWallet, memo, psbt, deepLinkPSBT, launchedBy } = route.params;
|
||||
|
@ -116,10 +117,10 @@ const PsbtWithHardwareWallet = () => {
|
|||
|
||||
const broadcast = async () => {
|
||||
setIsLoading(true);
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -123,24 +123,24 @@ const SettingsPrivacy: React.FC = () => {
|
|||
</Pressable>
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
{!storageIsEncrypted && (
|
||||
<>
|
||||
<ListItem
|
||||
hideChevron
|
||||
title={loc.settings.privacy_quickactions}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{
|
||||
onValueChange: onQuickActionsValueChange,
|
||||
value: isQuickActionsEnabled,
|
||||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
testID: 'QuickActionsSwitch',
|
||||
}}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_quickactions_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
</>
|
||||
)}
|
||||
<ListItem
|
||||
hideChevron
|
||||
title={loc.settings.privacy_quickactions}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{
|
||||
onValueChange: onQuickActionsValueChange,
|
||||
value: storageIsEncrypted ? false : isQuickActionsEnabled,
|
||||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||
testID: 'QuickActionsSwitch',
|
||||
}}
|
||||
/>
|
||||
{}
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_quickactions_explanation}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
||||
</BlueCard>
|
||||
|
||||
<ListItem
|
||||
hideChevron
|
||||
title={loc.settings.privacy_do_not_track}
|
||||
|
@ -150,7 +150,7 @@ const SettingsPrivacy: React.FC = () => {
|
|||
<BlueCard>
|
||||
<BlueText>{loc.settings.privacy_do_not_track_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
{Platform.OS === 'ios' && !storageIsEncrypted && (
|
||||
{Platform.OS === 'ios' && (
|
||||
<>
|
||||
<BlueSpacing40 />
|
||||
<Text adjustsFontSizeToFit style={[styles.widgetsHeader, styleHooks.widgetsHeader]}>
|
||||
|
@ -162,12 +162,14 @@ const SettingsPrivacy: React.FC = () => {
|
|||
Component={TouchableWithoutFeedback}
|
||||
switch={{
|
||||
onValueChange: onWidgetsTotalBalanceValueChange,
|
||||
value: isWidgetBalanceDisplayAllowed,
|
||||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
value: storageIsEncrypted ? false : isWidgetBalanceDisplayAllowed,
|
||||
disabled: isLoading === SettingsPrivacySection.All || storageIsEncrypted,
|
||||
}}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.total_balance_explanation}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
{storageIsEncrypted && <BlueText>{loc.settings.encrypted_feature_disabled}</BlueText>}
|
||||
</BlueCard>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -28,14 +28,14 @@ import {
|
|||
BlueDismissKeyboardInputAccessory,
|
||||
} from '../../BlueComponents';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import { reloadAllTimelines } from '../../components/WidgetCommunication';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import Button from '../../components/Button';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import { navigationRef } from '../../NavigationService';
|
||||
|
||||
export default class ElectrumSettings extends Component {
|
||||
static contextType = BlueStorageContext;
|
||||
|
@ -169,7 +169,6 @@ export default class ElectrumSettings extends Component {
|
|||
await DefaultPreference.clear(BlueElectrum.ELECTRUM_HOST);
|
||||
await DefaultPreference.clear(BlueElectrum.ELECTRUM_SSL_PORT);
|
||||
await DefaultPreference.clear(BlueElectrum.ELECTRUM_TCP_PORT);
|
||||
reloadAllTimelines();
|
||||
} catch (e) {
|
||||
// Must be running on Android
|
||||
console.log(e);
|
||||
|
@ -198,7 +197,6 @@ export default class ElectrumSettings extends Component {
|
|||
await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, host);
|
||||
await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, port);
|
||||
await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, sslPort);
|
||||
reloadAllTimelines();
|
||||
} catch (e) {
|
||||
// Must be running on Android
|
||||
console.log(e);
|
||||
|
@ -225,17 +223,9 @@ export default class ElectrumSettings extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
importScan = () => {
|
||||
requestCameraAuthorization().then(() =>
|
||||
this.props.navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: this.props.route.name,
|
||||
onBarScanned: this.onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
importScan = async () => {
|
||||
const scanned = await scanQrHelper(navigationRef.navigate, 'ElectrumSettings', true);
|
||||
this.onBarScanned(scanned);
|
||||
};
|
||||
|
||||
useSSLPortToggled = value => {
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import React, { useEffect, useState, useCallback, useContext } from 'react';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { View, ScrollView, Alert, TouchableOpacity, TouchableWithoutFeedback, Text, StyleSheet, Platform } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { BlueLoading, BlueSpacing20, BlueCard, BlueText } from '../../BlueComponents';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import loc from '../../loc';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const EncryptStorage = () => {
|
||||
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useContext(BlueStorageContext);
|
||||
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useStorage();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [biometrics, setBiometrics] = useState({ isDeviceBiometricCapable: false, isBiometricsEnabled: false, biometricsType: '' });
|
||||
const { isDeviceBiometricCapable, biometricEnabled, setBiometricUseEnabled, deviceBiometricType, unlockWithBiometrics } = useBiometrics();
|
||||
const [storageIsEncryptedSwitchEnabled, setStorageIsEncryptedSwitchEnabled] = useState(false);
|
||||
const [deviceBiometricCapable, setDeviceBiometricCapable] = useState(false);
|
||||
const { navigate, popToTop } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const styleHooks = StyleSheet.create({
|
||||
|
@ -28,12 +29,10 @@ const EncryptStorage = () => {
|
|||
});
|
||||
|
||||
const initialState = useCallback(async () => {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseEnabled();
|
||||
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
|
||||
const biometricsType = (await Biometric.biometricType()) || loc.settings.biometrics;
|
||||
const isStorageEncryptedSwitchEnabled = await isStorageEncrypted();
|
||||
const isDeviceBiometricCapableSync = await isDeviceBiometricCapable();
|
||||
setStorageIsEncryptedSwitchEnabled(isStorageEncryptedSwitchEnabled);
|
||||
setBiometrics({ isBiometricsEnabled, isDeviceBiometricCapable, biometricsType });
|
||||
setDeviceBiometricCapable(isDeviceBiometricCapableSync);
|
||||
setIsLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
@ -107,15 +106,8 @@ const EncryptStorage = () => {
|
|||
};
|
||||
|
||||
const onUseBiometricSwitch = async value => {
|
||||
const isBiometricsEnabled = {
|
||||
isDeviceBiometricCapable: biometrics.isDeviceBiometricCapable,
|
||||
isBiometricsEnabled: biometrics.isBiometricsEnabled,
|
||||
biometricsType: biometrics.biometricsType,
|
||||
};
|
||||
if (await Biometric.unlockWithBiometrics()) {
|
||||
isBiometricsEnabled.isBiometricsEnabled = value;
|
||||
await Biometric.setBiometricUseEnabled(value);
|
||||
setBiometrics(isBiometricsEnabled);
|
||||
if (await unlockWithBiometrics()) {
|
||||
setBiometricUseEnabled(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -135,7 +127,7 @@ const EncryptStorage = () => {
|
|||
return isCapable ? (
|
||||
<>
|
||||
<BlueText />
|
||||
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: biometrics.biometricsType })}</BlueText>
|
||||
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType })}</BlueText>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
@ -147,18 +139,18 @@ const EncryptStorage = () => {
|
|||
) : (
|
||||
<ScrollView contentContainerStyle={styles.root} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
|
||||
<View style={styles.paddingTop} />
|
||||
{biometrics.isDeviceBiometricCapable && (
|
||||
{deviceBiometricCapable && (
|
||||
<>
|
||||
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
|
||||
{loc.settings.biometrics}
|
||||
</Text>
|
||||
<ListItem
|
||||
title={loc.formatString(loc.settings.encrypt_use, { type: biometrics.biometricsType })}
|
||||
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType })}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ value: biometrics.isBiometricsEnabled, onValueChange: onUseBiometricSwitch }}
|
||||
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch }}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: biometrics.biometricsType })}</BlueText>
|
||||
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType })}</BlueText>
|
||||
{renderPasscodeExplanation()}
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
|
|
|
@ -23,6 +23,22 @@ interface TransactionDetailsProps {
|
|||
navigation: NativeStackNavigationProp<any>;
|
||||
}
|
||||
|
||||
const actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
GoToWallet: 'goToWallet',
|
||||
};
|
||||
|
||||
const actionIcons = {
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
GoToWallet: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'wallet.pass',
|
||||
},
|
||||
};
|
||||
|
||||
function onlyUnique(value: any, index: number, self: any[]) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
@ -37,6 +53,14 @@ function arrDiff(a1: any[], a2: any[]) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
const toolTipMenuActions = [
|
||||
{
|
||||
id: actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.copy_link,
|
||||
icon: actionIcons.Clipboard,
|
||||
},
|
||||
];
|
||||
|
||||
const TransactionDetails = () => {
|
||||
const { setOptions, navigate } = useNavigation();
|
||||
const { hash, walletID } = useRoute<TransactionDetailsProps['route']>().params;
|
||||
|
@ -162,9 +186,7 @@ const TransactionDetails = () => {
|
|||
};
|
||||
|
||||
const handleCopyPress = (stringToCopy: string) => {
|
||||
Clipboard.setString(
|
||||
stringToCopy !== TransactionDetails.actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`,
|
||||
);
|
||||
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`);
|
||||
};
|
||||
|
||||
if (isLoading || !tx) {
|
||||
|
@ -189,9 +211,9 @@ const TransactionDetails = () => {
|
|||
};
|
||||
|
||||
const onPressMenuItem = (key: string) => {
|
||||
if (key === TransactionDetails.actionKeys.CopyToClipboard) {
|
||||
if (key === actionKeys.CopyToClipboard) {
|
||||
handleCopyPress(key);
|
||||
} else if (key === TransactionDetails.actionKeys.GoToWallet) {
|
||||
} else if (key === actionKeys.GoToWallet) {
|
||||
const wallet = weOwnAddress(key);
|
||||
if (wallet) {
|
||||
navigateToWallet(wallet);
|
||||
|
@ -205,16 +227,16 @@ const TransactionDetails = () => {
|
|||
for (const [index, address] of array.entries()) {
|
||||
const actions = [];
|
||||
actions.push({
|
||||
id: TransactionDetails.actionKeys.CopyToClipboard,
|
||||
id: actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: TransactionDetails.actionIcons.Clipboard,
|
||||
icon: actionIcons.Clipboard,
|
||||
});
|
||||
const isWeOwnAddress = weOwnAddress(address);
|
||||
if (isWeOwnAddress) {
|
||||
actions.push({
|
||||
id: TransactionDetails.actionKeys.GoToWallet,
|
||||
id: actionKeys.GoToWallet,
|
||||
text: loc.formatString(loc.transactions.view_wallet, { walletLabel: isWeOwnAddress.getLabel() }),
|
||||
icon: TransactionDetails.actionIcons.GoToWallet,
|
||||
icon: actionIcons.GoToWallet,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -320,16 +342,10 @@ const TransactionDetails = () => {
|
|||
)}
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
actions={[
|
||||
{
|
||||
id: TransactionDetails.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.copy_link,
|
||||
icon: TransactionDetails.actionIcons.Clipboard,
|
||||
},
|
||||
]}
|
||||
actions={toolTipMenuActions}
|
||||
onPressMenuItem={handleCopyPress}
|
||||
onPress={handleOnOpenTransactionOnBlockExplorerTapped}
|
||||
buttonStyle={[styles.greyButton, stylesHooks.greyButton]}
|
||||
buttonStyle={StyleSheet.flatten([styles.greyButton, stylesHooks.greyButton])}
|
||||
>
|
||||
<Text style={[styles.Link, stylesHooks.Link]}>{loc.transactions.details_show_in_block_explorer}</Text>
|
||||
</ToolTipMenu>
|
||||
|
@ -338,22 +354,6 @@ const TransactionDetails = () => {
|
|||
);
|
||||
};
|
||||
|
||||
TransactionDetails.actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
GoToWallet: 'goToWallet',
|
||||
};
|
||||
|
||||
TransactionDetails.actionIcons = {
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
GoToWallet: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'wallet.pass',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scroll: {
|
||||
flex: 1,
|
||||
|
|
259
screen/wallets/PaymentCodesList.tsx
Normal file
259
screen/wallets/PaymentCodesList.tsx
Normal file
|
@ -0,0 +1,259 @@
|
|||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { SectionList, StyleSheet, Text, View } from 'react-native';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import createHash from 'create-hash';
|
||||
import Button from '../../components/Button';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { ContactList } from '../../class/contact-list';
|
||||
import assert from 'assert';
|
||||
import { HDSegwitBech32Wallet } from '../../class';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import confirm from '../../helpers/confirm';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { satoshiToLocalCurrency } from '../../blue_modules/currency';
|
||||
import { BlueLoading } from '../../BlueComponents';
|
||||
import { PaymentCodeStackParamList } from '../../navigation/PaymentCodeStack';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { Action } from '../../components/types';
|
||||
|
||||
interface DataSection {
|
||||
title: string;
|
||||
data: string[];
|
||||
}
|
||||
enum Actions {
|
||||
pay,
|
||||
rename,
|
||||
copyToClipboard,
|
||||
}
|
||||
|
||||
const actionKeys: Action[] = [
|
||||
{
|
||||
id: Actions.pay,
|
||||
text: loc.bip47.pay_this_contact,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: Actions.rename,
|
||||
text: loc.bip47.rename_contact,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'note.text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: Actions.copyToClipboard,
|
||||
text: loc.bip47.copy_payment_code,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
type Props = NativeStackScreenProps<PaymentCodeStackParamList, 'PaymentCodesList'>;
|
||||
|
||||
function onlyUnique(value: any, index: number, self: any[]) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export default function PaymentCodesList() {
|
||||
const route = useRoute();
|
||||
const { walletID } = route.params as Props['route']['params'];
|
||||
const { wallets, txMetadata, counterpartyMetadata, saveToDisk } = useContext(BlueStorageContext);
|
||||
const [reload, setReload] = useState<number>(0);
|
||||
const [data, setData] = useState<DataSection[]>([]);
|
||||
const { colors } = useTheme();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [loadingText, setLoadingText] = useState<string>('Loading...');
|
||||
|
||||
useEffect(() => {
|
||||
if (!walletID) return;
|
||||
|
||||
const foundWallet = wallets.find(w => w.getID() === walletID) as unknown as AbstractHDElectrumWallet;
|
||||
if (!foundWallet) return;
|
||||
|
||||
const newData: DataSection[] = [
|
||||
{
|
||||
title: '',
|
||||
data: foundWallet.getBIP47SenderPaymentCodes().concat(foundWallet.getBIP47ReceiverPaymentCodes()).filter(onlyUnique),
|
||||
},
|
||||
];
|
||||
setData(newData);
|
||||
}, [walletID, wallets, reload]);
|
||||
|
||||
const toolTipActions = useMemo(() => actionKeys, []);
|
||||
|
||||
const shortenContactName = (name: string): string => {
|
||||
if (name.length < 20) return name;
|
||||
return name.substr(0, 10) + '...' + name.substr(name.length - 10, 10);
|
||||
};
|
||||
|
||||
const onToolTipPress = async (id: any, pc: string) => {
|
||||
if (String(id) === String(Actions.copyToClipboard)) {
|
||||
Clipboard.setString(pc);
|
||||
presentAlert({ message: loc.bip47.copied });
|
||||
}
|
||||
|
||||
if (String(id) === String(Actions.rename)) {
|
||||
const newName = await prompt(loc.bip47.rename, loc.bip47.provide_name, false, 'plain-text');
|
||||
if (!newName) return;
|
||||
|
||||
counterpartyMetadata[pc] = { label: newName };
|
||||
setReload(Math.random());
|
||||
}
|
||||
|
||||
if (String(id) === String(Actions.pay)) {
|
||||
presentAlert({ message: 'Not implemented yet' });
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = (pc: string) => {
|
||||
const color = createHash('sha256').update(pc).digest().toString('hex').substring(0, 6);
|
||||
|
||||
const displayName = shortenContactName(counterpartyMetadata?.[pc]?.label ?? pc);
|
||||
|
||||
return (
|
||||
<ToolTipMenu
|
||||
actions={toolTipActions}
|
||||
onPressMenuItem={(item: any) => onToolTipPress(item, pc)}
|
||||
isButton={true}
|
||||
isMenuPrimaryAction={true}
|
||||
>
|
||||
<View style={styles.contactRowContainer}>
|
||||
<View style={[styles.circle, { backgroundColor: '#' + color }]} />
|
||||
<View style={styles.contactRowBody}>
|
||||
<Text style={[styles.contactRowNameText, { color: colors.shadowColor }]}>{displayName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.stick} />
|
||||
</ToolTipMenu>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddContactPress = async () => {
|
||||
try {
|
||||
const foundWallet = wallets.find(w => w.getID() === walletID) as unknown as HDSegwitBech32Wallet;
|
||||
assert(foundWallet);
|
||||
|
||||
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, false, 'plain-text');
|
||||
if (!newPc) return;
|
||||
const cl = new ContactList(foundWallet);
|
||||
|
||||
if (!cl.isPaymentCodeValid(newPc)) {
|
||||
presentAlert({ message: loc.bip47.invalid_pc });
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const notificationTx = foundWallet.getBIP47NotificationTransaction(newPc);
|
||||
|
||||
if (notificationTx && notificationTx.confirmations > 0) {
|
||||
// we previously sent notification transaction to him, so just need to add him to internals
|
||||
foundWallet.addBIP47Receiver(newPc);
|
||||
await foundWallet.syncBip47ReceiversAddresses(newPc); // so we can unwrap and save all his possible addresses
|
||||
// (for a case if already have txs with him, we will now be able to label them on tx list)
|
||||
await saveToDisk();
|
||||
setReload(Math.random());
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationTx && notificationTx.confirmations === 0) {
|
||||
// for a rare case when we just sent the confirmation tx and it havent confirmed yet
|
||||
presentAlert({ message: loc.bip47.notification_tx_unconfirmed });
|
||||
return;
|
||||
}
|
||||
|
||||
// need to send notif tx:
|
||||
|
||||
setLoadingText('Fetching UTXO...');
|
||||
await foundWallet.fetchUtxo();
|
||||
setLoadingText('Fetching fees...');
|
||||
const fees = await BlueElectrum.estimateFees();
|
||||
setLoadingText('Fetching change address...');
|
||||
const changeAddress = await foundWallet.getChangeAddressAsync();
|
||||
setLoadingText('Crafting notification transaction...');
|
||||
const { tx, fee } = foundWallet.createBip47NotificationTransaction(foundWallet.getUtxo(), newPc, fees.fast, changeAddress);
|
||||
|
||||
if (!tx) {
|
||||
presentAlert({ message: loc.bip47.failed_create_notif_tx });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingText('');
|
||||
if (
|
||||
await confirm(
|
||||
loc.bip47.onchain_tx_needed,
|
||||
`${loc.send.create_fee}: ${formatBalance(fee, BitcoinUnit.BTC)} (${satoshiToLocalCurrency(fee)}). `,
|
||||
)
|
||||
) {
|
||||
setLoadingText('Broadcasting...');
|
||||
try {
|
||||
await foundWallet.broadcastTx(tx.toHex());
|
||||
foundWallet.addBIP47Receiver(newPc);
|
||||
presentAlert({ message: loc.bip47.notif_tx_sent });
|
||||
txMetadata[tx.getId()] = { memo: loc.bip47.notif_tx };
|
||||
setReload(Math.random());
|
||||
await new Promise(resolve => setTimeout(resolve, 5000)); // tx propagate on backend so our fetch will actually get the new tx
|
||||
} catch (_) {}
|
||||
setLoadingText('Fetching transactions...');
|
||||
await foundWallet.fetchTransactions();
|
||||
}
|
||||
} catch (error: any) {
|
||||
presentAlert({ message: error.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<BlueLoading />
|
||||
<Text>{loadingText}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{!walletID ? (
|
||||
<Text>Internal error</Text>
|
||||
) : (
|
||||
<View style={styles.sectionListContainer}>
|
||||
<SectionList sections={data} keyExtractor={(item, index) => item + index} renderItem={({ item }) => renderItem(item)} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Button title={loc.bip47.add_contact} onPress={onAddContactPress} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
sectionListContainer: { flex: 1, width: '100%' },
|
||||
circle: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderRadius: 25,
|
||||
},
|
||||
contactRowBody: { flex: 6, justifyContent: 'center', top: -3 },
|
||||
contactRowNameText: { marginLeft: 10, fontSize: 16 },
|
||||
contactRowContainer: { flexDirection: 'row', padding: 15 },
|
||||
stick: { borderStyle: 'solid', borderWidth: 0.5, borderColor: 'gray', opacity: 0.5, top: 0, left: -10, width: '110%' },
|
||||
});
|
|
@ -31,7 +31,6 @@ import * as NavigationService from '../../NavigationService';
|
|||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import { encodeUR } from '../../blue_modules/ur';
|
||||
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import BottomModal from '../../components/BottomModal';
|
||||
import Button from '../../components/Button';
|
||||
|
@ -52,11 +51,13 @@ import SaveFileButton from '../../components/SaveFileButton';
|
|||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { useSettings } from '../../components/Context/SettingsContext';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const hasLoaded = useRef(false);
|
||||
const { colors } = useTheme();
|
||||
const { wallets, setWalletsWithNewOrder, isElectrumDisabled } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const { navigate, dispatch, addListener } = useExtendedNavigation();
|
||||
const openScannerButtonRef = useRef();
|
||||
|
@ -173,10 +174,10 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
}
|
||||
setIsLoading(true);
|
||||
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
|
@ -20,7 +20,7 @@ import {
|
|||
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useStorage } from '../../blue_modules/storage-context';
|
||||
import {
|
||||
HDAezeedWallet,
|
||||
HDSegwitBech32Wallet,
|
||||
|
@ -31,7 +31,6 @@ import {
|
|||
SegwitP2SHWallet,
|
||||
WatchOnlyWallet,
|
||||
} from '../../class';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
|
||||
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
|
||||
import presentAlert from '../../components/Alert';
|
||||
|
@ -47,6 +46,7 @@ import SaveFileButton from '../../components/SaveFileButton';
|
|||
import { useSettings } from '../../components/Context/SettingsContext';
|
||||
import HeaderRightButton from '../../components/HeaderRightButton';
|
||||
import { writeFileAndExport } from '../../blue_modules/fs';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollViewContent: {
|
||||
|
@ -107,7 +107,8 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const WalletDetails = () => {
|
||||
const { saveToDisk, wallets, deleteWallet, setSelectedWalletID, txMetadata } = useContext(BlueStorageContext);
|
||||
const { saveToDisk, wallets, deleteWallet, setSelectedWalletID, txMetadata } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const { walletID } = useRoute().params;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [backdoorPressed, setBackdoorPressed] = useState(0);
|
||||
|
@ -402,10 +403,10 @@ const WalletDetails = () => {
|
|||
{
|
||||
text: loc.wallets.details_yes_delete,
|
||||
onPress: async () => {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { SectionList, StyleSheet, Text, View } from 'react-native';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import loc from '../../loc';
|
||||
import CopyTextToClipboard from '../../components/CopyTextToClipboard';
|
||||
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
|
||||
import { PaymentCodeStackParamList } from '../../navigation/PaymentCodeStack';
|
||||
|
||||
interface DataSection {
|
||||
title: string;
|
||||
data: string[];
|
||||
}
|
||||
|
||||
type Props = NativeStackScreenProps<PaymentCodeStackParamList, 'PaymentCodesList'>;
|
||||
|
||||
export default function PaymentCodesList() {
|
||||
const route = useRoute();
|
||||
const { walletID } = route.params as Props['route']['params'];
|
||||
const { wallets } = useContext(BlueStorageContext);
|
||||
const [data, setData] = useState<DataSection[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!walletID) return;
|
||||
|
||||
const foundWallet = wallets.find(w => w.getID() === walletID) as unknown as AbstractHDElectrumWallet;
|
||||
if (!foundWallet) return;
|
||||
|
||||
const newData: DataSection[] = [
|
||||
{
|
||||
title: loc.bip47.who_can_pay_me,
|
||||
data: foundWallet.getBIP47SenderPaymentCodes(),
|
||||
},
|
||||
{
|
||||
title: loc.bip47.whom_can_i_pay,
|
||||
data: foundWallet.getBIP47ReceiverPaymentCodes(),
|
||||
},
|
||||
];
|
||||
setData(newData);
|
||||
}, [walletID, wallets]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{!walletID ? (
|
||||
<Text>Internal error</Text>
|
||||
) : (
|
||||
<View>
|
||||
<SectionList
|
||||
sections={data}
|
||||
keyExtractor={(item, index) => item + index}
|
||||
renderItem={({ item }) => (
|
||||
<View>
|
||||
<CopyTextToClipboard truncated text={item} />
|
||||
</View>
|
||||
)}
|
||||
renderSectionHeader={({ section: { title } }) => <Text style={styles.titleText}>{title}</Text>}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
titleText: { fontSize: 20 },
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
|
@ -24,9 +24,8 @@ import BlueClipboard from '../../blue_modules/clipboard';
|
|||
import { isDesktop } from '../../blue_modules/environment';
|
||||
import * as fs from '../../blue_modules/fs';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { BlueStorageContext, WalletTransactionsStatus } from '../../blue_modules/storage-context';
|
||||
import { WalletTransactionsStatus, useStorage } from '../../blue_modules/storage-context';
|
||||
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import WalletGradient from '../../class/wallet-gradient';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { FButton, FContainer } from '../../components/FloatButtons';
|
||||
|
@ -41,6 +40,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
|||
import loc from '../../loc';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
|
||||
const buttonFontSize =
|
||||
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
|
||||
|
@ -55,7 +55,8 @@ const WalletTransactions = ({ navigation }) => {
|
|||
walletTransactionUpdateStatus,
|
||||
isElectrumDisabled,
|
||||
setReloadTransactionsMenuActionFunction,
|
||||
} = useContext(BlueStorageContext);
|
||||
} = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { walletID } = useRoute().params;
|
||||
const { name } = useRoute();
|
||||
|
@ -485,10 +486,10 @@ const WalletTransactions = ({ navigation }) => {
|
|||
})
|
||||
}
|
||||
onWalletBalanceVisibilityChange={async isShouldBeVisible => {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (wallet.hideBalance && isBiometricsEnabled) {
|
||||
const unlocked = await Biometric.unlockWithBiometrics();
|
||||
const unlocked = await unlockWithBiometrics();
|
||||
if (!unlocked) {
|
||||
throw new Error('Biometrics failed');
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
--- ../node_modules/react-native-widget-center/android/build.gradle 2023-11-10 11:25:36
|
||||
+++ ../node_modules/react-native-widget-center/android/build.gradle 2023-11-10 11:25:42
|
||||
|
||||
@@ -103,12 +103,12 @@
|
||||
}
|
||||
|
||||
task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
|
||||
- classifier = 'javadoc'
|
||||
+ archiveClassifier.set('javadoc')
|
||||
from androidJavadoc.destinationDir
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
- classifier = 'sources'
|
||||
+ archiveClassifier.set('sources')
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
include '**/*.java'
|
||||
}
|
|
@ -164,12 +164,11 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||
assert.ok(walletSender.getTransactions().length >= 3);
|
||||
assert.ok(walletSender._receive_payment_codes.length === 1);
|
||||
|
||||
assert.strictEqual(walletSender.needToNotifyBIP47(bip47instanceReceiver.getSerializedPaymentCode()), false); // already notified in the past
|
||||
assert.strictEqual(
|
||||
walletSender.needToNotifyBIP47(
|
||||
assert.ok(walletSender.getBIP47NotificationTransaction(bip47instanceReceiver.getSerializedPaymentCode())); // already notified in the past
|
||||
assert.ok(
|
||||
!walletSender.getBIP47NotificationTransaction(
|
||||
'PM8TJdfXvRasx4WNpxky25ZKxhvfEiGYW9mka92tfiqDRSL7LQdxnC8uAk9k3okXctZowVwY2PUndjCQR6DHyuVVwqmy2aodmZNHgfFZcJRNTuBAXJCp',
|
||||
),
|
||||
true,
|
||||
); // random PC from interwebz. never interacted with him, so need to notify
|
||||
});
|
||||
|
||||
|
@ -201,7 +200,7 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||
); // sparrow paid us after sparrow made a notification tx
|
||||
|
||||
assert.ok(
|
||||
!w.needToNotifyBIP47(
|
||||
w.getBIP47NotificationTransaction(
|
||||
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
||||
),
|
||||
);
|
||||
|
@ -216,7 +215,7 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
||||
);
|
||||
assert.ok(
|
||||
!w.needToNotifyBIP47(
|
||||
w.getBIP47NotificationTransaction(
|
||||
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
||||
),
|
||||
); // dont need to notify
|
||||
|
@ -258,4 +257,51 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
||||
);
|
||||
});
|
||||
|
||||
it('can tell with which counterparty PC transaction is (sparrow)', async () => {
|
||||
if (!process.env.BIP47_HD_MNEMONIC) {
|
||||
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
const w = new HDSegwitBech32Wallet();
|
||||
w.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[1]);
|
||||
w.switchBIP47(true);
|
||||
|
||||
await w.fetchBIP47SenderPaymentCodes();
|
||||
await w.fetchBalance();
|
||||
await w.fetchTransactions();
|
||||
|
||||
assert.strictEqual(
|
||||
w.getTransactions().find(tx => tx.txid === '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')?.value,
|
||||
-102308,
|
||||
); // we paid samurai
|
||||
|
||||
assert.deepStrictEqual(w.getBIP47SenderPaymentCodes(), [
|
||||
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
||||
]); // samurai can pay us
|
||||
|
||||
assert.deepStrictEqual(w.getBIP47ReceiverPaymentCodes(), []); // we can pay no-one
|
||||
|
||||
assert.ok(!w.getBip47CounterpartyByTxid('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')); // dont know whom we paid
|
||||
|
||||
w.addBIP47Receiver(
|
||||
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
||||
);
|
||||
assert.deepStrictEqual(w.getBIP47ReceiverPaymentCodes(), [
|
||||
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
||||
]); // we can now pay samurai
|
||||
|
||||
// await w.fetchBIP47SenderPaymentCodes();
|
||||
// await w.fetchBalance();
|
||||
// await w.fetchTransactions();
|
||||
await w.syncBip47ReceiversAddresses(
|
||||
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
w.getBip47CounterpartyByTxid('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f'),
|
||||
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
||||
); // we paid samurai, and now we can seethat
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,6 +59,7 @@ jest.mock('react-native-device-info', () => {
|
|||
getDeviceType: jest.fn().mockReturnValue(false),
|
||||
hasGmsSync: jest.fn().mockReturnValue(true),
|
||||
hasHmsSync: jest.fn().mockReturnValue(false),
|
||||
isTablet: jest.fn().mockReturnValue(false),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -187,12 +188,6 @@ jest.mock('react-native-share', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../components/WidgetCommunication', () => {
|
||||
return {
|
||||
reloadAllTimelines: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockKeychain = {
|
||||
SECURITY_LEVEL_ANY: 'MOCK_SECURITY_LEVEL_ANY',
|
||||
SECURITY_LEVEL_SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
|
||||
|
@ -205,6 +200,6 @@ jest.mock('react-native-keychain', () => mockKeychain);
|
|||
|
||||
jest.mock('react-native-tcp-socket', () => mockKeychain);
|
||||
|
||||
jest.mock('../components/TooltipMenu.ios.js', () => require('../components/TooltipMenu.js'));
|
||||
jest.mock('../components/TooltipMenu.ios.tsx', () => require('../components/TooltipMenu.tsx'));
|
||||
|
||||
global.alert = () => {};
|
||||
|
|
19
tests/unit/contact-list.test.ts
Normal file
19
tests/unit/contact-list.test.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import assert from 'assert';
|
||||
|
||||
import { HDSegwitBech32Wallet } from '../../class';
|
||||
import { ContactList } from '../../class/contact-list';
|
||||
|
||||
describe('ContactList', () => {
|
||||
it('isPaymentCodeValid()', async () => {
|
||||
const w = new HDSegwitBech32Wallet();
|
||||
w.switchBIP47(true);
|
||||
const cl = new ContactList(w);
|
||||
|
||||
assert.ok(!cl.isPaymentCodeValid('sfdgsfdghsfd'));
|
||||
assert.ok(
|
||||
cl.isPaymentCodeValid(
|
||||
'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue