Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Commit

Permalink
Update Android Biometric UI (#931)
Browse files Browse the repository at this point in the history
* Mobile: Fix hardware back button press on transferConfirmation modal

* Mobile: Fix snapshot transition text cutoff

* Mobile: Align text input on delete account

* Mobile: Fix account info alert colour

* Mobile: Update Android biometric UI

* Mobile: Remove unused string

* Mobile: Add other fingerprint errors
  • Loading branch information
cvarley100 committed Jan 17, 2019
1 parent 71a9bad commit 001604f
Show file tree
Hide file tree
Showing 6 changed files with 2,138 additions and 96 deletions.
17 changes: 6 additions & 11 deletions src/mobile/src/ui/components/EnterPassword.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ class EnterPassword extends Component {
}
}

componentWillUnmount() {
if (isAndroid) {
FingerprintScanner.release();
}
}

/**
* Wrapper method for onLoginPress prop method
*
Expand All @@ -93,13 +87,10 @@ class EnterPassword extends Component {
activateFingerprintScanner() {
const { t } = this.props;
if (isAndroid) {
this.showModal();
return this.showModal();
}
FingerprintScanner.authenticate({ description: t('fingerprintSetup:instructionsLogin') })
.then(() => {
if (isAndroid) {
this.hideModal();
}
this.props.setUserActive();
})
.catch(() => {
Expand All @@ -118,9 +109,13 @@ class EnterPassword extends Component {
showModal() {
const { theme } = this.props;
this.props.toggleModalActivity('fingerprint', {
hideModal: () => this.props.toggleModalActivity(),
onBackButtonPress: () => this.props.toggleModalActivity(),
theme,
instance: 'login',
onSuccess: () => {
this.hideModal();
this.props.setUserActive();
},
});
}

Expand Down
134 changes: 102 additions & 32 deletions src/mobile/src/ui/components/FingerprintModal.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,84 @@
import React, { PureComponent } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Text, View, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import { withNamespaces } from 'react-i18next';
import { Styling } from 'ui/theme/general';
import Fonts from 'ui/theme/fonts';
import { leaveNavigationBreadcrumb } from 'libs/bugsnag';

import { width, height } from 'libs/dimensions';
import { connect } from 'react-redux';
import FingerprintScanner from 'react-native-fingerprint-scanner';
import { Icon } from 'ui/theme/icons';
import { height, width } from 'libs/dimensions';

const styles = StyleSheet.create({
modalContent: {
container: {
flex: 1,
alignItems: 'center',
borderRadius: Styling.borderRadius,
borderWidth: 2,
paddingVertical: height / 18,
paddingHorizontal: width / 10,
justifyContent: 'flex-end',
height,
width,
},
modalContent: {
width,
justifyContent: 'center',
alignItems: 'flex-start',
backgroundColor: '#ffffff',
paddingHorizontal: 24,
},
heading: {
color: '#212122',
fontSize: 20,
marginTop: 24,
},
modalText: {
fontFamily: Fonts.secondary,
fontSize: Styling.fontSize4,
textAlign: 'center',
backgroundColor: 'transparent',
footer: {
color: '#2e8277',
fontSize: 19,
},
info: {
fontSize: 17,
marginTop: 16,
},
});

export class FingerprintModal extends PureComponent {
class FingerprintModal extends Component {
static propTypes = {
/** @ignore */
t: PropTypes.func.isRequired,
/** Hide active modal */
hideModal: PropTypes.func.isRequired,
onBackButtonPress: PropTypes.func.isRequired,
/** Determines in which instance the modal is being used*/
instance: PropTypes.string.isRequired,
/** @ignore */
isFingerprintEnabled: PropTypes.bool.isRequired,
isFingerprintEnabled: PropTypes.bool,
/** Triggered on fingerprint success */
onSuccess: PropTypes.func.isRequired,
/** @ignore */
theme: PropTypes.object.isRequired,
minimised: PropTypes.bool.isRequired,
};

