diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index e9c0363a8f..fc7986dc5e 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -14,7 +14,7 @@ jobs: - name: Specify node version uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Use npm caches uses: actions/cache@v4 diff --git a/Gemfile b/Gemfile index 7e60e03851..7e4d042ad6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source "https://rubygems.org" # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version ruby "3.1.6" -gem "cocoapods", ">= 1.13", "< 1.15" +gem 'rubyzip', '2.3.0' +gem "cocoapods", "1.15.2" gem "activesupport", ">= 6.1.7.3", "< 7.1.0" gem "fastlane" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 60e4918247..2436e3220b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,7 +24,7 @@ GEM aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.84.0) + aws-sdk-kms (1.85.0) aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.152.3) @@ -36,10 +36,10 @@ GEM babosa (1.0.4) base64 (0.2.0) claide (1.1.0) - cocoapods (1.14.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.3) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -54,7 +54,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.3) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -117,7 +117,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.220.0) + fastlane (2.221.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -206,7 +206,7 @@ GEM concurrent-ruby (~> 1.0) jmespath (1.6.2) json (2.7.2) - jwt (2.8.1) + jwt (2.8.2) base64 mini_magick (4.13.1) mini_mime (1.1.5) @@ -234,7 +234,7 @@ GEM rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) - rubyzip (2.3.2) + rubyzip (2.3.0) security (0.1.5) signet (0.19.0) addressable (~> 2.8) @@ -277,8 +277,9 @@ PLATFORMS DEPENDENCIES activesupport (>= 6.1.7.3, < 7.1.0) - cocoapods (>= 1.13, < 1.15) + cocoapods (= 1.15.2) fastlane + rubyzip (= 2.3.0) RUBY VERSION ruby 3.1.6p260 diff --git a/blue_modules/showPopupMenu.android.ts b/blue_modules/showPopupMenu.android.ts deleted file mode 100644 index 3dcdfe16f1..0000000000 --- a/blue_modules/showPopupMenu.android.ts +++ /dev/null @@ -1,32 +0,0 @@ -// @ts-ignore: Ignore -import type { Element } from 'react'; -import { findNodeHandle, Text, TouchableNativeFeedback, TouchableWithoutFeedback, UIManager, View } from 'react-native'; - -type PopupMenuItem = { id?: any; label: string }; -type OnPopupMenuItemSelect = (selectedPopupMenuItem: PopupMenuItem) => void; -type PopupAnchor = Element; -type PopupMenuOptions = { onCancel?: () => void }; - -function showPopupMenu( - items: PopupMenuItem[], - onSelect: OnPopupMenuItemSelect, - anchor: PopupAnchor, - { onCancel }: PopupMenuOptions = {}, -): void { - UIManager.showPopupMenu( - // @ts-ignore: Ignore - findNodeHandle(anchor), - items.map(item => item.label), - function () { - if (onCancel) onCancel(); - }, - function (eventName: 'dismissed' | 'itemSelected', selectedIndex?: number) { - // @ts-ignore: Ignore - if (eventName === 'itemSelected') onSelect(items[selectedIndex]); - else onCancel && onCancel(); - }, - ); -} - -export type { OnPopupMenuItemSelect, PopupMenuItem, PopupMenuOptions }; -export default showPopupMenu; diff --git a/class/wallets/watch-only-wallet.ts b/class/wallets/watch-only-wallet.ts index 5b0b6a5ebb..72dc4b6964 100644 --- a/class/wallets/watch-only-wallet.ts +++ b/class/wallets/watch-only-wallet.ts @@ -18,6 +18,7 @@ export class WatchOnlyWallet extends LegacyWallet { public readonly type = WatchOnlyWallet.type; // @ts-ignore: override public readonly typeReadable = WatchOnlyWallet.typeReadable; + public isWatchOnlyWarningVisible = true; public _hdWalletInstance?: THDWalletForWatchOnly; use_with_hardware_wallet = false; diff --git a/components/AddressInput.tsx b/components/AddressInput.tsx index b12f8e36fa..fbff8e926f 100644 --- a/components/AddressInput.tsx +++ b/components/AddressInput.tsx @@ -1,13 +1,13 @@ -import React from 'react'; -import { Image, Keyboard, StyleSheet, Text, TextInput, View } from 'react-native'; +import React, { useCallback, useMemo } from 'react'; +import { Image, Keyboard, Platform, StyleSheet, Text, TextInput, View } from 'react-native'; import { scanQrHelper } from '../helpers/scan-qr'; import loc from '../loc'; import { useTheme } from './themes'; -import ToolTipMenu from './TooltipMenu'; import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs'; import Clipboard from '@react-native-clipboard/clipboard'; import presentAlert from './Alert'; +import ToolTipMenu from './TooltipMenu'; interface AddressInputProps { isLoading?: boolean; @@ -69,52 +69,63 @@ const AddressInput = ({ Keyboard.dismiss(); }; - const onMenuItemPressed = (action: string) => { - if (onBarScanned === undefined) throw new Error('onBarScanned is required'); - switch (action) { - case actionKeys.ScanQR: - scanButtonTapped(); - if (launchedBy) { - scanQrHelper(launchedBy) - .then(value => onBarScanned({ data: value })) + const toolTipOnPress = useCallback(async () => { + await scanButtonTapped(); + Keyboard.dismiss(); + if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value })); + }, [launchedBy, onBarScanned, scanButtonTapped]); + + const onMenuItemPressed = useCallback( + (action: string) => { + if (onBarScanned === undefined) throw new Error('onBarScanned is required'); + switch (action) { + case actionKeys.ScanQR: + scanButtonTapped(); + if (launchedBy) { + scanQrHelper(launchedBy) + .then(value => onBarScanned({ data: value })) + .catch(error => { + presentAlert({ message: error.message }); + }); + } + + break; + case actionKeys.CopyFromClipboard: + Clipboard.getString() + .then(onChangeText) + .catch(error => { + presentAlert({ message: error.message }); + }); + break; + case actionKeys.ChoosePhoto: + showImagePickerAndReadImage() + .then(value => { + if (value) { + onChangeText(value); + } + }) .catch(error => { presentAlert({ message: error.message }); }); - } + break; + case actionKeys.ImportFile: + showFilePickerAndReadFile() + .then(value => { + if (value.data) { + onChangeText(value.data); + } + }) + .catch(error => { + presentAlert({ message: error.message }); + }); + break; + } + Keyboard.dismiss(); + }, + [launchedBy, onBarScanned, onChangeText, scanButtonTapped], + ); - break; - case actionKeys.CopyFromClipboard: - Clipboard.getString() - .then(onChangeText) - .catch(error => { - presentAlert({ message: error.message }); - }); - break; - case actionKeys.ChoosePhoto: - showImagePickerAndReadImage() - .then(value => { - if (value) { - onChangeText(value); - } - }) - .catch(error => { - presentAlert({ message: error.message }); - }); - break; - case actionKeys.ImportFile: - showFilePickerAndReadFile() - .then(value => { - if (value.data) { - onChangeText(value.data); - } - }) - .catch(error => { - presentAlert({ message: error.message }); - }); - break; - } - Keyboard.dismiss(); - }; + const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]); return ( @@ -141,12 +152,8 @@ const AddressInput = ({ onPressMenuItem={onMenuItemPressed} testID="BlueAddressInputScanQrButton" disabled={isLoading} - onPress={async () => { - await scanButtonTapped(); - Keyboard.dismiss(); - if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value })); - }} - style={[styles.scan, stylesHook.scan]} + onPress={toolTipOnPress} + buttonStyle={buttonStyle} accessibilityLabel={loc.send.details_scan} accessibilityHint={loc.send.details_scan_hint} > @@ -202,20 +209,16 @@ const actionKeys = { const actionIcons = { ScanQR: { - iconType: 'SYSTEM', - iconValue: 'qrcode', + iconValue: Platform.OS === 'ios' ? 'qrcode' : 'ic_menu_camera', }, ImportFile: { - iconType: 'SYSTEM', iconValue: 'doc', }, ChoosePhoto: { - iconType: 'SYSTEM', - iconValue: 'photo', + iconValue: Platform.OS === 'ios' ? 'photo' : 'ic_menu_gallery', }, Clipboard: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', + iconValue: Platform.OS === 'ios' ? 'doc' : 'ic_menu_file', }, }; diff --git a/components/QRCodeComponent.tsx b/components/QRCodeComponent.tsx index 72f0a65b13..24949702c4 100644 --- a/components/QRCodeComponent.tsx +++ b/components/QRCodeComponent.tsx @@ -1,5 +1,5 @@ import Clipboard from '@react-native-clipboard/clipboard'; -import React, { useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import { Platform, StyleSheet, View } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import Share from 'react-native-share'; @@ -22,11 +22,9 @@ interface QRCodeComponentProps { const actionIcons: { [key: string]: ActionIcons } = { Share: { - iconType: 'SYSTEM', iconValue: 'square.and.arrow.up', }, Copy: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, }; @@ -76,13 +74,13 @@ const QRCodeComponent: React.FC = ({ }); }; - const onPressMenuItem = (id: string) => { + const onPressMenuItem = useCallback((id: string) => { if (id === actionKeys.Share) { handleShareQRCode(); } else if (id === actionKeys.Copy) { qrCode.current.toDataURL(Clipboard.setImage); } - }; + }, []); const renderQRCode = ( = ({ onMenuWillHide, onMenuWillShow, }) => { - const handlePressMenuItem = async (actionId: string) => { - if (beforeOnPress) { - await beforeOnPress(); - } - const action = actions.find(a => a.id === actionId); + const handlePressMenuItem = useCallback( + async (actionId: string) => { + if (beforeOnPress) { + await beforeOnPress(); + } + const action = actions.find(a => a.id === actionId); - if (action?.id === 'save') { - await fs.writeFileAndExport(fileName, fileContent, false).finally(() => { - afterOnPress?.(); - }); - } else if (action?.id === 'share') { - await fs.writeFileAndExport(fileName, fileContent, true).finally(() => { - afterOnPress?.(); - }); - } - }; + if (action?.id === 'save') { + await fs.writeFileAndExport(fileName, fileContent, false).finally(() => { + afterOnPress?.(); + }); + } else if (action?.id === 'share') { + await fs.writeFileAndExport(fileName, fileContent, true).finally(() => { + afterOnPress?.(); + }); + } + }, + [afterOnPress, beforeOnPress, fileContent, fileName], + ); return ( { - console.log('dismissMenu Not implemented'); -}; -const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => void }>) => { - const menuRef = useRef(null); - const { - actions, - children, - onPressMenuItem, - isMenuPrimaryAction = false, - buttonStyle = {}, - enableAndroidRipple = true, - disabled = false, - onPress, - ...restProps - } = props; - - const handleToolTipSelection = useCallback( - (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 ( - - {children} - - ); -}; - -const ToolTipMenu = forwardRef(BaseToolTipMenu); - -export default ToolTipMenu; diff --git a/components/TooltipMenu.ios.tsx b/components/TooltipMenu.ios.tsx deleted file mode 100644 index 90eb899583..0000000000 --- a/components/TooltipMenu.ios.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { forwardRef, Ref, useCallback, useMemo } from 'react'; -import { TouchableOpacity } from 'react-native'; -import { ContextMenuButton, ContextMenuView, RenderItem } from 'react-native-ios-context-menu'; - -import { Action, ToolTipMenuProps } from './types'; - -const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { - const { - title = '', - isButton = false, - isMenuPrimaryAction = false, - renderPreview, - disabled = false, - onPress, - onMenuWillShow, - onMenuWillHide, - buttonStyle, - onPressMenuItem, - ...restProps - } = 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 = () => ( - - - {props.children} - - - ); - - const renderContextMenuView = () => ( - - {onPress ? ( - - {props.children} - - ) : ( - props.children - )} - - ); - - return isMenuPrimaryAction && onPress ? ( - - {renderContextMenuButton()} - - ) : isButton ? ( - renderContextMenuButton() - ) : ( - renderContextMenuView() - ); -}; - -const ToolTipMenu = forwardRef(BaseToolTipMenu); - -export default ToolTipMenu; diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx index 629dc02c4e..93240927c6 100644 --- a/components/TooltipMenu.tsx +++ b/components/TooltipMenu.tsx @@ -1,12 +1,144 @@ -import { forwardRef, Ref } from 'react'; +import React, { Ref, useCallback, useMemo } from 'react'; +import { Platform, Pressable, TouchableOpacity, View } from 'react-native'; +import { ContextMenuView, RenderItem, OnPressMenuItemEventObject, MenuState, IconConfig } from 'react-native-ios-context-menu'; +import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu'; +import { ToolTipMenuProps, Action } from './types'; -import { ToolTipMenuProps } from './types'; +const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref) => { + const { + title = '', + isMenuPrimaryAction = false, + renderPreview, + disabled = false, + onPress, + onMenuWillShow, + onMenuWillHide, + buttonStyle, + onPressMenuItem, + children, + isButton = false, + ...restProps + } = props; -const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { - console.debug('ToolTipMenu.tsx ref:', ref); - return props.children; -}; + const mapMenuItemForContextMenuView = useCallback((action: Action) => { + return { + actionKey: action.id.toString(), + actionTitle: action.text, + icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined, + state: action.menuStateOn ? ('on' as MenuState) : ('off' as MenuState), + attributes: action.disabled ? ['disabled'] : [], + }; + }, []); -const ToolTipMenu = forwardRef(BaseToolTipMenu); + const mapMenuItemForMenuView = useCallback((action: Action): MenuAction => { + return { + id: action.id.toString(), + title: action.text, + image: action.menuStateOn && Platform.OS === 'android' ? 'checkbox_on_background' : action.icon?.iconValue || undefined, + state: action.menuStateOn ? ('on' as MenuState) : undefined, + attributes: { disabled: action.disabled }, + }; + }, []); + + const contextMenuItems = useMemo(() => { + const flattenedActions = props.actions.flat(); + return flattenedActions.map(mapMenuItemForContextMenuView); + }, [props.actions, mapMenuItemForContextMenuView]); + + const menuViewItemsIOS = useMemo(() => { + return props.actions.map(actionGroup => { + if (Array.isArray(actionGroup)) { + return { + id: actionGroup[0].id.toString(), + title: '', + subactions: actionGroup.map(mapMenuItemForMenuView), + displayInline: true, + }; + } else { + return mapMenuItemForMenuView(actionGroup); + } + }); + }, [props.actions, mapMenuItemForMenuView]); + + const menuViewItemsAndroid = useMemo(() => { + const mergedActions = props.actions.flat(); + return mergedActions.map(mapMenuItemForMenuView); + }, [props.actions, mapMenuItemForMenuView]); + + const handlePressMenuItemForContextMenuView = useCallback( + (event: OnPressMenuItemEventObject) => { + onPressMenuItem(event.nativeEvent.actionKey); + }, + [onPressMenuItem], + ); + + const handlePressMenuItemForMenuView = useCallback( + ({ nativeEvent }: NativeActionEvent) => { + onPressMenuItem(nativeEvent.event); + }, + [onPressMenuItem], + ); + + const renderContextMenuView = () => { + console.debug('ToolTipMenu.tsx rendering: renderContextMenuView'); + return ( + + {onPress ? ( + + {children} + + ) : ( + children + )} + + ); + }; + + const renderMenuView = () => { + console.debug('ToolTipMenu.tsx rendering: renderMenuView'); + return ( + + + {isMenuPrimaryAction || isButton ? ( + + {children} + + ) : ( + children + )} + + + ); + }; + + return Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView(); +}); export default ToolTipMenu; diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx index ca4598c648..8ceea39bd9 100644 --- a/components/TransactionListItem.tsx +++ b/components/TransactionListItem.tsx @@ -16,12 +16,12 @@ import { BitcoinUnit } from '../models/bitcoinUnits'; import { useSettings } from '../hooks/context/useSettings'; import ListItem from './ListItem'; import { useTheme } from './themes'; -import ToolTipMenu from './TooltipMenu'; import { Action, ToolTipMenuProps } from './types'; import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList'; import { useStorage } from '../hooks/context/useStorage'; +import ToolTipMenu from './TooltipMenu'; interface TransactionListItemProps { itemPriceUnit: BitcoinUnit; @@ -339,7 +339,7 @@ export const TransactionListItem: React.FC = React.mem // eslint-disable-next-line react-hooks/exhaustive-deps }, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]); return ( - + verifyIfWalletAllowsOnchainAddress(); }, [wallet, verifyIfWalletAllowsOnchainAddress]); - const handleCopyPress = () => { + const handleCopyPress = useCallback(() => { const value = formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit()); if (value) { Clipboard.setString(value); } - }; + }, [wallet]); - const handleBalanceVisibility = () => { + const handleBalanceVisibility = useCallback(() => { onWalletBalanceVisibilityChange?.(!wallet.hideBalance); - }; + }, [onWalletBalanceVisibilityChange, wallet.hideBalance]); const updateWalletWithNewUnit = (w: TWallet, newPreferredUnit: BitcoinUnit) => { w.preferredBalanceUnit = newPreferredUnit; @@ -101,19 +100,40 @@ const TransactionsNavigationHeader: React.FC onWalletUnitChange?.(updatedWallet); }; - const handleManageFundsPressed = (actionKeyID?: string) => { - if (onManageFundsPressed) { - onManageFundsPressed(actionKeyID); - } - }; + const handleManageFundsPressed = useCallback( + (actionKeyID?: string) => { + if (onManageFundsPressed) { + onManageFundsPressed(actionKeyID); + } + }, + [onManageFundsPressed], + ); - const onPressMenuItem = (id: string) => { - if (id === 'walletBalanceVisibility') { - handleBalanceVisibility(); - } else if (id === 'copyToClipboard') { - handleCopyPress(); - } - }; + const onPressMenuItem = useCallback( + (id: string) => { + if (id === 'walletBalanceVisibility') { + handleBalanceVisibility(); + } else if (id === 'copyToClipboard') { + handleCopyPress(); + } + }, + [handleBalanceVisibility, handleCopyPress], + ); + + const toolTipActions = useMemo(() => { + return [ + { + id: actionKeys.Refill, + text: loc.lnd.refill, + icon: actionIcons.Refill, + }, + { + id: actionKeys.RefillWithExternalWallet, + text: loc.lnd.refill_external, + icon: actionIcons.RefillWithExternalWallet, + }, + ]; + }, []); const balance = useMemo(() => { const hideBalance = wallet.hideBalance; @@ -126,6 +146,35 @@ const TransactionsNavigationHeader: React.FC // eslint-disable-next-line react-hooks/exhaustive-deps }, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]); + const toolTipWalletBalanceActions = useMemo(() => { + return wallet.hideBalance + ? [ + { + id: 'walletBalanceVisibility', + text: loc.transactions.details_balance_show, + icon: { + iconValue: 'eye', + }, + }, + ] + : [ + { + id: 'walletBalanceVisibility', + text: loc.transactions.details_balance_hide, + icon: { + iconValue: 'eye.slash', + }, + }, + { + id: 'copyToClipboard', + text: loc.transactions.details_copy, + icon: { + iconValue: 'doc.on.doc', + }, + }, + ]; + }, [wallet.hideBalance]); + return ( isMenuPrimaryAction isButton enableAndroidRipple={false} - ref={menuRef} buttonStyle={styles.walletBalance} onPressMenuItem={onPressMenuItem} - actions={ - wallet.hideBalance - ? [ - { - id: 'walletBalanceVisibility', - text: loc.transactions.details_balance_show, - icon: { - iconType: 'SYSTEM', - iconValue: 'eye', - }, - }, - ] - : [ - { - id: 'walletBalanceVisibility', - text: loc.transactions.details_balance_hide, - icon: { - iconType: 'SYSTEM', - iconValue: 'eye.slash', - }, - }, - { - id: 'copyToClipboard', - text: loc.transactions.details_copy, - icon: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, - }, - ] - } + actions={toolTipWalletBalanceActions} > {wallet.hideBalance ? ( @@ -223,18 +241,7 @@ const TransactionsNavigationHeader: React.FC isMenuPrimaryAction isButton onPressMenuItem={handleManageFundsPressed} - actions={[ - { - id: actionKeys.Refill, - text: loc.lnd.refill, - icon: actionIcons.Refill, - }, - { - id: actionKeys.RefillWithExternalWallet, - text: loc.lnd.refill_external, - icon: actionIcons.RefillWithExternalWallet, - }, - ]} + actions={toolTipActions} buttonStyle={styles.manageFundsButton} > {loc.lnd.title} @@ -333,23 +340,18 @@ export const actionKeys = { export const actionIcons = { Eye: { - iconType: 'SYSTEM', iconValue: 'eye', }, EyeSlash: { - iconType: 'SYSTEM', iconValue: 'eye.slash', }, Clipboard: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, Refill: { - iconType: 'SYSTEM', iconValue: 'goforward.plus', }, RefillWithExternalWallet: { - iconType: 'SYSTEM', iconValue: 'qrcode', }, }; diff --git a/components/WatchOnlyWarning.tsx b/components/WatchOnlyWarning.tsx new file mode 100644 index 0000000000..047ea39287 --- /dev/null +++ b/components/WatchOnlyWarning.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { Icon } from '@rneui/themed'; +import loc from '../loc'; + +interface Props { + handleDismiss: () => void; + isLoading?: boolean; +} + +const WatchOnlyWarning: React.FC = ({ handleDismiss, isLoading }) => { + return ( + + + + + + + {loc.transactions.watchOnlyWarningTitle} + {loc.transactions.watchOnlyWarningDescription} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#ff4c4c', + padding: 16, + margin: 16, + borderRadius: 8, + position: 'relative', + }, + dismissButton: { + position: 'absolute', + top: 8, + right: 8, + backgroundColor: 'black', + borderRadius: 15, + width: 30, + height: 30, + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + }, + content: { + alignItems: 'center', + }, + title: { + color: 'white', + fontWeight: 'bold', + marginBottom: 8, + marginTop: 8, + }, + description: { + color: 'white', + textAlign: 'center', + }, +}); + +export default WatchOnlyWarning; diff --git a/components/addresses/AddressItem.tsx b/components/addresses/AddressItem.tsx index 20821906ab..dd93d5dd52 100644 --- a/components/addresses/AddressItem.tsx +++ b/components/addresses/AddressItem.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; import Clipboard from '@react-native-clipboard/clipboard'; import { useNavigation } from '@react-navigation/native'; import { StyleSheet, Text, View } from 'react-native'; @@ -12,12 +12,12 @@ import { BitcoinUnit } from '../../models/bitcoinUnits'; import presentAlert from '../Alert'; import QRCodeComponent from '../QRCodeComponent'; import { useTheme } from '../themes'; -import TooltipMenu from '../TooltipMenu'; -import { Action, ToolTipMenuProps } from '../types'; +import { Action } from '../types'; import { AddressTypeBadge } from './AddressTypeBadge'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList'; import { useStorage } from '../../hooks/context/useStorage'; +import ToolTipMenu from '../TooltipMenu'; interface AddressItemProps { // todo: fix `any` after addresses.js is converted to the church of holy typescript @@ -57,16 +57,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad const { navigate } = useNavigation(); - const menuRef = useRef(); - - const dismissMenu = () => { - if (menuRef.current?.dismissMenu) { - menuRef.current.dismissMenu(); - } - }; - const navigateToReceive = () => { - dismissMenu(); navigate('ReceiveDetailsRoot', { screen: 'ReceiveDetails', params: { @@ -77,7 +68,6 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad }; const navigateToSignVerify = () => { - dismissMenu(); navigate('SignVerifyRoot', { screen: 'SignVerify', params: { @@ -145,13 +135,13 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad const render = () => { return ( - @@ -170,7 +160,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad - + ); }; @@ -186,19 +176,15 @@ const actionKeys = { const actionIcons = { Signature: { - iconType: 'SYSTEM', iconValue: 'signature', }, Share: { - iconType: 'SYSTEM', iconValue: 'square.and.arrow.up', }, Clipboard: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, ExportPrivateKey: { - iconType: 'SYSTEM', iconValue: 'key', }, }; diff --git a/components/icons/SettingsButton.tsx b/components/icons/SettingsButton.tsx index 1cb2fdea99..77d5ae0331 100644 --- a/components/icons/SettingsButton.tsx +++ b/components/icons/SettingsButton.tsx @@ -17,7 +17,7 @@ const SettingsButton = () => { accessibilityRole="button" accessibilityLabel={loc.settings.default_title} testID="SettingsButton" - style={[style.buttonStyle, { backgroundColor: colors.darkGray }]} + style={[style.buttonStyle, { backgroundColor: colors.lightButton }]} onPress={onPress} > diff --git a/components/types.ts b/components/types.ts index 4c36718c70..8718353e3d 100644 --- a/components/types.ts +++ b/components/types.ts @@ -3,13 +3,13 @@ import { AccessibilityRole, ViewStyle } from 'react-native'; export interface Action { id: string | number; text: string; - icon: { - iconType: string; + icon?: { iconValue: string; }; menuTitle?: string; menuStateOn?: boolean; disabled?: boolean; + displayInline?: boolean; } export interface ToolTipMenuProps { @@ -30,7 +30,7 @@ export interface ToolTipMenuProps { style?: ViewStyle | ViewStyle[]; accessibilityLabel?: string; accessibilityHint?: string; - buttonStyle?: ViewStyle; + buttonStyle?: ViewStyle | ViewStyle[]; onMenuWillShow?: () => void; onMenuWillHide?: () => void; } diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 1c61778810..c03da42a84 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -169,7 +169,7 @@ B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.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 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -491,7 +491,7 @@ files = ( 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, - C978A716948AB7DEC5B6F677 /* (null) in Frameworks */, + C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */, 773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -571,7 +571,6 @@ 13B07FAE1A68108700A75B9A /* BlueWallet */ = { isa = PBXGroup; children = ( - B4AB21052B61D8890080440C /* SplashScreen */, B461B850299599F800E431AA /* AppDelegate.h */, B461B851299599F800E431AA /* AppDelegate.mm */, 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */, @@ -911,13 +910,6 @@ path = BlueWalletUITests; sourceTree = ""; }; - B4AB21052B61D8890080440C /* SplashScreen */ = { - isa = PBXGroup; - children = ( - ); - name = SplashScreen; - sourceTree = ""; - }; FAA856B639C61E61D2CF90A8 /* Pods */ = { isa = PBXGroup; children = ( @@ -1169,9 +1161,9 @@ ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( - 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */, + 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */, B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */, - B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */, + B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; @@ -1852,7 +1844,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1906,7 +1898,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -2161,7 +2153,7 @@ OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -2219,7 +2211,7 @@ REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -2360,7 +2352,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -2406,7 +2398,7 @@ SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -2554,7 +2546,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,6"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -2602,7 +2594,7 @@ SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,6"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -2686,7 +2678,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */ = { + 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EFPrefix/EFQRCode.git"; requirement = { @@ -2702,7 +2694,7 @@ version = 6.28.1; }; }; - B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */ = { + B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/evgenyneu/keychain-swift.git"; requirement = { @@ -2715,7 +2707,7 @@ /* Begin XCSwiftPackageProductDependency section */ 6DFC806F24EA0B6C007B8700 /* EFQRCode */ = { isa = XCSwiftPackageProductDependency; - package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */; + package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */; productName = EFQRCode; }; B41B76842B66B2FF002C48D5 /* Bugsnag */ = { @@ -2730,10 +2722,10 @@ }; B48A6A282C1DF01000030AB9 /* KeychainSwift */ = { isa = XCSwiftPackageProductDependency; - package = B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */; + package = B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */; productName = KeychainSwift; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} \ No newline at end of file +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b8acce3a80..f03a5fc238 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -339,6 +339,8 @@ PODS: - React-Core - react-native-ios-context-menu (1.15.3): - React-Core + - react-native-menu (1.1.2): + - React - react-native-qrcode-local-image (1.0.4): - React - react-native-randombytes (3.6.1): @@ -462,7 +464,7 @@ PODS: - React-perflogger (= 0.72.14) - ReactNativeCameraKit (13.0.0): - React-Core - - RealmJS (12.10.0): + - RealmJS (12.11.0): - React - rn-ldk (0.8.4): - React-Core @@ -478,7 +480,7 @@ PODS: - React-Core - RNFS (2.20.0): - React-Core - - RNGestureHandler (2.16.2): + - RNGestureHandler (2.17.0): - RCT-Folly (= 2021.07.22.00) - React-Core - RNHandoff (0.0.3): @@ -501,7 +503,7 @@ PODS: - RCT-Folly (= 2021.07.22.00) - React-Core - ReactCommon/turbomodule/core - - RNScreens (3.31.1): + - RNScreens (3.32.0): - RCT-Folly (= 2021.07.22.00) - React-Core - React-RCTImage @@ -551,6 +553,7 @@ DEPENDENCIES: - react-native-idle-timer (from `../node_modules/react-native-idle-timer`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`) + - "react-native-menu (from `../node_modules/@react-native-menu/menu`)" - "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - react-native-randombytes (from `../node_modules/react-native-randombytes`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -670,6 +673,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-picker" react-native-ios-context-menu: :path: "../node_modules/react-native-ios-context-menu" + react-native-menu: + :path: "../node_modules/@react-native-menu/menu" react-native-qrcode-local-image: :path: "../node_modules/@remobile/react-native-qrcode-local-image" react-native-randombytes: @@ -801,6 +806,7 @@ SPEC CHECKSUMS: react-native-idle-timer: ee2053f2cd458f6fef1db7bebe5098ca281cce07 react-native-image-picker: 1889c342e6a4ba089ff11ae0c3bf5cc30a3134d0 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 + react-native-menu: d32728a357dfb360cf01cd5979cf7713c5acbb95 react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-safe-area-context: a240ad4b683349e48b1d51fed1611138d1bdad97 @@ -824,7 +830,7 @@ SPEC CHECKSUMS: React-utils: 22a77b05da25ce49c744faa82e73856dcae1734e ReactCommon: ff94462e007c568d8cdebc32e3c97af86ec93bb5 ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb - RealmJS: f86da4f2c5b089d976db335f370449903ddc8fbb + RealmJS: 5f96d3e02420b5f579296c465a437f6e20026da9 rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643 RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37 @@ -832,7 +838,7 @@ SPEC CHECKSUMS: RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31 RNDeviceInfo: b899ce37a403a4dea52b7cb85e16e49c04a5b88e RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: 982741f345785f2927e7b28f67dc83679cf3bfc8 + RNGestureHandler: 2d351f93c68bf410fc0fe8d9ace7bdddb0c2e566 RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa RNKeychain: f1b48665a4646f61191eb048c4c05c58d9a7596f RNLocalize: b77875884750cb6a58cd6865863fe2ba2729b72b @@ -842,7 +848,7 @@ SPEC CHECKSUMS: RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 RNReanimated: d4f25b2a931c4f0b2bb12173a3096f02ea4cfb05 - RNScreens: b8d370282cdeae9df85dd5eab20c88eb5181243b + RNScreens: ad1c105ac9107cf1a613bf80889485458eb20bd7 RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c RNSVG: af3907ac5d4fa26a862b75a16d8f15bc74f2ceda RNVectorIcons: 32462e7c7e58fe457474fc79c4d7de3f0ef08d70 @@ -852,4 +858,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f19eea438501edfe85fb2fa51d40ba1b57540758 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/loc/en.json b/loc/en.json index 86e35c5bee..3eb436edfa 100644 --- a/loc/en.json +++ b/loc/en.json @@ -382,7 +382,9 @@ "txid": "Transaction ID", "from": "From: {counterparty}", "to": "To: {counterparty}", - "updating": "Updating..." + "updating": "Updating...", + "watchOnlyWarningTitle": "Scam Alert", + "watchOnlyWarningDescription": "Be aware that scammers usually use this type of wallet 'watch-only' to try to steal from users. This wallet you cannot control or send from it, only allows to watch the balance." }, "wallets": { "add_bitcoin": "Groestlcoin", diff --git a/loc/es_419.json b/loc/es_419.json index 70eb6a90de..e9d1d2399f 100644 --- a/loc/es_419.json +++ b/loc/es_419.json @@ -428,6 +428,7 @@ "details_show_xpub": "Mostrar el XPUB de la Billetera", "details_show_addresses": "Mostrar direcciones", "details_title": "Billetera", + "wallets": "Billeteras", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usar con Billetera de Hardware", "details_wallet_updated": "Billetera actualizada", diff --git a/metro.config.js b/metro.config.js index e91aba937c..72fcb3cbf0 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,17 +1,11 @@ +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); + /** - * Metro configuration for React Native - * https://github.com/facebook/react-native + * Metro configuration + * https://reactnative.dev/docs/metro * - * @format + * @type {import('metro-config').MetroConfig} */ +const config = {}; -module.exports = { - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, -}; +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/package.json b/package.json index ae9bcf39d4..5af79366c5 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@react-native-async-storage/async-storage": "1.23.1", "@react-native-clipboard/clipboard": "1.14.1", "@react-native-community/push-notification-ios": "1.11.0", + "@react-native-menu/menu": "1.1.2", "@react-navigation/drawer": "6.6.15", "@react-navigation/native": "6.1.17", "@react-navigation/native-stack": "6.9.26", @@ -128,7 +129,7 @@ "coinselect": "3.1.13", "crypto-js": "4.2.0", "dayjs": "1.11.11", - "detox": "20.22.2", + "detox": "20.23.0", "ecpairgrs": "2.0.1", "ecurve": "1.0.6", "electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc", @@ -154,7 +155,7 @@ "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-fs": "2.20.0", - "react-native-gesture-handler": "2.16.2", + "react-native-gesture-handler": "2.17.1", "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#7300b637c465c86e8db874c442e687950111da40", @@ -175,15 +176,15 @@ "react-native-rate": "1.2.12", "react-native-reanimated": "3.11.0", "react-native-safe-area-context": "4.10.5", - "react-native-screens": "3.31.1", + "react-native-screens": "3.32.0", "react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48", "react-native-share": "10.2.1", "react-native-svg": "13.14.1", - "react-native-tcp-socket": "6.0.6", + "react-native-tcp-socket": "6.1.0", "react-native-vector-icons": "10.1.0", "react-native-watch-connectivity": "1.1.0", "readable-stream": "3.6.2", - "realm": "12.10.0", + "realm": "12.11.0", "rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4", "rn-nodeify": "10.3.0", "scryptsy": "2.1.0", diff --git a/screen/receive/details.js b/screen/receive/details.js index 9bdb8acb02..bd23e62824 100644 --- a/screen/receive/details.js +++ b/screen/receive/details.js @@ -271,8 +271,6 @@ const ReceiveDetails = () => { const obtainWalletAddress = useCallback(async () => { console.log('receive/details - componentDidMount'); - wallet.setUserHasSavedExport(true); - await saveToDisk(); let newAddress; if (address) { setAddressBIP21Encoded(address); diff --git a/screen/send/SendDetails.tsx b/screen/send/SendDetails.tsx index 0015fe2883..1a3aa6436a 100644 --- a/screen/send/SendDetails.tsx +++ b/screen/send/SendDetails.tsx @@ -57,6 +57,7 @@ import { isTablet } from '../../blue_modules/environment'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { ContactList } from '../../class/contact-list'; import { useStorage } from '../../hooks/context/useStorage'; +import { Action } from '../../components/types'; interface IPaymentDestinations { address: string; // btc address or payment code @@ -990,7 +991,7 @@ const SendDetails = () => { }; const headerRightActions = () => { - const actions = []; + const actions: Action[] & Action[][] = []; if (isEditable) { if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) { actions.push([ @@ -1620,16 +1621,16 @@ SendDetails.actionKeys = { }; SendDetails.actionIcons = { - InsertContact: { iconType: 'SYSTEM', iconValue: 'at.badge.plus' }, - SignPSBT: { iconType: 'SYSTEM', iconValue: 'signature' }, + InsertContact: { iconValue: 'at.badge.plus' }, + SignPSBT: { iconValue: 'signature' }, SendMax: 'SendMax', - AddRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.plus' }, - RemoveRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.minus' }, + AddRecipient: { iconValue: 'person.badge.plus' }, + RemoveRecipient: { iconValue: 'person.badge.minus' }, AllowRBF: 'AllowRBF', - ImportTransaction: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down' }, - ImportTransactionMultsig: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down.on.square' }, - ImportTransactionQR: { iconType: 'SYSTEM', iconValue: 'qrcode.viewfinder' }, - CoinControl: { iconType: 'SYSTEM', iconValue: 'switch.2' }, + ImportTransaction: { iconValue: 'square.and.arrow.down' }, + ImportTransactionMultsig: { iconValue: 'square.and.arrow.down.on.square' }, + ImportTransactionQR: { iconValue: 'qrcode.viewfinder' }, + CoinControl: { iconValue: 'switch.2' }, }; const styles = StyleSheet.create({ diff --git a/screen/transactions/TransactionDetails.tsx b/screen/transactions/TransactionDetails.tsx index ef80ac5a15..0f7623ac36 100644 --- a/screen/transactions/TransactionDetails.tsx +++ b/screen/transactions/TransactionDetails.tsx @@ -32,11 +32,9 @@ const actionKeys = { const actionIcons = { Clipboard: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, GoToWallet: { - iconType: 'SYSTEM', iconValue: 'wallet.pass', }, }; diff --git a/screen/wallets/PaymentCodesList.tsx b/screen/wallets/PaymentCodesList.tsx index 979bd7e9ff..c9eb0f757a 100644 --- a/screen/wallets/PaymentCodesList.tsx +++ b/screen/wallets/PaymentCodesList.tsx @@ -42,7 +42,6 @@ const actionKeys: Action[] = [ id: Actions.pay, text: loc.bip47.pay_this_contact, icon: { - iconType: 'SYSTEM', iconValue: 'paperplane', }, }, @@ -50,7 +49,6 @@ const actionKeys: Action[] = [ id: Actions.rename, text: loc.bip47.rename_contact, icon: { - iconType: 'SYSTEM', iconValue: 'pencil', }, }, @@ -58,7 +56,6 @@ const actionKeys: Action[] = [ id: Actions.copyToClipboard, text: loc.bip47.copy_payment_code, icon: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, }, @@ -66,7 +63,6 @@ const actionKeys: Action[] = [ id: Actions.hide, text: loc.bip47.hide_contact, icon: { - iconType: 'SYSTEM', iconValue: 'eye.slash', }, }, diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 2ca35df945..066fb65a09 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -42,6 +42,7 @@ import { Chain } from '../../models/bitcoinUnits'; import ActionSheet from '../ActionSheet'; import { useStorage } from '../../hooks/context/useStorage'; import { WalletTransactionsStatus } from '../../components/Context/StorageProvider'; +import WatchOnlyWarning from '../../components/WatchOnlyWarning'; const buttonFontSize = PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 @@ -286,7 +287,6 @@ const WalletTransactions = ({ navigation }) => { ); }; - const onWalletSelect = async selectedWallet => { if (selectedWallet) { navigate('WalletTransactions', { @@ -530,6 +530,16 @@ const WalletTransactions = ({ navigation }) => { }} /> + {wallet.type === WatchOnlyWallet.type && wallet.isWatchOnlyWarningVisible && ( + { + wallet.isWatchOnlyWarningVisible = false; + LayoutAnimation.configureNext(LayoutAnimation.Presets.linear); + saveToDisk(); + }} + /> + )} mockKeychain); jest.mock('react-native-tcp-socket', () => mockKeychain); -jest.mock('../components/TooltipMenu.ios.tsx', () => require('../components/TooltipMenu.tsx')); - global.alert = () => {}; diff --git a/typings/ActionIcons.ts b/typings/ActionIcons.ts index c3b7af6ac0..a455d1ebaf 100644 --- a/typings/ActionIcons.ts +++ b/typings/ActionIcons.ts @@ -1,4 +1,3 @@ export interface ActionIcons { - iconType: 'SYSTEM'; iconValue: string; }