mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-21 14:34:55 +01:00
REF: Add Wallet advanced options (#7023)
This commit is contained in:
parent
4bc7e53a6e
commit
fdfb6d11cf
23 changed files with 586 additions and 454 deletions
|
@ -134,11 +134,11 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa
|
||||
path: ./ios/build/BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa
|
||||
path: ./build/BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa
|
||||
|
||||
testflight-upload:
|
||||
needs: build
|
||||
runs-on: macos-14
|
||||
runs-on: macos-latest
|
||||
if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'testflight')
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
|
@ -168,12 +168,12 @@ jobs:
|
|||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: BlueWallet.${{ needs.build.outputs.project_version }}(${{ needs.build.outputs.new_build_number }}).ipa
|
||||
path: ./ios/build
|
||||
path: ./
|
||||
- name: Create App Store Connect API Key JSON
|
||||
run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./ios/appstore_api_key.json
|
||||
run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json
|
||||
- name: Upload to TestFlight
|
||||
env:
|
||||
APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/ios/appstore_api_key.p8
|
||||
APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
|
||||
GIT_URL: ${{ secrets.GIT_URL }}
|
||||
|
|
|
@ -67,13 +67,12 @@ const isReactNative = typeof navigator !== 'undefined' && navigator?.product ===
|
|||
export class BlueApp {
|
||||
static FLAG_ENCRYPTED = 'data_encrypted';
|
||||
static LNDHUB = 'lndhub';
|
||||
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
|
||||
static DO_NOT_TRACK = 'donottrack';
|
||||
static HANDOFF_STORAGE_KEY = 'HandOff';
|
||||
|
||||
private static _instance: BlueApp | null = null;
|
||||
|
||||
static keys2migrate = [BlueApp.HANDOFF_STORAGE_KEY, BlueApp.DO_NOT_TRACK, BlueApp.ADVANCED_MODE_ENABLED];
|
||||
static keys2migrate = [BlueApp.HANDOFF_STORAGE_KEY, BlueApp.DO_NOT_TRACK];
|
||||
|
||||
public cachedPassword?: false | string;
|
||||
public tx_metadata: TTXMetadata;
|
||||
|
@ -882,17 +881,6 @@ export class BlueApp {
|
|||
return finalBalance;
|
||||
};
|
||||
|
||||
isAdvancedModeEnabled = async (): Promise<boolean> => {
|
||||
try {
|
||||
return !!(await AsyncStorage.getItem(BlueApp.ADVANCED_MODE_ENABLED));
|
||||
} catch (_) {}
|
||||
return false;
|
||||
};
|
||||
|
||||
setIsAdvancedModeEnabled = async (value: boolean) => {
|
||||
await AsyncStorage.setItem(BlueApp.ADVANCED_MODE_ENABLED, value ? '1' : '');
|
||||
};
|
||||
|
||||
isHandoffEnabled = async (): Promise<boolean> => {
|
||||
try {
|
||||
return !!(await AsyncStorage.getItem(BlueApp.HANDOFF_STORAGE_KEY));
|
||||
|
|
|
@ -69,8 +69,6 @@ interface SettingsContextType {
|
|||
setIsHandOffUseEnabledAsyncStorage: (value: boolean) => Promise<void>;
|
||||
isPrivacyBlurEnabled: boolean;
|
||||
setIsPrivacyBlurEnabledState: (value: boolean) => void;
|
||||
isAdvancedModeEnabled: boolean;
|
||||
setIsAdvancedModeEnabledStorage: (value: boolean) => Promise<void>;
|
||||
isDoNotTrackEnabled: boolean;
|
||||
setDoNotTrackStorage: (value: boolean) => Promise<void>;
|
||||
isWidgetBalanceDisplayAllowed: boolean;
|
||||
|
@ -96,8 +94,6 @@ const defaultSettingsContext: SettingsContextType = {
|
|||
setIsHandOffUseEnabledAsyncStorage: async () => {},
|
||||
isPrivacyBlurEnabled: true,
|
||||
setIsPrivacyBlurEnabledState: () => {},
|
||||
isAdvancedModeEnabled: false,
|
||||
setIsAdvancedModeEnabledStorage: async () => {},
|
||||
isDoNotTrackEnabled: false,
|
||||
setDoNotTrackStorage: async () => {},
|
||||
isWidgetBalanceDisplayAllowed: true,
|
||||
|
@ -125,8 +121,6 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
const [isHandOffUseEnabled, setHandOffUseEnabled] = useState<boolean>(false);
|
||||
// PrivacyBlur
|
||||
const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState<boolean>(true);
|
||||
// AdvancedMode
|
||||
const [isAdvancedModeEnabled, setIsAdvancedModeEnabled] = useState<boolean>(false);
|
||||
// DoNotTrack
|
||||
const [isDoNotTrackEnabled, setIsDoNotTrackEnabled] = useState<boolean>(false);
|
||||
// WidgetCommunication
|
||||
|
@ -141,19 +135,10 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
const [isTotalBalanceEnabled, setIsTotalBalanceEnabled] = useState<boolean>(true);
|
||||
const [totalBalancePreferredUnit, setTotalBalancePreferredUnitState] = useState<BitcoinUnit>(BitcoinUnit.BTC);
|
||||
|
||||
const advancedModeStorage = useAsyncStorage(BlueApp.ADVANCED_MODE_ENABLED);
|
||||
const languageStorage = useAsyncStorage(STORAGE_KEY);
|
||||
const { walletsInitialized } = useStorage();
|
||||
|
||||
useEffect(() => {
|
||||
advancedModeStorage
|
||||
.getItem()
|
||||
.then(advMode => {
|
||||
console.debug('SettingsContext advMode:', advMode);
|
||||
setIsAdvancedModeEnabled(advMode ? JSON.parse(advMode) : false);
|
||||
})
|
||||
.catch(error => console.error('Error fetching advanced mode settings:', error));
|
||||
|
||||
getIsHandOffUseEnabled()
|
||||
.then(handOff => {
|
||||
console.debug('SettingsContext handOff:', handOff);
|
||||
|
@ -243,14 +228,6 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setLanguage(newLanguage);
|
||||
}, []);
|
||||
|
||||
const setIsAdvancedModeEnabledStorage = useCallback(
|
||||
async (value: boolean) => {
|
||||
await advancedModeStorage.setItem(JSON.stringify(value));
|
||||
setIsAdvancedModeEnabled(value);
|
||||
},
|
||||
[advancedModeStorage],
|
||||
);
|
||||
|
||||
const setDoNotTrackStorage = useCallback(async (value: boolean) => {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
if (value) {
|
||||
|
@ -321,8 +298,6 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setIsHandOffUseEnabledAsyncStorage,
|
||||
isPrivacyBlurEnabled,
|
||||
setIsPrivacyBlurEnabledState,
|
||||
isAdvancedModeEnabled,
|
||||
setIsAdvancedModeEnabledStorage,
|
||||
isDoNotTrackEnabled,
|
||||
setDoNotTrackStorage,
|
||||
isWidgetBalanceDisplayAllowed,
|
||||
|
@ -347,8 +322,6 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
setIsHandOffUseEnabledAsyncStorage,
|
||||
isPrivacyBlurEnabled,
|
||||
setIsPrivacyBlurEnabledState,
|
||||
isAdvancedModeEnabled,
|
||||
setIsAdvancedModeEnabledStorage,
|
||||
isDoNotTrackEnabled,
|
||||
setDoNotTrackStorage,
|
||||
isWidgetBalanceDisplayAllowed,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Ref, useCallback, useMemo } from 'react';
|
||||
import { Platform, Pressable, TouchableOpacity } from 'react-native';
|
||||
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
|
||||
import {
|
||||
ContextMenuView,
|
||||
RenderItem,
|
||||
|
@ -8,7 +9,6 @@ import {
|
|||
IconConfig,
|
||||
MenuElementConfig,
|
||||
} from 'react-native-ios-context-menu';
|
||||
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
|
||||
import { ToolTipMenuProps, Action } from './types';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
|
||||
|
@ -30,6 +30,7 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
|
||||
const { language } = useSettings();
|
||||
|
||||
// Map Menu Items for iOS Context Menu
|
||||
const mapMenuItemForContextMenuView = useCallback((action: Action) => {
|
||||
if (!action.id) return null;
|
||||
return {
|
||||
|
@ -41,14 +42,30 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// Map Menu Items for RN Menu (supports subactions and displayInline)
|
||||
const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => {
|
||||
if (!action.id) return null;
|
||||
|
||||
// Check for subactions
|
||||
const subactions =
|
||||
action.subactions?.map(subaction => ({
|
||||
id: subaction.id.toString(),
|
||||
title: subaction.text,
|
||||
subtitle: subaction.subtitle,
|
||||
image: subaction.icon?.iconValue ? subaction.icon.iconValue : undefined,
|
||||
state: subaction.menuState === undefined ? undefined : ((subaction.menuState ? 'on' : 'off') as MenuState),
|
||||
attributes: { disabled: subaction.disabled, destructive: subaction.destructive, hidden: subaction.hidden },
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
id: action.id.toString(),
|
||||
title: action.text,
|
||||
subtitle: action.subtitle,
|
||||
image: action.icon?.iconValue ? action.icon.iconValue : undefined,
|
||||
state: action.menuState === undefined ? undefined : ((action.menuState ? 'on' : 'off') as MenuState),
|
||||
attributes: { disabled: action.disabled },
|
||||
attributes: { disabled: action.disabled, destructive: action.destructive, hidden: action.hidden },
|
||||
subactions: subactions.length > 0 ? subactions : undefined,
|
||||
displayInline: action.displayInline || false,
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -98,7 +115,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
);
|
||||
|
||||
const renderContextMenuView = () => {
|
||||
console.debug('ToolTipMenu.tsx rendering: renderContextMenuView');
|
||||
return (
|
||||
<ContextMenuView
|
||||
lazyPreview
|
||||
|
@ -139,7 +155,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
};
|
||||
|
||||
const renderMenuView = () => {
|
||||
console.debug('ToolTipMenu.tsx rendering: renderMenuView');
|
||||
return (
|
||||
<MenuView
|
||||
title={title}
|
||||
|
@ -147,7 +162,7 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
onPressAction={handlePressMenuItemForMenuView}
|
||||
actions={Platform.OS === 'ios' ? menuViewItemsIOS : menuViewItemsAndroid}
|
||||
shouldOpenOnLongPress={!isMenuPrimaryAction}
|
||||
// @ts-ignore: its not in the types but it works
|
||||
// @ts-ignore: Not exposed in types
|
||||
accessibilityLabel={props.accessibilityLabel}
|
||||
accessibilityHint={props.accessibilityHint}
|
||||
accessibilityRole={props.accessibilityRole}
|
||||
|
|
|
@ -289,7 +289,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
handleOnViewOnBlockExplorer,
|
||||
],
|
||||
);
|
||||
const toolTipActions = useMemo((): Action[] | Action[][] => {
|
||||
const toolTipActions = useMemo((): Action[] => {
|
||||
const actions: (Action | Action[])[] = [];
|
||||
|
||||
if (rowTitle !== loc.lnd.expired) {
|
||||
|
@ -308,7 +308,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
actions.push([CommonToolTipActions.ExpandNote]);
|
||||
}
|
||||
|
||||
return actions as Action[] | Action[][];
|
||||
return actions as Action[];
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines]);
|
||||
|
||||
const accessibilityState = useMemo(() => {
|
||||
|
|
|
@ -209,7 +209,7 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] | Action[][] => {
|
||||
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] => {
|
||||
const actions = [
|
||||
{
|
||||
id: actionKeys.CopyToClipboard,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AccessibilityRole, ViewStyle } from 'react-native';
|
||||
import { AccessibilityRole, ViewStyle, ColorValue } from 'react-native';
|
||||
|
||||
export interface Action {
|
||||
id: string | number;
|
||||
|
@ -7,13 +7,19 @@ export interface Action {
|
|||
iconValue: string;
|
||||
};
|
||||
menuTitle?: string;
|
||||
subtitle?: string;
|
||||
menuState?: 'mixed' | boolean | undefined;
|
||||
displayInline?: boolean; // Indicates if subactions should be displayed inline or nested (iOS only)
|
||||
image?: string;
|
||||
imageColor?: ColorValue;
|
||||
destructive?: boolean;
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
displayInline?: boolean;
|
||||
subactions?: Action[]; // Nested/Inline actions (subactions) within an action
|
||||
}
|
||||
|
||||
export interface ToolTipMenuProps {
|
||||
actions: Action[] | Action[][];
|
||||
actions: Action[];
|
||||
children: React.ReactNode;
|
||||
enableAndroidRipple?: boolean;
|
||||
dismissMenu?: () => void;
|
||||
|
|
|
@ -167,7 +167,8 @@ platform :ios do
|
|||
type: "development",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false, # This will regenerate the provisioning profile if needed
|
||||
force_for_new_devices: true # This forces match to add new devices to the profile
|
||||
force_for_new_devices: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -211,6 +212,7 @@ platform :ios do
|
|||
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
|
||||
git_url: ENV["GIT_URL"],
|
||||
type: "appstore",
|
||||
clone_branch_directly: true, # Skip if the branch already exists (Exit 128 error)
|
||||
platform: platform,
|
||||
app_identifier: app_identifier,
|
||||
team_id: ENV["ITC_TEAM_ID"],
|
||||
|
@ -228,7 +230,8 @@ platform :ios do
|
|||
type: "development",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -238,7 +241,9 @@ platform :ios do
|
|||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -250,14 +255,16 @@ platform :ios do
|
|||
platform: "catalyst",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false,
|
||||
force_for_new_devices: true
|
||||
force_for_new_devices: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
|
||||
match(
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false
|
||||
readonly: false,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -326,8 +333,8 @@ platform :ios do
|
|||
changelog = ENV["LATEST_COMMIT_MESSAGE"]
|
||||
|
||||
upload_to_testflight(
|
||||
api_key_path: "appstore_api_key.json",
|
||||
ipa: "./build/BlueWallet.#{ENV['PROJECT_VERSION']}(#{ENV['NEW_BUILD_NUMBER']}).ipa",
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: "./BlueWallet.#{ENV['PROJECT_VERSION']}(#{ENV['NEW_BUILD_NUMBER']}).ipa",
|
||||
skip_waiting_for_build_processing: true, # Do not wait for processing
|
||||
changelog: changelog
|
||||
)
|
||||
|
|
|
@ -219,7 +219,6 @@
|
|||
"about_sm_twitter": "Follow us on Twitter",
|
||||
"privacy_temporary_screenshots": "Allow Screenshots",
|
||||
"privacy_temporary_screenshots_instructions": "Screen capture protection will be turned off for this session, allowing you to take screenshots. Once you close and reopen the app, the protection will be automatically turned back on.",
|
||||
"advanced_options": "Advanced Options",
|
||||
"biometrics": "Biometrics",
|
||||
"biometrics_no_longer_available": "Your device settings have changed and no longer match the selected security settings in the app. Please re-enable biometrics or passcode, then restart the app to apply these changes.",
|
||||
"biom_10times": "You have attempted to enter your password 10 times. Would you like to reset your storage? This will remove all wallets and decrypt your storage.",
|
||||
|
@ -272,8 +271,6 @@
|
|||
"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",
|
||||
"general_adv_mode": "Advanced Mode",
|
||||
"general_adv_mode_e": "When enabled, you will see advanced options such as different wallet types, the ability to specify the LNDHub instance you wish to connect to, and custom entropy during wallet creation.",
|
||||
"general_continuity": "Continuity",
|
||||
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.",
|
||||
"groundcontrol_explanation": "GroundControl is a free, open-source push notifications server for Bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallet’s infrastructure. Leave blank to use GroundControl’s default server.",
|
||||
|
@ -479,7 +476,8 @@
|
|||
"add_ln_wallet_first": "You must first add a Lightning wallet.",
|
||||
"identity_pubkey": "Identity Pubkey",
|
||||
"xpub_title": "Wallet XPUB",
|
||||
"manage_wallets_search_placeholder": "Search wallets, memos"
|
||||
"manage_wallets_search_placeholder": "Search wallets, memos",
|
||||
"more_info": "More Info"
|
||||
},
|
||||
"total_balance_view": {
|
||||
"view_in_bitcoin": "View in Bitcoin",
|
||||
|
@ -489,7 +487,7 @@
|
|||
"explanation": "View the total balance of all your wallets in the overview screen."
|
||||
},
|
||||
"multisig": {
|
||||
"multisig_vault": "Vault",
|
||||
"multisig_vault": "Multisig Vault",
|
||||
"default_label": "Multisig Vault",
|
||||
"multisig_vault_explain": "Best security for large amounts",
|
||||
"provide_signature": "Provide signature",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import React from 'react';
|
||||
|
||||
import navigationStyle from '../components/navigationStyle';
|
||||
import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle';
|
||||
import { useTheme } from '../components/themes';
|
||||
import loc from '../loc';
|
||||
import {
|
||||
|
@ -49,7 +49,7 @@ const AddWalletStack = () => {
|
|||
name="AddWallet"
|
||||
component={AddComponent}
|
||||
options={navigationStyle({
|
||||
headerBackVisible: false,
|
||||
closeButtonPosition: CloseButtonPosition.Left,
|
||||
title: loc.wallets.add_title,
|
||||
})(theme)}
|
||||
/>
|
||||
|
|
|
@ -73,7 +73,6 @@ const DetailViewStackScreensStack = () => {
|
|||
const { wallets } = useStorage();
|
||||
const { isTotalBalanceEnabled } = useSettings();
|
||||
|
||||
const SaveButton = useMemo(() => <HeaderRightButton testID="SaveButton" disabled={true} title={loc.wallets.details_save} />, []);
|
||||
const DetailButton = useMemo(() => <HeaderRightButton testID="DetailButton" disabled={true} title={loc.send.create_details} />, []);
|
||||
|
||||
const navigateToAddWallet = useCallback(() => {
|
||||
|
@ -122,7 +121,6 @@ const DetailViewStackScreensStack = () => {
|
|||
options={navigationStyle({
|
||||
headerTitle: loc.wallets.details_title,
|
||||
statusBarStyle: 'auto',
|
||||
headerRight: () => SaveButton,
|
||||
})(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
|
@ -247,7 +245,11 @@ const DetailViewStackScreensStack = () => {
|
|||
options={navigationStyle({ title: loc.addresses.addresses_title, statusBarStyle: 'auto' })(theme)}
|
||||
/>
|
||||
|
||||
<DetailViewStack.Screen name="AddWalletRoot" component={AddWalletStack} options={NavigationFormModalOptions} />
|
||||
<DetailViewStack.Screen
|
||||
name="AddWalletRoot"
|
||||
component={AddWalletStack}
|
||||
options={navigationStyle({ closeButtonPosition: CloseButtonPosition.Left, ...NavigationFormModalOptions })(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen name="SendDetailsRoot" component={SendDetailsStack} options={NavigationDefaultOptions} />
|
||||
<DetailViewStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={NavigationDefaultOptions} />
|
||||
<DetailViewStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={NavigationDefaultOptions} />
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { BackHandler, InteractionManager, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
BackHandler,
|
||||
Image,
|
||||
InteractionManager,
|
||||
LayoutAnimation,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Share from 'react-native-share';
|
||||
|
||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
|
@ -24,6 +35,9 @@ import { SuccessView } from '../send/success';
|
|||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
import SegmentedControl from '../../components/SegmentControl';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
|
||||
const segmentControlValues = [loc.wallets.details_address, loc.bip47.payment_code];
|
||||
|
||||
|
@ -43,9 +57,9 @@ const ReceiveDetails = () => {
|
|||
const [showConfirmedBalance, setShowConfirmedBalance] = useState(false);
|
||||
const [showAddress, setShowAddress] = useState(false);
|
||||
const [currentTab, setCurrentTab] = useState(segmentControlValues[0]);
|
||||
const { goBack, setParams } = useExtendedNavigation();
|
||||
const { goBack, setParams, setOptions } = useExtendedNavigation();
|
||||
const bottomModalRef = useRef(null);
|
||||
const { colors } = useTheme();
|
||||
const { colors, closeImage } = useTheme();
|
||||
const [intervalMs, setIntervalMs] = useState(5000);
|
||||
const [eta, setEta] = useState('');
|
||||
const [initialConfirmed, setInitialConfirmed] = useState(0);
|
||||
|
@ -79,15 +93,119 @@ const ReceiveDetails = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const setAddressBIP21Encoded = useCallback(
|
||||
addr => {
|
||||
const newBip21encoded = DeeplinkSchemaMatch.bip21encode(addr);
|
||||
setParams({ address: addr });
|
||||
setBip21encoded(newBip21encoded);
|
||||
setShowAddress(true);
|
||||
},
|
||||
[setParams],
|
||||
);
|
||||
|
||||
const obtainWalletAddress = useCallback(async () => {
|
||||
console.debug('receive/details - componentDidMount');
|
||||
let newAddress;
|
||||
if (address) {
|
||||
setAddressBIP21Encoded(address);
|
||||
await Notifications.tryToObtainPermissions(receiveAddressButton);
|
||||
Notifications.majorTomToGroundControl([address], [], []);
|
||||
} else {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
try {
|
||||
if (!isElectrumDisabled) newAddress = await Promise.race([wallet.getAddressAsync(), sleep(1000)]);
|
||||
} catch (_) {}
|
||||
if (newAddress === undefined) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
console.warn('either sleep expired or getAddressAsync threw an exception');
|
||||
newAddress = wallet._getExternalAddressByIndex(wallet.getNextFreeAddressIndex());
|
||||
} else {
|
||||
saveToDisk(); // caching whatever getAddressAsync() generated internally
|
||||
}
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
try {
|
||||
await Promise.race([wallet.getAddressAsync(), sleep(1000)]);
|
||||
newAddress = wallet.getAddress();
|
||||
} catch (_) {}
|
||||
if (newAddress === undefined) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
console.warn('either sleep expired or getAddressAsync threw an exception');
|
||||
newAddress = wallet.getAddress();
|
||||
} else {
|
||||
saveToDisk(); // caching whatever getAddressAsync() generated internally
|
||||
}
|
||||
}
|
||||
setAddressBIP21Encoded(newAddress);
|
||||
await Notifications.tryToObtainPermissions(receiveAddressButton);
|
||||
Notifications.majorTomToGroundControl([newAddress], [], []);
|
||||
}
|
||||
}, [wallet, saveToDisk, address, setAddressBIP21Encoded, isElectrumDisabled, sleep]);
|
||||
|
||||
const onEnablePaymentsCodeSwitchValue = useCallback(() => {
|
||||
if (wallet.allowBIP47()) {
|
||||
wallet.switchBIP47(!wallet.isBIP47Enabled());
|
||||
}
|
||||
saveToDisk();
|
||||
obtainWalletAddress();
|
||||
}, [wallet, saveToDisk, obtainWalletAddress]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showConfirmedBalance) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
}
|
||||
}, [showConfirmedBalance]);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const action = CommonToolTipActions.PaymentCode;
|
||||
action.menuState = wallet.isBIP47Enabled();
|
||||
return [action];
|
||||
}, [wallet]);
|
||||
|
||||
const onPressMenuItem = useCallback(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
onEnablePaymentsCodeSwitchValue();
|
||||
}, [onEnablePaymentsCodeSwitchValue]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu isButton isMenuPrimaryAction onPressMenuItem={onPressMenuItem} actions={[toolTipActions]}>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
[colors.foregroundColor, onPressMenuItem, toolTipActions],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
goBack();
|
||||
}, [goBack]);
|
||||
|
||||
const HeaderLeft = useMemo(
|
||||
() => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={loc._.close}
|
||||
style={styles.button}
|
||||
onPress={handleClose}
|
||||
testID="NavigationCloseButton"
|
||||
>
|
||||
<Image source={closeImage} />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
[closeImage, handleClose],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
wallet.allowBIP47() &&
|
||||
!wallet.isBIP47Enabled() &&
|
||||
setOptions({
|
||||
headerLeft: () => (wallet.isBIP47Enabled() ? null : HeaderLeft),
|
||||
headerRight: () => (wallet.isBIP47Enabled() ? HeaderLeft : HeaderRight),
|
||||
});
|
||||
}, [HeaderLeft, HeaderRight, colors.foregroundColor, setOptions, wallet]);
|
||||
|
||||
// re-fetching address balance periodically
|
||||
useEffect(() => {
|
||||
console.log('receive/details - useEffect');
|
||||
console.debug('receive/details - useEffect');
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
try {
|
||||
|
@ -95,9 +213,9 @@ const ReceiveDetails = () => {
|
|||
const addressToUse = address || decoded.address;
|
||||
if (!addressToUse) return;
|
||||
|
||||
console.log('checking address', addressToUse, 'for balance...');
|
||||
console.debug('checking address', addressToUse, 'for balance...');
|
||||
const balance = await BlueElectrum.getBalanceByAddress(addressToUse);
|
||||
console.log('...got', balance);
|
||||
console.debug('...got', balance);
|
||||
|
||||
if (balance.unconfirmed > 0) {
|
||||
if (initialConfirmed === 0 && initialUnconfirmed === 0) {
|
||||
|
@ -157,7 +275,7 @@ const ReceiveDetails = () => {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.debug(error);
|
||||
}
|
||||
}, intervalMs);
|
||||
|
||||
|
@ -209,16 +327,6 @@ const ReceiveDetails = () => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const setAddressBIP21Encoded = useCallback(
|
||||
addr => {
|
||||
const newBip21encoded = DeeplinkSchemaMatch.bip21encode(addr);
|
||||
setParams({ address: addr });
|
||||
setBip21encoded(newBip21encoded);
|
||||
setShowAddress(true);
|
||||
},
|
||||
[setParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
|
||||
|
||||
|
@ -256,44 +364,6 @@ const ReceiveDetails = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const obtainWalletAddress = useCallback(async () => {
|
||||
console.log('receive/details - componentDidMount');
|
||||
let newAddress;
|
||||
if (address) {
|
||||
setAddressBIP21Encoded(address);
|
||||
await Notifications.tryToObtainPermissions(receiveAddressButton);
|
||||
Notifications.majorTomToGroundControl([address], [], []);
|
||||
} else {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
try {
|
||||
if (!isElectrumDisabled) newAddress = await Promise.race([wallet.getAddressAsync(), sleep(1000)]);
|
||||
} catch (_) {}
|
||||
if (newAddress === undefined) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
console.warn('either sleep expired or getAddressAsync threw an exception');
|
||||
newAddress = wallet._getExternalAddressByIndex(wallet.getNextFreeAddressIndex());
|
||||
} else {
|
||||
saveToDisk(); // caching whatever getAddressAsync() generated internally
|
||||
}
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
try {
|
||||
await Promise.race([wallet.getAddressAsync(), sleep(1000)]);
|
||||
newAddress = wallet.getAddress();
|
||||
} catch (_) {}
|
||||
if (newAddress === undefined) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
console.warn('either sleep expired or getAddressAsync threw an exception');
|
||||
newAddress = wallet.getAddress();
|
||||
} else {
|
||||
saveToDisk(); // caching whatever getAddressAsync() generated internally
|
||||
}
|
||||
}
|
||||
setAddressBIP21Encoded(newAddress);
|
||||
await Notifications.tryToObtainPermissions(receiveAddressButton);
|
||||
Notifications.majorTomToGroundControl([newAddress], [], []);
|
||||
}
|
||||
}, [wallet, saveToDisk, address, setAddressBIP21Encoded, isElectrumDisabled, sleep]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
|
@ -357,7 +427,7 @@ const ReceiveDetails = () => {
|
|||
|
||||
const handleShareButtonPressed = () => {
|
||||
Share.open({ message: currentTab === loc.wallets.details_address ? bip21encoded : wallet.getBIP47PaymentCode() }).catch(error =>
|
||||
console.log(error),
|
||||
console.debug(error),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -16,14 +16,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
const GeneralSettings: React.FC = () => {
|
||||
const { wallets } = useStorage();
|
||||
const {
|
||||
isAdvancedModeEnabled,
|
||||
setIsAdvancedModeEnabledStorage,
|
||||
isHandOffUseEnabled,
|
||||
setIsHandOffUseEnabledAsyncStorage,
|
||||
isLegacyURv1Enabled,
|
||||
setIsLegacyURv1EnabledStorage,
|
||||
} = useSettings();
|
||||
const { isHandOffUseEnabled, setIsHandOffUseEnabledAsyncStorage, isLegacyURv1Enabled, setIsLegacyURv1EnabledStorage } = useSettings();
|
||||
const { navigate } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
|
||||
|
@ -64,14 +57,6 @@ const GeneralSettings: React.FC = () => {
|
|||
<BlueSpacing20 />
|
||||
</>
|
||||
) : null}
|
||||
<ListItem
|
||||
Component={PressableWrapper}
|
||||
title={loc.settings.general_adv_mode}
|
||||
switch={{ onValueChange: setIsAdvancedModeEnabledStorage, value: isAdvancedModeEnabled, testID: 'AdvancedMode' }}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.general_adv_mode_e}</BlueText>
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
<ListItem
|
||||
Component={PressableWrapper}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import React, { useEffect, useReducer } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
|
@ -9,7 +9,6 @@ import {
|
|||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
useColorScheme,
|
||||
View,
|
||||
|
@ -21,13 +20,15 @@ import { BlueButtonLink, BlueFormLabel, BlueSpacing20, BlueSpacing40, BlueText }
|
|||
import { BlueApp, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, SegwitP2SHWallet } from '../../class';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import WalletButton from '../../components/WalletButton';
|
||||
import loc from '../../loc';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { Action } from '../../components/types';
|
||||
|
||||
enum ButtonSelected {
|
||||
// @ts-ignore: Return later to update
|
||||
|
@ -43,7 +44,6 @@ interface State {
|
|||
selectedIndex: number;
|
||||
label: string;
|
||||
selectedWalletType: ButtonSelected;
|
||||
backdoorPressed: number;
|
||||
entropy: Buffer | undefined;
|
||||
entropyButtonText: string;
|
||||
}
|
||||
|
@ -54,13 +54,12 @@ const ActionTypes = {
|
|||
SET_SELECTED_INDEX: 'SET_SELECTED_INDEX',
|
||||
SET_LABEL: 'SET_LABEL',
|
||||
SET_SELECTED_WALLET_TYPE: 'SET_SELECTED_WALLET_TYPE',
|
||||
INCREMENT_BACKDOOR_PRESSED: 'INCREMENT_BACKDOOR_PRESSED',
|
||||
SET_ENTROPY: 'SET_ENTROPY',
|
||||
SET_ENTROPY_BUTTON_TEXT: 'SET_ENTROPY_BUTTON_TEXT',
|
||||
} as const;
|
||||
type ActionTypes = (typeof ActionTypes)[keyof typeof ActionTypes];
|
||||
|
||||
interface Action {
|
||||
interface TAction {
|
||||
type: ActionTypes;
|
||||
payload?: any;
|
||||
}
|
||||
|
@ -71,25 +70,22 @@ const initialState: State = {
|
|||
selectedIndex: 0,
|
||||
label: '',
|
||||
selectedWalletType: ButtonSelected.ONCHAIN,
|
||||
backdoorPressed: 1,
|
||||
entropy: undefined,
|
||||
entropyButtonText: loc.wallets.add_entropy_provide,
|
||||
};
|
||||
|
||||
const walletReducer = (state: State, action: Action): State => {
|
||||
const walletReducer = (state: State, action: TAction): State => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_LOADING:
|
||||
return { ...state, isLoading: action.payload };
|
||||
case ActionTypes.SET_WALLET_BASE_URI:
|
||||
return { ...state, walletBaseURI: action.payload };
|
||||
case ActionTypes.SET_SELECTED_INDEX:
|
||||
return { ...state, selectedIndex: action.payload };
|
||||
return { ...state, selectedIndex: action.payload, selectedWalletType: ButtonSelected.ONCHAIN };
|
||||
case ActionTypes.SET_LABEL:
|
||||
return { ...state, label: action.payload };
|
||||
case ActionTypes.SET_SELECTED_WALLET_TYPE:
|
||||
return { ...state, selectedWalletType: action.payload };
|
||||
case ActionTypes.INCREMENT_BACKDOOR_PRESSED:
|
||||
return { ...state, backdoorPressed: state.backdoorPressed + 1 };
|
||||
case ActionTypes.SET_ENTROPY:
|
||||
return { ...state, entropy: action.payload };
|
||||
case ActionTypes.SET_ENTROPY_BUTTON_TEXT:
|
||||
|
@ -111,10 +107,9 @@ const WalletsAdd: React.FC = () => {
|
|||
const selectedWalletType = state.selectedWalletType;
|
||||
const entropy = state.entropy;
|
||||
const entropyButtonText = state.entropyButtonText;
|
||||
//
|
||||
const colorScheme = useColorScheme();
|
||||
//
|
||||
const { addWallet, saveToDisk } = useStorage();
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const { navigate, goBack, setOptions } = useNavigation();
|
||||
const stylesHook = {
|
||||
advancedText: {
|
||||
|
@ -138,20 +133,7 @@ const WalletsAdd: React.FC = () => {
|
|||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem(BlueApp.LNDHUB)
|
||||
.then(url => (url ? setWalletBaseURI(url) : setWalletBaseURI('')))
|
||||
.catch(() => setWalletBaseURI(''))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions({
|
||||
statusBarStyle: Platform.select({ ios: 'light', default: colorScheme === 'dark' ? 'light' : 'dark' }),
|
||||
});
|
||||
}, [colorScheme, setOptions]);
|
||||
|
||||
const entropyGenerated = (newEntropy: Buffer) => {
|
||||
const entropyGenerated = useCallback((newEntropy: Buffer) => {
|
||||
let entropyTitle;
|
||||
if (!newEntropy) {
|
||||
entropyTitle = loc.wallets.add_entropy_provide;
|
||||
|
@ -162,7 +144,127 @@ const WalletsAdd: React.FC = () => {
|
|||
}
|
||||
setEntropy(newEntropy);
|
||||
setEntropyButtonText(entropyTitle);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigateToEntropy = useCallback(() => {
|
||||
Alert.alert(
|
||||
loc.wallets.add_wallet_seed_length,
|
||||
loc.wallets.add_wallet_seed_length_message,
|
||||
[
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {},
|
||||
style: 'default',
|
||||
},
|
||||
{
|
||||
text: loc.wallets.add_wallet_seed_length_12,
|
||||
onPress: () => {
|
||||
// @ts-ignore: Return later to update
|
||||
navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 12 });
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{
|
||||
text: loc.wallets.add_wallet_seed_length_24,
|
||||
onPress: () => {
|
||||
// @ts-ignore: Return later to update
|
||||
navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 24 });
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
}, [entropyGenerated, navigate]);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const walletSubactions: Action[] = [
|
||||
{
|
||||
id: HDSegwitBech32Wallet.type,
|
||||
text: `${loc.multisig.native_segwit_title}`,
|
||||
subtitle: 'p2wsh/HD',
|
||||
menuState: selectedIndex === 0 && selectedWalletType === ButtonSelected.ONCHAIN,
|
||||
},
|
||||
{
|
||||
id: SegwitP2SHWallet.type,
|
||||
text: `${loc.multisig.wrapped_segwit_title}`,
|
||||
subtitle: 'p2sh-p2wsh/HD',
|
||||
menuState: selectedIndex === 1 && selectedWalletType === ButtonSelected.ONCHAIN,
|
||||
},
|
||||
{
|
||||
id: HDSegwitP2SHWallet.type,
|
||||
text: `${loc.multisig.legacy_title}`,
|
||||
subtitle: 'p2sh/non-HD',
|
||||
menuState: selectedIndex === 2 && selectedWalletType === ButtonSelected.ONCHAIN,
|
||||
},
|
||||
{
|
||||
id: LightningCustodianWallet.type,
|
||||
text: LightningCustodianWallet.typeReadable,
|
||||
menuState: selectedWalletType === ButtonSelected.OFFCHAIN,
|
||||
},
|
||||
];
|
||||
|
||||
const walletAction: Action = {
|
||||
id: 'wallets',
|
||||
text: loc.multisig.wallet_type,
|
||||
subactions: walletSubactions,
|
||||
displayInline: true,
|
||||
};
|
||||
|
||||
const entropyAction = {
|
||||
...CommonToolTipActions.Entropy,
|
||||
text: entropyButtonText,
|
||||
menuState: false,
|
||||
};
|
||||
|
||||
return [walletAction, entropyAction];
|
||||
}, [entropyButtonText, selectedIndex, selectedWalletType]);
|
||||
|
||||
const handleOnLightningButtonPressed = useCallback(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setSelectedWalletType(ButtonSelected.OFFCHAIN);
|
||||
}, []);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={(id: string) => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
if (id === HDSegwitBech32Wallet.type) {
|
||||
setSelectedIndex(0);
|
||||
} else if (id === SegwitP2SHWallet.type) {
|
||||
setSelectedIndex(1);
|
||||
} else if (id === HDSegwitP2SHWallet.type) {
|
||||
setSelectedIndex(2);
|
||||
} else if (id === LightningCustodianWallet.type) {
|
||||
handleOnLightningButtonPressed();
|
||||
} else if (id === CommonToolTipActions.Entropy.id) {
|
||||
navigateToEntropy();
|
||||
}
|
||||
}}
|
||||
actions={toolTipActions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
[colors.foregroundColor, handleOnLightningButtonPressed, navigateToEntropy, toolTipActions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions({
|
||||
headerRight: () => HeaderRight,
|
||||
statusBarStyle: Platform.select({ ios: 'light', default: colorScheme === 'dark' ? 'light' : 'dark' }),
|
||||
});
|
||||
}, [HeaderRight, colorScheme, colors.foregroundColor, navigateToEntropy, setOptions, toolTipActions]);
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem(BlueApp.LNDHUB)
|
||||
.then(url => (url ? setWalletBaseURI(url) : setWalletBaseURI('')))
|
||||
.catch(() => setWalletBaseURI(''))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, []);
|
||||
|
||||
const setIsLoading = (value: boolean) => {
|
||||
dispatch({ type: 'SET_LOADING', payload: value });
|
||||
|
@ -184,10 +286,6 @@ const WalletsAdd: React.FC = () => {
|
|||
dispatch({ type: 'SET_SELECTED_WALLET_TYPE', payload: value });
|
||||
};
|
||||
|
||||
const setBackdoorPressed = (value: number) => {
|
||||
dispatch({ type: 'INCREMENT_BACKDOOR_PRESSED', payload: value });
|
||||
};
|
||||
|
||||
const setEntropy = (value: Buffer) => {
|
||||
dispatch({ type: 'SET_ENTROPY', payload: value });
|
||||
};
|
||||
|
@ -225,7 +323,6 @@ const WalletsAdd: React.FC = () => {
|
|||
} catch (e: any) {
|
||||
console.log(e.toString());
|
||||
presentAlert({ message: e.toString() });
|
||||
goBack();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
@ -291,37 +388,6 @@ const WalletsAdd: React.FC = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const navigateToEntropy = () => {
|
||||
Alert.alert(
|
||||
loc.wallets.add_wallet_seed_length,
|
||||
loc.wallets.add_wallet_seed_length_message,
|
||||
[
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {},
|
||||
style: 'default',
|
||||
},
|
||||
{
|
||||
text: loc.wallets.add_wallet_seed_length_12,
|
||||
onPress: () => {
|
||||
// @ts-ignore: Return later to update
|
||||
navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 12 });
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{
|
||||
text: loc.wallets.add_wallet_seed_length_24,
|
||||
onPress: () => {
|
||||
// @ts-ignore: Return later to update
|
||||
navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 24 });
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
};
|
||||
|
||||
const navigateToImportWallet = () => {
|
||||
// @ts-ignore: Return later to update
|
||||
navigate('ImportWallet');
|
||||
|
@ -339,16 +405,6 @@ const WalletsAdd: React.FC = () => {
|
|||
setSelectedWalletType(ButtonSelected.ONCHAIN);
|
||||
};
|
||||
|
||||
const handleOnLightningButtonPressed = () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
// @ts-ignore: Return later to update
|
||||
setBackdoorPressed((prevState: number) => {
|
||||
return prevState + 1;
|
||||
});
|
||||
Keyboard.dismiss();
|
||||
setSelectedWalletType(ButtonSelected.OFFCHAIN);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView style={stylesHook.root} testID="ScrollView" automaticallyAdjustKeyboardInsets>
|
||||
<BlueSpacing20 />
|
||||
|
@ -374,12 +430,6 @@ const WalletsAdd: React.FC = () => {
|
|||
onPress={handleOnBitcoinButtonPressed}
|
||||
size={styles.button}
|
||||
/>
|
||||
<WalletButton
|
||||
buttonType="Lightning"
|
||||
active={selectedWalletType === ButtonSelected.OFFCHAIN}
|
||||
onPress={handleOnLightningButtonPressed}
|
||||
size={styles.button}
|
||||
/>
|
||||
<WalletButton
|
||||
buttonType="Vault"
|
||||
testID="ActivateVaultButton"
|
||||
|
@ -390,65 +440,29 @@ const WalletsAdd: React.FC = () => {
|
|||
</View>
|
||||
|
||||
<View style={styles.advanced}>
|
||||
{(() => {
|
||||
if (selectedWalletType === ButtonSelected.ONCHAIN && isAdvancedModeEnabled) {
|
||||
return (
|
||||
<View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
|
||||
<ListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(0)}
|
||||
title={HDSegwitBech32Wallet.typeReadable}
|
||||
checkmark={selectedIndex === 0}
|
||||
/>
|
||||
<ListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(1)}
|
||||
title={SegwitP2SHWallet.typeReadable}
|
||||
checkmark={selectedIndex === 1}
|
||||
/>
|
||||
<ListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(2)}
|
||||
title={HDSegwitP2SHWallet.typeReadable}
|
||||
checkmark={selectedIndex === 2}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
} else if (selectedWalletType === ButtonSelected.OFFCHAIN) {
|
||||
return (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
|
||||
<BlueSpacing20 />
|
||||
<BlueText>{loc.wallets.add_lndhub}</BlueText>
|
||||
<View style={[styles.lndUri, stylesHook.lndUri]}>
|
||||
<TextInput
|
||||
value={walletBaseURI}
|
||||
onChangeText={setWalletBaseURI}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
placeholder={loc.wallets.add_lndhub_placeholder}
|
||||
clearButtonMode="while-editing"
|
||||
autoCapitalize="none"
|
||||
textContentType="URL"
|
||||
autoCorrect={false}
|
||||
placeholderTextColor="#81868e"
|
||||
style={styles.textInputCommon}
|
||||
editable={!isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{isAdvancedModeEnabled === true && selectedWalletType === ButtonSelected.ONCHAIN && !isLoading && (
|
||||
<BlueButtonLink style={styles.import} title={entropyButtonText} onPress={navigateToEntropy} />
|
||||
{selectedWalletType === ButtonSelected.OFFCHAIN && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<BlueText>{loc.wallets.add_lndhub}</BlueText>
|
||||
<View style={[styles.lndUri, stylesHook.lndUri]}>
|
||||
<TextInput
|
||||
value={walletBaseURI}
|
||||
onChangeText={setWalletBaseURI}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
placeholder={loc.wallets.add_lndhub_placeholder}
|
||||
clearButtonMode="while-editing"
|
||||
autoCapitalize="none"
|
||||
textContentType="URL"
|
||||
autoCorrect={false}
|
||||
placeholderTextColor="#81868e"
|
||||
style={styles.textInputCommon}
|
||||
editable={!isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<BlueSpacing20 />
|
||||
{!isLoading ? (
|
||||
<>
|
||||
|
@ -508,9 +522,6 @@ const styles = StyleSheet.create({
|
|||
advanced: {
|
||||
marginHorizontal: 20,
|
||||
},
|
||||
advancedText: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
lndUri: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
|
@ -524,9 +535,6 @@ const styles = StyleSheet.create({
|
|||
import: {
|
||||
marginVertical: 24,
|
||||
},
|
||||
noPadding: {
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export default WalletsAdd;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
ListRenderItemInfo,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
@ -20,12 +19,11 @@ import { isDesktop } from '../../blue_modules/environment';
|
|||
import { encodeUR } from '../../blue_modules/ur';
|
||||
import {
|
||||
BlueButtonLink,
|
||||
BlueCard,
|
||||
BlueFormMultiInput,
|
||||
BlueLoading,
|
||||
BlueSpacing10,
|
||||
BlueSpacing20,
|
||||
BlueSpacing40,
|
||||
BlueText,
|
||||
BlueTextCentered,
|
||||
} from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
|
||||
|
@ -49,14 +47,14 @@ import usePrivacy from '../../hooks/usePrivacy';
|
|||
import loc from '../../loc';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
|
||||
const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const hasLoaded = useRef(false);
|
||||
const { colors } = useTheme();
|
||||
const { wallets, setWalletsWithNewOrder, isElectrumDisabled } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const { navigate, dispatch, addListener } = useExtendedNavigation();
|
||||
const openScannerButtonRef = useRef();
|
||||
const route = useRoute();
|
||||
|
@ -97,6 +95,9 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
vaultKeyText: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
askPassphrase: {
|
||||
backgroundColor: colors.lightButton,
|
||||
},
|
||||
vaultKeyCircleSuccess: {
|
||||
backgroundColor: colors.msSuccessBG,
|
||||
},
|
||||
|
@ -523,6 +524,12 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
|
||||
const hideShareModal = () => {};
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const passphrase = CommonToolTipActions.Passphrase;
|
||||
passphrase.menuState = askPassphrase;
|
||||
return [passphrase];
|
||||
}, [askPassphrase]);
|
||||
|
||||
const renderProvideMnemonicsModal = () => {
|
||||
return (
|
||||
<BottomModal
|
||||
|
@ -545,18 +552,24 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
</>
|
||||
}
|
||||
>
|
||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
||||
{isAdvancedModeEnabled && (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<View style={styles.row}>
|
||||
<BlueText>{loc.wallets.import_passphrase}</BlueText>
|
||||
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={(id: string) => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setAskPassphrase(!askPassphrase);
|
||||
}}
|
||||
actions={toolTipActions}
|
||||
style={[styles.askPassprase, stylesHook.askPassphrase]}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
|
||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
||||
</>
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
@ -639,10 +652,11 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
contentInsetAdjustmentBehavior="automatic"
|
||||
automaticallyAdjustContentInsets
|
||||
keyExtractor={(_item, index) => `${index}`}
|
||||
contentContainerStyle={styles.contentContainerStyle}
|
||||
/>
|
||||
<BlueSpacing10 />
|
||||
{footer}
|
||||
<BlueSpacing40 />
|
||||
<BlueCard>{footer}</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
|
||||
{renderProvideMnemonicsModal()}
|
||||
|
||||
|
@ -665,6 +679,7 @@ const styles = StyleSheet.create({
|
|||
paddingTop: 32,
|
||||
minHeight: 370,
|
||||
},
|
||||
contentContainerStyle: { padding: 16 },
|
||||
modalContent: {
|
||||
padding: 22,
|
||||
justifyContent: 'center',
|
||||
|
@ -700,12 +715,8 @@ const styles = StyleSheet.create({
|
|||
tipLabelText: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
||||
askPassprase: { top: 0, left: 0, justifyContent: 'center', width: 33, height: 33, borderRadius: 33 / 2 },
|
||||
});
|
||||
|
||||
export default ViewEditMultisigCosigners;
|
||||
|
|
|
@ -11,7 +11,6 @@ import ListItem from '../../components/ListItem';
|
|||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
|
||||
|
@ -27,7 +26,6 @@ const WalletsAddMultisig: React.FC = () => {
|
|||
const [m, setM] = useState(2);
|
||||
const [n, setN] = useState(3);
|
||||
const [format, setFormat] = useState(MultisigHDWallet.FORMAT_P2WSH);
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -202,17 +200,16 @@ const WalletsAddMultisig: React.FC = () => {
|
|||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
{isAdvancedModeEnabled && (
|
||||
<View>
|
||||
<ListItem
|
||||
testID="VaultAdvancedCustomize"
|
||||
onPress={showAdvancedOptionsModal}
|
||||
title={loc.multisig.vault_advanced_customize}
|
||||
subtitle={`${getCurrentlySelectedFormat('format')}, ${getCurrentlySelectedFormat('quorum')}`}
|
||||
chevron
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View>
|
||||
<ListItem
|
||||
testID="VaultAdvancedCustomize"
|
||||
onPress={showAdvancedOptionsModal}
|
||||
title={loc.multisig.vault_advanced_customize}
|
||||
subtitle={`${getCurrentlySelectedFormat('format')}, ${getCurrentlySelectedFormat('quorum')}`}
|
||||
chevron
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
testID="LetsStart"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
|
@ -8,7 +8,6 @@ import {
|
|||
LayoutAnimation,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
|
@ -17,7 +16,7 @@ import { Icon } from '@rneui/themed';
|
|||
import A from '../../blue_modules/analytics';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { encodeUR } from '../../blue_modules/ur';
|
||||
import { BlueButtonLink, BlueFormMultiInput, BlueSpacing10, BlueSpacing20, BlueText, BlueTextCentered } from '../../BlueComponents';
|
||||
import { BlueButtonLink, BlueFormMultiInput, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import BottomModal from '../../components/BottomModal';
|
||||
|
@ -35,15 +34,15 @@ import prompt from '../../helpers/prompt';
|
|||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
|
||||
const staticCache = {};
|
||||
|
||||
const WalletsAddMultisigStep2 = () => {
|
||||
const { addWallet, saveToDisk, isElectrumDisabled, sleep, currentSharedCosigner, setSharedCosigner } = useStorage();
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const { navigate, navigateToWalletsList } = useExtendedNavigation();
|
||||
|
@ -108,6 +107,9 @@ const WalletsAddMultisigStep2 = () => {
|
|||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
askPassphrase: {
|
||||
backgroundColor: colors.lightButton,
|
||||
},
|
||||
textDestination: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
|
@ -611,6 +613,12 @@ const WalletsAddMultisigStep2 = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const passphrase = CommonToolTipActions.Passphrase;
|
||||
passphrase.menuState = askPassphrase;
|
||||
return [passphrase];
|
||||
}, [askPassphrase]);
|
||||
|
||||
const renderProvideMnemonicsModal = () => {
|
||||
return (
|
||||
<BottomModal
|
||||
|
@ -648,18 +656,24 @@ const WalletsAddMultisigStep2 = () => {
|
|||
setAskPassphrase(false);
|
||||
}}
|
||||
>
|
||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
||||
{isAdvancedModeEnabled && (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<View style={styles.row}>
|
||||
<BlueText>{loc.wallets.import_passphrase}</BlueText>
|
||||
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={_id => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setAskPassphrase(!askPassphrase);
|
||||
}}
|
||||
actions={toolTipActions}
|
||||
style={[styles.askPassprase, stylesHook.askPassphrase]}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
|
||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
||||
</>
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
@ -798,6 +812,8 @@ const styles = StyleSheet.create({
|
|||
paddingRight: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
askPassprase: { top: 0, left: 0, justifyContent: 'center', width: 33, height: 33, borderRadius: 33 / 2 },
|
||||
|
||||
secretContainer: {
|
||||
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
|
||||
justifyContent: 'flex-start',
|
||||
|
@ -829,12 +845,6 @@ const styles = StyleSheet.create({
|
|||
fontWeight: 'bold',
|
||||
marginLeft: 8,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
});
|
||||
|
||||
export default WalletsAddMultisigStep2;
|
||||
|
|
|
@ -41,10 +41,9 @@ import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
|||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import loc, { formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { popToTop } from '../../NavigationService';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollViewContent: {
|
||||
|
@ -113,7 +112,6 @@ const WalletDetails = () => {
|
|||
const wallet = useRef(wallets.find(w => w.getID() === walletID)).current;
|
||||
const [walletName, setWalletName] = useState(wallet.getLabel());
|
||||
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const [isBIP47Enabled, setIsBIP47Enabled] = useState(wallet.isBIP47Enabled());
|
||||
const [isContactsVisible, setIsContactsVisible] = useState(wallet.allowBIP47() && wallet.isBIP47Enabled());
|
||||
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
|
||||
|
@ -138,13 +136,17 @@ const WalletDetails = () => {
|
|||
setIsContactsVisible(isBIP47Enabled);
|
||||
}, [isBIP47Enabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdvancedModeEnabled && wallet.allowMasterFingerprint()) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setMasterFingerprint(wallet.getMasterFingerprintHex());
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
if (wallet.allowMasterFingerprint()) {
|
||||
setMasterFingerprint(wallet.getMasterFingerprintHex());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [isAdvancedModeEnabled, wallet]);
|
||||
|
||||
return () => task.cancel();
|
||||
}, [wallet]),
|
||||
);
|
||||
const stylesHook = StyleSheet.create({
|
||||
textLabel1: {
|
||||
color: colors.feeText,
|
||||
|
@ -541,25 +543,21 @@ const WalletDetails = () => {
|
|||
</View>
|
||||
</>
|
||||
)}
|
||||
{isAdvancedModeEnabled && (
|
||||
<View style={styles.row}>
|
||||
{wallet.allowMasterFingerprint() && (
|
||||
<View style={styles.marginRight16}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>
|
||||
{loc.wallets.details_master_fingerprint.toLowerCase()}
|
||||
</Text>
|
||||
<BlueText>{masterFingerprint ?? <ActivityIndicator />}</BlueText>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.row}>
|
||||
{wallet.allowMasterFingerprint() && (
|
||||
<View style={styles.marginRight16}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_master_fingerprint.toLowerCase()}</Text>
|
||||
<BlueText>{masterFingerprint ?? <ActivityIndicator />}</BlueText>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{derivationPath && (
|
||||
<View>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_derivation_path}</Text>
|
||||
<BlueText testID="DerivationPath">{derivationPath}</BlueText>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{derivationPath && (
|
||||
<View>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_derivation_path}</Text>
|
||||
<BlueText testID="DerivationPath">{derivationPath}</BlueText>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</BlueCard>
|
||||
{(wallet instanceof AbstractHDElectrumWallet || (wallet.type === WatchOnlyWallet.type && wallet.isHd())) && (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Keyboard, Platform, StyleSheet, Switch, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Keyboard, Platform, StyleSheet, TouchableWithoutFeedback, View, ScrollView } from 'react-native';
|
||||
|
||||
import {
|
||||
BlueButtonLink,
|
||||
|
@ -8,24 +8,25 @@ import {
|
|||
BlueFormLabel,
|
||||
BlueFormMultiInput,
|
||||
BlueSpacing20,
|
||||
BlueText,
|
||||
} from '../../BlueComponents';
|
||||
import Button from '../../components/Button';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import loc from '../../loc';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const WalletsImport = () => {
|
||||
const navigation = useNavigation();
|
||||
const navigation = useExtendedNavigation();
|
||||
const { colors } = useTheme();
|
||||
const route = useRoute();
|
||||
const label = route?.params?.label ?? '';
|
||||
const triggerImport = route?.params?.triggerImport ?? false;
|
||||
const scannedData = route?.params?.scannedData ?? '';
|
||||
const { isAdvancedModeEnabled } = useSettings();
|
||||
const [importText, setImportText] = useState(label);
|
||||
const [isToolbarVisibleForAndroid, setIsToolbarVisibleForAndroid] = useState(false);
|
||||
const [, setSpeedBackdoor] = useState(0);
|
||||
|
@ -33,23 +34,18 @@ const WalletsImport = () => {
|
|||
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
paddingTop: 10,
|
||||
backgroundColor: colors.elevated,
|
||||
flex: 1,
|
||||
},
|
||||
center: {
|
||||
flex: 1,
|
||||
marginHorizontal: 16,
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 16,
|
||||
marginTop: 10,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
});
|
||||
|
||||
const onBlur = () => {
|
||||
|
@ -58,18 +54,18 @@ const WalletsImport = () => {
|
|||
return valueWithSingleWhitespace;
|
||||
};
|
||||
|
||||
useKeyboard({
|
||||
onKeyboardDidShow: () => {
|
||||
setIsToolbarVisibleForAndroid(true);
|
||||
},
|
||||
onKeyboardDidHide: () => {
|
||||
setIsToolbarVisibleForAndroid(false);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
enableBlur();
|
||||
|
||||
const showSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', () =>
|
||||
setIsToolbarVisibleForAndroid(true),
|
||||
);
|
||||
const hideSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', () =>
|
||||
setIsToolbarVisibleForAndroid(false),
|
||||
);
|
||||
return () => {
|
||||
showSubscription.remove();
|
||||
hideSubscription.remove();
|
||||
disableBlur();
|
||||
};
|
||||
}, [disableBlur, enableBlur]);
|
||||
|
@ -125,21 +121,51 @@ const WalletsImport = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const toolTipOnPressMenuItem = useCallback(
|
||||
menuItem => {
|
||||
if (menuItem === CommonToolTipActions.Passphrase.id) {
|
||||
setAskPassphrase(!askPassphrase);
|
||||
} else if (menuItem === CommonToolTipActions.SearchAccount.id) {
|
||||
setSearchAccounts(!searchAccounts);
|
||||
}
|
||||
},
|
||||
[askPassphrase, searchAccounts],
|
||||
);
|
||||
|
||||
// ToolTipMenu actions for advanced options
|
||||
const toolTipActions = useMemo(() => {
|
||||
const askPassphraseAction = CommonToolTipActions.Passphrase;
|
||||
askPassphraseAction.menuState = askPassphrase;
|
||||
|
||||
const searchAccountsAction = CommonToolTipActions.SearchAccount;
|
||||
searchAccountsAction.menuState = searchAccounts;
|
||||
return [askPassphraseAction, searchAccountsAction];
|
||||
}, [askPassphrase, searchAccounts]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
testID="HeaderRightButton"
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={toolTipOnPressMenuItem}
|
||||
actions={toolTipActions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
||||
</ToolTipMenu>
|
||||
),
|
||||
[toolTipOnPressMenuItem, toolTipActions, colors.foregroundColor],
|
||||
);
|
||||
|
||||
// Adding the ToolTipMenu to the header
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => HeaderRight,
|
||||
});
|
||||
}, [askPassphrase, searchAccounts, colors.foregroundColor, navigation, toolTipActions, HeaderRight]);
|
||||
|
||||
const renderOptionsAndImportButton = (
|
||||
<>
|
||||
{isAdvancedModeEnabled && (
|
||||
<>
|
||||
<View style={styles.row}>
|
||||
<BlueText>{loc.wallets.import_passphrase}</BlueText>
|
||||
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<BlueText>{loc.wallets.import_search_accounts}</BlueText>
|
||||
<Switch testID="SearchAccounts" value={searchAccounts} onValueChange={setSearchAccounts} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.center}>
|
||||
<>
|
||||
|
@ -157,7 +183,14 @@ const WalletsImport = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SafeArea style={styles.root}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.root}
|
||||
automaticallyAdjustContentInsets
|
||||
automaticallyAdjustsScrollIndicatorInsets
|
||||
keyboardShouldPersistTaps
|
||||
automaticallyAdjustKeyboardInsets
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
>
|
||||
<BlueSpacing20 />
|
||||
<TouchableWithoutFeedback accessibilityRole="button" onPress={speedBackdoorTap} testID="SpeedBackdoor">
|
||||
<BlueFormLabel>{loc.wallets.import_explanation}</BlueFormLabel>
|
||||
|
@ -197,7 +230,7 @@ const WalletsImport = () => {
|
|||
/>
|
||||
),
|
||||
})}
|
||||
</SafeArea>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
import assert from 'assert';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
|
||||
import {
|
||||
expectToBeVisible,
|
||||
extractTextFromElementById,
|
||||
hashIt,
|
||||
helperCreateWallet,
|
||||
helperDeleteWallet,
|
||||
helperSwitchAdvancedMode,
|
||||
sleep,
|
||||
sup,
|
||||
yo,
|
||||
} from './helperz';
|
||||
import { expectToBeVisible, extractTextFromElementById, hashIt, helperCreateWallet, helperDeleteWallet, sleep, sup, yo } from './helperz';
|
||||
import { element } from 'detox';
|
||||
|
||||
/**
|
||||
|
@ -64,14 +54,8 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await element(by.id('QuickActionsSwitch')).tap();
|
||||
await element(by.id('QuickActionsSwitch')).tap();
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
|
||||
// enable AdvancedMode
|
||||
await element(by.id('AdvancedMode')).tap();
|
||||
await device.pressBack();
|
||||
// disable it:
|
||||
await element(by.id('GeneralSettings')).tap();
|
||||
await element(by.id('AdvancedMode')).tap();
|
||||
await device.pressBack();
|
||||
//
|
||||
// currency
|
||||
// change currency to ARS ($) and switch it back to USD ($)
|
||||
|
@ -472,7 +456,6 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
if (require('fs').existsSync(lockFile)) return console.warn('skipping as it previously passed on Travis');
|
||||
}
|
||||
await device.launchApp({ delete: true }); // reinstalling the app just for any case to clean up app's storage
|
||||
await helperSwitchAdvancedMode();
|
||||
await yo('WalletsList');
|
||||
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
||||
await sleep(200); // Wait until bounce animation finishes.
|
||||
|
@ -542,7 +525,6 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
|
||||
await device.pressBack();
|
||||
await helperDeleteWallet('Multisig Vault');
|
||||
await helperSwitchAdvancedMode(); // turn off advanced mode
|
||||
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
|
||||
|
@ -686,9 +668,6 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await device.launchApp({ delete: true }); // reinstalling the app just for any case to clean up app's storage
|
||||
await yo('WalletsList');
|
||||
|
||||
// enable AdvancedMode to see derivation path in wallet details
|
||||
await helperSwitchAdvancedMode();
|
||||
|
||||
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
||||
await sleep(200); // Wait until bounce animation finishes.
|
||||
// going to Import Wallet screen and importing mnemonic
|
||||
|
@ -698,8 +677,10 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await element(by.id('MnemonicInput')).replaceText(
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
);
|
||||
await element(by.id('AskPassphrase')).tap();
|
||||
await element(by.id('SearchAccounts')).tap();
|
||||
await element(by.id('HeaderRightButton')).tap();
|
||||
await element(by.text('Passphrase')).tap();
|
||||
await element(by.id('HeaderRightButton')).tap();
|
||||
await element(by.text('Search accounts')).tap();
|
||||
await element(by.id('DoImport')).tap();
|
||||
await sleep(1000);
|
||||
|
||||
|
@ -734,7 +715,6 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
await helperDeleteWallet('Imported HD Legacy (BIP44 P2PKH)');
|
||||
await helperSwitchAdvancedMode();
|
||||
|
||||
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
|
|
|
@ -382,6 +382,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
if (!(await getSwitchValue('BIP47Switch'))) {
|
||||
await expect(element(by.text('Contacts'))).not.toBeVisible();
|
||||
await element(by.id('BIP47Switch')).tap();
|
||||
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
|
||||
await expect(element(by.text('Contacts'))).toBeVisible();
|
||||
await element(by.text('Save')).tap(); // automatically goes back 1 screen
|
||||
await element(by.text('OK')).tap();
|
||||
|
|
|
@ -142,11 +142,3 @@ export async function helperCreateWallet(walletName) {
|
|||
await element(by.id('WalletsList')).swipe('right', 'fast', 1); // in case emu screen is small and it doesnt fit
|
||||
await expect(element(by.id(walletName || 'cr34t3d'))).toBeVisible();
|
||||
}
|
||||
|
||||
export async function helperSwitchAdvancedMode() {
|
||||
await element(by.id('SettingsButton')).tap();
|
||||
await element(by.id('GeneralSettings')).tap();
|
||||
await element(by.id('AdvancedMode')).tap();
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,12 @@ const keys = {
|
|||
ViewInBitcoin: 'viewInBitcoin',
|
||||
ViewInSats: 'viewInSats',
|
||||
ViewInFiat: 'viewInFiat',
|
||||
Entropy: 'entropy',
|
||||
SearchAccount: 'searchAccount',
|
||||
Passphrase: 'passphrase',
|
||||
MoreInfo: 'moreInfo',
|
||||
SaveChanges: 'saveChanges',
|
||||
PaymentsCode: 'paymentsCode',
|
||||
};
|
||||
|
||||
const icons = {
|
||||
|
@ -35,6 +41,24 @@ const icons = {
|
|||
ViewInFiat: {
|
||||
iconValue: 'coloncurrencysign.circle',
|
||||
},
|
||||
Entropy: {
|
||||
iconValue: 'dice',
|
||||
},
|
||||
SearchAccount: {
|
||||
iconValue: 'magnifyingglass',
|
||||
},
|
||||
Passphrase: {
|
||||
iconValue: 'rectangle.and.pencil.and.ellipsis',
|
||||
},
|
||||
MoreInfo: {
|
||||
iconValue: 'info.circle',
|
||||
},
|
||||
SaveChanges: {
|
||||
iconValue: 'checkmark',
|
||||
},
|
||||
PaymentsCode: {
|
||||
iconValue: 'qrcode',
|
||||
},
|
||||
};
|
||||
|
||||
export const CommonToolTipActions = {
|
||||
|
@ -78,7 +102,6 @@ export const CommonToolTipActions = {
|
|||
text: loc.total_balance_view.view_in_fiat,
|
||||
icon: icons.ViewInFiat,
|
||||
},
|
||||
|
||||
ViewInSats: {
|
||||
id: keys.ViewInSats,
|
||||
text: loc.total_balance_view.view_in_sats,
|
||||
|
@ -89,4 +112,39 @@ export const CommonToolTipActions = {
|
|||
text: loc.total_balance_view.view_in_bitcoin,
|
||||
icon: icons.ViewInBitcoin,
|
||||
},
|
||||
Entropy: {
|
||||
id: keys.Entropy,
|
||||
text: loc.wallets.add_entropy_provide,
|
||||
icon: icons.Entropy,
|
||||
menuState: false,
|
||||
},
|
||||
SearchAccount: {
|
||||
id: keys.SearchAccount,
|
||||
text: loc.wallets.import_search_accounts,
|
||||
icon: icons.SearchAccount,
|
||||
menuState: false,
|
||||
},
|
||||
Passphrase: {
|
||||
id: keys.Passphrase,
|
||||
text: loc.wallets.import_passphrase,
|
||||
icon: icons.Passphrase,
|
||||
menuState: false,
|
||||
},
|
||||
MoreInfo: {
|
||||
id: keys.MoreInfo,
|
||||
text: loc.wallets.more_info,
|
||||
icon: icons.MoreInfo,
|
||||
hidden: false,
|
||||
},
|
||||
SaveChanges: {
|
||||
id: keys.SaveChanges,
|
||||
text: loc._.save,
|
||||
icon: icons.SaveChanges,
|
||||
},
|
||||
PaymentCode: {
|
||||
id: keys.PaymentsCode,
|
||||
text: loc.bip47.purpose,
|
||||
icon: icons.PaymentsCode,
|
||||
menuState: false,
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue