From 4d3c44089222c688f14b57ddb87a9c00a0744560 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:43:59 -0300 Subject: [PATCH] Chore: Evaluate Passcode - TypeScript (#3931) * Chore: Migrate containers: Passcode to Typescript * minor tweak * minor tweak --- app/containers/Passcode/Base/Button.tsx | 6 +- app/containers/Passcode/Base/Dots.tsx | 70 ++++++++++++---------- app/containers/Passcode/Base/LockIcon.tsx | 19 +++--- app/containers/Passcode/Base/Locked.tsx | 26 ++++---- app/containers/Passcode/Base/Subtitle.tsx | 20 ++++--- app/containers/Passcode/Base/Title.tsx | 20 ++++--- app/containers/Passcode/Base/index.tsx | 54 +++++++++-------- app/containers/Passcode/PasscodeChoose.tsx | 15 ++--- app/containers/Passcode/PasscodeEnter.tsx | 22 +++---- app/containers/Passcode/constants.ts | 12 ++-- app/containers/Passcode/utils.ts | 8 +-- app/views/ChangePasscodeView.tsx | 2 +- app/views/ScreenLockedView.tsx | 5 +- 13 files changed, 147 insertions(+), 132 deletions(-) diff --git a/app/containers/Passcode/Base/Button.tsx b/app/containers/Passcode/Base/Button.tsx index 50a1cf41..26ef8eca 100644 --- a/app/containers/Passcode/Base/Button.tsx +++ b/app/containers/Passcode/Base/Button.tsx @@ -5,16 +5,18 @@ import styles from './styles'; import { themes } from '../../../constants/colors'; import Touch from '../../../utils/touch'; import { CustomIcon } from '../../../lib/Icons'; +import { useTheme } from '../../../theme'; interface IPasscodeButton { text?: string; icon?: string; - theme: string; disabled?: boolean; onPress?: Function; } -const Button = React.memo(({ text, disabled, theme, onPress, icon }: IPasscodeButton) => { +const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => { + const { theme } = useTheme(); + const press = () => onPress && onPress(text); return ( diff --git a/app/containers/Passcode/Base/Dots.tsx b/app/containers/Passcode/Base/Dots.tsx index 75ee4bc7..25e41d6c 100644 --- a/app/containers/Passcode/Base/Dots.tsx +++ b/app/containers/Passcode/Base/Dots.tsx @@ -4,47 +4,51 @@ import range from 'lodash/range'; import styles from './styles'; import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; const SIZE_EMPTY = 12; const SIZE_FULL = 16; interface IPasscodeDots { passcode: string; - theme: string; length: number; } -const Dots = React.memo(({ passcode, theme, length }: IPasscodeDots) => ( - - {range(length).map(val => { - const lengthSup = passcode.length >= val + 1; - const height = lengthSup ? SIZE_FULL : SIZE_EMPTY; - const width = lengthSup ? SIZE_FULL : SIZE_EMPTY; - let backgroundColor = ''; - if (lengthSup && passcode.length > 0) { - backgroundColor = themes[theme].passcodeDotFull; - } else { - backgroundColor = themes[theme].passcodeDotEmpty; - } - const borderRadius = lengthSup ? SIZE_FULL / 2 : SIZE_EMPTY / 2; - const marginRight = lengthSup ? 10 - (SIZE_FULL - SIZE_EMPTY) / 2 : 10; - const marginLeft = lengthSup ? 10 - (SIZE_FULL - SIZE_EMPTY) / 2 : 10; - return ( - - - - ); - })} - -)); +const Dots = React.memo(({ passcode, length }: IPasscodeDots) => { + const { theme } = useTheme(); + + return ( + + {range(length).map(val => { + const lengthSup = passcode.length >= val + 1; + const height = lengthSup ? SIZE_FULL : SIZE_EMPTY; + const width = lengthSup ? SIZE_FULL : SIZE_EMPTY; + let backgroundColor = ''; + if (lengthSup && passcode.length > 0) { + backgroundColor = themes[theme].passcodeDotFull; + } else { + backgroundColor = themes[theme].passcodeDotEmpty; + } + const borderRadius = lengthSup ? SIZE_FULL / 2 : SIZE_EMPTY / 2; + const marginRight = lengthSup ? 10 - (SIZE_FULL - SIZE_EMPTY) / 2 : 10; + const marginLeft = lengthSup ? 10 - (SIZE_FULL - SIZE_EMPTY) / 2 : 10; + return ( + + + + ); + })} + + ); +}); export default Dots; diff --git a/app/containers/Passcode/Base/LockIcon.tsx b/app/containers/Passcode/Base/LockIcon.tsx index 2015d7cb..ea795e8e 100644 --- a/app/containers/Passcode/Base/LockIcon.tsx +++ b/app/containers/Passcode/Base/LockIcon.tsx @@ -5,13 +5,18 @@ import { Row } from 'react-native-easy-grid'; import styles from './styles'; import { themes } from '../../../constants/colors'; import { CustomIcon } from '../../../lib/Icons'; +import { useTheme } from '../../../theme'; -const LockIcon = React.memo(({ theme }: { theme: string }) => ( - - - - - -)); +const LockIcon = React.memo(() => { + const { theme } = useTheme(); + + return ( + + + + + + ); +}); export default LockIcon; diff --git a/app/containers/Passcode/Base/Locked.tsx b/app/containers/Passcode/Base/Locked.tsx index 7cefeed6..6b9381fd 100644 --- a/app/containers/Passcode/Base/Locked.tsx +++ b/app/containers/Passcode/Base/Locked.tsx @@ -6,36 +6,35 @@ import { resetAttempts } from '../../../utils/localAuthentication'; import { TYPE } from '../constants'; import { getDiff, getLockedUntil } from '../utils'; import I18n from '../../../i18n'; +import { useTheme } from '../../../theme'; import styles from './styles'; import Title from './Title'; import Subtitle from './Subtitle'; import LockIcon from './LockIcon'; interface IPasscodeTimer { - time: string; - theme: string; + time: Date | null; setStatus: Function; } interface IPasscodeLocked { - theme: string; setStatus: Function; } -const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => { +const Timer = React.memo(({ time, setStatus }: IPasscodeTimer) => { const calcTimeLeft = () => { - const diff = getDiff(time); + const diff = getDiff(time || 0); if (diff > 0) { return Math.floor((diff / 1000) % 60); } }; - const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); + const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); useEffect(() => { setTimeout(() => { setTimeLeft(calcTimeLeft()); - if (timeLeft <= 1) { + if (timeLeft && timeLeft <= 1) { resetAttempts(); setStatus(TYPE.ENTER); } @@ -46,11 +45,12 @@ const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => { return null; } - return ; + return ; }); -const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => { - const [lockedUntil, setLockedUntil] = useState(null); +const Locked = React.memo(({ setStatus }: IPasscodeLocked) => { + const [lockedUntil, setLockedUntil] = useState(null); + const { theme } = useTheme(); const readItemFromStorage = async () => { const l = await getLockedUntil(); @@ -63,9 +63,9 @@ const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => { return ( - - - <Timer theme={theme} time={lockedUntil} setStatus={setStatus} /> + <LockIcon /> + <Title text={I18n.t('Passcode_app_locked_title')} /> + <Timer time={lockedUntil} setStatus={setStatus} /> </Grid> ); }); diff --git a/app/containers/Passcode/Base/Subtitle.tsx b/app/containers/Passcode/Base/Subtitle.tsx index 76a5a740..766cfcd7 100644 --- a/app/containers/Passcode/Base/Subtitle.tsx +++ b/app/containers/Passcode/Base/Subtitle.tsx @@ -4,18 +4,22 @@ import { Row } from 'react-native-easy-grid'; import styles from './styles'; import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; interface IPasscodeSubtitle { text: string; - theme: string; } -const Subtitle = React.memo(({ text, theme }: IPasscodeSubtitle) => ( - <Row style={styles.row}> - <View style={styles.subtitleView}> - <Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text> - </View> - </Row> -)); +const Subtitle = React.memo(({ text }: IPasscodeSubtitle) => { + const { theme } = useTheme(); + + return ( + <Row style={styles.row}> + <View style={styles.subtitleView}> + <Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text> + </View> + </Row> + ); +}); export default Subtitle; diff --git a/app/containers/Passcode/Base/Title.tsx b/app/containers/Passcode/Base/Title.tsx index 0c54e035..b66b861a 100644 --- a/app/containers/Passcode/Base/Title.tsx +++ b/app/containers/Passcode/Base/Title.tsx @@ -4,18 +4,22 @@ import { Row } from 'react-native-easy-grid'; import styles from './styles'; import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; interface IPasscodeTitle { text: string; - theme: string; } -const Title = React.memo(({ text, theme }: IPasscodeTitle) => ( - <Row style={styles.row}> - <View style={styles.titleView}> - <Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text> - </View> - </Row> -)); +const Title = React.memo(({ text }: IPasscodeTitle) => { + const { theme } = useTheme(); + + return ( + <Row style={styles.row}> + <View style={styles.titleView}> + <Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text> + </View> + </Row> + ); +}); export default Title; diff --git a/app/containers/Passcode/Base/index.tsx b/app/containers/Passcode/Base/index.tsx index c6591770..3a1bd3f7 100644 --- a/app/containers/Passcode/Base/index.tsx +++ b/app/containers/Passcode/Base/index.tsx @@ -1,6 +1,7 @@ import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; import { Col, Grid, Row } from 'react-native-easy-grid'; import range from 'lodash/range'; +import { View } from 'react-native'; import * as Animatable from 'react-native-animatable'; import * as Haptics from 'expo-haptics'; @@ -10,12 +11,12 @@ import Dots from './Dots'; import { TYPE } from '../constants'; import { themes } from '../../../constants/colors'; import { PASSCODE_LENGTH } from '../../../constants/localAuthentication'; +import { useTheme } from '../../../theme'; import LockIcon from './LockIcon'; import Title from './Title'; import Subtitle from './Subtitle'; interface IPasscodeBase { - theme: string; type: string; previousPasscode?: string; title: string; @@ -26,25 +27,30 @@ interface IPasscodeBase { onBiometryPress?(): void; } -const Base = forwardRef( - ( - { theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }: IPasscodeBase, - ref - ) => { - const rootRef = useRef<any>(); - const dotsRef = useRef<any>(); +export interface IBase { + clearPasscode: () => void; + wrongPasscode: () => void; + animate: (animation: Animatable.Animation, duration?: number) => void; +} + +const Base = forwardRef<IBase, IPasscodeBase>( + ({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => { + const { theme } = useTheme(); + + const rootRef = useRef<Animatable.View & View>(null); + const dotsRef = useRef<Animatable.View & View>(null); const [passcode, setPasscode] = useState(''); const clearPasscode = () => setPasscode(''); const wrongPasscode = () => { clearPasscode(); - dotsRef?.current?.shake(500); + dotsRef?.current?.shake?.(500); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); }; - const animate = (animation: string, duration = 500) => { - rootRef?.current?.[animation](duration); + const animate = (animation: Animatable.Animation, duration = 500) => { + rootRef?.current?.[animation]?.(duration); }; const onPressNumber = (text: string) => @@ -90,48 +96,48 @@ const Base = forwardRef( return ( <Animatable.View ref={rootRef} style={styles.container}> <Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}> - <LockIcon theme={theme} /> - <Title text={title} theme={theme} /> - <Subtitle text={subtitle!} theme={theme} /> + <LockIcon /> + <Title text={title} /> + {subtitle ? <Subtitle text={subtitle} /> : null} <Row style={styles.row}> <Animatable.View ref={dotsRef}> - <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> + <Dots passcode={passcode} length={PASSCODE_LENGTH} /> </Animatable.View> </Row> <Row style={[styles.row, styles.buttonRow]}> - {range(1, 4).map((i: any) => ( + {range(1, 4).map(i => ( <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> + <Button text={i.toString()} onPress={onPressNumber} /> </Col> ))} </Row> <Row style={[styles.row, styles.buttonRow]}> - {range(4, 7).map((i: any) => ( + {range(4, 7).map(i => ( <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> + <Button text={i.toString()} onPress={onPressNumber} /> </Col> ))} </Row> <Row style={[styles.row, styles.buttonRow]}> - {range(7, 10).map((i: any) => ( + {range(7, 10).map(i => ( <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> + <Button text={i.toString()} onPress={onPressNumber} /> </Col> ))} </Row> <Row style={[styles.row, styles.buttonRow]}> {showBiometry ? ( <Col style={styles.colButton}> - <Button icon='fingerprint' theme={theme} onPress={onBiometryPress} /> + <Button icon='fingerprint' onPress={onBiometryPress} /> </Col> ) : ( <Col style={styles.colButton} /> )} <Col style={styles.colButton}> - <Button text='0' theme={theme} onPress={onPressNumber} /> + <Button text='0' onPress={onPressNumber} /> </Col> <Col style={styles.colButton}> - <Button icon='backspace' theme={theme} onPress={onPressDelete} /> + <Button icon='backspace' onPress={onPressDelete} /> </Col> </Row> </Grid> diff --git a/app/containers/Passcode/PasscodeChoose.tsx b/app/containers/Passcode/PasscodeChoose.tsx index a475c3d4..cc21b8bf 100644 --- a/app/containers/Passcode/PasscodeChoose.tsx +++ b/app/containers/Passcode/PasscodeChoose.tsx @@ -2,24 +2,23 @@ import React, { useRef, useState } from 'react'; import * as Haptics from 'expo-haptics'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; -import Base from './Base'; +import Base, { IBase } from './Base'; import { TYPE } from './constants'; import I18n from '../../i18n'; interface IPasscodeChoose { - theme: string; force?: boolean; finishProcess: Function; } -const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose) => { - const chooseRef = useRef<any>(null); - const confirmRef = useRef<any>(null); +const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => { + const chooseRef = useRef<IBase>(null); + const confirmRef = useRef<IBase>(null); const [subtitle, setSubtitle] = useState(null); const [status, setStatus] = useState(TYPE.CHOOSE); - const [previousPasscode, setPreviouPasscode] = useState<any>(null); + const [previousPasscode, setPreviouPasscode] = useState(''); - const firstStep = (p: any) => { + const firstStep = (p: string) => { setTimeout(() => { setStatus(TYPE.CONFIRM); setPreviouPasscode(p); @@ -43,7 +42,6 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose return ( <Base ref={confirmRef} - theme={theme} type={TYPE.CONFIRM} onEndProcess={changePasscode} previousPasscode={previousPasscode} @@ -56,7 +54,6 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose return ( <Base ref={chooseRef} - theme={theme} type={TYPE.CHOOSE} onEndProcess={firstStep} title={I18n.t('Passcode_choose_title')} diff --git a/app/containers/Passcode/PasscodeEnter.tsx b/app/containers/Passcode/PasscodeEnter.tsx index f61bf91a..145a46d2 100644 --- a/app/containers/Passcode/PasscodeEnter.tsx +++ b/app/containers/Passcode/PasscodeEnter.tsx @@ -4,7 +4,7 @@ import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { sha256 } from 'js-sha256'; -import Base from './Base'; +import Base, { IBase } from './Base'; import Locked from './Base/Locked'; import { TYPE } from './constants'; import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication'; @@ -14,18 +14,17 @@ import { useUserPreferences } from '../../lib/userPreferences'; import I18n from '../../i18n'; interface IPasscodePasscodeEnter { - theme: string; hasBiometry: boolean; finishProcess: Function; } -const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeEnter) => { - const ref = useRef(null); - let attempts: any = 0; +const PasscodeEnter = ({ hasBiometry, finishProcess }: IPasscodePasscodeEnter) => { + const ref = useRef<IBase>(null); + let attempts = 0; let lockedUntil: any = false; const [passcode] = useUserPreferences(PASSCODE_KEY); - const [status, setStatus] = useState(null); - const { getItem: getAttempts, setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY); + const [status, setStatus] = useState<TYPE | null>(null); + const { setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY); const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); const biometry = async () => { @@ -45,7 +44,6 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE await resetAttempts(); setStatus(TYPE.ENTER); } else { - attempts = await getAttempts(); setStatus(TYPE.LOCKED); } } else { @@ -58,7 +56,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE readStorage(); }, [status]); - const onEndProcess = (p: any) => { + const onEndProcess = (p: string) => { setTimeout(() => { if (sha256(p) === passcode) { finishProcess(); @@ -69,8 +67,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE setLockedUntil(new Date().toISOString()); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); } else { - // @ts-ignore - ref.current.wrongPasscode(); + ref?.current?.wrongPasscode(); setAttempts(attempts?.toString()); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); } @@ -79,13 +76,12 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE }; if (status === TYPE.LOCKED) { - return <Locked theme={theme} setStatus={setStatus} />; + return <Locked setStatus={setStatus} />; } return ( <Base ref={ref} - theme={theme} type={TYPE.ENTER} title={I18n.t('Passcode_enter_title')} showBiometry={hasBiometry} diff --git a/app/containers/Passcode/constants.ts b/app/containers/Passcode/constants.ts index ae268799..49178cdb 100644 --- a/app/containers/Passcode/constants.ts +++ b/app/containers/Passcode/constants.ts @@ -1,6 +1,6 @@ -export const TYPE: any = { - CHOOSE: 'choose', - CONFIRM: 'confirm', - ENTER: 'enter', - LOCKED: 'locked' -}; +export enum TYPE { + CHOOSE = 'choose', + CONFIRM = 'confirm', + ENTER = 'enter', + LOCKED = 'locked' +} diff --git a/app/containers/Passcode/utils.ts b/app/containers/Passcode/utils.ts index f497e95a..4c6f21a7 100644 --- a/app/containers/Passcode/utils.ts +++ b/app/containers/Passcode/utils.ts @@ -4,11 +4,11 @@ import moment from 'moment'; import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../constants/localAuthentication'; export const getLockedUntil = async () => { - const t: any = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY); + const t = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY); if (t) { - return moment(t).add(TIME_TO_LOCK); + return moment(t).add(TIME_TO_LOCK).toDate(); } return null; }; -// @ts-ignore -export const getDiff = t => new Date(t) - new Date(); + +export const getDiff = (t: string | number | Date) => new Date(t).getTime() - new Date().getTime(); diff --git a/app/views/ChangePasscodeView.tsx b/app/views/ChangePasscodeView.tsx index 5ed58a27..a69d8f55 100644 --- a/app/views/ChangePasscodeView.tsx +++ b/app/views/ChangePasscodeView.tsx @@ -80,7 +80,7 @@ const ChangePasscodeView = React.memo(() => { return ( <Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}> - <PasscodeChoose theme={theme} finishProcess={onSubmit} force={data?.force} /> + <PasscodeChoose finishProcess={onSubmit} force={data?.force} /> {!data?.force ? ( <Touchable onPress={onCancel} style={styles.close}> <CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} /> diff --git a/app/views/ScreenLockedView.tsx b/app/views/ScreenLockedView.tsx index 6e20152b..c6e2efa0 100644 --- a/app/views/ScreenLockedView.tsx +++ b/app/views/ScreenLockedView.tsx @@ -4,7 +4,6 @@ import useDeepCompareEffect from 'use-deep-compare-effect'; import isEmpty from 'lodash/isEmpty'; import Orientation from 'react-native-orientation-locker'; -import { useTheme } from '../theme'; import EventEmitter from '../utils/events'; import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication'; import { isTablet } from '../utils/deviceInfo'; @@ -19,8 +18,6 @@ const ScreenLockedView = (): JSX.Element => { const [visible, setVisible] = useState(false); const [data, setData] = useState<IData>({}); - const { theme } = useTheme(); - useDeepCompareEffect(() => { if (!isEmpty(data)) { setVisible(true); @@ -62,7 +59,7 @@ const ScreenLockedView = (): JSX.Element => { style={{ margin: 0 }} animationIn='fadeIn' animationOut='fadeOut'> - <PasscodeEnter theme={theme} hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} /> + <PasscodeEnter hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} /> </Modal> ); };