Standardize half modal back (#894)
* standardized chain row sizing * add back to online listings * add back to gift quantity page * add back button to view tokens page * update pool half modal to back button * Added back button to swap flow * adding back button to savings pages
This commit is contained in:
+4
-154
@@ -1,19 +1,11 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
|
||||
import { useMemo } from 'react';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import GetThemeColors from '../../../../hooks/themeColors';
|
||||
import { HIDDEN_OPACITY } from '../../../../constants/theme';
|
||||
|
||||
import { ACCUMULATION_CHAINS } from '../../../../constants/accumulationAddresses';
|
||||
import { CENTER, ICONS } from '../../../../constants';
|
||||
import { Image } from 'expo-image';
|
||||
import { useGlobalThemeContext } from '../../../../../context-store/theme';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import ChainRow from './chainRow';
|
||||
|
||||
export default function CreateAccumulationAddressDepositModal({
|
||||
setContentHeight,
|
||||
@@ -66,152 +58,10 @@ export default function CreateAccumulationAddressDepositModal({
|
||||
return <View style={styles.container}>{chainElements}</View>;
|
||||
}
|
||||
|
||||
function ChainRow({
|
||||
chain,
|
||||
expanded,
|
||||
onToggleExpand,
|
||||
onSelectAsset,
|
||||
isAssetTaken,
|
||||
onDisabledAssetPress,
|
||||
theme,
|
||||
darkModeType,
|
||||
backgroundColor,
|
||||
backgroundOffset,
|
||||
}) {
|
||||
const expandHeight = useSharedValue(0);
|
||||
const chevronRotation = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
expandHeight.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
chevronRotation.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
}, [expanded]);
|
||||
|
||||
const expandedStyle = useAnimatedStyle(() => ({
|
||||
height: expandHeight.value * (chain.assets.length * 65 + 26),
|
||||
opacity: expandHeight.value,
|
||||
}));
|
||||
|
||||
const chevronStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${chevronRotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={styles.chainRow}
|
||||
onPress={() => onToggleExpand(chain.id)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.chainIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.assetIcon}
|
||||
source={ICONS[`chain_${chain.label.toLowerCase()}`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={chain.label} />
|
||||
|
||||
<Animated.View style={[{ opacity: HIDDEN_OPACITY }, chevronStyle]}>
|
||||
<ThemeIcon iconName="ChevronDown" size={18} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Animated.View style={[styles.assetOptionsContainer, expandedStyle]}>
|
||||
{chain.assets.map(asset => {
|
||||
const disabled = isAssetTaken(asset);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={asset}
|
||||
activeOpacity={0.7}
|
||||
style={[
|
||||
styles.assetOptionRow,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
opacity: disabled ? HIDDEN_OPACITY : 1,
|
||||
},
|
||||
]}
|
||||
onPress={() =>
|
||||
disabled ? onDisabledAssetPress() : onSelectAsset(chain, asset)
|
||||
}
|
||||
>
|
||||
<View style={styles.assetOptionIconContainer}>
|
||||
<Image
|
||||
style={styles.assetOptionIcon}
|
||||
source={ICONS[`${asset.toLowerCase()}Logo`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={asset} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
...CENTER,
|
||||
},
|
||||
chainRow: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
// paddingVertical: 8,
|
||||
},
|
||||
chainIconContainer: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
borderRadius: 22.5,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 15,
|
||||
},
|
||||
optionLabel: {
|
||||
flex: 1,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
assetIcon: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
},
|
||||
assetOptionsContainer: {
|
||||
overflow: 'hidden',
|
||||
gap: 12,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
assetOptionRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
gap: 12,
|
||||
},
|
||||
assetOptionIconContainer: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderRadius: 17.5,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
assetOptionIcon: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
},
|
||||
});
|
||||
|
||||
+259
-313
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -27,8 +27,8 @@ import Animated, {
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { scheduleOnRN } from 'react-native-worklets';
|
||||
import { useGlobalInsets } from '../../../../../context-store/insetsProvider';
|
||||
import ChainRow from './chainRow';
|
||||
|
||||
// Steps: 'chain' | 'destination' | 'confirm'
|
||||
const STEPS = ['chain', 'destination', 'confirm'];
|
||||
@@ -37,6 +37,7 @@ export default function CreateAccumulationAddressModal({
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
forcedDestination,
|
||||
setBackNav,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
@@ -46,8 +47,6 @@ export default function CreateAccumulationAddressModal({
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
|
||||
const [step, setStep] = useState('chain');
|
||||
const [renderedStep, setRenderedStep] = useState('chain');
|
||||
const renderedStepRef = useRef('chain');
|
||||
const [expandedChain, setExpandedChain] = useState(null);
|
||||
const [selectedAsset, setSelectedAsset] = useState(null);
|
||||
const [selectedChain, setSelectedChain] = useState(null);
|
||||
@@ -55,6 +54,7 @@ export default function CreateAccumulationAddressModal({
|
||||
forcedDestination || null,
|
||||
);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [mountedSteps, setMountedSteps] = useState(() => new Set(['chain']));
|
||||
|
||||
const isPairTaken = useCallback(
|
||||
(chainId, asset, dest) =>
|
||||
@@ -67,39 +67,66 @@ export default function CreateAccumulationAddressModal({
|
||||
[addresses],
|
||||
);
|
||||
|
||||
const pageOpacity = useSharedValue(1);
|
||||
const pageTranslateX = useSharedValue(0);
|
||||
|
||||
const startInAnimation = useCallback(
|
||||
(newStep, goingForward) => {
|
||||
renderedStepRef.current = newStep;
|
||||
setRenderedStep(newStep);
|
||||
pageTranslateX.value = goingForward ? 30 : -30;
|
||||
pageOpacity.value = withTiming(1, { duration: 125 });
|
||||
pageTranslateX.value = withTiming(0, { duration: 125 });
|
||||
},
|
||||
[pageTranslateX, pageOpacity],
|
||||
);
|
||||
const chainOpacity = useSharedValue(1);
|
||||
const chainTranslateX = useSharedValue(0);
|
||||
const destinationOpacity = useSharedValue(0);
|
||||
const destinationTranslateX = useSharedValue(30);
|
||||
const confirmOpacity = useSharedValue(0);
|
||||
const confirmTranslateX = useSharedValue(30);
|
||||
|
||||
useEffect(() => {
|
||||
if (step === renderedStepRef.current) return;
|
||||
const goingForward =
|
||||
STEPS.indexOf(step) > STEPS.indexOf(renderedStepRef.current);
|
||||
pageOpacity.value = withTiming(0, { duration: 125 });
|
||||
pageTranslateX.value = withTiming(
|
||||
goingForward ? -30 : 30,
|
||||
{ duration: 125 },
|
||||
finished => {
|
||||
if (finished) scheduleOnRN(startInAnimation, step, goingForward);
|
||||
},
|
||||
);
|
||||
const activeIndex = STEPS.indexOf(step);
|
||||
const translateForStep = screenStep => {
|
||||
const screenIndex = STEPS.indexOf(screenStep);
|
||||
if (screenIndex === activeIndex) return 0;
|
||||
return screenIndex < activeIndex ? -30 : 30;
|
||||
};
|
||||
|
||||
chainOpacity.value = withTiming(step === 'chain' ? 1 : 0, {
|
||||
duration: 250,
|
||||
});
|
||||
chainTranslateX.value = withTiming(translateForStep('chain'), {
|
||||
duration: 250,
|
||||
});
|
||||
destinationOpacity.value = withTiming(step === 'destination' ? 1 : 0, {
|
||||
duration: 250,
|
||||
});
|
||||
destinationTranslateX.value = withTiming(translateForStep('destination'), {
|
||||
duration: 250,
|
||||
});
|
||||
confirmOpacity.value = withTiming(step === 'confirm' ? 1 : 0, {
|
||||
duration: 250,
|
||||
});
|
||||
confirmTranslateX.value = withTiming(translateForStep('confirm'), {
|
||||
duration: 250,
|
||||
});
|
||||
}, [step]);
|
||||
|
||||
const pageAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: pageOpacity.value,
|
||||
transform: [{ translateX: pageTranslateX.value }],
|
||||
const chainAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: chainOpacity.value,
|
||||
transform: [{ translateX: chainTranslateX.value }],
|
||||
}));
|
||||
|
||||
const destinationAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: destinationOpacity.value,
|
||||
transform: [{ translateX: destinationTranslateX.value }],
|
||||
}));
|
||||
|
||||
const confirmAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: confirmOpacity.value,
|
||||
transform: [{ translateX: confirmTranslateX.value }],
|
||||
}));
|
||||
|
||||
const goToStep = useCallback(nextStep => {
|
||||
setMountedSteps(prev => {
|
||||
if (prev.has(nextStep)) return prev;
|
||||
const next = new Set(prev);
|
||||
next.add(nextStep);
|
||||
return next;
|
||||
});
|
||||
setStep(nextStep);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setContentHeight(450);
|
||||
}, [step]);
|
||||
@@ -109,22 +136,38 @@ export default function CreateAccumulationAddressModal({
|
||||
|
||||
if (step === 'destination') {
|
||||
setSelectedDestination(null);
|
||||
setStep('chain');
|
||||
goToStep('chain');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (step === 'confirm') {
|
||||
setSelectedDestination(forcedDestination || null);
|
||||
setStep(forcedDestination ? 'chain' : 'destination');
|
||||
goToStep(forcedDestination ? 'chain' : 'destination');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'chain' step — let the modal close naturally
|
||||
return false;
|
||||
}, [isCreating, step, forcedDestination]);
|
||||
}, [isCreating, step, forcedDestination, goToStep]);
|
||||
|
||||
useHandleBackPressNew(handleBackPress);
|
||||
|
||||
// Register the chrome's back arrow whenever past the first step.
|
||||
useEffect(() => {
|
||||
if (step === 'chain') {
|
||||
setBackNav?.(null);
|
||||
} else {
|
||||
setBackNav?.({
|
||||
onPress: handleBackPress,
|
||||
title:
|
||||
step === 'destination'
|
||||
? t('screens.accumulationAddresses.create.pickDestination')
|
||||
: t('screens.accumulationAddresses.create.confirm'),
|
||||
});
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [step, handleBackPress, setBackNav]);
|
||||
|
||||
const handleCreate = useCallback(async () => {
|
||||
setIsCreating(true);
|
||||
const result = await createAddress({
|
||||
@@ -150,15 +193,21 @@ export default function CreateAccumulationAddressModal({
|
||||
t,
|
||||
]);
|
||||
|
||||
// ── Chain step ──────────────────────────────────────────────────────────────
|
||||
if (renderedStep === 'chain') {
|
||||
return (
|
||||
<Animated.View style={[styles.container, pageAnimatedStyle]}>
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Chain step */}
|
||||
<Animated.View
|
||||
style={[styles.animatedContainer, chainAnimatedStyle]}
|
||||
pointerEvents={step === 'chain' ? 'auto' : 'none'}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.stepTitle}
|
||||
content={t('screens.accumulationAddresses.create.pickChain')}
|
||||
/>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<ScrollView
|
||||
contentContainerStyle={{ paddingBottom: bottomPadding }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{ACCUMULATION_CHAINS.map(chain => (
|
||||
<ChainRow
|
||||
key={chain.id}
|
||||
@@ -170,7 +219,8 @@ export default function CreateAccumulationAddressModal({
|
||||
onSelectAsset={(c, asset) => {
|
||||
setSelectedChain(c);
|
||||
setSelectedAsset(asset);
|
||||
setStep(forcedDestination ? 'confirm' : 'destination');
|
||||
goToStep(forcedDestination ? 'confirm' : 'destination');
|
||||
setExpandedChain(null);
|
||||
}}
|
||||
isAssetTaken={
|
||||
forcedDestination
|
||||
@@ -192,268 +242,176 @@ export default function CreateAccumulationAddressModal({
|
||||
))}
|
||||
</ScrollView>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Destination step ────────────────────────────────────────────────────────
|
||||
if (renderedStep === 'destination') {
|
||||
return (
|
||||
<Animated.View style={[styles.container, pageAnimatedStyle]}>
|
||||
<ThemeText
|
||||
styles={styles.stepTitle}
|
||||
content={t('screens.accumulationAddresses.create.pickDestination')}
|
||||
/>
|
||||
{ACCUMULATION_DESTINATIONS.map(dest => {
|
||||
const taken = isPairTaken(selectedChain?.id, selectedAsset, dest);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={dest}
|
||||
activeOpacity={taken ? HIDDEN_OPACITY : 0.2}
|
||||
style={[
|
||||
styles.optionRow,
|
||||
{ opacity: taken ? HIDDEN_OPACITY : 1 },
|
||||
]}
|
||||
onPress={() => {
|
||||
if (taken) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(
|
||||
'screens.accumulationAddresses.create.alreadyExists',
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedDestination(dest);
|
||||
setStep('confirm');
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset
|
||||
: dest === 'BTC'
|
||||
? COLORS.bitcoinOrange
|
||||
: COLORS.dollarGreen,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeImage
|
||||
styles={{ width: 25, height: 25 }}
|
||||
lightModeIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
darkModeIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
lightsOutIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<ThemeText
|
||||
styles={styles.optionLabel}
|
||||
content={
|
||||
dest === 'BTC'
|
||||
? t('constants.bitcoin_upper')
|
||||
: t('constants.dollars_upper')
|
||||
}
|
||||
/>
|
||||
<ThemeIcon iconName="ChevronRight" size={18} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Confirm step ────────────────────────────────────────────────────────────
|
||||
return (
|
||||
<Animated.View style={[styles.container, pageAnimatedStyle]}>
|
||||
<ThemeText
|
||||
styles={styles.stepTitle}
|
||||
content={t('screens.accumulationAddresses.create.confirm')}
|
||||
/>
|
||||
|
||||
<View style={styles.confirmContent}>
|
||||
<View style={styles.assetRow}>
|
||||
<View style={styles.confirmIconWrapper}>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmChainCircle,
|
||||
{ backgroundColor: backgroundOffset },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.confirmChainIcon}
|
||||
source={ICONS[`chain_${selectedChain?.label.toLowerCase()}`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmCurrencyBadge,
|
||||
{ borderColor: backgroundColor },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.confirmCurrencyIcon}
|
||||
source={ICONS[`${selectedAsset?.toLowerCase()}Logo`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ThemeIcon
|
||||
styles={{ opacity: HIDDEN_OPACITY }}
|
||||
iconName={'ArrowRight'}
|
||||
/>
|
||||
<View style={styles.confirmIconWrapper}>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmChainCircle,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: selectedDestination === 'BTC'
|
||||
? COLORS.bitcoinOrange
|
||||
: COLORS.dollarGreen,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={[styles.confirmChainIcon, { width: 50, height: 50 }]}
|
||||
source={
|
||||
ICONS[
|
||||
selectedDestination === 'BTC' ? 'bitcoinIcon' : 'dollarIcon'
|
||||
]
|
||||
}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.confirmChainName}
|
||||
content={selectedChain?.label}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.confirmSubtitle}
|
||||
content={t('screens.accumulationAddresses.create.convertDesc', {
|
||||
chain: selectedChain?.label,
|
||||
asset: selectedAsset,
|
||||
receiveCurrency:
|
||||
selectedDestination === 'BTC'
|
||||
? t('constants.bitcoin_upper')
|
||||
: t('constants.dollars_upper'),
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={[styles.createBtn, { marginBottom: bottomPadding }]}
|
||||
textContent={t('screens.accumulationAddresses.summary.create')}
|
||||
actionFunction={handleCreate}
|
||||
useLoading={isCreating}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
function ChainRow({
|
||||
chain,
|
||||
expanded,
|
||||
onToggleExpand,
|
||||
onSelectAsset,
|
||||
isAssetTaken,
|
||||
onDisabledAssetPress,
|
||||
theme,
|
||||
darkModeType,
|
||||
backgroundColor,
|
||||
backgroundOffset,
|
||||
}) {
|
||||
const expandHeight = useSharedValue(0);
|
||||
const chevronRotation = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
expandHeight.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
chevronRotation.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
}, [expanded]);
|
||||
|
||||
const expandedStyle = useAnimatedStyle(() => ({
|
||||
height: expandHeight.value * (chain.assets.length * 65 + 16),
|
||||
opacity: expandHeight.value,
|
||||
}));
|
||||
|
||||
const chevronStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${chevronRotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={styles.chainRow}
|
||||
onPress={() => onToggleExpand(chain.id)}
|
||||
>
|
||||
<View
|
||||
{mountedSteps.has('destination') && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.chainIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
StyleSheet.absoluteFill,
|
||||
styles.animatedContainer,
|
||||
destinationAnimatedStyle,
|
||||
]}
|
||||
pointerEvents={step === 'destination' ? 'auto' : 'none'}
|
||||
>
|
||||
<Image
|
||||
style={styles.assetIcon}
|
||||
source={ICONS[`chain_${chain.label.toLowerCase()}`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={chain.label} />
|
||||
<View style={{ opacity: HIDDEN_OPACITY }}>
|
||||
<Animated.View style={chevronStyle}>
|
||||
<ThemeIcon iconName="ChevronDown" size={18} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Animated.View style={[styles.assetOptionsContainer, expandedStyle]}>
|
||||
{chain.assets.map(asset => {
|
||||
const disabled = isAssetTaken(asset);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={asset}
|
||||
activeOpacity={0.7}
|
||||
style={[
|
||||
styles.assetOptionRow,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
opacity: disabled ? HIDDEN_OPACITY : 1,
|
||||
},
|
||||
]}
|
||||
onPress={() =>
|
||||
disabled ? onDisabledAssetPress() : onSelectAsset(chain, asset)
|
||||
}
|
||||
>
|
||||
<View style={styles.assetOptionIconContainer}>
|
||||
<Image
|
||||
style={styles.assetOptionIcon}
|
||||
source={ICONS[`${asset.toLowerCase()}Logo`]}
|
||||
contentFit="contain"
|
||||
{ACCUMULATION_DESTINATIONS.map(dest => {
|
||||
const taken = isPairTaken(selectedChain?.id, selectedAsset, dest);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={dest}
|
||||
activeOpacity={taken ? HIDDEN_OPACITY : 0.2}
|
||||
style={[
|
||||
styles.optionRow,
|
||||
{ opacity: taken ? HIDDEN_OPACITY : 1 },
|
||||
]}
|
||||
onPress={() => {
|
||||
if (taken) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(
|
||||
'screens.accumulationAddresses.create.alreadyExists',
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedDestination(dest);
|
||||
goToStep('confirm');
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset
|
||||
: dest === 'BTC'
|
||||
? COLORS.bitcoinOrange
|
||||
: COLORS.dollarGreen,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeImage
|
||||
styles={{ width: 25, height: 25 }}
|
||||
lightModeIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
darkModeIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
lightsOutIcon={
|
||||
dest === 'BTC' ? ICONS.bitcoinIcon : ICONS.dollarIcon
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<ThemeText
|
||||
styles={styles.optionLabel}
|
||||
content={
|
||||
dest === 'BTC'
|
||||
? t('constants.bitcoin_upper')
|
||||
: t('constants.dollars_upper')
|
||||
}
|
||||
/>
|
||||
<ThemeIcon iconName="ChevronRight" size={18} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{mountedSteps.has('confirm') && (
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.animatedContainer,
|
||||
confirmAnimatedStyle,
|
||||
]}
|
||||
pointerEvents={step === 'confirm' ? 'auto' : 'none'}
|
||||
>
|
||||
<View style={styles.confirmContent}>
|
||||
<View style={styles.assetRow}>
|
||||
<View style={styles.confirmIconWrapper}>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmChainCircle,
|
||||
{ backgroundColor: backgroundOffset },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.confirmChainIcon}
|
||||
source={
|
||||
ICONS[`chain_${selectedChain?.label.toLowerCase()}`]
|
||||
}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmCurrencyBadge,
|
||||
{ borderColor: backgroundColor },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.confirmCurrencyIcon}
|
||||
source={ICONS[`${selectedAsset?.toLowerCase()}Logo`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={asset} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</Animated.View>
|
||||
<ThemeIcon styles={{ opacity: 0.7 }} iconName={'ArrowRight'} />
|
||||
<View style={styles.confirmIconWrapper}>
|
||||
<View
|
||||
style={[
|
||||
styles.confirmChainCircle,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: selectedDestination === 'BTC'
|
||||
? COLORS.bitcoinOrange
|
||||
: COLORS.dollarGreen,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={[styles.confirmChainIcon, { width: 50, height: 50 }]}
|
||||
source={
|
||||
ICONS[
|
||||
selectedDestination === 'BTC'
|
||||
? 'bitcoinIcon'
|
||||
: 'dollarIcon'
|
||||
]
|
||||
}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.confirmChainName}
|
||||
content={selectedChain?.label}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.confirmSubtitle}
|
||||
content={t('screens.accumulationAddresses.create.convertDesc', {
|
||||
chain: selectedChain?.label,
|
||||
asset: selectedAsset,
|
||||
receiveCurrency:
|
||||
selectedDestination === 'BTC'
|
||||
? t('constants.bitcoin_upper')
|
||||
: t('constants.dollars_upper'),
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={[styles.createBtn, { marginBottom: bottomPadding }]}
|
||||
textContent={t('screens.accumulationAddresses.summary.create')}
|
||||
actionFunction={handleCreate}
|
||||
useLoading={isCreating}
|
||||
/>
|
||||
</Animated.View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -464,32 +422,20 @@ const styles = StyleSheet.create({
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
...CENTER,
|
||||
},
|
||||
animatedContainer: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
},
|
||||
stepTitle: {
|
||||
fontSize: SIZES.large,
|
||||
fontWeight: 500,
|
||||
marginBottom: 8,
|
||||
},
|
||||
chainRow: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
},
|
||||
chainIconContainer: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
borderRadius: 22.5,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 15,
|
||||
},
|
||||
optionRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
marginBottom: 8,
|
||||
paddingBottom: 16,
|
||||
gap: 10,
|
||||
},
|
||||
assetRow: {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { CENTER, ICONS } from '../../../../constants';
|
||||
import { HIDDEN_OPACITY } from '../../../../constants/theme';
|
||||
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function ChainRow({
|
||||
chain,
|
||||
expanded,
|
||||
onToggleExpand,
|
||||
onSelectAsset,
|
||||
isAssetTaken,
|
||||
onDisabledAssetPress,
|
||||
theme,
|
||||
darkModeType,
|
||||
backgroundColor,
|
||||
backgroundOffset,
|
||||
}) {
|
||||
const expandHeight = useSharedValue(0);
|
||||
const chevronRotation = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
expandHeight.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
chevronRotation.value = withTiming(expanded ? 1 : 0, { duration: 200 });
|
||||
}, [expanded]);
|
||||
|
||||
const expandedStyle = useAnimatedStyle(() => ({
|
||||
height: expandHeight.value * (chain.assets.length * 65 + 26),
|
||||
opacity: expandHeight.value,
|
||||
}));
|
||||
|
||||
const chevronStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${chevronRotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={styles.chainRow}
|
||||
onPress={() => onToggleExpand(chain.id)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.chainIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.assetIcon}
|
||||
source={ICONS[`chain_${chain.label.toLowerCase()}`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={chain.label} />
|
||||
|
||||
<Animated.View style={[{ opacity: HIDDEN_OPACITY }, chevronStyle]}>
|
||||
<ThemeIcon iconName="ChevronDown" size={18} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Animated.View style={[styles.assetOptionsContainer, expandedStyle]}>
|
||||
{chain.assets.map(asset => {
|
||||
const disabled = isAssetTaken(asset);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={asset}
|
||||
activeOpacity={0.7}
|
||||
style={[
|
||||
styles.assetOptionRow,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
opacity: disabled ? HIDDEN_OPACITY : 1,
|
||||
},
|
||||
]}
|
||||
onPress={() =>
|
||||
disabled ? onDisabledAssetPress() : onSelectAsset(chain, asset)
|
||||
}
|
||||
>
|
||||
<View style={styles.assetOptionIconContainer}>
|
||||
<Image
|
||||
style={styles.assetOptionIcon}
|
||||
source={ICONS[`${asset.toLowerCase()}Logo`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
</View>
|
||||
<ThemeText styles={styles.optionLabel} content={asset} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
chainRow: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
// paddingVertical: 8,
|
||||
},
|
||||
chainIconContainer: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
borderRadius: 22.5,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 15,
|
||||
},
|
||||
optionLabel: {
|
||||
flex: 1,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
assetIcon: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
},
|
||||
assetOptionsContainer: {
|
||||
overflow: 'hidden',
|
||||
gap: 12,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
assetOptionRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
gap: 12,
|
||||
},
|
||||
assetOptionIconContainer: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderRadius: 17.5,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
assetOptionIcon: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
},
|
||||
});
|
||||
+11
-27
@@ -48,6 +48,7 @@ export default function OnlineListingsFilterHalfModal({
|
||||
handleBackPressFunction,
|
||||
setContentHeight,
|
||||
categoryOptions = [],
|
||||
setBackNav,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
@@ -238,6 +239,16 @@ export default function OnlineListingsFilterHalfModal({
|
||||
|
||||
useHandleBackPressNew(handleCountryBackPress);
|
||||
|
||||
// Register the chrome's back arrow while the country picker is open.
|
||||
useEffect(() => {
|
||||
if (showCountryPicker) {
|
||||
setBackNav?.({ onPress: handleCountryBackPress, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [showCountryPicker, handleCountryBackPress, setBackNav]);
|
||||
|
||||
const renderCountryItem = useCallback(
|
||||
({ item }) => {
|
||||
const isSelected = draft.countryCode === item.code;
|
||||
@@ -325,11 +336,6 @@ export default function OnlineListingsFilterHalfModal({
|
||||
{ paddingBottom: bottomPadding },
|
||||
]}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.title}
|
||||
content={t('apps.onlineListings.filterTitle')}
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -514,16 +520,6 @@ export default function OnlineListingsFilterHalfModal({
|
||||
countryOverlayStyle,
|
||||
]}
|
||||
>
|
||||
<View style={styles.countryOverlayHeader}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleCountryBackPress}
|
||||
style={styles.countryOverlayBack}
|
||||
>
|
||||
<ThemeIcon iconName="ArrowLeft" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<CustomSearchInput
|
||||
inputText={countrySearch}
|
||||
setInputText={setCountrySearch}
|
||||
@@ -659,18 +655,6 @@ const styles = StyleSheet.create({
|
||||
countryOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
countryOverlayHeader: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 16,
|
||||
},
|
||||
countryOverlayBack: {
|
||||
// width: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
searchInput: {
|
||||
width: '100%',
|
||||
paddingBottom: CONTENT_KEYBOARD_OFFSET,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import {
|
||||
CENTER,
|
||||
@@ -24,9 +25,12 @@ import { useGlobalContextProvider } from '../../../../../context-store/context';
|
||||
import { useFlashnet } from '../../../../../context-store/flashnetContext';
|
||||
import { dollarsToSats } from '../../../../functions/spark/flashnet';
|
||||
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
|
||||
import useHalfModalStepTransition from '../../../../hooks/useHalfModalStepTransition';
|
||||
import displayCorrectDenomination from '../../../../functions/displayCorrectDenomination';
|
||||
import { useNodeContext } from '../../../../../context-store/nodeContext';
|
||||
|
||||
const STEP_ORDER = ['select', 'custom'];
|
||||
|
||||
export default function AddGiftQuantityHalfModal({
|
||||
amount,
|
||||
amountValue,
|
||||
@@ -34,6 +38,7 @@ export default function AddGiftQuantityHalfModal({
|
||||
giftDenomination,
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
setBackNav,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
@@ -47,6 +52,11 @@ export default function AddGiftQuantityHalfModal({
|
||||
const [step, setStep] = useState('select');
|
||||
const [customValue, setCustomValue] = useState('');
|
||||
|
||||
const { renderedPage, pageAnimatedStyle } = useHalfModalStepTransition(
|
||||
step,
|
||||
STEP_ORDER,
|
||||
);
|
||||
|
||||
const currentDerivedGiftIndex = masterInfoObject.currentDerivedGiftIndex || 1;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,6 +74,16 @@ export default function AddGiftQuantityHalfModal({
|
||||
|
||||
useHandleBackPressNew(handleBackPress);
|
||||
|
||||
// Register the chrome's back arrow while on the custom-quantity step.
|
||||
useEffect(() => {
|
||||
if (step === 'custom') {
|
||||
setBackNav?.({ onPress: handleBackPress, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [step, handleBackPress, setBackNav]);
|
||||
|
||||
const validateAndProceed = quantity => {
|
||||
if (!quantity || quantity <= 0) return;
|
||||
|
||||
@@ -229,12 +249,12 @@ export default function AddGiftQuantityHalfModal({
|
||||
);
|
||||
};
|
||||
|
||||
if (step === 'custom') {
|
||||
if (renderedPage === 'custom') {
|
||||
const customQty = parseInt(customValue, 10) || 0;
|
||||
const isValid = customQty >= 1 && customQty <= MAX_GIFT_QUANTITY;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View style={[styles.container, pageAnimatedStyle]}>
|
||||
<View style={styles.inputContainer}>
|
||||
<ThemeText
|
||||
styles={styles.quantityDisplay}
|
||||
@@ -274,12 +294,12 @@ export default function AddGiftQuantityHalfModal({
|
||||
textContent={isValid ? t('constants.continue') : t('constants.back')}
|
||||
actionFunction={handleCustomContinue}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View style={[styles.container, pageAnimatedStyle]}>
|
||||
<ThemeText
|
||||
styles={styles.header}
|
||||
content={t('screens.inAccount.giftPages.createGift.quantityHeader')}
|
||||
@@ -291,7 +311,7 @@ export default function AddGiftQuantityHalfModal({
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
INFINITY_SYMBOL,
|
||||
TOKEN_TICKER_MAX_LENGTH,
|
||||
} from '../../../../constants';
|
||||
import CustomButton from '../../../../functions/CustomElements/button';
|
||||
import { useGlobalThemeContext } from '../../../../../context-store/theme';
|
||||
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
|
||||
import openWebBrowser from '../../../../functions/openWebBrowser';
|
||||
@@ -125,13 +124,7 @@ function TokenListItem({
|
||||
|
||||
// ─── Token Detail View ───────────────────────────────────────────────────────
|
||||
|
||||
function TokenDetailView({
|
||||
token,
|
||||
tokenIdentifier,
|
||||
onBack,
|
||||
theme,
|
||||
darkModeType,
|
||||
}) {
|
||||
function TokenDetailView({ token, tokenIdentifier, theme, darkModeType }) {
|
||||
const navigate = useNavigation();
|
||||
const { tokensImageCache } = useSparkWallet();
|
||||
const { masterInfoObject } = useGlobalContextProvider();
|
||||
@@ -368,13 +361,6 @@ function TokenDetailView({
|
||||
content={t('screens.inAccount.lrc20TokenDataHalfModal.tokenInfo')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Back button */}
|
||||
<CustomButton
|
||||
actionFunction={onBack}
|
||||
buttonStyles={{ ...CENTER, marginTop: 'auto' }}
|
||||
textContent={t('constants.back')}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -384,6 +370,7 @@ function TokenDetailView({
|
||||
export default function ViewAllTokensHalfModal({
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
setBackNav,
|
||||
}) {
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { sparkInformation } = useSparkWallet();
|
||||
@@ -413,6 +400,16 @@ export default function ViewAllTokensHalfModal({
|
||||
|
||||
useHandleBackPressNew(customBackHandler);
|
||||
|
||||
// Register the chrome's back arrow while viewing a token's detail page.
|
||||
useEffect(() => {
|
||||
if (selectedIdentifier && selectedToken) {
|
||||
setBackNav?.({ onPress: handleBack, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [selectedIdentifier, selectedToken, setBackNav]);
|
||||
|
||||
useEffect(() => {
|
||||
setContentHeight(selectedIdentifier && selectedToken ? 700 : 500);
|
||||
}, [selectedIdentifier, selectedToken]);
|
||||
@@ -423,7 +420,6 @@ export default function ViewAllTokensHalfModal({
|
||||
<TokenDetailView
|
||||
token={selectedToken}
|
||||
tokenIdentifier={selectedIdentifier}
|
||||
onBack={handleBack}
|
||||
theme={theme}
|
||||
darkModeType={darkModeType}
|
||||
/>
|
||||
@@ -478,8 +474,8 @@ const styles = StyleSheet.create({
|
||||
|
||||
sectionTitle: {
|
||||
fontSize: SIZES.large,
|
||||
fontWeight: 500,
|
||||
marginBottom: 14,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tokenList: {
|
||||
flexGrow: 1,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import { CENTER, SIZES, SATSPERBITCOIN } from '../../../../constants';
|
||||
import { CENTER, SIZES } from '../../../../constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { HIDDEN_OPACITY } from '../../../../constants/theme';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
@@ -44,11 +49,14 @@ import { simulateSwap } from '../../../../functions/spark/flashnet';
|
||||
|
||||
const confirmTxAnimation = require('../../../../assets/confirmTxAnimation.json');
|
||||
|
||||
const STEP_ORDER = ['select', 'custom', 'confirm', 'loading', 'success'];
|
||||
|
||||
export default function ContributeToPoolHalfModal({
|
||||
pool,
|
||||
poolId,
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
setBackNav,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { bitcoinBalance, dollarBalanceSat, dollarBalanceToken } =
|
||||
@@ -73,6 +81,96 @@ export default function ContributeToPoolHalfModal({
|
||||
);
|
||||
// extract current page for easier handling
|
||||
const currentPage = step[step.length - 1];
|
||||
const previousPageRef = useRef(currentPage);
|
||||
|
||||
const selectOpacity = useSharedValue(1);
|
||||
const selectTranslateX = useSharedValue(0);
|
||||
const customOpacity = useSharedValue(0);
|
||||
const customTranslateX = useSharedValue(30);
|
||||
const confirmOpacity = useSharedValue(0);
|
||||
const confirmTranslateX = useSharedValue(30);
|
||||
const loadingOpacity = useSharedValue(0);
|
||||
const loadingTranslateX = useSharedValue(30);
|
||||
const successOpacity = useSharedValue(0);
|
||||
const successTranslateX = useSharedValue(30);
|
||||
|
||||
const getStepAnimation = useCallback(
|
||||
page => {
|
||||
switch (page) {
|
||||
case 'custom':
|
||||
return { opacity: customOpacity, translateX: customTranslateX };
|
||||
case 'confirm':
|
||||
return { opacity: confirmOpacity, translateX: confirmTranslateX };
|
||||
case 'loading':
|
||||
return { opacity: loadingOpacity, translateX: loadingTranslateX };
|
||||
case 'success':
|
||||
return { opacity: successOpacity, translateX: successTranslateX };
|
||||
case 'select':
|
||||
default:
|
||||
return { opacity: selectOpacity, translateX: selectTranslateX };
|
||||
}
|
||||
},
|
||||
[
|
||||
confirmOpacity,
|
||||
confirmTranslateX,
|
||||
customOpacity,
|
||||
customTranslateX,
|
||||
loadingOpacity,
|
||||
loadingTranslateX,
|
||||
selectOpacity,
|
||||
selectTranslateX,
|
||||
successOpacity,
|
||||
successTranslateX,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const previousPage = previousPageRef.current;
|
||||
if (previousPage === currentPage) return;
|
||||
|
||||
const previousIndex = STEP_ORDER.indexOf(previousPage);
|
||||
const currentIndex = STEP_ORDER.indexOf(currentPage);
|
||||
const isForward = currentIndex > previousIndex;
|
||||
const previousAnimation = getStepAnimation(previousPage);
|
||||
const currentAnimation = getStepAnimation(currentPage);
|
||||
|
||||
previousAnimation.opacity.value = withTiming(0, { duration: 250 });
|
||||
previousAnimation.translateX.value = withTiming(isForward ? -30 : 30, {
|
||||
duration: 250,
|
||||
});
|
||||
|
||||
currentAnimation.opacity.value = 0;
|
||||
currentAnimation.translateX.value = isForward ? 30 : -30;
|
||||
currentAnimation.opacity.value = withTiming(1, { duration: 250 });
|
||||
currentAnimation.translateX.value = withTiming(0, { duration: 250 });
|
||||
|
||||
previousPageRef.current = currentPage;
|
||||
}, [currentPage, getStepAnimation]);
|
||||
|
||||
const selectAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: selectOpacity.value,
|
||||
transform: [{ translateX: selectTranslateX.value }],
|
||||
}));
|
||||
|
||||
const customAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: customOpacity.value,
|
||||
transform: [{ translateX: customTranslateX.value }],
|
||||
}));
|
||||
|
||||
const confirmAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: confirmOpacity.value,
|
||||
transform: [{ translateX: confirmTranslateX.value }],
|
||||
}));
|
||||
|
||||
const loadingAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: loadingOpacity.value,
|
||||
transform: [{ translateX: loadingTranslateX.value }],
|
||||
}));
|
||||
|
||||
const successAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: successOpacity.value,
|
||||
transform: [{ translateX: successTranslateX.value }],
|
||||
}));
|
||||
|
||||
const normalizedInputDenomination = inputDenomination
|
||||
? inputDenomination
|
||||
@@ -123,11 +221,24 @@ export default function ContributeToPoolHalfModal({
|
||||
|
||||
useHandleBackPressNew(handleBackPress);
|
||||
|
||||
// Register the chrome's back arrow whenever a previous step exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
step.length > 1 &&
|
||||
currentPage !== 'loading' &&
|
||||
currentPage !== 'success'
|
||||
) {
|
||||
setBackNav?.({ onPress: handleBackPress, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [step, currentPage, handleBackPress, setBackNav]);
|
||||
|
||||
const isFiatMode = masterInfoObject.userBalanceDenomination === 'fiat';
|
||||
|
||||
// Convert current keyboard input to sats (used only in custom step)
|
||||
const localSatAmount = convertDisplayToSats(amountValue);
|
||||
const localFiatAmount = convertSatsToDisplay(localSatAmount);
|
||||
const effectiveSats =
|
||||
currentPage === 'custom' ? localSatAmount : selectedAmountSats;
|
||||
|
||||
@@ -143,18 +254,8 @@ export default function ContributeToPoolHalfModal({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPage === 'select') {
|
||||
setContentHeight(500);
|
||||
} else if (
|
||||
currentPage === 'confirm' ||
|
||||
currentPage === 'loading' ||
|
||||
currentPage === 'success'
|
||||
) {
|
||||
setContentHeight(500);
|
||||
} else {
|
||||
setContentHeight(550);
|
||||
}
|
||||
}, [currentPage]);
|
||||
setContentHeight(550);
|
||||
}, []);
|
||||
|
||||
const confirmAnimation = useMemo(() => {
|
||||
return updateConfirmAnimation(
|
||||
@@ -342,79 +443,61 @@ export default function ContributeToPoolHalfModal({
|
||||
dollarBalanceToken,
|
||||
]);
|
||||
|
||||
if (currentPage === 'loading') {
|
||||
return (
|
||||
<View style={styles.stepContainer}>
|
||||
<FullLoadingScreen text={t('wallet.pools.sendingContribution')} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const displayAmount = isFiatMode
|
||||
? convertSatsToDisplay(effectiveSats)
|
||||
: effectiveSats;
|
||||
const stepBackgroundStyle = {
|
||||
backgroundColor: theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
};
|
||||
const layerChrome = page => ({
|
||||
zIndex: currentPage === page ? 2 : 1,
|
||||
});
|
||||
const layerPointerEvents = page => (currentPage === page ? 'auto' : 'none');
|
||||
|
||||
if (currentPage === 'success') {
|
||||
return (
|
||||
<View style={[styles.stepContainer, styles.successContainer]}>
|
||||
<LottieView
|
||||
source={confirmAnimation}
|
||||
loop={false}
|
||||
style={styles.lottieView}
|
||||
autoPlay={true}
|
||||
/>
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.stepContainer,
|
||||
stepBackgroundStyle,
|
||||
selectAnimatedStyle,
|
||||
layerChrome('select'),
|
||||
]}
|
||||
pointerEvents={layerPointerEvents('select')}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.successText}
|
||||
content={t('wallet.pools.contributionSent')}
|
||||
styles={styles.selectTitle}
|
||||
content={t('wallet.pools.chooseContributionAmount')}
|
||||
/>
|
||||
|
||||
<PresetAmountGrid
|
||||
onSelectPreset={setSelectedAmountSats}
|
||||
selectedAmountSats={selectedAmountSats}
|
||||
fiatStats={fiatStats}
|
||||
onCustomPress={handleCustomPress}
|
||||
/>
|
||||
|
||||
<CustomButton
|
||||
actionFunction={handleBackPressFunction}
|
||||
textContent={t('constants.back')}
|
||||
buttonStyles={[
|
||||
styles.continueButton,
|
||||
{ opacity: !selectedAmountSats ? HIDDEN_OPACITY : 1 },
|
||||
]}
|
||||
textContent={t('constants.continue')}
|
||||
actionFunction={handleContinue}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
</Animated.View>
|
||||
|
||||
if (currentPage === 'confirm') {
|
||||
const displayAmount = isFiatMode ? localFiatAmount : effectiveSats;
|
||||
|
||||
return (
|
||||
<View style={styles.stepContainer}>
|
||||
<FormattedBalanceInput
|
||||
maxWidth={0.9}
|
||||
amountValue={displayAmount}
|
||||
inputDenomination={isFiatMode ? 'fiat' : 'sats'}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.confirmPoolTitle}
|
||||
content={`${t('wallet.pools.contributeTo')}${pool.poolTitle}`}
|
||||
/>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.infoItem}
|
||||
content={t('wallet.pools.contributionWarning')}
|
||||
/>
|
||||
|
||||
<SwipeButtonNew
|
||||
onSwipeSuccess={handleConfirmPayment}
|
||||
width={0.95}
|
||||
containerStyles={{ marginBottom: 12 }}
|
||||
thumbIconStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
railStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentPage === 'custom') {
|
||||
return (
|
||||
<View style={styles.stepContainer}>
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.stepContainer,
|
||||
stepBackgroundStyle,
|
||||
customAnimatedStyle,
|
||||
layerChrome('custom'),
|
||||
]}
|
||||
pointerEvents={layerPointerEvents('custom')}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={{ marginTop: 10 }}
|
||||
activeOpacity={1}
|
||||
@@ -450,38 +533,101 @@ export default function ContributeToPoolHalfModal({
|
||||
Number(amountValue) ? t('constants.continue') : t('constants.back')
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
</Animated.View>
|
||||
|
||||
// Step: 'select'
|
||||
return (
|
||||
<View style={styles.stepContainer}>
|
||||
<ThemeText
|
||||
styles={styles.selectTitle}
|
||||
content={t('wallet.pools.chooseContributionAmount')}
|
||||
/>
|
||||
|
||||
<PresetAmountGrid
|
||||
onSelectPreset={setSelectedAmountSats}
|
||||
selectedAmountSats={selectedAmountSats}
|
||||
fiatStats={fiatStats}
|
||||
onCustomPress={handleCustomPress}
|
||||
/>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={[
|
||||
styles.continueButton,
|
||||
{ opacity: !selectedAmountSats ? HIDDEN_OPACITY : 1 },
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.stepContainer,
|
||||
stepBackgroundStyle,
|
||||
confirmAnimatedStyle,
|
||||
layerChrome('confirm'),
|
||||
]}
|
||||
textContent={t('constants.continue')}
|
||||
actionFunction={handleContinue}
|
||||
/>
|
||||
pointerEvents={layerPointerEvents('confirm')}
|
||||
>
|
||||
<FormattedBalanceInput
|
||||
maxWidth={0.9}
|
||||
amountValue={displayAmount}
|
||||
inputDenomination={isFiatMode ? 'fiat' : 'sats'}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.confirmPoolTitle}
|
||||
content={`${t('wallet.pools.contributeTo')}${pool.poolTitle}`}
|
||||
/>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.infoItem}
|
||||
content={t('wallet.pools.contributionWarning')}
|
||||
/>
|
||||
|
||||
<SwipeButtonNew
|
||||
onSwipeSuccess={handleConfirmPayment}
|
||||
width={0.9}
|
||||
containerStyles={{ marginBottom: 12 }}
|
||||
thumbIconStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
railStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.stepContainer,
|
||||
stepBackgroundStyle,
|
||||
loadingAnimatedStyle,
|
||||
layerChrome('loading'),
|
||||
]}
|
||||
pointerEvents={layerPointerEvents('loading')}
|
||||
>
|
||||
<FullLoadingScreen text={t('wallet.pools.sendingContribution')} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
styles.stepContainer,
|
||||
styles.successContainer,
|
||||
stepBackgroundStyle,
|
||||
successAnimatedStyle,
|
||||
layerChrome('success'),
|
||||
]}
|
||||
pointerEvents={layerPointerEvents('success')}
|
||||
>
|
||||
{currentPage === 'success' && (
|
||||
<LottieView
|
||||
source={confirmAnimation}
|
||||
loop={false}
|
||||
style={styles.lottieView}
|
||||
autoPlay={true}
|
||||
/>
|
||||
)}
|
||||
<ThemeText
|
||||
styles={styles.successText}
|
||||
content={t('wallet.pools.contributionSent')}
|
||||
/>
|
||||
<CustomButton
|
||||
actionFunction={handleBackPressFunction}
|
||||
textContent={t('constants.back')}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
stepContainer: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
|
||||
@@ -70,6 +70,7 @@ export default function AddMoneyToSavingsHalfModal({
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
selectedGoalUUID,
|
||||
setBackNav,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
@@ -211,6 +212,20 @@ export default function AddMoneyToSavingsHalfModal({
|
||||
|
||||
useHandleBackPressNew(handleBackPress);
|
||||
|
||||
// Register the chrome's back arrow whenever a previous step exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
step.length > 1 &&
|
||||
currentPage !== 'loading' &&
|
||||
currentPage !== 'success'
|
||||
) {
|
||||
setBackNav?.({ onPress: handleBackPress, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [step, currentPage, handleBackPress, setBackNav]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
setStep(prev => [...prev, 'loading']);
|
||||
setLoadingStep('processing');
|
||||
|
||||
@@ -103,6 +103,7 @@ export default function WithdrawFromSavingsHalfModal({
|
||||
setContentHeight,
|
||||
handleBackPressFunction,
|
||||
selectedGoalUUID,
|
||||
setBackNav,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
@@ -379,6 +380,20 @@ export default function WithdrawFromSavingsHalfModal({
|
||||
|
||||
useHandleBackPressNew(handleBackPress);
|
||||
|
||||
// Register the chrome's back arrow whenever a previous step exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
step.length > 1 &&
|
||||
currentPage !== 'loading' &&
|
||||
currentPage !== 'success'
|
||||
) {
|
||||
setBackNav?.({ onPress: handleBackPress, title: '' });
|
||||
} else {
|
||||
setBackNav?.(null);
|
||||
}
|
||||
return () => setBackNav?.(null);
|
||||
}, [step, currentPage, handleBackPress, setBackNav]);
|
||||
|
||||
const parsedAmount = Number(amountValue || 0);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -555,6 +555,7 @@ export default function CustomHalfModal(props) {
|
||||
poolId={props?.route?.params?.poolId}
|
||||
setContentHeight={setContentHeight}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
case 'addGiftQuantity':
|
||||
@@ -566,6 +567,7 @@ export default function CustomHalfModal(props) {
|
||||
giftDenomination={props?.route?.params?.giftDenomination}
|
||||
setContentHeight={setContentHeight}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
case 'addMoneyToSavings':
|
||||
@@ -575,6 +577,7 @@ export default function CustomHalfModal(props) {
|
||||
selectedGoalUUID={props?.route?.params?.selectedGoalUUID}
|
||||
setContentHeight={setContentHeight}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
</SavingsProvider>
|
||||
);
|
||||
@@ -586,6 +589,7 @@ export default function CustomHalfModal(props) {
|
||||
selectedGoalUUID={props?.route?.params?.selectedGoalUUID}
|
||||
setContentHeight={setContentHeight}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
</SavingsProvider>
|
||||
);
|
||||
@@ -602,6 +606,7 @@ export default function CustomHalfModal(props) {
|
||||
<ViewAllTokensHalfModal
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setContentHeight={setContentHeight}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -618,6 +623,7 @@ export default function CustomHalfModal(props) {
|
||||
<SwapFlowHalfModal
|
||||
setContentHeight={setContentHeight}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
case 'txFilter':
|
||||
@@ -638,6 +644,7 @@ export default function CustomHalfModal(props) {
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setContentHeight={setContentHeight}
|
||||
setIsKeyboardActive={setIsKeyboardActive}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
case 'payLinkCurrencySelect':
|
||||
@@ -670,6 +677,7 @@ export default function CustomHalfModal(props) {
|
||||
<CreateAccumulationAddressModal
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setContentHeight={setContentHeight}
|
||||
setBackNav={setBackNav}
|
||||
/>
|
||||
);
|
||||
case 'RemoveBudgetHalfModal':
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { scheduleOnRN } from 'react-native-worklets';
|
||||
|
||||
/**
|
||||
* Standard half-modal step transition (fade + directional translateX), modeled on
|
||||
* CreateAccumulationAddressModal. Lets step-based flows that render via early-return
|
||||
* branches animate between steps without duplicating the animation logic.
|
||||
*
|
||||
* @param {string} page The active page key.
|
||||
* @param {string[]} order Ordered list of page keys — defines slide direction.
|
||||
* @returns {{ renderedPage: string, pageAnimatedStyle: object }} Render JSX off
|
||||
* renderedPage and wrap it in an Animated.View with pageAnimatedStyle. Keep all
|
||||
* logic/height/back behavior on the live `page`.
|
||||
*/
|
||||
export default function useHalfModalStepTransition(page, order) {
|
||||
const opacity = useSharedValue(1);
|
||||
const translateX = useSharedValue(0);
|
||||
const [renderedPage, setRenderedPage] = useState(page);
|
||||
const renderedRef = useRef(page);
|
||||
|
||||
const animateIn = useCallback(
|
||||
(next, forward) => {
|
||||
renderedRef.current = next;
|
||||
setRenderedPage(next);
|
||||
translateX.value = forward ? 30 : -30;
|
||||
opacity.value = withTiming(1, { duration: 125 });
|
||||
translateX.value = withTiming(0, { duration: 125 });
|
||||
},
|
||||
[opacity, translateX],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (page === renderedRef.current) return;
|
||||
const forward = order.indexOf(page) > order.indexOf(renderedRef.current);
|
||||
opacity.value = withTiming(0, { duration: 125 });
|
||||
translateX.value = withTiming(
|
||||
forward ? -30 : 30,
|
||||
{ duration: 125 },
|
||||
fin => {
|
||||
if (fin) scheduleOnRN(animateIn, page, forward);
|
||||
},
|
||||
);
|
||||
}, [page]);
|
||||
|
||||
const pageAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: opacity.value,
|
||||
transform: [{ translateX: translateX.value }],
|
||||
}));
|
||||
|
||||
return { renderedPage, pageAnimatedStyle };
|
||||
}
|
||||
Reference in New Issue
Block a user