From 94fdf44260363d042c86d145a3d4eb5ad01c3273 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 23 Apr 2020 15:28:40 -0300 Subject: [PATCH] Stash --- app/containers/Passcode/Base.js | 122 ++++++++++++++++++++++++ app/containers/Passcode/Locked.js | 94 ++++++++++++++++++ app/containers/Passcode/constants.js | 6 ++ app/containers/Passcode/index.js | 136 ++++++++++++--------------- app/views/ScreenLockedView.js | 3 +- 5 files changed, 285 insertions(+), 76 deletions(-) create mode 100644 app/containers/Passcode/Base.js create mode 100644 app/containers/Passcode/Locked.js create mode 100644 app/containers/Passcode/constants.js diff --git a/app/containers/Passcode/Base.js b/app/containers/Passcode/Base.js new file mode 100644 index 000000000..cb9795148 --- /dev/null +++ b/app/containers/Passcode/Base.js @@ -0,0 +1,122 @@ +import React, { useState, forwardRef, useImperativeHandle } from 'react'; +import { View } from 'react-native'; +import { Col, Row, Grid } from 'react-native-easy-grid'; +import _ from 'lodash'; + +import styles from './styles'; +import Button from './Button'; +import Dots from './Dots'; +import Title from './Title'; +import Subtitle from './Subtitle'; +import { TYPE } from './constants'; + +const PASSCODE_LENGTH = 6; + +const Base = forwardRef(({ + theme, type, onEndProcess, previousPasscode +}, ref) => { + const [passcode, setPasscode] = useState(''); + + const handleEnd = () => { + alert('END') + }; + + const wrongPasscode = () => { + setPasscode(''); + console.log('TODO: wrong animation and vibration'); + }; + + const onPressNumber = text => setPasscode((p) => { + const currentPasscode = p + text; + if (currentPasscode?.length === PASSCODE_LENGTH) { + switch (type) { + case TYPE.CHOOSE: + // if (this.props.validationRegex && this.props.validationRegex.test(currentPasscode)) { + // this.showError(true); + // } else { + // // this.endProcess(currentPasscode); + onEndProcess(currentPasscode); + // } + break; + case TYPE.CONFIRM: + if (currentPasscode !== previousPasscode) { + // this.showError(); + alert('SHOW ERROR'); + } else { + // this.endProcess(currentPasscode); + onEndProcess(currentPasscode); + } + break; + case TYPE.ENTER: + // this.props.endProcess(currentPasscode); + onEndProcess(currentPasscode); + // await delay(300); + break; + default: + break; + } + return ''; + } + return currentPasscode; + }); + + const onPressDelete = () => setPasscode((p) => { + if (p?.length > 0) { + const newPasscode = p.slice(0, -1); + return newPasscode; + } + return ''; + }); + + useImperativeHandle(ref, () => ({ + wrongPasscode + })); + + return ( + + + + + <Subtitle theme={theme} /> + </View> + <View style={styles.flexCirclePasscode}> + <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> + </View> + <Grid style={styles.grid}> + <Row style={styles.row}> + {_.range(1, 4).map(i => ( + <Col key={i} style={styles.colButtonCircle}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={styles.row}> + {_.range(4, 7).map(i => ( + <Col key={i} style={styles.colButtonCircle}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={styles.row}> + {_.range(7, 10).map(i => ( + <Col key={i} style={styles.colButtonCircle}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={styles.row}> + <Col style={styles.colButtonCircle} /> + <Col style={styles.colButtonCircle}> + <Button text='0' theme={theme} onPress={onPressNumber} /> + </Col> + <Col style={styles.colButtonCircle}> + <Button text='X' theme={theme} onPress={onPressDelete} /> + </Col> + </Row> + </Grid> + </View> + </View> + ); +}); + +export default Base; diff --git a/app/containers/Passcode/Locked.js b/app/containers/Passcode/Locked.js new file mode 100644 index 000000000..1f1c01dcd --- /dev/null +++ b/app/containers/Passcode/Locked.js @@ -0,0 +1,94 @@ +import React, { useEffect, useState } from 'react'; +import { + View, StyleSheet, Text +} from 'react-native'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import { useAsyncStorage } from '@react-native-community/async-storage'; + +import sharedStyles from '../../views/Styles'; +import { themes } from '../../constants/colors'; +import { + PASSCODE_KEY, PASSCODE_LENGTH, LOCAL_AUTHENTICATE_EMITTER, LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY +} from '../../constants/localAuthentication'; +import { resetAttempts } from '../../utils/localAuthentication'; +import { TYPE } from './constants'; + +const TIME_TO_LOCK = 10000; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + width: '100%' + }, + title: { + ...sharedStyles.textRegular, + fontSize: 20, + fontWeight: '400', + marginBottom: 10, + textAlign: 'center' + }, + subtitle: { + ...sharedStyles.textRegular, + fontSize: 16, + fontWeight: '400', + textAlign: 'center' + } +}); + +const getLockedUntil = t => moment(t).add(TIME_TO_LOCK); + +const getDiff = t => new Date(t) - new Date(); + +const Timer = ({ time, theme, setStatus }) => { + const calcTimeLeft = () => { + const diff = getDiff(time); + if (diff > 0) { + return Math.floor((diff / 1000) % 60); + } + }; + + const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); + + useEffect(() => { + setTimeout(() => { + setTimeLeft(calcTimeLeft()); + if (timeLeft <= 1) { + resetAttempts(); + setStatus(TYPE.ENTER); + } + }, 1000); + }); + + if (!timeLeft) { + return null; + } + + return ( + <Text style={[styles.subtitle, { color: themes[theme].bodyText }]}>Try again in {timeLeft} seconds</Text> + ); +}; + +const Locked = ({ theme, setStatus }) => { + const [lockedUntil, setLockedUntil] = useState(null); + const { getItem } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); + + const readItemFromStorage = async() => { + const item = await getItem(); + setLockedUntil(getLockedUntil(item)); + }; + + useEffect(() => { + readItemFromStorage(); + }, []); + + return ( + <View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}> + <Text style={[styles.title, { color: themes[theme].titleText }]}>App locked</Text> + <Timer theme={theme} time={lockedUntil} setStatus={setStatus} /> + </View> + ); +}; + +export default Locked; diff --git a/app/containers/Passcode/constants.js b/app/containers/Passcode/constants.js new file mode 100644 index 000000000..d284ee241 --- /dev/null +++ b/app/containers/Passcode/constants.js @@ -0,0 +1,6 @@ +export const TYPE = { + CHOOSE: 'choose', + CONFIRM: 'confirm', + ENTER: 'enter', + LOCKED: 'locked' +}; diff --git a/app/containers/Passcode/index.js b/app/containers/Passcode/index.js index 99caf6bb9..113b03899 100644 --- a/app/containers/Passcode/index.js +++ b/app/containers/Passcode/index.js @@ -1,85 +1,71 @@ -import React, { useState } from 'react'; -import { View } from 'react-native'; -import { Col, Row, Grid } from 'react-native-easy-grid'; -import _ from 'lodash'; +import React, { useEffect, useRef, useState } from 'react'; +import { useAsyncStorage } from '@react-native-community/async-storage'; +import RNUserDefaults from 'rn-user-defaults'; -import styles from './styles'; -import Button from './Button'; -import Dots from './Dots'; -import Title from './Title'; -import Subtitle from './Subtitle'; +import Base from './Base'; +import Locked from './Locked'; +import { TYPE } from './constants'; +import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, PASSCODE_KEY } from '../../constants/localAuthentication'; +console.log('LOCKED_OUT_TIMER_KEY', LOCKED_OUT_TIMER_KEY); -const PASSCODE_LENGTH = 6; +const MAX_ATTEMPTS = 2; -const Passcode = ({ theme }) => { - const [passcode, setPasscode] = useState(''); +const PasscodeEnter = ({ + theme, type, finishProcess +}) => { + const ref = useRef(null); + let attempts = 0; + let isLocked = false; + let passcode = null; + const [status, setStatus] = useState(type); + console.log('status', status); + // const [attempts, setAttempts] = useState(null); + // console.log('PasscodeEnter -> attempts', attempts); + // const [isLocked, setIsLocked] = useState(null); + // console.log('PasscodeEnter -> isLocked', isLocked); + // const [passcode, setPasscode] = useState(''); + // console.log('passcode', passcode); + const { getItem: getAttempts, setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY); + const { getItem: getIsLocked, setItem: setIsLocked } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); - const handleEnd = () => { - alert('END') + const fetchPasscode = async() => { + passcode = await RNUserDefaults.get(PASSCODE_KEY); }; - const onPressNumber = text => setPasscode((p) => { - const newPasscode = p + text; - if (newPasscode?.length === PASSCODE_LENGTH) { - handleEnd(); - return ''; - } - return newPasscode; - }); + const readStorage = async() => { + isLocked = await getIsLocked(); + attempts = await getAttempts(); + fetchPasscode(); + }; - const onPressDelete = () => setPasscode((p) => { - if (p?.length > 0) { - const newPasscode = p.slice(0, -1); - return newPasscode; - } - return ''; - }); + useEffect(() => { + readStorage(); + }, []); - return ( - <View style={styles.container}> - <View style={styles.container}> - <View style={styles.viewTitle}> - <Title theme={theme} /> - <Subtitle theme={theme} /> - </View> - <View style={styles.flexCirclePasscode}> - <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> - </View> - <Grid style={styles.grid}> - <Row style={styles.row}> - {_.range(1, 4).map(i => ( - <Col key={i} style={styles.colButtonCircle}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={styles.row}> - {_.range(4, 7).map(i => ( - <Col key={i} style={styles.colButtonCircle}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={styles.row}> - {_.range(7, 10).map(i => ( - <Col key={i} style={styles.colButtonCircle}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={styles.row}> - <Col style={styles.colButtonCircle} /> - <Col style={styles.colButtonCircle}> - <Button text='0' theme={theme} onPress={onPressNumber} /> - </Col> - <Col style={styles.colButtonCircle}> - <Button text='X' theme={theme} onPress={onPressDelete} /> - </Col> - </Row> - </Grid> - </View> - </View> - ); + const onEndProcess = (p) => { + if (p === passcode) { + finishProcess(); + } else { + attempts += 1; + if (attempts >= MAX_ATTEMPTS) { + setStatus(TYPE.LOCKED); + setIsLocked(new Date().toISOString()); + } else { + ref.current.wrongPasscode(); + setAttempts(attempts?.toString()); + } + } + }; + + finishProcess = () => { + alert('faz submit') + } + + if (status === TYPE.LOCKED) { + return <Locked theme={theme} setStatus={setStatus} />; + } + + return <Base ref={ref} theme={theme} type={TYPE.ENTER} onEndProcess={onEndProcess} />; }; -export default Passcode; +export default PasscodeEnter; diff --git a/app/views/ScreenLockedView.js b/app/views/ScreenLockedView.js index b7b13bc7c..363271136 100644 --- a/app/views/ScreenLockedView.js +++ b/app/views/ScreenLockedView.js @@ -24,6 +24,7 @@ import { resetAttempts } from '../utils/localAuthentication'; import { isTablet } from '../utils/deviceInfo'; import Orientation from 'react-native-orientation-locker'; import Passcode from '../containers/Passcode'; +import { TYPE } from '../containers/Passcode/constants'; const MAX_ATTEMPTS = 6; const TIME_TO_LOCK = 30000; @@ -201,7 +202,7 @@ const ScreenLockedView = ({ theme }) => { pinAttemptsAsyncStorageName={ATTEMPTS_KEY} lockedPage={<AppLocked />} /> */} - <Passcode theme={theme} /> + <Passcode theme={theme} type={TYPE.ENTER} /> </View> </Modal> );