adding confirm page for contact payments (#966)
* adding confirm page for contact payments * improve confirm style
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LottieView from 'lottie-react-native';
|
||||
|
||||
import { CENTER, SIZES } from '../../../../constants';
|
||||
import {
|
||||
HIDDEN_OPACITY,
|
||||
INSET_WINDOW_WIDTH,
|
||||
} from '../../../../constants/theme';
|
||||
import { useGlobalInsets } from '../../../../../context-store/insetsProvider';
|
||||
import GetThemeColors from '../../../../hooks/themeColors';
|
||||
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
|
||||
@@ -14,6 +20,12 @@ import ContactAmountEntry from './internalComponents/contactAmountEntry';
|
||||
import useContactPayment from './hooks/useContactPayment';
|
||||
import { useAppStatus } from '../../../../../context-store/appStatus';
|
||||
import CurrencySwitchButton from '../../../../functions/CustomElements/currencySwitchButton';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import CustomButton from '../../../../functions/CustomElements/button';
|
||||
import FormattedBalanceInput from '../../../../functions/CustomElements/formattedBalanceInput';
|
||||
import { updateConfirmAnimation } from '../../../../functions/lottieViewColorTransformer';
|
||||
|
||||
const confirmTxAnimation = require('../../../../assets/confirmTxAnimation.json');
|
||||
|
||||
export default function ContactPaymentOverlay({
|
||||
visible,
|
||||
@@ -43,22 +55,71 @@ export default function ContactPaymentOverlay({
|
||||
t,
|
||||
});
|
||||
|
||||
// Snapshot of the just-submitted request, captured at submit time so the
|
||||
// confirmation view keeps showing the right amount even if the payment hook's
|
||||
// state changes afterwards. null = still on the amount-entry step.
|
||||
const [successData, setSuccessData] = useState(null);
|
||||
|
||||
const overlayOpacity = useSharedValue(0);
|
||||
const entryOpacity = useSharedValue(1);
|
||||
const entryTranslateX = useSharedValue(0);
|
||||
const successOpacity = useSharedValue(0);
|
||||
const successTranslateX = useSharedValue(30);
|
||||
|
||||
useEffect(() => {
|
||||
overlayOpacity.value = withTiming(visible ? 1 : 0, { duration: 250 });
|
||||
}, [visible]);
|
||||
|
||||
// Cross-fade between the amount-entry step and the confirmation step.
|
||||
useEffect(() => {
|
||||
const showingSuccess = successData !== null;
|
||||
entryOpacity.value = withTiming(showingSuccess ? 0 : 1, { duration: 250 });
|
||||
entryTranslateX.value = withTiming(showingSuccess ? -30 : 0, {
|
||||
duration: 250,
|
||||
});
|
||||
successOpacity.value = withTiming(showingSuccess ? 1 : 0, {
|
||||
duration: 250,
|
||||
});
|
||||
successTranslateX.value = withTiming(showingSuccess ? 0 : 30, {
|
||||
duration: 250,
|
||||
});
|
||||
}, [successData]);
|
||||
|
||||
const overlayStyle = useAnimatedStyle(() => ({
|
||||
opacity: overlayOpacity.value,
|
||||
pointerEvents: overlayOpacity.value > 0.5 ? 'auto' : 'none',
|
||||
}));
|
||||
|
||||
const entryStyle = useAnimatedStyle(() => ({
|
||||
opacity: entryOpacity.value,
|
||||
transform: [{ translateX: entryTranslateX.value }],
|
||||
}));
|
||||
|
||||
const successStyle = useAnimatedStyle(() => ({
|
||||
opacity: successOpacity.value,
|
||||
transform: [{ translateX: successTranslateX.value }],
|
||||
}));
|
||||
|
||||
const confirmAnimation = useMemo(
|
||||
() =>
|
||||
updateConfirmAnimation(
|
||||
confirmTxAnimation,
|
||||
theme ? (darkModeType ? 'lightsOut' : 'dark') : 'light',
|
||||
),
|
||||
[theme, darkModeType],
|
||||
);
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
if (!visible) return false;
|
||||
// Once the request has been sent there's nothing to step back to — close
|
||||
// the whole modal, matching the Done button.
|
||||
if (successData !== null) {
|
||||
handleBackPressFunction();
|
||||
return true;
|
||||
}
|
||||
onClose();
|
||||
return true;
|
||||
}, [onClose, visible]);
|
||||
}, [onClose, visible, successData, handleBackPressFunction]);
|
||||
|
||||
const openCurrencyPicker = useCallback(
|
||||
() =>
|
||||
@@ -77,13 +138,15 @@ export default function ContactPaymentOverlay({
|
||||
setBackNav?.({
|
||||
onPress: handleBackPress,
|
||||
title: '',
|
||||
rightElement: (
|
||||
<CurrencySwitchButton
|
||||
displayCurrency={payment.displayCurrency}
|
||||
onPress={openCurrencyPicker}
|
||||
disabled={payment.isResolvingDisplayCurrency}
|
||||
/>
|
||||
),
|
||||
// The currency switcher is meaningless on the confirmation step.
|
||||
rightElement:
|
||||
successData !== null ? null : (
|
||||
<CurrencySwitchButton
|
||||
displayCurrency={payment.displayCurrency}
|
||||
onPress={openCurrencyPicker}
|
||||
disabled={payment.isResolvingDisplayCurrency}
|
||||
/>
|
||||
),
|
||||
});
|
||||
return () => {
|
||||
setBackNav?.(null);
|
||||
@@ -93,6 +156,7 @@ export default function ContactPaymentOverlay({
|
||||
handleBackPress,
|
||||
setBackNav,
|
||||
visible,
|
||||
successData,
|
||||
payment.displayCurrency,
|
||||
payment.isResolvingDisplayCurrency,
|
||||
openCurrencyPicker,
|
||||
@@ -146,15 +210,35 @@ export default function ContactPaymentOverlay({
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleBackPressFunction();
|
||||
}, [handleBackPressFunction, navigate, payment, paymentType]);
|
||||
// Replace the entry screen with an in-place confirmation instead of closing
|
||||
// straight to the homepage, so the user gets clear feedback the request
|
||||
// actually went through.
|
||||
setSuccessData({
|
||||
amountValue: payment.amountValue,
|
||||
denomination: payment.primaryDisplay.denomination,
|
||||
forceCurrency: payment.primaryDisplay.forceCurrency,
|
||||
forceFiatStats: payment.primaryDisplay.forceFiatStats,
|
||||
contactName: selectedContact?.name || selectedContact?.uniqueName || '',
|
||||
});
|
||||
}, [
|
||||
handleBackPressFunction,
|
||||
navigate,
|
||||
payment,
|
||||
paymentType,
|
||||
selectedContact,
|
||||
]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, overlayStyle]}>
|
||||
<Animated.View
|
||||
style={[styles.stepContainer, { paddingBottom: bottomPadding }]}
|
||||
style={[
|
||||
styles.stepContainer,
|
||||
entryStyle,
|
||||
{ paddingBottom: bottomPadding },
|
||||
]}
|
||||
pointerEvents={successData !== null ? 'none' : 'auto'}
|
||||
>
|
||||
<ContactAmountEntry
|
||||
selectedContact={selectedContact}
|
||||
@@ -181,6 +265,39 @@ export default function ContactPaymentOverlay({
|
||||
isResolvingDisplayCurrency={payment.isResolvingDisplayCurrency}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.stepContainer,
|
||||
styles.successContainer,
|
||||
successStyle,
|
||||
{ paddingBottom: bottomPadding },
|
||||
]}
|
||||
pointerEvents={successData !== null ? 'auto' : 'none'}
|
||||
>
|
||||
<View style={styles.successContent}>
|
||||
{successData !== null && (
|
||||
<LottieView
|
||||
source={confirmAnimation}
|
||||
loop={false}
|
||||
autoPlay
|
||||
style={styles.lottie}
|
||||
/>
|
||||
)}
|
||||
<ThemeText
|
||||
CustomNumberOfLines={2}
|
||||
styles={styles.successSubtitle}
|
||||
content={t('wallet.halfModal.requestSentSubtitle', {
|
||||
name: successData?.contactName,
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
<CustomButton
|
||||
buttonStyles={styles.doneButton}
|
||||
actionFunction={handleBackPressFunction}
|
||||
textContent={t('constants.done')}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
@@ -201,4 +318,30 @@ const styles = StyleSheet.create({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
successContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
successContent: {
|
||||
flex: 1,
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
alignItems: 'center',
|
||||
// justifyContent: 'center',
|
||||
},
|
||||
lottie: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
successSubtitle: {
|
||||
fontSize: SIZES.medium,
|
||||
// opacity: HIDDEN_OPACITY,
|
||||
textAlign: 'center',
|
||||
// marginTop: 20,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
doneButton: {
|
||||
// width: INSET_WINDOW_WIDTH,
|
||||
...CENTER,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Schnell bezahlen",
|
||||
"createInvoice": "Rechnung erstellen",
|
||||
"invoiceDescription": "Erstellen Sie eine Rechnung und erhalten Sie sofort eine Zahlung.",
|
||||
"requestSentTitle": "Anfrage gesendet",
|
||||
"requestSentSubtitle": "{{name}} wurde über Ihre Anfrage benachrichtigt",
|
||||
"poolsDescription": "Sammeln Sie Beiträge von einer Gruppe für ein gemeinsames Ziel.",
|
||||
"noContactHead": "{{username}} nicht gefunden",
|
||||
"noContactDesc": "Fügen Sie {{username}} zu Ihren Kontakten hinzu, damit der Kontakt hier künftig angezeigt wird, oder fahren Sie fort, um jetzt eine Zahlung zu senden.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Quick Pay",
|
||||
"createInvoice": "Create Invoice",
|
||||
"invoiceDescription": "Create an invoice and get paid instantly.",
|
||||
"requestSentTitle": "Request Sent",
|
||||
"requestSentSubtitle": "{{name}} has been notified of your request",
|
||||
"poolsDescription": "Collect contributions from a group toward a shared goal.",
|
||||
"noContactHead": "{{username}} not found",
|
||||
"noContactDesc": "Add {{username}} to your contacts to find them here in the future, or continue to send a payment now.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Pago rápido",
|
||||
"createInvoice": "Crear factura",
|
||||
"invoiceDescription": "Crea una factura y recibe el pago al instante.",
|
||||
"requestSentTitle": "Solicitud enviada",
|
||||
"requestSentSubtitle": "{{name}} ha sido notificado de tu solicitud",
|
||||
"poolsDescription": "Reúne contribuciones de un grupo para un objetivo compartido.",
|
||||
"noContactHead": "{{username}} no encontrado",
|
||||
"noContactDesc": "Agrega a {{username}} a tus contactos para encontrarlo aquí en el futuro, o continúa para enviar un pago ahora.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Paiement rapide",
|
||||
"createInvoice": "Créer une facture",
|
||||
"invoiceDescription": "Créez une facture et recevez un paiement instantanément.",
|
||||
"requestSentTitle": "Demande envoyée",
|
||||
"requestSentSubtitle": "{{name}} a été informé de votre demande",
|
||||
"poolsDescription": "Collectez des contributions d’un groupe pour un objectif commun.",
|
||||
"noContactHead": "{{username}} introuvable",
|
||||
"noContactDesc": "Ajoutez {{username}} à vos contacts pour le retrouver ici à l’avenir, ou continuez pour envoyer un paiement maintenant.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Pagamento rapido",
|
||||
"createInvoice": "Crea fattura",
|
||||
"invoiceDescription": "Crea una fattura e ricevi il pagamento immediatamente.",
|
||||
"requestSentTitle": "Richiesta inviata",
|
||||
"requestSentSubtitle": "{{name}} è stato informato della tua richiesta",
|
||||
"poolsDescription": "Raccogli contributi da un gruppo per un obiettivo condiviso.",
|
||||
"noContactHead": "{{username}} non trovato",
|
||||
"noContactDesc": "Aggiungi {{username}} ai tuoi contatti per trovarlo qui in futuro, oppure continua per inviare un pagamento adesso.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Endereço Blitz",
|
||||
"createInvoice": "Criar Fatura",
|
||||
"invoiceDescription": "Escolha a moeda e quantia desejada",
|
||||
"requestSentTitle": "Solicitação enviada",
|
||||
"requestSentSubtitle": "{{name}} foi notificado sobre sua solicitação",
|
||||
"poolsDescription": "Colete pagamentos para um objetivo",
|
||||
"noContactHead": "{{username}} não encontrado",
|
||||
"noContactDesc": "Adicione {{username}} aos seus contatos para encontrá-lo aqui no futuro, ou continue para enviar um pagamento agora.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Быстрая оплата",
|
||||
"createInvoice": "Создать счёт",
|
||||
"invoiceDescription": "Создайте счёт и получите оплату мгновенно.",
|
||||
"requestSentTitle": "Запрос отправлен",
|
||||
"requestSentSubtitle": "{{name}} был уведомлён о вашем запросе",
|
||||
"poolsDescription": "Собирайте взносы от группы для общей цели.",
|
||||
"noContactHead": "{{username}} не найден",
|
||||
"noContactDesc": "Добавьте {{username}} в свои контакты, чтобы находить здесь в будущем, или продолжите, чтобы отправить платёж сейчас.",
|
||||
|
||||
@@ -1006,6 +1006,8 @@
|
||||
"payMe": "Snabb betalning",
|
||||
"createInvoice": "Skapa faktura",
|
||||
"invoiceDescription": "Skapa en faktura och få betalt direkt.",
|
||||
"requestSentTitle": "Begäran skickad",
|
||||
"requestSentSubtitle": "{{name}} har meddelats om din begäran",
|
||||
"poolsDescription": "Samla in bidrag från en grupp till ett gemensamt mål.",
|
||||
"noContactHead": "{{username}} hittades inte",
|
||||
"noContactDesc": "Lägg till {{username}} i dina kontakter för att hitta dem här i framtiden, eller fortsätt för att skicka en betalning nu.",
|
||||
|
||||
Reference in New Issue
Block a user