diff --git a/components/BottomModal.tsx b/components/BottomModal.tsx index 5c17d3760..0d7bae17a 100644 --- a/components/BottomModal.tsx +++ b/components/BottomModal.tsx @@ -5,10 +5,9 @@ import { Keyboard, StyleSheet, View, TouchableOpacity, Platform, Image } from 'r interface BottomModalProps extends TrueSheetProps { children?: React.ReactNode; onClose?: () => void; - name?: string; isGrabberVisible?: boolean; sizes?: SheetSize[] | undefined; - footer?: ReactElement | ComponentType; + footer?: ReactElement | ComponentType | null; footerDefaultMargins?: boolean | number; onPresent?: () => void; onSizeChange?: (size: SizeInfo) => void; @@ -42,7 +41,6 @@ const styles = StyleSheet.create({ const BottomModal = forwardRef( ( { - name, onClose, onPresent, onSizeChange, diff --git a/components/PromptPasswordConfirmationModal.tsx b/components/PromptPasswordConfirmationModal.tsx new file mode 100644 index 000000000..b091cfa46 --- /dev/null +++ b/components/PromptPasswordConfirmationModal.tsx @@ -0,0 +1,285 @@ +import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react'; +import { View, Text, TextInput, StyleSheet, Animated, Easing, ViewStyle } from 'react-native'; +import BottomModal, { BottomModalHandle } from './BottomModal'; +import { useTheme } from './themes'; +import loc from '../loc'; +import { SecondButton } from './SecondButton'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; + +export const MODAL_TYPES = { + ENTER_PASSWORD: 'ENTER_PASSWORD', + CREATE_PASSWORD: 'CREATE_PASSWORD', +} as const; + +export type ModalType = (typeof MODAL_TYPES)[keyof typeof MODAL_TYPES]; + +interface PromptPasswordConfirmationModalProps { + modalType: ModalType; + onConfirmationSuccess: (password: string) => Promise; + onConfirmationFailure: () => void; + onDismiss?: () => void; +} + +export interface PromptPasswordConfirmationModalHandle { + present: () => Promise; + dismiss: () => Promise; +} + +const PromptPasswordConfirmationModal = forwardRef( + ({ modalType, onConfirmationSuccess, onConfirmationFailure, onDismiss }, ref) => { + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const modalRef = useRef(null); + const fadeOutAnimation = useRef(new Animated.Value(1)).current; + const fadeInAnimation = useRef(new Animated.Value(0)).current; + const scaleAnimation = useRef(new Animated.Value(1)).current; + const { colors } = useTheme(); + + const stylesHook = StyleSheet.create({ + modalContent: { + backgroundColor: colors.elevated, + width: '100%', + }, + input: { + backgroundColor: colors.inputBackgroundColor, + borderColor: colors.formBorder, + color: colors.foregroundColor, + width: '100%', + }, + feeModalCustomText: { + color: colors.buttonAlternativeTextColor, + }, + feeModalLabel: { + color: colors.successColor, + }, + }); + + useImperativeHandle(ref, () => ({ + present: async () => { + resetState(); + modalRef.current?.present(); + }, + dismiss: async () => modalRef.current?.dismiss(), + })); + + const resetState = () => { + setPassword(''); + setConfirmPassword(''); + setIsSuccess(false); + setIsLoading(false); + fadeOutAnimation.setValue(1); + fadeInAnimation.setValue(0); + scaleAnimation.setValue(1); + }; + + const handleSuccessAnimation = () => { + Animated.parallel([ + Animated.timing(fadeOutAnimation, { + toValue: 0, + duration: 300, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(scaleAnimation, { + toValue: 1.1, + duration: 300, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + ]).start(() => { + setIsSuccess(true); + Animated.timing(fadeInAnimation, { + toValue: 1, + duration: 500, + easing: Easing.out(Easing.ease), + useNativeDriver: true, + }).start(() => { + setTimeout(() => { + modalRef.current?.dismiss(); + }, 1000); + }); + }); + }; + + const handleSubmit = async () => { + setIsLoading(true); + let success = false; + if (modalType === MODAL_TYPES.CREATE_PASSWORD) { + if (password === confirmPassword && password) { + success = await onConfirmationSuccess(password); + if (success) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + handleSuccessAnimation(); + } else { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + setIsLoading(false); + onConfirmationFailure(); + } + } else { + setIsLoading(false); + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + onConfirmationFailure(); + } + } else if (modalType === MODAL_TYPES.ENTER_PASSWORD) { + success = await onConfirmationSuccess(password); + setIsLoading(false); + if (success) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + handleSuccessAnimation(); + } else { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + onConfirmationFailure(); + } + } + }; + + const handleCancel = async () => { + resetState(); + onConfirmationFailure(); + await modalRef.current?.dismiss(); + }; + + useEffect(() => { + resetState(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [modalType]); + + const animatedViewStyle: Animated.WithAnimatedObject = { + opacity: fadeOutAnimation, + transform: [{ scale: scaleAnimation }], + width: '100%', + }; + + return ( + + + + + + + + )) || + null + } + > + {!isSuccess && ( + + + {modalType === MODAL_TYPES.CREATE_PASSWORD ? loc.settings.password_explain : loc._.enter_password} + + + + {modalType === MODAL_TYPES.CREATE_PASSWORD && ( + + )} + + + )} + {isSuccess && ( + + + + ✔️ + + + + )} + + ); + }, +); + +export default PromptPasswordConfirmationModal; + +const styles = StyleSheet.create({ + modalContent: { + padding: 22, + justifyContent: 'center', + alignItems: 'center', + flex: 1, + width: '100%', // Ensure modal content takes full width + }, + feeModalFooter: { + paddingBottom: 36, + paddingHorizontal: 24, + flexDirection: 'row', + justifyContent: 'space-between', + }, + feeModalFooterSpacing: { + paddingHorizontal: 24, + }, + inputContainer: { + marginBottom: 10, + width: '100%', // Ensure full width + }, + input: { + borderWidth: 1, + borderRadius: 4, + padding: 8, + marginVertical: 8, + fontSize: 16, + width: '100%', // Ensure full width + }, + textLabel: { + fontSize: 22, + fontWeight: '600', + marginBottom: 16, + textAlign: 'center', + }, + successContainer: { + justifyContent: 'center', + alignItems: 'center', + height: 100, + }, + circle: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: 'green', + justifyContent: 'center', + alignItems: 'center', + }, + checkmark: { + color: 'white', + fontSize: 30, + }, +}); \ No newline at end of file diff --git a/components/SecondButton.tsx b/components/SecondButton.tsx index e80df4e6b..1650fb83d 100644 --- a/components/SecondButton.tsx +++ b/components/SecondButton.tsx @@ -1,5 +1,5 @@ import React, { forwardRef } from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, Text, TouchableOpacity, View, ActivityIndicator } from 'react-native'; import { Icon } from '@rneui/themed'; import { useTheme } from './themes'; @@ -16,6 +16,7 @@ type SecondButtonProps = { icon?: IconProps; title?: string; onPress?: () => void; + loading?: boolean; }; export const SecondButton = forwardRef((props, ref) => { @@ -27,7 +28,9 @@ export const SecondButton = forwardRef((pro fontColor = colors.buttonDisabledTextColor; } - const buttonView = ( + const buttonView = props.loading ? ( + + ) : ( {props.icon && } {props.title && {props.title}} @@ -36,7 +39,7 @@ export const SecondButton = forwardRef((pro return props.onPress ? ( ", - "retype_password": "패스워드를 다시 넣어세요", + "confirm_password": "패스워드를 다시 넣어세요", "success": "성공했습니다.", "title": "당위적 거부" }, @@ -274,7 +274,7 @@ "privacy_do_not_track_explanation": "성능과 신뢰성 정보는 분석하는 데 제출되지 않을 것 입니다.", "push_notifications": "푸시 알림", "rate": "환율", - "retype_password": "패스워드를 다시 넣어주세요", + "confirm_password": "패스워드를 다시 넣어주세요", "selfTest": "자가 테스트", "save": "저장", "saved": "저장됨", diff --git a/loc/lrc.json b/loc/lrc.json index 04e2e9c62..61c508486 100644 --- a/loc/lrc.json +++ b/loc/lrc.json @@ -59,7 +59,7 @@ "plausibledeniability": { "password_should_not_match": "رزم ها وه کار مؽره. یه رزم هنی نه وه کار بیر.", "passwords_do_not_match": "رزمیات چی یک نؽسن، دۏورته امتهانشو بکو.", - "retype_password": "رزمن دۏورته هؽل بکو", + "confirm_password": "رزمن دۏورته هؽل بکو", "success": "مۏوفق بی" }, "pleasebackup": { @@ -133,7 +133,7 @@ "privacy_read_clipboard": "ونن ویرگه", "privacy_system_settings": "سامونیا دسگا", "rate": "نرخ", - "retype_password": "رزمن دۏورته هؽل بکو", + "confirm_password": "رزمن دۏورته هؽل بکو", "widgets": "اوزارکیا", "tools": "اوزاریا" }, diff --git a/loc/ms.json b/loc/ms.json index d1906347b..afbcce03b 100644 --- a/loc/ms.json +++ b/loc/ms.json @@ -81,7 +81,7 @@ "help2": "Simpanan baharu itu akan berfungsi secara penuh, dan anda boleh menyimpan sedikit jumlah minimum di sana agar ia kelihatan tulen.", "password_should_not_match": "Kata laluan sudah dalam penggunaan. Sila cuba kata laluan lain.", "passwords_do_not_match": "Kata laluan tidak sepadan. Sila cuba lagi.", - "retype_password": "Ulang taip kata laluan.", + "confirm_password": "Ulang taip kata laluan.", "success": "Berjaya", "title": "Penafian Munasabah" }, @@ -258,7 +258,7 @@ "privacy_do_not_track": "Lumpuhkan Penyelidik", "privacy_do_not_track_explanation": "Maklumat pencapaian dan keandalan tidak akan dihantar untuk kaji selidik.", "push_notifications": "Pemberitahuan Pacu", - "retype_password": "Ulang taip kata laluan", + "confirm_password": "Ulang taip kata laluan", "selfTest": "Swaujian", "save": "Simpan", "saved": "Disimpan", diff --git a/loc/nb_no.json b/loc/nb_no.json index 1bbc643eb..a358fa772 100644 --- a/loc/nb_no.json +++ b/loc/nb_no.json @@ -85,7 +85,7 @@ "help2": "Den nye lagringen vil være fullt funksjonell, og du kan lagre noen minimumsbeløp der slik at den ser mer troverdig ut.", "password_should_not_match": "Passordet er i bruk. Prøv et annet passord.", "passwords_do_not_match": "Passordene stemmer ikke overens. Vennligst prøv igjen.", - "retype_password": "Skriv inn passordet på nytt", + "confirm_password": "Skriv inn passordet på nytt", "success": "Vellykket", "title": "Plausibel Fornektelse" }, @@ -276,7 +276,7 @@ "privacy_do_not_track_explanation": "Informasjon om ytelse og pålitelighet vil ikke bli sendt inn for analyse.", "push_notifications": "Varslinger", "rate": "Rate", - "retype_password": "Skriv inn passordet på nytt", + "confirm_password": "Skriv inn passordet på nytt", "selfTest": "Selvtest", "save": "Lagre", "saved": "Lagret", diff --git a/loc/ne.json b/loc/ne.json index a44c45c22..6d9a19b54 100644 --- a/loc/ne.json +++ b/loc/ne.json @@ -91,7 +91,7 @@ "help2": "नयाँ स्टोरेज पूर्ण रूपमा कार्यात्मक हुनेछ, र तपाईंले त्यहाँ केही न्यूनतम रकमहरू स्टोर गर्न सक्नुहुन्छ ताकि यो अझ विश्वासयोग्य देखिन्छ।", "password_should_not_match": "पासवर्ड हाल प्रयोगमा छ। कृपया फरक पासवर्ड प्रयास गर्नुहोस्।", "passwords_do_not_match": "पासवर्डहरू मेल खाएन। फेरि प्रयास गर्नुहोस।", - "retype_password": "पासवर्ड पुन: लेख्नुहोस", + "confirm_password": "पासवर्ड पुन: लेख्नुहोस", "success": "सफल", "title": "व्यावहारिक अस्वीकार्यता" }, @@ -249,7 +249,7 @@ "privacy": "गोपनीयता", "privacy_quickactions": "वालेट सर्टकटहरू", "rate": "दर", - "retype_password": "पासवर्ड पुन: लेख्नुहोस", + "confirm_password": "पासवर्ड पुन: लेख्नुहोस", "save": "सेव", "saved": "बचत गरियो", "total_balance": "पूरा रकम" diff --git a/loc/nl_nl.json b/loc/nl_nl.json index 93b6b8a21..052d40198 100644 --- a/loc/nl_nl.json +++ b/loc/nl_nl.json @@ -98,7 +98,7 @@ "help2": "De nieuwe opslag zal volledig functioneel zijn en u kunt er een minimum aantal munten opslaan zodat het geloofwaardig lijkt.", "password_should_not_match": "Wachtwoord is momenteel in gebruik. Probeer een ander wachtwoord.", "passwords_do_not_match": "Wachtwoorden komen niet overeen, probeer het opnieuw", - "retype_password": "Herhaal wachtwoord", + "confirm_password": "Herhaal wachtwoord", "success": "Succes", "title": "Plausibele ontkenning" }, @@ -284,7 +284,7 @@ "privacy_do_not_track_explanation": "Informatie over de prestatie en betrouwbaarheid zal niet worden opgegeven voor analyse.", "push_notifications": "Push notificaties", "rate": "Rate", - "retype_password": "Geef nogmaals het wachtwoord op", + "confirm_password": "Geef nogmaals het wachtwoord op", "selfTest": "Zelf-Test", "save": "Opslaan", "saved": "Opgeslagen", diff --git a/loc/pl.json b/loc/pl.json index 2e9976b51..b89f0f1f8 100644 --- a/loc/pl.json +++ b/loc/pl.json @@ -85,7 +85,7 @@ "help2": "Nowa przestrzeń będzie w pełni funkcjonalna i możesz przechowywać w niej minimalne ilości, aby wyglądało to wiarygodnie.", "password_should_not_match": "Hasło jest aktualnie w użyciu. Spróbuj z innym hasłem.", "passwords_do_not_match": "Hasła do siebie nie pasują, spróbuj ponownie.", - "retype_password": "Wpisz ponownie hasło", + "confirm_password": "Wpisz ponownie hasło", "success": "Sukces", "title": "Wiarygodna zaprzeczalność" }, @@ -304,7 +304,7 @@ "privacy_do_not_track_explanation": "Informacje dotyczące wydajności i niezawodności nie będą przesyłane do analizy.", "push_notifications": "Powiadomienia Push", "rate": "Kurs", - "retype_password": "Wprowadź Ponownie hasło", + "confirm_password": "Wprowadź Ponownie hasło", "selfTest": "Autotest", "save": "Zapisz", "saved": "Zapisano", diff --git a/loc/pt_br.json b/loc/pt_br.json index a5e1b3768..c791df0c0 100644 --- a/loc/pt_br.json +++ b/loc/pt_br.json @@ -103,7 +103,7 @@ "help2": "Essa nova interface é completamente funcional e você pode inclusive manter nela um valor mínimo para que pareça mais real.", "password_should_not_match": "Esta senha já está sendo usada. Por favor, tente uma senha diferente.", "passwords_do_not_match": "As senhas não coincidem. Por favor, tente outra vez.", - "retype_password": "Inserir senha novamente", + "confirm_password": "Inserir senha novamente", "success": "Sucesso", "title": "Negação plausível" }, @@ -321,7 +321,7 @@ "privacy_do_not_track_explanation": "Informações de confiabilidade e desempenho não serão enviadas para análise.", "push_notifications": "Notificações push", "rate": "Taxa", - "retype_password": "Inserir senha novamente", + "confirm_password": "Inserir senha novamente", "selfTest": "Autoteste", "save": "Salvar", "saved": "Salvo", diff --git a/loc/pt_pt.json b/loc/pt_pt.json index 339eb4a38..703c116f1 100644 --- a/loc/pt_pt.json +++ b/loc/pt_pt.json @@ -76,7 +76,7 @@ "help2": "Este novo armazenamento é completamente funcional, e pode guardar um valor minímo para parecer mais real.", "password_should_not_match": "Password para armazenamento FALSO não deve coincidir com a password principal", "passwords_do_not_match": "Passwords não coincidem, tente novamente", - "retype_password": "Inserir password novamente", + "confirm_password": "Inserir password novamente", "success": "Sucesso", "title": "Negação plausível" }, @@ -235,7 +235,7 @@ "privacy_system_settings": "Configurações do Sistema", "privacy_quickactions": "Atalhos da Carteira", "push_notifications": "Notificações via push", - "retype_password": "Inserir password novamente", + "confirm_password": "Inserir password novamente", "save": "Guardar", "saved": "Guardado", "total_balance": "Saldo Total", diff --git a/loc/ro.json b/loc/ro.json index 74bc69ffc..db70fc1c0 100644 --- a/loc/ro.json +++ b/loc/ro.json @@ -81,7 +81,7 @@ "help2": "Noul spațiu de stocare va fi complet funcțional, și poți păstra o cantitate minimă pentru a părea mai credibil. ", "password_should_not_match": "Parola este deja în folosință. Încearcă o parolă diferită.", "passwords_do_not_match": "Parolele nu sunt identice. Încearcă din nou.", - "retype_password": "Re-introdu parola", + "confirm_password": "Re-introdu parola", "success": "Succes", "title": "Negare plauzibilă" }, @@ -261,7 +261,7 @@ "privacy_do_not_track": "Dezactivează Analytics", "push_notifications": "Notificări push", "rate": "Rata", - "retype_password": "Scrie parola din nou", + "confirm_password": "Scrie parola din nou", "selfTest": "Auto-test", "save": "Salvează", "saved": "Salvat", diff --git a/loc/ru.json b/loc/ru.json index 29575eb28..739a16509 100644 --- a/loc/ru.json +++ b/loc/ru.json @@ -99,7 +99,7 @@ "help2": "Новое хранилище будет полностью функциональным, и вы даже можете хранить на нем небольшое количество биткоинов, чтобы это выглядело более правдоподобно.", "password_should_not_match": "Пароль для фальшивого хранилища не должен быть таким же как основной пароль", "passwords_do_not_match": "Пароли не совпадают, попробуйте еще раз", - "retype_password": "Повторите пароль", + "confirm_password": "Повторите пароль", "success": "Готово", "title": "Двойное дно" }, @@ -303,7 +303,7 @@ "privacy_do_not_track_explanation": "Информация о производительности и надежности не будет отправлена на анализ.", "push_notifications": "Push-уведомления", "rate": "Курс", - "retype_password": "Повторите пароль", + "confirm_password": "Повторите пароль", "selfTest": "Проверка приложения", "save": "Сохранить", "saved": "Сохранено", diff --git a/loc/si_LK.json b/loc/si_LK.json index e30e414fd..7a8b9cef8 100644 --- a/loc/si_LK.json +++ b/loc/si_LK.json @@ -84,7 +84,7 @@ "help2": "නව ගබඩාව සම්පුර්ණයෙන්ම ක්‍රියාත්මක වන අතර ඔබට විශ්වාසදායක ලෙස පෙනෙන පරිදි අවම ප්‍රමාණයක් එහි ගබඩා කළ හැකිය.", "password_should_not_match": "මුර පදය දැනටමත් භාවිතයේ ඇත. කරුණාකර වෙනත් මුර පදයක් සඳහා උත්සාහ කරන්න.", "passwords_do_not_match": "මුරපද නොගැලපේ. කරුණාකර නැවත උත්සාහ කරන්න.", - "retype_password": "මුරපදය නැවත ඇතුළත් කරන්න", + "confirm_password": "මුරපදය නැවත ඇතුළත් කරන්න", "success": "සාර්ථකයි", "title": "පිළිගතහැකි ප්‍රතික්ෂේප කිරීම" }, @@ -272,7 +272,7 @@ "privacy_do_not_track_explanation": "විශ්ලේෂණය සඳහා කාර්ය සාධනය සහ විශ්වසනීයත්ව තොරතුරු ඉදිරිපත් නොකෙරේ.", "push_notifications": "තල්ලු දැනුම්දීම්", "rate": "අනුපාතය", - "retype_password": "මුරපදය යළි ඇතුළත් කරන්න", + "confirm_password": "මුරපදය යළි ඇතුළත් කරන්න", "selfTest": "ස්වයං පරීක්ෂණය", "save": "සුරකින්න", "saved": "සුරකින ලදි", diff --git a/loc/sk_sk.json b/loc/sk_sk.json index f37c05429..9a4a5bad4 100644 --- a/loc/sk_sk.json +++ b/loc/sk_sk.json @@ -64,7 +64,7 @@ "help": "Za určitých okolností môžete byť donútení k prezradeniu hesla. K zaisteniu bezpečnosti vaších prostriedkov, BlueWallet môže vytvořiť ďalšie zašifrované úložiská s rozdielným heslom. V prípade potreby môžete toto heslo dať tretej strane. Pokiaľ bude zadané do BlueWallet, odomkne nové \"falošné\" úložisko. Toto bude vyzerať hodnoverne, ale udrží vaše pravé hlavné úložisko v bezpečí.", "help2": "Nové úložisko bude plne funkčné, môžete naň uložiť minimálnu čiastku, aby vyzeralo uveriteľnejšie.", "password_should_not_match": "Heslo k falošnému úložisku nesmie byť rovnaké ako heslo k hlavnému úložisku", - "retype_password": "Heslo znovu", + "confirm_password": "Heslo znovu", "success": "Úspech", "title": "Plausible deniability..." }, @@ -160,7 +160,7 @@ "plausible_deniability": "Plausible deniability...", "privacy_system_settings": "Systémové nastavenia", "push_notifications": "Push notifikácie", - "retype_password": "Heslo znovu", + "confirm_password": "Heslo znovu", "save": "Uložiť", "saved": "Uložené" }, diff --git a/loc/sl_SI.json b/loc/sl_SI.json index 499fcde86..6616c2477 100644 --- a/loc/sl_SI.json +++ b/loc/sl_SI.json @@ -85,7 +85,7 @@ "help2": "Nova shramba bo popolnoma uporabna, za večjo verodostojnost lahko tam hranite manjši znesek.", "password_should_not_match": "Geslo je trenutno v uporabi. Prosimo, poskusite z drugim geslom.", "passwords_do_not_match": "Gesli se ne ujemata, prosimo poskusite ponovno.", - "retype_password": "Ponovno vpišite geslo", + "confirm_password": "Ponovno vpišite geslo", "success": "Uspešno", "title": "Verodostojno Zanikanje" }, @@ -279,7 +279,7 @@ "privacy_do_not_track_explanation": "Informacije o zmogljivosti in zanesljivosti ne bodo poslane v analizo.", "push_notifications": "Potisna obvestila", "rate": "Tečaj", - "retype_password": "Ponovno vpišite geslo", + "confirm_password": "Ponovno vpišite geslo", "selfTest": "Samotestiranje", "save": "Shrani", "saved": "Shranjeno", diff --git a/loc/sv_se.json b/loc/sv_se.json index dbf5be359..6e8488587 100644 --- a/loc/sv_se.json +++ b/loc/sv_se.json @@ -95,7 +95,7 @@ "help2": "Den alternativa lagringsytan kommer att vara fullt fungerade och du kan eventuellt spara en mindre summa där för att den ska verka mer trovärdig.", "password_should_not_match": "Lösenordet för den fejkade lagringsytan får inte vara samma som ditt huvudlösenord", "passwords_do_not_match": "Lösenorden du angav matchar inte. Försök igen.", - "retype_password": "Ange lösenord igen", + "confirm_password": "Ange lösenord igen", "success": "Fejkad lagringsyta skapad!", "title": "Trovärdigt förnekande" }, @@ -291,7 +291,7 @@ "privacy_do_not_track_explanation": "Information om prestanda och tillförlitlighet kommer inte att skickas in för analys.", "push_notifications": "Pushmeddelanden", "rate": "Betygsätta", - "retype_password": "Ange lösenord igen", + "confirm_password": "Ange lösenord igen", "selfTest": "Självtest", "save": "Spara", "saved": "Sparad", diff --git a/loc/th_th.json b/loc/th_th.json index 1e79693d5..dcbbc6474 100644 --- a/loc/th_th.json +++ b/loc/th_th.json @@ -62,7 +62,7 @@ "help2": "ที่เก็บข้อมูลอันใหม่จะทำงานได้สมบูรณ์ และคุณสามารถเก็บจำนวนเงินขั้นต่ำได้ โดยที่มีความน่าเชื่อถือ.", "password_should_not_match": "รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง", "passwords_do_not_match": "รหัสผ่านไม่ตรงกัน ", - "retype_password": "ใส่รหัสผ่านอีกครั้ง ใส่รหัสผ่านอีกครั้ง", + "confirm_password": "ใส่รหัสผ่านอีกครั้ง ใส่รหัสผ่านอีกครั้ง", "success": "สำเร็จ", "title": "การปฏิเสธที่เป็นไปได้" }, @@ -193,7 +193,7 @@ "privacy_read_clipboard": "อ่านค่าจากคลิปบอร์ด", "privacy_system_settings": "ตั้งค่าระบบ", "push_notifications": "การแจ้งเตือนแบบ Push", - "retype_password": "ใส่รหัสผ่านอีกครั้ง", + "confirm_password": "ใส่รหัสผ่านอีกครั้ง", "save": "บันทึก", "saved": "บันทึกแล้ว", "total_balance": "ยอดรวม" diff --git a/loc/tr_tr.json b/loc/tr_tr.json index 82cafbbad..721027d7f 100644 --- a/loc/tr_tr.json +++ b/loc/tr_tr.json @@ -81,7 +81,7 @@ "help": "Bazı koşullar altında, şifrenizi açıklamanız gerekebilir. Paralarınızı güvende tutmak için, BlueWallet başka bir şifre ile şifreli depolama alanı yaratabilir. Baskı altında, Bu şifreyi 3. bir tarafa söyleyebilirsiniz. Girilirse BlueWallet, yeni 'sahte' bir depolamanın kilidini açacaktır. Bu 3. şahıslara normal görünecektir, ancak paraların olduğu ana depolama alanınızı gizlice saklamaya devam edecektir.", "help2": "Yeni depolama alanı tamamen işlevsel olacak ve ufak bir miktar tutarsanız daha inanılır görünecektir.", "password_should_not_match": "Sahte depolama şifreniz, ana depolama şifrenizle aynı olmamalıdır", - "retype_password": "Şifrenizi yeniden yazın", + "confirm_password": "Şifrenizi yeniden yazın", "success": "Başarılı", "title": "Makul Ret" }, @@ -178,7 +178,7 @@ "privacy_system_settings": "Sistem Ayarları", "privacy_quickactions": "Cüzdan Kısayolları", "push_notifications": "Bildirimler", - "retype_password": "Şifrenizi yeniden girin", + "confirm_password": "Şifrenizi yeniden girin", "save": "Kaydet", "saved": "Kaydedildi", "total_balance": "Bakiye" diff --git a/loc/ua.json b/loc/ua.json index 4be522203..da5ee422e 100644 --- a/loc/ua.json +++ b/loc/ua.json @@ -93,7 +93,7 @@ "help2": "Нове сховище буде повністю функціональним і ви навіть можете зберігати на ньому невелику кількість монет, щоб це виглядало правдоподібніше.", "password_should_not_match": "Пароль для фальшивого сховища не може бути таким же як основний пароль.", "passwords_do_not_match": "Паролі не збігаються, спробуйте ще раз.", - "retype_password": "Наберіть пароль ще раз", + "confirm_password": "Наберіть пароль ще раз", "success": "Операція успішна", "title": "Правдоподібне Заперечення" }, @@ -227,7 +227,7 @@ "privacy_quickactions": "Ярлики Гаманця", "privacy_do_not_track": "Вимкнути Аналітику", "push_notifications": "Пуш Сповіщення", - "retype_password": "Наберіть пароль ще раз", + "confirm_password": "Наберіть пароль ще раз", "save": "Зберегти", "saved": "Збережено", "total_balance": "Загальний Баланс", diff --git a/loc/vi_vn.json b/loc/vi_vn.json index a8cd5e11c..a4005b64c 100644 --- a/loc/vi_vn.json +++ b/loc/vi_vn.json @@ -92,7 +92,7 @@ "help2": "Lưu trữ mới sẻ hoạt động hoàn toàn, và bạn có thể nạp một ít tiền tại đó để nó có vẻ đáng tin cậy.", "password_should_not_match": "Mật khẩu hiện đang được sử dụng. Vui lòng thử một mật khẩu khác.", "passwords_do_not_match": "Mật khẩu không phù hợp. Vui lòng thử lại.", - "retype_password": "Nhập lại mật khẩu", + "confirm_password": "Nhập lại mật khẩu", "success": "Thành công", "title": "Sự từ chối hợp lý " }, @@ -285,7 +285,7 @@ "privacy_do_not_track_explanation": "Thông tin về hiệu suất và độ tin cậy không sẽ được gửi để phân tích.", "push_notifications": " Thông báo đẩy", "rate": "Tỷ lệ", - "retype_password": "Nhập lại mật khẩu", + "confirm_password": "Nhập lại mật khẩu", "selfTest": "Tự kiểm tra", "save": "Lưu", "saved": "Đã lưu", diff --git a/loc/zar_afr.json b/loc/zar_afr.json index 8e8b2511b..7a8c59d94 100644 --- a/loc/zar_afr.json +++ b/loc/zar_afr.json @@ -89,7 +89,7 @@ "help2": "Fop berging is heeltemal funksioneel", "password_should_not_match": "Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging.", "passwords_do_not_match": "Wagwoorde vergelyk nie, probeer weer", - "retype_password": "Hervoer wagwoord", + "confirm_password": "Hervoer wagwoord", "success": "Sukses", "title": "Geloofwaardige Ontkenbaarheid" }, @@ -172,7 +172,7 @@ "password_explain": "Skep die wagwoord wat jy sal gebruik om jou berging te de-enkripteer", "passwords_do_not_match": "Wagwoorde stem nie oor een nie", "plausible_deniability": "Geloofwaardige ontkenbaarheid...", - "retype_password": "Hervoer wagwoord", + "confirm_password": "Hervoer wagwoord", "save": "stoor" }, "notifications": { diff --git a/loc/zar_xho.json b/loc/zar_xho.json index b9aeb0f4f..9129b9e11 100644 --- a/loc/zar_xho.json +++ b/loc/zar_xho.json @@ -52,7 +52,7 @@ "help2": "Igumbi lokugcina elitsha liza kusebenza ngokupheleleyo, kwaye unako ukugcina okunye ‘ + ‘lxabiso elincinci apho likhangeleka ngakumbi.", "password_should_not_match": "Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo", "passwords_do_not_match": "Inombolo yokuvula ayihambelani, zama kwakhona", - "retype_password": "Phinda inombolo yokuvula", + "confirm_password": "Phinda inombolo yokuvula", "success": "Iphumelele", "title": "Ukuphika" }, @@ -98,7 +98,7 @@ "password_explain": "Ukudala iinombolo yokuvula oyisebenzisayo ukucima ukugcina", "passwords_do_not_match": "Inombolo yokuvula azifani", "plausible_deniability": "Ukuphika...", - "retype_password": "Phina inombolo yokuvula", + "confirm_password": "Phina inombolo yokuvula", "save": "ndoloza" }, "transactions": { diff --git a/loc/zh_cn.json b/loc/zh_cn.json index d1ee7bbfd..cb231074c 100644 --- a/loc/zh_cn.json +++ b/loc/zh_cn.json @@ -68,7 +68,7 @@ "help2": "新的储存空间具备完整的功能,你可以存入少量的金额在里面。", "password_should_not_match": "此密码已被使用,请用另一个密码。", "passwords_do_not_match": "密码不匹配,请再试一遍。", - "retype_password": "重输密码", + "confirm_password": "重输密码", "success": "成功", "title": "合理推诿" }, @@ -229,7 +229,7 @@ "privacy_quickactions": "钱包捷径", "privacy_clipboard_explanation": "如果在剪贴板中找到地址或发票,请提供捷径。", "push_notifications": "推送通知", - "retype_password": "再次输入密码", + "confirm_password": "再次输入密码", "selfTest": "自行测试", "save": "保存", "saved": "已保存", diff --git a/loc/zh_tw.json b/loc/zh_tw.json index e9a3008a5..c51598b8c 100644 --- a/loc/zh_tw.json +++ b/loc/zh_tw.json @@ -65,7 +65,7 @@ "help2": "新的儲存空間具備完整的功能,你可以存入少量的金額在裏面。", "password_should_not_match": "此密碼已被使用,請用另一個密碼。", "passwords_do_not_match": "密碼不匹配,請再試一遍。", - "retype_password": "重新輸入密碼", + "confirm_password": "重新輸入密碼", "success": "成功", "title": "合理推諉" }, @@ -223,7 +223,7 @@ "privacy_quickactions": "錢包捷徑", "privacy_clipboard_explanation": "如果在剪貼簿中找到地址或賬單,請提供捷徑。", "push_notifications": "推送通知", - "retype_password": "再次輸入密碼", + "confirm_password": "再次輸入密碼", "selfTest": "自行測試", "save": "儲存", "saved": "已儲存", diff --git a/screen/PlausibleDeniability.tsx b/screen/PlausibleDeniability.tsx index 4b4642416..bad675d04 100644 --- a/screen/PlausibleDeniability.tsx +++ b/screen/PlausibleDeniability.tsx @@ -53,7 +53,7 @@ const PlausibleDeniability: React.FC = () => { dispatch({ type: SET_LOADING, payload: false }); return; } - const p2 = await prompt(loc.plausibledeniability.create_password, loc.plausibledeniability.retype_password); + const p2 = await prompt(loc.plausibledeniability.create_password, loc.plausibledeniability.confirm_password); if (p1 !== p2) { dispatch({ type: SET_LOADING, payload: false }); triggerHapticFeedback(HapticFeedbackTypes.NotificationError); diff --git a/screen/settings/EncryptStorage.tsx b/screen/settings/EncryptStorage.tsx index 2655a9f09..672f85f27 100644 --- a/screen/settings/EncryptStorage.tsx +++ b/screen/settings/EncryptStorage.tsx @@ -1,29 +1,26 @@ -import React, { useCallback, useEffect, useReducer } from 'react'; +import React, { useCallback, useEffect, useReducer, useRef } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'; -import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents'; import presentAlert from '../../components/Alert'; import ListItem from '../../components/ListItem'; import { useTheme } from '../../components/themes'; -import prompt from '../../helpers/prompt'; import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics'; import loc from '../../loc'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useStorage } from '../../hooks/context/useStorage'; -import { popToTop } from '../../NavigationService'; +import PromptPasswordConfirmationModal, { + MODAL_TYPES, + PromptPasswordConfirmationModalHandle, +} from '../../components/PromptPasswordConfirmationModal'; enum ActionType { SetLoading = 'SET_LOADING', SetStorageEncryptedSwitch = 'SET_STORAGE_ENCRYPTED_SWITCH', SetDeviceBiometricCapable = 'SET_DEVICE_BIOMETRIC_CAPABLE', SetCurrentLoadingSwitch = 'SET_CURRENT_LOADING_SWITCH', -} - -interface State { - isLoading: boolean; - storageIsEncryptedSwitchEnabled: boolean; - deviceBiometricCapable: boolean; - currentLoadingSwitch: string | null; + SetModalType = 'SET_MODAL_TYPE', + SetIsSuccess = 'SET_IS_SUCCESS', + ResetState = 'RESET_STATE', } interface Action { @@ -31,11 +28,22 @@ interface Action { payload?: any; } +interface State { + isLoading: boolean; + storageIsEncryptedSwitchEnabled: boolean; + deviceBiometricCapable: boolean; + currentLoadingSwitch: string | null; + modalType: keyof typeof MODAL_TYPES; + isSuccess: boolean; +} + const initialState: State = { isLoading: true, storageIsEncryptedSwitchEnabled: false, deviceBiometricCapable: false, currentLoadingSwitch: null, + modalType: MODAL_TYPES.ENTER_PASSWORD, + isSuccess: false, }; const reducer = (state: State, action: Action): State => { @@ -48,6 +56,12 @@ const reducer = (state: State, action: Action): State => { return { ...state, deviceBiometricCapable: action.payload }; case ActionType.SetCurrentLoadingSwitch: return { ...state, currentLoadingSwitch: action.payload }; + case ActionType.SetModalType: + return { ...state, modalType: action.payload }; + case ActionType.SetIsSuccess: + return { ...state, isSuccess: action.payload }; + case ActionType.ResetState: + return initialState; default: return state; } @@ -59,6 +73,7 @@ const EncryptStorage = () => { const [state, dispatch] = useReducer(reducer, initialState); const { navigate } = useExtendedNavigation(); const { colors } = useTheme(); + const promptRef = useRef(null); const styleHooks = StyleSheet.create({ root: { @@ -75,71 +90,25 @@ const EncryptStorage = () => { dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: isStorageEncryptedSwitchEnabled }); dispatch({ type: ActionType.SetDeviceBiometricCapable, payload: isDeviceBiometricCapableSync }); dispatch({ type: ActionType.SetLoading, payload: false }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isStorageEncrypted, isDeviceBiometricCapable]); useEffect(() => { initializeState(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [initializeState]); const handleDecryptStorage = async () => { dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'decrypt' }); - const password = await prompt(loc.settings.password, loc._.storage_is_encrypted).catch(() => { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - }); - if (!password) { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - return; - } - try { - await decryptStorage(password); - await saveToDisk(); - popToTop(); - } catch (e) { - if (password) { - triggerHapticFeedback(HapticFeedbackTypes.NotificationError); - presentAlert({ message: loc._.bad_password }); - } - - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() }); - } + dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.ENTER_PASSWORD }); + promptRef.current?.present(); }; const onEncryptStorageSwitch = async (value: boolean) => { dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'encrypt' }); dispatch({ type: ActionType.SetLoading, payload: true }); + if (value) { - let p1 = await prompt(loc.settings.password, loc.settings.password_explain).catch(() => { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - p1 = undefined; - }); - if (!p1) { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - return; - } - const p2 = await prompt(loc.settings.password, loc.settings.retype_password).catch(() => { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - }); - if (p1 === p2) { - await encryptStorage(p1); - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() }); - saveToDisk(); - } else { - dispatch({ type: ActionType.SetLoading, payload: false }); - dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); - triggerHapticFeedback(HapticFeedbackTypes.NotificationError); - presentAlert({ message: loc.settings.passwords_do_not_match }); - } + dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.CREATE_PASSWORD }); + promptRef.current?.present(); } else { Alert.alert( loc.settings.encrypt_decrypt, @@ -179,20 +148,18 @@ const EncryptStorage = () => { }; const renderPasscodeExplanation = () => { - let isCapable = true; + return ( + Platform.OS === 'android' && Platform.Version >= 30 ? ( + <> + + {loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })} + + ) : null + ); + }; - if (Platform.OS === 'android') { - if (Platform.Version < 30) { - isCapable = false; - } - } - - return isCapable ? ( - <> - - {loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })} - - ) : null; + const onModalDismiss = () => { + initializeState(); // Reinitialize state on modal dismiss to refresh the UI }; return ( @@ -246,6 +213,38 @@ const EncryptStorage = () => { containerStyle={[styles.row, styleHooks.root]} /> )} + { + let success = false; + if (state.modalType === MODAL_TYPES.CREATE_PASSWORD) { + try { + await encryptStorage(password); + await saveToDisk(); + success = true; + } catch (error) { + presentAlert({ title: loc.errors.error, message: (error as Error).message }); + success = false; + } + } else if (state.modalType === MODAL_TYPES.ENTER_PASSWORD) { + try { + await decryptStorage(password); + await saveToDisk(); + success = true; + } catch (error) { + presentAlert({ message: loc._.bad_password }); + success = false; + } + } + return success; + }} + onConfirmationFailure={() => { + dispatch({ type: ActionType.SetLoading, payload: false }); + dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null }); + }} + onDismiss={onModalDismiss} + /> ); };