added feedback for screenreaders

moved settings state back to original

fixed status updates

updated message for more clarity

renamed variables for clarity

added comment

fixed variables

fixed variable again

decreased delay between feedbacks

updated comment
This commit is contained in:
Alie Langston
2020-09-16 09:16:30 -04:00
parent 917152df22
commit 1d01abc7da
2 changed files with 157 additions and 6 deletions

View File

@@ -20,14 +20,16 @@ class Camera extends React.Component {
videoHasLoaded: false,
shouldDetect: false,
isFinishedLoadingDetection: true,
shouldGiveFeedback: true,
feedback: '',
};
}
componentDidMount() {
this.cameraPhoto = new CameraPhoto(this.videoRef.current);
this.cameraPhoto.startCamera(
this.props.isPortrait ? FACING_MODES.USER : FACING_MODES.ENVIRONMENT,
{ width: 640, height: 480 }
this.props.isPortrait ? FACING_MODES.USER : FACING_MODES.ENVIRONMENT,
{ width: 640, height: 480 },
);
}
@@ -97,11 +99,14 @@ class Camera extends React.Component {
const x = features[j][0];
const y = features[j][1];
let isInRange;
if (this.props.isPortrait) {
isInPosition = isInPosition && this.isInRangeForPortrait(x, y);
isInRange = this.isInRangeForPortrait(x, y);
} else {
isInPosition = isInPosition && this.isInRangeForID(x, y);
isInRange = this.isInRangeForID(x, y);
}
// if it is not in range, give feedback depending on which feature is out of range
isInPosition = isInPosition && isInRange;
}
// draw a box depending on if all landmarks are in position
@@ -109,11 +114,81 @@ class Camera extends React.Component {
canvasContext.strokeStyle = '#00ffff';
canvasContext.lineWidth = 6;
canvasContext.strokeRect(start[0], start[1], size[0], size[1]);
// give positive feedback here if user is in correct position
this.giveFeedback(predictions.length, [], true);
} else {
canvasContext.fillStyle = 'rgba(255, 51, 0, 0.75)';
canvasContext.fillRect(start[0], start[1], size[0], size[1]);
this.giveFeedback(predictions.length, features[0], false);
}
});
if (predictions.length === 0) {
this.giveFeedback(predictions.length, [], false);
}
}
giveFeedback(numFaces, rightEye, isCorrect) {
if (this.state.shouldGiveFeedback) {
const currentFeedback = this.state.feedback;
let newFeedback = '';
if (numFaces === 1) {
// only give feedback if one face is detected otherwise
// it would be difficult to tell a user which face to move
if (isCorrect) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.correct']);
} else {
// give feedback based on where user is
newFeedback = this.props.intl.formatMessage(messages[this.getGridPosition(rightEye)]);
}
} else if (numFaces > 1) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.two.faces']);
} else {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.no.faces']);
}
if (currentFeedback !== newFeedback) {
// only update status if it is different, so we don't overload the user with status updates
this.setState({ feedback: newFeedback });
}
// turn off feedback for one to ensure that instructions aren't disruptive/interrupting
this.setState({ shouldGiveFeedback: false });
setTimeout(() => {
this.setState({ shouldGiveFeedback: true });
}, 1000);
}
}
getGridPosition(coordinates) {
// Used to determine where a face is (i.e. top-left, center-right, bottom-center, etc.)
const x = coordinates[0];
const y = coordinates[1];
let messageBase = 'id.verification.photo.feedback';
const heightUpperLimit = 320;
const heightMiddleLimit = 160;
if (y < heightMiddleLimit) {
messageBase += '.top';
} else if (y < heightUpperLimit && y >= heightMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.bottom';
}
const widthRightLimit = 213;
const widthMiddleLimit = 427;
if (x < widthRightLimit) {
messageBase += '.right';
} else if (x >= widthRightLimit && x < widthMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.left';
}
return messageBase;
}
isInRangeForPortrait(x, y) {
@@ -207,16 +282,32 @@ class Camera extends React.Component {
autoPlay
className="camera-video"
onLoadedData={() => { this.setVideoHasLoaded(); }}
style={{ display: this.state.dataUri ? 'none' : 'block' }}
style={{
display: this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
playsInline
/>
<canvas ref={this.canvasRef} data-testid="detection-canvas" className="canvas-video" style={{ display: !this.state.shouldDetect || this.state.dataUri ? 'none' : 'block' }} width="640" height="480" />
<canvas
ref={this.canvasRef}
data-testid="detection-canvas"
className="canvas-video"
style={{
display: !this.state.shouldDetect || this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
width="640"
height="480"
/>
<img
alt="imgCamera"
src={this.state.dataUri}
className="camera-video"
style={{ display: this.state.dataUri ? 'block' : 'none' }}
/>
<div role="status" className="sr-only">{this.state.feedback}</div>
</div>
<button
className={`btn camera-btn ${

View File

@@ -96,6 +96,66 @@ const messages = defineMessages({
defaultMessage: 'If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.',
description: 'Help text that appears for enabling face detection on the portrait photo panel.',
},
'id.verification.photo.feedback.correct': {
id: 'id.verification.photo.feedback.correct',
defaultMessage: 'Face is in a good position.',
description: 'Text for screen reader when user\'s face is in a good position.',
},
'id.verification.photo.feedback.two.faces': {
id: 'id.verification.photo.feedback.two.faces',
defaultMessage: 'More than one face detected.',
description: 'Text for screen reader when more than one face detected.',
},
'id.verification.photo.feedback.no.faces': {
id: 'id.verification.photo.feedback.no.faces',
defaultMessage: 'No face detected.',
description: 'Text for screen reader when no face detected.',
},
'id.verification.photo.feedback.top.left': {
id: 'id.verification.photo.feedback.top.left',
defaultMessage: 'Incorrect position. Top left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.center': {
id: 'id.verification.photo.feedback.top.center',
defaultMessage: 'Incorrect position. Top center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.right': {
id: 'id.verification.photo.feedback.top.right',
defaultMessage: 'Incorrect position. Top right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.left': {
id: 'id.verification.photo.feedback.center.left',
defaultMessage: 'Incorrect position. Center left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.center': {
id: 'id.verification.photo.feedback.center.center',
defaultMessage: 'Incorrect position. Too close to camera.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.right': {
id: 'id.verification.photo.feedback.center.right',
defaultMessage: 'Incorrect position. Center right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.left': {
id: 'id.verification.photo.feedback.bottom.left',
defaultMessage: 'Incorrect position. Bottom left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.center': {
id: 'id.verification.photo.feedback.bottom.center',
defaultMessage: 'Incorrect position. Bottom center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.right': {
id: 'id.verification.photo.feedback.bottom.right',
defaultMessage: 'Incorrect position. Bottom right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.camera.access.title': {
id: 'id.verification.camera.access.title',
defaultMessage: 'Camera Permissions',