This commit is contained in:
Marcos Rodriguez Velez 2024-07-25 18:41:01 -04:00
parent 47c3c13c9e
commit e7b41786bd
No known key found for this signature in database
GPG Key ID: 6030B2F48CCE86D7
5 changed files with 119 additions and 94 deletions

View File

@ -23,6 +23,7 @@ import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList
import { useStorage } from '../hooks/context/useStorage'; import { useStorage } from '../hooks/context/useStorage';
import ToolTipMenu from './TooltipMenu'; import ToolTipMenu from './TooltipMenu';
import { CommonToolTipActions } from '../typings/CommonToolTipActions'; import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import useBounceAnimation from '../hooks/useBounceAnimation';
interface TransactionListItemProps { interface TransactionListItemProps {
itemPriceUnit: BitcoinUnit; itemPriceUnit: BitcoinUnit;
@ -34,30 +35,6 @@ interface TransactionListItemProps {
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>; type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
const useBounceAnimation = (query: string) => {
const bounceAnim = useRef(new Animated.Value(1.0)).current;
useEffect(() => {
if (query) {
Animated.spring(bounceAnim, {
toValue: 1.2,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start(() => {
Animated.spring(bounceAnim, {
toValue: 1.0,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
});
}
}, [query, bounceAnim]);
return bounceAnim;
};
export const TransactionListItem: React.FC<TransactionListItemProps> = React.memo( export const TransactionListItem: React.FC<TransactionListItemProps> = React.memo(
({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style }) => { ({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1); const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
@ -339,17 +316,17 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
const renderHighlightedText = (text: string, query: string) => { const renderHighlightedText = (text: string, query: string) => {
const parts = text.split(new RegExp(`(${query})`, 'gi')); const parts = text.split(new RegExp(`(${query})`, 'gi'));
return ( return (
<> <Text>
{parts.map((part, index) => {parts.map((part, index) =>
part.toLowerCase() === query.toLowerCase() ? ( part.toLowerCase() === query.toLowerCase() ? (
<Animated.Text key={index} style={[styles.highlighted, { transform: [{ scale: bounceAnim }] }]}> <Animated.View key={index} style={[styles.highlightedContainer, { transform: [{ scale: bounceAnim }] }]}>
{part} <Text style={[styles.highlighted, styles.defaultText]}>{part}</Text>
</Animated.Text> </Animated.View>
) : ( ) : (
<Text key={index}>{part}</Text> <Text key={index} style={query ? styles.dimmedText : {}}>{part}</Text>
), ),
)} )}
</> </Text>
); );
}; };
@ -373,7 +350,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
chevron={false} chevron={false}
rightTitle={rowTitle} rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle} rightTitleStyle={rowTitleStyle}
containerStyle={containerStyle} containerStyle={[containerStyle, style]}
/> />
</ToolTipMenu> </ToolTipMenu>
); );
@ -381,11 +358,24 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
); );
const styles = StyleSheet.create({ const styles = StyleSheet.create({
highlighted: { highlightedContainer: {
backgroundColor: 'white', backgroundColor: 'white',
borderColor: 'black', borderColor: 'black',
borderWidth: 1, borderWidth: 1,
borderRadius: 5, borderRadius: 5,
padding: 2,
alignSelf: 'flex-start',
},
highlighted: {
color: 'black', color: 'black',
fontSize: 14,
fontWeight: '600',
},
defaultText: {
fontSize: 14,
fontWeight: '600',
},
dimmedText: {
opacity: 0.5,
}, },
}); });

View File

@ -0,0 +1,26 @@
import { useEffect, useRef } from 'react';
import { Animated } from 'react-native';
const useBounceAnimation = (query: string) => {
const bounceAnim = useRef(new Animated.Value(1.0)).current;
useEffect(() => {
if (query) {
Animated.timing(bounceAnim, {
toValue: 1.2,
duration: 150,
useNativeDriver: true,
}).start(() => {
Animated.timing(bounceAnim, {
toValue: 1.0,
duration: 150,
useNativeDriver: true,
}).start();
});
}
}, [bounceAnim, query]);
return bounceAnim;
};
export default useBounceAnimation;

View File

@ -181,7 +181,7 @@
B4EFF7472C3F70010095D655 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; }; B4EFF7472C3F70010095D655 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; };
B4EFF7482C3F70090095D655 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; }; B4EFF7482C3F70090095D655 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; };
C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -497,7 +497,7 @@
files = ( files = (
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */, C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */, 773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -496,7 +496,7 @@
"add_ln_wallet_first": "You must first add a Lightning wallet.", "add_ln_wallet_first": "You must first add a Lightning wallet.",
"identity_pubkey": "Identity Pubkey", "identity_pubkey": "Identity Pubkey",
"xpub_title": "Wallet XPUB", "xpub_title": "Wallet XPUB",
"search_wallets": "Search wallets, memos" "manage_wallets_search_placeholder": "Search wallets, memos"
}, },
"multisig": { "multisig": {
"multisig_vault": "Vault", "multisig_vault": "Vault",

View File

@ -5,6 +5,7 @@ import DraggableFlatList, { ScaleDecorator } from 'react-native-draggable-flatli
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { GestureHandlerRootView } from 'react-native-gesture-handler';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useTheme } from '../../components/themes'; import { useTheme } from '../../components/themes';
import { WalletCarouselItem } from '../../components/WalletsCarousel';
import { TransactionListItem } from '../../components/TransactionListItem'; import { TransactionListItem } from '../../components/TransactionListItem';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc from '../../loc'; import loc from '../../loc';
@ -12,9 +13,26 @@ import { useStorage } from '../../hooks/context/useStorage';
import useDebounce from '../../hooks/useDebounce'; import useDebounce from '../../hooks/useDebounce';
import { Header } from '../../components/Header'; import { Header } from '../../components/Header';
import { TTXMetadata } from '../../class'; import { TTXMetadata } from '../../class';
import { TWallet } from '../../class/wallets/types'; import { ExtendedTransaction, LightningTransaction, Transaction, TWallet } from '../../class/wallets/types';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import { WalletCarouselItem } from '../../components/WalletsCarousel'; import useBounceAnimation from '../../hooks/useBounceAnimation';
enum ItemType {
WalletSection = 'wallet',
TransactionSection = 'transaction',
}
interface WalletItem {
type: ItemType.WalletSection;
data: TWallet;
}
interface TransactionItem {
type: ItemType.TransactionSection;
data: ExtendedTransaction & LightningTransaction;
}
type Item = WalletItem | TransactionItem;
const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY';
const SET_IS_SEARCH_FOCUSED = 'SET_IS_SEARCH_FOCUSED'; const SET_IS_SEARCH_FOCUSED = 'SET_IS_SEARCH_FOCUSED';
@ -34,17 +52,17 @@ interface SetIsSearchFocusedAction {
interface SetWalletDataAction { interface SetWalletDataAction {
type: typeof SET_WALLET_DATA; type: typeof SET_WALLET_DATA;
payload: any[]; payload: TWallet[];
} }
interface SetTxMetadataAction { interface SetTxMetadataAction {
type: typeof SET_TX_METADATA; type: typeof SET_TX_METADATA;
payload: { [key: string]: { memo?: string } }; payload: TTXMetadata;
} }
interface SetOrderAction { interface SetOrderAction {
type: typeof SET_ORDER; type: typeof SET_ORDER;
payload: any[]; payload: Item[];
} }
type Action = SetSearchQueryAction | SetIsSearchFocusedAction | SetWalletDataAction | SetTxMetadataAction | SetOrderAction; type Action = SetSearchQueryAction | SetIsSearchFocusedAction | SetWalletDataAction | SetTxMetadataAction | SetOrderAction;
@ -54,7 +72,7 @@ interface State {
isSearchFocused: boolean; isSearchFocused: boolean;
walletData: TWallet[]; walletData: TWallet[];
txMetadata: TTXMetadata; txMetadata: TTXMetadata;
order: any[]; order: Item[];
} }
const initialState: State = { const initialState: State = {
@ -82,30 +100,6 @@ const reducer = (state: State, action: Action): State => {
} }
}; };
const useBounceAnimation = (query: string) => {
const bounceAnim = useRef(new Animated.Value(1.0)).current;
useEffect(() => {
if (query) {
Animated.spring(bounceAnim, {
toValue: 1.2,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start(() => {
Animated.spring(bounceAnim, {
toValue: 1.0,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
});
}
}, [query]);
return bounceAnim;
};
const ManageWallets: React.FC = () => { const ManageWallets: React.FC = () => {
const sortableList = useRef(null); const sortableList = useRef(null);
const { colors, closeImage } = useTheme(); const { colors, closeImage } = useTheme();
@ -127,14 +121,14 @@ const ManageWallets: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
const initialOrder = wallets.map(wallet => ({ type: 'wallet', data: wallet })); const initialOrder: Item[] = wallets.map(wallet => ({ type: ItemType.WalletSection, data: wallet }));
dispatch({ type: SET_WALLET_DATA, payload: wallets }); dispatch({ type: SET_WALLET_DATA, payload: wallets });
dispatch({ type: SET_TX_METADATA, payload: txMetadata }); dispatch({ type: SET_TX_METADATA, payload: txMetadata });
dispatch({ type: SET_ORDER, payload: initialOrder }); dispatch({ type: SET_ORDER, payload: initialOrder });
}, [wallets, txMetadata]); }, [wallets, txMetadata]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
const walletOrder = state.order.filter(item => item.type === 'wallet').map(item => item.data); const walletOrder = state.order.filter(item => item.type === ItemType.WalletSection).map(item => item.data);
setWalletsWithNewOrder(walletOrder); setWalletsWithNewOrder(walletOrder);
goBack(); goBack();
}, [goBack, setWalletsWithNewOrder, state.order]); }, [goBack, setWalletsWithNewOrder, state.order]);
@ -169,15 +163,28 @@ const ManageWallets: React.FC = () => {
const filteredTxMetadata = Object.entries(txMetadata).filter(([_, tx]) => const filteredTxMetadata = Object.entries(txMetadata).filter(([_, tx]) =>
tx.memo?.toLowerCase().includes(debouncedSearchQuery.toLowerCase()), tx.memo?.toLowerCase().includes(debouncedSearchQuery.toLowerCase()),
); );
const filteredOrder = [
...filteredWallets.map(wallet => ({ type: 'wallet', data: wallet })), // Filter transactions
...Object.entries(filteredTxMetadata).map(([txid, tx]) => ({ type: 'transaction', data: { txid, ...tx } })), const filteredTransactions = wallets.flatMap(wallet =>
wallet
.getTransactions()
.filter((tx: Transaction) =>
filteredTxMetadata.some(
([txid, txMeta]) => tx.hash === txid && txMeta.memo?.toLowerCase().includes(debouncedSearchQuery.toLowerCase()),
),
),
);
const filteredOrder: Item[] = [
...filteredWallets.map(wallet => ({ type: ItemType.WalletSection, data: wallet })),
...filteredTransactions.map(tx => ({ type: ItemType.TransactionSection, data: tx })),
]; ];
dispatch({ type: SET_WALLET_DATA, payload: filteredWallets }); dispatch({ type: SET_WALLET_DATA, payload: filteredWallets });
dispatch({ type: SET_TX_METADATA, payload: Object.fromEntries(filteredTxMetadata) }); dispatch({ type: SET_TX_METADATA, payload: Object.fromEntries(filteredTxMetadata) });
dispatch({ type: SET_ORDER, payload: filteredOrder }); dispatch({ type: SET_ORDER, payload: filteredOrder });
} else { } else {
const initialOrder = wallets.map(wallet => ({ type: 'wallet', data: wallet })); const initialOrder: Item[] = wallets.map(wallet => ({ type: ItemType.WalletSection, data: wallet }));
dispatch({ type: SET_WALLET_DATA, payload: wallets }); dispatch({ type: SET_WALLET_DATA, payload: wallets });
dispatch({ type: SET_TX_METADATA, payload: {} }); dispatch({ type: SET_TX_METADATA, payload: {} });
dispatch({ type: SET_ORDER, payload: initialOrder }); dispatch({ type: SET_ORDER, payload: initialOrder });
@ -192,13 +199,13 @@ const ManageWallets: React.FC = () => {
onClear: () => dispatch({ type: SET_SEARCH_QUERY, payload: '' }), onClear: () => dispatch({ type: SET_SEARCH_QUERY, payload: '' }),
onFocus: () => dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: true }), onFocus: () => dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: true }),
onBlur: () => dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: false }), onBlur: () => dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: false }),
placeholder: loc.wallets.search_wallets, placeholder: loc.wallets.manage_wallets_search_placeholder, // New placeholder text
}, },
}); });
}, [setOptions]); }, [setOptions]);
const navigateToWallet = useCallback( const navigateToWallet = useCallback(
(wallet: any) => { (wallet: TWallet) => {
const walletID = wallet.getID(); const walletID = wallet.getID();
goBack(); goBack();
navigate('WalletTransactions', { navigate('WalletTransactions', {
@ -236,37 +243,37 @@ const ManageWallets: React.FC = () => {
); );
const renderItem = useCallback( const renderItem = useCallback(
({ item, drag, isActive }: any) => { ({ item, drag, isActive }: { item: Item; drag: () => void; isActive: boolean }) => {
const itemOpacity = isActive ? 1 : state.searchQuery ? 0.5 : 1; const itemOpacity = isActive ? 1 : state.searchQuery ? 0.5 : 1;
if (item.type === 'transaction') { if (item.type === ItemType.TransactionSection && item.data) {
return ( return (
<View style={StyleSheet.flatten([styles.padding16, { opacity: itemOpacity }])}> <View style={StyleSheet.flatten([styles.padding16, { opacity: itemOpacity }])}>
<TransactionListItem <TransactionListItem
item={item.data} item={item.data}
// update later to support other units
itemPriceUnit={BitcoinUnit.BTC} itemPriceUnit={BitcoinUnit.BTC}
walletID={item.data.walletID} walletID={item.data.walletID}
searchQuery={state.searchQuery} searchQuery={state.searchQuery}
renderHighlightedText={renderHighlightedText} style={{ opacity: itemOpacity }}
/> />
</View> </View>
); );
} else if (item.type === ItemType.WalletSection) {
return (
<ScaleDecorator>
<WalletCarouselItem
item={item.data}
handleLongPress={isDraggingDisabled ? undefined : drag}
isActive={isActive}
onPress={() => navigateToWallet(item.data)}
customStyle={StyleSheet.flatten([styles.padding16, { opacity: itemOpacity }])}
searchQuery={state.searchQuery}
renderHighlightedText={state.searchQuery ? renderHighlightedText : undefined}
/>
</ScaleDecorator>
);
} }
return null;
return (
<ScaleDecorator>
<WalletCarouselItem
item={item.data}
handleLongPress={isDraggingDisabled ? null : drag}
isActive={isActive}
onPress={navigateToWallet}
customStyle={StyleSheet.flatten([styles.padding16, { opacity: itemOpacity }])}
searchQuery={state.searchQuery}
renderHighlightedText={state.searchQuery ? renderHighlightedText : undefined}
/>
</ScaleDecorator>
);
}, },
[isDraggingDisabled, navigateToWallet, state.searchQuery, renderHighlightedText], [isDraggingDisabled, navigateToWallet, state.searchQuery, renderHighlightedText],
); );
@ -354,14 +361,16 @@ const iStyles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderRadius: 5, borderRadius: 5,
padding: 2, padding: 2,
alignSelf: 'flex-start', // ensure the container resizes based on its content
}, },
highlighted: { highlighted: {
color: 'black', color: 'black',
fontSize: 14,
fontWeight: '600',
}, },
defaultText: { defaultText: {
fontSize: 19, fontSize: 14,
fontWeight: 'bold', fontWeight: '600',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}, },
dimmedText: { dimmedText: {
opacity: 0.5, opacity: 0.5,