mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-18 21:35:21 +01:00
FIX: Manage Wallets dragging (#6999)
This commit is contained in:
parent
c7f15c08d8
commit
d0bca03d02
@ -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<ManageWalletsListItemProps> = ({
|
||||
item,
|
||||
isDraggingDisabled,
|
||||
drag,
|
||||
isActive,
|
||||
state,
|
||||
isPlaceHolder = false,
|
||||
navigateToWallet,
|
||||
renderHighlightedText,
|
||||
handleDeleteWallet,
|
||||
@ -76,27 +76,59 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
|
||||
}
|
||||
}, [item, navigateToWallet]);
|
||||
|
||||
const leftContent = useCallback(
|
||||
(reset: () => void) => (
|
||||
<LeftSwipeContent
|
||||
onPress={() => {
|
||||
handleToggleHideBalance(item.data as TWallet);
|
||||
reset();
|
||||
}}
|
||||
hideBalance={(item.data as TWallet).hideBalance}
|
||||
colors={colors}
|
||||
/>
|
||||
),
|
||||
[colors, handleToggleHideBalance, item.data],
|
||||
);
|
||||
|
||||
const rightContent = useCallback(
|
||||
(reset: () => void) => (
|
||||
<RightSwipeContent
|
||||
onPress={() => {
|
||||
handleDeleteWallet(item.data as TWallet);
|
||||
reset();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[handleDeleteWallet, item.data],
|
||||
);
|
||||
|
||||
if (item.type === ItemType.WalletSection) {
|
||||
return (
|
||||
<ListItem.Swipeable
|
||||
leftWidth={80}
|
||||
rightWidth={90}
|
||||
minSlideWidth={40}
|
||||
leftContent={
|
||||
<LeftSwipeContent onPress={() => handleToggleHideBalance(item.data)} hideBalance={item.data.hideBalance} colors={colors} />
|
||||
}
|
||||
rightContent={<RightSwipeContent colors={colors} onPress={() => handleDeleteWallet(item.data)} />}
|
||||
animation={{ duration: 400 }}
|
||||
containerStyle={{ backgroundColor: colors.background }}
|
||||
leftContent={leftContent}
|
||||
rightContent={rightContent}
|
||||
>
|
||||
<View style={styles.walletCarouselItemContainer}>
|
||||
<WalletCarouselItem
|
||||
item={item.data}
|
||||
handleLongPress={isDraggingDisabled ? undefined : drag}
|
||||
isActive={isActive}
|
||||
onPress={onPress}
|
||||
searchQuery={state.searchQuery}
|
||||
renderHighlightedText={renderHighlightedText}
|
||||
/>
|
||||
</View>
|
||||
<ListItem.Content
|
||||
style={{
|
||||
backgroundColor: colors.background,
|
||||
}}
|
||||
>
|
||||
<View style={styles.walletCarouselItemContainer}>
|
||||
<WalletCarouselItem
|
||||
item={item.data}
|
||||
handleLongPress={isDraggingDisabled ? undefined : drag}
|
||||
onPress={onPress}
|
||||
animationsEnabled={false}
|
||||
searchQuery={state.searchQuery}
|
||||
isPlaceHolder={isPlaceHolder}
|
||||
renderHighlightedText={renderHighlightedText}
|
||||
/>
|
||||
</View>
|
||||
</ListItem.Content>
|
||||
</ListItem.Swipeable>
|
||||
);
|
||||
} else if (item.type === ItemType.TransactionSection && item.data) {
|
||||
@ -135,4 +167,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
export default ManageWalletsListItem;
|
||||
export { ManageWalletsListItem, LeftSwipeContent, RightSwipeContent };
|
||||
|
@ -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<WalletCarouselItemProps> = 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<WalletCarouselItemProps> = 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<WalletCarouselItemProps> = React.memo(
|
||||
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}>
|
||||
<Image defaultSource={image} source={image} style={iStyles.image} />
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
|
||||
{renderHighlightedText && searchQuery ? renderHighlightedText(item.getLabel(), searchQuery) : item.getLabel()}
|
||||
</Text>
|
||||
<View style={iStyles.balanceContainer}>
|
||||
{item.hideBalance ? (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<BlurredBalanceView />
|
||||
</>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
key={`${balance}`} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
|
||||
>
|
||||
{`${balance} `}
|
||||
{!isPlaceHolder && (
|
||||
<>
|
||||
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
|
||||
{renderHighlightedText && searchQuery ? renderHighlightedText(item.getLabel(), searchQuery) : item.getLabel()}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
|
||||
{loc.wallets.list_latest_transaction}
|
||||
</Text>
|
||||
<Text numberOfLines={1} style={[iStyles.latestTxTime, { color: colors.inverseForegroundColor }]}>
|
||||
{latestTransactionText}
|
||||
</Text>
|
||||
<View style={iStyles.balanceContainer}>
|
||||
{item.hideBalance ? (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<BlurredBalanceView />
|
||||
</>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
key={`${balance}`} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
|
||||
>
|
||||
{`${balance} `}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={iStyles.br} />
|
||||
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
|
||||
{loc.wallets.list_latest_transaction}
|
||||
</Text>
|
||||
<Text numberOfLines={1} style={[iStyles.latestTxTime, { color: colors.inverseForegroundColor }]}>
|
||||
{latestTransactionText}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</Pressable>
|
||||
|
@ -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)}
|
||||
/>
|
||||
</DetailViewStack.Navigator>
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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<TWallet[]>(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<Item>) => (
|
||||
<ScaleDecorator drag={drag} activeScale={1.1}>
|
||||
<OpacityDecorator activeOpacity={0.5}>
|
||||
<ManageWalletsListItem
|
||||
item={item}
|
||||
isDraggingDisabled={state.searchQuery.length > 0 || state.isSearchFocused}
|
||||
drag={drag}
|
||||
state={state}
|
||||
navigateToWallet={navigateToWallet}
|
||||
renderHighlightedText={renderHighlightedText}
|
||||
handleDeleteWallet={handleDeleteWallet}
|
||||
handleToggleHideBalance={handleToggleHideBalance}
|
||||
/>
|
||||
</OpacityDecorator>
|
||||
</ScaleDecorator>
|
||||
),
|
||||
[state, navigateToWallet, renderHighlightedText, handleDeleteWallet, handleToggleHideBalance],
|
||||
);
|
||||
|
||||
const renderPlaceholder = useCallback(
|
||||
({ item, drag, isActive }: RenderItem<Item>) => (
|
||||
<ManageWalletsListItem
|
||||
item={item}
|
||||
isDraggingDisabled={state.searchQuery.length > 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 (
|
||||
<GestureHandlerRootView style={[styles.root, stylesHook.root]}>
|
||||
<GestureHandlerRootView style={[{ backgroundColor: colors.background }, styles.root]}>
|
||||
<NestableScrollContainer contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets>
|
||||
{renderHeader}
|
||||
<NestableDraggableFlatList
|
||||
data={state.tempOrder.filter((item): item is WalletItem => 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}
|
||||
/>
|
||||
<NestableDraggableFlatList
|
||||
data={state.tempOrder.filter((item): item is TransactionItem => item.type === ItemType.TransactionSection)}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderWalletItem}
|
||||
dragItemOverflow
|
||||
containerStyle={styles.root}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
automaticallyAdjustContentInsets
|
||||
useNativeDriver={true}
|
||||
ListHeaderComponent={renderHeader}
|
||||
/>
|
||||
</NestableScrollContainer>
|
||||
</GestureHandlerRootView>
|
||||
|
Loading…
Reference in New Issue
Block a user