import * as React from 'react'; import { Flex, Loader, View } from '@aws-amplify/ui-react'; import { IlluminationState, FaceMatchState } from '../service'; import { useLivenessActor, useLivenessSelector, createLivenessSelector, } from '../hooks'; import { Toast } from './Toast'; import { Overlay } from './Overlay'; import { HintDisplayText } from '../displayText'; import { LivenessClassNames } from '../types/classNames'; export const selectErrorState = createLivenessSelector( (state) => state.context.errorState ); export const selectFaceMatchState = createLivenessSelector( (state) => state.context.faceMatchAssociatedParams!.faceMatchState ); export const selectIlluminationState = createLivenessSelector( (state) => state.context.faceMatchAssociatedParams!.illuminationState ); export const selectIsFaceFarEnoughBeforeRecording = createLivenessSelector( (state) => state.context.isFaceFarEnoughBeforeRecording ); export const selectFaceMatchStateBeforeStart = createLivenessSelector( (state) => state.context.faceMatchStateBeforeStart ); export interface HintProps { hintDisplayText: Required; } export const Hint: React.FC = ({ hintDisplayText }) => { const [state] = useLivenessActor(); // NOTE: Do not change order of these selectors as the unit tests depend on this order const errorState = useLivenessSelector(selectErrorState); const faceMatchState = useLivenessSelector(selectFaceMatchState); const illuminationState = useLivenessSelector(selectIlluminationState); const faceMatchStateBeforeStart = useLivenessSelector( selectFaceMatchStateBeforeStart ); const isFaceFarEnoughBeforeRecordingState = useLivenessSelector( selectIsFaceFarEnoughBeforeRecording ); const isCheckFaceDetectedBeforeStart = state.matches( 'checkFaceDetectedBeforeStart' ); const isCheckFaceDistanceBeforeRecording = state.matches( 'checkFaceDistanceBeforeRecording' ); const isRecording = state.matches('recording'); const isNotRecording = state.matches('notRecording'); const isUploading = state.matches('uploading'); const isCheckSuccessful = state.matches('checkSucceeded'); const isCheckFailed = state.matches('checkFailed'); const isFlashingFreshness = state.matches({ recording: 'flashFreshnessColors', }); const FaceMatchStateStringMap: Record = { [FaceMatchState.CANT_IDENTIFY]: hintDisplayText.hintCanNotIdentifyText, [FaceMatchState.FACE_IDENTIFIED]: hintDisplayText.hintTooFarText, [FaceMatchState.TOO_MANY]: hintDisplayText.hintTooManyFacesText, [FaceMatchState.TOO_CLOSE]: hintDisplayText.hintTooCloseText, [FaceMatchState.TOO_FAR]: hintDisplayText.hintTooFarText, [FaceMatchState.MATCHED]: hintDisplayText.hintHoldFaceForFreshnessText, }; const IlluminationStateStringMap: Record = { [IlluminationState.BRIGHT]: hintDisplayText.hintIlluminationTooBrightText, [IlluminationState.DARK]: hintDisplayText.hintIlluminationTooDarkText, [IlluminationState.NORMAL]: hintDisplayText.hintIlluminationNormalText, }; const getInstructionContent = () => { if (errorState || isCheckFailed || isCheckSuccessful) { return; } if (!isRecording) { if (isCheckFaceDetectedBeforeStart) { if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) { return ( {FaceMatchStateStringMap[faceMatchStateBeforeStart]} ); } return {hintDisplayText.hintMoveFaceFrontOfCameraText}; } // Specifically checking for false here because initially the value is undefined and we do not want to show the instruction if ( isCheckFaceDistanceBeforeRecording && isFaceFarEnoughBeforeRecordingState === false ) { return {hintDisplayText.hintTooCloseText}; } if (isNotRecording) { return ( {hintDisplayText.hintConnectingText} ); } if (isUploading) { return ( {hintDisplayText.hintVerifyingText} ); } if (illuminationState && illuminationState !== IlluminationState.NORMAL) { return {IlluminationStateStringMap[illuminationState]}; } } if (isFlashingFreshness) { return ( {hintDisplayText.hintHoldFaceForFreshnessText} ); } if (isRecording && !isFlashingFreshness) { // During face matching, we want to only show the TOO_CLOSE or // TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show // the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY // we are defaulting to the TOO_FAR text (for now). For MATCHED state, // we don't want to show any toasts. return ( {faceMatchState === FaceMatchState.TOO_CLOSE ? FaceMatchStateStringMap[FaceMatchState.TOO_CLOSE] : FaceMatchStateStringMap[FaceMatchState.TOO_FAR]} ); } return null; }; const instructionContent = getInstructionContent(); return instructionContent ? instructionContent : null; };