diff --git a/android/app/build.gradle b/android/app/build.gradle index 6371b74d1..78c3bd778 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -178,6 +178,10 @@ dependencies { } androidTestImplementation('com.wix:detox:+') + + // For animated GIF support + implementation('com.facebook.fresco:fresco:2.5.0') + implementation 'com.facebook.fresco:animated-gif:2.5.0' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/android/build.gradle b/android/build.gradle index 7dbb4c4c5..13cb0bb78 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { ext { buildToolsVersion = "33.0.0" - minSdkVersion = 23 + minSdkVersion = 24 compileSdkVersion = 33 targetSdkVersion = 33 kotlinVersion = '1.8.10' @@ -13,6 +13,8 @@ buildscript { ndkVersion = "23.1.7779620" androidXAnnotation = "1.5.0" androidXBrowser = "1.4.0" + // https://github.com/arthenica/ffmpeg-kit/tree/main/react-native + ffmpegKitPackage = "video" } repositories { google() diff --git a/ios/Podfile b/ios/Podfile index 12dbb35ca..f69f1d891 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -49,6 +49,9 @@ target 'ice' do pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications" pod 'ReactNativeMoEngage', :path => '../node_modules/react-native-moengage' + # https://github.com/arthenica/ffmpeg-kit/tree/main/react-native#222-enabling-a-package-on-ios + pod 'ffmpeg-kit-react-native', :subspecs => ['video'], :podspec => '../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec' + pod 'Firebase', :modular_headers => true pod 'FirebaseCore', :modular_headers => true pod 'GoogleUtilities', :modular_headers => true diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a0f7f4b89..c2b95c54b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,7 +15,9 @@ PODS: - ExpoModulesCore - ZXingObjC/OneD - ZXingObjC/PDF417 - - EXCamera (13.4.2): + - EXCamera (13.6.0): + - ExpoModulesCore + - EXFileSystem (15.4.2): - ExpoModulesCore - EXImageLoader (4.3.0): - ExpoModulesCore @@ -30,6 +32,8 @@ PODS: - ExpoModulesCore - GoogleSignIn (~> 7.0) - React-Core + - ExpoKeepAwake (12.3.0): + - ExpoModulesCore - ExpoModulesCore (1.5.7): - RCT-Folly (= 2021.07.22.00) - React-Core @@ -58,6 +62,10 @@ PODS: - FBSDKCoreKit (= 15.0.0) - FBSDKShareKit (15.0.0): - FBSDKCoreKit (= 15.0.0) + - ffmpeg-kit-ios-video (6.0) + - ffmpeg-kit-react-native/video (6.0.0): + - ffmpeg-kit-ios-video (= 6.0) + - React-Core - Firebase (10.11.0): - Firebase/Core (= 10.11.0) - Firebase/AnalyticsWithoutAdIdSupport (10.11.0): @@ -745,7 +753,7 @@ PODS: - React-Core - RNCClipboard (1.11.1): - React-Core - - RNCMaskedView (0.2.8): + - RNCMaskedView (0.2.9): - React-Core - RNDeviceInfo (10.3.0): - React-Core @@ -794,7 +802,7 @@ PODS: - React-Core - RNReactNativeHapticFeedback (1.14.0): - React-Core - - RNReanimated (3.3.0): + - RNReanimated (3.5.4): - DoubleConversion - FBLazyVector - glog @@ -862,13 +870,16 @@ DEPENDENCIES: - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXBarCodeScanner (from `../node_modules/expo-barcode-scanner/ios`) - EXCamera (from `../node_modules/expo-camera/ios`) + - EXFileSystem (from `../node_modules/expo-file-system/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`) - Expo (from `../node_modules/expo`) - ExpoAdapterFBSDKNext (from `../node_modules/react-native-fbsdk-next/ios`) - "ExpoAdapterGoogleSignIn (from `../node_modules/@react-native-google-signin/google-signin/expo/ios`)" + - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) + - ffmpeg-kit-react-native/video (from `../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec`) - Firebase - Firebase/Messaging - FirebaseCore @@ -986,6 +997,7 @@ SPEC REPOS: - FBSDKGamingServicesKit - FBSDKLoginKit - FBSDKShareKit + - ffmpeg-kit-ios-video - Firebase - FirebaseAnalytics - FirebaseAppCheckInterop @@ -1037,6 +1049,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-barcode-scanner/ios" EXCamera: :path: "../node_modules/expo-camera/ios" + EXFileSystem: + :path: "../node_modules/expo-file-system/ios" EXImageLoader: :path: "../node_modules/expo-image-loader/ios" Expo: @@ -1045,12 +1059,16 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-fbsdk-next/ios" ExpoAdapterGoogleSignIn: :path: "../node_modules/@react-native-google-signin/google-signin/expo/ios" + ExpoKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" FBReactNativeSpec: :path: "../node_modules/react-native/React/FBReactNativeSpec" + ffmpeg-kit-react-native: + :podspec: "../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec" glog: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: @@ -1214,11 +1232,13 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 EXBarCodeScanner: 44783bf0be375ab18a838dcfef29dd648397000a - EXCamera: 0fbfa338a3776af2722d626a3437abe33f708aad + EXCamera: 2dc2bd2828bca4e283018a0b5a84aec6639ff0b4 + EXFileSystem: d7f59869885cfeab3ac771e2a8d0f5ed98cd3fdb EXImageLoader: 34b214f9387e98f3c73989f15d8d5b399c9ab3f7 Expo: d5956de70d4076190861f6730b93e75478b6faf8 ExpoAdapterFBSDKNext: cb13ee1823c70383d17a82e7e2385053fa917d01 ExpoAdapterGoogleSignIn: b6e7ca3d25261737d31c274460b503ff1b6cb303 + ExpoKeepAwake: be4cbd52d9b177cde0fd66daa1913afa3161fc1d ExpoModulesCore: f8f3cac222bb0f574c77664d7e05817eaa97434b FBAEMKit: d8312d8451ead46282adc7f3565ffc4965e3a4a7 FBLazyVector: 4cce221dd782d3ff7c4172167bba09d58af67ccb @@ -1228,6 +1248,8 @@ SPEC CHECKSUMS: FBSDKGamingServicesKit: e0766514d64d26a9f846e793c0a44bc4a236723b FBSDKLoginKit: 11995469cd7da16fd4d5245e8d2ec61060479270 FBSDKShareKit: 9501792c3024580809f811a8a549868699d9bd32 + ffmpeg-kit-ios-video: d3a8a535570063b717f88da68f9089e213aa21ba + ffmpeg-kit-react-native: a8aa8b5bed0a53ae573617e790984075bee2c716 Firebase: 31d9575c124839fb5abc0db6d39511cc1dab1b85 FirebaseAnalytics: 6c6bf99e8854475bf1fa342028841be8ecd236da FirebaseAppCheckInterop: 5e12dc623d443dedffcde9c6f3ed41510125d8ef @@ -1314,7 +1336,7 @@ SPEC CHECKSUMS: RNBootSplash: 85f6b879c080e958afdb4c62ee04497b05fd7552 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd - RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a + RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6 RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7 RNFBAnalytics: f13089c0af878ab74fba09f5e873c63e3f9de9c8 RNFBApp: fdbde65110240cd843e644270e974e9b455b0fc1 @@ -1329,7 +1351,7 @@ SPEC CHECKSUMS: RNNotifee: 05692d7bb42b6c718a3906aeb8431c48ff2e8097 RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c - RNReanimated: 9f7068e43b9358a46a688d94a5a3adb258139457 + RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87 RNScreens: 50ffe2fa2342eabb2d0afbe19f7c1af286bc7fb3 RNSentry: 9f0447b3ce13806f544903748de423259ead8552 RNSha256: 80bea5b2e7005f813f6438cb41e573b3d531146c @@ -1345,6 +1367,6 @@ SPEC CHECKSUMS: YogaKit: f782866e155069a2cca2517aafea43200b01fd5a ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 8067a8dad58e9d795400789b2440f9bfc6719bb0 +PODFILE CHECKSUM: 63d2fc0fc5753f2bd77dd80c3aa10b9a91ff85f5 COCOAPODS: 1.12.1 diff --git a/package.json b/package.json index c47554132..7f97aa3c7 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,9 @@ "dayjs": "^1.11.6", "expo": "^49.0.0", "expo-barcode-scanner": "^12.5.3", - "expo-camera": "^13.4.2", + "expo-camera": "^13.6.0", "exponential-backoff": "^3.1.0", + "ffmpeg-kit-react-native": "6.0.0", "getstream": "^8.1.0", "i18n-js": "^4.1.1", "immer": "^9.0.16", @@ -93,7 +94,7 @@ "react-native-pager-view": "^6.2.0", "react-native-permissions": "^3.6.1", "react-native-play-install-referrer": "^1.1.8", - "react-native-reanimated": "^3.3.0", + "react-native-reanimated": "^3.5.4", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.22.1", @@ -162,10 +163,8 @@ "expo": { "autolinking": { "exclude": [ - "expo-keep-awake", "expo-application", "expo-constants", - "expo-file-system", "expo-font" ] } diff --git a/patches/expo-camera+13.6.0.patch b/patches/expo-camera+13.6.0.patch new file mode 100644 index 000000000..21d8c8e4e --- /dev/null +++ b/patches/expo-camera+13.6.0.patch @@ -0,0 +1,74 @@ +Extra safety checks before calling stopRecording + +diff --git a/node_modules/expo-camera/android/src/main/java/expo/modules/camera/CameraViewModule.kt b/node_modules/expo-camera/android/src/main/java/expo/modules/camera/CameraViewModule.kt +index 61240ed..5cf79d2 100644 +--- a/node_modules/expo-camera/android/src/main/java/expo/modules/camera/CameraViewModule.kt ++++ b/node_modules/expo-camera/android/src/main/java/expo/modules/camera/CameraViewModule.kt +@@ -14,6 +14,7 @@ import expo.modules.kotlin.functions.Queues + import expo.modules.kotlin.modules.Module + import expo.modules.kotlin.modules.ModuleDefinition + import java.io.File ++import android.util.Log + + class CameraViewModule : Module() { + override fun definition() = ModuleDefinition { +@@ -97,10 +98,18 @@ class CameraViewModule : Module() { + }.runOnQueue(Queues.MAIN) + + AsyncFunction("stopRecording") { viewTag: Int -> ++ if (viewTag !is Int) { ++ return@AsyncFunction ++ } + val view = findView(viewTag) + +- if (view.cameraView.isCameraOpened) { +- view.cameraView.stopRecording() ++ if (view != null && view.cameraView != null && view.cameraView.isCameraOpened) { ++ try { ++ view.cameraView.stopRecording() ++ } catch (e: Exception) { ++ // Handle or log the exception, depending on your needs ++ Log.e("StopRecording", "Error stopping recording: ${e.message}") ++ } + } + }.runOnQueue(Queues.MAIN) + +diff --git a/node_modules/expo-camera/android/src/main/java/expo/modules/camera/ExpoCameraView.kt b/node_modules/expo-camera/android/src/main/java/expo/modules/camera/ExpoCameraView.kt +index b3591cd..a7c874a 100644 +--- a/node_modules/expo-camera/android/src/main/java/expo/modules/camera/ExpoCameraView.kt ++++ b/node_modules/expo-camera/android/src/main/java/expo/modules/camera/ExpoCameraView.kt +@@ -7,6 +7,7 @@ import android.graphics.SurfaceTexture + import android.net.Uri + import android.os.Bundle + import android.view.View ++import android.util.Log + import com.google.android.cameraview.CameraView + import expo.modules.camera.CameraViewHelper.getCamcorderProfile + import expo.modules.camera.CameraViewHelper.getCorrectCameraRotation +@@ -314,13 +315,19 @@ class ExpoCameraView( + } + } + +- override fun onHostPause() { +- if (!isPaused && cameraView.isCameraOpened) { +- faceDetector?.release() +- isPaused = true +- cameraView.stop() +- } +- } ++ override fun onHostPause() { ++ if (!isPaused && cameraView?.isCameraOpened == true) { ++ try { ++ faceDetector?.release() ++ cameraView?.stop() ++ } catch (e: Exception) { ++ Log.e("onHostPause", "Error occurred: ${e.message}") ++ } finally { ++ isPaused = true ++ } ++ } ++ } ++ + + override fun onHostDestroy() { + faceDetector?.release() diff --git a/src/api/auth/types.ts b/src/api/auth/types.ts index d6b57d1da..1cd12195c 100644 --- a/src/api/auth/types.ts +++ b/src/api/auth/types.ts @@ -1,9 +1,15 @@ // SPDX-License-Identifier: ice License 1.0 +export type FaceAuthConfig = { + enabled: boolean; +}; + export type AuthConfig = | { emailCodeAuthWhiteList: string[]; + 'face-auth': FaceAuthConfig; } | { emailCodeAuthBlackList: string[]; + 'face-auth': FaceAuthConfig; }; diff --git a/src/api/client/index.ts b/src/api/client/index.ts index b52f17462..f0ba75fb2 100644 --- a/src/api/client/index.ts +++ b/src/api/client/index.ts @@ -127,6 +127,14 @@ export const is4xxApiError = (error: unknown) => { ); }; +export const is5xxApiError = (error: unknown) => { + return ( + axios.isAxiosError(error) && + error.response?.status && + error.response.status >= 500 + ); +}; + export const isNetworkError = ( error: unknown, ): error is AxiosError<{ diff --git a/src/api/faceRecognition/deleteFaceAuthData.ts b/src/api/faceRecognition/deleteFaceAuthData.ts new file mode 100644 index 000000000..0f76217c9 --- /dev/null +++ b/src/api/faceRecognition/deleteFaceAuthData.ts @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {del} from '@api/client'; + +export function deleteFaceAuthData(): Promise { + return del('/face-auth/'); +} diff --git a/src/api/faceRecognition/emotionsAuth.ts b/src/api/faceRecognition/emotionsAuth.ts new file mode 100644 index 000000000..8fc0611aa --- /dev/null +++ b/src/api/faceRecognition/emotionsAuth.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {buildFormData, post} from '@api/client'; +import {AuthEmotion} from '@api/faceRecognition/types'; +import {getFilenameFromPath, normalizePictureUri} from '@utils/file'; + +type Response = { + result: boolean; + sessionEnded: boolean; + emotions: AuthEmotion[]; + sessionId: string; +}; + +export function emotionsAuth({ + userId, + sessionId, + pictureUris, +}: { + userId: string; + sessionId?: string | null; + pictureUris: string[]; +}): Promise { + const formData = buildFormData({ + image: pictureUris.map(pictureUri => ({ + name: getFilenameFromPath(pictureUri), + type: 'image/jpeg', + uri: normalizePictureUri(pictureUri), + })), + }); + return post( + `/face-auth/liveness/${userId}/${sessionId}`, + formData, + ); +} diff --git a/src/api/faceRecognition/faceAuth.ts b/src/api/faceRecognition/faceAuth.ts new file mode 100644 index 000000000..6ddc5b37c --- /dev/null +++ b/src/api/faceRecognition/faceAuth.ts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {buildFormData, post} from '@api/client'; +import {getFilenameFromPath, normalizePictureUri} from '@utils/file'; + +export function faceAuth({ + userId, + pictureUri, +}: { + userId: string; + pictureUri: string; +}): Promise { + const formData = buildFormData({ + image: { + name: getFilenameFromPath(pictureUri), + type: 'image/jpeg', + uri: normalizePictureUri(pictureUri), + }, + }); + return post(`/face-auth/primary_photo/${userId}`, formData); +} diff --git a/src/api/faceRecognition/fetchEmotionsForAuth.ts b/src/api/faceRecognition/fetchEmotionsForAuth.ts new file mode 100644 index 000000000..a0e13f7e8 --- /dev/null +++ b/src/api/faceRecognition/fetchEmotionsForAuth.ts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {post} from '@api/client'; +import {AuthEmotion} from '@api/faceRecognition/types'; + +type Response = { + emotions: AuthEmotion[]; + sessionId: string; + sessionExpiredAt: string; +}; + +export function fetchEmotionsForAuth({ + userId, +}: { + userId: string; +}): Promise { + return post(`/face-auth/emotions/${userId}`, null); +} diff --git a/src/api/faceRecognition/index.ts b/src/api/faceRecognition/index.ts new file mode 100644 index 000000000..e70667f0c --- /dev/null +++ b/src/api/faceRecognition/index.ts @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {deleteFaceAuthData} from '@api/faceRecognition/deleteFaceAuthData'; +import {emotionsAuth} from '@api/faceRecognition/emotionsAuth'; +import {faceAuth} from '@api/faceRecognition/faceAuth'; +import {fetchEmotionsForAuth} from '@api/faceRecognition/fetchEmotionsForAuth'; + +export const faceRecognition = Object.freeze({ + fetchEmotionsForAuth, + faceAuth, + emotionsAuth, + deleteFaceAuthData, +}); diff --git a/src/api/faceRecognition/types.ts b/src/api/faceRecognition/types.ts new file mode 100644 index 000000000..076c74edd --- /dev/null +++ b/src/api/faceRecognition/types.ts @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: ice License 1.0 + +export type AuthEmotion = + | 'anger' + | 'contempt' + | 'disgust' + | 'fear' + | 'happiness' + | 'neutral' + | 'sadness' + | 'surprise'; diff --git a/src/api/index.ts b/src/api/index.ts index 72854bbc8..a556c5558 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,6 +3,7 @@ import {achievements} from '@api/achievements'; import {auth} from '@api/auth'; import {devices} from '@api/devices'; +import {faceRecognition} from '@api/faceRecognition'; import {news} from '@api/news'; import {notifications} from '@api/notifications'; import {referrals} from '@api/referrals'; @@ -28,4 +29,5 @@ export const Api = Object.freeze({ time, notifications, auth, + faceRecognition, }); diff --git a/src/api/user/types.ts b/src/api/user/types.ts index e6a4c27f9..7f97a805b 100644 --- a/src/api/user/types.ts +++ b/src/api/user/types.ts @@ -53,6 +53,25 @@ export type User = { skipPhoneNumberValidation?: boolean | null; loginSession?: string; + + /* + 0/undefined (show terms and conditions screen) + 1 (show selfie screen) + 2 (show emotion screens) + */ + kycStepPassed?: number; + + /* + 0/undefined + 1 banned + */ + kycStepBlocked?: number; + + /* + A map of `kycStepPassed` keys and a timestamp as value. + So if now > `repeatableKYCSteps[xxx]` you retry that step + */ + repeatableKYCSteps?: Record; }; export type ReferralType = 'CONTACTS' | 'T1' | 'T2'; diff --git a/src/assets/images/badges/face_auth.png b/src/assets/images/badges/face_auth.png new file mode 100644 index 000000000..2c1756baf Binary files /dev/null and b/src/assets/images/badges/face_auth.png differ diff --git a/src/assets/images/badges/face_auth@2x.png b/src/assets/images/badges/face_auth@2x.png new file mode 100644 index 000000000..86ce5fdb7 Binary files /dev/null and b/src/assets/images/badges/face_auth@2x.png differ diff --git a/src/assets/images/badges/face_auth@3x.png b/src/assets/images/badges/face_auth@3x.png new file mode 100644 index 000000000..b36dd9e22 Binary files /dev/null and b/src/assets/images/badges/face_auth@3x.png differ diff --git a/src/assets/images/emotions/anger.gif b/src/assets/images/emotions/anger.gif new file mode 100644 index 000000000..016cef962 Binary files /dev/null and b/src/assets/images/emotions/anger.gif differ diff --git a/src/assets/images/emotions/anger_preview.jpg b/src/assets/images/emotions/anger_preview.jpg new file mode 100644 index 000000000..0c7457d1e Binary files /dev/null and b/src/assets/images/emotions/anger_preview.jpg differ diff --git a/src/assets/images/emotions/contempt.gif b/src/assets/images/emotions/contempt.gif new file mode 100644 index 000000000..c0abe78cf Binary files /dev/null and b/src/assets/images/emotions/contempt.gif differ diff --git a/src/assets/images/emotions/contempt_preview.jpg b/src/assets/images/emotions/contempt_preview.jpg new file mode 100644 index 000000000..47421570a Binary files /dev/null and b/src/assets/images/emotions/contempt_preview.jpg differ diff --git a/src/assets/images/emotions/disgust.gif b/src/assets/images/emotions/disgust.gif new file mode 100644 index 000000000..a380ddff5 Binary files /dev/null and b/src/assets/images/emotions/disgust.gif differ diff --git a/src/assets/images/emotions/disgust_preview.jpg b/src/assets/images/emotions/disgust_preview.jpg new file mode 100644 index 000000000..1ba68628a Binary files /dev/null and b/src/assets/images/emotions/disgust_preview.jpg differ diff --git a/src/assets/images/emotions/fear.gif b/src/assets/images/emotions/fear.gif new file mode 100644 index 000000000..f78ca3c21 Binary files /dev/null and b/src/assets/images/emotions/fear.gif differ diff --git a/src/assets/images/emotions/fear_preview.jpg b/src/assets/images/emotions/fear_preview.jpg new file mode 100644 index 000000000..9bf53db81 Binary files /dev/null and b/src/assets/images/emotions/fear_preview.jpg differ diff --git a/src/assets/images/emotions/happiness.gif b/src/assets/images/emotions/happiness.gif new file mode 100644 index 000000000..4cfd445ad Binary files /dev/null and b/src/assets/images/emotions/happiness.gif differ diff --git a/src/assets/images/emotions/happiness_preview.jpg b/src/assets/images/emotions/happiness_preview.jpg new file mode 100644 index 000000000..299776909 Binary files /dev/null and b/src/assets/images/emotions/happiness_preview.jpg differ diff --git a/src/assets/images/emotions/neutral.gif b/src/assets/images/emotions/neutral.gif new file mode 100644 index 000000000..a8ae786eb Binary files /dev/null and b/src/assets/images/emotions/neutral.gif differ diff --git a/src/assets/images/emotions/neutral_preview.jpg b/src/assets/images/emotions/neutral_preview.jpg new file mode 100644 index 000000000..d2cfe747b Binary files /dev/null and b/src/assets/images/emotions/neutral_preview.jpg differ diff --git a/src/assets/images/emotions/sadness.gif b/src/assets/images/emotions/sadness.gif new file mode 100644 index 000000000..34d1fa4af Binary files /dev/null and b/src/assets/images/emotions/sadness.gif differ diff --git a/src/assets/images/emotions/sadness_preview.jpg b/src/assets/images/emotions/sadness_preview.jpg new file mode 100644 index 000000000..d45bdbb69 Binary files /dev/null and b/src/assets/images/emotions/sadness_preview.jpg differ diff --git a/src/assets/images/emotions/surprise.gif b/src/assets/images/emotions/surprise.gif new file mode 100644 index 000000000..60294539b Binary files /dev/null and b/src/assets/images/emotions/surprise.gif differ diff --git a/src/assets/images/emotions/surprise_preview.jpg b/src/assets/images/emotions/surprise_preview.jpg new file mode 100644 index 000000000..923111291 Binary files /dev/null and b/src/assets/images/emotions/surprise_preview.jpg differ diff --git a/src/assets/images/index.ts b/src/assets/images/index.ts index 048262478..e05132d85 100644 --- a/src/assets/images/index.ts +++ b/src/assets/images/index.ts @@ -67,6 +67,7 @@ export const Images = { placeholder0: require('./badges/placeholder0.png'), placeholder1: require('./badges/placeholder1.png'), placeholder2: require('./badges/placeholder2.png'), + faceAuth: require('./badges/face_auth.png'), }, phone: { confirmPhoneNumber: require('./phone/confirmPhoneNumber.png'), @@ -138,4 +139,22 @@ export const Images = { badPractices: require('./creative_library/practices_section/bad_practices.png'), }, }, + emotions: { + anger: require('./emotions/anger.gif'), + anger_preview: require('./emotions/anger_preview.jpg'), + contempt: require('./emotions/contempt.gif'), + contempt_preview: require('./emotions/contempt_preview.jpg'), + disgust: require('./emotions/disgust.gif'), + disgust_preview: require('./emotions/disgust_preview.jpg'), + fear: require('./emotions/fear.gif'), + fear_preview: require('./emotions/fear_preview.jpg'), + happiness: require('./emotions/happiness.gif'), + happiness_preview: require('./emotions/happiness_preview.jpg'), + neutral: require('./emotions/neutral.gif'), + neutral_preview: require('./emotions/neutral_preview.jpg'), + sadness: require('./emotions/sadness.gif'), + sadness_preview: require('./emotions/sadness_preview.jpg'), + surprise: require('./emotions/surprise.gif'), + surprise_preview: require('./emotions/surprise_preview.jpg'), + }, } as const; diff --git a/src/assets/svg/FaceAuthIcon.tsx b/src/assets/svg/FaceAuthIcon.tsx new file mode 100644 index 000000000..f6787eef0 --- /dev/null +++ b/src/assets/svg/FaceAuthIcon.tsx @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import * as React from 'react'; +import Svg, {Path, SvgProps} from 'react-native-svg'; +import {rem} from 'rn-units'; + +export const FaceAuthIcon = ({color = COLORS.white, ...props}: SvgProps) => ( + + + +); diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index 9d9fc5513..286992eca 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -34,7 +34,7 @@ export const Avatar = memo( }: AvatarProps) => { const navigation = useNavigation>(); - const imageRef = useRef(null); + const imageRef = useRef(null); const dynamicStyle = useMemo( () => ({ diff --git a/src/components/CheckMark/index.tsx b/src/components/CheckMark/index.tsx index f3f4d6c68..f34d083e2 100644 --- a/src/components/CheckMark/index.tsx +++ b/src/components/CheckMark/index.tsx @@ -8,12 +8,17 @@ import {rem} from 'rn-units'; type Props = { style?: StyleProp; + fill?: string; }; -export const CheckMark = ({style}: Props = {}) => { +export const CheckMark = ({style, fill}: Props = {}) => { return ( - + ); }; diff --git a/src/components/Forms/components/ConfirmCode/index.tsx b/src/components/Forms/components/ConfirmCode/index.tsx index c614d5629..77cadda33 100644 --- a/src/components/Forms/components/ConfirmCode/index.tsx +++ b/src/components/Forms/components/ConfirmCode/index.tsx @@ -67,7 +67,7 @@ const styles = StyleSheet.create({ ...font(16, 26, 'medium', 'secondary', 'center'), }, codeSourceText: { - ...font(16, 26, 'bold', 'codeFieldText', 'center'), + ...font(16, 26, 'bold', 'gunmetalGrey', 'center'), }, codeInput: { marginTop: rem(30), diff --git a/src/components/Inputs/CodeInput/index.tsx b/src/components/Inputs/CodeInput/index.tsx index c1e9efc54..a3ef86c18 100644 --- a/src/components/Inputs/CodeInput/index.tsx +++ b/src/components/Inputs/CodeInput/index.tsx @@ -139,7 +139,7 @@ const styles = StyleSheet.create({ borderColor: COLORS.shamrock, }, cellText: { - ...font(17, 26, 'semibold', 'codeFieldText'), + ...font(17, 26, 'semibold', 'gunmetalGrey'), }, error: { minHeight: ERROR_SECTION_HEIGHT, diff --git a/src/components/Inputs/PhoneNumberInput/components/CountryButton.tsx b/src/components/Inputs/PhoneNumberInput/components/CountryButton.tsx index 4be25cdd7..d3acfb635 100644 --- a/src/components/Inputs/PhoneNumberInput/components/CountryButton.tsx +++ b/src/components/Inputs/PhoneNumberInput/components/CountryButton.tsx @@ -17,7 +17,7 @@ export const CountryButton = ({flag, onPress}: Props) => { {flag} diff --git a/src/components/Inputs/PhoneNumberInput/index.tsx b/src/components/Inputs/PhoneNumberInput/index.tsx index 5da75d2c9..13567f7c1 100644 --- a/src/components/Inputs/PhoneNumberInput/index.tsx +++ b/src/components/Inputs/PhoneNumberInput/index.tsx @@ -89,6 +89,6 @@ const styles = StyleSheet.create({ }, valueText: { alignItems: 'center', - ...font(16, 21, 'medium', 'codeFieldText', 'left'), + ...font(16, 21, 'medium', 'gunmetalGrey', 'left'), }, }); diff --git a/src/components/KeyboardAvoider/index.tsx b/src/components/KeyboardAvoider/index.tsx index c596bfda6..5cfe2fb84 100644 --- a/src/components/KeyboardAvoider/index.tsx +++ b/src/components/KeyboardAvoider/index.tsx @@ -1,11 +1,8 @@ // SPDX-License-Identifier: ice License 1.0 +import {commonStyles} from '@constants/styles'; import React, {ReactNode} from 'react'; -import { - KeyboardAvoidingView, - KeyboardAvoidingViewProps, - StyleSheet, -} from 'react-native'; +import {KeyboardAvoidingView, KeyboardAvoidingViewProps} from 'react-native'; import {isIOS} from 'rn-units'; type Props = { @@ -14,13 +11,9 @@ type Props = { export const KeyboardAvoider = ({children, ...props}: Props) => ( {children} ); - -const styles = StyleSheet.create({ - flex: {flex: 1}, -}); diff --git a/src/components/PullToRefreshContainer/index.tsx b/src/components/PullToRefreshContainer/index.tsx index 48381457d..7cd875e0e 100644 --- a/src/components/PullToRefreshContainer/index.tsx +++ b/src/components/PullToRefreshContainer/index.tsx @@ -3,7 +3,7 @@ import {ActivityIndicatorTheme} from '@components/ActivityIndicator'; import {RefreshIceIcon} from '@components/RefreshControl'; import {hapticFeedback} from '@utils/device'; -import React, {cloneElement, RefObject, useCallback, useMemo} from 'react'; +import React, {cloneElement, useCallback, useMemo} from 'react'; import { FlatListProps, ScrollViewProps, @@ -13,11 +13,13 @@ import { } from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import Animated, { - AnimateProps, + AnimatedProps, + AnimatedRef, Extrapolate, interpolate, runOnJS, scrollTo, + SharedValue, useAnimatedReaction, useAnimatedScrollHandler, useAnimatedStyle, @@ -35,11 +37,11 @@ interface Props { * In theory can be used with any views */ children: React.ReactElement< - AnimateProps> + AnimatedProps> >; theme?: ActivityIndicatorTheme; - onScrollTranslateY?: Animated.SharedValue; - animatedScrollViewRef?: RefObject; + onScrollTranslateY?: SharedValue; + animatedScrollViewRef?: AnimatedRef; } /** diff --git a/src/constants/colors.ts b/src/constants/colors.ts index b046cdf07..1108c200b 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -7,10 +7,14 @@ export const COLORS = { black01opacity: 'rgba(0,0,0,.1)', primary: '#073e91', primaryDark: '#0D265E', + primaryDark09opacity: 'rgba(13,38,94,0.9)', primaryLight: '#1B47C3', + primaryLight05opacity: 'rgba(27, 71, 195, 0.5)', + primaryLight01opacity: 'rgba(27, 71, 195, 0.1)', secondary: '#707489', socialLink: '#3366CC', secondaryLight: '#B6B4BA', + secondaryPale: '#A3A5AA', secondaryFaint: '#E3EBF8', attention: '#FD4E4E', attentionDark: '#F53333', @@ -41,7 +45,7 @@ export const COLORS = { wildSand: '#F5F5F5', primaryButtonGradientStart: '#02337C', primaryButtonGradientEnd: '#0946A1', - codeFieldText: '#0E0E0E', + gunmetalGrey: '#0E0E0E', congressBlue: '#063C8C', catalinaBlue: '#033580', transparent: 'rgba(255, 255, 255, 0)', diff --git a/src/constants/faceRecognition.ts b/src/constants/faceRecognition.ts new file mode 100644 index 000000000..032376a7f --- /dev/null +++ b/src/constants/faceRecognition.ts @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + +export const FACE_RECOGNITION_PICTURE_SIZE = 224; + +export const VIDEO_DURATION_SEC = 5; // 5 seconds diff --git a/src/constants/styles.ts b/src/constants/styles.ts index 3b1a14519..2d80c0ac0 100644 --- a/src/constants/styles.ts +++ b/src/constants/styles.ts @@ -38,5 +38,6 @@ export const MIDDLE_BUTTON_HIT_SLOP = { }; export const windowWidth = Dimensions.get('window').width; +export const windowHeight = Dimensions.get('window').height; export const smallHeightDevice = screenHeight < 680; diff --git a/src/screens/Modals/QRCodeScanner/hooks/useCameraPermissions.tsx b/src/hooks/useCameraPermissions.tsx similarity index 100% rename from src/screens/Modals/QRCodeScanner/hooks/useCameraPermissions.tsx rename to src/hooks/useCameraPermissions.tsx diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index 5c729893c..a953ee9da 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -19,6 +19,7 @@ import { import {NavigatorScreenParams} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; import {CreativeIceLibrary} from '@screens/CreativeIceLibrary'; +import {FaceRecognition} from '@screens/FaceRecognitionFlow'; import {BalanceHistory} from '@screens/HomeFlow/BalanceHistory'; import {Home} from '@screens/HomeFlow/Home'; import { @@ -101,6 +102,7 @@ export type MainStackParamList = { targetCircleSize?: number; descriptionOffset?: number; }; + FaceRecognition: undefined; Staking: undefined; CreativeIceLibrary: undefined; ImageView: { @@ -362,6 +364,7 @@ export function MainNavigator() { options={modalOptions} component={DateSelect} /> + void; }; export const BackButton = ({ containerStyle, color = COLORS.black, + onGoBack, label, allowOnTab = false, }: Props = {}) => { @@ -32,7 +34,10 @@ export const BackButton = ({ return ( { + onGoBack?.(); + navigation.goBack(); + }} hitSlop={buttonHitSlop} style={[styles.container, containerStyle]}> diff --git a/src/navigation/components/Header/index.tsx b/src/navigation/components/Header/index.tsx index 0ac40c483..22e660b02 100644 --- a/src/navigation/components/Header/index.tsx +++ b/src/navigation/components/Header/index.tsx @@ -20,6 +20,7 @@ type Props = { containerStyle?: StyleProp; topMargin?: number; showBackButton?: boolean; + onGoBack?: () => void; }; export const HEADER_HEIGHT = rem(56); @@ -39,6 +40,7 @@ export const Header = memo( containerStyle, topMargin, showBackButton = true, + onGoBack, }: Props) => { const {top: topNotchHeight} = useSafeAreaInsets(); const topInset = topMargin ?? topNotchHeight; @@ -74,6 +76,7 @@ export const Header = memo( containerStyle={styles.backButton} color={color} label={backLabel} + onGoBack={onGoBack} /> )} {!!renderRightButtons && ( diff --git a/src/screens/AuthFlow/ConfirmPhone/components/Description/index.tsx b/src/screens/AuthFlow/ConfirmPhone/components/Description/index.tsx index 627246cf8..bc81d91a4 100644 --- a/src/screens/AuthFlow/ConfirmPhone/components/Description/index.tsx +++ b/src/screens/AuthFlow/ConfirmPhone/components/Description/index.tsx @@ -28,6 +28,6 @@ const styles = StyleSheet.create({ ...font(16, 26, 'medium', 'secondary', 'center'), }, phoneText: { - ...font(16, 26, 'bold', 'codeFieldText', 'center'), + ...font(16, 26, 'bold', 'gunmetalGrey', 'center'), }, }); diff --git a/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/EmotionsSentStep/index.tsx b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/EmotionsSentStep/index.tsx new file mode 100644 index 000000000..4e79a040d --- /dev/null +++ b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/EmotionsSentStep/index.tsx @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {CheckMark} from '@components/CheckMark'; +import {COLORS} from '@constants/colors'; +import {commonStyles} from '@constants/styles'; +import {Header} from '@navigation/components/Header'; +import {useNavigation} from '@react-navigation/native'; +import {StatusOverlay} from '@screens/FaceRecognitionFlow/components/StatusOverlay'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {emotionsAuthStatusSelector} from '@store/modules/FaceRecognition/selectors'; +import {TokenomicsActions} from '@store/modules/Tokenomics/actions'; +import {LogoIcon} from '@svg/LogoIcon'; +import {RestartIcon} from '@svg/RestartIcon'; +import {t} from '@translations/i18n'; +import React, {useCallback, useEffect} from 'react'; +import {BackHandler, StyleSheet, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +type Props = { + onGatherMoreEmotions: () => void; +}; + +export function EmotionsSentStep({onGatherMoreEmotions}: Props) { + const emotionsAuthStatus = useSelector(emotionsAuthStatusSelector); + const dispatch = useDispatch(); + const navigation = useNavigation(); + const onFaceAuthSuccess = () => { + dispatch(TokenomicsActions.START_MINING_SESSION.START.create()); + navigation.goBack(); + }; + + const onBanned = () => { + navigation.goBack(); + }; + + const onTryLater = () => { + navigation.goBack(); + }; + + const onTryAgain = useCallback(() => { + dispatch(FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.create()); + onGatherMoreEmotions(); + }, [dispatch, onGatherMoreEmotions]); + + useEffect(() => { + if (emotionsAuthStatus === 'SESSION_EXPIRED') { + onTryAgain(); + } + }, [emotionsAuthStatus, onTryAgain]); + useEffect(() => { + if (emotionsAuthStatus === 'NEED_MORE_EMOTIONS' || !emotionsAuthStatus) { + onGatherMoreEmotions(); + } + }, [emotionsAuthStatus, onGatherMoreEmotions]); + + const onGoBack = useCallback(() => { + if (emotionsAuthStatus !== 'SUCCESS' && emotionsAuthStatus !== 'BANNED') { + dispatch( + FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.create(), + ); + } + }, [dispatch, emotionsAuthStatus]); + + useEffect(() => { + const backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + () => { + onGoBack(); + return false; + }, + ); + return () => backHandler.remove(); + }, [onGoBack]); + return ( + +
+ {emotionsAuthStatus === 'LOADING' ? ( + + } + /> + ) : null} + {emotionsAuthStatus === 'SUCCESS' ? ( + + } + action={onFaceAuthSuccess} + /> + ) : null} + {emotionsAuthStatus === 'TRY_LATER' ? ( + + ) : null} + {emotionsAuthStatus === 'BANNED' ? ( + + ) : null} + {emotionsAuthStatus === 'FAILED' ? ( + + } + action={onTryAgain} + /> + ) : null} + + ); +} + +const styles = StyleSheet.create({ + checkmarkStyle: { + backgroundColor: COLORS.white, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/EmotionCard/index.tsx b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/EmotionCard/index.tsx new file mode 100644 index 000000000..8ff0e26dc --- /dev/null +++ b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/EmotionCard/index.tsx @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {AuthEmotion} from '@api/faceRecognition/types'; +import {COLORS} from '@constants/colors'; +import {Images} from '@images'; +import {ClockIcon} from '@svg/ClockIcon'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import {Duration} from 'dayjs/plugin/duration'; +import React, {useEffect, useState} from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +type Props = { + emotion: AuthEmotion; + countDownSecs: Duration; + previewTimeInMs: number; +}; + +function getEmotionImage({ + emotion, + showPreview, +}: { + emotion: AuthEmotion; + showPreview: boolean; +}) { + return showPreview + ? Images.emotions[`${emotion}_preview`] + : Images.emotions[emotion]; +} + +function getEmotionTranslation(emotion: AuthEmotion) { + switch (emotion) { + case 'anger': + return t('face_auth.emotions_recognition.emotions.anger'); + case 'contempt': + return t('face_auth.emotions_recognition.emotions.contempt'); + case 'disgust': + return t('face_auth.emotions_recognition.emotions.disgust'); + case 'fear': + return t('face_auth.emotions_recognition.emotions.fear'); + case 'happiness': + return t('face_auth.emotions_recognition.emotions.happiness'); + case 'neutral': + return t('face_auth.emotions_recognition.emotions.neutral'); + case 'sadness': + return t('face_auth.emotions_recognition.emotions.sadness'); + case 'surprise': + return t('face_auth.emotions_recognition.emotions.surprise'); + default: + return ''; + } +} + +export function EmotionCard({emotion, countDownSecs, previewTimeInMs}: Props) { + const [showPreview, setShowPreview] = useState(true); + useEffect(() => { + setShowPreview(true); + setTimeout(() => setShowPreview(false), previewTimeInMs); + }, [emotion, previewTimeInMs]); + + return ( + + + + {showPreview ? null : ( + + )} + + + + + + {`${countDownSecs.seconds()}${t('general.seconds_short')}`} + + + {getEmotionTranslation(emotion)} + + {t('face_auth.emotions_recognition.please_show_this')} + + + + ); +} + +const IMAGE_SIZE = rem(120); + +const styles = StyleSheet.create({ + container: { + marginHorizontal: rem(16), + width: '100%', + borderRadius: rem(16), + backgroundColor: COLORS.white, + paddingVertical: rem(20), + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }, + imageContainer: { + overflow: 'hidden', + borderRadius: rem(20), + marginHorizontal: rem(12), + }, + image: { + width: IMAGE_SIZE, + height: IMAGE_SIZE, + }, + countDownContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + dataContainer: { + justifyContent: 'center', + paddingRight: rem(12), + }, + countDownText: { + marginStart: rem(6), + ...font(14, 18, 'medium', 'secondaryPale'), + }, + emotionText: { + ...font(24, 34, 'bold', 'gunmetalGrey'), + textTransform: 'capitalize', + }, + descriptionText: { + ...font(13, 19, 'medium', 'primaryLight'), + }, +}); diff --git a/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/StartButton/index.tsx b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/StartButton/index.tsx new file mode 100644 index 000000000..3631392df --- /dev/null +++ b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/StartButton/index.tsx @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {BackButtonIcon} from '@svg/BackButtonIcon'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +type Props = { + onPress: () => void; +}; + +const BUTTON_SIZE = rem(64); +const ICON_CONTAINER_SIZE = rem(24); +const ICON_SIZE = rem(12); + +export function StartButton({onPress}: Props) { + return ( + + + {t('face_auth.emotions_recognition.start')} + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + height: BUTTON_SIZE, + borderRadius: BUTTON_SIZE / 2, + backgroundColor: COLORS.white, + paddingHorizontal: rem(68), + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + }, + text: { + ...font(17, 24, 'bold', 'primaryLight', 'center'), + }, + delimiter: { + width: rem(16), + }, + iconContainer: { + width: ICON_CONTAINER_SIZE, + height: ICON_CONTAINER_SIZE, + borderRadius: ICON_CONTAINER_SIZE / 2, + backgroundColor: COLORS.primaryLight, + justifyContent: 'center', + alignItems: 'center', + transform: [{scaleX: -1}], + }, +}); diff --git a/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/index.tsx b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/index.tsx new file mode 100644 index 000000000..1a8032c59 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/index.tsx @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {AuthEmotion} from '@api/faceRecognition/types'; +import {COLORS} from '@constants/colors'; +import {VIDEO_DURATION_SEC} from '@constants/faceRecognition'; +import {commonStyles} from '@constants/styles'; +import {Header} from '@navigation/components/Header'; +import { + CameraFeed, + cameraStyles, +} from '@screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed'; +import {EmotionCard} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/EmotionCard'; +import {StartButton} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep/components/StartButton'; +import {getPictureCropStartY} from '@screens/FaceRecognitionFlow/utils'; +import {dayjs} from '@services/dayjs'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import { + cameraRatioSelector, + emotionsAuthEmotionsSelector, + emotionsAuthNextEmotionIndexSelector, + emotionsAuthSessionExpiredAtSelector, + emotionsAuthSessionSelector, + emotionsAuthStatusSelector, +} 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 React, {useCallback, useEffect, useRef, useState} from 'react'; +import {BackHandler, StyleSheet, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; +import {rem, wait} from 'rn-units'; + +type Props = { + onAllEmotionsGathered: () => void; + onStartPressed: () => void; + started: boolean; +}; + +function getSecondsPassed(since: number) { + const msPassed = Date.now() - since; + return Math.floor(msPassed / 1000); +} + +// Needed for the Camera component. +// If to start recording a new video right after previous one is stopped recording there camera feed behaves wierd on ios +const WAIT_BEFORE_RECORDING_MS = 1000; + +export function GatherEmotionsStep({ + onAllEmotionsGathered, + onStartPressed, + started, +}: Props) { + const cameraRef = useRef(null); + const [isCameraReady, setIsCameraReady] = useState(false); + const emotions = useSelector(emotionsAuthEmotionsSelector); + const session = useSelector(emotionsAuthSessionSelector); + const sessionExpiredAt = useSelector(emotionsAuthSessionExpiredAtSelector); + const emotionsAuthNextEmotionIndex = useSelector( + emotionsAuthNextEmotionIndexSelector, + ); + const [currentVideoCountdown, setCurrentVideoCountdown] = useState( + dayjs.duration(VIDEO_DURATION_SEC, 'seconds'), + ); + const emotionsAuthStatus = useSelector(emotionsAuthStatusSelector); + + const dispatch = useDispatch(); + const isSessionExpired = !!sessionExpiredAt && Date.now() > sessionExpiredAt; + + const [isVideoRecording, setIsVideoRecording] = useState(false); + const [isAllRecorded, setIsAllRecorded] = useState(false); + const [recordingEmotion, setRecordingEmotion] = useState( + null, + ); + + useEffect(() => { + if (isAllRecorded && !isVideoRecording) { + onAllEmotionsGathered(); + } + }, [isAllRecorded, isVideoRecording, onAllEmotionsGathered]); + + useEffect(() => { + if ( + started && + !isSessionExpired && + !!session && + !!emotions[emotionsAuthNextEmotionIndex] && + isCameraReady && + cameraRef.current + ) { + let toAbort = false; + const recordVideo = async () => { + if (cameraRef.current) { + setRecordingEmotion(emotions[emotionsAuthNextEmotionIndex]); + setCurrentVideoCountdown( + dayjs.duration(VIDEO_DURATION_SEC, 'seconds'), + ); + + await wait(WAIT_BEFORE_RECORDING_MS); + if (toAbort) { + return; + } + + const recordingStartTime = Date.now(); + const handle = setInterval(() => { + setCurrentVideoCountdown( + dayjs.duration( + Math.max( + 0, + VIDEO_DURATION_SEC - getSecondsPassed(recordingStartTime), + ), + 'seconds', + ), + ); + }, 1000); + setIsVideoRecording(true); + const video = await cameraRef.current + .recordAsync({ + maxDuration: 5, + quality: VideoQuality['480p'], + mute: true, + }) + .catch(() => { + toAbort = true; + return {uri: ''}; + }) + .finally(() => { + setIsVideoRecording(false); + clearInterval(handle); + }); + if (toAbort) { + return; + } + // You now have the video object which contains the URI to the video file + const {width, height} = await getVideoDimensionsWithFFmpeg(video.uri); + if (toAbort) { + return; + } + dispatch( + FaceRecognitionActions.EMOTIONS_AUTH.START.create({ + videoUri: video.uri, + cropStartY: getPictureCropStartY({ + pictureWidth: width, + pictureHeight: height, + }), + videoWidth: width, + }), + ); + } + }; + recordVideo(); + + return () => { + toAbort = true; + }; + } + }, [ + dispatch, + emotions, + emotionsAuthNextEmotionIndex, + session, + isCameraReady, + isSessionExpired, + started, + ]); + + useEffect(() => { + if ( + (emotions.length && + emotionsAuthNextEmotionIndex >= emotions.length && + emotionsAuthStatus !== 'NEED_MORE_EMOTIONS' && + !isSessionExpired) || + isEmotionsAuthFinalised(emotionsAuthStatus) + ) { + setIsAllRecorded(true); + } + }, [ + emotions, + emotionsAuthNextEmotionIndex, + emotionsAuthStatus, + isSessionExpired, + ]); + + useEffect(() => { + if ( + (!session && !isEmotionsAuthFinalised(emotionsAuthStatus)) || + isSessionExpired || + (emotions.length && + emotionsAuthNextEmotionIndex >= emotions.length && + emotionsAuthStatus === 'NEED_MORE_EMOTIONS') + ) { + dispatch(FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.START.create()); + } + }, [ + dispatch, + emotions.length, + emotionsAuthNextEmotionIndex, + emotionsAuthStatus, + isSessionExpired, + session, + ]); + + const onGoBack = useCallback(() => { + if (isVideoRecording && cameraRef.current) { + cameraRef.current.stopRecording(); + } + }, [isVideoRecording]); + useEffect(() => { + const backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + () => { + onGoBack(); + return false; + }, + ); + return () => backHandler.remove(); + }, [onGoBack]); + + const cameraRatio = useSelector(cameraRatioSelector); + + return ( + +
+ + { + setIsCameraReady(true); + }} + /> + + {!started || !recordingEmotion ? ( + + ) : ( + + )} + + + + ); +} + +const styles = StyleSheet.create({ + bottomContainer: { + position: 'absolute', + bottom: rem(38), + width: '100%', + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/index.tsx b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/index.tsx new file mode 100644 index 000000000..b1b340bfd --- /dev/null +++ b/src/screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/index.tsx @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {commonStyles} from '@constants/styles'; +import {EmotionsSentStep} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/EmotionsSentStep'; +import {GatherEmotionsStep} from '@screens/FaceRecognitionFlow/EmotionsAuthCameraFeed/components/GatherEmotionsStep'; +import {emotionsAuthSessionSelector} from '@store/modules/FaceRecognition/selectors'; +import React, {useCallback, useState} from 'react'; +import {View} from 'react-native'; +import {useSelector} from 'react-redux'; + +type EmotionsAuthPhase = 'GATHER_EMOTIONS' | 'ALL_SENT'; + +export function EmotionsAuthCameraFeed() { + const [phase, setPhase] = useState('GATHER_EMOTIONS'); + const session = useSelector(emotionsAuthSessionSelector); + const [showStart, setShowStart] = useState(!session); + const onAllEmotionsGathered = useCallback(() => { + setPhase('ALL_SENT'); + }, []); + const onStartPressed = useCallback(() => { + setShowStart(false); + }, []); + const onGatherMoreEmotions = useCallback(() => { + setPhase('GATHER_EMOTIONS'); + }, []); + + return ( + + {phase === 'GATHER_EMOTIONS' ? ( + + ) : null} + {phase === 'ALL_SENT' ? ( + + ) : null} + + ); +} diff --git a/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/PictureSentStep/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/PictureSentStep/index.tsx new file mode 100644 index 000000000..72c905361 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/PictureSentStep/index.tsx @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import {useNavigation} from '@react-navigation/native'; +import {cameraStyles} from '@screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed'; +import {StatusOverlay} from '@screens/FaceRecognitionFlow/components/StatusOverlay'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import { + cameraRatioSelector, + faceAuthStatusSelector, +} from '@store/modules/FaceRecognition/selectors'; +import {LogoIcon} from '@svg/LogoIcon'; +import {RestartIcon} from '@svg/RestartIcon'; +import {t} from '@translations/i18n'; +import {CameraCapturedPicture} from 'expo-camera'; +import React from 'react'; +import {Image, StyleSheet, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +type Props = { + picture: CameraCapturedPicture; + onRetakePicture: () => void; + onFaceAuthSuccess: () => void; +}; + +export function PictureSentStep({ + picture, + onRetakePicture, + onFaceAuthSuccess, +}: Props) { + const faceAuthStatus = useSelector(faceAuthStatusSelector); + const dispatch = useDispatch(); + const navigation = useNavigation(); + const onFaceAuthFailure = () => { + dispatch(FaceRecognitionActions.RESET_FACE_AUTH_STATUS.STATE.create()); + onRetakePicture(); + }; + const onFaceAuthBanned = () => { + navigation.goBack(); + }; + const onFaceAuthTryLater = () => { + navigation.goBack(); + }; + + const cameraRatio = useSelector(cameraRatioSelector); + + return ( + + + + {faceAuthStatus === 'LOADING' ? ( + + } + /> + ) : null} + {faceAuthStatus === 'SUCCESS' ? ( + + ) : null} + {faceAuthStatus === 'FAILED' ? ( + + } + action={onFaceAuthFailure} + /> + ) : null} + {faceAuthStatus === 'TRY_LATER' ? ( + + ) : null} + {faceAuthStatus === 'BANNED' ? ( + + ) : null} + + ); +} + +const styles = StyleSheet.create({ + picture: { + ...StyleSheet.absoluteFillObject, + transform: [{scaleX: -1}], + }, + pictureOverlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: COLORS.primaryLight05opacity, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/SendOrRetakeStep/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/SendOrRetakeStep/index.tsx new file mode 100644 index 000000000..b5d798bc3 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/SendOrRetakeStep/index.tsx @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {CheckMark} from '@components/CheckMark'; +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {cameraStyles} from '@screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed'; +import {FaceAuthOverlay} from '@screens/FaceRecognitionFlow/components/FaceAuthOverlay'; +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'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import {CameraCapturedPicture} from 'expo-camera'; +import React from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +type Props = { + picture: CameraCapturedPicture; + onRetakePicture: () => void; + onPictureSent: () => void; +}; + +export function SendOrRetakeStep({ + onRetakePicture, + onPictureSent, + picture, +}: Props) { + const dispatch = useDispatch(); + const sendPicture = () => { + dispatch( + FaceRecognitionActions.FACE_AUTH.START.create({ + pictureUri: picture.uri, + pictureWidth: picture.width, + cropStartY: getPictureCropStartY({ + pictureWidth: picture.width, + pictureHeight: picture.height, + }), + }), + ); + onPictureSent(); + }; + + const cameraRatio = useSelector(cameraRatioSelector); + + return ( + + + + + + + + {t('face_auth.auth_status.failure.action')} + + + + + + {t('face_auth.auth_status.success.action')} + + + + + ); +} + +const styles = StyleSheet.create({ + picture: { + ...StyleSheet.absoluteFillObject, + transform: [{scaleX: -1}], + }, + buttonContainer: { + position: 'absolute', + bottom: rem(40), + paddingHorizontal: rem(20), + flexDirection: 'row', + width: '100%', + alignItems: 'center', + justifyContent: 'space-between', + }, + button: { + height: rem(40), + width: '47%', + borderRadius: rem(12), + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + }, + retakeButton: { + backgroundColor: COLORS.attention, + }, + sendButton: { + backgroundColor: COLORS.shamrock, + }, + text: { + marginStart: rem(8), + ...font(14, 20, 'black', 'white', 'center'), + }, + checkmarkStyle: { + backgroundColor: COLORS.white, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/TakeSelfieStep/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/TakeSelfieStep/index.tsx new file mode 100644 index 000000000..0f10ee771 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/TakeSelfieStep/index.tsx @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {windowWidth} from '@constants/styles'; +import {CameraFeed} from '@screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed'; +import {Camera, CameraCapturedPicture} from 'expo-camera'; +import React, {useRef, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; +import {rem} from 'rn-units'; + +type Props = { + onPictureTaken: ({picture}: {picture: CameraCapturedPicture}) => void; +}; + +export function TakeSelfieStep({onPictureTaken}: Props) { + const cameraRef = useRef(null); + const [isCameraReady, setIsCameraReady] = useState(false); + + const takePicture = async () => { + if (cameraRef.current) { + const facePhoto = await cameraRef.current.takePictureAsync({ + quality: 0.95, + }); + onPictureTaken({ + picture: facePhoto, + }); + } + }; + return ( + + setIsCameraReady(true)} + /> + {isCameraReady ? ( + + ) : null} + + ); +} + +const BUTTON_SIZE = rem(100); + +const styles = StyleSheet.create({ + cameraButton: { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + borderRadius: BUTTON_SIZE / 2, + backgroundColor: COLORS.white, + position: 'absolute', + bottom: rem(40), + left: windowWidth / 2 - BUTTON_SIZE / 2, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/index.tsx new file mode 100644 index 000000000..8d1c9026b --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthCameraFeed/index.tsx @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import {commonStyles} from '@constants/styles'; +import {Header} from '@navigation/components/Header'; +import {PictureSentStep} from '@screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/PictureSentStep'; +import {SendOrRetakeStep} from '@screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/SendOrRetakeStep'; +import {TakeSelfieStep} from '@screens/FaceRecognitionFlow/FaceAuthCameraFeed/components/TakeSelfieStep'; +import {t} from '@translations/i18n'; +import {CameraCapturedPicture} from 'expo-camera'; +import React, {useState} from 'react'; +import {View} from 'react-native'; + +type FaceAuthPhase = 'TAKE_SELFIE' | 'SEND_OR_RETAKE' | 'SENT'; + +type Props = { + updateKycStepPassed: () => void; +}; + +export function FaceAuthCameraFeed({updateKycStepPassed}: Props) { + const [faceAuthPhase, setFaceAuthPhase] = + useState('TAKE_SELFIE'); + + const [faceAuthPicture, setFaceAuthPicture] = + useState(null); + const onPictureTaken = async ({ + picture, + }: { + picture: CameraCapturedPicture; + }) => { + setFaceAuthPicture(picture); + setFaceAuthPhase('SEND_OR_RETAKE'); + }; + const onRetakePicture = async () => { + setFaceAuthPhase('TAKE_SELFIE'); + setFaceAuthPicture(null); + }; + const onPictureSent = async () => { + setFaceAuthPhase('SENT'); + }; + return ( + +
+ {faceAuthPhase === 'TAKE_SELFIE' ? ( + + ) : null} + {faceAuthPhase === 'SEND_OR_RETAKE' && faceAuthPicture ? ( + + ) : null} + {faceAuthPhase === 'SENT' && faceAuthPicture ? ( + + ) : null} + + ); +} diff --git a/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/CountrySelect/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/CountrySelect/index.tsx new file mode 100644 index 000000000..79c65359c --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/CountrySelect/index.tsx @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {User} from '@api/user/types'; +import {COLORS} from '@constants/colors'; +import {Country} from '@constants/countries'; +import {commonStyles} from '@constants/styles'; +import {BUTTON_WIDTH} from '@screens/FaceRecognitionFlow/FaceAuthUserConsent/constants'; +import {PopUpButton} from '@screens/Modals/PopUp/components/PopUpButton'; +import {CountrySelectFeed} from '@screens/Templates/CountrySelectFeed'; +import {AccountActions} from '@store/modules/Account/actions'; +import {unsafeUserSelector} from '@store/modules/Account/selectors'; +import { + isFailedSelector, + isSuccessSelector, +} from '@store/modules/UtilityProcessStatuses/selectors'; +import {FaceAuthIcon} from '@svg/FaceAuthIcon'; +import {t} from '@translations/i18n'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +type Props = { + onContinue: () => void; +}; + +export function CountrySelect({onContinue}: Props) { + const [selectedCountry, setSelectedCountry] = useState(null); + const user = useSelector(unsafeUserSelector); + const [isUpdateSent, setIsUpdateSent] = useState(false); + const dispatch = useDispatch(); + + const isSuccessUpdate = useSelector( + isSuccessSelector.bind(null, AccountActions.UPDATE_ACCOUNT), + ); + const isFailureUpdate = useSelector( + isFailedSelector.bind(null, AccountActions.UPDATE_ACCOUNT), + ); + + useEffect(() => { + if (isUpdateSent) { + if (isSuccessUpdate) { + onContinue(); + } + if (isFailureUpdate) { + setIsUpdateSent(false); + } + } + }, [isFailureUpdate, isSuccessUpdate, isUpdateSent, onContinue]); + + return ( + + { + setSelectedCountry(country); + }} + /> + + setSelectedCountry(null)} + /> + } + style={[selectedCountry ? styles.button : styles.disabledButton]} + onPress={() => { + if (selectedCountry) { + const userInfo: Partial = { + country: selectedCountry.isoCode, + city: user.country === selectedCountry.isoCode ? user.city : '', + }; + dispatch(AccountActions.UPDATE_ACCOUNT.START.create(userInfo)); + } + setIsUpdateSent(true); + }} + /> + + + ); +} + +const styles = StyleSheet.create({ + buttonsContainer: { + paddingTop: rem(12), + paddingBottom: rem(34), + paddingHorizontal: rem(34), + flexDirection: 'row', + justifyContent: 'space-between', + }, + button: { + width: BUTTON_WIDTH, + }, + disabledButton: { + width: BUTTON_WIDTH, + backgroundColor: COLORS.primaryDark, + opacity: 0.5, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/UserConsent/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/UserConsent/index.tsx new file mode 100644 index 000000000..8f70c40c8 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/UserConsent/index.tsx @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {CheckBox} from '@components/CheckBox'; +import {COLORS} from '@constants/colors'; +import {LINKS} from '@constants/links'; +import {SCREEN_SIDE_OFFSET} from '@constants/styles'; +import {Images} from '@images'; +import {navigate} from '@navigation/utils'; +import {useNavigation} from '@react-navigation/native'; +import { + BUTTON_WIDTH, + FOOTER_PADDING_HORIZONTAL, +} from '@screens/FaceRecognitionFlow/FaceAuthUserConsent/constants'; +import {Message} from '@screens/Modals/PopUp/components/Message'; +import {PopUpButton} from '@screens/Modals/PopUp/components/PopUpButton'; +import {FaceAuthIcon} from '@svg/FaceAuthIcon'; +import {replaceString, t, tagRegex} from '@translations/i18n'; +import {openLinkWithInAppBrowser} from '@utils/device'; +import {font} from '@utils/styles'; +import React, {useState} from 'react'; +import {Image, ScrollView, StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +type Props = { + updateKycStepPassed: () => void; +}; + +export function UserConsent({updateKycStepPassed}: Props) { + const navigation = useNavigation(); + const [isAgreeWithTermsAndConditions, setIsAgreeWithTermsAndConditions] = + useState(false); + const onContinue = () => { + navigate({ + name: 'PopUp', + params: { + title: t('face_auth.title'), + message: , + buttons: [ + { + text: t('button.cancel'), + preset: 'outlined', + }, + { + text: t('button.continue'), + onPress: updateKycStepPassed, + }, + ], + dismissOnAndroidHardwareBack: false, + dismissOnOutsideTouch: false, + }, + }); + }; + return ( + + + + + {t('face_auth.title')} + + {replaceString( + t('face_auth.description'), + tagRegex('bold', false), + (match, index) => ( + + {match} + + ), + )} + + + + + + {replaceString( + t('face_auth.consent'), + tagRegex('link', false), + (match, index) => ( + openLinkWithInAppBrowser({url: LINKS.TERMS})}> + {match} + + ), + )} + + + + + } + style={[ + isAgreeWithTermsAndConditions + ? styles.button + : styles.disabledButton, + ]} + onPress={onContinue} + /> + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.white, + }, + contentContainer: { + flexGrow: 1, + backgroundColor: COLORS.white, + }, + imageContainer: { + alignSelf: 'center', + paddingTop: rem(20), + }, + title: { + paddingTop: SCREEN_SIDE_OFFSET, + ...font(24, 34, 'black', 'primaryDark', 'center'), + }, + description: { + paddingTop: SCREEN_SIDE_OFFSET, + paddingHorizontal: rem(48), + ...font(14, 20, 'medium', 'secondary', 'center'), + }, + bold: { + ...font(14, 20, 'bold', 'secondary', 'center'), + }, + footerContainer: { + flex: 1, + justifyContent: 'flex-end', + paddingHorizontal: FOOTER_PADDING_HORIZONTAL, + paddingBottom: rem(40), + }, + checkboxRow: { + flexDirection: 'row', + width: '100%', + alignItems: 'center', + }, + noteText: { + paddingHorizontal: rem(12), + ...font(14, 18, 'medium', 'primaryDark'), + }, + termsLink: { + color: COLORS.primaryLight, + }, + buttonsContainer: { + paddingTop: rem(34), + flexDirection: 'row', + justifyContent: 'space-between', + }, + button: { + width: BUTTON_WIDTH, + }, + disabledButton: { + width: BUTTON_WIDTH, + backgroundColor: COLORS.primaryDark, + opacity: 0.5, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/constants.ts b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/constants.ts new file mode 100644 index 000000000..8718fd5db --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/constants.ts @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {windowWidth} from '@constants/styles'; +import {rem} from 'rn-units'; + +export const FOOTER_PADDING_HORIZONTAL = rem(28); +export const BUTTON_WIDTH = + windowWidth / 2 - FOOTER_PADDING_HORIZONTAL - rem(16); diff --git a/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/index.tsx b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/index.tsx new file mode 100644 index 000000000..5e42962f9 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/FaceAuthUserConsent/index.tsx @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import {commonStyles} from '@constants/styles'; +import {Header} from '@navigation/components/Header'; +import {CountrySelect} from '@screens/FaceRecognitionFlow/FaceAuthUserConsent/CountrySelect'; +import {UserConsent} from '@screens/FaceRecognitionFlow/FaceAuthUserConsent/UserConsent'; +import {t} from '@translations/i18n'; +import React, {useState} from 'react'; +import {View} from 'react-native'; + +type Props = { + updateKycStepPassed: () => void; +}; + +export function FaceAuthUserConsent({updateKycStepPassed}: Props) { + const [consentPassed, setConsentPassed] = useState(false); + + return ( + +
+ {consentPassed ? ( + + ) : ( + { + setConsentPassed(true); + }} + /> + )} + + ); +} diff --git a/src/screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed.tsx b/src/screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed.tsx new file mode 100644 index 000000000..df9cb3f9d --- /dev/null +++ b/src/screens/FaceRecognitionFlow/components/CameraFeed/CameraFeed.tsx @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {commonStyles, windowWidth} from '@constants/styles'; +import {useCameraPermissions} from '@hooks/useCameraPermissions'; +import {FaceAuthOverlay} from '@screens/FaceRecognitionFlow/components/FaceAuthOverlay'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {cameraRatioSelector} from '@store/modules/FaceRecognition/selectors'; +import {Camera, CameraType} from 'expo-camera'; +import {activateKeepAwakeAsync, deactivateKeepAwake} from 'expo-keep-awake'; +import React, {Ref, useEffect, useImperativeHandle, useRef} from 'react'; +import {StyleSheet, View} from 'react-native'; +import {useDispatch, useSelector} from 'react-redux'; + +type Props = { + onCameraReady: () => void; +}; + +export const CameraFeed = React.forwardRef( + ({onCameraReady}: Props, forwardedRef: Ref) => { + const {permissionsGranted} = useCameraPermissions(); + const cameraRef = useRef(null); + useImperativeHandle(forwardedRef, () => cameraRef.current); + + useEffect(() => { + if (permissionsGranted) { + activateKeepAwakeAsync(); + return () => { + deactivateKeepAwake(); + }; + } + }, [permissionsGranted]); + + const cameraRatio = useSelector(cameraRatioSelector); + const dispatch = useDispatch(); + const getSupportedRatios = async () => { + if (cameraRef.current) { + const ratios = await cameraRef.current.getSupportedRatiosAsync(); + dispatch( + FaceRecognitionActions.SET_CAMERA_RATIO.STATE.create({ + cameraRatio: ratios.includes('16:9') ? '16:9' : '4:3', + }), + ); + } + }; + + return permissionsGranted ? ( + + { + getSupportedRatios(); + }} + ratio={cameraRatio ?? '4:3'} + onCameraReady={onCameraReady} + type={CameraType.front}> + + + + ) : null; + }, +); + +export const cameraStyles = StyleSheet.create({ + cameraContainer16to9: { + width: windowWidth, + height: (windowWidth * 16) / 9, + }, + cameraContainer4to3: { + width: windowWidth, + height: (windowWidth * 4) / 3, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/components/FaceAuthOverlay/index.tsx b/src/screens/FaceRecognitionFlow/components/FaceAuthOverlay/index.tsx new file mode 100644 index 000000000..3f6715130 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/components/FaceAuthOverlay/index.tsx @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import {commonStyles, windowWidth} from '@constants/styles'; +import React from 'react'; +import {View} from 'react-native'; +import Svg, {ClipPath, Defs, G, Path, Rect} from 'react-native-svg'; +import {rem} from 'rn-units'; + +export const PADDING_HORIZONTAL = rem(52); +export const FACE_CONTAINER_PADDING_TOP = rem(120); +export const FACE_CONTAINER_WIDTH = windowWidth - PADDING_HORIZONTAL * 2; +export const FACE_CONTAINER_ASPECT_RATIO = 270 / 340; + +export function FaceAuthOverlay() { + const ovalPath = ` + M${PADDING_HORIZONTAL},${ + FACE_CONTAINER_PADDING_TOP + + (FACE_CONTAINER_WIDTH / 2) * FACE_CONTAINER_ASPECT_RATIO + } + a${(FACE_CONTAINER_WIDTH / 2) * FACE_CONTAINER_ASPECT_RATIO},${ + FACE_CONTAINER_WIDTH / 2 + } 0 1,0 ${FACE_CONTAINER_WIDTH},0 + a${(FACE_CONTAINER_WIDTH / 2) * FACE_CONTAINER_ASPECT_RATIO},${ + FACE_CONTAINER_WIDTH / 2 + } 0 1,0 -${FACE_CONTAINER_WIDTH},0 + `; + return ( + + + + + + + + + + + + + + + + ); +} diff --git a/src/screens/FaceRecognitionFlow/components/StatusOverlay/index.tsx b/src/screens/FaceRecognitionFlow/components/StatusOverlay/index.tsx new file mode 100644 index 000000000..354826ee6 --- /dev/null +++ b/src/screens/FaceRecognitionFlow/components/StatusOverlay/index.tsx @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {replaceString, tagRegex} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +type Props = { + title?: string; + titleIcon?: React.ReactNode; + description: string; + actionText?: string; + actionColor?: string; + actionIcon?: React.ReactNode; + action?: () => void; + onLightBackground?: boolean; +}; + +export function StatusOverlay({ + title, + titleIcon, + description, + actionIcon, + actionText, + actionColor, + action, + onLightBackground, +}: Props) { + return ( + + {titleIcon} + {title ? ( + + {title} + + ) : null} + + {replaceString(description, tagRegex('bold', false), (match, index) => ( + + {match} + + ))} + + {actionText ? ( + + + {actionIcon} + {actionText} + + + ) : null} + + ); +} + +const styles = StyleSheet.create({ + statusOverlay: { + ...StyleSheet.absoluteFillObject, + alignItems: 'center', + justifyContent: 'center', + }, + statusTitle: { + paddingHorizontal: rem(54), + ...font(24, null, 'black', 'white', 'center'), + }, + statusTitleOnLightBackground: { + color: COLORS.primaryDark, + }, + statusDescription: { + paddingTop: rem(16), + paddingHorizontal: rem(54), + ...font(14, 20, 'medium', 'white', 'center'), + }, + bold: { + ...font(14, 20, 'bold', 'white', 'center'), + }, + statusDescriptionOnLightBackground: { + color: COLORS.secondary, + }, + statusButtonContainer: { + position: 'absolute', + bottom: rem(40), + width: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + statusButton: { + height: rem(40), + width: rem(240), + borderRadius: rem(12), + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + }, + statusText: { + marginStart: rem(8), + ...font(14, 20, 'black', 'white', 'center'), + }, +}); diff --git a/src/screens/FaceRecognitionFlow/index.tsx b/src/screens/FaceRecognitionFlow/index.tsx new file mode 100644 index 000000000..61729079d --- /dev/null +++ b/src/screens/FaceRecognitionFlow/index.tsx @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: ice License 1.0 + +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 {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 {t} from '@translations/i18n'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; +import {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 ( + setFaceRecognitionPhase('FACE_AUTH')} + /> + ); + } + case 'FACE_AUTH': { + return ( + setFaceRecognitionPhase('EMOTIONS_AUTH')} + /> + ); + } + case 'EMOTIONS_AUTH': { + return ; + } + } +} + +function kycStepToFaceRecognitionPhase(kycStepPassed?: number) { + if (!kycStepPassed) { + return 'USER_CONSENT'; + } + switch (kycStepPassed) { + case 0: + return 'USER_CONSENT'; + default: + return 'EMOTIONS_AUTH'; + } +} + +export function FaceRecognition() { + useFocusStatusBar({style: 'dark-content'}); + const navigation = useNavigation(); + const user = useSelector(unsafeUserSelector); + const faceAuthStatus = useSelector(faceAuthStatusSelector); + const emotionsAuthStatus = useSelector(emotionsAuthStatusSelector); + + const isBanned = + faceAuthStatus === 'BANNED' || + emotionsAuthStatus === 'BANNED' || + (user.kycStepBlocked && !user.kycStepPassed); + + const [faceRecognitionPhase, setFaceRecognitionPhase] = + useState(() => + kycStepToFaceRecognitionPhase(user.kycStepPassed), + ); + + 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)); + } + } + }, [user.kycStepPassed, user?.repeatableKYCSteps]); + + return ( + + {isBanned ? ( + +
+ + + ) : ( + renderContent({faceRecognitionPhase, setFaceRecognitionPhase}) + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.white, + }, +}); diff --git a/src/screens/FaceRecognitionFlow/utils.ts b/src/screens/FaceRecognitionFlow/utils.ts new file mode 100644 index 000000000..f975446fe --- /dev/null +++ b/src/screens/FaceRecognitionFlow/utils.ts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {windowWidth} from '@constants/styles'; +import { + FACE_CONTAINER_ASPECT_RATIO, + FACE_CONTAINER_PADDING_TOP, + FACE_CONTAINER_WIDTH, +} from '@screens/FaceRecognitionFlow/components/FaceAuthOverlay'; +import {screenHeight} from 'rn-units'; + +export function getPictureCropStartY({ + pictureWidth, + pictureHeight, +}: { + pictureWidth: number; + pictureHeight: number; +}) { + const windowToPhotoAspectRatio = windowWidth / pictureWidth; + const cameraHeight = (pictureHeight / pictureWidth) * windowWidth; + const topOffset = (screenHeight - cameraHeight) / 2; + const ovalCenterY = + FACE_CONTAINER_PADDING_TOP + + FACE_CONTAINER_WIDTH / FACE_CONTAINER_ASPECT_RATIO / 2 - + topOffset; + return Math.max(0, ovalCenterY / windowToPhotoAspectRatio - pictureWidth / 2); +} diff --git a/src/screens/HomeFlow/Home/components/Overview/components/AdoptionCard/index.tsx b/src/screens/HomeFlow/Home/components/Overview/components/AdoptionCard/index.tsx index f1c25b7cd..e95190a39 100644 --- a/src/screens/HomeFlow/Home/components/Overview/components/AdoptionCard/index.tsx +++ b/src/screens/HomeFlow/Home/components/Overview/components/AdoptionCard/index.tsx @@ -21,7 +21,7 @@ import {GraphIcon} from '@svg/GraphIcon'; import {t} from '@translations/i18n'; import {formatNumber} from '@utils/numbers'; import {font} from '@utils/styles'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import { Image, InteractionManager, @@ -67,7 +67,7 @@ export const AdoptionCard = ({ }: AdoptionCardProps) => { const adoption = useSelector(adoptionSelector); const isSplashHidden = useSelector(isSplashHiddenSelector); - const refFlatList = useRef | null>(null); + const refFlatList = useRef(null); const sharedItems = useSharedValue([]); @@ -137,11 +137,12 @@ export const AdoptionCard = ({ [activeIndex, scrollToIndex], ); + const [isRendered, setIsRendered] = useState(false); useEffect(() => { - if (activeIndex >= 0) { + if (activeIndex >= 0 && isRendered) { scrollToIndex(activeIndex); } - }, [activeIndex, scrollToIndex]); + }, [activeIndex, isRendered, scrollToIndex]); const animatedStyleFlatList = useAnimatedStyle(() => { return { @@ -149,15 +150,6 @@ export const AdoptionCard = ({ }; }, []); - const setRefFlatList = useCallback( - (ref: Animated.FlatList | null) => { - refFlatList.current = ref; - - scrollToIndex(activeIndex); - }, - [activeIndex, scrollToIndex], - ); - const renderItem: ListRenderItem = useCallback( ({item, index}) => { return ( @@ -193,7 +185,7 @@ export const AdoptionCard = ({ /> setIsRendered(true)} /> { style={[styles.chevron, isExpanded && styles.rotatedChevron]} width={rem(12)} height={rem(12)} - color={COLORS.codeFieldText} + color={COLORS.gunmetalGrey} /> ); diff --git a/src/screens/Modals/CountrySelect/index.tsx b/src/screens/Modals/CountrySelect/index.tsx index c7b268dbe..83b930a31 100644 --- a/src/screens/Modals/CountrySelect/index.tsx +++ b/src/screens/Modals/CountrySelect/index.tsx @@ -1,105 +1,36 @@ // SPDX-License-Identifier: ice License 1.0 -import {SearchInput} from '@components/Inputs/SearchInput'; -import {KeyboardAvoider} from '@components/KeyboardAvoider'; -import {CountryListItem} from '@components/ListItems/CountryListItem'; -import {Touchable} from '@components/Touchable'; -import {countries, Country} from '@constants/countries'; +import {Country} from '@constants/countries'; +import {commonStyles} from '@constants/styles'; import {Header} from '@navigation/components/Header'; -import {useBottomOffsetStyle} from '@navigation/hooks/useBottomOffsetStyle'; import {MainStackParamList} from '@navigation/Main'; import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; +import {CountrySelectFeed} from '@screens/Templates/CountrySelectFeed'; import {t} from '@translations/i18n'; -import {font} from '@utils/styles'; -import debounce from 'lodash/debounce'; -import React, {useCallback, useMemo, useState} from 'react'; -import {ListRenderItem, StyleSheet, Text} from 'react-native'; -import {FlatList} from 'react-native-gesture-handler'; -import {isIOS, rem} from 'rn-units'; +import React from 'react'; +import {View} from 'react-native'; +import {isIOS} from 'rn-units'; export const CountrySelect = () => { const { params: {onSelect}, } = useRoute>(); - const bottomOffsetStyle = useBottomOffsetStyle({extraOffset: rem(12)}); const navigation = useNavigation(); - const [searchCountries, setSearchCountries] = useState(countries); - - const search = useMemo( - () => - debounce((term: string) => { - setSearchCountries( - term - ? countries.filter(c => - c.name.toLowerCase().includes(term.toLowerCase()), - ) - : countries, - ); - }, 600), - [], - ); - - const renderItem: ListRenderItem = useCallback( - ({item}) => { - return ( - { - navigation.goBack(); - onSelect(item); - }}> - {item.iddCode} - } - nameStyle={styles.countryNameText} - containerStyle={styles.countryContainer} - /> - - ); - }, - [navigation, onSelect], - ); return ( - +
- - { + navigation.goBack(); + onSelect(country); + }} + keyboardVerticalOffset={isIOS ? 57 : 0} // presentation: modal top offset on iOS /> - + ); }; - -const styles = StyleSheet.create({ - search: { - marginHorizontal: rem(16), - marginBottom: rem(6), - }, - listContent: { - paddingHorizontal: rem(16), - }, - countryContainer: { - marginTop: rem(12), - }, - countryNameText: { - ...font(17, 22, 'regular', 'codeFieldText'), - }, - iddCodeText: { - ...font(15, 22, 'regular', 'codeFieldText'), - }, -}); diff --git a/src/screens/Modals/QRCodeScanner/index.tsx b/src/screens/Modals/QRCodeScanner/index.tsx index 21f33aec7..c0300ab21 100644 --- a/src/screens/Modals/QRCodeScanner/index.tsx +++ b/src/screens/Modals/QRCodeScanner/index.tsx @@ -1,10 +1,10 @@ // SPDX-License-Identifier: ice License 1.0 import {COLORS} from '@constants/colors'; +import {useCameraPermissions} from '@hooks/useCameraPermissions'; import {Header} from '@navigation/components/Header'; import {WelcomeStackParamList} from '@navigation/Welcome'; import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; -import {useCameraPermissions} from '@screens/Modals/QRCodeScanner/hooks/useCameraPermissions'; import {useCameraRatio} from '@screens/Modals/QRCodeScanner/hooks/useCameraRatio'; import {useDetectBarcode} from '@screens/Modals/QRCodeScanner/hooks/useDetectBarcode'; import {BarCodeScanner} from 'expo-barcode-scanner'; diff --git a/src/screens/SettingsFlow/LanguageSettings/components/LanguageListItem.tsx b/src/screens/SettingsFlow/LanguageSettings/components/LanguageListItem.tsx index b09d8d68e..172e397cf 100644 --- a/src/screens/SettingsFlow/LanguageSettings/components/LanguageListItem.tsx +++ b/src/screens/SettingsFlow/LanguageSettings/components/LanguageListItem.tsx @@ -64,7 +64,7 @@ const styles = StyleSheet.create({ languageText: { flex: isRTL ? 0 : 1, marginLeft: rem(16), - ...font(17, 22, 'regular', 'codeFieldText'), + ...font(17, 22, 'regular', 'gunmetalGrey'), }, checkMarkWrapper: { width: rem(60), diff --git a/src/screens/Templates/CountrySelectFeed/index.tsx b/src/screens/Templates/CountrySelectFeed/index.tsx new file mode 100644 index 000000000..8cf4e079c --- /dev/null +++ b/src/screens/Templates/CountrySelectFeed/index.tsx @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {CheckMark} from '@components/CheckMark'; +import {SearchInput} from '@components/Inputs/SearchInput'; +import {KeyboardAvoider} from '@components/KeyboardAvoider'; +import {CountryListItem} from '@components/ListItems/CountryListItem'; +import {Touchable} from '@components/Touchable'; +import {countries, Country} from '@constants/countries'; +import {useBottomOffsetStyle} from '@navigation/hooks/useBottomOffsetStyle'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import debounce from 'lodash/debounce'; +import React, {useCallback, useMemo, useState} from 'react'; +import {ListRenderItem, StyleSheet, Text} from 'react-native'; +import {FlatList} from 'react-native-gesture-handler'; +import {rem} from 'rn-units'; + +type Props = { + onSelect: (country: Country) => void; + dontShowPhoneCodes?: boolean; + keyboardVerticalOffset?: number; +}; + +export function CountrySelectFeed({ + onSelect, + dontShowPhoneCodes, + keyboardVerticalOffset, +}: Props) { + const bottomOffsetStyle = useBottomOffsetStyle({extraOffset: rem(12)}); + const [searchCountries, setSearchCountries] = useState(countries); + const [selectedCountry, setSelectedCountry] = useState(null); + + const search = useMemo( + () => + debounce((term: string) => { + setSearchCountries( + term + ? countries.filter(c => + c.name.toLowerCase().includes(term.toLowerCase()), + ) + : countries, + ); + }, 600), + [], + ); + + const renderItem: ListRenderItem = useCallback( + ({item}) => { + return ( + { + onSelect(item); + setSelectedCountry(item); + }}> + + ) : dontShowPhoneCodes ? null : ( + {item.iddCode} + ) + } + nameStyle={styles.countryNameText} + containerStyle={styles.countryContainer} + /> + + ); + }, + [dontShowPhoneCodes, onSelect, selectedCountry?.isoCode], + ); + return ( + + + + + ); +} + +const styles = StyleSheet.create({ + search: { + marginHorizontal: rem(16), + marginBottom: rem(6), + }, + listContent: { + paddingHorizontal: rem(16), + }, + countryContainer: { + marginTop: rem(12), + }, + countryNameText: { + ...font(17, 22, 'regular', 'gunmetalGrey'), + }, + iddCodeText: { + ...font(15, 22, 'regular', 'gunmetalGrey'), + }, +}); diff --git a/src/store/modules/Account/sagas/deleteAccount.ts b/src/store/modules/Account/sagas/deleteAccount.ts index 9d5a698a9..bdc9b8dc1 100644 --- a/src/store/modules/Account/sagas/deleteAccount.ts +++ b/src/store/modules/Account/sagas/deleteAccount.ts @@ -11,6 +11,7 @@ export function* deleteAccountSaga() { let userId: ReturnType = yield select( userIdSelector, ); + yield call(Api.faceRecognition.deleteFaceAuthData); yield call(Api.user.deleteUser, userId); yield put(AccountActions.DELETE_ACCOUNT.SUCCESS.create()); yield put(AccountActions.SIGN_OUT.START.create({skipMetadataUpdate: true})); diff --git a/src/store/modules/FaceRecognition/actions/index.ts b/src/store/modules/FaceRecognition/actions/index.ts new file mode 100644 index 000000000..b3dd1e740 --- /dev/null +++ b/src/store/modules/FaceRecognition/actions/index.ts @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {AuthEmotion} from '@api/faceRecognition/types'; +import { + CameraRatio, + EmotionsAuthStatus, + FaceAuthStatus, +} from '@store/modules/FaceRecognition/types'; +import {createAction} from '@store/utils/actions/createAction'; + +const FACE_AUTH = createAction('FACE_AUTH', { + START: (payload: { + pictureUri: string; + cropStartY: number; + pictureWidth: number; + }) => payload, + SUCCESS: true, + FAILURE: (payload: {status: FaceAuthStatus}) => payload, +}); + +const FETCH_EMOTIONS_FOR_AUTH = createAction('FETCH_EMOTIONS_FOR_AUTH', { + START: true, + SUCCESS: (payload: { + emotions: AuthEmotion[]; + sessionId: string; + sessionExpiredAt: number; + }) => payload, + FAILURE: (payload: {status: EmotionsAuthStatus}) => payload, +}); + +const EMOTIONS_AUTH = createAction('EMOTIONS_AUTH', { + START: (payload: { + videoUri: string; + cropStartY: number; + videoWidth: number; + }) => payload, + NEED_MORE_EMOTIONS: (payload: {emotions: AuthEmotion[]}) => payload, + SUCCESS: true, + FAILURE: (payload: {status: EmotionsAuthStatus}) => payload, +}); + +const RESET_FACE_AUTH_STATUS = createAction('RESET_FACE_AUTH_STATUS', { + STATE: true, +}); + +const RESET_EMOTIONS_AUTH_STATUS = createAction('RESET_EMOTIONS_AUTH_STATUS', { + STATE: true, +}); + +const SET_CAMERA_RATIO = createAction('SET_CAMERA_RATIO', { + STATE: (payload: {cameraRatio: CameraRatio}) => payload, +}); + +export const FaceRecognitionActions = Object.freeze({ + FACE_AUTH, + EMOTIONS_AUTH, + FETCH_EMOTIONS_FOR_AUTH, + RESET_FACE_AUTH_STATUS, + RESET_EMOTIONS_AUTH_STATUS, + SET_CAMERA_RATIO, +}); diff --git a/src/store/modules/FaceRecognition/reducer/index.ts b/src/store/modules/FaceRecognition/reducer/index.ts new file mode 100644 index 000000000..aff4bab30 --- /dev/null +++ b/src/store/modules/FaceRecognition/reducer/index.ts @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {AuthEmotion} from '@api/faceRecognition/types'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import {AccountActions} from '@store/modules/Account/actions'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import { + CameraRatio, + EmotionsAuthStatus, + FaceAuthStatus, +} from '@store/modules/FaceRecognition/types'; +import produce from 'immer'; +import {persistReducer} from 'redux-persist'; + +export interface State { + faceAuthStatus: FaceAuthStatus | null; + emotionsAuthStatus: EmotionsAuthStatus | null; + + sessionId: string | null; + emotions: AuthEmotion[]; + nextEmotionIndex: number; + sessionExpiredAt: number | null; + activeRequests: number; + + cameraRatio: CameraRatio | null; +} + +type Actions = ReturnType< + | typeof FaceRecognitionActions.FACE_AUTH.START.create + | typeof FaceRecognitionActions.FACE_AUTH.SUCCESS.create + | typeof FaceRecognitionActions.FACE_AUTH.FAILURE.create + | typeof FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.SUCCESS.create + | typeof FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.FAILURE.create + | typeof FaceRecognitionActions.EMOTIONS_AUTH.START.create + | typeof FaceRecognitionActions.EMOTIONS_AUTH.SUCCESS.create + | typeof FaceRecognitionActions.EMOTIONS_AUTH.NEED_MORE_EMOTIONS.create + | typeof FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create + | typeof FaceRecognitionActions.RESET_FACE_AUTH_STATUS.STATE.create + | typeof FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.create + | typeof FaceRecognitionActions.SET_CAMERA_RATIO.STATE.create + | typeof AccountActions.SIGN_OUT.SUCCESS.create +>; + +const INITIAL_STATE: State = { + faceAuthStatus: null, + emotionsAuthStatus: null, + sessionId: null, + emotions: [], + nextEmotionIndex: 0, + sessionExpiredAt: null, + activeRequests: 0, + cameraRatio: null, +}; + +function reducer(state = INITIAL_STATE, action: Actions): State { + return produce(state, draft => { + const resetSession = () => { + draft.emotions = []; + draft.sessionId = null; + draft.nextEmotionIndex = 0; + draft.sessionExpiredAt = null; + draft.activeRequests = 0; + }; + switch (action.type) { + case FaceRecognitionActions.FACE_AUTH.START.type: + draft.faceAuthStatus = 'LOADING'; + break; + case FaceRecognitionActions.FACE_AUTH.SUCCESS.type: + draft.faceAuthStatus = 'SUCCESS'; + break; + case FaceRecognitionActions.FACE_AUTH.FAILURE.type: + draft.faceAuthStatus = action.payload.status; + break; + case FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.SUCCESS.type: + if (draft.sessionId !== action.payload.sessionId) { + resetSession(); + } + draft.emotions = action.payload.emotions; + draft.sessionId = action.payload.sessionId; + draft.sessionExpiredAt = action.payload.sessionExpiredAt; + break; + case FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.FAILURE.type: + draft.emotionsAuthStatus = action.payload.status; + resetSession(); + break; + case FaceRecognitionActions.EMOTIONS_AUTH.START.type: + draft.emotionsAuthStatus = 'LOADING'; + draft.nextEmotionIndex += 1; + draft.activeRequests += 1; + break; + case FaceRecognitionActions.EMOTIONS_AUTH.SUCCESS.type: + draft.emotionsAuthStatus = 'SUCCESS'; + resetSession(); + break; + case FaceRecognitionActions.EMOTIONS_AUTH.NEED_MORE_EMOTIONS.type: + draft.activeRequests -= 1; + // Else if there are still not completed requests to process an emotion then keep the loading status + if (!draft.activeRequests) { + draft.emotionsAuthStatus = 'NEED_MORE_EMOTIONS'; + } + draft.emotions = action.payload.emotions; + break; + case FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.type: + draft.emotionsAuthStatus = action.payload.status; + resetSession(); + break; + case FaceRecognitionActions.RESET_FACE_AUTH_STATUS.STATE.type: + draft.faceAuthStatus = null; + break; + case FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.type: + draft.emotionsAuthStatus = null; + resetSession(); + break; + case FaceRecognitionActions.SET_CAMERA_RATIO.STATE.type: + draft.cameraRatio = action.payload.cameraRatio; + break; + case AccountActions.SIGN_OUT.SUCCESS.type: + return {...INITIAL_STATE, cameraRatio: draft.cameraRatio}; + } + }); +} + +export const faceRecognitionReducer = persistReducer( + { + key: 'faceRecognition', + storage: AsyncStorage, + whitelist: [ + 'sessionId', + 'emotions', + 'nextEmotionIndex', + 'sessionExpiredAt', + 'cameraRatio', + ], + }, + reducer, +); diff --git a/src/store/modules/FaceRecognition/sagas/fetchEmotionsForAuth.ts b/src/store/modules/FaceRecognition/sagas/fetchEmotionsForAuth.ts new file mode 100644 index 000000000..fe46177a3 --- /dev/null +++ b/src/store/modules/FaceRecognition/sagas/fetchEmotionsForAuth.ts @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {is5xxApiError, isApiError} from '@api/client'; +import {Api} from '@api/index'; +import {dayjs} from '@services/dayjs'; +import {userIdSelector} from '@store/modules/Account/selectors'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {showError} from '@utils/errors'; +import {call, put, SagaReturnType, select, spawn} from 'redux-saga/effects'; + +export function* fetchEmotionsForAuthSaga() { + try { + const userId: ReturnType = yield select( + userIdSelector, + ); + const response: SagaReturnType< + typeof Api.faceRecognition.fetchEmotionsForAuth + > = yield call(Api.faceRecognition.fetchEmotionsForAuth, { + userId, + }); + yield put( + FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.SUCCESS.create({ + emotions: response.emotions, + sessionId: response.sessionId, + sessionExpiredAt: dayjs(response.sessionExpiredAt).valueOf(), + }), + ); + } catch (error: unknown) { + if (isApiError(error, 403, 'USER_DISABLED')) { + yield put( + FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.FAILURE.create({ + status: 'BANNED', + }), + ); + } else if ( + isApiError(error, 429, 'RATE_LIMIT_EXCEEDED') || + isApiError(error, 429, 'RATE_LIMIT_NEGATIVE_EXCEEDED') + ) { + yield put( + FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.FAILURE.create({ + status: 'TRY_LATER', + }), + ); + } else { + yield put( + FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.FAILURE.create({ + status: 'FAILED', + }), + ); + if (is5xxApiError(error)) { + yield spawn(showError, error); + } + } + throw error; + } +} diff --git a/src/store/modules/FaceRecognition/sagas/index.ts b/src/store/modules/FaceRecognition/sagas/index.ts new file mode 100644 index 000000000..415206dca --- /dev/null +++ b/src/store/modules/FaceRecognition/sagas/index.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {fetchEmotionsForAuthSaga} from '@store/modules/FaceRecognition/sagas/fetchEmotionsForAuth'; +import {initEmotionsAuthSaga} from '@store/modules/FaceRecognition/sagas/initEmotionsAuth'; +import {initFaceAuthSaga} from '@store/modules/FaceRecognition/sagas/initFaceAuth'; +import {takeEvery, takeLatest} from 'redux-saga/effects'; + +export const faceRecognitionWatchers = [ + takeLatest(FaceRecognitionActions.FACE_AUTH.START.type, initFaceAuthSaga), + takeEvery( + FaceRecognitionActions.EMOTIONS_AUTH.START.type, + initEmotionsAuthSaga, + ), + takeLatest( + FaceRecognitionActions.FETCH_EMOTIONS_FOR_AUTH.START.type, + fetchEmotionsForAuthSaga, + ), +]; diff --git a/src/store/modules/FaceRecognition/sagas/initEmotionsAuth.ts b/src/store/modules/FaceRecognition/sagas/initEmotionsAuth.ts new file mode 100644 index 000000000..f9957d385 --- /dev/null +++ b/src/store/modules/FaceRecognition/sagas/initEmotionsAuth.ts @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {is5xxApiError, isApiError} from '@api/client'; +import {Api} from '@api/index'; +import {FACE_RECOGNITION_PICTURE_SIZE} from '@constants/faceRecognition'; +import {userIdSelector} from '@store/modules/Account/selectors'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import { + emotionsAuthEmotionsSelector, + emotionsAuthSessionExpiredAtSelector, + emotionsAuthSessionSelector, + emotionsAuthStatusSelector, +} from '@store/modules/FaceRecognition/selectors'; +import {isEmotionsAuthFinalised} from '@store/modules/FaceRecognition/utils'; +import {shallowCompare} from '@utils/array'; +import {showError} from '@utils/errors'; +import {extractFramesWithFFmpeg} from '@utils/ffmpeg'; +import {call, put, SagaReturnType, select, spawn} from 'redux-saga/effects'; + +type Actions = ReturnType< + typeof FaceRecognitionActions.EMOTIONS_AUTH.START.create +>; + +export function* initEmotionsAuthSaga(action: Actions) { + try { + const {videoUri, cropStartY, videoWidth} = action.payload; + const sessionId: ReturnType = + yield select(emotionsAuthSessionSelector); + const emotions: ReturnType = + yield select(emotionsAuthEmotionsSelector); + const userId: ReturnType = yield select( + userIdSelector, + ); + const sessionExpiredAt: ReturnType< + typeof emotionsAuthSessionExpiredAtSelector + > = yield select(emotionsAuthSessionExpiredAtSelector); + const isSessionExpired = sessionExpiredAt + ? Date.now() >= sessionExpiredAt + : false; + if (isSessionExpired) { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'SESSION_EXPIRED', + }), + ); + return; + } + + const frames: SagaReturnType = yield call( + extractFramesWithFFmpeg, + { + inputUri: videoUri, + cropStartY, + outputSize: FACE_RECOGNITION_PICTURE_SIZE, + width: videoWidth, + }, + ); + const response: SagaReturnType = + yield call(Api.faceRecognition.emotionsAuth, { + userId, + sessionId, + pictureUris: frames, + }); + const emotionsAuthStatus: ReturnType = + yield select(emotionsAuthStatusSelector); + // If while we were waiting for this response the whole auth is already finalised + if (isEmotionsAuthFinalised(emotionsAuthStatus)) { + return; + } + if (response.sessionEnded) { + if (response.result) { + yield put(FaceRecognitionActions.EMOTIONS_AUTH.SUCCESS.create()); + } else { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'TRY_LATER', + }), + ); + } + } else { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.NEED_MORE_EMOTIONS.create({ + emotions: shallowCompare(emotions, response.emotions) + ? emotions + : response.emotions, + }), + ); + } + } catch (error: unknown) { + const emotionsAuthStatus: ReturnType = + yield select(emotionsAuthStatusSelector); + // If while we were waiting for this response the whole auth is already finalised + if (isEmotionsAuthFinalised(emotionsAuthStatus)) { + return; + } + if (isApiError(error, 403, 'USER_DISABLED')) { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'BANNED', + }), + ); + } else if ( + isApiError(error, 429, 'RATE_LIMIT_EXCEEDED') || + isApiError(error, 429, 'RATE_LIMIT_NEGATIVE_EXCEEDED') + ) { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'TRY_LATER', + }), + ); + } else if ( + isApiError(error, 403, 'SESSION_TIMED_OUT') || + isApiError(error, 404, 'SESSION_NOT_FOUND') + ) { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'SESSION_EXPIRED', + }), + ); + } else { + yield put( + FaceRecognitionActions.EMOTIONS_AUTH.FAILURE.create({ + status: 'FAILED', + }), + ); + if (is5xxApiError(error)) { + yield spawn(showError, error); + } + } + throw error; + } +} diff --git a/src/store/modules/FaceRecognition/sagas/initFaceAuth.ts b/src/store/modules/FaceRecognition/sagas/initFaceAuth.ts new file mode 100644 index 000000000..2f8e05d02 --- /dev/null +++ b/src/store/modules/FaceRecognition/sagas/initFaceAuth.ts @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {is5xxApiError, isApiError} from '@api/client'; +import {Api} from '@api/index'; +import {FACE_RECOGNITION_PICTURE_SIZE} from '@constants/faceRecognition'; +import {userIdSelector} from '@store/modules/Account/selectors'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {showError} from '@utils/errors'; +import {cropAndResizeWithFFmpeg} from '@utils/ffmpeg'; +import {getFilenameFromPath} from '@utils/file'; +import {cacheDirectory} from 'expo-file-system'; +import {call, put, SagaReturnType, select, spawn} from 'redux-saga/effects'; + +type Actions = ReturnType; + +export function* initFaceAuthSaga(action: Actions) { + try { + const {pictureUri, cropStartY, pictureWidth} = action.payload; + + const croppedPictureUri: SagaReturnType = + yield call(cropAndResizeWithFFmpeg, { + inputUri: pictureUri, + outputUri: `${cacheDirectory}/cropped_${getFilenameFromPath( + pictureUri, + )}`, + imgWidth: pictureWidth, + cropStartY, + outputSize: FACE_RECOGNITION_PICTURE_SIZE, + }); + const userId: ReturnType = yield select( + userIdSelector, + ); + + yield call(Api.faceRecognition.faceAuth, { + userId, + pictureUri: croppedPictureUri, + }); + yield put(FaceRecognitionActions.FACE_AUTH.SUCCESS.create()); + } catch (error: unknown) { + if (isApiError(error, 403, 'USER_DISABLED')) { + yield put( + FaceRecognitionActions.FACE_AUTH.FAILURE.create({ + status: 'BANNED', + }), + ); + } else if ( + isApiError(error, 429, 'RATE_LIMIT_EXCEEDED') || + isApiError(error, 429, 'RATE_LIMIT_NEGATIVE_EXCEEDED') + ) { + yield put( + FaceRecognitionActions.FACE_AUTH.FAILURE.create({ + status: 'TRY_LATER', + }), + ); + } else { + yield put( + FaceRecognitionActions.FACE_AUTH.FAILURE.create({ + status: 'FAILED', + }), + ); + if (is5xxApiError(error)) { + yield spawn(showError, error); + } + } + } +} diff --git a/src/store/modules/FaceRecognition/selectors/index.ts b/src/store/modules/FaceRecognition/selectors/index.ts new file mode 100644 index 000000000..0c52266fe --- /dev/null +++ b/src/store/modules/FaceRecognition/selectors/index.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {RootState} from '@store/rootReducer'; + +export const faceAuthStatusSelector = (state: RootState) => + state.faceRecognition.faceAuthStatus; + +export const emotionsAuthStatusSelector = (state: RootState) => + state.faceRecognition.emotionsAuthStatus; + +export const emotionsAuthSessionSelector = (state: RootState) => + state.faceRecognition.sessionId; + +export const emotionsAuthEmotionsSelector = (state: RootState) => + state.faceRecognition.emotions; +export const emotionsAuthNextEmotionIndexSelector = (state: RootState) => + state.faceRecognition.nextEmotionIndex; +export const emotionsAuthSessionExpiredAtSelector = (state: RootState) => + state.faceRecognition.sessionExpiredAt; + +export const cameraRatioSelector = (state: RootState) => + state.faceRecognition.cameraRatio; diff --git a/src/store/modules/FaceRecognition/types.ts b/src/store/modules/FaceRecognition/types.ts new file mode 100644 index 000000000..7d9e2fcf1 --- /dev/null +++ b/src/store/modules/FaceRecognition/types.ts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: ice License 1.0 + +export type BaseAuthStatus = + | 'LOADING' + | 'SUCCESS' + | 'FAILED' + | 'BANNED' + | 'TRY_LATER'; + +export type FaceAuthStatus = BaseAuthStatus; + +export type EmotionsAuthStatus = + | BaseAuthStatus + | 'NEED_MORE_EMOTIONS' + | 'SESSION_EXPIRED'; + +export type CameraRatio = '16:9' | '4:3'; diff --git a/src/store/modules/FaceRecognition/utils.ts b/src/store/modules/FaceRecognition/utils.ts new file mode 100644 index 000000000..d76651f78 --- /dev/null +++ b/src/store/modules/FaceRecognition/utils.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {EmotionsAuthStatus} from '@store/modules/FaceRecognition/types'; + +export function isEmotionsAuthFinalised(status: EmotionsAuthStatus | null) { + return ( + status === 'FAILED' || + status === 'BANNED' || + status === 'TRY_LATER' || + status === 'SUCCESS' || + status === 'SESSION_EXPIRED' + ); +} + +export function isEmotionsAuthInRecoverableFailureStatus( + status: EmotionsAuthStatus | null, +) { + return status === 'FAILED' || status === 'TRY_LATER'; +} diff --git a/src/store/modules/Tokenomics/reducer/index.ts b/src/store/modules/Tokenomics/reducer/index.ts index 06746de2a..c94d1058f 100644 --- a/src/store/modules/Tokenomics/reducer/index.ts +++ b/src/store/modules/Tokenomics/reducer/index.ts @@ -25,11 +25,14 @@ export interface State { endDate: string | null; pageNumber: number; }; + tapToMineActionType?: 'Extended' | 'Default'; } type Actions = ReturnType< | typeof TokenomicsActions.GET_MINING_SUMMARY.SUCCESS.create + | typeof TokenomicsActions.START_MINING_SESSION.START.create | typeof TokenomicsActions.START_MINING_SESSION.SUCCESS.create + | typeof TokenomicsActions.START_MINING_SESSION.FAILED.create | typeof TokenomicsActions.GET_BALANCE_SUMMARY.SUCCESS.create | typeof TokenomicsActions.GET_PRE_STAKING_SUMMARY.SUCCESS.create | typeof TokenomicsActions.GET_RANKING_SUMMARY.SUCCESS.create @@ -55,9 +58,19 @@ const INITIAL_STATE: State = { function reducer(state = INITIAL_STATE, action: Actions): State { return produce(state, draft => { switch (action.type) { + case TokenomicsActions.START_MINING_SESSION.START.type: + if (action.payload?.tapToMineActionType) { + draft.tapToMineActionType = action.payload.tapToMineActionType; + } + break; + case TokenomicsActions.GET_MINING_SUMMARY.SUCCESS.type: case TokenomicsActions.START_MINING_SESSION.SUCCESS.type: draft.miningSummary = action.payload.miningSummary; + draft.tapToMineActionType = undefined; + break; + case TokenomicsActions.START_MINING_SESSION.FAILED.type: + draft.tapToMineActionType = undefined; break; case TokenomicsActions.GET_BALANCE_SUMMARY.SUCCESS.type: diff --git a/src/store/modules/Tokenomics/sagas/startMiningSession.ts b/src/store/modules/Tokenomics/sagas/startMiningSession.ts index b292a6419..d823554db 100644 --- a/src/store/modules/Tokenomics/sagas/startMiningSession.ts +++ b/src/store/modules/Tokenomics/sagas/startMiningSession.ts @@ -5,18 +5,25 @@ import {Api} from '@api/index'; import {ResurrectRequiredData} from '@api/tokenomics/types'; import {User} from '@api/user/types'; import {LocalAudio} from '@audio'; +import {navigate} from '@navigation/utils'; import {loadLocalAudio} from '@services/audio'; import {dayjs} from '@services/dayjs'; import {AccountActions} from '@store/modules/Account/actions'; import { + authConfigSelector, firstMiningDateSelector, unsafeUserSelector, userIdSelector, } from '@store/modules/Account/selectors'; import {AnalyticsActions} from '@store/modules/Analytics/actions'; import {AnalyticsEventLogger} from '@store/modules/Analytics/constants'; +import {FaceRecognitionActions} from '@store/modules/FaceRecognition/actions'; +import {emotionsAuthStatusSelector} from '@store/modules/FaceRecognition/selectors'; import {TokenomicsActions} from '@store/modules/Tokenomics/actions'; -import {isMiningActiveSelector} from '@store/modules/Tokenomics/selectors'; +import { + isMiningActiveSelector, + tapToMineActionTypeSelector, +} from '@store/modules/Tokenomics/selectors'; import {openConfirmResurrect} from '@store/modules/Tokenomics/utils/openConfirmResurrect'; import {openConfirmResurrectNo} from '@store/modules/Tokenomics/utils/openConfirmResurrectNo'; import {openConfirmResurrectYes} from '@store/modules/Tokenomics/utils/openConfirmResurrectYes'; @@ -36,10 +43,25 @@ export function* startMiningSessionSaga( typeof TokenomicsActions.START_MINING_SESSION.START.create >, ) { + const emotionsAuthStatus: ReturnType = + yield select(emotionsAuthStatusSelector); + const authConfig: ReturnType = yield select( + authConfigSelector, + ); + if (emotionsAuthStatus !== 'SUCCESS' && authConfig?.['face-auth']?.enabled) { + navigate({ + name: 'FaceRecognition', + params: undefined, + }); + return; + } const user: ReturnType = yield select( unsafeUserSelector, ); + const tapToMineActionType: ReturnType = + yield select(tapToMineActionTypeSelector); + try { const miningSummary: SagaReturnType< typeof Api.tokenomics.startMiningSession @@ -50,6 +72,8 @@ export function* startMiningSessionSaga( yield put( TokenomicsActions.START_MINING_SESSION.SUCCESS.create(miningSummary), ); + // Reset face auth status here so on next tap to mine a user would have to face auth again + yield put(FaceRecognitionActions.RESET_EMOTIONS_AUTH_STATUS.STATE.create()); yield call(setFirstMiningDate, user); @@ -68,7 +92,7 @@ export function* startMiningSessionSaga( } AnalyticsEventLogger.trackTapToMine({ - tapToMineActionType: action.payload?.tapToMineActionType ?? 'Default', + tapToMineActionType: tapToMineActionType ?? 'Default', }); } catch (error) { yield put( diff --git a/src/store/modules/Tokenomics/selectors/index.ts b/src/store/modules/Tokenomics/selectors/index.ts index 5b21ffc4b..855fd77ab 100644 --- a/src/store/modules/Tokenomics/selectors/index.ts +++ b/src/store/modules/Tokenomics/selectors/index.ts @@ -62,3 +62,7 @@ export const globalRankSelector = (userId: string) => (state: RootState) => { export const balanceHistorySelector = (state: RootState) => { return state.tokenomics.balanceHistory; }; + +export const tapToMineActionTypeSelector = (state: RootState) => { + return state.tokenomics.tapToMineActionType; +}; diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 28db15c8b..9ac28a73b 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -10,6 +10,7 @@ import {collectionsReducer} from '@store/modules/Collections/reducer'; import {contactsReducer} from '@store/modules/Contacts/reducer'; import {creativeLibraryReducer} from '@store/modules/CreativeLibrary/reducer'; import {devicesReducer} from '@store/modules/Devices/reducer'; +import {faceRecognitionReducer} from '@store/modules/FaceRecognition/reducer'; import {inAppNotificationsReducer} from '@store/modules/InAppNotifications/reducer'; import {linkingReducer} from '@store/modules/Linking/reducer'; import {newsReducer} from '@store/modules/News/reducer'; @@ -33,6 +34,7 @@ export const rootReducer = combineReducers({ creativeLibrary: creativeLibraryReducer, permissions: permissionsReducer, account: accountReducer, + faceRecognition: faceRecognitionReducer, stats: statsReducer, news: newsReducer, contacts: contactsReducer, diff --git a/src/store/rootSaga.ts b/src/store/rootSaga.ts index d4d7a345d..34f574337 100644 --- a/src/store/rootSaga.ts +++ b/src/store/rootSaga.ts @@ -14,6 +14,7 @@ import {collectionsWatchers} from '@store/modules/Collections/sagas'; import {teamWatchers} from '@store/modules/Contacts/sagas'; import {creativeLibraryWatchers} from '@store/modules/CreativeLibrary/sagas'; import {devicesWatchers} from '@store/modules/Devices/sagas'; +import {faceRecognitionWatchers} from '@store/modules/FaceRecognition/sagas'; import {inAppNotificationsWatchers} from '@store/modules/InAppNotifications/sagas'; import {linkingWatchers} from '@store/modules/Linking/sagas'; import {newsWatchers} from '@store/modules/News/sagas'; @@ -38,6 +39,7 @@ const watchers = [ ...newsWatchers, ...statsWatchers, ...analyticsWatchers, + ...faceRecognitionWatchers, ...permissionsWatchers, ...referralsWatchers, ...collectionsWatchers, diff --git a/src/translations/locales/af.json b/src/translations/locales/af.json index 30bf5d17f..8718013c7 100644 --- a/src/translations/locales/af.json +++ b/src/translations/locales/af.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Gesigsautentifikasie", + "title": "KYC Stap #1", + "description": "Vir 'n naadloose ervaring voeg ons gesigsautentifikasie by. Hierdie stap tree op as jou [[:bold]]vroeë Stap #1 KYC[[/:bold]], wat jou voorberei vir 'n gestroomlynde verifikasie vir die toekomstige Mainnet. \n\nBevestig jou identiteit vandag om 'n gladdeloopende reis te verseker!", + "consent": "Ek stem in met die Ice [[:link]]KYC-terme en -voorwaardes[[/:link]].", + "confirmation": "Ek bevestig dat die identiteit wat ek vir hierdie stawing gebruik dieselfde een sal wees wat met hierdie rekening geassosieer word, en hierdie keuse kan nie later verander word nie.", + "auth_status": { + "loading": { + "description": "Wag asseblief terwyl ons jou autentifiseer..." + }, + "success": { + "title": "✅ Autentifikasie suksesvol", + "description": "Dankie dat jy jou identiteit bevestig het. Jou incheck-sessie begin nou.", + "action": "Klaar" + }, + "failure": { + "title": "❌ Autentifikasie misluk", + "description": "Ons kon nie jou identiteit verifieer nie. Maak seker dat daar goeie beligting is en dat jou gesig volledig sigbaar is, en probeer weer.", + "action": "Probeer weer" + }, + "try_later": { + "title": "❌ Autentifikasie misluk", + "description": "Ons kon nie jou identiteit verifieer nie. Maak seker dat daar goeie beligting is en dat jou gesig volledig sigbaar is, en probeer weer binne 1 uur.", + "action": "Sluit af" + }, + "continue": { + "title": "\uD83D\uDE01 KYC-emosieverifikasie", + "description": "Fantasties! Jy het die eerste stap voltooi deur 'n selfie te neem. Nou, gaan voort deur die getoonde emosies te volg om jou KYC-proses voort te sit.", + "action": "Gaan voort" + }, + "banned": { + "title": "❌ Mynbou gedeaktiveer", + "description": "Ons het vasgestel dat jou identiteit verband hou met 'n ander rekening. As jy dink dit is 'n fout, stuur asseblief 'n e-pos na [[:bold]]feedback@ice.io[[/:bold]] met jou bynaam en 'n hoëgehalte-selfie.", + "action": "Sluit af" + } + }, + "emotions_recognition": { + "start": "Begin", + "please_show_this": "Wys asseblief hierdie emosie", + "emotions": { + "anger": "woede", + "contempt": "minagting", + "disgust": "afkeer", + "fear": "vrees", + "happiness": "geluk", + "neutral": "neutraal", + "sadness": "hartseer", + "surprise": "verrassing" + } + } } } diff --git a/src/translations/locales/am.json b/src/translations/locales/am.json index 95735c842..e72dd70bc 100644 --- a/src/translations/locales/am.json +++ b/src/translations/locales/am.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "የፊት ማረጋገጫ", + "title": "KYC ደረጃ #1", + "description": "እንከን የለሽ ተሞክሮ ለማግኘት፣ የፊት ማረጋገጥን እየጨመርን ነው። ይህ እርምጃ እንደ የእርስዎ [[:bold]]የመጀመሪያ ደረጃ #1 KYC[/:bold]] ሆኖ ያገለግላል፣ ይህም ለወደፊቱ Mainnet ለተሳለጠ ማረጋገጫ ያዘጋጀዎታል። \n\nለቀጣይ ሰላማዊ ጉዞ ለማረጋገጥ ማንነትህን ዛሬ አረጋግጥ!", + "consent": "በበረዶው [[:link]] የKYC ውሎች እና ሁኔታዎች[[/:link]] እስማማለሁ።", + "confirmation": "ለዚህ ማረጋገጫ እየተጠቀምኩበት ያለሁት ማንነት ከዚህ መለያ ጋር አንድ አይነት እንደሚሆን አረጋግጣለሁ፣ እና ይህ ምርጫ በኋላ ሊቀየር አይችልም።", + "auth_status": { + "loading": { + "description": "እባክህ እስክናረጋግጥህ ድረስ ጠብቅ..." + }, + "success": { + "title": "✅ ማረጋገጥ ተሳክቷል።", + "description": "ማንነትዎን ስላረጋገጡ እናመሰግናለን። የመግቢያ ክፍለ ጊዜዎ አሁን ይጀምራል።", + "action": "ተከናውኗል" + }, + "failure": { + "title": "❌ ማረጋገጥ አልተሳካም።", + "description": "ማንነትህን ማረጋገጥ አልቻልንም። እባክዎ ጥሩ ብርሃን እና ፊትዎ ሙሉ በሙሉ የሚታይ መሆኑን ያረጋግጡ፣ ከዚያ እንደገና ይሞክሩ።", + "action": "እንደገና ይሞክሩ" + }, + "try_later": { + "title": "❌ ማረጋገጥ አልተሳካም።", + "description": "ማንነትህን ማረጋገጥ አልቻልንም። እባኮትን ጥሩ ብርሃን እና ፊትዎ ሙሉ በሙሉ የሚታይ መሆኑን ያረጋግጡ፣ ከዚያ በ1 ሰአት ውስጥ እንደገና ይሞክሩ።", + "action": "ገጠመ" + }, + "continue": { + "title": "\uD83D\uDE01 የKYC ስሜት ማረጋገጫ", + "description": "ታላቅ ስራ! የራስ ፎቶ በማንሳት የመጀመሪያውን እርምጃ ጨርሰሃል። አሁን፣ የእርስዎን የKYC ሂደት ለመቀጠል የታዩትን ስሜቶች በመከተል እንቀጥል።", + "action": "ቀጥል" + }, + "banned": { + "title": "❌ ማዕድን ማውጣት ተሰናክሏል።", + "description": "ማንነትህ ከሌላ መለያ ጋር የተቆራኘ መሆኑን ለይተናል። ይህ ስህተት ነው ብለው ካሰቡ፣ እባክዎን ወደ [[:bold]]feedback@ice.io[/:bold]] ቅጽል ስምዎ እና ከፍተኛ ጥራት ባለው የራስ ፎቶ ይላኩ።", + "action": "ገጠመ" + } + }, + "emotions_recognition": { + "start": "ጀምር", + "please_show_this": "እባኮትን ይህን ስሜት ያሳዩ", + "emotions": { + "anger": "ቁጣ", + "contempt": "ንቀት", + "disgust": "አስጸያፊ", + "fear": "ፍርሃት", + "happiness": "ደስታ", + "neutral": "ገለልተኛ", + "sadness": "ሀዘን", + "surprise": "መደነቅ" + } + } } } diff --git a/src/translations/locales/ar.json b/src/translations/locales/ar.json index d739c58a5..6768fea0e 100644 --- a/src/translations/locales/ar.json +++ b/src/translations/locales/ar.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "المصادقة بواسطة الوجه", + "title": "الخطوة KYC #1", + "description": "من أجل تجربة سلسة، نقوم بإضافة المصادقة بواسطة الوجه. تعتبر هذه الخطوة خطوة [[:bold]]KYC الأولى المبكرة[[/:bold]] الخاصة بك، مما يُعِدّك للتحقق المبسط في Mainnet المستقبلي. \n\nقم بتأكيد هويتك اليوم لضمان رحلة سهلة في المستقبل!", + "consent": "أوافق على [[:link]]شروط وأحكام KYC لـ Ice[[/:link]].", + "confirmation": "أؤكد أن الهوية التي أستخدمها لهذه المصادقة ستكون هي نفسها المرتبطة بهذا الحساب، ولا يمكن تغيير هذا الاختيار لاحقًا.", + "auth_status": { + "loading": { + "description": "يرجى الانتظار بينما نقوم بالمصادقة عليك..." + }, + "success": { + "title": "✅ مصادقة ناجحة", + "description": "شكرًا لتأكيد هويتك. ستبدأ جلسة السجل الخاصة بك الآن.", + "action": "تم" + }, + "failure": { + "title": "❌ فشل المصادقة", + "description": "لم نتمكن من التحقق من هويتك. يرجى التأكد من وجود إضاءة جيدة وأن وجهك مرئي بشكل كامل، ثم حاول مرة أخرى.", + "action": "حاول مرة أخرى" + }, + "try_later": { + "title": "❌ فشل المصادقة", + "description": "لم نتمكن من التحقق من هويتك. يرجى التأكد من وجود إضاءة جيدة وأن وجهك مرئي بشكل كامل، ثم حاول مرة أخرى بعد ساعة واحدة.", + "action": "أغلق" + }, + "continue": { + "title": "\uD83D\uDE01 التحقق من العواطف KYC", + "description": "عمل رائع! لقد أكملت الخطوة الأولى بأخذ صورة شخصية. الآن، دعنا نستمر عن طريق اتباع العواطف المعروضة لمتابعة عملية التحقق الخاصة بك.", + "action": "متابعة" + }, + "banned": { + "title": "❌ تعطيل التعدين", + "description": "لقد تعرفنا أن هويتك مرتبطة بحساب آخر. إذا كنت تعتقد أن هذا خطأ، فيرجى إرسال بريد إلكتروني إلى [[:bold]]feedback@ice.io[[/:bold]] مع اسمك المستعار وصورة شخصية عالية الجودة.", + "action": "أغلق" + } + }, + "emotions_recognition": { + "start": "بدء", + "please_show_this": "يرجى عرض هذا العاطفة", + "emotions": { + "anger": "غضب", + "contempt": "ازدراء", + "disgust": "اشمئزاز", + "fear": "خوف", + "happiness": "سعادة", + "neutral": "محايد", + "sadness": "حزن", + "surprise": "مفاجأة" + } + } } } diff --git a/src/translations/locales/az.json b/src/translations/locales/az.json index 3143bdef1..3d7b58546 100644 --- a/src/translations/locales/az.json +++ b/src/translations/locales/az.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Üzünün Doğrulaması", + "title": "KYC Addımı #1", + "description": "Problemsiz bir təcrübə üçün üzünün doğrulamasını əlavə edirik. Bu addım sizi gələcək Mainnet üçün sürətli doğrulama üçün hazırlayır və sizin üçün [[:bold]]təzələmə Addımı #1 KYC[[/:bold]] rolunu oynayır. \n\nBugün öz kimliyinizi təsdiq edin və səyahətinizi asan etmək üçün!", + "consent": "Mən Ice [[:link]]KYC Şərtlərini və Qaydalarını[[/:link]] qəbul edirəm.", + "confirmation": "Təsdiq edirəm ki, bu autentifikasiya üçün istifadə etdiyim şəxsiyyət bu hesabla əlaqəli eyni olacaq və bu seçimi sonradan dəyişdirmək mümkün deyil.", + "auth_status": { + "loading": { + "description": "Sizi doğrulayarkən gözləyin..." + }, + "success": { + "title": "✅ Doğrulama Uğurlu Oldu", + "description": "Kimliyinizi təsdiq etdiyiniz üçün təşəkkür edirik. Giriş sessiyanız indi başlayacaq.", + "action": "Hazır" + }, + "failure": { + "title": "❌ Doğrulama Uğursuz Oldu", + "description": "Kimliyinizi doğrulaya bilmədik. Zəif işıqlandırma və üzünüzün tam görünürlüyündən əmin olun, sonra yenidən cəhd edin.", + "action": "Təkrar cəhd edin" + }, + "try_later": { + "title": "❌ Doğrulama Uğursuz Oldu", + "description": "Kimliyinizi doğrulaya bilmədik. Zəif işıqlandırma və üzünüzün tam görünürlüyündən əmin olun, sonra bir saat sonra yenidən cəhd edin.", + "action": "Bağlayın" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Hisslər Doğrulaması", + "description": "Əla iş! İlk addımı öz şəkilinizi çəkərək başa vurmuşsunuz. İndi, KYC prosesinizi davam etdirmək üçün göstərilən hissləri izləyərək davam edək.", + "action": "Davam edin" + }, + "banned": { + "title": "❌ Qazmaq söndürüldü", + "description": "Sizin kimliyinizin başqa bir hesabla əlaqələndirildiyini müəyyən etdik. Bu səhv olduğunu düşünürsünüzsə, xahiş edirik [[:bold]]feedback@ice.io[[/:bold]] ünvanına adınızı və yüksək keyfiyyətli şəklinizi göndərin.", + "action": "Bağlayın" + } + }, + "emotions_recognition": { + "start": "Başlamaq", + "please_show_this": "Xahiş edirik bu hissi göstərin", + "emotions": { + "anger": "qəzəb", + "contempt": "əzab", + "disgust": "nafrat", + "fear": "qorxu", + "happiness": "şadlıq", + "neutral": "neutral", + "sadness": "gəlmə", + "surprise": "təəccüb" + } + } } } diff --git a/src/translations/locales/bg.json b/src/translations/locales/bg.json index b76cdc125..971bd4430 100644 --- a/src/translations/locales/bg.json +++ b/src/translations/locales/bg.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Лицева аутентикация", + "title": "KYC Стъпка #1", + "description": "За безпроблемен опит добавяме лицева аутентикация. Тази стъпка служи като ваша [[:bold]]раничка Стъпка #1 KYC[[/:bold]], подготвяйки ви за бъдеща опростена верификация в бъдещия Mainnet. \n\nПотвърдете своята идентичност днес, за да гарантирате гладко предстоящо пътуване!", + "consent": "Съгласен съм с [[:link]]Условията и правилата на KYC на Ice[[/:link]].", + "confirmation": "Потвърждавам, че самоличността, която използвам за това удостоверяване, ще бъде същата, свързана с този акаунт, и този избор не може да бъде променен по-късно.", + "auth_status": { + "loading": { + "description": "Моля, изчакайте, докато ви аутентифицираме..." + }, + "success": { + "title": "✅ Успешна аутентикация", + "description": "Благодарим ви, че потвърдихте своята идентичност. Вашият сесия за регистрация започва сега.", + "action": "Готово" + }, + "failure": { + "title": "❌ Аутентикацията неуспешна", + "description": "Не можем да потвърдим вашата идентичност. Моля, уверете се, че има достатъчно осветление и че вашето лице е напълно видимо, след което опитайте отново.", + "action": "Опитайте отново" + }, + "try_later": { + "title": "❌ Аутентикацията неуспешна", + "description": "Не можем да потвърдим вашата идентичност. Моля, уверете се, че има достатъчно осветление и че вашето лице е напълно видимо, след което опитайте отново след 1 час.", + "action": "Затворете" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Верификация на Емоции", + "description": "Чудесна работа! Завършили сте първата стъпка, като направихте селфи. Сега, продължете, следвайки показаните емоции, за да продължите процеса си KYC.", + "action": "Продължете" + }, + "banned": { + "title": "❌ Забранено добиване", + "description": "Установихме, че вашата идентичност е свързана с друга сметка. Ако смятате, че това е грешка, моля, изпратете имейл на [[:bold]]feedback@ice.io[[/:bold]] с вашия прякор и снимка на високо качество.", + "action": "Затворете" + } + }, + "emotions_recognition": { + "start": "Започнете", + "please_show_this": "Моля, покажете тази емоция", + "emotions": { + "anger": "гняв", + "contempt": "презрение", + "disgust": "отвращение", + "fear": "страх", + "happiness": "щастие", + "neutral": "нейтрален", + "sadness": "тъга", + "surprise": "изненада" + } + } } } diff --git a/src/translations/locales/bn.json b/src/translations/locales/bn.json index 4f2e4b9de..89e5b4877 100644 --- a/src/translations/locales/bn.json +++ b/src/translations/locales/bn.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "মুখোমুখি প্রমাণীকরণ", + "title": "KYC ধাপ #1", + "description": "সমস্যার বিহীন একটি অভিজ্ঞতার জন্য, আমরা মুখোমুখি প্রমাণীকরণ যোগ করছি। এই ধাপটি আপনার [[:bold]]প্রাথমিক ধাপ #1 KYC[[/:bold]] হিসেবে কাজ করে, ভবিষ্যতের মেইননেটে সহজ যাচাইর জন্য আপনাকে প্রস্তুত করে দেয়। \n\nআগামী মেইননেটে সমস্যার সম্ভাবনা বেশি নেওয়ার জন্য আপনার আজকের স্থায়ীকরণ নিশ্চিত করতে আপনার পরিচিতি নিশ্চিত করুন!", + "consent": "আমি Ice [[:link]]KYC শর্তাদি এবং নির্দেশনাগুলি[[/:link]] সাথে সম্মত।", + "confirmation": "আমি নিশ্চিত করছি যে এই প্রমাণীকরণের জন্য আমি যে পরিচয়টি ব্যবহার করছি সেটিই এই অ্যাকাউন্টের সাথে যুক্ত হবে এবং এই পছন্দটি পরে পরিবর্তন করা যাবে না।", + "auth_status": { + "loading": { + "description": "দয়া করে আমরা আপনাকে প্রমাণীকরণ করতে অপেক্ষা করুন..." + }, + "success": { + "title": "✅ প্রমাণীকরণ সফল", + "description": "আপনার পরিচয় নিশ্চিত করার জন্য ধন্যবাদ। আপনার চেক-ইন সেশন এখন শুরু হবে।", + "action": "সম্পন্ন" + }, + "failure": { + "title": "❌ প্রমাণীকরণ ব্যর্থ", + "description": "আমরা আপনার পরিচয় নিশ্চিত করতে পারি নি। দয়া করে নির্দেশনা সার্থক আছে এবং আপনার মুখটি সম্পূর্ণ দৃশ্যমান হওয়া নিশ্চিত করুন, তারপর আবার চেষ্টা করুন।", + "action": "আবার চেষ্টা করুন" + }, + "try_later": { + "title": "❌ প্রমাণীকরণ ব্যর্থ", + "description": "আমরা আপনার পরিচয় নিশ্চিত করতে পারি নি। দয়া করে নির্দেশনা সার্থক আছে এবং আপনার মুখটি সম্পূর্ণ দৃশ্যমান হওয়া নিশ্চিত করুন, তারপর 1 ঘণ্টা পরে আবার চেষ্টা করুন।", + "action": "বন্ধ করুন" + }, + "continue": { + "title": "\uD83D\uDE01 KYC ভাবনা প্রমাণীকরণ", + "description": "সুন্দর কাজ! আপনি একটি সেলফি তুলে প্রথম ধাপ সম্পন্ন করেছেন। এখন, আমরা আপনার KYC প্রক্রিয়া চালিয়ে যাওয়ার জন্য প্রদর্শিত ভাবনা অনুসরণ করার জন্য এগিয়ে যাই।", + "action": "চালিয়ে যান" + }, + "banned": { + "title": "❌ খনন করা স্থগিত", + "description": "আমরা চেনেছি যে আপনার পরিচয় অন্য একটি অ্যাকাউন্টের সাথে সংযুক্ত আছে। আপনি যদি এটি একটি ভুল মনে করেন, তবে দয়া করে আপনার উপনাম এবং একটি উচ্চ মানের সেলফির সাথে [[:bold]]feedback@ice.io[[/:bold]] ইমেল পাঠান।", + "action": "বন্ধ করুন" + } + }, + "emotions_recognition": { + "start": "শুরু", + "please_show_this": "দয়া করে এই ভাবনা দেখান", + "emotions": { + "anger": "রাগ", + "contempt": "অবজ্ঞা", + "disgust": "ঘৃণা", + "fear": "ভয়", + "happiness": "খুশি", + "neutral": "নিরপেক্ষ", + "sadness": "দু: খিত", + "surprise": "আশ্চর্য" + } + } } } diff --git a/src/translations/locales/cs.json b/src/translations/locales/cs.json index 9f01d62eb..8d35f65f8 100644 --- a/src/translations/locales/cs.json +++ b/src/translations/locales/cs.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Ověření tváří", + "title": "KYC Krok #1", + "description": "Pro bezproblémový zážitek přidáváme ověření tváří. Tento krok slouží jako váš [[:bold]]raný Krok #1 KYC[[/:bold]], připravující vás na jednodušší ověření v budoucím Mainnetu. \n\nPotvrďte svou identitu ještě dnes, abyste zajistili hladký postup do budoucnosti!", + "consent": "Souhlasím s [[:link]]KYC Podmínkami a pravidly Ice[[/:link]].", + "confirmation": "Potvrzuji, že identita, kterou používám pro toto ověření, bude stejná jako identita spojená s tímto účtem a tuto volbu nelze později změnit.", + "auth_status": { + "loading": { + "description": "Počkejte, zatímco vás ověřujeme..." + }, + "success": { + "title": "✅ Ověření proběhlo úspěšně", + "description": "Děkujeme za potvrzení vaší identity. Vaše relace check-in začne nyní.", + "action": "Hotovo" + }, + "failure": { + "title": "❌ Ověření selhalo", + "description": "Nepodařilo se nám ověřit vaši identitu. Ujistěte se, že je dostatečné osvětlení a že je vaše tvář plně viditelná, a zkuste to znovu.", + "action": "Zkusit znovu" + }, + "try_later": { + "title": "❌ Ověření selhalo", + "description": "Nepodařilo se nám ověřit vaši identitu. Ujistěte se, že je dostatečné osvětlení a že je vaše tvář plně viditelná, a zkuste to znovu za 1 hodinu.", + "action": "Zavřít" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Ověření emocí", + "description": "Skvělá práce! První krok jste dokončili tím, že jste pořídili selfie. Teď pokračujte tím, že budete následovat zobrazené emoce, abyste mohli pokračovat ve svém KYC procesu.", + "action": "Pokračovat" + }, + "banned": { + "title": "❌ Těžba zakázána", + "description": "Zjistili jsme, že vaše identita je spojena s jiným účtem. Pokud si myslíte, že se jedná o chybu, pošlete e-mail na adresu [[:bold]]feedback@ice.io[[/:bold]] se svým přezdívkou a kvalitní selfie.", + "action": "Zavřít" + } + }, + "emotions_recognition": { + "start": "Začít", + "please_show_this": "Prosím, ukážete tuto emoci", + "emotions": { + "anger": "hněv", + "contempt": "pohrdání", + "disgust": "znechucení", + "fear": "strach", + "happiness": "štěstí", + "neutral": "neutrální", + "sadness": "smutek", + "surprise": "překvapení" + } + } } } diff --git a/src/translations/locales/de.json b/src/translations/locales/de.json index 56f7aa45b..446cb0373 100644 --- a/src/translations/locales/de.json +++ b/src/translations/locales/de.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Gesichtsauthentifizierung", + "title": "KYC Schritt #1", + "description": "Für ein reibungsloses Erlebnis fügen wir die Gesichtsauthentifizierung hinzu. Dieser Schritt dient als Ihr [[:bold]]früher Schritt #1 KYC[[/:bold]], der Sie auf eine vereinfachte Verifizierung in der zukünftigen Mainnet vorbereitet. \n\nBestätigen Sie noch heute Ihre Identität, um eine reibungslose Reise in die Zukunft zu gewährleisten!", + "consent": "Ich stimme den [[:link]]KYC-Bedingungen und -Bestimmungen von Ice[[/:link]] zu.", + "confirmation": "Ich bestätige, dass die Identität, die ich für diese Authentifizierung verwende, dieselbe ist, die mit diesem Konto verknüpft ist, und dass diese Auswahl später nicht mehr geändert werden kann.", + "auth_status": { + "loading": { + "description": "Bitte warten Sie, während wir Sie authentifizieren..." + }, + "success": { + "title": "✅ Authentifizierung erfolgreich", + "description": "Vielen Dank für die Bestätigung Ihrer Identität. Ihre Check-in-Sitzung beginnt jetzt.", + "action": "Fertig" + }, + "failure": { + "title": "❌ Authentifizierung fehlgeschlagen", + "description": "Wir konnten Ihre Identität nicht überprüfen. Stellen Sie sicher, dass ausreichend Licht vorhanden ist und Ihr Gesicht vollständig sichtbar ist, und versuchen Sie es erneut.", + "action": "Erneut versuchen" + }, + "try_later": { + "title": "❌ Authentifizierung fehlgeschlagen", + "description": "Wir konnten Ihre Identität nicht überprüfen. Stellen Sie sicher, dass ausreichend Licht vorhanden ist und Ihr Gesicht vollständig sichtbar ist, und versuchen Sie es in 1 Stunde erneut.", + "action": "Schließen" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Emotionsverifizierung", + "description": "Gute Arbeit! Sie haben den ersten Schritt abgeschlossen, indem Sie ein Selfie gemacht haben. Jetzt können Sie fortfahren, indem Sie die angezeigten Emotionen verfolgen, um Ihren KYC-Prozess fortzusetzen.", + "action": "Weiter" + }, + "banned": { + "title": "❌ Mining deaktiviert", + "description": "Wir haben festgestellt, dass Ihre Identität mit einem anderen Konto verknüpft ist. Wenn Sie glauben, dass dies ein Fehler ist, senden Sie bitte eine E-Mail an [[:bold]]feedback@ice.io[[/:bold]] mit Ihrem Spitznamen und einem hochwertigen Selfie.", + "action": "Schließen" + } + }, + "emotions_recognition": { + "start": "Start", + "please_show_this": "Bitte zeigen Sie diese Emotion", + "emotions": { + "anger": "Wut", + "contempt": "Verachtung", + "disgust": "Ekel", + "fear": "Angst", + "happiness": "Glück", + "neutral": "Neutral", + "sadness": "Traurigkeit", + "surprise": "Überraschung" + } + } } } diff --git a/src/translations/locales/el.json b/src/translations/locales/el.json index 4276d3807..00a780c3b 100644 --- a/src/translations/locales/el.json +++ b/src/translations/locales/el.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Αυθεντικοποίηση προσώπου", + "title": "KYC Βήμα #1", + "description": "Για μια άνετη εμπειρία, προσθέτουμε την αυθεντικοποίηση προσώπου. Αυτό το βήμα λειτουργεί ως το [[:bold]]πρώιμο Βήμα #1 ΚΥΧ[[/:bold]] σας, προετοιμάζοντάς σας για μια απλούστερη επαλήθευση στο μελλοντικό Mainnet. \n\nΕπιβεβαιώστε σήμερα την ταυτότητά σας για να διασφαλίσετε μια ομαλή πορεία προς το μέλλον!", + "consent": "Συμφωνώ με τους [[:link]]Όρους και Προϋποθέσεις ΚΥΧ της Ice[[/:link]].", + "confirmation": "Επιβεβαιώνω ότι η ταυτότητα που χρησιμοποιώ για αυτόν τον έλεγχο ταυτότητας θα είναι η ίδια που σχετίζεται με αυτόν τον λογαριασμό και αυτή η επιλογή δεν μπορεί να αλλάξει αργότερα.", + "auth_status": { + "loading": { + "description": "Παρακαλώ περιμένετε ενώ σας αυθεντικοποιούμε..." + }, + "success": { + "title": "✅ Επιτυχής αυθεντικοποίηση", + "description": "Σας ευχαριστούμε για την επιβεβαίωση της ταυτότητάς σας. Η συνεδρία σας ελέγχου θα ξεκινήσει τώρα.", + "action": "Ολοκληρώθηκε" + }, + "failure": { + "title": "❌ Αποτυχία αυθεντικοποίησης", + "description": "Δεν μπορέσαμε να επαληθεύσουμε την ταυτότητά σας. Βεβαιωθείτε ότι υπάρχει καλός φωτισμός και ότι το πρόσωπό σας είναι πλήρως ορατό, και δοκιμάστε ξανά.", + "action": "Δοκιμάστε ξανά" + }, + "try_later": { + "title": "❌ Αποτυχία αυθεντικοποίησης", + "description": "Δεν μπορέσαμε να επαληθεύσουμε την ταυτότητά σας. Βεβαιωθείτε ότι υπάρχει καλός φωτισμός και ότι το πρόσωπό σας είναι πλήρως ορατό, και δοκιμάστε ξανά σε 1 ώρα.", + "action": "Κλείσιμο" + }, + "continue": { + "title": "\uD83D\uDE01 Επαλήθευση συναισθημάτων KYC", + "description": "Καλή δουλειά! Έχετε ολοκληρώσει τον πρώτο βήμα λαμβάνοντας μια selfie. Τώρα, συνεχίστε ακολουθώντας τα εμφανιζόμενα συναισθήματα για να συνεχίσετε τη διαδικασία KYC σας.", + "action": "Συνέχεια" + }, + "banned": { + "title": "❌ Απενεργοποίηση εξόρυξης", + "description": "Έχουμε εντοπίσει ότι η ταυτότητά σας συνδέεται με άλλο λογαριασμό. Εάν πιστεύετε ότι πρόκειται για σφάλμα, παρακαλούμε στείλτε email στο [[:bold]]feedback@ice.io[[/:bold]] με το ψευδώνυμό σας και μια φωτογραφία selfie υψηλής ποιότητας.", + "action": "Κλείσιμο" + } + }, + "emotions_recognition": { + "start": "Έναρξη", + "please_show_this": "Παρακαλώ δείξτε αυτό το συναίσθημα", + "emotions": { + "anger": "Θυμός", + "contempt": "Περιφρόνηση", + "disgust": "Άηδος", + "fear": "Φόβος", + "happiness": "Χαρά", + "neutral": "Ουδέτερο", + "sadness": "Λύπη", + "surprise": "Εκπληξη" + } + } } } diff --git a/src/translations/locales/en.json b/src/translations/locales/en.json index 41cbacbf6..9cfef8acd 100644 --- a/src/translations/locales/en.json +++ b/src/translations/locales/en.json @@ -990,5 +990,56 @@ } } } + }, + "face_auth": { + "header": "Face Auth", + "title": "KYC Step #1", + "description": "For a seamless experience, we're adding face authentication. This step acts as your [[:bold]]early Step #1 KYC[[/:bold]], preparing you for a streamlined verification for the future Mainnet. \n\nConfirm your identity today to ensure a smooth journey ahead!", + "consent": "I agree with the Ice [[:link]]KYC Terms and Conditions[[/:link]].", + "confirmation": "I confirm that the identity I'm using for this authentication will be the same one associated with this account, and this choice cannot be changed later.", + "auth_status": { + "loading": { + "description": "Please wait while we authenticate you..." + }, + "success": { + "title": "✅ Authentication Successful", + "description": "Thank you for confirming your identity. Your check-in session will start now.", + "action": "Done" + }, + "failure": { + "title": "❌ Authentication Failed", + "description": "We couldn't verify your identity. Please ensure good lighting and that your face is fully visible, then try again.", + "action": "Retry" + }, + "try_later": { + "title": "❌ Authentication Failed", + "description": "We couldn't verify your identity. Please ensure good lighting and that your face is fully visible, then try again in 1 hour.", + "action": "Close" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Emotion Verification", + "description": "Great job! You've completed the first step by taking a selfie. Now, let's proceed by following the displayed emotions to continue your KYC process.", + "action": "Continue" + }, + "banned": { + "title": "❌ Mining disabled", + "description": "We have identified that your identity is associated with another account. If you think this is an error, please send an email to [[:bold]]feedback@ice.io[[/:bold]] with your nickname and a high-quality selfie.", + "action": "Close" + } + }, + "emotions_recognition": { + "start": "Start", + "please_show_this": "Please show this emotion", + "emotions": { + "anger": "anger", + "contempt": "contempt", + "disgust": "disgust", + "fear": "fear", + "happiness": "happiness", + "neutral": "neutral", + "sadness": "sadness", + "surprise": "surprise" + } + } } } diff --git a/src/translations/locales/en.json.d.ts b/src/translations/locales/en.json.d.ts index 2affe656f..f2f54ce27 100644 --- a/src/translations/locales/en.json.d.ts +++ b/src/translations/locales/en.json.d.ts @@ -621,4 +621,35 @@ export type Translations = { 'creative_library.practices_section.bad.rules.02.description': null; 'creative_library.practices_section.bad.rules.03.title': null; 'creative_library.practices_section.bad.rules.03.description': null; + 'face_auth.header': null; + 'face_auth.title': null; + 'face_auth.description': null; + 'face_auth.consent': null; + 'face_auth.confirmation': null; + 'face_auth.auth_status.loading.description': null; + 'face_auth.auth_status.success.title': null; + 'face_auth.auth_status.success.description': null; + 'face_auth.auth_status.success.action': null; + 'face_auth.auth_status.failure.title': null; + 'face_auth.auth_status.failure.description': null; + 'face_auth.auth_status.failure.action': null; + 'face_auth.auth_status.try_later.title': null; + 'face_auth.auth_status.try_later.description': null; + 'face_auth.auth_status.try_later.action': null; + 'face_auth.auth_status.continue.title': null; + 'face_auth.auth_status.continue.description': null; + 'face_auth.auth_status.continue.action': null; + 'face_auth.auth_status.banned.title': null; + 'face_auth.auth_status.banned.description': null; + 'face_auth.auth_status.banned.action': null; + 'face_auth.emotions_recognition.start': null; + 'face_auth.emotions_recognition.please_show_this': null; + 'face_auth.emotions_recognition.emotions.anger': null; + 'face_auth.emotions_recognition.emotions.contempt': null; + 'face_auth.emotions_recognition.emotions.disgust': null; + 'face_auth.emotions_recognition.emotions.fear': null; + 'face_auth.emotions_recognition.emotions.happiness': null; + 'face_auth.emotions_recognition.emotions.neutral': null; + 'face_auth.emotions_recognition.emotions.sadness': null; + 'face_auth.emotions_recognition.emotions.surprise': null; }; diff --git a/src/translations/locales/es.json b/src/translations/locales/es.json index 407db8bff..e58b53733 100644 --- a/src/translations/locales/es.json +++ b/src/translations/locales/es.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Autenticación facial", + "title": "Paso KYC #1", + "description": "Para una experiencia sin problemas, estamos agregando la autenticación facial. Este paso actúa como su [[:bold]]Paso temprano #1 KYC[[/:bold]], preparándolo para una verificación simplificada en el futuro Mainnet. \n\n¡Confirme su identidad hoy para asegurarse un viaje sin problemas en el futuro!", + "consent": "Acepto los [[:link]]Términos y condiciones KYC de Ice[[/:link]].", + "confirmation": "Confirmo que la identidad que estoy usando para esta autenticación será la misma asociada con esta cuenta y esta elección no se puede cambiar más adelante.", + "auth_status": { + "loading": { + "description": "Por favor, espere mientras lo autenticamos..." + }, + "success": { + "title": "✅ Autenticación exitosa", + "description": "Gracias por confirmar su identidad. Su sesión de check-in comenzará ahora.", + "action": "Hecho" + }, + "failure": { + "title": "❌ Autenticación fallida", + "description": "No pudimos verificar su identidad. Asegúrese de que haya buena iluminación y de que su rostro esté completamente visible, luego inténtelo nuevamente.", + "action": "Reintentar" + }, + "try_later": { + "title": "❌ Autenticación fallida", + "description": "No pudimos verificar su identidad. Asegúrese de que haya buena iluminación y de que su rostro esté completamente visible, luego inténtelo nuevamente en 1 hora.", + "action": "Cerrar" + }, + "continue": { + "title": "\uD83D\uDE01 Verificación de emociones KYC", + "description": "¡Buen trabajo! Ha completado el primer paso tomando un selfie. Ahora, continúe siguiendo las emociones que se muestran para continuar con su proceso KYC.", + "action": "Continuar" + }, + "banned": { + "title": "❌ Minería deshabilitada", + "description": "Hemos identificado que su identidad está asociada con otra cuenta. Si cree que esto es un error, envíe un correo electrónico a [[:bold]]feedback@ice.io[[/:bold]] con su apodo y una selfie de alta calidad.", + "action": "Cerrar" + } + }, + "emotions_recognition": { + "start": "Comenzar", + "please_show_this": "Por favor, muestre esta emoción", + "emotions": { + "anger": "Enojo", + "contempt": "Desprecio", + "disgust": "Asco", + "fear": "Miedo", + "happiness": "Felicidad", + "neutral": "Neutral", + "sadness": "Tristeza", + "surprise": "Sorpresa" + } + } } } diff --git a/src/translations/locales/fa.json b/src/translations/locales/fa.json index b01e79309..4d5b28f82 100644 --- a/src/translations/locales/fa.json +++ b/src/translations/locales/fa.json @@ -1000,5 +1000,56 @@ } } } + }, + "face_auth": { + "header": "تایید هویت چهره", + "title": "مرحله KYC #1", + "description": "برای یک تجربه بی‌نقص، ما تایید هویت چهره را افزوده‌ایم. این مرحله به عنوان [[:bold]]مرحله اولیه KYC شما #1[[/:bold]] عمل می‌کند و شما را برای یک بررسی ساده و سریع در آینده Mainnet آماده می‌کند. \n\n هویت خود را امروز تأیید کنید تا مطمئن باشید مسیر پیش روی شما روان خواهد بود!", + "consent": "من با [[:link]]شرایط و ضوابط KYC[[/:link]] Ice موافقم.", + "confirmation": "من تصدیق می‌کنم که هویتی که برای این تایید هویت استفاده می‌کنم همان هویتی است که با این حساب مرتبط خواهد شد و این انتخاب نمی‌تواند در آینده تغییر کند.", + "auth_status": { + "loading": { + "description": "لطفاً در حالی که شما را تایید هویت می‌کنیم، منتظر بمانید..." + }, + "success": { + "title": "✅ تایید هویت موفقیت‌آمیز بود", + "description": "با تشکر از شما برای تأیید هویت‌تان. جلسه ورود شما هم‌اکنون شروع خواهد شد.", + "action": "انجام شد" + }, + "failure": { + "title": "❌ تایید هویت ناموفق بود", + "description": "ما نتوانستیم هویت شما را تایید کنیم. لطفاً مطمئن شوید که نور کافی است و چهره شما کاملاً قابل مشاهده است، سپس دوباره تلاش کنید.", + "action": "تلاش مجدد" + }, + "try_later": { + "title": "❌ تایید هویت ناموفق بود", + "description": "ما نتوانستیم هویت شما را تایید کنیم. لطفاً مطمئن شوید که نور کافی است و چهره شما کاملاً قابل مشاهده است، سپس در 1 ساعت دیگر دوباره تلاش کنید.", + "action": "بستن" + }, + "continue": { + "title": "\uD83D\uDE01 تایید هویت احساسات KYC", + "description": "کار عالی! شما با گرفتن یک سلفی اولین مرحله را به پایان بردید. حالا بیایید با دنبال کردن احساسات نمایش داده شده، فرآیند KYC خود را ادامه دهیم.", + "action": "ادامه" + }, + "banned": { + "title": "❌ استخراج ممنوع است", + "description": "ما تشخیص داده‌ایم که هویت شما با یک حساب دیگر مرتبط است. اگر فکر می‌کنید این یک اشتباه است، لطفاً یک ایمیل به [[:bold]]feedback@ice.io[[/:bold]] با نام کاربری و یک سلفی با کیفیت بالا ارسال کنید.", + "action": "بستن" + } + }, + "emotions_recognition": { + "start": "شروع", + "please_show_this": "لطفاً این احساس را نشان دهید", + "emotions": { + "anger": "خشم", + "contempt": "تحقیر", + "disgust": "نفرت", + "fear": "ترس", + "happiness": "خوشحالی", + "neutral": "خنثی", + "sadness": "غم", + "surprise": "تعجب" + } + } } } diff --git a/src/translations/locales/fr.json b/src/translations/locales/fr.json index de45bfca4..69fb9ba5e 100644 --- a/src/translations/locales/fr.json +++ b/src/translations/locales/fr.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Authentification faciale", + "title": "Étape KYC n°1", + "description": "Pour une expérience fluide, nous ajoutons l'authentification faciale. Cette étape agit comme votre [[:bold]]KYC Étape précoce n°1[[/:bold]], vous préparant pour une vérification simplifiée sur le futur Mainnet. \n\nConfirmez votre identité aujourd'hui pour vous assurer un parcours sans encombre !", + "consent": "J'accepte les [[:link]]Conditions KYC de Ice[[/:link]].", + "confirmation": "Je confirme que l'identité que j'utilise pour cette authentification sera la même que celle associée à ce compte, et ce choix ne pourra pas être modifié ultérieurement.", + "auth_status": { + "loading": { + "description": "Veuillez patienter pendant que nous vous authentifions..." + }, + "success": { + "title": "✅ Authentification réussie", + "description": "Merci d'avoir confirmé votre identité. Votre session de check-in va maintenant commencer.", + "action": "Terminé" + }, + "failure": { + "title": "❌ Échec de l'authentification", + "description": "Nous n'avons pas pu vérifier votre identité. Assurez-vous qu'il y a une bonne luminosité et que votre visage est entièrement visible, puis réessayez.", + "action": "Réessayer" + }, + "try_later": { + "title": "❌ Échec de l'authentification", + "description": "Nous n'avons pas pu vérifier votre identité. Assurez-vous qu'il y a une bonne luminosité et que votre visage est entièrement visible, puis réessayez dans 1 heure.", + "action": "Fermer" + }, + "continue": { + "title": "\uD83D\uDE01 Vérification des émotions KYC", + "description": "Excellent travail ! Vous avez terminé la première étape en prenant un selfie. Maintenant, continuons en suivant les émotions affichées pour poursuivre votre processus KYC.", + "action": "Continuer" + }, + "banned": { + "title": "❌ Mining désactivé", + "description": "Nous avons identifié que votre identité est associée à un autre compte. Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un e-mail à [[:bold]]feedback@ice.io[[/:bold]] avec votre pseudonyme et un selfie de haute qualité.", + "action": "Fermer" + } + }, + "emotions_recognition": { + "start": "Démarrer", + "please_show_this": "Veuillez montrer cette émotion", + "emotions": { + "anger": "colère", + "contempt": "mépris", + "disgust": "dégoût", + "fear": "peur", + "happiness": "joie", + "neutral": "neutre", + "sadness": "tristesse", + "surprise": "surprise" + } + } } } diff --git a/src/translations/locales/gu.json b/src/translations/locales/gu.json index 9913bdafe..325b1d83b 100644 --- a/src/translations/locales/gu.json +++ b/src/translations/locales/gu.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "ચહેરું પ્રમાણીકરણ", + "title": "KYC પગલું #1", + "description": "સરળ અનુભવ માટે, અમે ચહેરું પ્રમાણીકરણ ઉમેરીએ છીએ. આ પગલું તમારું [[:bold]]પ્રારંભિક પગલું #1 KYC[[/:bold]] તરીકે કાર્ય કરે છે, જે તમને ભવિષ્યનો મેન્નેટ માં સુચલિત ચકાસો માટે તૈયાર કરે છે. \n\nઆગળની સ્મૂથ માર્ગ માટે આજ તમારી પછીની પથપ્રગતિ ખરીદો!", + "consent": "હું [[:link]]Ice KYC શરતો અને નિયમો[[/:link]] સાથે સંમત છું.", + "confirmation": "હું ખરીદી રહ્યું છું કે આ પ્રમાણીકરણ માટે હોય એ મારી આની એક સમની હોય છે અને આ પસંદ પછી બદલવી શકાતી નથી.", + "auth_status": { + "loading": { + "description": "કૃપા કરીને જો અમે તમને પ્રમાણીકરણ કરીએ તો મુકાબલો કરવા માંગો..." + }, + "success": { + "title": "✅ પ્રમાણીકરણ સફળ", + "description": "આપની પરિચય પ્રમાણીકરણ કરવા બદલ ધન્યવાદ. આપનું ચેક-ઇન સેશન હવે શરૂ થશે.", + "action": "થયું" + }, + "failure": { + "title": "❌ પ્રમાણીકરણ નિષ્ફળ", + "description": "અમે તમારી પરિચય પ્રમાણીકરણ કરી શક્યો ન હતો. કૃપા કરીને સારો પ્રકાશ અને તમારું ચહેરું પૂરી રીતે દિખાતો રહે, પછી ફરીથી પ્રયાસ કરો.", + "action": "ફરીથી પ્રયાસ કરો" + }, + "try_later": { + "title": "❌ પ્રમાણીકરણ નિષ્ફળ", + "description": "અમે તમારી પરિચય પ્રમાણીકરણ કરી શક્યો ન હતો. કૃપા કરીને સારો પ્રકાશ અને તમારું ચહેરું પૂરી રીતે દિખાતો રહે, પછી 1 કલાક બાદ ફરીથી પ્રયાસ કરો.", + "action": "બંધ કરો" + }, + "continue": { + "title": "\uD83D\uDE01 KYC ભાવના પ્રમાણીકરણ", + "description": "શાનદાર કામ! તમે એક સેલ્ફી લઈને પહેલો પગલો પૂરો કર્યો છો. હવે, આપને KYC પ્રક્રિયાને આગળ વધવા માટે દર્શાવવામાં આવતા ભાવનાઓને અનુસરવીને આગળ વધવું જોઈએ.", + "action": "આગળ વધો" + }, + "banned": { + "title": "❌ ખનિજ ખણગર અચલ", + "description": "અમે શોધ્યો છે કે તમારી પરિચય અન્ય એક ખાતા સાથે જોડાયેલી છે. જો તમે આ એક ભૂલ માનો છો તો, [[:bold]]feedback@ice.io[[/:bold]] પર આપનું પરિચય નામ અને ઉચ્ચ ગુણવત્તાવાળી સેલ્ફી સાથે ઇમેઇલ મોકલો કે નહિં.", + "action": "બંધ કરો" + } + }, + "emotions_recognition": { + "start": "શરૂ કરો", + "please_show_this": "આ ભાવનું દર્શાવો", + "emotions": { + "anger": "ક્રોધ", + "contempt": "અવમાન", + "disgust": "ઘૃણા", + "fear": "ડર", + "happiness": "આનંદ", + "neutral": "સ્થિર", + "sadness": "ઉદાસી", + "surprise": "આશ્ચર્ય" + } + } } } diff --git a/src/translations/locales/he.json b/src/translations/locales/he.json index d58a7c5fc..eff972e3e 100644 --- a/src/translations/locales/he.json +++ b/src/translations/locales/he.json @@ -1000,5 +1000,56 @@ } } } + }, + "face_auth": { + "header": "אימות פנים", + "title": "שלב KYC #1", + "description": "עבור חוויית משתמש חלקה, אנחנו מוסיפים אימות פנים. שלב זה משמש כ[[:bold]]שלב מוקדם #1 KYC[[/:bold]], המכין אתכם לאימות נצל בעתיד ברשת הראשית. \n\nאשרו את הזהות שלכם היום כדי לוודא מסע חלק הליכם הקדימה!", + "consent": "אני מסכים עם [[:link]]תנאי השימוש של KYC של Ice[[/:link]].", + "confirmation": "אני מאשר שהזהות שאני משתמש בה לאימות זה תהיה אותה הזהות הקשורה לחשבון זה, ולא יהיה ניתן לשנות זאת מאוחר יותר.", + "auth_status": { + "loading": { + "description": "אנא המתינו בזמן שאנחנו מאמתים אתכם..." + }, + "success": { + "title": "✅ אימות הצליח", + "description": "תודה על אימות הזהות שלכם. הסשן שלכם יתחיל כעת.", + "action": "סיים" + }, + "failure": { + "title": "❌ אימות נכשל", + "description": "לא הצלחנו לאמת את הזהות שלכם. אנא הוודאו שישנה הארות טובה ושהפנים שלכם גלויות לחלוטין, ונסו שוב.", + "action": "נסה שוב" + }, + "try_later": { + "title": "❌ אימות נכשל", + "description": "לא הצלחנו לאמת את הזהות שלכם. אנא הוודאו שישנה הארות טובה ושהפנים שלכם גלויות לחלוטין, ונסו שוב בעוד שעה.", + "action": "סגור" + }, + "continue": { + "title": "\uD83D\uDE01 אימות רגשי KYC", + "description": "עבודה טובה! השלב הראשון הושלם בצילום תמונה עצמית. כעת, בואו נמשיך לפי הרגשות המוצגים להמשך התהליך של KYC.", + "action": "המשך" + }, + "banned": { + "title": "❌ הכרנת המכריזה מנוטרלת", + "description": "זיהינו שהזהות שלכם מקושרת לחשבון אחר. אם חשבון זה הוא טעות, אנא שלחו אימייל אל [[:bold]]feedback@ice.io[[/:bold]] עם השם הכינוי שלכם וצילום עצמי באיכות גבוהה.", + "action": "סגור" + } + }, + "emotions_recognition": { + "start": "התחלה", + "please_show_this": "אנא הציגו רגש זה", + "emotions": { + "anger": "כעס", + "contempt": "בוז", + "disgust": "געגוע", + "fear": "פחד", + "happiness": "שמחה", + "neutral": "נייטרלי", + "sadness": "עצב", + "surprise": "הפתעה" + } + } } } diff --git a/src/translations/locales/hi.json b/src/translations/locales/hi.json index 7a2f75002..1677bee55 100644 --- a/src/translations/locales/hi.json +++ b/src/translations/locales/hi.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "फेस ऑथेंटिकेशन", + "title": "KYC स्टेप #1", + "description": "एक सुविधाजनक अनुभव के लिए, हम फेस ऑथेंटिकेशन जोड़ रहे हैं। यह स्टेप आपके [[:bold]]पहले स्टेप #1 KYC[[/:bold]] के रूप में कार्य करता है, आपको भविष्य के मेननेट के लिए सरलीकृत सत्यापन के लिए तैयार करता है। \n\nआगे की स्वरुप सत्यापन के लिए आज ही अपनी पहचान की पुष्टि करें!", + "consent": "मैं सहमत हूं [[:link]]Ice KYC नियम और शर्तों[[/:link]] के साथ।", + "confirmation": "मैं पुष्टि करता हूं कि इस प्रमाणीकरण के लिए मैं जिस पहचान का उपयोग कर रहा हूं, वही एक खाते के साथ जुड़ा हुआ होगा, और इस चयन को बाद में नहीं बदला जा सकता है।", + "auth_status": { + "loading": { + "description": "कृपया हम आपकी पुष्टि करते वक़्त प्रतीक्षा करें..." + }, + "success": { + "title": "✅ पुष्टि सफल", + "description": "आपकी पहचान पुष्टि करने के लिए धन्यवाद। आपका चेक-इन सत्र अब शुरू होगा।", + "action": "कृतिक" + }, + "failure": { + "title": "❌ पुष्टि असफल", + "description": "हम आपकी पहचान पुष्टि नहीं कर सके। कृपया अच्छी बत्ती और अपने चेहरे को पूरी तरह से दिखाई देने का सुनिश्चित करें, और फिर से कोशिश करें।", + "action": "पुनः प्रयास करें" + }, + "try_later": { + "title": "❌ पुष्टि असफल", + "description": "हम आपकी पहचान पुष्टि नहीं कर सके। कृपया अच्छी बत्ती और अपने चेहरे को पूरी तरह से दिखाई देने का सुनिश्चित करें, और फिर से 1 घंटे बाद कोशिश करें।", + "action": "बंद करें" + }, + "continue": { + "title": "\uD83D\uDE01 KYC भावना सत्यापन", + "description": "महान काम! आपने एक सेल्फी लेने के द्वारा पहले स्टेप को पूरा किया है। अब, आपकी KYC प्रक्रिया को जारी रखने के लिए प्रदर्शित भावनाओं का पालन करके आगे बढ़ते हैं।", + "action": "जारी रखें" + }, + "banned": { + "title": "❌ खनन निष्क्रिय", + "description": "हमने पहचान किया है कि आपकी पहचान दूसरे खाते से जुड़ी हुई है। अगर आपको लगता है कि यह एक त्रुटि है, तो कृपया अपने उपनाम और एक उच्च गुणवत्ता वाली सेल्फी के साथ [[:bold]]feedback@ice.io[[/:bold]] पर ईमेल भेजें।", + "action": "बंद करें" + } + }, + "emotions_recognition": { + "start": "शुरू करें", + "please_show_this": "कृपया इस भावना को दिखाएं", + "emotions": { + "anger": "रोष", + "contempt": "निन्दा", + "disgust": "घृणा", + "fear": "डर", + "happiness": "खुशी", + "neutral": "न्यूट्रल", + "sadness": "दुख", + "surprise": "आश्चर्य" + } + } } } diff --git a/src/translations/locales/hu.json b/src/translations/locales/hu.json index 000124642..6638f3ebd 100644 --- a/src/translations/locales/hu.json +++ b/src/translations/locales/hu.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Arcfelismerési Azonosítás", + "title": "KYC Lépés #1", + "description": "Egy zökkenőmentes élmény érdekében hozzáadunk egy arcfelismerési azonosítást. Ez a lépés az [[:bold]]első KYC lépés[[/:bold]] szerepét tölti be, hogy felkészítsen a jövőbeli Mainnet számára tervezett egyszerűsített ellenőrzésre. \n\nAzonosítsa magát ma, hogy biztosítsa egy sima út előtt álló utazását!", + "consent": "Egyetértek az Ice [[:link]]KYC Feltételeivel és Állapotokkal[[/:link]].", + "confirmation": "Megerősítem, hogy az azonosítást végző személyazonosság azonos lesz azzal, amelyik ehhez a fiókhoz van társítva, és ezt a választást később nem lehet megváltoztatni.", + "auth_status": { + "loading": { + "description": "Kérem, várjon, amíg azonosítjuk Önt..." + }, + "success": { + "title": "✅ Sikeres Azonosítás", + "description": "Köszönjük, hogy megerősítette az azonosságát. Az ellenőrzési munkamenet most elkezdődik.", + "action": "Kész" + }, + "failure": { + "title": "❌ Azonosítás Sikertelen", + "description": "Nem tudtuk ellenőrizni az azonosságát. Kérjük, győződjön meg arról, hogy megfelelő a megvilágítás, és az arcának teljesen láthatónak kell lennie, majd próbálja újra.", + "action": "Újra" + }, + "try_later": { + "title": "❌ Azonosítás Sikertelen", + "description": "Nem tudtuk ellenőrizni az azonosságát. Kérjük, győződjön meg arról, hogy megfelelő a megvilágítás, és az arcának teljesen láthatónak kell lennie, majd próbálja újra 1 óra múlva.", + "action": "Bezárás" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Érzelem Felismerés", + "description": "Gratulálunk! Elvégezte az első lépést, hogy elkészítse a selfie-jét. Most folytassuk a KYC folyamatot az ábrázolt érzelmek követésével.", + "action": "Folytatás" + }, + "banned": { + "title": "❌ Bányászat Letiltva", + "description": "Azonosítottuk, hogy az azonossága egy másik fiókhoz van társítva. Ha úgy gondolja, hogy ez egy hiba, kérjük, küldjön egy e-mailt a [[:bold]]feedback@ice.io[[/:bold]] címre a becenévével és egy magas minőségű selfie-val.", + "action": "Bezárás" + } + }, + "emotions_recognition": { + "start": "Indítás", + "please_show_this": "Kérjük, mutassa be ezt az érzelmet", + "emotions": { + "anger": "harag", + "contempt": "megvetés", + "disgust": "undor", + "fear": "félelem", + "happiness": "boldogság", + "neutral": "semleges", + "sadness": "szomorúság", + "surprise": "meglepetés" + } + } } } diff --git a/src/translations/locales/id.json b/src/translations/locales/id.json index 5d046790a..607464425 100644 --- a/src/translations/locales/id.json +++ b/src/translations/locales/id.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Otentikasi Wajah", + "title": "Langkah KYC #1", + "description": "Untuk pengalaman yang lancar, kami menambahkan otentikasi wajah. Langkah ini berfungsi sebagai [[:bold]]Langkah #1 KYC awal[[/:bold]], mempersiapkan Anda untuk verifikasi yang lebih mudah di Mainnet masa depan. \n\nKonfirmasi identitas Anda hari ini untuk memastikan perjalanan yang mulus ke depan!", + "consent": "Saya setuju dengan [[:link]]Syarat dan Ketentuan KYC Ice[[/:link]].", + "confirmation": "Saya mengkonfirmasi bahwa identitas yang saya gunakan untuk otentikasi ini akan sama dengan yang terkait dengan akun ini, dan pilihan ini tidak dapat diubah nanti.", + "auth_status": { + "loading": { + "description": "Harap tunggu saat kami mengotentikasi Anda..." + }, + "success": { + "title": "✅ Otentikasi Berhasil", + "description": "Terima kasih telah mengkonfirmasi identitas Anda. Sesi pemeriksaan Anda akan dimulai sekarang.", + "action": "Selesai" + }, + "failure": { + "title": "❌ Otentikasi Gagal", + "description": "Kami tidak dapat memverifikasi identitas Anda. Pastikan pencahayaan cukup dan wajah Anda sepenuhnya terlihat, kemudian coba lagi.", + "action": "Coba Lagi" + }, + "try_later": { + "title": "❌ Otentikasi Gagal", + "description": "Kami tidak dapat memverifikasi identitas Anda. Pastikan pencahayaan cukup dan wajah Anda sepenuhnya terlihat, kemudian coba lagi dalam 1 jam.", + "action": "Tutup" + }, + "continue": { + "title": "\uD83D\uDE01 Verifikasi Emosi KYC", + "description": "Bagus! Anda telah menyelesaikan langkah pertama dengan mengambil selfie. Sekarang, mari lanjutkan dengan mengikuti emosi yang ditampilkan untuk melanjutkan proses KYC Anda.", + "action": "Lanjutkan" + }, + "banned": { + "title": "❌ Penambangan Dinonaktifkan", + "description": "Kami telah mengidentifikasi bahwa identitas Anda terkait dengan akun lain. Jika Anda menganggap ini sebagai kesalahan, harap kirim email ke [[:bold]]feedback@ice.io[[/:bold]] dengan nama panggilan Anda dan selfie berkualitas tinggi.", + "action": "Tutup" + } + }, + "emotions_recognition": { + "start": "Mulai", + "please_show_this": "Tunjukkan emosi ini, tolong", + "emotions": { + "anger": "marah", + "contempt": "meremehkan", + "disgust": "jijik", + "fear": "ketakutan", + "happiness": "kebahagiaan", + "neutral": "netral", + "sadness": "kesedihan", + "surprise": "kejutan" + } + } } } diff --git a/src/translations/locales/it.json b/src/translations/locales/it.json index fe20a5ba2..e567d5c93 100644 --- a/src/translations/locales/it.json +++ b/src/translations/locales/it.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Autenticazione del Viso", + "title": "Passo KYC #1", + "description": "Per un'esperienza senza intoppi, stiamo aggiungendo l'autenticazione del viso. Questo passo funge da [[:bold]]Passo #1 KYC iniziale[[/:bold]], preparandoti per una verifica semplificata nella futura Mainnet. \n\nConferma la tua identità oggi per assicurarti un percorso agevole in avanti!", + "consent": "Accetto i [[:link]]Termini e Condizioni KYC di Ice[[/:link]].", + "confirmation": "Confermo che l'identità che sto utilizzando per questa autenticazione sarà la stessa associata a questo account e che questa scelta non potrà essere cambiata in seguito.", + "auth_status": { + "loading": { + "description": "Attendere mentre ti autentichiamo..." + }, + "success": { + "title": "✅ Autenticazione Riuscita", + "description": "Grazie per aver confermato la tua identità. La tua sessione di verifica inizierà ora.", + "action": "Fatto" + }, + "failure": { + "title": "❌ Autenticazione Fallita", + "description": "Non siamo riusciti a verificare la tua identità. Assicurati di avere una buona illuminazione e che il tuo viso sia completamente visibile, poi riprova.", + "action": "Riprova" + }, + "try_later": { + "title": "❌ Autenticazione Fallita", + "description": "Non siamo riusciti a verificare la tua identità. Assicurati di avere una buona illuminazione e che il tuo viso sia completamente visibile, poi riprova tra 1 ora.", + "action": "Chiudi" + }, + "continue": { + "title": "\uD83D\uDE01 Verifica Emozioni KYC", + "description": "Ottimo lavoro! Hai completato il primo passo scattando un selfie. Ora procediamo seguendo le emozioni visualizzate per continuare il processo KYC.", + "action": "Continua" + }, + "banned": { + "title": "❌ Mining Disabilitato", + "description": "Abbiamo identificato che la tua identità è associata a un altro account. Se pensi che si tratti di un errore, invia un'email a [[:bold]]feedback@ice.io[[/:bold]] con il tuo nickname e un selfie di alta qualità.", + "action": "Chiudi" + } + }, + "emotions_recognition": { + "start": "Inizia", + "please_show_this": "Mostra questa emozione, per favore", + "emotions": { + "anger": "rabbia", + "contempt": "disprezzo", + "disgust": "disgusto", + "fear": "paura", + "happiness": "felicità", + "neutral": "neutrale", + "sadness": "tristezza", + "surprise": "sorpresa" + } + } } } diff --git a/src/translations/locales/ja.json b/src/translations/locales/ja.json index 08d2b8844..3ce08fd48 100644 --- a/src/translations/locales/ja.json +++ b/src/translations/locales/ja.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "顔認証", + "title": "KYC ステップ #1", + "description": "スムーズなエクスペリエンスのため、顔認証を追加しています。このステップは将来のメインネットのスムーズな確認のための準備として、[[:bold]]初期のステップ #1 KYC[[/:bold]] の役割を果たします。\n\n今日、あなたのアイデンティティを確認して、スムーズな旅を確保してください!", + "consent": "私はIceの[[:link]]KYC利用規約[[/:link]]に同意します。", + "confirmation": "この認証に使用するアイデンティティは、このアカウントに関連付けられているものと同じであり、後で変更できないことを確認します。", + "auth_status": { + "loading": { + "description": "認証中、お待ちください..." + }, + "success": { + "title": "✅ 認証成功", + "description": "アイデンティティを確認していただき、ありがとうございます。チェックインセッションが開始されます。", + "action": "完了" + }, + "failure": { + "title": "❌ 認証失敗", + "description": "アイデンティティを確認できませんでした。適切な照明が確保され、顔が完全に見えることを確認し、再試行してください。", + "action": "再試行" + }, + "try_later": { + "title": "❌ 認証失敗", + "description": "アイデンティティを確認できませんでした。適切な照明が確保され、顔が完全に見えることを確認し、1時間後に再試行してください。", + "action": "閉じる" + }, + "continue": { + "title": "\uD83D\uDE01 KYC 感情認識", + "description": "素晴らしい仕事です!セルフィーを撮影して最初のステップを完了しました。次に、表示された感情に従ってKYCプロセスを続行しましょう。", + "action": "続行" + }, + "banned": { + "title": "❌ マイニングが無効になっています", + "description": "あなたのアイデンティティが別のアカウントに関連付けられていることが特定されました。これが誤りであると思われる場合、[[:bold]]feedback@ice.io[[/:bold]]にニックネームと高品質のセルフィーを含むメールを送信してください。", + "action": "閉じる" + } + }, + "emotions_recognition": { + "start": "開始", + "please_show_this": "この感情を表示してください", + "emotions": { + "anger": "怒り", + "contempt": "軽蔑", + "disgust": "嫌悪", + "fear": "恐れ", + "happiness": "幸福", + "neutral": "中立", + "sadness": "悲しみ", + "surprise": "驚き" + } + } } } diff --git a/src/translations/locales/jv.json b/src/translations/locales/jv.json index c05ebe488..d3615d136 100644 --- a/src/translations/locales/jv.json +++ b/src/translations/locales/jv.json @@ -906,5 +906,56 @@ } } } + }, + "face_auth": { + "header": "Pangenal Wajah", + "title": "Langkah KYC #1", + "description": "Kanggo pengalaman sing lancar, kita nambahake pangenal wajah. Langkah iki dadi [[:bold]]Langkah #1 KYC awal[[/:bold]], njalari sampeyan kanggo verifikasi sing mudhah ing Mainnet ing masa depan. \n\nKonfirmasi jati diri sampeyan saiki kanggo njamin perjalanan sing lancar menyang depan!", + "consent": "Aku setuju karo [[:link]]Syarat lan Ketentuan KYC Ice[[/:link]].", + "confirmation": "Aku konfirmasi yen jati diri sing aku gunakake kanggo pangenal iki bakal ana ing akun iki, lan pilihan iki ora bisa diganti nanti.", + "auth_status": { + "loading": { + "description": "Mangga nunggu sajeroning kita nyambungake sampeyan..." + }, + "success": { + "title": "✅ Pangenal Sukses", + "description": "Matur nuwun kanggo konfirmasi jati diri sampeyan. Sesi cek-in sampeyan bakal dimulai saiki.", + "action": "Rampung" + }, + "failure": { + "title": "❌ Pangenal Gagal", + "description": "Kita ora bisa ngecek jati diri sampeyan. Pastikan penerangan apik lan wajah sampeyan dadi pethak banget, trus coba maneh.", + "action": "Coba Maneh" + }, + "try_later": { + "title": "❌ Pangenal Gagal", + "description": "Kita ora bisa ngecek jati diri sampeyan. Pastikan penerangan apik lan wajah sampeyan dadi pethak banget, trus coba maneh sajroning 1 jam.", + "action": "Tutup" + }, + "continue": { + "title": "\uD83D\uDE01 Verifikasi Emosi KYC", + "description": "Kerja apik! Sampeyan wis rampung langkah pertama dening ngrusak swa potret. Ayo terusake dening ngikutin emosi sing ditampilake kanggo lanjutake proses KYC sampeyan.", + "action": "Terusake" + }, + "banned": { + "title": "❌ Mining Ditilak", + "description": "Kita ngerti yen jati diri sampeyan kaitake karo akun liyane. Yen sampeyan mikir iki sing salah, monggo kirim email menyang [[:bold]]feedback@ice.io[[/:bold]] karo jeneng panggilan lan swa potret sing apik.", + "action": "Tutup" + } + }, + "emotions_recognition": { + "start": "Mulai", + "please_show_this": "Mangga njaluk emosi iki", + "emotions": { + "anger": "amarah", + "contempt": "sirah", + "disgust": "welek", + "fear": "kiwa", + "happiness": "seneng", + "neutral": "netral", + "sadness": "kesedihan", + "surprise": "kejutan" + } + } } } diff --git a/src/translations/locales/kn.json b/src/translations/locales/kn.json index cf0e6b506..43321f788 100644 --- a/src/translations/locales/kn.json +++ b/src/translations/locales/kn.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "ಮುಖದ ದೃಢೀಕರಣ", + "title": "KYC ಹಂತ #1", + "description": "ತಡೆರಹಿತ ಅನುಭವಕ್ಕಾಗಿ, ನಾವು ಮುಖದ ದೃಢೀಕರಣವನ್ನು ಸೇರಿಸುತ್ತಿದ್ದೇವೆ. ಈ ಹಂತವು ನಿಮ್ಮ [[:bold]]ಆರಂಭಿಕ ಹಂತ #1 KYC[[/:bold]] ನಂತೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ, ಭವಿಷ್ಯದ Mainnet ಗಾಗಿ ಸುವ್ಯವಸ್ಥಿತ ಪರಿಶೀಲನೆಗಾಗಿ ನಿಮ್ಮನ್ನು ಸಿದ್ಧಪಡಿಸುತ್ತದೆ. \n\nಮುಂದೆ ಸುಗಮ ಪ್ರಯಾಣವನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು ನಿಮ್ಮ ಗುರುತನ್ನು ಇಂದೇ ದೃಢೀಕರಿಸಿ!", + "consent": "ನಾನು Ice [[:link]]KYC ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳನ್ನು[[/:link]] ಒಪ್ಪುತ್ತೇನೆ.", + "confirmation": "ಈ ದೃಢೀಕರಣಕ್ಕಾಗಿ ನಾನು ಬಳಸುತ್ತಿರುವ ಗುರುತನ್ನು ಈ ಖಾತೆಯೊಂದಿಗೆ ಸಂಯೋಜಿತವಾಗಿರುವ ಒಂದೇ ಗುರುತನ್ನು ನಾನು ದೃಢೀಕರಿಸುತ್ತೇನೆ ಮತ್ತು ಈ ಆಯ್ಕೆಯನ್ನು ನಂತರ ಬದಲಾಯಿಸಲಾಗುವುದಿಲ್ಲ.", + "auth_status": { + "loading": { + "description": "ನಾವು ನಿಮ್ಮನ್ನು ದೃಢೀಕರಿಸುವವರೆಗೆ ದಯವಿಟ್ಟು ನಿರೀಕ್ಷಿಸಿ..." + }, + "success": { + "title": "✅ ದೃಢೀಕರಣ ಯಶಸ್ವಿಯಾಗಿದೆ", + "description": "ನಿಮ್ಮ ಗುರುತನ್ನು ದೃಢೀಕರಿಸಿದ್ದಕ್ಕಾಗಿ ಧನ್ಯವಾದಗಳು. ನಿಮ್ಮ ಚೆಕ್-ಇನ್ ಸೆಶನ್ ಈಗ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ.", + "action": "ಮುಗಿದಿದೆ" + }, + "failure": { + "title": "❌ ದೃಢೀಕರಣ ವಿಫಲವಾಗಿದೆ", + "description": "ನಿಮ್ಮ ಗುರುತನ್ನು ಪರಿಶೀಲಿಸಲು ನಮಗೆ ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ದಯವಿಟ್ಟು ಉತ್ತಮ ಬೆಳಕನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ನಿಮ್ಮ ಮುಖವು ಸಂಪೂರ್ಣವಾಗಿ ಗೋಚರಿಸುತ್ತದೆ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ, ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", + "action": "ಮರುಪ್ರಯತ್ನಿಸಿ" + }, + "try_later": { + "title": "❌ ದೃಢೀಕರಣ ವಿಫಲವಾಗಿದೆ", + "description": "ನಿಮ್ಮ ಗುರುತನ್ನು ಪರಿಶೀಲಿಸಲು ನಮಗೆ ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ದಯವಿಟ್ಟು ಉತ್ತಮ ಬೆಳಕನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ನಿಮ್ಮ ಮುಖವು ಸಂಪೂರ್ಣವಾಗಿ ಗೋಚರಿಸುತ್ತದೆ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ, ನಂತರ 1 ಗಂಟೆಯ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", + "action": "ಮುಚ್ಚಿ" + }, + "continue": { + "title": "\uD83D\uDE01 KYC ಭಾವನೆ ಪರಿಶೀಲನೆ", + "description": "ಉತ್ತಮ ಕೆಲಸ! ಸೆಲ್ಫಿ ತೆಗೆದುಕೊಳ್ಳುವ ಮೂಲಕ ನೀವು ಮೊದಲ ಹಂತವನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ. ಈಗ, ನಿಮ್ಮ KYC ಪ್ರಕ್ರಿಯೆಯನ್ನು ಮುಂದುವರಿಸಲು ಪ್ರದರ್ಶಿಸಲಾದ ಭಾವನೆಗಳನ್ನು ಅನುಸರಿಸುವ ಮೂಲಕ ಮುಂದುವರಿಯೋಣ.", + "action": "ಮುಂದುವರಿಸಿ" + }, + "banned": { + "title": "❌ ಗಣಿಗಾರಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ", + "description": "ನಿಮ್ಮ ಗುರುತನ್ನು ಮತ್ತೊಂದು ಖಾತೆಯೊಂದಿಗೆ ಸಂಯೋಜಿಸಲಾಗಿದೆ ಎಂದು ನಾವು ಗುರುತಿಸಿದ್ದೇವೆ. ಇದು ದೋಷ ಎಂದು ನೀವು ಭಾವಿಸಿದರೆ, ದಯವಿಟ್ಟು ನಿಮ್ಮ ಅಡ್ಡಹೆಸರು ಮತ್ತು ಉತ್ತಮ ಗುಣಮಟ್ಟದ ಸೆಲ್ಫಿಯೊಂದಿಗೆ [[:bold]]feedback@ice.io[[/:bold]] ಗೆ ಇಮೇಲ್ ಕಳುಹಿಸಿ.", + "action": "ಮುಚ್ಚಿ" + } + }, + "emotions_recognition": { + "start": "ಪ್ರಾರಂಭಿಸಿ", + "please_show_this": "ದಯವಿಟ್ಟು ಈ ಭಾವನೆಯನ್ನು ತೋರಿಸಿ", + "emotions": { + "anger": "ಕೋಪ", + "contempt": "ತಿರಸ್ಕಾರ", + "disgust": "ಅಸಹ್ಯ", + "fear": "ಭಯ", + "happiness": "ಸಂತೋಷ", + "neutral": "ತಟಸ್ಥ", + "sadness": "ದುಃಖ", + "surprise": "ಆಶ್ಚರ್ಯ" + } + } } } diff --git a/src/translations/locales/ko.json b/src/translations/locales/ko.json index 5f287e337..13d4487e5 100644 --- a/src/translations/locales/ko.json +++ b/src/translations/locales/ko.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "얼굴 인증", + "title": "KYC 단계 #1", + "description": "원활한 경험을 위해 얼굴 인증을 추가하고 있습니다. 이 단계는 [[:bold]]초기 KYC 단계 #1[[/:bold]]로서, 미래 Mainnet에서 간편한 확인을 위해 여러분을 준비합니다. \n\n오늘 자신의 신원을 확인하여 앞으로의 원활한 여정을 보장하세요!", + "consent": "저는 Ice [[:link]]KYC 약관 및 조건[[/:link]]에 동의합니다.", + "confirmation": "이 인증에 사용하는 신분증은 이 계정과 관련된 것과 동일하며 나중에 변경할 수 없음을 확인합니다.", + "auth_status": { + "loading": { + "description": "인증 중이니 기다려 주십시오..." + }, + "success": { + "title": "✅ 인증 성공", + "description": "신원을 확인해 주셔서 감사합니다. 체크인 세션이 지금 시작됩니다.", + "action": "완료" + }, + "failure": { + "title": "❌ 인증 실패", + "description": "우리는 여러분의 신원을 확인할 수 없었습니다. 적절한 조명이 확보되어 있고 얼굴이 완전히 보이는지 확인한 후 다시 시도하십시오.", + "action": "재시도" + }, + "try_later": { + "title": "❌ 인증 실패", + "description": "우리는 여러분의 신원을 확인할 수 없었습니다. 적절한 조명이 확보되어 있고 얼굴이 완전히 보이는지 확인한 후 1 시간 뒤에 다시 시도하십시오.", + "action": "닫기" + }, + "continue": { + "title": "\uD83D\uDE01 KYC 감정 인식", + "description": "좋은 일했어요! 셀피를 찍어 첫 번째 단계를 완료했습니다. 이제 표시된 감정을 따라 KYC 프로세스를 계속하세요.", + "action": "계속" + }, + "banned": { + "title": "❌ 채굴 비활성화", + "description": "당신의 신원이 다른 계정과 연관되어 있음을 확인했습니다. 이것이 오류라고 생각한다면 [[:bold]]feedback@ice.io[[/:bold]]로 닉네임과 고품질 셀피를 포함한 이메일을 보내 주세요.", + "action": "닫기" + } + }, + "emotions_recognition": { + "start": "시작", + "please_show_this": "이 감정을 보여 주세요", + "emotions": { + "anger": "분노", + "contempt": "경멸", + "disgust": "역겨움", + "fear": "두려움", + "happiness": "행복", + "neutral": "중립", + "sadness": "슬픔", + "surprise": "놀람" + } + } } } diff --git a/src/translations/locales/mr.json b/src/translations/locales/mr.json index 08bbe1e09..e29a3678a 100644 --- a/src/translations/locales/mr.json +++ b/src/translations/locales/mr.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "चेहरा प्रमाणीकरण", + "title": "KYC पायवड #1", + "description": "संकटिक अनुभवासाठी, आम्ही चेहरा प्रमाणीकरण जोडतो. हे पायवड आपल्याला मुख्यनेटच्या भविष्यातील सुसंगत पुष्कळ सत्यापनासाठी तयार करणार आहे, [[:bold]]प्रारंभिक पायवड #1 KYC[[/:bold]] म्हणून. \n\nआपल्याला आज आपली पहचान सुनिश्चित करण्यात आपले साहित्य अगोदरच्या मार्गाच्या यात्रेला दाखविण्यात आनंद होईल!", + "consent": "मी Ice [[:link]]KYC अटी[[/:link]]सह सहमत आहे.", + "confirmation": "माझ्या ईमेल सत्यापनसाठी आपला वापरलेला पर्याय हा त्याच्या खात्याशी संबंधित आहे आणि याची नंतर बदल केली जाऊ शकत नाही हे पुष्टी करतो.", + "auth_status": { + "loading": { + "description": "कृपया आपली पुष्टीकरण करताना प्रतीक्षा करा..." + }, + "success": { + "title": "✅ पुष्टीकरण सफळ", + "description": "आपल्याला आपल्या पर्यायाची पुष्टी करण्यासाठी धन्यवाद. आपल्या तपास सत्र सद्याची पुरातात्मक होईल.", + "action": "संपलं" + }, + "failure": { + "title": "❌ पुष्टीकरण असफळ", + "description": "आपल्याची पर्याय सत्यापित करू शकली नाही. कृपया चांदणी आणि आपला चेहरा पूर्णत: दिसल्यास खात्री करा, नंतर पुन्हा प्रयत्न करा.", + "action": "पुन्हा प्रयत्न करा" + }, + "try_later": { + "title": "❌ पुष्टीकरण असफळ", + "description": "आपल्याची पर्याय सत्यापित करू शकली नाही. कृपया चांदणी आणि आपला चेहरा पूर्णत: दिसल्यास खात्री करा, नंतर 1 तासांनी पुन्हा प्रयत्न करा.", + "action": "बंद करा" + }, + "continue": { + "title": "\uD83D\uDE01 KYC भावना सत्यापन", + "description": "सुंदर काम! आपल्याने सेल्फी घेतल्यानंतर पहिल्या पायवडला पूर्ण केलंय. आता आपल्या KYC प्रक्रियेच्या सूचीत दिलेल्या भावनांना अनुसरून आपला प्रक्रियेचा अद्यतित करा.", + "action": "सुरू करा" + }, + "banned": { + "title": "❌ मायनिंग अक्षम", + "description": "आम्हाला आपल्याची पर्याय इतर खात्यांसह संबंधित आहे. आपल्याला आपल्याच्या आडनाविक आणि उच्च गुणवत्तेच्या सेल्फीसह त्याच्याकिवा [[:bold]]feedback@ice.io[[/:bold]] वर एक ईमेल पाठवण्याची कृपया करा.", + "action": "बंद करा" + } + }, + "emotions_recognition": { + "start": "सुरू करा", + "please_show_this": "कृपया ह्या भावना दाखवा", + "emotions": { + "anger": "क्रोध", + "contempt": "निरादर", + "disgust": "विमुक्ती", + "fear": "भीती", + "happiness": "आनंद", + "neutral": "अनभिग्य", + "sadness": "दुःख", + "surprise": "आश्चर्य" + } + } } } diff --git a/src/translations/locales/ms.json b/src/translations/locales/ms.json index 05b00f7dc..a2c2d6266 100644 --- a/src/translations/locales/ms.json +++ b/src/translations/locales/ms.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Pengesahan Wajah", + "title": "Langkah KYC #1", + "description": "Untuk pengalaman yang lancar, kami menambah pengesahan wajah. Langkah ini berperanan sebagai [[:bold]]Langkah #1 KYC awal[[/:bold]], menyediakan anda untuk pengesahan yang lebih pantas di dalam Mainnet masa depan. \n\nSahkan identiti anda hari ini untuk memastikan perjalanan yang lancar ke hadapan!", + "consent": "Saya bersetuju dengan [[:link]]Terma dan Syarat KYC Ice[[/:link]].", + "confirmation": "Saya mengesahkan bahawa identiti yang saya gunakan untuk pengesahan ini adalah sama dengan yang berkaitan dengan akaun ini, dan pilihan ini tidak boleh diubah kemudian.", + "auth_status": { + "loading": { + "description": "Sila tunggu sementara kami mengesahkan anda..." + }, + "success": { + "title": "✅ Pengesahan Berjaya", + "description": "Terima kasih kerana mengesahkan identiti anda. Sesi daftar masuk anda akan bermula sekarang.", + "action": "Selesai" + }, + "failure": { + "title": "❌ Pengesahan Gagal", + "description": "Kami tidak dapat mengesahkan identiti anda. Sila pastikan pencahayaan yang baik dan bahawa wajah anda dapat dilihat sepenuhnya, kemudian cuba lagi.", + "action": "Cuba Semula" + }, + "try_later": { + "title": "❌ Pengesahan Gagal", + "description": "Kami tidak dapat mengesahkan identiti anda. Sila pastikan pencahayaan yang baik dan bahawa wajah anda dapat dilihat sepenuhnya, kemudian cuba lagi dalam 1 jam.", + "action": "Tutup" + }, + "continue": { + "title": "\uD83D\uDE01 Pengesahan Emosi KYC", + "description": "Kerja yang baik! Anda telah menyelesaikan langkah pertama dengan mengambil selfie. Sekarang, mari terus dengan mengikuti emosi yang dipaparkan untuk meneruskan proses KYC anda.", + "action": "Teruskan" + }, + "banned": { + "title": "❌ Penambangan Dinonaktifkan", + "description": "Kami telah mengenal pasti bahawa identiti anda berkaitan dengan akaun lain. Jika anda menganggap ini kesalahan, sila hantar emel kepada [[:bold]]feedback@ice.io[[/:bold]] dengan nama panggilan anda dan selfie berkualiti tinggi.", + "action": "Tutup" + } + }, + "emotions_recognition": { + "start": "Mula", + "please_show_this": "Sila tunjukkan emosi ini", + "emotions": { + "anger": "marah", + "contempt": "cemburu", + "disgust": "jijik", + "fear": "takut", + "happiness": "kegembiraan", + "neutral": "neutral", + "sadness": "kesedihan", + "surprise": "terkejut" + } + } } } diff --git a/src/translations/locales/nb.json b/src/translations/locales/nb.json index e076878ec..b4c9d44ed 100644 --- a/src/translations/locales/nb.json +++ b/src/translations/locales/nb.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Ansiktsgodkjenning", + "title": "KYC Trinn #1", + "description": "For en sømløs opplevelse legger vi til ansiktsgodkjenning. Dette trinnet fungerer som din [[:bold]]tidlige trinn #1 KYC[[/:bold]], og forbereder deg for en enklere verifisering på fremtidig Mainnet. \n\nBekreft din identitet i dag for å sikre en smidig reise fremover!", + "consent": "Jeg samtykker til Ice [[:link]]KYC Vilkår og betingelser[[/:link]].", + "confirmation": "Jeg bekrefter at identiteten jeg bruker for denne godkjenningen vil være den samme som er knyttet til denne kontoen, og dette valget kan ikke endres senere.", + "auth_status": { + "loading": { + "description": "Vennligst vent mens vi godkjenner deg..." + }, + "success": { + "title": "✅ Godkjenning Vellykket", + "description": "Takk for at du bekreftet din identitet. Din innsjekkingsøkt vil starte nå.", + "action": "Ferdig" + }, + "failure": { + "title": "❌ Godkjenning Mislyktes", + "description": "Vi kunne ikke verifisere din identitet. Vennligst forsikre deg om god belysning og at ansiktet ditt er fullstendig synlig, og prøv igjen.", + "action": "Prøv på nytt" + }, + "try_later": { + "title": "❌ Godkjenning Mislyktes", + "description": "Vi kunne ikke verifisere din identitet. Vennligst forsikre deg om god belysning og at ansiktet ditt er fullstendig synlig, og prøv igjen om 1 time.", + "action": "Lukk" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Emosjonsgodkjenning", + "description": "Bra jobba! Du har fullført første trinn ved å ta en selfie. Nå, la oss fortsette ved å følge de viste følelsene for å fortsette KYC-prosessen din.", + "action": "Fortsett" + }, + "banned": { + "title": "❌ Mining Deaktivert", + "description": "Vi har identifisert at identiteten din er knyttet til en annen konto. Hvis du tror dette er en feil, vennligst send en e-post til [[:bold]]feedback@ice.io[[/:bold]] med ditt kallenavn og en høykvalitets selfie.", + "action": "Lukk" + } + }, + "emotions_recognition": { + "start": "Start", + "please_show_this": "Vennligst vis denne følelsen", + "emotions": { + "anger": "sinne", + "contempt": "forakt", + "disgust": "avsky", + "fear": "redsel", + "happiness": "glede", + "neutral": "nøytral", + "sadness": "tristhet", + "surprise": "overraskelse" + } + } } } diff --git a/src/translations/locales/nn.json b/src/translations/locales/nn.json index 11d96be82..cff4e3267 100644 --- a/src/translations/locales/nn.json +++ b/src/translations/locales/nn.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Ansiktsgodkjenning", + "title": "KYC Trinn #1", + "description": "For en sømløs opplevelse legger vi til ansiktsgodkjenning. Dette trinnet fungerer som din [[:bold]]tidlige trinn #1 KYC[[/:bold]], og forbereder deg for en enklere verifisering på fremtidig Mainnet. \n\nBekreft din identitet i dag for å sikre en smidig reise fremover!", + "consent": "Jeg samtykker til Ice [[:link]]KYC Vilkår og betingelser[[/:link]].", + "confirmation": "Jeg bekrefter at identiteten jeg bruker for denne godkjenningen vil være den samme som er knyttet til denne kontoen, og dette valget kan ikke endres senere.", + "auth_status": { + "loading": { + "description": "Vennligst vent mens vi godkjenner deg..." + }, + "success": { + "title": "✅ Godkjenning Vellykket", + "description": "Takk for at du bekreftet din identitet. Din innsjekkingsøkt vil starte nå.", + "action": "Ferdig" + }, + "failure": { + "title": "❌ Godkjenning Mislyktes", + "description": "Vi kunne ikke verifisere din identitet. Vennligst forsikre deg om god belysning og at ansiktet ditt er fullstendig synlig, og prøv igjen.", + "action": "Prøv på nytt" + }, + "try_later": { + "title": "❌ Godkjenning Mislyktes", + "description": "Vi kunne ikke verifisere din identitet. Vennligst forsikre deg om god belysning og at ansiktet ditt er fullstendig synlig, og prøv igjen om 1 time.", + "action": "Lukk" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Emosjonsgodkjenning", + "description": "Bra jobba! Du har fullført første trinn ved å ta en selfie. Nå, la oss fortsette ved å følge de viste følelsene for å fortsette KYC-prosessen din.", + "action": "Fortsett" + }, + "banned": { + "title": "❌ Mining Deaktivert", + "description": "Vi har identifisert at identiteten din er knyttet til en annen konto. Hvis du tror dette er en feil, vennligst send en e-post til [[:bold]]feedback@ice.io[[/:bold]] med ditt kallenavn og en høykvalitets selfie.", + "action": "Lukk" + } + }, + "emotions_recognition": { + "start": "Start", + "please_show_this": "Vennligst vis denne følelsen", + "emotions": { + "anger": "sinne", + "contempt": "forakt", + "disgust": "avsky", + "fear": "redsel", + "happiness": "glede", + "neutral": "nøytral", + "sadness": "tristhet", + "surprise": "overraskelse" + } + } } } diff --git a/src/translations/locales/pa.json b/src/translations/locales/pa.json index 5baa86afb..d2797e1af 100644 --- a/src/translations/locales/pa.json +++ b/src/translations/locales/pa.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "ਚਿਹਰਾ ਪ੍ਰਮਾਣੀਕਰਣ", + "title": "KYC ਸਟੈਪ #1", + "description": "ਇੱਕ ਸਮਰੂਪ ਅਨੁਭਵ ਲਈ, ਅਸੀਂ ਚਿਹਰਾ ਪ੍ਰਮਾਣੀਕਰਣ ਜੋੜ ਰਹੇ ਹਾਂ. ਇਹ ਸਟੈਪ ਤੁਹਾਡੇ [[:bold]]ਪਹਿਲਾ ਸਟੈਪ #1 KYC[[/:bold]] ਵਜੋਂ ਕੰਮ ਕਰਦਾ ਹੈ, ਜੋ ਤੁਹਾਨੂੰ ਆਗਾਮੀ ਮੈਨਨੈੱਟ ਵਿੱਚ ਸੁਵਿਧਾਪੂਰਕ ਪ੍ਰਮਾਣੀਕਰਣ ਲਈ ਤਿਆਰ ਕਰਦਾ ਹੈ. \n\nਆਪਣੀ ਪਛਾਣ ਆਜ ਹੀ ਪ੍ਰਮਾਣ ਕਰੋ ਅਤੇ ਸਮੁੰਦਰ ਯਾਤਰਾ ਨੂੰ ਮੁਸ਼ਕਿਲ ਸੰਜੋਗ ਦੇਣ ਲਈ!", + "consent": "ਮੈਂ ਆਈਸ [[:link]]KYC ਸ਼ਰਤਾਂ ਅਤੇ ਸ਼ਰਤਾਂ[[/:link]] ਦੇ ਸਾਥ ਸਹਿਮਤ ਹਾਂ।", + "confirmation": "ਮੈਂ ਪੁਸ਼ਟੀ ਕਰਦਾ ਹਾਂ ਕਿ ਇਸ ਪ੍ਰਮਾਣੀਕਰਣ ਲਈ ਮੈਨੇ ਜੋ ਅਸਲ ਹੈ ਉਹੀ ਇਸ ਖਾਤੇ ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ, ਅਤੇ ਇਸ ਚੋਣ ਨੂੰ ਬਾਅਦ ਵਿੱਚ ਨਹੀਂ ਬਦਲ ਸਕਦਾ।", + "auth_status": { + "loading": { + "description": "ਕਿਰਪਾ ਕਰਕੇ ਸਾਨੂੰ ਤੁਹਾਨੂੰ ਪ੍ਰਮਾਣ ਕਰਦੇ ਸਮੇਂ ਰੁਕਣ ਦੀ ਆਗਾਹੀ ਦੇਣ ਲਓ..." + }, + "success": { + "title": "✅ ਪ੍ਰਮਾਣੀਕਰਣ ਸਫਲ", + "description": "ਤੁਹਾਡੀ ਪਛਾਣ ਪ੍ਰਮਾਣੀਕਰਣ ਕਰਨ ਲਈ ਧੰਨਵਾਦ। ਤੁਹਾਡਾ ਚੈੱਕ-ਇਨ ਸੈਸ਼ਨ ਹੁਣ ਸ਼ੁਰੂ ਹੋਵੇਗਾ।", + "action": "ਮੁਕੰਮਲ" + }, + "failure": { + "title": "❌ ਪ੍ਰਮਾਣੀਕਰਣ ਅਸਫਲ", + "description": "ਅਸੀਂ ਤੁਹਾਡੀ ਪਛਾਣ ਪ੍ਰਮਾਣੀਕਰਣ ਨਹੀਂ ਕਰ ਸਕੇ। ਕਿਰਪਾ ਕਰਕੇ ਠੀਕ ਰੌਸ਼ਨੀ ਅਤੇ ਇਹ ਨਿਸਚਿਤ ਕਰੋ ਕਿ ਤੁਹਾਡਾ ਚਿਹਰਾ ਪੂਰੀ ਤਰ੍ਹਾਂ ਦਿਖ ਰਿਹਾ ਹੈ, ਫਿਰ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", + "action": "ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + }, + "try_later": { + "title": "❌ ਪ੍ਰਮਾਣੀਕਰਣ ਅਸਫਲ", + "description": "ਅਸੀਂ ਤੁਹਾਡੀ ਪਛਾਣ ਪ੍ਰਮਾਣੀਕਰਣ ਨਹੀਂ ਕਰ ਸਕੇ। ਕਿਰਪਾ ਕਰਕੇ ਠੀਕ ਰੌਸ਼ਨੀ ਅਤੇ ਇਹ ਨਿਸਚਿਤ ਕਰੋ ਕਿ ਤੁਹਾਡਾ ਚਿਹਰਾ ਪੂਰੀ ਤਰ੍ਹਾਂ ਦਿਖ ਰਿਹਾ ਹੈ, ਫਿਰ 1 ਘੰਟੇ ਬਾਅਦ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", + "action": "ਬੰਦ ਕਰੋ" + }, + "continue": { + "title": "\uD83D\uDE01 KYC ਇਮੋਸ਼ਨ ਪ੍ਰਮਾਣੀਕਰਣ", + "description": "ਬੜਾ ਵਧੀਆ ਕੰਮ! ਤੁਸੀਂ ਪਹਿਲੇ ਸਟੈਪ ਨੂੰ ਸੇਲਫੀ ਲੈ ਕੇ ਮੁਕੰਮਲ ਕਰ ਦਿੱਤਾ ਹੈ। ਹੁਣ, ਸਾਡੀ ਇਕਸਪ੍ਰੈਸ ਦਿਖਾਈ ਦਿੰਦੀ ਭਾਅਵਨਾਂ ਨੂੰ ਪੱਛਾ ਕਰਨ ਦਾ ਇਹ ਦਿਖਾਓ ਅਤੇ ਆਪਣੇ KYC ਪ੍ਰਕਿਰਿਆ ਨੂੰ ਜਾਰੀ ਰੱਖੋ।", + "action": "ਜਾਰੀ ਰੱਖੋ" + }, + "banned": { + "title": "❌ ਖਨਨ ਬੰਦ", + "description": "ਅਸੀਂ ਇਹ ਪਛਾਣ ਲਈ ਤੁਹਾਡੀ ਪਛਾਣ ਦੂਜੇ ਖਾਤੇ ਨਾਲ ਜੁੜੀ ਹੋਈ ਹੈ ਦਾ ਪਤਾ ਲਗਾਇਆ ਹੈ। ਜੇ ਤੁਸੀਂ ਇਸਨੂੰ ਇਕ ਗਲਤੀ ਸਮਝਦੇ ਹੋ ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ [[:bold]]feedback@ice.io[[/:bold]] ਨੂੰ ਆਪਣੇ ਨਿਕਨੇਮ ਅਤੇ ਉੱਚੀ ਗੁਣਵਤਾ ਵਾਲੀ ਸੇਲਫੀ ਦੇ ਨਾਲ ਇੱਕ ਈਮੇਲ ਭੇਜੋ।", + "action": "ਬੰਦ ਕਰੋ" + } + }, + "emotions_recognition": { + "start": "ਸ਼ੁਰੂ ਕਰੋ", + "please_show_this": "ਕਿਰਪਾ ਕਰਕੇ ਇਸ ਭਾਵਨਾ ਨੂੰ ਦਿਖਾਓ", + "emotions": { + "anger": "ਗੁਸਸਾ", + "contempt": "ਘਿਨ", + "disgust": "ਘਿਨ", + "fear": "ਡਰ", + "happiness": "ਖੁਸ਼ੀ", + "neutral": "ਨਿਊਟ੍ਰਲ", + "sadness": "ਦੁਖ", + "surprise": "ਸੁਰਪ੍ਰਾਈਜ਼" + } + } } } diff --git a/src/translations/locales/pl.json b/src/translations/locales/pl.json index 390a8ad66..c73b6af23 100644 --- a/src/translations/locales/pl.json +++ b/src/translations/locales/pl.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Autoryzacja twarzą", + "title": "KYC Krok #1", + "description": "Dla płynnego doświadczenia dodajemy autoryzację twarzą. Ten krok pełni funkcję [[:bold]]wczesnego Krok #1 KYC[[/:bold]], przygotowując cię do bardziej efektywnej weryfikacji w przyszłym Mainnet. \n\nPotwierdź dzisiaj swoją tożsamość, aby zapewnić płynną podróż w przyszłość!", + "consent": "Zgadzam się z [[:link]]Warunkami KYC Ice[[/:link]].", + "confirmation": "Potwierdzam, że tożsamość, którą używam do tej autoryzacji, będzie tą samą, która jest powiązana z tym kontem, i tego wyboru nie można później zmienić.", + "auth_status": { + "loading": { + "description": "Proszę czekać, aż autoryzujemy cię..." + }, + "success": { + "title": "✅ Autoryzacja udana", + "description": "Dziękujemy za potwierdzenie tożsamości. Sesja zameldowania rozpocznie się teraz.", + "action": "Zakończ" + }, + "failure": { + "title": "❌ Autoryzacja nieudana", + "description": "Nie mogliśmy zweryfikować twojej tożsamości. Upewnij się, że jest dobre oświetlenie i twarz jest w pełni widoczna, a następnie spróbuj ponownie.", + "action": "Spróbuj ponownie" + }, + "try_later": { + "title": "❌ Autoryzacja nieudana", + "description": "Nie mogliśmy zweryfikować twojej tożsamości. Upewnij się, że jest dobre oświetlenie i twarz jest w pełni widoczna, a następnie spróbuj ponownie za 1 godzinę.", + "action": "Zamknij" + }, + "continue": { + "title": "\uD83D\uDE01 Weryfikacja emocji KYC", + "description": "Świetna robota! Ukończyłeś pierwszy krok, robiąc sobie zdjęcie. Teraz kontynuujmy, kierując się wyświetlanymi emocjami, aby kontynuować proces KYC.", + "action": "Kontynuuj" + }, + "banned": { + "title": "❌ Wyłączono kopanie", + "description": "Zidentyfikowaliśmy, że twoja tożsamość jest powiązana z innym kontem. Jeśli uważasz, że to błąd, prześlij e-mail na adres [[:bold]]feedback@ice.io[[/:bold]] ze swoim pseudonimem i wysokiej jakości zdjęciem selfie.", + "action": "Zamknij" + } + }, + "emotions_recognition": { + "start": "Rozpocznij", + "please_show_this": "Proszę pokaż tę emocję", + "emotions": { + "anger": "gniew", + "contempt": "pogarda", + "disgust": "niesmak", + "fear": "strach", + "happiness": "szczęście", + "neutral": "neutralny", + "sadness": "smutek", + "surprise": "niespodzianka" + } + } } } diff --git a/src/translations/locales/pt.json b/src/translations/locales/pt.json index 34e4a94ff..f262f7769 100644 --- a/src/translations/locales/pt.json +++ b/src/translations/locales/pt.json @@ -994,5 +994,56 @@ } } } + }, + "face_auth": { + "header": "Autenticação Facial", + "title": "KYC Etapa #1", + "description": "Para uma experiência sem problemas, estamos adicionando a autenticação facial. Esta etapa atua como o seu [[:bold]]KYC Etapa #1 inicial[[/:bold]], preparando você para uma verificação simplificada no futuro Mainnet. \n\nConfirme sua identidade hoje para garantir uma jornada tranquila no futuro!", + "consent": "Eu concordo com os [[:link]]Termos e Condições KYC da Ice[[/:link]].", + "confirmation": "Eu confirmo que a identidade que estou usando para esta autenticação será a mesma associada a esta conta, e essa escolha não pode ser alterada posteriormente.", + "auth_status": { + "loading": { + "description": "Por favor, aguarde enquanto autenticamos você..." + }, + "success": { + "title": "✅ Autenticação Bem-sucedida", + "description": "Obrigado por confirmar sua identidade. Sua sessão de check-in começará agora.", + "action": "Concluído" + }, + "failure": { + "title": "❌ Autenticação Falhou", + "description": "Não conseguimos verificar sua identidade. Por favor, certifique-se de haver uma boa iluminação e que seu rosto esteja completamente visível, e tente novamente.", + "action": "Tentar Novamente" + }, + "try_later": { + "title": "❌ Autenticação Falhou", + "description": "Não conseguimos verificar sua identidade. Por favor, certifique-se de haver uma boa iluminação e que seu rosto esteja completamente visível, e tente novamente em 1 hora.", + "action": "Fechar" + }, + "continue": { + "title": "\uD83D\uDE01 Verificação de Emoções KYC", + "description": "Ótimo trabalho! Você concluiu a primeira etapa tirando uma selfie. Agora, vamos continuar seguindo as emoções exibidas para continuar o processo KYC.", + "action": "Continuar" + }, + "banned": { + "title": "❌ Mineração Desativada", + "description": "Identificamos que sua identidade está associada a outra conta. Se você acredita que isso é um erro, envie um e-mail para [[:bold]]feedback@ice.io[[/:bold]] com seu apelido e uma selfie de alta qualidade.", + "action": "Fechar" + } + }, + "emotions_recognition": { + "start": "Iniciar", + "please_show_this": "Por favor, mostre esta emoção", + "emotions": { + "anger": "raiva", + "contempt": "desprezo", + "disgust": "nojo", + "fear": "medo", + "happiness": "felicidade", + "neutral": "neutro", + "sadness": "tristeza", + "surprise": "surpresa" + } + } } } diff --git a/src/translations/locales/ro.json b/src/translations/locales/ro.json index 6badb12e2..165e658c2 100644 --- a/src/translations/locales/ro.json +++ b/src/translations/locales/ro.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Autentificare facială", + "title": "Pasul KYC #1", + "description": "Pentru o experiență fără probleme, adăugăm autentificarea facială. Acest pas servește drept [[:bold]]Pasul #1 KYC timpuriu[[/:bold]], pregătindu-vă pentru o verificare simplificată în viitorul Mainnet. \n\nConfirmați-vă identitatea astăzi pentru a asigura o călătorie fără obstacole înainte!", + "consent": "Sunt de acord cu [[:link]]Termenii și condițiile KYC Ice[[/:link]].", + "confirmation": "Confirm că identitatea pe care o folosesc pentru această autentificare va fi aceeași asociată cu acest cont și această alegere nu poate fi modificată ulterior.", + "auth_status": { + "loading": { + "description": "Vă rugăm să așteptați în timp ce vă autentificăm..." + }, + "success": { + "title": "✅ Autentificare reușită", + "description": "Vă mulțumim că ați confirmat identitatea dumneavoastră. Ședința dvs. de check-in va începe acum.", + "action": "Gata" + }, + "failure": { + "title": "❌ Autentificare eșuată", + "description": "Nu am putut verifica identitatea dumneavoastră. Asigurați-vă că aveți iluminare bună și că fața este complet vizibilă, apoi încercați din nou.", + "action": "Reîncercați" + }, + "try_later": { + "title": "❌ Autentificare eșuată", + "description": "Nu am putut verifica identitatea dumneavoastră. Asigurați-vă că aveți iluminare bună și că fața este complet vizibilă, apoi încercați din nou în 1 oră.", + "action": "Închideți" + }, + "continue": { + "title": "\uD83D\uDE01 Verificare emoțională KYC", + "description": "Lucru excelent! Ați completat primul pas prin a vă face un selfie. Acum, să continuăm urmând emoțiile afișate pentru a continua procesul KYC.", + "action": "Continuați" + }, + "banned": { + "title": "❌ Minerit dezactivat", + "description": "Am identificat că identitatea dumneavoastră este asociată cu un alt cont. Dacă credeți că este o eroare, vă rugăm să trimiteți un e-mail la [[:bold]]feedback@ice.io[[/:bold]] cu porecla și un selfie de înaltă calitate.", + "action": "Închideți" + } + }, + "emotions_recognition": { + "start": "Start", + "please_show_this": "Vă rugăm să arătați această emoție", + "emotions": { + "anger": "mânie", + "contempt": "dispreț", + "disgust": "dezgust", + "fear": "teamă", + "happiness": "fericire", + "neutral": "neutral", + "sadness": "tristețe", + "surprise": "surpriză" + } + } } } diff --git a/src/translations/locales/ru.json b/src/translations/locales/ru.json index 2aaf397a3..c472dd63a 100644 --- a/src/translations/locales/ru.json +++ b/src/translations/locales/ru.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Аутентификация по лицу", + "title": "KYC Шаг #1", + "description": "Для беспроблемного опыта мы добавляем аутентификацию по лицу. Этот шаг является вашим [[:bold]]KYC Шагом #1[[/:bold]], подготавливающим вас к более простой верификации в будущей основной сети. \n\nПодтвердите свою личность сегодня, чтобы обеспечить гладкое продвижение вперед!", + "consent": "Я соглашаюсь с [[:link]]Условиями KYC Ice[[/:link]].", + "confirmation": "Я подтверждаю, что личность, которую я использую для этой аутентификации, будет той же, что связана с этой учетной записью, и этот выбор нельзя будет изменить впоследствии.", + "auth_status": { + "loading": { + "description": "Пожалуйста, подождите, пока мы аутентифицируем вас..." + }, + "success": { + "title": "✅ Успешная аутентификация", + "description": "Спасибо за подтверждение вашей личности. Сессия регистрации начнется сейчас.", + "action": "Готово" + }, + "failure": { + "title": "❌ Аутентификация не удалась", + "description": "Мы не смогли проверить вашу личность. Убедитесь, что есть хорошее освещение и что ваше лицо полностью видно, а затем попробуйте снова.", + "action": "Повторить" + }, + "try_later": { + "title": "❌ Аутентификация не удалась", + "description": "Мы не смогли проверить вашу личность. Убедитесь, что есть хорошее освещение и что ваше лицо полностью видно, а затем попробуйте снова через 1 час.", + "action": "Закрыть" + }, + "continue": { + "title": "\uD83D\uDE01 Проверка эмоций KYC", + "description": "Отличная работа! Вы выполнили первый шаг, сделав селфи. Теперь давайте продолжим, следуя показанным эмоциям, чтобы продолжить процесс KYC.", + "action": "Продолжить" + }, + "banned": { + "title": "❌ Отключение майнинга", + "description": "Мы выявили, что ваша личность связана с другой учетной записью. Если вы считаете, что это ошибка, отправьте электронное письмо на адрес [[:bold]]feedback@ice.io[[/:bold]] с вашим никнеймом и высококачественным селфи.", + "action": "Закрыть" + } + }, + "emotions_recognition": { + "start": "Начать", + "please_show_this": "Пожалуйста, покажите эту эмоцию", + "emotions": { + "anger": "гнев", + "contempt": "презрение", + "disgust": "отвращение", + "fear": "страх", + "happiness": "счастье", + "neutral": "нейтральное", + "sadness": "печаль", + "surprise": "удивление" + } + } } } diff --git a/src/translations/locales/sk.json b/src/translations/locales/sk.json index 8c175a55c..33aefce28 100644 --- a/src/translations/locales/sk.json +++ b/src/translations/locales/sk.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Rozpoznávanie tváre", + "title": "KYC Krok #1", + "description": "Pre bezproblémový zážitok pridávame rozpoznávanie tváre. Tento krok slúži ako váš [[:bold]]Prvý krok KYC[[/:bold]], pripravujúci vás na jednoduchšiu overovaciu procedúru v budúcej Mainnet sieti. \n\nPotvrďte dnes svoju totožnosť, aby ste zabezpečili hladkú cestu vpred!", + "consent": "Súhlasím s [[:link]]Podmienkami a ustanoveniami KYC Ice[[/:link]].", + "confirmation": "Potvrdzujem, že totožnosť, ktorú používam pre túto autentifikáciu, bude tá istá, ktorá je spojená s touto účtom, a tento výber nemožno neskôr zmeniť.", + "auth_status": { + "loading": { + "description": "Čakajte prosím, kým vás autentifikujeme..." + }, + "success": { + "title": "✅ Úspešná autentifikácia", + "description": "Ďakujeme za potvrdenie vašej totožnosti. Vaša relácia prihlásenia sa teraz spustí.", + "action": "Dokončiť" + }, + "failure": { + "title": "❌ Autentifikácia zlyhala", + "description": "Nepodarilo sa nám overiť vašu totožnosť. Prosím, uistite sa, že je dostatočné osvetlenie a vaša tvár je plne viditeľná, a skúste to znova.", + "action": "Skúsiť znova" + }, + "try_later": { + "title": "❌ Autentifikácia zlyhala", + "description": "Nepodarilo sa nám overiť vašu totožnosť. Prosím, uistite sa, že je dostatočné osvetlenie a vaša tvár je plne viditeľná, a potom to skúste znova o 1 hodinu.", + "action": "Zavrieť" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Overenie emócií", + "description": "Skvelá práca! Dokončili ste prvý krok fotením selfíčka. Teraz pokračujme tým, že budeme nasledovať zobrazované emócie, aby sme pokračovali v procese KYC.", + "action": "Pokračovať" + }, + "banned": { + "title": "❌ Ťažba zakázaná", + "description": "Zistili sme, že vaša totožnosť je spojená s iným účtom. Ak si myslíte, že ide o chybu, pošlite e-mail na adresu [[:bold]]feedback@ice.io[[/:bold]] so svojím prezývkou a selfie vo vysokom rozlíšení.", + "action": "Zavrieť" + } + }, + "emotions_recognition": { + "start": "Začať", + "please_show_this": "Prosím, ukážte túto emóciu", + "emotions": { + "anger": "hnev", + "contempt": "pohŕdanie", + "disgust": "odpor", + "fear": "strach", + "happiness": "šťastie", + "neutral": "neutrálne", + "sadness": "smútok", + "surprise": "prekvapenie" + } + } } } diff --git a/src/translations/locales/sl.json b/src/translations/locales/sl.json index f60a04cef..6e31a9e53 100644 --- a/src/translations/locales/sl.json +++ b/src/translations/locales/sl.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Prepoznavanje obraza", + "title": "KYC Korak #1", + "description": "Za nemoteno izkušnjo dodajamo prepoznavanje obraza. Ta korak deluje kot vaš [[:bold]]Prvi korak KYC[[/:bold]], ki vas pripravlja na poenostavljeno preverjanje v prihodnji glavni omrežji. \n\nDanes potrdite svojo identiteto, da zagotovite gladko pot naprej!", + "consent": "Strinjam se s [[:link]]Pogoji KYC Ice[[/:link]].", + "confirmation": "Potrjujem, da bo identiteta, ki jo uporabljam za to avtentikacijo, ista kot tista, ki je povezana s to računom, in tega izbora kasneje ne bo mogoče spremeniti.", + "auth_status": { + "loading": { + "description": "Prosim, počakajte, medtem ko vas avtentificiramo..." + }, + "success": { + "title": "✅ Uspešna avtentikacija", + "description": "Hvala za potrditev vaše identitete. Vaša seja prijave se bo začela zdaj.", + "action": "Dokončano" + }, + "failure": { + "title": "❌ Avtentikacija ni uspela", + "description": "Vaše identitete nismo mogli preveriti. Prosimo, poskrbite za dobro osvetlitev in da je vaš obraz popolnoma viden, nato poskusite znova.", + "action": "Poskusite znova" + }, + "try_later": { + "title": "❌ Avtentikacija ni uspela", + "description": "Vaše identitete nismo mogli preveriti. Prosimo, poskrbite za dobro osvetlitev in da je vaš obraz popolnoma viden, nato pa poskusite znova čez 1 uro.", + "action": "Zapri" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Prepoznavanje čustev", + "description": "Odlično opravljeno! Prvi korak ste zaključili s selfijem. Sedaj nadaljujte tako, da sledite prikazanim čustvom, da nadaljujete postopek KYC.", + "action": "Nadaljuj" + }, + "banned": { + "title": "❌ Onemogočena rudarjenja", + "description": "Ugotovili smo, da je vaša identiteta povezana z drugim računom. Če menite, da gre za napako, pošljite e-pošto na naslov [[:bold]]feedback@ice.io[[/:bold]] s svojim vzdevkom in selfijem visoke kakovosti.", + "action": "Zapri" + } + }, + "emotions_recognition": { + "start": "Začni", + "please_show_this": "Prosimo, pokažite to čustvo", + "emotions": { + "anger": "jeza", + "contempt": "prezir", + "disgust": "omraza", + "fear": "strah", + "happiness": "sreča", + "neutral": "nevtralno", + "sadness": "žalost", + "surprise": "presenečenje" + } + } } } diff --git a/src/translations/locales/sq.json b/src/translations/locales/sq.json index 91724ffc8..67c5a66d6 100644 --- a/src/translations/locales/sq.json +++ b/src/translations/locales/sq.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Identifikimi i fytyrës", + "title": "KYC Hapi #1", + "description": "Për një përvojë të pa ndërprerë, ne po shtojmë identifikimin e fytyrës. Ky hap vepron si [[:bold]]Hapi #1 i hershëm KYC[[/:bold]], që ju përgatit për një verifikim më të lehtë në Mainnetin e ardhshëm. \n\nKonfirmoni identitetin tuaj sot për të siguruar një rrugë të qetë përpara!", + "consent": "Unë pajtohem me [[:link]]Kushtet dhe Kushtetutat KYC të Ice[[/:link]].", + "confirmation": "Unë konfirmoj se identiteti që po përdor për këtë autentifikim do të jetë i njëjti me atë të lidhur me këtë llogari, dhe kjo zgjedhje nuk mund të ndryshohet më vonë.", + "auth_status": { + "loading": { + "description": "Ju lutem prisni ndërsa ju autentifikojmë..." + }, + "success": { + "title": "✅ Autentikimi është i suksesshëm", + "description": "Faleminderit që konfirmuat identitetin tuaj. Sesioni juaj i check-in do të nis tani.", + "action": "Përfunduar" + }, + "failure": { + "title": "❌ Autentikimi Dështoi", + "description": "Nuk mundëm të verifikojmë identitetin tuaj. Ju lutem sigurohuni që të ketë dritë të mjaftueshme dhe fytyra juaj të jetë plotësisht e dukshme, pastaj provoni përsëri.", + "action": "Provo përsëri" + }, + "try_later": { + "title": "❌ Autentikimi Dështoi", + "description": "Nuk mundëm të verifikojmë identitetin tuaj. Ju lutem sigurohuni që të ketë dritë të mjaftueshme dhe fytyra juaj të jetë plotësisht e dukshme, pastaj provoni përsëri në 1 orë.", + "action": "Mbyll" + }, + "continue": { + "title": "\uD83D\uDE01 Verifikimi i emocioneve KYC", + "description": "Puna e shkëlqyer! Ju keni përfunduar hapin e parë duke bërë një selfi. Tani le të vazhdojmë duke ndjekur emocionet e treguara për të vazhduar procesin KYC.", + "action": "Vazhdo" + }, + "banned": { + "title": "❌ Minimizimi i çaktivizuar", + "description": "Kemi identifikuar që identiteti juaj është i lidhur me një llogari tjetër. Nëse mendoni se është një gabim, ju lutem dërgoni një email në adresën [[:bold]]feedback@ice.io[[/:bold]] me emrin tuaj të përdoruesit dhe një selfi cilësie të lartë.", + "action": "Mbyll" + } + }, + "emotions_recognition": { + "start": "Fillo", + "please_show_this": "Ju lutem tregoni këtë emocion", + "emotions": { + "anger": "zemërim", + "contempt": "përçmim", + "disgust": "neveri", + "fear": "trembje", + "happiness": "gëzim", + "neutral": "i qetë", + "sadness": "trishtim", + "surprise": "çuditje" + } + } } } diff --git a/src/translations/locales/sv.json b/src/translations/locales/sv.json index 9dab6dfc5..42e9db0b1 100644 --- a/src/translations/locales/sv.json +++ b/src/translations/locales/sv.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Ansiktsautentisering", + "title": "KYC Steg #1", + "description": "För en sömlös upplevelse lägger vi till ansiktsautentisering. Detta steg fungerar som din [[:bold]]Tidiga Steg #1 KYC[[/:bold]], vilket förbereder dig för en smidigare verifiering i den framtida Mainnet. \n\nBekräfta din identitet idag för att säkerställa en problemfri resa framåt!", + "consent": "Jag godkänner Ice [[:link]]KYC Villkor och Bestämmelser[[/:link]].", + "confirmation": "Jag bekräftar att identiteten jag använder för denna autentisering kommer att vara densamma som är kopplad till detta konto, och detta val kan inte ändras senare.", + "auth_status": { + "loading": { + "description": "Vänligen vänta medan vi autentiserar dig..." + }, + "success": { + "title": "✅ Autentisering Lyckades", + "description": "Tack för att du har bekräftat din identitet. Din incheckningssession kommer att börja nu.", + "action": "Klart" + }, + "failure": { + "title": "❌ Autentisering Misslyckades", + "description": "Vi kunde inte verifiera din identitet. Se till att det finns bra belysning och att ditt ansikte är helt synligt, försök sedan igen.", + "action": "Försök Igen" + }, + "try_later": { + "title": "❌ Autentisering Misslyckades", + "description": "Vi kunde inte verifiera din identitet. Se till att det finns bra belysning och att ditt ansikte är helt synligt, försök igen om 1 timme.", + "action": "Stäng" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Emotionsverifiering", + "description": "Bra jobbat! Du har slutfört det första steget genom att ta en selfie. Nu, låt oss fortsätta genom att följa de visade känslorna för att fortsätta din KYC-process.", + "action": "Fortsätt" + }, + "banned": { + "title": "❌ Gruvbrytning Inaktiverad", + "description": "Vi har identifierat att din identitet är kopplad till ett annat konto. Om du tror att detta är ett misstag, vänligen skicka ett e-postmeddelande till [[:bold]]feedback@ice.io[[/:bold]] med ditt användarnamn och en högkvalitativ selfie.", + "action": "Stäng" + } + }, + "emotions_recognition": { + "start": "Starta", + "please_show_this": "Visa denna känsla, var snäll", + "emotions": { + "anger": "ilska", + "contempt": "förakt", + "disgust": "avsky", + "fear": "rädsla", + "happiness": "glädje", + "neutral": "neutral", + "sadness": "sorg", + "surprise": "förvåning" + } + } } } diff --git a/src/translations/locales/te.json b/src/translations/locales/te.json index 0bd0f696f..aa62f9e0e 100644 --- a/src/translations/locales/te.json +++ b/src/translations/locales/te.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "ముఖం ప్రమాణీకరణ", + "title": "KYC దశ #1", + "description": "అతుకులు లేని అనుభవం కోసం, మేము ముఖ ప్రమాణీకరణను జోడిస్తున్నాము. ఈ దశ మీ [[:bold]]ప్రారంభ దశ #1 KYC[[/:bold]] వలె పని చేస్తుంది, భవిష్యత్తులో Mainnet కోసం క్రమబద్ధీకరించబడిన ధృవీకరణ కోసం మిమ్మల్ని సిద్ధం చేస్తుంది. \n\nముందు ప్రయాణం సాఫీగా సాగేందుకు ఈరోజే మీ గుర్తింపును నిర్ధారించండి!", + "consent": "నేను Ice [[:link]]KYC నిబంధనలు మరియు షరతులు[[/:link]]తో అంగీకరిస్తున్నాను.", + "confirmation": "ఈ ప్రామాణీకరణ కోసం నేను ఉపయోగిస్తున్న గుర్తింపు ఈ ఖాతాతో అనుబంధించబడినదేనని నేను ధృవీకరిస్తున్నాను మరియు ఈ ఎంపిక తర్వాత మార్చబడదు.", + "auth_status": { + "loading": { + "description": "మేము మిమ్మల్ని ప్రామాణీకరించే వరకు దయచేసి వేచి ఉండండి..." + }, + "success": { + "title": "✅ ప్రామాణీకరణ విజయవంతమైంది", + "description": "మీ గుర్తింపును ధృవీకరించినందుకు ధన్యవాదాలు. మీ చెక్-ఇన్ సెషన్ ఇప్పుడు ప్రారంభమవుతుంది.", + "action": "పూర్తి" + }, + "failure": { + "title": "❌ ప్రమాణీకరణ విఫలమైంది", + "description": "మేము మీ గుర్తింపును ధృవీకరించలేకపోయాము. దయచేసి మంచి లైటింగ్ మరియు మీ ముఖం పూర్తిగా కనిపించేలా చూసుకోండి, ఆపై మళ్లీ ప్రయత్నించండి.", + "action": "మళ్లీ ప్రయత్నించండి" + }, + "try_later": { + "title": "❌ ప్రమాణీకరణ విఫలమైంది", + "description": "మేము మీ గుర్తింపును ధృవీకరించలేకపోయాము. దయచేసి మంచి లైటింగ్ మరియు మీ ముఖం పూర్తిగా కనిపించేలా చూసుకోండి, ఆపై 1 గంట తర్వాత మళ్లీ ప్రయత్నించండి.", + "action": "దగ్గరగా" + }, + "continue": { + "title": "\uD83D\uDE01 KYC భావోద్వేగ ధృవీకరణ", + "description": "గొప్ప పని! మీరు సెల్ఫీ తీసుకోవడం ద్వారా మొదటి దశను పూర్తి చేసారు. ఇప్పుడు, మీ KYC ప్రక్రియను కొనసాగించడానికి ప్రదర్శించబడిన భావోద్వేగాలను అనుసరించడం ద్వారా కొనసాగండి.", + "action": "కొనసాగించు" + }, + "banned": { + "title": "❌ మైనింగ్ డిసేబుల్", + "description": "మీ గుర్తింపు మరొక ఖాతాతో అనుబంధించబడిందని మేము గుర్తించాము. ఇది లోపం అని మీరు భావిస్తే, దయచేసి మీ మారుపేరు మరియు అధిక నాణ్యత గల సెల్ఫీతో [[:bold]]feedback@ice.io[[/:bold]]కి ఇమెయిల్ పంపండి.", + "action": "దగ్గరగా" + } + }, + "emotions_recognition": { + "start": "ప్రారంభించండి", + "please_show_this": "దయచేసి ఈ భావోద్వేగాన్ని చూపించండి", + "emotions": { + "anger": "కోపం", + "contempt": "ధిక్కారం", + "disgust": "అసహ్యము", + "fear": "భయం", + "happiness": "ఆనందం", + "neutral": "తటస్థ", + "sadness": "విచారం", + "surprise": "ఆశ్చర్యం" + } + } } } diff --git a/src/translations/locales/th.json b/src/translations/locales/th.json index 4c0fb8dc3..b195f750f 100644 --- a/src/translations/locales/th.json +++ b/src/translations/locales/th.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "การยืนยันตัวตนด้วยใบหน้า", + "title": "ขั้นตอน KYC #1", + "description": "สำหรับประสบการณ์ที่ไม่มีข้อขัดแย้ง เรากำลังเพิ่มการยืนยันตัวตนด้วยใบหน้า ขั้นตอนนี้ทำหน้าที่เป็น [[:bold]]ขั้นตอนแรกของ KYC[[/:bold]] ในขั้นตอนต่อไป ที่จะทำให้การตรวจสอบรายละเอียดใน Mainnet มีความราบรื่นมากขึ้น \n\nยืนยันตัวตนของคุณวันนี้เพื่อให้มั่นใจในการเดินทางที่ราบรื่น!", + "consent": "ฉันยินยอมกับ [[:link]]ข้อกำหนดและเงื่อนไข KYC ของ Ice[[/:link]]", + "confirmation": "ฉันยืนยันว่าตัวตนที่ฉันใช้สำหรับการยืนยันตัวตนนี้จะเป็นตัวตนเดียวกันกับที่เชื่อมโยงกับบัญชีนี้ และเลือกนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง", + "auth_status": { + "loading": { + "description": "โปรดรอในขณะที่เรายืนยันตัวตนของคุณ..." + }, + "success": { + "title": "✅ ยืนยันตัวตนสำเร็จ", + "description": "ขอบคุณที่ยืนยันตัวตนของคุณ เซสชั่นการเช็คอินของคุณจะเริ่มต้นทันที", + "action": "เสร็จสิ้น" + }, + "failure": { + "title": "❌ การยืนยันตัวตนล้มเหลว", + "description": "เราไม่สามารถยืนยันตัวตนของคุณได้ โปรดตรวจสอบแสงที่ดีและให้ใบหน้าของคุณเห็นชัดเจน แล้วลองอีกครั้ง", + "action": "ลองอีกครั้ง" + }, + "try_later": { + "title": "❌ การยืนยันตัวตนล้มเหลว", + "description": "เราไม่สามารถยืนยันตัวตนของคุณได้ โปรดตรวจสอบแสงที่ดีและให้ใบหน้าของคุณเห็นชัดเจน แล้วลองอีกครั้งใน 1 ชั่วโมง", + "action": "ปิด" + }, + "continue": { + "title": "\uD83D\uDE01 การยืนยันอารมณ์ KYC", + "description": "งานดีมาก! คุณได้ทำขั้นตอนแรกเสร็จสิ้นโดยการถ่ายรูปเซลฟี่ ตอนนี้เรามาดำเนินการต่อด้วยการติดตามอารมณ์ที่แสดงเพื่อดำเนินกระบวนการ KYC ของคุณต่อไป", + "action": "ดำเนินการต่อ" + }, + "banned": { + "title": "❌ การขุดเหมืองถูกปิดใช้งาน", + "description": "เราพบว่าตัวตนของคุณเชื่อมโยงกับบัญชีอื่น หากคุณคิดว่าเป็นข้อผิดพลาด โปรดส่งอีเมลไปที่ [[:bold]]feedback@ice.io[[/:bold]] พร้อมชื่อเล่นและรูปเซลฟี่คุณคุณภาพสูง", + "action": "ปิด" + } + }, + "emotions_recognition": { + "start": "เริ่ม", + "please_show_this": "โปรดแสดงอารมณ์นี้", + "emotions": { + "anger": "โกรธ", + "contempt": "ดูถูก", + "disgust": "เกลียด", + "fear": "กลัว", + "happiness": "ยินดี", + "neutral": "ธรรมดา", + "sadness": "เศร้า", + "surprise": "ประหลาดใจ" + } + } } } diff --git a/src/translations/locales/tl-ph.json b/src/translations/locales/tl-ph.json index 4eab8c3ec..10face42b 100644 --- a/src/translations/locales/tl-ph.json +++ b/src/translations/locales/tl-ph.json @@ -906,5 +906,56 @@ } } } + }, + "face_auth": { + "header": "Pagkakakilanlan sa Mukha", + "title": "KYC Hakbang #1", + "description": "Para sa isang maginhawang karanasan, idinadagdag namin ang pagkakakilanlan sa mukha. Ang hakbang na ito ay naglilingkod bilang [[:bold]]Una at pangunahing Hakbang #1 sa KYC[[/:bold]], naghahanda sa iyo para sa isang mabilisang pag-verify para sa hinaharap na Mainnet. \n\nKumpirmahin ang iyong pagkakakilanlan ngayon upang tiyakin ang maayos na paglalakbay sa kinabukasan!", + "consent": "Sumasang-ayon ako sa [[:link]]Mga Tuntunin at Kondisyon sa KYC ng Ice[[/:link]].", + "confirmation": "Kumpirmahin ko na ang pagkakakilanlan na aking ginagamit para sa autentikasyong ito ay magiging pareho sa nakakabit sa account na ito, at hindi ito maaaring baguhin sa mga susunod na pagkakataon.", + "auth_status": { + "loading": { + "description": "Mangyaring maghintay habang kami ay naga-authenticate sa iyo..." + }, + "success": { + "title": "✅ Matagumpay na Autentikasyon", + "description": "Salamat sa pagkumpirma ng iyong pagkakakilanlan. Ang iyong sesyon ng check-in ay magsisimula na ngayon.", + "action": "Tapos Na" + }, + "failure": { + "title": "❌ Nagtagumpay ang Autentikasyon", + "description": "Hindi namin ma-verify ang iyong pagkakakilanlan. Mangyaring tiyakin na magandang liwanag at buo ang pagkakakitaan ng iyong mukha, pagkatapos ay subukan ulit.", + "action": "Subukan Muli" + }, + "try_later": { + "title": "❌ Nagtagumpay ang Autentikasyon", + "description": "Hindi namin ma-verify ang iyong pagkakakilanlan. Mangyaring tiyakin na magandang liwanag at buo ang pagkakakitaan ng iyong mukha, at subukan ulit sa loob ng 1 oras.", + "action": "Isara" + }, + "continue": { + "title": "\uD83D\uDE01 Pag-verify ng Emosyon sa KYC", + "description": "Magaling! Natapos mo na ang unang hakbang sa pamamagitan ng pagkuha ng iyong selfie. Ngayon, hayaan nating magpatuloy sa pamamagitan ng pagsunod sa mga emosyon na ipinapakita upang magpatuloy sa iyong proseso ng KYC.", + "action": "Magpatuloy" + }, + "banned": { + "title": "❌ Bawal ang Pagmimina", + "description": "Natukoy namin na ang iyong pagkakakilanlan ay kaugnay ng isa pang account. Kung sa palagay mo ito ay isang kamalian, mangyaring mag-email sa [[:bold]]feedback@ice.io[[/:bold]] kasama ang iyong nickname at isang mataas-kalidad na selfie.", + "action": "Isara" + } + }, + "emotions_recognition": { + "start": "Simulan", + "please_show_this": "Mangyaring ipakita ang emosyong ito", + "emotions": { + "anger": "galit", + "contempt": "hinamak", + "disgust": "pagkayamot", + "fear": "takot", + "happiness": "kaligayahan", + "neutral": "neutral", + "sadness": "kalungkutan", + "surprise": "kagulatang" + } + } } } diff --git a/src/translations/locales/tr.json b/src/translations/locales/tr.json index 48d97bbc3..cdc0a132e 100644 --- a/src/translations/locales/tr.json +++ b/src/translations/locales/tr.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Yüz Onayı", + "title": "KYC Adımı #1", + "description": "Sorunsuz bir deneyim için yüz onayı ekliyoruz. Bu adım, gelecekteki Ana Ağ için sizi hazırlayan [[:bold]]erken Adım #1 KYC[[/:bold]] olarak görev yapar. \n\nBugün kimliğinizi onaylayın ve gelecekteki yolculuğunuzu sorunsuz hale getirin!", + "consent": "Ice [[:link]]KYC Şartları ve Koşulları[[/:link]] ile aynı fikirdeyim.", + "confirmation": "Bu kimlik doğrulaması için kullandığım kimliğin, bu hesapla ilişkilendirilen kimlikle aynı olacağını ve bu seçimin daha sonra değiştirilemeyeceğini onaylıyorum.", + "auth_status": { + "loading": { + "description": "Sizi kimlik doğrularken lütfen bekleyin..." + }, + "success": { + "title": "✅ Kimlik Doğrulaması Başarılı", + "description": "Kimliğinizi onayladığınız için teşekkür ederiz. Giriş oturumunuz şimdi başlayacak.", + "action": "Tamam" + }, + "failure": { + "title": "❌ Kimlik Doğrulaması Başarısız", + "description": "Kimliğinizi doğrulayamadık. Lütfen iyi bir aydınlatma ve yüzünüzün tamamen görünür olduğundan emin olun, sonra tekrar deneyin.", + "action": "Yeniden Dene" + }, + "try_later": { + "title": "❌ Kimlik Doğrulaması Başarısız", + "description": "Kimliğinizi doğrulayamadık. Lütfen iyi bir aydınlatma ve yüzünüzün tamamen görünür olduğundan emin olun, sonra 1 saat içinde tekrar deneyin.", + "action": "Kapat" + }, + "continue": { + "title": "\uD83D\uDE01 KYC Duygu Onayı", + "description": "Harika iş! İlk adımı bir selfie çekerek tamamladınız. Şimdi KYC sürecinizi devam ettirmek için gösterilen duyguları takip ederek devam edelim.", + "action": "Devam" + }, + "banned": { + "title": "❌ Madencilik Devre Dışı Bırakıldı", + "description": "Kimliğinizin başka bir hesapla ilişkilendirildiğini tespit ettik. Bu bir hata olduğunu düşünüyorsanız, lütfen [[:bold]]feedback@ice.io[[/:bold]] adresine kullanıcı adınızı ve yüksek kaliteli bir selfie ekleyerek bir e-posta gönderin.", + "action": "Kapat" + } + }, + "emotions_recognition": { + "start": "Başla", + "please_show_this": "Lütfen bu duyguyu gösterin", + "emotions": { + "anger": "öfke", + "contempt": "hoşgörüsüzlük", + "disgust": "iğrenme", + "fear": "korku", + "happiness": "mutluluk", + "neutral": "tarafsız", + "sadness": "üzüntü", + "surprise": "şaşkınlık" + } + } } } diff --git a/src/translations/locales/uk.json b/src/translations/locales/uk.json index 565c92d36..63b070c08 100644 --- a/src/translations/locales/uk.json +++ b/src/translations/locales/uk.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Аутентифікація обличчя", + "title": "KYC Крок #1", + "description": "Для безшовного досвіду ми додаємо аутентифікацію обличчя. Цей крок виступає як [[:bold]]Початковий Крок #1 KYC[[/:bold]], готуючи вас до спрощеної перевірки в майбутньому Mainnet. \n\nПідтвердіть свою ідентичність сьогодні, щоб забезпечити гладкий подальший шлях!", + "consent": "Я погоджуюся з [[:link]]Умовами та Положеннями KYC Ice[[/:link]].", + "confirmation": "Я підтверджую, що ідентичність, яку я використовую для цієї аутентифікації, буде такою ж, як та, яка пов'язана з цим обліковим записом, і цей вибір не може бути змінений пізніше.", + "auth_status": { + "loading": { + "description": "Будь ласка, зачекайте, поки ми вас аутентифікуємо..." + }, + "success": { + "title": "✅ Успішна аутентифікація", + "description": "Дякуємо за підтвердження вашої ідентичності. Ваша сесія реєстрації почнеться зараз.", + "action": "Завершити" + }, + "failure": { + "title": "❌ Аутентифікація не вдалася", + "description": "Ми не змогли перевірити вашу ідентичність. Будь ласка, переконайтеся, що є гарне освітлення і ваше обличчя повністю видно, а потім спробуйте ще раз.", + "action": "Спробувати ще раз" + }, + "try_later": { + "title": "❌ Аутентифікація не вдалася", + "description": "Ми не змогли перевірити вашу ідентичність. Будь ласка, переконайтеся, що є гарне освітлення і ваше обличчя повністю видно, а потім спробуйте ще раз через 1 годину.", + "action": "Закрити" + }, + "continue": { + "title": "\uD83D\uDE01 Перевірка емоцій KYC", + "description": "Чудова робота! Ви завершили перший крок, зробивши селфі. Тепер давайте продовжимо, дотримуючись показаних емоцій, щоб продовжити процес KYC.", + "action": "Продовжити" + }, + "banned": { + "title": "❌ Вимкнення видобутку", + "description": "Ми визначили, що ваша ідентичність пов'язана з іншим обліковим записом. Якщо ви вважаєте, що це помилка, будь ласка, надішліть листа на адресу [[:bold]]feedback@ice.io[[/:bold]] з вашим псевдонімом та селфі високої якості.", + "action": "Закрити" + } + }, + "emotions_recognition": { + "start": "Почати", + "please_show_this": "Будь ласка, покажіть цю емоцію", + "emotions": { + "anger": "гнів", + "contempt": "погордження", + "disgust": "відраза", + "fear": "страх", + "happiness": "щастя", + "neutral": "нейтральний", + "sadness": "сум", + "surprise": "здивування" + } + } } } diff --git a/src/translations/locales/ur.json b/src/translations/locales/ur.json index 1d82f0065..e065d7b3e 100644 --- a/src/translations/locales/ur.json +++ b/src/translations/locales/ur.json @@ -1000,5 +1000,56 @@ } } } + }, + "face_auth": { + "header": "چہرہ تصدیق", + "title": "KYC مرحلہ #1", + "description": "سیملیس تجربے کے لئے ہم چہرہ تصدیق شامل کر رہے ہیں۔ یہ مرحلہ آپ کے لئے [[:bold]]ابتدائی مرحلہ #1 KYC[[/:bold]] کے طور پر کام کرتا ہے، آپ کو مستقبل کی مین نیٹ کی سادہ تصدیق کے لئے تیار کرتا ہے۔ \n\nآپ کی شناخت کو آج تصدیق کریں تاکہ آپ کی آنے والی سفر کو بہتر بنایا جا سکے!", + "consent": "میں Ice [[:link]]KYC شرائط اور ضوابط[[/:link]] سے متفق ہوں۔", + "confirmation": "میں تصدیق کرتا ہوں کہ میں اس تصدیق کے لئے استعمال کر رہا ہوں وہی شناختی کاغذ ہوگی جو اس اکاؤنٹ سے منسلک ہو، اور یہ انتخاب بعد میں نہیں بدل سکتا۔", + "auth_status": { + "loading": { + "description": "براہ کرم ہم آپ کی تصدیق کرتے وقت انتظار کریں..." + }, + "success": { + "title": "✅ تصدیق کامیابی", + "description": "آپ کی شناخت کی تصدیق کرنے کا شکریہ۔ آپ کی چیک ان سیشن اب شروع ہوگی۔", + "action": "مکمل" + }, + "failure": { + "title": "❌ تصدیق ناکامی", + "description": "ہم آپ کی شناخت کو تصدیق نہیں کر سکے۔ براہ کرم مطمئن ہوں کہ روشنی اچھی ہے اور آپ کا چہرہ مکمل طرح دیکھایئی دیتا ہے، پھر دوبارہ کوشش کریں۔", + "action": "دوبارہ کوشش کریں" + }, + "try_later": { + "title": "❌ تصدیق ناکامی", + "description": "ہم آپ کی شناخت کو تصدیق نہیں کر سکے۔ براہ کرم مطمئن ہوں کہ روشنی اچھی ہے اور آپ کا چہرہ مکمل طرح دیکھایئی دیتا ہے، پھر ایک گھنٹے بعد دوبارہ کوشش کریں۔", + "action": "بند کریں" + }, + "continue": { + "title": "\uD83D\uDE01 KYC احساسات کی تصدیق", + "description": "شاندار کام! آپ نے اپنے پہلے قدم کو خودی سے لیا ہے ایک سیلفی کی شکل میں۔ اب ہم دکھائی گئی جذبات کی پیروی کرکے آپ کی KYC کی پروسیس جاری رکھیں۔", + "action": "جاری رکھیں" + }, + "banned": { + "title": "❌ مائننگ غیر فعال", + "description": "ہم نے تشخیص دیا ہے کہ آپ کی شناخت کسی دوسرے اکاؤنٹ سے منسلک ہے۔ اگر آپ کو لگتا ہے کہ یہ ایک خرابی ہے تو براہ کرم اپنے پروفائل کا نام اور ایک بلند کوالٹی کی سیلفی کے ساتھ [[:bold]]feedback@ice.io[[/:bold]] پر ایک ای میل بھیجیں۔", + "action": "بند کریں" + } + }, + "emotions_recognition": { + "start": "شروع کریں", + "please_show_this": "براہ کرم اس جذبات کو دکھائیں", + "emotions": { + "anger": "غصہ", + "contempt": "تنقید", + "disgust": "نفرت", + "fear": "ڈر", + "happiness": "خوشی", + "neutral": "بے طرف", + "sadness": "غم", + "surprise": "تعجب" + } + } } } diff --git a/src/translations/locales/vi.json b/src/translations/locales/vi.json index 37c6cd94d..45b68cbc5 100644 --- a/src/translations/locales/vi.json +++ b/src/translations/locales/vi.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "Xác thực khuôn mặt", + "title": "Bước KYC #1", + "description": "Để trải nghiệm mượt mà, chúng tôi đang thêm tính năng xác thực khuôn mặt. Bước này đóng vai trò là [[:bold]]Bước #1 KYC sớm[[/:bold]], chuẩn bị bạn cho việc xác minh mạng Mainnet trong tương lai. \n\nHãy xác nhận danh tính của bạn hôm nay để đảm bảo một hành trình mượt mà phía trước!", + "consent": "Tôi đồng ý với [[:link]]Điều khoản và Điều kiện KYC của Ice[[/:link]].", + "confirmation": "Tôi xác nhận rằng danh tính tôi sử dụng cho xác thực này sẽ giống với danh tính được liên kết với tài khoản này và lựa chọn này không thể thay đổi sau này.", + "auth_status": { + "loading": { + "description": "Vui lòng chờ trong khi chúng tôi xác thực bạn..." + }, + "success": { + "title": "✅ Xác thực thành công", + "description": "Cảm ơn bạn đã xác nhận danh tính. Phiên kiểm tra của bạn sẽ bắt đầu ngay bây giờ.", + "action": "Hoàn tất" + }, + "failure": { + "title": "❌ Xác thực thất bại", + "description": "Chúng tôi không thể xác minh danh tính của bạn. Vui lòng đảm bảo ánh sáng tốt và khuôn mặt của bạn được nhìn thấy đầy đủ, sau đó thử lại.", + "action": "Thử lại" + }, + "try_later": { + "title": "❌ Xác thực thất bại", + "description": "Chúng tôi không thể xác minh danh tính của bạn. Vui lòng đảm bảo ánh sáng tốt và khuôn mặt của bạn được nhìn thấy đầy đủ, sau đó thử lại sau 1 giờ.", + "action": "Đóng" + }, + "continue": { + "title": "\uD83D\uDE01 Xác minh cảm xúc KYC", + "description": "Làm tốt lắm! Bạn đã hoàn thành bước đầu tiên bằng cách tự chụp ảnh selfie. Bây giờ, hãy tiếp tục theo các cảm xúc được hiển thị để tiếp tục quá trình KYC của bạn.", + "action": "Tiếp tục" + }, + "banned": { + "title": "❌ Tắt chức năng đào tiền", + "description": "Chúng tôi đã xác định rằng danh tính của bạn liên quan đến một tài khoản khác. Nếu bạn nghĩ đó là một lỗi, vui lòng gửi email đến [[:bold]]feedback@ice.io[[/:bold]] với biệt danh và một bức ảnh tự sướng chất lượng cao.", + "action": "Đóng" + } + }, + "emotions_recognition": { + "start": "Bắt đầu", + "please_show_this": "Vui lòng hiển thị cảm xúc này", + "emotions": { + "anger": "tức giận", + "contempt": "khinh bỉ", + "disgust": "ghê tởm", + "fear": "sợ hãi", + "happiness": "hạnh phúc", + "neutral": "trung lập", + "sadness": "buồn", + "surprise": "ngạc nhiên" + } + } } } diff --git a/src/translations/locales/zh-cn.json b/src/translations/locales/zh-cn.json index e761bcaf7..9dd1e7a25 100644 --- a/src/translations/locales/zh-cn.json +++ b/src/translations/locales/zh-cn.json @@ -906,5 +906,56 @@ } } } + }, + "face_auth": { + "header": "人脸认证", + "title": "KYC 步骤 #1", + "description": "为了提供无缝的体验,我们正在添加人脸认证。这一步将作为您的 [[:bold]]早期步骤 #1 KYC[[/:bold]],为您未来 Mainnet 的简化验证做好准备。\n\n今天确认您的身份,以确保未来的旅程顺畅!", + "consent": "我同意 Ice 的 [[:link]]KYC 条款和条件[[/:link]]。", + "confirmation": "我确认我用于此认证的身份将与此帐户关联的相同,此选择不能在以后更改。", + "auth_status": { + "loading": { + "description": "请稍候,我们正在验证您的身份..." + }, + "success": { + "title": "✅ 认证成功", + "description": "感谢您确认您的身份。您的签到会话现在将开始。", + "action": "完成" + }, + "failure": { + "title": "❌ 认证失败", + "description": "我们无法验证您的身份。请确保充足的光线和您的脸部完全可见,然后重试。", + "action": "重试" + }, + "try_later": { + "title": "❌ 认证失败", + "description": "我们无法验证您的身份。请确保充足的光线和您的脸部完全可见,然后在1小时后重试。", + "action": "关闭" + }, + "continue": { + "title": "\uD83D\uDE01 KYC 情感验证", + "description": "干得好!您已通过自拍完成了第一步。现在,让我们继续按照显示的情感来完成您的 KYC 过程。", + "action": "继续" + }, + "banned": { + "title": "❌ 禁止挖矿", + "description": "我们发现您的身份与另一个帐户相关联。如果您认为这是错误的,请发送电子邮件至 [[:bold]]feedback@ice.io[[/:bold]],提供您的昵称和高质量的自拍照片。", + "action": "关闭" + } + }, + "emotions_recognition": { + "start": "开始", + "please_show_this": "请展示这种情感", + "emotions": { + "anger": "愤怒", + "contempt": "蔑视", + "disgust": "厌恶", + "fear": "恐惧", + "happiness": "快乐", + "neutral": "中性", + "sadness": "悲伤", + "surprise": "惊讶" + } + } } } diff --git a/src/translations/locales/zh-hant.json b/src/translations/locales/zh-hant.json index e258a331e..6c926e7ee 100644 --- a/src/translations/locales/zh-hant.json +++ b/src/translations/locales/zh-hant.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "人臉認證", + "title": "KYC 步驟 #1", + "description": "為了提供無縫的體驗,我們正在添加人臉認證。這一步將作為您的 [[:bold]]早期步驟 #1 KYC[[/:bold]],為您未來 Mainnet 的簡化驗證做好準備。\n\n今天確認您的身份,以確保未來的旅程順暢!", + "consent": "我同意 Ice 的 [[:link]]KYC 條款和條件[[/:link]]。", + "confirmation": "我確認我用於此認證的身份將與此帳戶關聯的相同,此選擇不能在以後更改。", + "auth_status": { + "loading": { + "description": "請稍候,我們正在驗證您的身份..." + }, + "success": { + "title": "✅ 認證成功", + "description": "感謝您確認您的身份。您的簽到會話現在將開始。", + "action": "完成" + }, + "failure": { + "title": "❌ 認證失敗", + "description": "我們無法驗證您的身份。請確保充足的光線和您的臉部完全可見,然後重試。", + "action": "重試" + }, + "try_later": { + "title": "❌ 認證失敗", + "description": "我們無法驗證您的身份。請確保充足的光線和您的臉部完全可見,然後在1小時後重試。", + "action": "關閉" + }, + "continue": { + "title": "\uD83D\uDE01 KYC 情感驗證", + "description": "幹得好!您已通過自拍完成了第一步。現在,讓我們繼續按照顯示的情感來完成您的 KYC 過程。", + "action": "繼續" + }, + "banned": { + "title": "❌ 禁止挖礦", + "description": "我們發現您的身份與另一個帳戶相關聯。如果您認為這是錯誤的,請發送電子郵件至 [[:bold]]feedback@ice.io[[/:bold]],提供您的暱稱和高質量的自拍照片。", + "action": "關閉" + } + }, + "emotions_recognition": { + "start": "開始", + "please_show_this": "請展示這種情感", + "emotions": { + "anger": "憤怒", + "contempt": "輕蔑", + "disgust": "厭惡", + "fear": "恐懼", + "happiness": "快樂", + "neutral": "中性", + "sadness": "悲傷", + "surprise": "驚訝" + } + } } } diff --git a/src/translations/locales/zh.json b/src/translations/locales/zh.json index fbf575ff1..fae79905e 100644 --- a/src/translations/locales/zh.json +++ b/src/translations/locales/zh.json @@ -993,5 +993,56 @@ } } } + }, + "face_auth": { + "header": "人脸认证", + "title": "KYC 步骤 #1", + "description": "为了提供无缝的体验,我们正在添加人脸认证。这一步将作为您的 [[:bold]]早期步骤 #1 KYC[[/:bold]],为您未来 Mainnet 的简化验证做好准备。\n\n今天确认您的身份,以确保未来的旅程顺畅!", + "consent": "我同意 Ice 的 [[:link]]KYC 条款和条件[[/:link]]。", + "confirmation": "我确认我用于此认证的身份将与此帐户关联的相同,此选择不能在以后更改。", + "auth_status": { + "loading": { + "description": "请稍候,我们正在验证您的身份..." + }, + "success": { + "title": "✅ 认证成功", + "description": "感谢您确认您的身份。您的签到会话现在将开始。", + "action": "完成" + }, + "failure": { + "title": "❌ 认证失败", + "description": "我们无法验证您的身份。请确保充足的光线和您的脸部完全可见,然后重试。", + "action": "重试" + }, + "try_later": { + "title": "❌ 认证失败", + "description": "我们无法验证您的身份。请确保充足的光线和您的脸部完全可见,然后在1小时后重试。", + "action": "关闭" + }, + "continue": { + "title": "\uD83D\uDE01 KYC 情感验证", + "description": "干得好!您已通过自拍完成了第一步。现在,让我们继续按照显示的情感来完成您的 KYC 过程。", + "action": "继续" + }, + "banned": { + "title": "❌ 禁止挖矿", + "description": "我们发现您的身份与另一个帐户相关联。如果您认为这是错误的,请发送电子邮件至 [[:bold]]feedback@ice.io[[/:bold]],提供您的昵称和高质量的自拍照片。", + "action": "关闭" + } + }, + "emotions_recognition": { + "start": "开始", + "please_show_this": "请展示这种情感", + "emotions": { + "anger": "愤怒", + "contempt": "蔑视", + "disgust": "厌恶", + "fear": "恐惧", + "happiness": "快乐", + "neutral": "中性", + "sadness": "悲伤", + "surprise": "惊讶" + } + } } } diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 000000000..8f56a4b76 --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: ice License 1.0 + +export function shallowCompare(a1: T[], a2: T[]) { + if (a1.length !== a2.length) { + return false; + } + return a1.every((value, index) => value === a2[index]); +} diff --git a/src/utils/ffmpeg.ts b/src/utils/ffmpeg.ts new file mode 100644 index 000000000..c8d1718bb --- /dev/null +++ b/src/utils/ffmpeg.ts @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {logError} from '@services/logging'; +import {getFilenameFromPathWithoutExtension} from '@utils/file'; +import {cacheDirectory} from 'expo-file-system'; +import {FFmpegKit} from 'ffmpeg-kit-react-native'; + +export async function cropAndResizeWithFFmpeg({ + inputUri, + outputUri, + imgWidth, + outputSize, + cropStartY, +}: { + inputUri: string; + outputUri: string; + imgWidth: number; + outputSize: number; + cropStartY: number; +}) { + try { + const command = `-i "${inputUri}" -vf "crop=${imgWidth}:${imgWidth}:0:${cropStartY},scale=${outputSize}:${outputSize}" -update true "${outputUri}"`; + const session = await FFmpegKit.execute(command); + const returnCode = await session?.getReturnCode(); + + if (returnCode?.isValueSuccess()) { + return outputUri; + } else { + throw new Error(`Failed to execute FFmpeg command: ${command}`); + } + } catch (e) { + logError(e); + throw e; + } +} + +export async function extractFramesWithFFmpeg({ + inputUri, + width, + outputSize, + cropStartY, +}: { + inputUri: string; + width: number; + outputSize: number; + cropStartY: number; +}): Promise { + try { + const outputUri = `${cacheDirectory}/${getFilenameFromPathWithoutExtension( + inputUri, + )}_%03d.jpg`; + const command = `-i "${inputUri}" -vf "crop=${width}:${width}:0:${cropStartY},scale=${outputSize}:${outputSize},setsar=1,fps=3" "${outputUri}"`; + const session = await FFmpegKit.execute(command); + const returnCode = await session?.getReturnCode(); + + if (!returnCode?.isValueSuccess()) { + throw new Error(`Failed to execute FFmpeg command: ${command}`); + } + const output = await session.getOutput(); + const numberOfFrames = parseInt( + output + .match(/frame=(.*?)(\d+)/g) + ?.pop() + ?.match(/\d+/)?.[0] ?? '', + 10, + ); + if (!numberOfFrames || isNaN(numberOfFrames)) { + throw new Error('Error parsing number of frames'); + } + return Array(numberOfFrames) + .fill(null) + .map( + (_, i) => + `${cacheDirectory}/${getFilenameFromPathWithoutExtension( + inputUri, + )}_${(i + 1).toString().padStart(3, '0')}.jpg`, + ); + } catch (e) { + logError(e); + throw e; + } +} + +export async function getVideoDimensionsWithFFmpeg(videoUri: string) { + try { + const session = await FFmpegKit.execute(`-i ${videoUri}`); + const output = await session.getOutput(); + + // Use regex to extract video dimensions from FFmpeg output + const regex = /, (\d+)x(\d+),/; + const match = output.match(regex); + + if (match) { + const height = parseInt(match[1], 10); + const width = parseInt(match[2], 10); + return {width, height}; + } else { + throw new Error('Failed to extract video dimensions.'); + } + } catch (error) { + logError(error); + throw error; + } +} diff --git a/src/utils/file.ts b/src/utils/file.ts index 92414f85f..2c918dd3a 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,12 +1,22 @@ // SPDX-License-Identifier: ice License 1.0 -import {PixelRatio} from 'react-native'; +import {PixelRatio, Platform} from 'react-native'; export const getFilenameFromPath = (path: string) => { // eslint-disable-next-line no-useless-escape return path.replace(/^.*[\\\/]/, ''); }; +export const getFilenameFromPathWithoutExtension = (path: string) => { + return getFilenameFromPath(path).split('.')[0]; +}; + +export function normalizePictureUri(pictureUri: string) { + return Platform.OS === 'android' + ? pictureUri + : pictureUri.replace('file://', ''); +} + export const getImageUriForSize = ( uri: string, {width, height}: {width?: number; height?: number}, diff --git a/yarn.lock b/yarn.lock index 49eed7636..e4ea771b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2687,13 +2687,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@koale/useworker@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@koale/useworker/-/useworker-4.0.2.tgz#cb540a2581cd6025307c3ca6685bc60748773e58" - integrity sha512-xPIPADtom8/3/4FLNj7MvNcBM/Z2FleH85Fdx2O869eoKW8+PoEgtSVvoxWjCWMA46Sm9A5/R1TyzNGc+yM0wg== - dependencies: - dequal "^1.0.0" - "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -2960,9 +2953,9 @@ integrity sha512-oZoU2lfKyn0s0GqqdFsi4v2FSENrxQYQU9DD/RSkxDdkIQ49Wwo6p5LKlgXY04GwZEVdYMuvZN3G89gQW0ig2g== "@react-native-masked-view/masked-view@^0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.8.tgz#34405a4361882dae7c81b1b771fe9f5fbd545a97" - integrity sha512-+1holBPDF1yi/y0uc1WB6lA5tSNHhM7PpTMapT3ypvSnKQ9+C6sy/zfjxNxRA/llBQ1Ci6f94EaK56UCKs5lTA== + version "0.2.9" + resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.9.tgz#723a7a076d56b8f5f3fda076eaa5b6b82988e854" + integrity sha512-Hs4vKBKj+15VxHZHFtMaFWSBxXoOE5Ea8saoigWhahp8Mepssm0ezU+2pTl7DK9z8Y9s5uOl/aPb4QmBZ3R3Zw== "@react-native/assets-registry@^0.72.0": version "0.72.0" @@ -5470,11 +5463,6 @@ deprecated-react-native-prop-types@4.1.0: invariant "*" prop-types "*" -dequal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.1.tgz#dbbf9795ec626e9da8bd68782f4add1d23700d8b" - integrity sha512-Fx8jxibzkJX2aJgyfSdLhr9tlRoTnHKrRJuu2XHlAgKioN2j19/Bcbe0d4mFXYZ3+wpE2KVobUVTfDutcD17xQ== - des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -6186,12 +6174,11 @@ expo-barcode-scanner@^12.5.3: dependencies: expo-image-loader "~4.3.0" -expo-camera@^13.4.2: - version "13.4.2" - resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-13.4.2.tgz#858de25720e5de4b21c43107fa5c926a021a2f0f" - integrity sha512-3G4ahx0pF56PyYixFlU39jrZof6I9XtBzizGObKN6ZKjSgCqqL0tjsrlDrCn44ZwSY4SngsMJmWR9JQDkKx2WA== +expo-camera@^13.6.0: + version "13.6.0" + resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-13.6.0.tgz#a38537f474da4073ca4c19b5551e5178be5c899e" + integrity sha512-8lxK15D2tuEZom9bhDMMqNPW+5241Ak4wIup/ebh4JekgOtSQ/egbq7qDn2FCFi+5vPFKX4nRsvGOuzfuzvmZA== dependencies: - "@koale/useworker" "^4.0.2" invariant "^2.2.4" expo-constants@~14.4.2: @@ -6422,6 +6409,11 @@ fetch-retry@^4.1.1: resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-4.1.1.tgz#fafe0bb22b54f4d0a9c788dff6dd7f8673ca63f3" integrity sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA== +ffmpeg-kit-react-native@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ffmpeg-kit-react-native/-/ffmpeg-kit-react-native-6.0.0.tgz#0dafb861092f3535caf0e94aaf4b6a691818986c" + integrity sha512-nLpl5fYoHP/o5Jja0Av2Ln6eTITpqy4HLP4eheXnP23ER0Hg05DKIeZ86xcd8pUlxlhPEhjmrXT4wNGL2xkftg== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -10505,10 +10497,10 @@ react-native-play-install-referrer@^1.1.8: resolved "https://registry.yarnpkg.com/react-native-play-install-referrer/-/react-native-play-install-referrer-1.1.8.tgz#db9b7ab787be0af9a335c939cf0d165c91069fa9" integrity sha512-g+LLv4Tkt3gWJ68OnhaBy25zYpP43OlLydL6siRjGAcl1fMc/DvfnNFWL4Oe1zrpwW4cmB0QGrKYtcbKnSGK3Q== -react-native-reanimated@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.3.0.tgz#80f9d58e28fddf62fe4c1bc792337b8ab57936ab" - integrity sha512-LzfpPZ1qXBGy5BcUHqw3pBC0qSd22qXS3t8hWSbozXNrBkzMhhOrcILE/nEg/PHpNNp1xvGOW8NwpAMF006roQ== +react-native-reanimated@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.5.4.tgz#a6c2b0c43b6dad246f5d276213974afedb8e3fc7" + integrity sha512-8we9LLDO1o4Oj9/DICeEJ2K1tjfqkJagqQUglxeUAkol/HcEJ6PGxIrpBcNryLqCDYEcu6FZWld/FzizBIw6bg== dependencies: "@babel/plugin-transform-object-assign" "^7.16.7" "@babel/preset-typescript" "^7.16.7"