componentDidMount() {
constructor(props) {
super(props);
leaveNavigationBreadcrumb('FingerprintModal');
this.props.hideModal = this.props.hideModal.bind(this);
this.props.onBackButtonPress = this.props.onBackButtonPress.bind(this);
this.state = { error: undefined };
}

componentDidMount() {
FingerprintScanner.authenticate({ onAttempt: this.handleAuthenticationAttempted })
.then(() => {
this.props.onSuccess();
})
.catch((error) => {
this.setState({ error: error.name });
});
}

componentWillReceiveProps(newProps) {
if (this.props.minimised && !newProps.minimised) {
this.props.onBackButtonPress();
}
}

componentWillUnmount() {
FingerprintScanner.release();
}

getText() {
Expand All @@ -64,20 +102,52 @@ export class FingerprintModal extends PureComponent {
return modalText;
}

render() {
const { theme: { body } } = this.props;
handleAuthenticationAttempted = (error) => {
this.setState({ error: error.name });
};

render() {
const { error } = this.state;
const { t, onBackButtonPress } = this.props;
const errors = {
AuthenticationNotMatch: t('fingerprintSetup:fingerprintAuthFailed'),
AuthenticationFailed: t('fingerprintSetup:fingerprintAuthFailed'),
FingerprintScannerNotAvailable: t('fingerprintSetup:fingerprintUnavailable'),
FingerprintScannerNotEnrolled: t('fingerprintSetup:fingerprintUnavailableExplanation'),
FingerprintScannerNotSupported: t('fingerprintSetup:fingerprintUnavailableExplanation'),
DeviceLocked: t('fingerprintSetup:deviceLocked'),
UserCancel: t('fingerprintSetup:fingerprintAuthFailed'),
UserFallback: t('fingerprintSetup:fingerprintAuthFailed'),
SystemCancel: t('fingerprintSetup:fingerprintAuthFailed'),
PasscodeNotSet: t('fingerprintSetup:fingerprintUnavailableExplanation'),
};
return (
<TouchableOpacity
style={[{ width: Styling.contentWidth, alignItems: 'center' }, { backgroundColor: body.bg }]}
onPress={this.props.hideModal}
>
<View style={[styles.modalContent, { borderColor: body.color }]}>
<Text style={[styles.modalText, { color: body.color }]}>{this.getText()}</Text>
<View style={styles.container}>
<View style={styles.modalContent}>
<Text style={styles.heading}>{this.getText()}</Text>
<View style={{ width: width - 48, alignItems: 'center', marginTop: 48, marginBottom: 24 }}>
<Icon
name={(error && 'info') || 'fingerprint'}
size={64}
color={(error && '#F4511E') || '#2e8277'}
/>
<Text style={[styles.info, { color: (error && '#F4511E') || '#2e8277' }]}>
{errors[error] || t('touchSensor')}
</Text>
</View>
<TouchableWithoutFeedback onPress={onBackButtonPress}>
<View style={{ width: width - 48, height: 72, justifyContent: 'center' }}>
<Text style={styles.footer}>{t('cancel').toUpperCase()}</Text>
</View>
</TouchableWithoutFeedback>
</View>
</TouchableOpacity>
</View>
);
}
}

export default withNamespaces(['global'])(FingerprintModal);
const mapStateToProps = (state) => ({
minimised: state.ui.minimised,
});

export default withNamespaces(['global'])(connect(mapStateToProps)(FingerprintModal));
78 changes: 37 additions & 41 deletions src/mobile/src/ui/views/wallet/BiometricAuthentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,30 +90,55 @@ class BiometricAuthentication extends Component {
this.navigateToHome = this.navigateToHome.bind(this);
this.onFingerprintPress = this.onFingerprintPress.bind(this);
this.hideModal = this.hideModal.bind(this);
this.onSuccess = this.onSuccess.bind(this);
}

componentDidMount() {
leaveNavigationBreadcrumb('FingerprintEnable');
}

componentWillUnmount() {
if (isAndroid) {
FingerprintScanner.release();
}
}

/**
* Wrapper method for activation/deactivation of fingerprint
*/
onFingerprintPress() {
const { isFingerprintEnabled } = this.props;
if (isAndroid) {
return this.openModal();
}
if (isFingerprintEnabled) {
this.deactivateFingerprintScanner();
} else {
this.activateFingerprintScanner();
}
}

onSuccess() {
const { t, isFingerprintEnabled } = this.props;
if (isAndroid) {
this.hideModal();
}
if (isFingerprintEnabled) {
this.props.setFingerprintStatus(false);
this.timeout = setTimeout(() => {
this.props.generateAlert(
'success',
t('fingerprintAuthDisabled'),
t('fingerprintAuthDisabledExplanation'),
);
}, 300);
return;
}
this.props.setFingerprintStatus(true);
this.timeout = setTimeout(() => {
this.props.generateAlert('success', t('fingerprintAuthEnabled'), t('fingerprintAuthEnabledExplanation'));
}, 300);
}

onFailure() {
const { t } = this.props;
this.props.generateAlert('error', t('fingerprintAuthFailed'), t('fingerprintAuthFailedExplanation'));
}

getButtonInstructions() {
const { t, isFingerprintEnabled } = this.props;
if (isIPhoneX) {
Expand All @@ -125,10 +150,11 @@ class BiometricAuthentication extends Component {
openModal() {
const { theme, isFingerprintEnabled } = this.props;
this.props.toggleModalActivity('fingerprint', {
hideModal: this.hideModal,
onBackButtonPress: this.hideModal,
theme,
isFingerprintEnabled,
instance: 'setup',
onSuccess: this.onSuccess,
});
}

Expand All @@ -137,32 +163,15 @@ class BiometricAuthentication extends Component {

FingerprintScanner.isSensorAvailable()
.then(() => {
if (isAndroid) {
this.openModal();
}
FingerprintScanner.authenticate({
description: t('instructionsEnable'),
onAttempt: this.handleAuthenticationAttempted,
})
.then(() => {
if (isAndroid) {
this.hideModal();
}
this.props.setFingerprintStatus(true);
this.timeout = setTimeout(() => {
this.props.generateAlert(
'success',
t('fingerprintAuthEnabled'),
t('fingerprintAuthEnabledExplanation'),
);
}, 300);
this.onSuccess();
})
.catch(() => {
this.props.generateAlert(
'error',
t('fingerprintAuthFailed'),
t('fingerprintAuthFailedExplanation'),
);
this.onFailure();
});
})
.catch(() => {
Expand All @@ -172,28 +181,15 @@ class BiometricAuthentication extends Component {

deactivateFingerprintScanner() {
const { t } = this.props;
if (isAndroid) {
this.openModal();
}
FingerprintScanner.authenticate({
description: t('instructionsDisable'),
onAttempt: this.handleAuthenticationAttempted,
})
.then(() => {
if (isAndroid) {
this.hideModal();
}
this.props.setFingerprintStatus(false);
this.timeout = setTimeout(() => {
this.props.generateAlert(
'success',
t('fingerprintAuthDisabled'),
t('fingerprintAuthDisabledExplanation'),
);
}, 300);
this.onSuccess();
})
.catch(() => {
this.props.generateAlert('error', t('fingerprintAuthFailed'), t('fingerprintAuthFailedExplanation'));
this.onFailure();
});
}

Expand Down
22 changes: 10 additions & 12 deletions src/mobile/src/ui/views/wallet/Send.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,17 @@ export class Send extends Component {
});
case 'fingerprint':
return this.props.toggleModalActivity(modalContent, {
hideModal: this.hideModal,
borderColor: { borderColor: body.color },
textColor: { color: body.color },
backgroundColor: { backgroundColor: body.bg },
onBackButtonPress: () => {
this.interuptSendAnimation();
this.hideModal();
},
instance: 'send',
theme,
isFingerprintEnabled,
onSuccess: () => {
this.setSendingTransferFlag();
this.hideModal();
this.sendTransfer();
},
});
default:
break;
Expand Down Expand Up @@ -649,20 +653,14 @@ export class Send extends Component {
const { t } = this.props;
if (isAndroid) {
this.props.toggleModalActivity();
this.showModal('fingerprint');
return timer.setTimeout('displayFingerPrintModal', () => this.showModal('fingerprint'), 300);
}
FingerprintScanner.authenticate({ description: t('fingerprintOnSend') })
.then(() => {
this.setSendingTransferFlag();
if (isAndroid) {
this.hideModal();
}
this.sendTransfer();
})
.catch(() => {
if (isAndroid) {
this.hideModal();
}
this.props.generateAlert(
'error',
t('fingerprintSetup:fingerprintAuthFailed'),
Expand Down
Loading

0 comments on commit 001604f

Please sign in to comment.