mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-19 05:45:15 +01:00
wip
This commit is contained in:
parent
6f3e02c8a4
commit
30f05d57ac
@ -9,6 +9,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapt
|
||||
export const MODAL_TYPES = {
|
||||
ENTER_PASSWORD: 'ENTER_PASSWORD',
|
||||
CREATE_PASSWORD: 'CREATE_PASSWORD',
|
||||
SUCCESS: 'SUCCESS',
|
||||
} as const;
|
||||
|
||||
export type ModalType = (typeof MODAL_TYPES)[keyof typeof MODAL_TYPES];
|
||||
@ -17,7 +18,6 @@ interface PromptPasswordConfirmationModalProps {
|
||||
modalType: ModalType;
|
||||
onConfirmationSuccess: (password: string) => Promise<boolean>;
|
||||
onConfirmationFailure: () => void;
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
export interface PromptPasswordConfirmationModalHandle {
|
||||
@ -26,7 +26,7 @@ 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);
|
||||
@ -35,7 +35,10 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
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 { colors } = useTheme();
|
||||
const passwordInputRef = useRef<TextInput>(null);
|
||||
const confirmPasswordInputRef = useRef<TextInput>(null);
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
modalContent: {
|
||||
@ -60,6 +63,11 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
present: async () => {
|
||||
resetState();
|
||||
modalRef.current?.present();
|
||||
if (modalType === MODAL_TYPES.CREATE_PASSWORD) {
|
||||
passwordInputRef.current?.focus();
|
||||
} else if (modalType === MODAL_TYPES.ENTER_PASSWORD) {
|
||||
passwordInputRef.current?.focus();
|
||||
}
|
||||
},
|
||||
dismiss: async () => modalRef.current?.dismiss(),
|
||||
}));
|
||||
@ -72,6 +80,33 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
fadeOutAnimation.setValue(1);
|
||||
fadeInAnimation.setValue(0);
|
||||
scaleAnimation.setValue(1);
|
||||
shakeAnimation.setValue(0);
|
||||
};
|
||||
|
||||
const handleShakeAnimation = () => {
|
||||
Animated.sequence([
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: 10,
|
||||
duration: 100,
|
||||
easing: Easing.bounce,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: -10,
|
||||
duration: 100,
|
||||
easing: Easing.bounce,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: 0,
|
||||
duration: 100,
|
||||
easing: Easing.bounce,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start(() => {
|
||||
confirmPasswordInputRef.current?.focus();
|
||||
confirmPasswordInputRef.current?.setNativeProps({ selection: { start: 0, end: confirmPassword.length } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleSuccessAnimation = () => {
|
||||
@ -119,8 +154,8 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
}
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
handleShakeAnimation();
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
onConfirmationFailure();
|
||||
}
|
||||
} else if (modalType === MODAL_TYPES.ENTER_PASSWORD) {
|
||||
success = await onConfirmationSuccess(password);
|
||||
@ -129,8 +164,8 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
handleSuccessAnimation();
|
||||
} else {
|
||||
handleShakeAnimation();
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
onConfirmationFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -143,6 +178,11 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
|
||||
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]);
|
||||
|
||||
@ -156,7 +196,7 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
<BottomModal
|
||||
ref={modalRef}
|
||||
showCloseButton={false}
|
||||
onDismiss={onDismiss}
|
||||
onDismiss={handleCancel}
|
||||
grabber={false}
|
||||
sizes={[350]}
|
||||
backgroundColor={colors.modal}
|
||||
@ -165,11 +205,12 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
(!isSuccess && (
|
||||
<Animated.View style={{ opacity: fadeOutAnimation, transform: [{ scale: scaleAnimation }] }}>
|
||||
<View style={styles.feeModalFooter}>
|
||||
<SecondButton title={loc._.cancel} onPress={handleCancel} disabled={isLoading} />
|
||||
<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)}
|
||||
/>
|
||||
@ -185,26 +226,32 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
|
||||
{modalType === MODAL_TYPES.CREATE_PASSWORD ? loc.settings.password_explain : loc._.enter_password}
|
||||
</Text>
|
||||
<View style={styles.inputContainer}>
|
||||
<TextInput
|
||||
testID="PasswordInput"
|
||||
secureTextEntry
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
style={[styles.input, stylesHook.input]}
|
||||
autoFocus
|
||||
onSubmitEditing={handleSubmit} // Handle Enter key as OK
|
||||
/>
|
||||
{modalType === MODAL_TYPES.CREATE_PASSWORD && (
|
||||
<Animated.View style={{ transform: [{ translateX: shakeAnimation }] }}>
|
||||
<TextInput
|
||||
testID="ConfirmPasswordInput"
|
||||
testID="PasswordInput"
|
||||
ref={passwordInputRef}
|
||||
secureTextEntry
|
||||
placeholder="Confirm Password"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
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>
|
||||
@ -282,4 +329,4 @@ const styles = StyleSheet.create({
|
||||
color: 'white',
|
||||
fontSize: 30,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ type SecondButtonProps = {
|
||||
title?: string;
|
||||
onPress?: () => void;
|
||||
loading?: boolean;
|
||||
testID?: string;
|
||||
};
|
||||
|
||||
export const SecondButton = forwardRef<TouchableOpacity, SecondButtonProps>((props, ref) => {
|
||||
@ -41,6 +42,7 @@ export const SecondButton = forwardRef<TouchableOpacity, SecondButtonProps>((pro
|
||||
<TouchableOpacity
|
||||
disabled={props.disabled || props.loading}
|
||||
accessibilityRole="button"
|
||||
testID={props.testID}
|
||||
style={[styles.button, { backgroundColor }]}
|
||||
{...props}
|
||||
ref={ref}
|
||||
|
@ -19,8 +19,6 @@ enum ActionType {
|
||||
SetDeviceBiometricCapable = 'SET_DEVICE_BIOMETRIC_CAPABLE',
|
||||
SetCurrentLoadingSwitch = 'SET_CURRENT_LOADING_SWITCH',
|
||||
SetModalType = 'SET_MODAL_TYPE',
|
||||
SetIsSuccess = 'SET_IS_SUCCESS',
|
||||
ResetState = 'RESET_STATE',
|
||||
}
|
||||
|
||||
interface Action {
|
||||
@ -34,7 +32,6 @@ interface State {
|
||||
deviceBiometricCapable: boolean;
|
||||
currentLoadingSwitch: string | null;
|
||||
modalType: keyof typeof MODAL_TYPES;
|
||||
isSuccess: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
@ -43,7 +40,6 @@ const initialState: State = {
|
||||
deviceBiometricCapable: false,
|
||||
currentLoadingSwitch: null,
|
||||
modalType: MODAL_TYPES.ENTER_PASSWORD,
|
||||
isSuccess: false,
|
||||
};
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
@ -58,10 +54,6 @@ const reducer = (state: State, action: Action): State => {
|
||||
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;
|
||||
}
|
||||
@ -97,7 +89,6 @@ const EncryptStorage = () => {
|
||||
}, [initializeState]);
|
||||
|
||||
const handleDecryptStorage = async () => {
|
||||
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'decrypt' });
|
||||
dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.ENTER_PASSWORD });
|
||||
promptRef.current?.present();
|
||||
};
|
||||
@ -158,10 +149,6 @@ const EncryptStorage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const onModalDismiss = () => {
|
||||
initializeState(); // Reinitialize state on modal dismiss to refresh the UI
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={[styles.root, styleHooks.root]}
|
||||
@ -222,6 +209,7 @@ const EncryptStorage = () => {
|
||||
try {
|
||||
await encryptStorage(password);
|
||||
await saveToDisk();
|
||||
dispatch({ type: ActionType.SetModalType, payload: MODAL_TYPES.SUCCESS });
|
||||
success = true;
|
||||
} catch (error) {
|
||||
presentAlert({ title: loc.errors.error, message: (error as Error).message });
|
||||
@ -233,17 +221,18 @@ const EncryptStorage = () => {
|
||||
await saveToDisk();
|
||||
success = true;
|
||||
} catch (error) {
|
||||
presentAlert({ message: loc._.bad_password });
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
dispatch({ type: ActionType.SetLoading, payload: false });
|
||||
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
|
||||
initializeState();
|
||||
return success;
|
||||
}}
|
||||
onConfirmationFailure={() => {
|
||||
dispatch({ type: ActionType.SetLoading, payload: false });
|
||||
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
|
||||
}}
|
||||
onDismiss={onModalDismiss}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
sup,
|
||||
yo,
|
||||
} from './helperz';
|
||||
import { element } from 'detox';
|
||||
|
||||
/**
|
||||
* this testsuite is for test cases that require no wallets to be present
|
||||
@ -248,19 +249,19 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// lets encrypt the storage.
|
||||
// first, trying to mistype second password:
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol. lets tap it
|
||||
await element(by.type('android.widget.EditText')).typeText('08902');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('666');
|
||||
await element(by.text('OK')).tap();
|
||||
await expect(element(by.text('Passwords do not match.'))).toBeVisible();
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.id('PasswordInput')).typeText('08902');
|
||||
await element(by.id('PasswordInput')).tapReturnKey();
|
||||
await element(by.id('ConfirmPasswordInput')).typeText('666');
|
||||
await element(by.id('ConfirmPasswordInput')).tapReturnKey();
|
||||
await element(by.id('OKButton')).tap();
|
||||
|
||||
// now, lets put correct passwords and encrypt the storage
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
||||
await element(by.type('android.widget.EditText')).typeText('qqq');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('qqq');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.id('PasswordInput')).clearText();
|
||||
await element(by.id('PasswordInput')).typeText('qqq');
|
||||
await element(by.id('PasswordInput')).tapReturnKey();
|
||||
await element(by.id('ConfirmPasswordInput')).typeText('qqq');
|
||||
await element(by.id('ConfirmPasswordInput')).tapReturnKey();
|
||||
await element(by.id('OKButton')).tap();
|
||||
|
||||
// relaunch app
|
||||
await device.launchApp({ newInstance: true });
|
||||
@ -391,12 +392,16 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// lets encrypt the storage.
|
||||
// lets put correct passwords and encrypt the storage
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
||||
await element(by.type('android.widget.EditText')).typeText('pass');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('pass');
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
await element(by.id('PasswordInput')).clearText();
|
||||
await element(by.id('PasswordInput')).typeText('pass');
|
||||
await element(by.id('PasswordInput')).tapReturnKey();
|
||||
await element(by.id('ConfirmPasswordInput')).typeText('pass');
|
||||
await element(by.id('ConfirmPasswordInput')).tapReturnKey();
|
||||
await element(by.id('OKButton')).tap();
|
||||
await element(by.id('PlausibleDeniabilityButton')).tap();
|
||||
|
||||
|
||||
// trying to enable plausible denability
|
||||
await element(by.id('CreateFakeStorageButton')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('fake');
|
||||
@ -418,8 +423,9 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
.withTimeout(33000);
|
||||
//
|
||||
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
|
||||
await element(by.type('android.widget.EditText')).typeText('pass');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.id('PasswordInput')).typeText('pass');
|
||||
await element(by.id('PasswordInput')).tapReturnKey();
|
||||
await element(by.id('OKButton')).tap();
|
||||
await yo('WalletsList');
|
||||
|
||||
// previously created wallet IN MAIN STORAGE should be visible
|
||||
@ -432,16 +438,13 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// putting FAKE storage password. should not succeed
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('fake');
|
||||
await element(by.text('OK')).tap();
|
||||
await expect(element(by.text('Incorrect password. Please try again.'))).toBeVisible();
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
await element(by.id('PasswordInput')).typeText('fake');
|
||||
await element(by.id('PasswordInput')).tapReturnKey();
|
||||
await element(by.id('OKButton')).tap();
|
||||
// correct password
|
||||
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText('pass');
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.ic('PasswordInput')).typeText('pass');
|
||||
await element(by.ic('PasswordInput')).tapReturnKey();
|
||||
await element(by.text('OKButton')).tap();
|
||||
|
||||
// relaunch app
|
||||
await device.launchApp({ newInstance: true });
|
||||
|
Loading…
Reference in New Issue
Block a user