From d0bca03d02d463fc14cb27ae6fe55668a630b058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 2 Sep 2024 18:16:31 -0400 Subject: [PATCH] FIX: Manage Wallets dragging (#6999) --- components/ManageWalletsListItem.tsx | 90 +++++++++++++------- components/WalletsCarousel.tsx | 115 ++++++++++++++++---------- navigation/DetailViewScreensStack.tsx | 4 +- package-lock.json | 4 +- package.json | 4 +- screen/wallets/ManageWallets.tsx | 68 +++++++++------ 6 files changed, 185 insertions(+), 100 deletions(-) diff --git a/components/ManageWalletsListItem.tsx b/components/ManageWalletsListItem.tsx index aeeb88f9a..6f929b042 100644 --- a/components/ManageWalletsListItem.tsx +++ b/components/ManageWalletsListItem.tsx @@ -7,18 +7,6 @@ import { TransactionListItem } from './TransactionListItem'; import { useTheme } from './themes'; import { BitcoinUnit } from '../models/bitcoinUnits'; -interface ManageWalletsListItemProps { - item: Item; - isDraggingDisabled: boolean; - drag: () => void; - isActive: boolean; - state: { wallets: TWallet[]; searchQuery: string }; - navigateToWallet: (wallet: TWallet) => void; - renderHighlightedText: (text: string, query: string) => JSX.Element; - handleDeleteWallet: (wallet: TWallet) => void; - handleToggleHideBalance: (wallet: TWallet) => void; -} - enum ItemType { WalletSection = 'wallet', TransactionSection = 'transaction', @@ -36,6 +24,18 @@ interface TransactionItem { type Item = WalletItem | TransactionItem; +interface ManageWalletsListItemProps { + item: Item; + isDraggingDisabled: boolean; + drag?: () => void; + isPlaceHolder?: boolean; + state: { wallets: TWallet[]; searchQuery: string }; + navigateToWallet: (wallet: TWallet) => void; + renderHighlightedText: (text: string, query: string) => JSX.Element; + handleDeleteWallet: (wallet: TWallet) => void; + handleToggleHideBalance: (wallet: TWallet) => void; +} + interface SwipeContentProps { onPress: () => void; hideBalance?: boolean; @@ -61,8 +61,8 @@ const ManageWalletsListItem: React.FC = ({ item, isDraggingDisabled, drag, - isActive, state, + isPlaceHolder = false, navigateToWallet, renderHighlightedText, handleDeleteWallet, @@ -76,27 +76,59 @@ const ManageWalletsListItem: React.FC = ({ } }, [item, navigateToWallet]); + const leftContent = useCallback( + (reset: () => void) => ( + { + handleToggleHideBalance(item.data as TWallet); + reset(); + }} + hideBalance={(item.data as TWallet).hideBalance} + colors={colors} + /> + ), + [colors, handleToggleHideBalance, item.data], + ); + + const rightContent = useCallback( + (reset: () => void) => ( + { + handleDeleteWallet(item.data as TWallet); + reset(); + }} + /> + ), + [handleDeleteWallet, item.data], + ); + if (item.type === ItemType.WalletSection) { return ( handleToggleHideBalance(item.data)} hideBalance={item.data.hideBalance} colors={colors} /> - } - rightContent={ handleDeleteWallet(item.data)} />} + animation={{ duration: 400 }} + containerStyle={{ backgroundColor: colors.background }} + leftContent={leftContent} + rightContent={rightContent} > - - - + + + + + ); } else if (item.type === ItemType.TransactionSection && item.data) { @@ -135,4 +167,4 @@ const styles = StyleSheet.create({ }, }); -export default ManageWalletsListItem; +export { ManageWalletsListItem, LeftSwipeContent, RightSwipeContent }; diff --git a/components/WalletsCarousel.tsx b/components/WalletsCarousel.tsx index d7f3606bc..7c2fc6f06 100644 --- a/components/WalletsCarousel.tsx +++ b/components/WalletsCarousel.tsx @@ -98,7 +98,6 @@ interface WalletCarouselItemProps { isSelectedWallet?: boolean; customStyle?: ViewStyle; horizontal?: boolean; - isActive?: boolean; searchQuery?: string; renderHighlightedText?: (text: string, query: string) => JSX.Element; } @@ -162,8 +161,32 @@ const iStyles = StyleSheet.create({ }, }); +interface WalletCarouselItemProps { + item: TWallet; + onPress: (item: TWallet) => void; + handleLongPress?: () => void; + isSelectedWallet?: boolean; + customStyle?: ViewStyle; + horizontal?: boolean; + isPlaceHolder?: boolean; + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => JSX.Element; + animationsEnabled?: boolean; +} + export const WalletCarouselItem: React.FC = React.memo( - ({ item, onPress, handleLongPress, isSelectedWallet, customStyle, horizontal, searchQuery, renderHighlightedText }) => { + ({ + item, + onPress, + handleLongPress, + isSelectedWallet, + customStyle, + horizontal, + searchQuery, + renderHighlightedText, + animationsEnabled = true, + isPlaceHolder = false, + }) => { const scaleValue = useRef(new Animated.Value(1.0)).current; const { colors } = useTheme(); const { walletTransactionUpdateStatus } = useStorage(); @@ -172,22 +195,26 @@ export const WalletCarouselItem: React.FC = React.memo( const isLargeScreen = useIsLargeScreen(); const onPressedIn = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 0.95, - useNativeDriver: true, - friction: 3, - tension: 100, - }).start(); - }, [scaleValue]); + if (animationsEnabled) { + Animated.spring(scaleValue, { + toValue: 0.95, + useNativeDriver: true, + friction: 3, + tension: 100, + }).start(); + } + }, [scaleValue, animationsEnabled]); const onPressedOut = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 1.0, - useNativeDriver: true, - friction: 3, - tension: 100, - }).start(); - }, [scaleValue]); + if (animationsEnabled) { + Animated.spring(scaleValue, { + toValue: 1.0, + useNativeDriver: true, + friction: 3, + tension: 100, + }).start(); + } + }, [scaleValue, animationsEnabled]); const handlePress = useCallback(() => { onPressedOut(); @@ -239,33 +266,37 @@ export const WalletCarouselItem: React.FC = React.memo( - - {renderHighlightedText && searchQuery ? renderHighlightedText(item.getLabel(), searchQuery) : item.getLabel()} - - - {item.hideBalance ? ( - <> - - - - ) : ( - - {`${balance} `} + {!isPlaceHolder && ( + <> + + {renderHighlightedText && searchQuery ? renderHighlightedText(item.getLabel(), searchQuery) : item.getLabel()} - )} - - - - {loc.wallets.list_latest_transaction} - - - {latestTransactionText} - + + {item.hideBalance ? ( + <> + + + + ) : ( + + {`${balance} `} + + )} + + + + {loc.wallets.list_latest_transaction} + + + {latestTransactionText} + + + )} diff --git a/navigation/DetailViewScreensStack.tsx b/navigation/DetailViewScreensStack.tsx index ed2754faf..8e40245a8 100644 --- a/navigation/DetailViewScreensStack.tsx +++ b/navigation/DetailViewScreensStack.tsx @@ -380,10 +380,10 @@ const DetailViewStackScreensStack = () => { component={ManageWallets} options={navigationStyle({ headerBackVisible: false, - headerLargeTitle: true, gestureEnabled: false, - presentation: 'modal', + presentation: 'containedModal', title: loc.wallets.manage_title, + statusBarStyle: 'auto', })(theme)} /> diff --git a/package-lock.json b/package-lock.json index 3a1bf26cb..7f77797d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "react-native-default-preference": "1.4.4", "react-native-device-info": "11.1.0", "react-native-document-picker": "9.3.1", - "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#v4.0.1", + "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627", "react-native-fs": "2.20.0", "react-native-gesture-handler": "2.18.1", "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", @@ -13015,7 +13015,7 @@ }, "node_modules/react-native-draggable-flatlist": { "version": "4.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#ebfddc4877e8f65d5391a748db61b9cd030430ba", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#3a61627474a4e35198ae961310c77fb305507509", "license": "MIT", "dependencies": { "@babel/preset-typescript": "^7.17.12" diff --git a/package.json b/package.json index c150f624f..66a5bfab0 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@react-native-community/push-notification-ios": "1.11.0", "@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#958fac3d40811f38b53042ada9168175e321b99f", "@react-native/gradle-plugin": "^0.74.85", + "@react-native/metro-config": "0.74.87", "@react-navigation/drawer": "6.7.2", "@react-navigation/native": "6.1.18", "@react-navigation/native-stack": "6.11.0", @@ -132,7 +133,7 @@ "react-native-default-preference": "1.4.4", "react-native-device-info": "11.1.0", "react-native-document-picker": "9.3.1", - "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#v4.0.1", + "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627", "react-native-fs": "2.20.0", "react-native-gesture-handler": "2.18.1", "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", @@ -153,7 +154,6 @@ "react-native-randombytes": "3.6.1", "react-native-rate": "1.2.12", "react-native-reanimated": "3.15.1", - "@react-native/metro-config": "0.74.87", "react-native-safe-area-context": "4.10.9", "react-native-screens": "3.34.0", "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", diff --git a/screen/wallets/ManageWallets.tsx b/screen/wallets/ManageWallets.tsx index 183f46059..ee5e849f0 100644 --- a/screen/wallets/ManageWallets.tsx +++ b/screen/wallets/ManageWallets.tsx @@ -1,18 +1,13 @@ import React, { useEffect, useLayoutEffect, useReducer, useCallback, useMemo, useRef } from 'react'; +import { StyleSheet, TouchableOpacity, Image, Text, Alert, I18nManager, Animated, LayoutAnimation } from 'react-native'; import { - Platform, - StyleSheet, - useColorScheme, - TouchableOpacity, - Image, - Text, - Alert, - I18nManager, - Animated, - LayoutAnimation, -} from 'react-native'; -// @ts-expect-error: react-native-draggable-flatlist is not typed -import { NestableScrollContainer, NestableDraggableFlatList, RenderItem } from 'react-native-draggable-flatlist'; + NestableScrollContainer, + ScaleDecorator, + OpacityDecorator, + NestableDraggableFlatList, + RenderItem, + // @ts-expect-error: react-native-draggable-flatlist is not typed +} from 'react-native-draggable-flatlist'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; @@ -28,7 +23,7 @@ import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics'; import presentAlert from '../../components/Alert'; import prompt from '../../helpers/prompt'; import HeaderRightButton from '../../components/HeaderRightButton'; -import ManageWalletsListItem from '../../components/ManageWalletsListItem'; +import { ManageWalletsListItem } from '../../components/ManageWalletsListItem'; enum ItemType { WalletSection = 'wallet', @@ -194,7 +189,6 @@ const ManageWallets: React.FC = () => { const { colors, closeImage } = useTheme(); const { wallets: storedWallets, setWalletsWithNewOrder, txMetadata } = useStorage(); const walletsRef = useRef(deepCopyWallets(storedWallets)); // Create a deep copy of wallets for the DraggableFlatList - const colorScheme = useColorScheme(); const { navigate, setOptions, goBack } = useExtendedNavigation(); const [state, dispatch] = useReducer(reducer, initialState); const { isBiometricUseCapableAndEnabled } = useBiometrics(); @@ -283,12 +277,11 @@ const ManageWallets: React.FC = () => { }; setOptions({ - statusBarStyle: Platform.select({ ios: 'light', default: colorScheme === 'dark' ? 'light' : 'dark' }), headerLeft: () => HeaderLeftButton, headerRight: () => SaveButton, headerSearchBarOptions: searchBarOptions, }); - }, [colorScheme, setOptions, HeaderLeftButton, SaveButton]); + }, [setOptions, HeaderLeftButton, SaveButton]); useFocusEffect( useCallback(() => { @@ -429,20 +422,39 @@ const ManageWallets: React.FC = () => { [goBack, navigate], ); const renderWalletItem = useCallback( + ({ item, drag, isActive }: RenderItem) => ( + + + 0 || state.isSearchFocused} + drag={drag} + state={state} + navigateToWallet={navigateToWallet} + renderHighlightedText={renderHighlightedText} + handleDeleteWallet={handleDeleteWallet} + handleToggleHideBalance={handleToggleHideBalance} + /> + + + ), + [state, navigateToWallet, renderHighlightedText, handleDeleteWallet, handleToggleHideBalance], + ); + + const renderPlaceholder = useCallback( ({ item, drag, isActive }: RenderItem) => ( 0 || state.isSearchFocused} - drag={drag} - isActive={isActive} state={state} navigateToWallet={navigateToWallet} renderHighlightedText={renderHighlightedText} + isPlaceHolder handleDeleteWallet={handleDeleteWallet} handleToggleHideBalance={handleToggleHideBalance} /> ), - [state, navigateToWallet, renderHighlightedText, handleDeleteWallet, handleToggleHideBalance], + [handleDeleteWallet, handleToggleHideBalance, navigateToWallet, renderHighlightedText, state], ); const onChangeOrder = useCallback(() => { @@ -482,28 +494,38 @@ const ManageWallets: React.FC = () => { }, [state.searchQuery, state.wallets.length, state.txMetadata, stylesHook.noResultsText]); return ( - + + {renderHeader} item.type === ItemType.WalletSection)} + extraData={state.tempOrder} keyExtractor={keyExtractor} renderItem={renderWalletItem} onChangeOrder={onChangeOrder} onDragBegin={onDragBegin} + onPlaceholderIndexChange={onChangeOrder} onRelease={onRelease} delayLongPress={150} useNativeDriver={true} + dragItemOverflow + autoscrollThreshold={1} + renderPlaceholder={renderPlaceholder} + autoscrollSpeed={0.5} + contentInsetAdjustmentBehavior="automatic" + automaticallyAdjustContentInsets onDragEnd={onDragEnd} containerStyle={styles.root} - ListHeaderComponent={renderHeader} /> item.type === ItemType.TransactionSection)} keyExtractor={keyExtractor} renderItem={renderWalletItem} + dragItemOverflow containerStyle={styles.root} + contentInsetAdjustmentBehavior="automatic" + automaticallyAdjustContentInsets useNativeDriver={true} - ListHeaderComponent={renderHeader} />