diff --git a/components/WalletsCarousel.js b/components/WalletsCarousel.js deleted file mode 100644 index 9b334f3e2..000000000 --- a/components/WalletsCarousel.js +++ /dev/null @@ -1,378 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; -import { - Animated, - FlatList, - I18nManager, - Image, - Platform, - Pressable, - StyleSheet, - Text, - TouchableOpacity, - useWindowDimensions, - View, -} from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import { BlueSpacing10 } from '../BlueComponents'; -import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; -import WalletGradient from '../class/wallet-gradient'; -import { useIsLargeScreen } from '../hooks/useIsLargeScreen'; -import loc, { formatBalance, transactionTimeToReadable } from '../loc'; -import { BlurredBalanceView } from './BlurredBalanceView'; -import { useSettings } from '../hooks/context/useSettings'; -import { useTheme } from './themes'; -import { useStorage } from '../hooks/context/useStorage'; -import { WalletTransactionsStatus } from './Context/StorageProvider'; - -const nStyles = StyleSheet.create({ - container: { - borderRadius: 10, - minHeight: Platform.OS === 'ios' ? 164 : 181, - justifyContent: 'center', - alignItems: 'flex-start', - }, - addAWAllet: { - fontWeight: '600', - fontSize: 24, - marginBottom: 4, - }, - addLine: { - fontSize: 13, - }, - button: { - marginTop: 12, - backgroundColor: '#007AFF', - paddingHorizontal: 32, - paddingVertical: 12, - borderRadius: 8, - }, - buttonText: { - fontWeight: '500', - }, -}); - -const NewWalletPanel = ({ onPress }) => { - const { colors } = useTheme(); - const { width } = useWindowDimensions(); - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - const isLargeScreen = useIsLargeScreen(); - const nStylesHooks = StyleSheet.create({ - container: isLargeScreen - ? { - paddingHorizontal: 24, - marginVertical: 16, - } - : { paddingVertical: 16, paddingHorizontal: 24 }, - }); - - return ( - - - {loc.wallets.list_create_a_wallet} - {loc.wallets.list_create_a_wallet_text} - - {loc.wallets.list_create_a_button} - - - - ); -}; - -NewWalletPanel.propTypes = { - onPress: PropTypes.func.isRequired, -}; - -const iStyles = StyleSheet.create({ - root: { paddingRight: 20 }, - rootLargeDevice: { marginVertical: 20 }, - grad: { - padding: 15, - borderRadius: 12, - minHeight: 164, - }, - balanceContainer: { - height: 40, - }, - image: { - width: 99, - height: 94, - position: 'absolute', - bottom: 0, - right: 0, - }, - br: { - backgroundColor: 'transparent', - }, - label: { - backgroundColor: 'transparent', - fontSize: 19, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - balance: { - backgroundColor: 'transparent', - fontWeight: 'bold', - fontSize: 36, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - latestTx: { - backgroundColor: 'transparent', - fontSize: 13, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - latestTxTime: { - backgroundColor: 'transparent', - fontWeight: 'bold', - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - fontSize: 16, - }, - shadowContainer: { - ...Platform.select({ - ios: { - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 25 / 100, - shadowRadius: 8, - borderRadius: 12, - }, - android: { - elevation: 8, - borderRadius: 12, - }, - }), - }, -}); - -export const WalletCarouselItem = React.memo(({ item, _, onPress, handleLongPress, isSelectedWallet, customStyle, horizontal }) => { - const scaleValue = new Animated.Value(1.0); - const { colors } = useTheme(); - const { walletTransactionUpdateStatus } = useStorage(); - const { width } = useWindowDimensions(); - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - const isLargeScreen = useIsLargeScreen(); - const onPressedIn = () => { - Animated.spring(scaleValue, { duration: 50, useNativeDriver: true, toValue: 0.9 }).start(); - }; - - const onPressedOut = () => { - Animated.spring(scaleValue, { duration: 50, useNativeDriver: true, toValue: 1.0 }).start(); - }; - - const opacity = isSelectedWallet === false ? 0.5 : 1.0; - let image; - switch (item.type) { - case LightningLdkWallet.type: - case LightningCustodianWallet.type: - image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); - break; - case MultisigHDWallet.type: - image = I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); - break; - default: - image = I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); - } - - const latestTransactionText = - walletTransactionUpdateStatus === WalletTransactionsStatus.ALL || walletTransactionUpdateStatus === item.getID() - ? loc.transactions.updating - : item.getBalance() !== 0 && item.getLatestTransactionTime() === 0 - ? loc.wallets.pull_to_refresh - : item.getTransactions().find(tx => tx.confirmations === 0) - ? loc.transactions.pending - : transactionTimeToReadable(item.getLatestTransactionTime()); - - const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true); - - return ( - - { - handleLongPress(); - }} - onPress={() => { - onPressedOut(); - setTimeout(() => { - onPress(item); // Replace 'onPress' with your navigation function - }, 50); - }} - > - - - - - - {item.getLabel()} - - - {item.hideBalance ? ( - <> - - - - ) : ( - - {`${balance} `} - - )} - - - - {loc.wallets.list_latest_transaction} - - - - {latestTransactionText} - - - - - - ); -}); - -WalletCarouselItem.propTypes = { - item: PropTypes.any, - onPress: PropTypes.func.isRequired, - handleLongPress: PropTypes.func, - isSelectedWallet: PropTypes.bool, -}; - -const cStyles = StyleSheet.create({ - content: { - paddingTop: 16, - }, - contentLargeScreen: { - paddingHorizontal: 16, - }, - separatorStyle: { - width: 16, - height: 20, - }, -}); - -const ListHeaderComponent = () => ; - -const WalletsCarousel = forwardRef((props, ref) => { - const { preferredFiatCurrency, language } = useSettings(); - const { horizontal, data, handleLongPress, onPress, selectedWallet, scrollEnabled } = props; - const renderItem = useCallback( - ({ item, index }) => - item ? ( - - ) : ( - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [horizontal, selectedWallet, preferredFiatCurrency, language], - ); - const flatListRef = useRef(); - - useImperativeHandle(ref, () => ({ - scrollToItem: ({ item }) => { - setTimeout(() => { - flatListRef?.current?.scrollToItem({ item, viewOffset: 16 }); - }, 300); - }, - scrollToIndex: ({ index }) => { - setTimeout(() => { - flatListRef?.current?.scrollToIndex({ index, viewOffset: 16 }); - }, 300); - }, - })); - - const onScrollToIndexFailed = error => { - console.log('onScrollToIndexFailed'); - console.log(error); - flatListRef.current.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); - setTimeout(() => { - if (data.length !== 0 && flatListRef.current !== null) { - flatListRef.current.scrollToIndex({ index: error.index, animated: true }); - } - }, 100); - }; - - const { width } = useWindowDimensions(); - const sliderHeight = 195; - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - return horizontal ? ( - index.toString()} - showsVerticalScrollIndicator={false} - pagingEnabled - disableIntervalMomentum={horizontal} - snapToInterval={itemWidth} // Adjust to your content width - decelerationRate="fast" - contentContainerStyle={cStyles.content} - directionalLockEnabled - showsHorizontalScrollIndicator={false} - initialNumToRender={10} - scrollEnabled={scrollEnabled} - ListHeaderComponent={ListHeaderComponent} - style={{ minHeight: sliderHeight + 12 }} - onScrollToIndexFailed={onScrollToIndexFailed} - {...props} - /> - ) : ( - - {data.map((item, index) => - item ? ( - - ) : ( - - ), - )} - - ); -}); - -WalletsCarousel.propTypes = { - horizontal: PropTypes.bool, - selectedWallet: PropTypes.string, - onPress: PropTypes.func.isRequired, - handleLongPress: PropTypes.func.isRequired, - data: PropTypes.array, -}; - -export default WalletsCarousel; diff --git a/components/WalletsCarousel.tsx b/components/WalletsCarousel.tsx new file mode 100644 index 000000000..7afd61456 --- /dev/null +++ b/components/WalletsCarousel.tsx @@ -0,0 +1,414 @@ +import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import { + Animated, + FlatList, + I18nManager, + Image, + Platform, + Pressable, + StyleSheet, + Text, + TouchableOpacity, + useWindowDimensions, + View, + FlatListProps, + ListRenderItemInfo, + ViewStyle, +} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { BlueSpacing10 } from '../BlueComponents'; +import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; +import WalletGradient from '../class/wallet-gradient'; +import { useIsLargeScreen } from '../hooks/useIsLargeScreen'; +import loc, { formatBalance, transactionTimeToReadable } from '../loc'; +import { BlurredBalanceView } from './BlurredBalanceView'; +import { useTheme } from './themes'; +import { useStorage } from '../hooks/context/useStorage'; +import { WalletTransactionsStatus } from './Context/StorageProvider'; +import { Transaction, TWallet } from '../class/wallets/types'; + +interface NewWalletPanelProps { + onPress: () => void; +} + +const nStyles = StyleSheet.create({ + container: { + borderRadius: 10, + minHeight: Platform.OS === 'ios' ? 164 : 181, + justifyContent: 'center', + alignItems: 'flex-start', + }, + addAWAllet: { + fontWeight: '600', + fontSize: 24, + marginBottom: 4, + }, + addLine: { + fontSize: 13, + }, + button: { + marginTop: 12, + backgroundColor: '#007AFF', + paddingHorizontal: 32, + paddingVertical: 12, + borderRadius: 8, + }, + buttonText: { + fontWeight: '500', + }, +}); + +const NewWalletPanel: React.FC = ({ onPress }) => { + const { colors } = useTheme(); + const { width } = useWindowDimensions(); + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + const isLargeScreen = useIsLargeScreen(); + const nStylesHooks = StyleSheet.create({ + container: isLargeScreen + ? { + paddingHorizontal: 24, + marginVertical: 16, + } + : { paddingVertical: 16, paddingHorizontal: 24 }, + }); + + return ( + + + {loc.wallets.list_create_a_wallet} + {loc.wallets.list_create_a_wallet_text} + + {loc.wallets.list_create_a_button} + + + + ); +}; + +interface WalletCarouselItemProps { + item: TWallet; + onPress: (item: TWallet) => void; + handleLongPress?: () => void; + isSelectedWallet?: boolean; + customStyle?: ViewStyle; + horizontal?: boolean; + isActive?: boolean; + allowOnPressAnimation?: boolean; +} + +const iStyles = StyleSheet.create({ + root: { paddingRight: 20 }, + rootLargeDevice: { marginVertical: 20 }, + grad: { + padding: 15, + borderRadius: 12, + minHeight: 164, + }, + balanceContainer: { + height: 40, + }, + image: { + width: 99, + height: 94, + position: 'absolute', + bottom: 0, + right: 0, + }, + br: { + backgroundColor: 'transparent', + }, + label: { + backgroundColor: 'transparent', + fontSize: 19, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + }, + balance: { + backgroundColor: 'transparent', + fontWeight: 'bold', + fontSize: 36, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + }, + latestTx: { + backgroundColor: 'transparent', + fontSize: 13, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + }, + latestTxTime: { + backgroundColor: 'transparent', + fontWeight: 'bold', + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + fontSize: 16, + }, + shadowContainer: { + ...Platform.select({ + ios: { + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 25 / 100, + shadowRadius: 8, + borderRadius: 12, + }, + android: { + elevation: 8, + borderRadius: 12, + }, + }), + }, +}); + +export const WalletCarouselItem: React.FC = React.memo( + ({ item, onPress, handleLongPress, isSelectedWallet, customStyle, horizontal, allowOnPressAnimation = true }) => { + const scaleValue = useRef(new Animated.Value(1.0)).current; + const { colors } = useTheme(); + const { walletTransactionUpdateStatus } = useStorage(); + const { width } = useWindowDimensions(); + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + const isLargeScreen = useIsLargeScreen(); + + const onPressedIn = useCallback(() => { + Animated.spring(scaleValue, { + toValue: 0.95, + useNativeDriver: true, + friction: 3, + tension: 100, + }).start(); + }, [scaleValue]); + + const onPressedOut = useCallback(() => { + Animated.spring(scaleValue, { + toValue: 1.0, + useNativeDriver: true, + friction: 3, + tension: 100, + }).start(); + }, [scaleValue]); + + const handlePress = useCallback(() => { + onPressedOut(); + onPress(item); + }, [item, onPress, onPressedOut]); + + const opacity = isSelectedWallet === false ? 0.5 : 1.0; + let image; + switch (item.type) { + case LightningLdkWallet.type: + case LightningCustodianWallet.type: + image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); + break; + case MultisigHDWallet.type: + image = I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); + break; + default: + image = I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); + } + + const latestTransactionText = + walletTransactionUpdateStatus === WalletTransactionsStatus.ALL || walletTransactionUpdateStatus === item.getID() + ? loc.transactions.updating + : item.getBalance() !== 0 && item.getLatestTransactionTime() === 0 + ? loc.wallets.pull_to_refresh + : item.getTransactions().find((tx: Transaction) => tx.confirmations === 0) + ? loc.transactions.pending + : transactionTimeToReadable(item.getLatestTransactionTime()); + + const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true); + + return ( + + { + if (handleLongPress) handleLongPress(); + }} + onPress={handlePress} + > + + + + + + {item.getLabel()} + + + {item.hideBalance ? ( + <> + + + + ) : ( + + {`${balance} `} + + )} + + + + {loc.wallets.list_latest_transaction} + + + {latestTransactionText} + + + + + + ); + }, +); + +interface WalletsCarouselProps extends Partial> { + horizontal?: boolean; + selectedWallet?: string; + onPress: (item: TWallet) => void; + onNewWalletPress?: () => void; + handleLongPress?: () => void; + data: TWallet[]; + scrollEnabled?: boolean; + showNewWalletPanel?: boolean; // New prop +} + +type FlatListRefType = FlatList & { + scrollToEnd(params?: { animated?: boolean | null }): void; + scrollToIndex(params: { animated?: boolean | null; index: number; viewOffset?: number; viewPosition?: number }): void; + scrollToItem(params: { animated?: boolean | null; item: TWallet; viewPosition?: number }): void; + scrollToOffset(params: { animated?: boolean | null; offset: number }): void; + recordInteraction(): void; + flashScrollIndicators(): void; + getNativeScrollRef(): View; +}; + +const cStyles = StyleSheet.create({ + content: { + paddingTop: 16, + }, + contentLargeScreen: { + paddingHorizontal: 16, + }, + separatorStyle: { + width: 16, + height: 20, + }, +}); + +const ListHeaderComponent: React.FC = () => ; + +const WalletsCarousel = forwardRef((props, ref) => { + const { horizontal, data, handleLongPress, onPress, selectedWallet, scrollEnabled, showNewWalletPanel, onNewWalletPress } = props; + const renderItem = useCallback( + ({ item, index }: ListRenderItemInfo) => + item ? ( + + ) : null, + [horizontal, selectedWallet, handleLongPress, onPress], + ); + + const flatListRef = useRef>(null); + + useImperativeHandle(ref, (): any => { + return { + scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params), + scrollToIndex: (params: { + animated?: boolean | null | undefined; + index: number; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToIndex(params), + scrollToItem: (params: { + animated?: boolean | null | undefined; + item: any; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToItem(params), + scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params), + recordInteraction: () => flatListRef.current?.recordInteraction(), + flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(), + getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(), + }; + }, []); + + const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => { + console.log('onScrollToIndexFailed'); + console.log(error); + flatListRef.current?.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); + setTimeout(() => { + if (data.length !== 0 && flatListRef.current !== null) { + flatListRef.current.scrollToIndex({ index: error.index, animated: true }); + } + }, 100); + }; + + const { width } = useWindowDimensions(); + const sliderHeight = 195; + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + + return horizontal ? ( + index.toString()} + showsVerticalScrollIndicator={false} + pagingEnabled + disableIntervalMomentum={horizontal} + snapToInterval={itemWidth} + decelerationRate="fast" + contentContainerStyle={cStyles.content} + directionalLockEnabled + showsHorizontalScrollIndicator={false} + initialNumToRender={10} + scrollEnabled={scrollEnabled} + ListHeaderComponent={ListHeaderComponent} + style={{ minHeight: sliderHeight + 12 }} + onScrollToIndexFailed={onScrollToIndexFailed} + ListFooterComponent={showNewWalletPanel && onNewWalletPress ? : null} + {...props} + /> + ) : ( + + {data.map((item, index) => + item ? ( + + ) : null, + )} + {showNewWalletPanel && onNewWalletPress && } + + ); +}); + +export default WalletsCarousel; diff --git a/loc/index.ts b/loc/index.ts index 82fbaa5eb..a4b0ffc0c 100644 --- a/loc/index.ts +++ b/loc/index.ts @@ -277,7 +277,7 @@ export const saveLanguage = async (lang: string) => { await setDateTimeLocale(); }; -export const transactionTimeToReadable = (time: number) => { +export const transactionTimeToReadable = (time: number | string) => { if (time === -1) { return 'unknown'; } diff --git a/screen/wallets/DrawerList.tsx b/screen/wallets/DrawerList.tsx index f16e91ac6..f34b0210e 100644 --- a/screen/wallets/DrawerList.tsx +++ b/screen/wallets/DrawerList.tsx @@ -1,7 +1,7 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import { NavigationProp, ParamListBase, useIsFocused } from '@react-navigation/native'; import React, { memo, useCallback, useEffect, useMemo, useReducer, useRef } from 'react'; -import { FlatList, InteractionManager, LayoutAnimation, StyleSheet, ViewStyle } from 'react-native'; +import { InteractionManager, LayoutAnimation, StyleSheet, ViewStyle } from 'react-native'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { TWallet } from '../../class/wallets/types'; @@ -83,7 +83,7 @@ const DrawerList: React.FC = memo(({ navigation }) => { }; const [state, dispatch] = useReducer(walletReducer, initialState); - const walletsCarousel = useRef>(null); + const walletsCarousel = useRef(null); const { wallets, selectedWalletID } = useStorage(); const { colors } = useTheme(); const isFocused = useIsFocused(); @@ -103,7 +103,7 @@ const DrawerList: React.FC = memo(({ navigation }) => { }, [wallets, isFocused]); const handleClick = useCallback( - (item: TWallet) => { + (item?: TWallet) => { if (item?.getID) { const walletID = item.getID(); const walletType = item.type; @@ -144,8 +144,8 @@ const DrawerList: React.FC = memo(({ navigation }) => { >
{ return ( ); diff --git a/screen/wallets/SelectWallet.tsx b/screen/wallets/SelectWallet.tsx index 0cdab8552..1e9a02ece 100644 --- a/screen/wallets/SelectWallet.tsx +++ b/screen/wallets/SelectWallet.tsx @@ -81,10 +81,6 @@ const SelectWallet: React.FC = () => { } }; - const handleLongPress = (item: TWallet) => { - // place holder. remove once WalletsCarousel is TSX - }; - if (isLoading) { return ( @@ -102,17 +98,7 @@ const SelectWallet: React.FC = () => { ); } else { - return ( - - ); + return ; } }; diff --git a/screen/wallets/WalletsList.tsx b/screen/wallets/WalletsList.tsx index 67381c4c1..db3143cac 100644 --- a/screen/wallets/WalletsList.tsx +++ b/screen/wallets/WalletsList.tsx @@ -247,13 +247,14 @@ const WalletsList: React.FC = () => { const renderWalletsCarousel = useCallback(() => { return (