Skip to content

Commit

Permalink
chore: rely on be to trigger face auth (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
ice-hades authored Nov 16, 2023
1 parent 036353e commit ed82e5b
Show file tree
Hide file tree
Showing 21 changed files with 178 additions and 200 deletions.
16 changes: 10 additions & 6 deletions src/api/tokenomics/startMiningSession.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// SPDX-License-Identifier: ice License 1.0

import {post} from '@api/client';
import {MiningSummary} from '@api/tokenomics/types';
import {FaceAuthKycNumber, MiningSummary} from '@api/tokenomics/types';

interface Params {
userId: string;
resurrect?: boolean | null;
skipKYCStep?: FaceAuthKycNumber | null;
}

export function startMiningSession({userId, resurrect}: Params) {
return post<{resurrect?: boolean | null}, MiningSummary | null>(
`/tokenomics/${userId}/mining-sessions`,
{resurrect},
);
export function startMiningSession({userId, resurrect, skipKYCStep}: Params) {
return post<
{resurrect?: boolean | null; skipKYCSteps?: number[] | undefined},
MiningSummary | null
>(`/tokenomics/${userId}/mining-sessions`, {
resurrect,
skipKYCSteps: skipKYCStep ? [skipKYCStep] : undefined,
});
}
4 changes: 4 additions & 0 deletions src/api/tokenomics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ export type BalanceHistoryPoint = {
balance: BalanceDiff;
timeSeries?: BalanceHistoryPoint[];
};

export type SELFIE_KYC_STEP = 1;
export type EMOTIONS_KYC_STEP = 2;
export type FaceAuthKycNumber = SELFIE_KYC_STEP | EMOTIONS_KYC_STEP;
5 changes: 5 additions & 0 deletions src/constants/faceRecognition.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// SPDX-License-Identifier: ice License 1.0

import {degreesToRadians} from '@utils/units';
import {VideoQuality} from 'expo-camera';
import {Platform} from 'react-native';

export const FACE_RECOGNITION_PICTURE_SIZE = 224;

export const VIDEO_DURATION_SEC = 5;

export const DEVICE_Y_ALLOWED_ROTATION_RADIANS = degreesToRadians(60);

export const VIDEO_QUALITY =
VideoQuality[Platform.OS === 'ios' ? '720p' : '480p'];
6 changes: 5 additions & 1 deletion src/navigation/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import {BadgeType} from '@api/achievements/types';
import {NotificationDeliveryChannel} from '@api/notifications/types';
import {FaceAuthKycNumber} from '@api/tokenomics/types';
import {Country} from '@constants/countries';
import {commonStyles} from '@constants/styles';
import {ViewMeasurementsResult} from '@ice/react-native';
Expand Down Expand Up @@ -102,7 +103,10 @@ export type MainStackParamList = {
targetCircleSize?: number;
descriptionOffset?: number;
};
FaceRecognition: undefined;
FaceRecognition: {
kycSteps: FaceAuthKycNumber[];
kycStepBlocked?: FaceAuthKycNumber;
};
Staking: undefined;
CreativeIceLibrary: undefined;
ImageView: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function EmotionsSentStep({onGatherMoreEmotions}: Props) {
const onFaceAuthSuccess = () => {
dispatch(TokenomicsActions.START_MINING_SESSION.START.create());
navigation.goBack();
dispatch(FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.create());
};

const onBanned = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: ice License 1.0

import {getVideoDimensionsWithFFmpeg, VideoDimensions} from '@utils/ffmpeg';
import {useCallback, useRef} from 'react';

export function useGetVideoDimensions() {
const videoDimensionsRef = useRef<VideoDimensions | null>(null);

return useCallback(async (videoUri: string) => {
if (!videoDimensionsRef.current) {
videoDimensionsRef.current = await getVideoDimensionsWithFFmpeg(videoUri);
}
return videoDimensionsRef.current;
}, []);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import {AuthEmotion} from '@api/faceRecognition/types';
import {COLORS} from '@constants/colors';
import {VIDEO_DURATION_SEC} from '@constants/faceRecognition';
import {VIDEO_DURATION_SEC, VIDEO_QUALITY} from '@constants/faceRecognition';
import {commonStyles} from '@constants/styles';
import {Header} from '@navigation/components/Header';
import {CameraFeed} from '@screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed';
import {DeviceAngleWarning} from '@screens/FaceRecognitionFlow/components/DeviceAngleWarning';
import {isSmallDevice} from '@screens/FaceRecognitionFlow/constants';
import {EmotionCard} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/EmotionCard';
import {StartButton} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/StartButton';
import {useGetVideoDimensions} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/hooks/useGetVideoDimensions';
import {useIsDeviceAngleAllowed} from '@screens/FaceRecognitionFlow/hooks/useIsDeviceAngleAllowed';
import {useMaxHeightStyle} from '@screens/FaceRecognitionFlow/hooks/useMaxHeightStyle';
import {getPictureCropStartY} from '@screens/FaceRecognitionFlow/utils';
import {dayjs} from '@services/dayjs';
import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions';
import {
Expand All @@ -23,11 +23,10 @@ import {
} from '@store/modules/FaceRecognition/selectors';
import {isEmotionsAuthFinalised} from '@store/modules/FaceRecognition/utils';
import {t} from '@translations/i18n';
import {getVideoDimensionsWithFFmpeg} from '@utils/ffmpeg';
import {Duration} from 'dayjs/plugin/duration';
import {Camera, VideoQuality} from 'expo-camera';
import {Camera} from 'expo-camera';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {BackHandler, Platform, StyleSheet, View} from 'react-native';
import {BackHandler, StyleSheet, View} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import {rem, wait} from 'rn-units';

Expand Down Expand Up @@ -72,6 +71,8 @@ export function GatherEmotionsStep({
null,
);

const getVideoDimensions = useGetVideoDimensions();

useEffect(() => {
if (isAllRecorded && !isVideoRecording && started) {
onAllEmotionsGathered();
Expand Down Expand Up @@ -115,7 +116,7 @@ export function GatherEmotionsStep({
const video = await cameraRef.current
.recordAsync({
maxDuration: 5,
quality: VideoQuality[Platform.OS === 'ios' ? '720p' : '480p'],
quality: VIDEO_QUALITY,
mute: true,
})
.catch(() => {
Expand All @@ -129,19 +130,15 @@ export function GatherEmotionsStep({
if (toAbort) {
return;
}
// You now have the video object which contains the URI to the video file
const {width, height} = await getVideoDimensionsWithFFmpeg(video.uri);
const {width, height} = await getVideoDimensions(video.uri);
if (toAbort) {
return;
}
dispatch(
FaceRecognitionActions.EMOTIONS_AUTH.START.create({
videoUri: video.uri,
cropStartY: getPictureCropStartY({
pictureWidth: width,
pictureHeight: height,
}),
videoWidth: width,
videoHeight: height,
}),
);
}
Expand All @@ -159,6 +156,7 @@ export function GatherEmotionsStep({
session,
isCameraReady,
started,
getVideoDimensions,
]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function PictureSentStep({
navigation.goBack();
};
const onFaceAuthTryLater = () => {
dispatch(FaceRecognitionActions.RESET_FACE_AUTH_STATUS.STATE.create());
navigation.goBack();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {cameraStyles} from '@screens/FaceRecognitionFlow/components/CameraFeed/C
import {FaceAuthOverlay} from '@screens/FaceRecognitionFlow/components/FaceAuthOverlay';
import {isSmallDevice} from '@screens/FaceRecognitionFlow/constants';
import {useMaxHeightStyle} from '@screens/FaceRecognitionFlow/hooks/useMaxHeightStyle';
import {getPictureCropStartY} from '@screens/FaceRecognitionFlow/utils';
import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions';
import {cameraRatioSelector} from '@store/modules/FaceRecognition/selectors';
import {RestartIcon} from '@svg/RestartIcon';
Expand Down Expand Up @@ -37,10 +36,7 @@ export function SendOrRetakeStep({
FaceRecognitionActions.FACE_AUTH.START.create({
pictureUri: picture.uri,
pictureWidth: picture.width,
cropStartY: getPictureCropStartY({
pictureWidth: picture.width,
pictureHeight: picture.height,
}),
pictureHeight: picture.height,
}),
);
onPictureSent();
Expand Down
6 changes: 3 additions & 3 deletions src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {View} from 'react-native';
type FaceAuthPhase = 'TAKE_SELFIE' | 'SEND_OR_RETAKE' | 'SENT';

type Props = {
updateKycStepPassed: () => void;
onFaceAuthSuccess: () => void;
};

export function FaceAuthCameraFeed({updateKycStepPassed}: Props) {
export function FaceAuthCameraFeed({onFaceAuthSuccess}: Props) {
const [faceAuthPhase, setFaceAuthPhase] =
useState<FaceAuthPhase>('TAKE_SELFIE');

Expand Down Expand Up @@ -59,7 +59,7 @@ export function FaceAuthCameraFeed({updateKycStepPassed}: Props) {
<PictureSentStep
picture={faceAuthPicture}
onRetakePicture={onRetakePicture}
onFaceAuthSuccess={updateKycStepPassed}
onFaceAuthSuccess={onFaceAuthSuccess}
/>
) : null}
</View>
Expand Down
87 changes: 34 additions & 53 deletions src/screens/FaceRecognitionFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,65 @@
// SPDX-License-Identifier: ice License 1.0

import {FaceAuthKycNumber} from '@api/tokenomics/types';
import {COLORS} from '@constants/colors';
import {commonStyles} from '@constants/styles';
import {Header} from '@navigation/components/Header';
import {useFocusStatusBar} from '@navigation/hooks/useFocusStatusBar';
import {useNavigation} from '@react-navigation/native';
import {MainStackParamList} from '@navigation/Main';
import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
import {StatusOverlay} from '@screens/FaceRecognitionFlow/components/StatusOverlay';
import {EmotionsAuthCameraFeed} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed';
import {FaceAuthCameraFeed} from '@screens/FaceRecognitionFlow/FaceAuthCameraFeed';
import {FaceAuthUserConsent} from '@screens/FaceRecognitionFlow/FaceAuthUserConsent';
import {dayjs} from '@services/dayjs';
import {unsafeUserSelector} from '@store/modules/Account/selectors';
import {
emotionsAuthStatusSelector,
faceAuthStatusSelector,
} from '@store/modules/FaceRecognition/selectors';
import {TokenomicsActions} from '@store/modules/Tokenomics/actions';
import {t} from '@translations/i18n';
import React, {useEffect, useState} from 'react';
import React, {useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {useSelector} from 'react-redux';
import {useDispatch, useSelector} from 'react-redux';

type FaceRecognitionPhase = 'USER_CONSENT' | 'FACE_AUTH' | 'EMOTIONS_AUTH';

function renderContent({
faceRecognitionPhase,
setFaceRecognitionPhase,
}: {
faceRecognitionPhase: FaceRecognitionPhase;
setFaceRecognitionPhase: (phase: FaceRecognitionPhase) => void;
}) {
switch (faceRecognitionPhase) {
case 'USER_CONSENT': {
return (
<FaceAuthUserConsent
updateKycStepPassed={() => setFaceRecognitionPhase('FACE_AUTH')}
/>
);
}
case 'FACE_AUTH': {
return (
<FaceAuthCameraFeed
updateKycStepPassed={() => setFaceRecognitionPhase('EMOTIONS_AUTH')}
/>
);
}
case 'EMOTIONS_AUTH': {
return <EmotionsAuthCameraFeed />;
}
}
}

function kycStepToFaceRecognitionPhase(kycStepPassed?: number) {
if (!kycStepPassed) {
return 'USER_CONSENT';
}
function kycStepToFaceRecognitionPhase(kycStepPassed: FaceAuthKycNumber) {
switch (kycStepPassed) {
case 0:
case 1:
return 'USER_CONSENT';
default:
return 'EMOTIONS_AUTH';
}
}

export function FaceRecognition() {
const {
params: {kycSteps, kycStepBlocked},
} = useRoute<RouteProp<MainStackParamList, 'FaceRecognition'>>();
useFocusStatusBar({style: 'dark-content'});
const navigation = useNavigation();
const user = useSelector(unsafeUserSelector);
const dispatch = useDispatch();
const faceAuthStatus = useSelector(faceAuthStatusSelector);
const emotionsAuthStatus = useSelector(emotionsAuthStatusSelector);

const isBanned =
faceAuthStatus === 'BANNED' ||
emotionsAuthStatus === 'BANNED' ||
(user.kycStepBlocked && !user.kycStepPassed);
!!kycStepBlocked;

const [faceRecognitionPhase, setFaceRecognitionPhase] =
useState<FaceRecognitionPhase>(() =>
kycStepToFaceRecognitionPhase(user.kycStepPassed),
kycStepToFaceRecognitionPhase(kycSteps[0]),
);

useEffect(() => {
if (user.kycStepPassed === 2) {
const step2Timestamp = user?.repeatableKYCSteps?.['2'];
if (step2Timestamp && dayjs(step2Timestamp).valueOf() < Date.now()) {
setFaceRecognitionPhase(kycStepToFaceRecognitionPhase(1));
}
const step1Timestamp = user?.repeatableKYCSteps?.['1'];
if (step1Timestamp && dayjs(step1Timestamp).valueOf() < Date.now()) {
setFaceRecognitionPhase(kycStepToFaceRecognitionPhase(0));
}
const onFaceAuthSuccess = () => {
if (kycSteps.length > 1) {
setFaceRecognitionPhase('EMOTIONS_AUTH');
} else {
dispatch(TokenomicsActions.START_MINING_SESSION.START.create());
navigation.goBack();
}
}, [user.kycStepPassed, user?.repeatableKYCSteps]);
};

return (
<View style={styles.container}>
Expand All @@ -111,7 +80,19 @@ export function FaceRecognition() {
/>
</View>
) : (
renderContent({faceRecognitionPhase, setFaceRecognitionPhase})
<>
{faceRecognitionPhase === 'USER_CONSENT' ? (
<FaceAuthUserConsent
updateKycStepPassed={() => setFaceRecognitionPhase('FACE_AUTH')}
/>
) : null}
{faceRecognitionPhase === 'FACE_AUTH' ? (
<FaceAuthCameraFeed onFaceAuthSuccess={onFaceAuthSuccess} />
) : null}
{faceRecognitionPhase === 'EMOTIONS_AUTH' ? (
<EmotionsAuthCameraFeed />
) : null}
</>
)}
</View>
);
Expand Down
26 changes: 0 additions & 26 deletions src/screens/FaceRecognitionFlow/utils.ts

This file was deleted.

Loading

0 comments on commit ed82e5b

Please sign in to comment.