This commit is contained in:
Marcos Rodriguez Velez 2024-08-04 18:08:01 -04:00
parent 27116f851c
commit 8a9127d22e
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
2 changed files with 119 additions and 71 deletions

View file

@ -18,7 +18,6 @@ interface PromptPasswordConfirmationModalProps {
modalType: ModalType; modalType: ModalType;
onConfirmationSuccess: (password: string) => Promise<boolean>; onConfirmationSuccess: (password: string) => Promise<boolean>;
onConfirmationFailure: () => void; onConfirmationFailure: () => void;
onDismiss?: () => void;
} }
export interface PromptPasswordConfirmationModalHandle { export interface PromptPasswordConfirmationModalHandle {
@ -27,16 +26,18 @@ export interface PromptPasswordConfirmationModalHandle {
} }
const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationModalHandle, PromptPasswordConfirmationModalProps>( const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationModalHandle, PromptPasswordConfirmationModalProps>(
({ modalType, onConfirmationSuccess, onConfirmationFailure, onDismiss }, ref) => { ({ modalType, onConfirmationSuccess, onConfirmationFailure }, ref) => {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false); const [isSuccess, setIsSuccess] = useState(false);
const [showExplanation, setShowExplanation] = useState(true); // State to toggle between explanation and password input for CREATE_PASSWORD
const modalRef = useRef<BottomModalHandle>(null); const modalRef = useRef<BottomModalHandle>(null);
const fadeOutAnimation = useRef(new Animated.Value(1)).current; const fadeOutAnimation = useRef(new Animated.Value(1)).current;
const fadeInAnimation = useRef(new Animated.Value(0)).current; const fadeInAnimation = useRef(new Animated.Value(0)).current;
const scaleAnimation = useRef(new Animated.Value(1)).current; const scaleAnimation = useRef(new Animated.Value(1)).current;
const shakeAnimation = useRef(new Animated.Value(0)).current; const shakeAnimation = useRef(new Animated.Value(0)).current;
const explanationOpacity = useRef(new Animated.Value(1)).current; // New animated value for opacity
const { colors } = useTheme(); const { colors } = useTheme();
const passwordInputRef = useRef<TextInput>(null); const passwordInputRef = useRef<TextInput>(null);
const confirmPasswordInputRef = useRef<TextInput>(null); const confirmPasswordInputRef = useRef<TextInput>(null);
@ -64,7 +65,7 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
present: async () => { present: async () => {
resetState(); resetState();
modalRef.current?.present(); modalRef.current?.present();
if (modalType === MODAL_TYPES.CREATE_PASSWORD) { if (modalType === MODAL_TYPES.CREATE_PASSWORD && !showExplanation) {
passwordInputRef.current?.focus(); passwordInputRef.current?.focus();
} else if (modalType === MODAL_TYPES.ENTER_PASSWORD) { } else if (modalType === MODAL_TYPES.ENTER_PASSWORD) {
passwordInputRef.current?.focus(); passwordInputRef.current?.focus();
@ -72,7 +73,7 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
}, },
dismiss: async () => { dismiss: async () => {
await modalRef.current?.dismiss(); await modalRef.current?.dismiss();
onDismiss?.(); // Call onDismiss if provided after modal dismisses resetState();
}, },
})); }));
@ -85,26 +86,46 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
fadeInAnimation.setValue(0); fadeInAnimation.setValue(0);
scaleAnimation.setValue(1); scaleAnimation.setValue(1);
shakeAnimation.setValue(0); shakeAnimation.setValue(0);
explanationOpacity.setValue(1);
if (modalType === MODAL_TYPES.CREATE_PASSWORD) {
setShowExplanation(true);
}
}; };
useEffect(() => {
resetState();
}, [modalType]);
const handleShakeAnimation = () => { const handleShakeAnimation = () => {
Animated.sequence([ Animated.sequence([
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimation, {
toValue: 10, toValue: 10,
duration: 100, duration: 150,
easing: Easing.bounce, easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimation, {
toValue: -10, toValue: -10,
duration: 150,
easing: Easing.inOut(Easing.ease),
useNativeDriver: true,
}),
Animated.timing(shakeAnimation, {
toValue: 5,
duration: 100, duration: 100,
easing: Easing.bounce, easing: Easing.inOut(Easing.ease),
useNativeDriver: true,
}),
Animated.timing(shakeAnimation, {
toValue: -5,
duration: 100,
easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimation, {
toValue: 0, toValue: 0,
duration: 100, duration: 100,
easing: Easing.bounce, easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
]).start(() => { ]).start(() => {
@ -175,22 +196,23 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
} }
}; };
const handleTransitionToCreatePassword = () => {
Animated.timing(explanationOpacity, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start(() => {
setShowExplanation(false);
explanationOpacity.setValue(1); // Reset opacity for when transitioning back
passwordInputRef.current?.focus();
});
};
const handleCancel = async () => { const handleCancel = async () => {
resetState();
onConfirmationFailure(); onConfirmationFailure();
await modalRef.current?.dismiss(); await modalRef.current?.dismiss();
}; };
useEffect(() => {
resetState();
if (modalType === MODAL_TYPES.CREATE_PASSWORD) {
passwordInputRef.current?.focus();
} else if (modalType === MODAL_TYPES.ENTER_PASSWORD) {
passwordInputRef.current?.focus();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [modalType]);
const animatedViewStyle: Animated.WithAnimatedObject<ViewStyle> = { const animatedViewStyle: Animated.WithAnimatedObject<ViewStyle> = {
opacity: fadeOutAnimation, opacity: fadeOutAnimation,
transform: [{ scale: scaleAnimation }], transform: [{ scale: scaleAnimation }],
@ -201,65 +223,82 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
<BottomModal <BottomModal
ref={modalRef} ref={modalRef}
showCloseButton={false} showCloseButton={false}
onDismiss={onDismiss || handleCancel} onDismiss={resetState}
grabber={false} grabber={false}
sizes={[350]} sizes={[370]}
backgroundColor={colors.modal} backgroundColor={colors.modal}
contentContainerStyle={styles.modalContent} contentContainerStyle={styles.modalContent}
footer={ footer={
!isSuccess && ( !isSuccess ? (
<Animated.View style={{ opacity: fadeOutAnimation, transform: [{ scale: scaleAnimation }] }}> showExplanation && modalType === MODAL_TYPES.CREATE_PASSWORD ? null : (
<View style={styles.feeModalFooter}> <Animated.View style={{ opacity: fadeOutAnimation, transform: [{ scale: scaleAnimation }] }}>
<SecondButton testID="CancelButton" title={loc._.cancel} onPress={handleCancel} disabled={isLoading} /> <View style={styles.feeModalFooter}>
<View style={styles.feeModalFooterSpacing} /> <SecondButton testID="CancelButton" title={loc._.cancel} onPress={handleCancel} disabled={isLoading} />
<SecondButton <View style={styles.feeModalFooterSpacing} />
title={isLoading ? '' : loc._.ok} <SecondButton
onPress={handleSubmit} title={isLoading ? '' : loc._.ok}
testID="OKButton" onPress={handleSubmit}
loading={isLoading} testID="OKButton"
disabled={isLoading || !password || (modalType === MODAL_TYPES.CREATE_PASSWORD && !confirmPassword)} loading={isLoading}
/> disabled={isLoading || !password || (modalType === MODAL_TYPES.CREATE_PASSWORD && !confirmPassword)}
</View> />
</Animated.View> </View>
) || undefined </Animated.View>
)
) : null
} }
> >
{!isSuccess && modalType !== MODAL_TYPES.SUCCESS && ( {!isSuccess && (
<Animated.View style={animatedViewStyle}> <Animated.View style={animatedViewStyle}>
<Text style={[styles.textLabel, stylesHook.feeModalLabel]}> {modalType === MODAL_TYPES.CREATE_PASSWORD && showExplanation && (
{modalType === MODAL_TYPES.CREATE_PASSWORD ? loc.settings.password_explain : loc._.enter_password} <Animated.View style={{ opacity: explanationOpacity }}>
</Text> <Text style={[styles.textLabel, stylesHook.feeModalLabel]}>{loc.settings.encrypt_storage_explanation_headline}</Text>
<View style={styles.inputContainer}> <Text style={[styles.description, stylesHook.feeModalCustomText]}>
<Animated.View style={{ transform: [{ translateX: shakeAnimation }] }}> {loc.settings.encrypt_storage_explanation_description_line1}
<TextInput </Text>
testID="PasswordInput" <Text style={[styles.description, stylesHook.feeModalCustomText]}>
ref={passwordInputRef} {loc.settings.encrypt_storage_explanation_description_line2}
secureTextEntry </Text>
placeholder="Password" <View style={styles.feeModalFooter} />
value={password} <SecondButton title={loc.settings.i_understand} onPress={handleTransitionToCreatePassword} disabled={isLoading} />
selectTextOnFocus
onChangeText={setPassword}
style={[styles.input, stylesHook.input]}
autoFocus
onSubmitEditing={handleSubmit} // Handle Enter key as OK
/>
</Animated.View> </Animated.View>
{modalType === MODAL_TYPES.CREATE_PASSWORD && ( )}
<Animated.View style={{ transform: [{ translateX: shakeAnimation }] }}> {(modalType === MODAL_TYPES.ENTER_PASSWORD || (modalType === MODAL_TYPES.CREATE_PASSWORD && !showExplanation)) && (
<TextInput <>
testID="ConfirmPasswordInput" <Text style={[styles.textLabel, stylesHook.feeModalLabel]}>
ref={confirmPasswordInputRef} {modalType === MODAL_TYPES.CREATE_PASSWORD ? loc.settings.password_explain : loc._.enter_password}
secureTextEntry </Text>
placeholder="Confirm Password" <View style={styles.inputContainer}>
value={confirmPassword} <Animated.View style={{ transform: [{ translateX: shakeAnimation }] }}>
selectTextOnFocus <TextInput
onChangeText={setConfirmPassword} testID="PasswordInput"
style={[styles.input, stylesHook.input]} ref={passwordInputRef}
onSubmitEditing={handleSubmit} // Handle Enter key as OK secureTextEntry
/> placeholder="Password"
</Animated.View> value={password}
)} onChangeText={setPassword}
</View> style={[styles.input, stylesHook.input]}
autoFocus
onSubmitEditing={handleSubmit} // Handle Enter key as OK
/>
</Animated.View>
{modalType === MODAL_TYPES.CREATE_PASSWORD && (
<Animated.View style={{ transform: [{ translateX: shakeAnimation }] }}>
<TextInput
testID="ConfirmPasswordInput"
ref={confirmPasswordInputRef}
secureTextEntry
placeholder="Confirm Password"
value={confirmPassword}
onChangeText={setConfirmPassword}
style={[styles.input, stylesHook.input]}
onSubmitEditing={handleSubmit} // Handle Enter key as OK
/>
</Animated.View>
)}
</View>
</>
)}
</Animated.View> </Animated.View>
)} )}
{isSuccess && ( {isSuccess && (
@ -278,7 +317,7 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
transform: [ transform: [
{ {
scale: scaleAnimation.interpolate({ scale: scaleAnimation.interpolate({
inputRange: [0, 1], inputRange: [0.8, 1],
outputRange: [0.8, 1], outputRange: [0.8, 1],
}), }),
}, },
@ -333,6 +372,11 @@ const styles = StyleSheet.create({
marginBottom: 16, marginBottom: 16,
textAlign: 'center', textAlign: 'center',
}, },
description: {
fontSize: 16,
marginBottom: 12,
textAlign: 'center',
},
successContainer: { successContainer: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View file

@ -264,6 +264,10 @@
"encrypt_decrypt": "Decrypt Storage", "encrypt_decrypt": "Decrypt Storage",
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.", "encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
"encrypt_enc_and_pass": "Encrypted and Password Protected", "encrypt_enc_and_pass": "Encrypted and Password Protected",
"encrypt_storage_explanation_headline": "Enable Storage Encryption",
"encrypt_storage_explanation_description_line1": "Enabling Storage Encryption adds an extra layer of protection to your app by securing the way your data is stored on your device. This makes it harder for anyone to access your information without permission.",
"encrypt_storage_explanation_description_line2": "However, it's important to know that this encryption only protects the access to your wallets stored on the device's keychain. It doesn't put a password or any extra protection on the wallets themselves.",
"i_understand": "I understand",
"encrypt_title": "Security", "encrypt_title": "Security",
"encrypt_tstorage": "Storage", "encrypt_tstorage": "Storage",
"encrypt_use": "Use {type}", "encrypt_use": "Use {type}",