Files
com.blitzwallet/app/components/admin/homeComponents/homeLightning/halfModalDepositFunds.js
T

610 lines
18 KiB
JavaScript

import { StyleSheet, View, TouchableOpacity, ScrollView } from 'react-native';
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useNavigation } from '@react-navigation/native';
import { useTranslation } from 'react-i18next';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';
import { COLORS, SIZES, CENTER } from '../../../../constants';
import {
HIDDEN_OPACITY,
INSET_WINDOW_WIDTH,
} from '../../../../constants/theme';
import { ThemeText } from '../../../../functions/CustomElements';
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
import GetThemeColors from '../../../../hooks/themeColors';
import CreateAccumulationAddressDepositModal from '../accumulationAddresses/CreateAccumulationAddressDepositModal';
import { ACCUMULATION_CHAINS } from '../../../../constants/accumulationAddresses';
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
import { Image } from 'expo-image';
import ICONS from '../../../../constants/icons';
import { useAppStatus } from '../../../../../context-store/appStatus';
import SelectOtherReceiveOptionHalfModal from './halfModalOtherOptions';
import AddFundsFromBankHalfModal from './halfModalBank';
import DepositQRView from './depositQRView';
import { useAccumulationAddresses } from '../../../../hooks/useAccumulationAddresses';
const capitalize = value =>
value ? value[0].toUpperCase() + value.slice(1) : '';
export default function HalfModalDepositFunds({
handleBackPressFunction,
setContentHeight,
setBackNav,
theme,
darkModeType,
showLightning = false,
}) {
const [activeView, setActiveView] = useState('options');
const [qrConfig, setQrConfig] = useState(null);
const [expandedChain, setExpandedChain] = useState(null);
const viewHistoryRef = useRef([]);
const navigate = useNavigation();
const { t } = useTranslation();
const { backgroundColor, backgroundOffset, textColor } = GetThemeColors();
const { bottomPadding, screenDimensions } = useAppStatus();
const { addressesForOption } = useAccumulationAddresses();
// Fix 1: Separate shared values per subview so exit animation can play
const stablecoinsOpacity = useSharedValue(0);
const stablecoinsTranslateX = useSharedValue(30);
const optionsOpacity = useSharedValue(1);
const optionsTranslateX = useSharedValue(0);
const othersOpacity = useSharedValue(0);
const othersTranslateX = useSharedValue(30);
const bankOpacity = useSharedValue(0);
const bankTranslateX = useSharedValue(30);
const qrOpacity = useSharedValue(0);
useEffect(() => {
const showStablecoins = activeView === 'stablecoins';
const showOthers = activeView === 'others';
const showOptions = activeView === 'options';
const showBank = activeView === 'bank';
const showQR = activeView === 'qr';
const subviewHiddenTranslateX = showQR ? -30 : 30;
stablecoinsOpacity.value = withTiming(showStablecoins ? 1 : 0, {
duration: 250,
});
stablecoinsTranslateX.value = withTiming(
showStablecoins ? 0 : subviewHiddenTranslateX,
{
duration: 250,
},
);
othersOpacity.value = withTiming(showOthers ? 1 : 0, {
duration: 250,
});
othersTranslateX.value = withTiming(
showOthers ? 0 : subviewHiddenTranslateX,
{
duration: 250,
},
);
optionsOpacity.value = withTiming(showOptions ? 1 : 0, { duration: 250 });
optionsTranslateX.value = withTiming(showOptions ? 0 : -30, {
duration: 250,
});
bankOpacity.value = withTiming(showBank ? 1 : 0, {
duration: 250,
});
bankTranslateX.value = withTiming(showBank ? 0 : -30, {
duration: 250,
});
qrOpacity.value = withTiming(showQR ? 1 : 0, {
duration: 250,
});
}, [activeView]);
const optionsAnimatedStyle = useAnimatedStyle(() => ({
opacity: optionsOpacity.value,
transform: [{ translateX: optionsTranslateX.value }],
}));
const stablecoinsAnimatedStyle = useAnimatedStyle(() => ({
opacity: stablecoinsOpacity.value,
transform: [{ translateX: stablecoinsTranslateX.value }],
}));
const othersAnimatedStyle = useAnimatedStyle(() => ({
opacity: othersOpacity.value,
transform: [{ translateX: othersTranslateX.value }],
}));
const bankAnimatedStyle = useAnimatedStyle(() => ({
opacity: bankOpacity.value,
transform: [{ translateX: bankTranslateX.value }],
}));
const qrAnimatedStyle = useAnimatedStyle(() => ({
opacity: qrOpacity.value,
}));
const restoreOptionsHeight = useCallback(() => {
setContentHeight(Math.round(screenDimensions.height * 0.6));
}, [setContentHeight, screenDimensions]);
const showView = useCallback(
nextView => {
if (nextView === activeView) return;
viewHistoryRef.current = [...viewHistoryRef.current, activeView];
setActiveView(nextView);
},
[activeView],
);
const handleShowQR = useCallback(
config => {
setQrConfig(config);
showView('qr');
},
[showView],
);
// Addresses for the current qr option (only populated for the stablecoins subview).
const qrGroupAddresses = useMemo(() => {
if (activeView !== 'qr') return [];
const option = qrConfig?.selectedRecieveOption?.toLowerCase();
if (option !== 'stablecoins') return [];
return addressesForOption({
sourceChain: qrConfig.sourceChain,
sourceAsset: qrConfig.sourceAsset,
destinationAsset: qrConfig.destinationAsset,
});
}, [activeView, qrConfig, addressesForOption]);
const openAddressSelector = useCallback(() => {
const currentId = qrGroupAddresses.find(
a => a.depositAddress === qrConfig?.depositAddress,
)?.accumulationAddressId;
navigate.push('CustomHalfModal', {
wantedContent: 'accumulationAddressSelect',
sliderHight: 0.5,
addresses: qrGroupAddresses,
selectedId: currentId || qrGroupAddresses[0]?.accumulationAddressId,
onSelect: addr =>
handleShowQR({ ...qrConfig, depositAddress: addr.depositAddress }),
});
}, [navigate, qrGroupAddresses, qrConfig, handleShowQR]);
const handleStepBack = useCallback(() => {
if (activeView === 'options') return false;
if (activeView === 'stablecoins' && expandedChain) {
setExpandedChain(null);
return true;
}
const previousView = viewHistoryRef.current.pop() || 'options';
if (activeView === 'qr') restoreOptionsHeight();
setActiveView(previousView);
return true;
}, [activeView, expandedChain, restoreOptionsHeight]);
// Android hardware back mirrors the visual back arrow.
useHandleBackPressNew(handleStepBack);
// Title shown in the chrome header next to the back arrow, per subview.
// The main options list and the title-less lightning/bank views show none.
const headerTitle = useMemo(() => {
switch (activeView) {
case 'qr': {
const option = qrConfig?.selectedRecieveOption?.toLowerCase();
return option === 'stablecoins'
? t('wallet.halfModal.stablecoinSelectedTitle', {
curr: qrConfig?.sourceAsset || '',
chain:
ACCUMULATION_CHAINS.find(c => c.id === qrConfig?.sourceChain)
?.label ?? capitalize(qrConfig?.sourceChain),
})
: option === 'spark' && qrConfig.fromStablecoin
? t('wallet.halfModal.stablecoinSelectedTitle', {
curr: 'USDB',
chain: 'Spark',
})
: capitalize(qrConfig?.selectedRecieveOption);
}
case 'others':
return t('wallet.halfModal.othersOptionTitle');
case 'stablecoins':
return t('screens.accumulationAddresses.create.pickChain');
default:
return '';
}
}, [activeView, qrConfig, t]);
// Register/unregister the chrome's back arrow + header based on the subview.
useEffect(() => {
if (activeView === 'options') {
setBackNav?.(null);
} else {
setBackNav?.({
onPress: handleStepBack,
title: headerTitle,
rightElement:
activeView === 'qr' && qrGroupAddresses.length > 1 ? (
<TouchableOpacity
style={[
styles.backButtonCircle,
{
backgroundColor:
theme && darkModeType ? backgroundColor : backgroundOffset,
},
]}
onPress={openAddressSelector}
>
<ThemeIcon iconName="Menu" size={22} />
</TouchableOpacity>
) : undefined,
});
}
return () => setBackNav?.(null);
}, [
activeView,
headerTitle,
handleStepBack,
setBackNav,
qrGroupAddresses,
openAddressSelector,
]);
return (
<View style={styles.container}>
{/* Options list (tiles) — Fix 2: pointerEvents blocks interaction when hidden */}
<Animated.View
style={[styles.animatedContainer, optionsAnimatedStyle]}
pointerEvents={activeView === 'options' ? 'auto' : 'none'}
>
<ThemeText
styles={styles.stepTitle}
content={
showLightning
? t('wallet.halfModal.chooseMethodTitle')
: t('wallet.halfModal.selectMethodTitle')
}
/>
<ScrollView
contentContainerStyle={{ flexGrow: 1, paddingBottom: bottomPadding }}
>
{/* Lightning Invoice */}
{showLightning && (
<TouchableOpacity
style={styles.scanButton}
onPress={() => showView('lightning')}
>
<View
style={[
styles.scanIconContainer,
{
backgroundColor:
theme && darkModeType ? backgroundColor : COLORS.primary,
},
]}
>
<Image
source={ICONS.lightningReceiveIcon}
style={[
styles.rowIcon,
{ tintColor: 'white', width: 15, height: 20 },
]}
/>
</View>
<View style={styles.scanTextContainer}>
<ThemeText
styles={styles.scanButtonText}
content={t('wallet.halfModal.lightningInvoice')}
/>
<ThemeText
styles={styles.scanButtonSubtext}
content={t('wallet.halfModal.lightningInvoiceSubtitle')}
/>
</View>
<ThemeIcon iconName={'ChevronRight'} size={18} />
</TouchableOpacity>
)}
{/* On-Chain Bitcoin */}
<TouchableOpacity
style={styles.scanButton}
onPress={() => handleShowQR({ selectedRecieveOption: 'Bitcoin' })}
>
<View
style={[
styles.scanIconContainer,
{
backgroundColor:
theme && darkModeType
? backgroundColor
: COLORS.bitcoinOrange,
},
]}
>
<Image
source={ICONS.bitcoinIcon}
style={[styles.rowIcon, { tintColor: 'white' }]}
/>
</View>
<View style={styles.scanTextContainer}>
<ThemeText
styles={styles.scanButtonText}
content={t('wallet.halfModal.onChainBitcoin')}
/>
<ThemeText
styles={styles.scanButtonSubtext}
content={t('wallet.halfModal.onChainBitcoinSubtitle')}
/>
</View>
<ThemeIcon iconName={'ChevronRight'} size={18} />
</TouchableOpacity>
{/* Stablecoins */}
<TouchableOpacity
style={styles.scanButton}
onPress={() => showView('stablecoins')}
>
<View
style={[
styles.scanIconContainer,
{
backgroundColor:
theme && darkModeType
? backgroundColor
: COLORS.dollarGreen,
},
]}
>
<Image
source={ICONS.dollarIcon}
style={[styles.rowIcon, { tintColor: 'white' }]}
/>
</View>
<View style={styles.scanTextContainer}>
<ThemeText
styles={styles.scanButtonText}
content={t('wallet.halfModal.stablecoins')}
/>
<ThemeText
styles={styles.scanButtonSubtext}
content={t('wallet.halfModal.stablecoinsSubtitle')}
/>
</View>
<ThemeIcon iconName={'ChevronRight'} size={18} />
</TouchableOpacity>
{/* Other Bitcoin */}
<TouchableOpacity
style={styles.scanButton}
onPress={() => showView('others')}
>
<View
style={[
styles.scanIconContainer,
{
backgroundColor:
theme && darkModeType ? backgroundColor : backgroundOffset,
},
]}
>
<ThemeIcon
// colorOverride={COLORS.darkModeText}
size={20}
iconName={'Ellipsis'}
/>
</View>
<View style={styles.scanTextContainer}>
<ThemeText
styles={styles.scanButtonText}
content={t('wallet.halfModal.otherBitcoin')}
/>
<ThemeText
styles={styles.scanButtonSubtext}
content={t('wallet.halfModal.otherBitcoinSubtitle')}
/>
</View>
<ThemeIcon iconName={'ChevronRight'} size={18} />
</TouchableOpacity>
</ScrollView>
</Animated.View>
{/* Stablecoins subview */}
<Animated.View
style={[
StyleSheet.absoluteFill,
styles.animatedContainer,
{
backgroundColor:
theme && darkModeType ? backgroundOffset : backgroundColor,
},
stablecoinsAnimatedStyle,
]}
pointerEvents={activeView === 'stablecoins' ? 'auto' : 'none'}
>
<CreateAccumulationAddressDepositModal
setContentHeight={() => {}}
handleBackPressFunction={handleBackPressFunction}
onShowQR={handleShowQR}
expandedChain={expandedChain}
setExpandedChain={setExpandedChain}
/>
</Animated.View>
{/* Others subview */}
<Animated.View
style={[
StyleSheet.absoluteFill,
styles.animatedContainer,
{
backgroundColor:
theme && darkModeType ? backgroundOffset : backgroundColor,
},
othersAnimatedStyle,
]}
pointerEvents={activeView === 'others' ? 'auto' : 'none'}
>
<SelectOtherReceiveOptionHalfModal
handleBackPressFunction={handleBackPressFunction}
onShowQR={handleShowQR}
/>
</Animated.View>
{/* bank subview */}
<Animated.View
style={[
StyleSheet.absoluteFill,
styles.animatedContainer,
{
backgroundColor:
theme && darkModeType ? backgroundOffset : backgroundColor,
},
bankAnimatedStyle,
]}
pointerEvents={activeView === 'bank' ? 'auto' : 'none'}
>
<AddFundsFromBankHalfModal
handleBackPressFunction={handleBackPressFunction}
setContentHeight={setContentHeight}
activeView={activeView}
/>
</Animated.View>
{/* qr subview */}
<Animated.View
style={[
StyleSheet.absoluteFill,
styles.animatedContainer,
{
backgroundColor:
theme && darkModeType ? backgroundOffset : backgroundColor,
},
qrAnimatedStyle,
]}
pointerEvents={activeView === 'qr' ? 'auto' : 'none'}
>
<DepositQRView
config={qrConfig}
setContentHeight={setContentHeight}
onBack={handleStepBack}
isActive={activeView === 'qr'}
/>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: INSET_WINDOW_WIDTH,
...CENTER,
flex: 1,
},
backButtonCircle: {
width: 36,
height: 36,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
},
animatedContainer: {
flex: 1,
},
stepTitle: {
fontSize: SIZES.large,
fontWeight: 500,
marginBottom: 8,
includeFontPadding: false,
},
subviewContainer: {},
subviewContent: {
flex: 1,
},
backButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
marginBottom: 16,
},
subviewTitle: {
fontSize: SIZES.large,
fontWeight: 500,
marginBottom: 12,
includeFontPadding: false,
},
amountInput: {
width: '100%',
fontSize: SIZES.large,
borderWidth: 1,
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 12,
marginBottom: 16,
},
addressPreview: {
fontSize: SIZES.small,
opacity: 0.6,
marginBottom: 8,
includeFontPadding: false,
},
viewDetailsLink: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: 12,
gap: 4,
},
viewDetailsText: {
fontSize: SIZES.small,
opacity: 0.7,
includeFontPadding: false,
},
scanButton: {
width: '100%',
flexDirection: 'row',
alignItems: 'center',
borderRadius: 12,
paddingVertical: 8,
gap: 15,
},
scanIconContainer: {
width: 48,
height: 48,
borderRadius: 30,
alignItems: 'center',
justifyContent: 'center',
},
scanTextContainer: {
flex: 1,
},
scanButtonText: {
fontSize: SIZES.medium,
marginBottom: 2,
includeFontPadding: false,
},
scanButtonSubtext: {
fontSize: SIZES.small,
opacity: HIDDEN_OPACITY,
},
rowIcon: {
width: 26,
height: 26,
},
otherIconGrid: {
alignItems: 'center',
gap: 3,
},
otherIconRow: {
flexDirection: 'row',
gap: 3,
},
otherSmallIcon: {
width: 13,
height: 13,
},
});