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

View file

@ -264,6 +264,10 @@
"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_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_tstorage": "Storage",
"encrypt_use": "Use {type}",