@getyoti/react-native-yoti-face-capture 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +4 -4
  2. package/android/build.gradle +34 -30
  3. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  4. package/android/src/main/java/com/yoti/reactnative/facecapture/YotiFaceCaptureEvent.java +34 -0
  5. package/android/src/main/java/com/yoti/reactnative/facecapture/YotiFaceCaptureModule.java +55 -56
  6. package/android/src/main/java/com/yoti/reactnative/facecapture/YotiFaceCapturePackage.java +5 -2
  7. package/android/src/main/java/com/yoti/reactnative/facecapture/YotiFaceCaptureView.java +129 -73
  8. package/android/src/main/java/com/yoti/reactnative/facecapture/YotiFaceCaptureViewManager.java +10 -1
  9. package/android/src/main/res/layout/yotifacecapture.xml +3 -9
  10. package/lib/commonjs/RNYotiCapture.android.js +63 -94
  11. package/lib/commonjs/RNYotiCapture.android.js.map +1 -1
  12. package/lib/commonjs/RNYotiCapture.ios.js +77 -84
  13. package/lib/commonjs/RNYotiCapture.ios.js.map +1 -1
  14. package/lib/commonjs/RNYotiCaptureTypes.js +9 -17
  15. package/lib/commonjs/RNYotiCaptureTypes.js.map +1 -1
  16. package/lib/commonjs/index.js +21 -6
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/module/RNYotiCapture.android.js +62 -85
  19. package/lib/module/RNYotiCapture.android.js.map +1 -1
  20. package/lib/module/RNYotiCapture.ios.js +76 -75
  21. package/lib/module/RNYotiCapture.ios.js.map +1 -1
  22. package/lib/module/RNYotiCaptureTypes.js +5 -7
  23. package/lib/module/RNYotiCaptureTypes.js.map +1 -1
  24. package/lib/module/index.js +1 -0
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/typescript/RNYotiCapture.android.d.ts +7 -46
  27. package/lib/typescript/RNYotiCapture.ios.d.ts +7 -9
  28. package/lib/typescript/RNYotiCaptureTypes.d.ts +6 -6
  29. package/lib/typescript/index.d.ts +1 -0
  30. package/package.json +6 -8
  31. package/react-native-yoti-face-capture.podspec +1 -1
  32. package/src/RNYotiCapture.android.tsx +128 -115
  33. package/src/RNYotiCapture.ios.tsx +107 -80
  34. package/src/index.tsx +6 -0
  35. package/CHANGELOG.md +0 -29
package/README.md CHANGED
@@ -52,14 +52,14 @@ You may then run:
52
52
 
53
53
  The library employs the [bundled version approach](https://github.com/getyoti/yoti-face-capture-android#bundled-vs-unbundled) approach for the AI models.
54
54
  ### iOS Configuration
55
- - Requres iOS 12.0+
56
- - Requres Swift 5.3+
55
+ - Requires iOS 15.1+
56
+ - Requires Swift 5.3+
57
57
 
58
58
  Make sure you've installed and are running the latest version of [Cocoapods](https://guides.cocoapods.org/using/getting-started.html).
59
59
  Add the `use_frameworks!` declaration to your Podfile and run `pod install` from the ios directory:
60
60
 
61
61
  ```bash
62
- platform :ios, '12.0'
62
+ platform :ios, '15.1'
63
63
 
64
64
  target 'TargetName' do
65
65
  use_frameworks!
@@ -286,5 +286,5 @@ protected List<ReactPackage> getPackages() {
286
286
  MIT
287
287
 
288
288
  ## Support
289
- If you have any other questions please do not hesitate to contact clientsupport@yoti.com.
289
+ For any questions or support please contact us here: https://support.yoti.com
290
290
  Once we have answered your question we may contact you again to discuss Yoti products and services. If you'd prefer us not to do this, please let us know when you e-mail.
@@ -1,61 +1,65 @@
1
1
  apply plugin: 'com.android.library'
2
- apply plugin: 'kotlin-android'
3
2
 
4
3
  def safeExtGet(prop, fallback) {
5
4
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
6
5
  }
7
6
 
8
7
  android {
9
- compileSdkVersion safeExtGet('compileSdkVersion', 28)
10
- buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
8
+ namespace "com.yoti.reactnative.facecapture"
9
+
10
+ compileSdk safeExtGet('compileSdkVersion', 35)
11
11
 
12
12
  defaultConfig {
13
- minSdkVersion safeExtGet('minSdkVersion', 16)
14
- targetSdkVersion safeExtGet('targetSdkVersion', 28)
15
- versionCode 200
16
- versionName "2.0.0"
13
+ minSdkVersion safeExtGet('minSdkVersion', 24)
14
+ targetSdkVersion safeExtGet('targetSdkVersion', 35)
15
+ versionCode 400
16
+ versionName "4.0.0"
17
17
  }
18
18
  buildTypes {
19
- release {
20
- minifyEnabled false
19
+ debug {
20
+ matchingFallbacks = ['release']
21
21
  }
22
22
  }
23
- lintOptions {
24
- abortOnError false
23
+ packagingOptions {
24
+ exclude 'META-INF/*.kotlin_module'
25
+ exclude "**/kotlin/**"
26
+ }
27
+ compileOptions {
28
+ sourceCompatibility = '1.8'
29
+ targetCompatibility = '1.8'
25
30
  }
26
31
  }
27
32
 
33
+ dependencies {
34
+ implementation "com.facebook.react:react-native:+"
35
+ implementation 'com.yoti.mobile.android:face-capture-bundled:4.8.1'
36
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
37
+ implementation 'androidx.appcompat:appcompat:1.2.0'
38
+ }
39
+
40
+
28
41
  buildscript {
29
42
  ext {
30
- kotlinVersion = '1.4.31'
43
+ kotlinVersion = '1.7.20'
31
44
  }
32
45
  repositories {
33
- jcenter()
34
46
  google()
35
47
  mavenCentral()
36
48
  }
37
49
  dependencies {
38
- classpath 'com.android.tools.build:gradle:4.1.3'
50
+ classpath 'com.android.tools.build:gradle:8.13.2'
39
51
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${project.ext.kotlinVersion}"
40
52
  }
41
53
  }
42
54
 
43
- repositories {
44
- jcenter()
45
- google()
46
- mavenCentral()
47
- maven {
48
- // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
49
- url "$rootDir/../node_modules/react-native/android"
55
+ allprojects {
56
+ repositories {
57
+ google()
58
+ mavenCentral()
59
+ maven {
60
+ url "https://jitpack.io"
61
+ }
50
62
  }
51
63
  }
52
64
 
53
- dependencies {
54
- //noinspection GradleDynamicVersion
55
- implementation "com.facebook.react:react-native:+" // From node_modules
56
- implementation "org.jetbrains.kotlin:kotlin-stdlib:${project.ext.kotlinVersion}"
57
- implementation 'com.yoti.mobile.android:face-capture-bundled:4.0.0'
58
- implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
59
- implementation 'androidx.appcompat:appcompat:1.2.0'
60
- implementation 'com.google.android.material:material:1.2.1'
61
- }
65
+
@@ -1,5 +1,5 @@
1
1
  distributionBase=GRADLE_USER_HOME
2
2
  distributionPath=wrapper/dists
3
- distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
4
4
  zipStoreBase=GRADLE_USER_HOME
5
5
  zipStorePath=wrapper/dists
@@ -0,0 +1,34 @@
1
+ package com.yoti.reactnative.facecapture;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ import com.facebook.react.bridge.WritableMap;
7
+ import com.facebook.react.uimanager.events.Event;
8
+
9
+ /**
10
+ * Custom event class for YotiFaceCapture events.
11
+ * This is compatible with both the old architecture and the new Fabric architecture.
12
+ */
13
+ public class YotiFaceCaptureEvent extends Event<YotiFaceCaptureEvent> {
14
+ private final String eventName;
15
+ private final WritableMap eventData;
16
+
17
+ public YotiFaceCaptureEvent(int surfaceId, int viewTag, String eventName, @Nullable WritableMap eventData) {
18
+ super(surfaceId, viewTag);
19
+ this.eventName = eventName;
20
+ this.eventData = eventData;
21
+ }
22
+
23
+ @NonNull
24
+ @Override
25
+ public String getEventName() {
26
+ return eventName;
27
+ }
28
+
29
+ @Nullable
30
+ @Override
31
+ protected WritableMap getEventData() {
32
+ return eventData;
33
+ }
34
+ }
@@ -1,13 +1,16 @@
1
1
  package com.yoti.reactnative.facecapture;
2
2
 
3
- import com.facebook.react.bridge.NativeModule;
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
4
7
  import com.facebook.react.bridge.ReactApplicationContext;
5
- import com.facebook.react.bridge.ReactContext;
6
8
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
7
9
  import com.facebook.react.bridge.ReactMethod;
8
- import com.facebook.react.uimanager.NativeViewHierarchyManager;
9
- import com.facebook.react.uimanager.UIBlock;
10
- import com.facebook.react.uimanager.UIManagerModule;
10
+ import com.facebook.react.bridge.UIManager;
11
+ import com.facebook.react.bridge.UiThreadUtil;
12
+ import com.facebook.react.uimanager.UIManagerHelper;
13
+ import com.facebook.react.uimanager.common.UIManagerType;
11
14
  import com.yoti.mobile.android.capture.face.ui.models.face.ImageQuality;
12
15
 
13
16
  import java.util.Map;
@@ -18,6 +21,7 @@ public class YotiFaceCaptureModule extends ReactContextBaseJavaModule {
18
21
  super(context);
19
22
  }
20
23
 
24
+ @NonNull
21
25
  @Override
22
26
  public String getName() {
23
27
  return "YotiFaceCaptureModule";
@@ -32,78 +36,73 @@ public class YotiFaceCaptureModule extends ReactContextBaseJavaModule {
32
36
  return constants;
33
37
  }
34
38
 
35
- @ReactMethod
36
- public void startCamera(final int viewTag) {
39
+ @Nullable
40
+ private YotiFaceCaptureView resolveView(int viewTag) {
37
41
  final ReactApplicationContext context = getReactApplicationContext();
38
- UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
39
- uiManager.addUIBlock(new UIBlock() {
40
- @Override
41
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
42
- final YotiFaceCaptureView faceCaptureView;
42
+ UIManager uiManager = UIManagerHelper.getUIManager(context, UIManagerType.FABRIC);
43
+ if (uiManager == null) {
44
+ uiManager = UIManagerHelper.getUIManager(context, UIManagerType.DEFAULT);
45
+ }
46
+ if (uiManager == null) {
47
+ return null;
48
+ }
43
49
 
44
- try {
45
- faceCaptureView = (YotiFaceCaptureView) nativeViewHierarchyManager.resolveView(viewTag);
46
- faceCaptureView.startCamera();
47
- } catch (Exception e) {
48
- e.printStackTrace();
49
- }
50
+ View view = uiManager.resolveView(viewTag);
51
+ if (view instanceof YotiFaceCaptureView) {
52
+ return (YotiFaceCaptureView) view;
53
+ }
54
+
55
+ return null;
56
+ }
57
+
58
+ @ReactMethod
59
+ public void startCamera(final int viewTag) {
60
+ UiThreadUtil.runOnUiThread(() -> {
61
+ try {
62
+ YotiFaceCaptureView faceCaptureView = resolveView(viewTag);
63
+
64
+ faceCaptureView.startCamera();
65
+ } catch (Exception e) {
66
+ e.printStackTrace();
50
67
  }
51
68
  });
52
69
  }
53
70
 
54
71
  @ReactMethod
55
72
  public void stopCamera(final int viewTag) {
56
- final ReactApplicationContext context = getReactApplicationContext();
57
- UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
58
- uiManager.addUIBlock(new UIBlock() {
59
- @Override
60
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
61
- final YotiFaceCaptureView faceCaptureView;
73
+ UiThreadUtil.runOnUiThread(() -> {
74
+ try {
75
+ YotiFaceCaptureView faceCaptureView = resolveView(viewTag);
62
76
 
63
- try {
64
- faceCaptureView = (YotiFaceCaptureView) nativeViewHierarchyManager.resolveView(viewTag);
65
- faceCaptureView.stopCamera();
66
- } catch (Exception e) {
67
- e.printStackTrace();
68
- }
77
+ faceCaptureView.stopCamera();
78
+ } catch (Exception e) {
79
+ e.printStackTrace();
69
80
  }
70
81
  });
71
82
  }
72
83
 
73
84
  @ReactMethod
74
85
  public void startAnalyzing(final int viewTag) {
75
- final ReactApplicationContext context = getReactApplicationContext();
76
- UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
77
- uiManager.addUIBlock(new UIBlock() {
78
- @Override
79
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
80
- final YotiFaceCaptureView faceCaptureView;
81
-
82
- try {
83
- faceCaptureView = (YotiFaceCaptureView) nativeViewHierarchyManager.resolveView(viewTag);
84
- faceCaptureView.startAnalyzing();
85
- } catch (Exception e) {
86
- e.printStackTrace();
87
- }
86
+ UiThreadUtil.runOnUiThread(() -> {
87
+ try {
88
+ YotiFaceCaptureView faceCaptureView = resolveView(viewTag);
89
+
90
+ faceCaptureView.startAnalyzing();
91
+ } catch (Exception e) {
92
+ e.printStackTrace();
88
93
  }
89
94
  });
90
95
  }
91
96
 
92
97
  @ReactMethod
93
98
  public void stopAnalyzing(final int viewTag) {
94
- final ReactApplicationContext context = getReactApplicationContext();
95
- UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
96
- uiManager.addUIBlock(new UIBlock() {
97
- @Override
98
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
99
- final YotiFaceCaptureView faceCaptureView;
100
-
101
- try {
102
- faceCaptureView = (YotiFaceCaptureView) nativeViewHierarchyManager.resolveView(viewTag);
103
- faceCaptureView.stopAnalyzing();
104
- } catch (Exception e) {
105
- e.printStackTrace();
106
- }
99
+ UiThreadUtil.runOnUiThread(() -> {
100
+ try {
101
+ YotiFaceCaptureView faceCaptureView = resolveView(viewTag);
102
+
103
+ faceCaptureView.stopAnalyzing();
104
+ } catch (Exception e) {
105
+ e.printStackTrace();
107
106
  }
108
107
  });
109
108
  }
@@ -1,5 +1,6 @@
1
1
  package com.yoti.reactnative.facecapture;
2
2
 
3
+ import androidx.annotation.NonNull;
3
4
  import com.facebook.react.ReactPackage;
4
5
  import com.facebook.react.bridge.NativeModule;
5
6
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -9,13 +10,15 @@ import java.util.Arrays;
9
10
  import java.util.List;
10
11
 
11
12
  public class YotiFaceCapturePackage implements ReactPackage {
13
+ @NonNull
12
14
  @Override
13
- public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
15
+ public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
14
16
  return Arrays.<NativeModule>asList(new YotiFaceCaptureModule(reactContext));
15
17
  }
16
18
 
19
+ @NonNull
17
20
  @Override
18
- public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
21
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
19
22
  return Arrays.<ViewManager>asList(new YotiFaceCaptureViewManager());
20
23
  }
21
24
  }
@@ -4,8 +4,9 @@ import android.graphics.PointF;
4
4
  import android.util.Base64;
5
5
  import android.view.Choreographer;
6
6
  import android.view.View;
7
- import android.widget.LinearLayout;
7
+ import android.widget.FrameLayout;
8
8
 
9
+ import androidx.annotation.Nullable;
9
10
  import androidx.lifecycle.LifecycleOwner;
10
11
 
11
12
  import com.facebook.react.bridge.Arguments;
@@ -13,20 +14,17 @@ import com.facebook.react.bridge.ReactContext;
13
14
  import com.facebook.react.bridge.ReadableArray;
14
15
  import com.facebook.react.bridge.WritableMap;
15
16
  import com.facebook.react.uimanager.ThemedReactContext;
16
-
17
- import com.facebook.react.uimanager.events.RCTEventEmitter;
17
+ import com.facebook.react.uimanager.UIManagerHelper;
18
+ import com.facebook.react.uimanager.events.EventDispatcher;
18
19
  import com.yoti.mobile.android.capture.face.ui.FaceCapture;
19
20
  import com.yoti.mobile.android.capture.face.ui.FaceCaptureListener;
20
- import com.yoti.mobile.android.capture.face.ui.models.camera.CameraState;
21
21
  import com.yoti.mobile.android.capture.face.ui.models.camera.CameraStateListener;
22
22
  import com.yoti.mobile.android.capture.face.ui.models.face.FaceCaptureConfiguration;
23
- import com.yoti.mobile.android.capture.face.ui.models.face.FaceCaptureResult;
24
23
  import com.yoti.mobile.android.capture.face.ui.models.face.FaceCaptureState;
25
24
  import com.yoti.mobile.android.capture.face.ui.models.face.ImageQuality;
25
+ import java.util.Objects;
26
26
 
27
- import org.jetbrains.annotations.NotNull;
28
-
29
- public class YotiFaceCaptureView extends LinearLayout {
27
+ public class YotiFaceCaptureView extends FrameLayout {
30
28
  private final ThemedReactContext context;
31
29
  private final FaceCapture mFaceCapture;
32
30
  private ImageQuality mImageQuality;
@@ -35,61 +33,60 @@ public class YotiFaceCaptureView extends LinearLayout {
35
33
  private boolean mRequireBrightEnvironment = true;
36
34
  private int mRequiredStableFrames;
37
35
  private ReadableArray mFaceCenter;
38
- private final CameraStateListener mCameraStateListener = new CameraStateListener() {
36
+ private boolean isCleanedUp = false;
37
+
38
+ private final Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
39
39
  @Override
40
- public void onCameraState(@NotNull CameraState cameraState) {
41
- WritableMap event = Arguments.createMap();
42
- event.putString("state", cameraState.getClass().getSimpleName());
43
- ReactContext reactContext = (ReactContext) getContext();
44
- reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
45
- getId(),
46
- "onCameraStateChange",
47
- event);
40
+ public void doFrame(long frameTimeNanos) {
41
+ if (!isCleanedUp) {
42
+ manuallyLayoutChildren();
43
+ getViewTreeObserver().dispatchOnGlobalLayout();
44
+ Choreographer.getInstance().postFrameCallback(this);
45
+ }
48
46
  }
49
47
  };
50
- private final FaceCaptureListener mFaceCaptureListener = new FaceCaptureListener() {
51
- @Override
52
- public void onFaceCaptureResult(@NotNull FaceCaptureResult faceCaptureResult) {
53
- WritableMap event = Arguments.createMap();
54
- event.putString("originalImage", Base64.encodeToString(faceCaptureResult.getOriginalImage().getData(), Base64.DEFAULT));
55
- event.putString("state", faceCaptureResult.getState().getClass().getSimpleName());
56
-
57
- FaceCaptureState s = faceCaptureResult.getState();
58
- if (s instanceof FaceCaptureState.InvalidFace) {
59
- event.putString("cause", ((FaceCaptureState.InvalidFace) s).getCause().getClass().getSimpleName());
60
- }
61
- if (s instanceof FaceCaptureState.ValidFace) {
62
- int croppedFaceBoundingBoxLeft = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().left;
63
- int croppedFaceBoundingBoxTop = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().top;
64
- int croppedFaceBoundingBoxRight = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().right;
65
- int croppedFaceBoundingBoxBottom = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().bottom;
66
- WritableMap croppedFaceBoundingBox = Arguments.createMap();
67
- croppedFaceBoundingBox.putInt("x", croppedFaceBoundingBoxLeft);
68
- croppedFaceBoundingBox.putInt("y", croppedFaceBoundingBoxTop);
69
- croppedFaceBoundingBox.putInt("width", croppedFaceBoundingBoxRight - croppedFaceBoundingBoxLeft);
70
- croppedFaceBoundingBox.putInt("height", croppedFaceBoundingBoxBottom - croppedFaceBoundingBoxTop);
71
-
72
- int faceBoundingBoxLeft = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().left;
73
- int faceBoundingBoxTop = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().top;
74
- int faceBoundingBoxRight = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().right;
75
- int faceBoundingBoxBottom = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().bottom;
76
- WritableMap faceBoundingBox = Arguments.createMap();
77
- faceBoundingBox.putInt("x", faceBoundingBoxLeft);
78
- faceBoundingBox.putInt("y", faceBoundingBoxTop);
79
- faceBoundingBox.putInt("width", faceBoundingBoxRight - faceBoundingBoxLeft);
80
- faceBoundingBox.putInt("height", faceBoundingBoxBottom - faceBoundingBoxTop);
81
-
82
- event.putString("croppedImage", Base64.encodeToString(((FaceCaptureState.ValidFace) s).getCroppedImage(), Base64.DEFAULT));
83
- event.putMap("croppedFaceBoundingBox", croppedFaceBoundingBox);
84
- event.putMap("faceBoundingBox", faceBoundingBox);
85
- }
86
-
87
- ReactContext reactContext = (ReactContext) getContext();
88
- reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
89
- getId(),
90
- "onFaceCaptureResult",
91
- event);
48
+
49
+ private final CameraStateListener mCameraStateListener = cameraState -> {
50
+ WritableMap event = Arguments.createMap();
51
+ event.putString("state", cameraState.getClass().getSimpleName());
52
+ sendEvent("onCameraStateChange", event);
53
+ };
54
+ private final FaceCaptureListener mFaceCaptureListener = faceCaptureResult -> {
55
+ WritableMap event = Arguments.createMap();
56
+ event.putString("originalImage", Base64.encodeToString(faceCaptureResult.getOriginalImage().getData(), Base64.DEFAULT));
57
+ event.putString("state", faceCaptureResult.getState().getClass().getSimpleName());
58
+
59
+ FaceCaptureState s = faceCaptureResult.getState();
60
+ if (s instanceof FaceCaptureState.InvalidFace) {
61
+ event.putString("cause", ((FaceCaptureState.InvalidFace) s).getCause().getClass().getSimpleName());
62
+ }
63
+ if (s instanceof FaceCaptureState.ValidFace) {
64
+ int croppedFaceBoundingBoxLeft = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().left;
65
+ int croppedFaceBoundingBoxTop = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().top;
66
+ int croppedFaceBoundingBoxRight = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().right;
67
+ int croppedFaceBoundingBoxBottom = ((FaceCaptureState.ValidFace) s).getCroppedFaceBoundingBox().bottom;
68
+ WritableMap croppedFaceBoundingBox = Arguments.createMap();
69
+ croppedFaceBoundingBox.putInt("x", croppedFaceBoundingBoxLeft);
70
+ croppedFaceBoundingBox.putInt("y", croppedFaceBoundingBoxTop);
71
+ croppedFaceBoundingBox.putInt("width", croppedFaceBoundingBoxRight - croppedFaceBoundingBoxLeft);
72
+ croppedFaceBoundingBox.putInt("height", croppedFaceBoundingBoxBottom - croppedFaceBoundingBoxTop);
73
+
74
+ int faceBoundingBoxLeft = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().left;
75
+ int faceBoundingBoxTop = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().top;
76
+ int faceBoundingBoxRight = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().right;
77
+ int faceBoundingBoxBottom = ((FaceCaptureState.ValidFace) s).getFaceBoundingBox().bottom;
78
+ WritableMap faceBoundingBox = Arguments.createMap();
79
+ faceBoundingBox.putInt("x", faceBoundingBoxLeft);
80
+ faceBoundingBox.putInt("y", faceBoundingBoxTop);
81
+ faceBoundingBox.putInt("width", faceBoundingBoxRight - faceBoundingBoxLeft);
82
+ faceBoundingBox.putInt("height", faceBoundingBoxBottom - faceBoundingBoxTop);
83
+
84
+ event.putString("croppedImage", Base64.encodeToString(((FaceCaptureState.ValidFace) s).getCroppedImage(), Base64.DEFAULT));
85
+ event.putMap("croppedFaceBoundingBox", croppedFaceBoundingBox);
86
+ event.putMap("faceBoundingBox", faceBoundingBox);
92
87
  }
88
+
89
+ sendEvent("onFaceCaptureResult", event);
93
90
  };
94
91
 
95
92
  YotiFaceCaptureView(ThemedReactContext context) {
@@ -97,14 +94,25 @@ public class YotiFaceCaptureView extends LinearLayout {
97
94
  this.context = context;
98
95
  inflate(context, R.layout.yotifacecapture, this);
99
96
  mFaceCapture = findViewById(R.id.faceCapture);
100
- Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
101
- @Override
102
- public void doFrame(long frameTimeNanos) {
103
- manuallyLayoutChildren();
104
- getViewTreeObserver().dispatchOnGlobalLayout();
105
- Choreographer.getInstance().postFrameCallback(this);
97
+
98
+ // Add layout change listener to handle React Native new architecture layout
99
+ addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
100
+ int width = right - left;
101
+ int height = bottom - top;
102
+ if (width > 0 && height > 0) {
103
+ // Force all children to match parent size
104
+ for (int i = 0; i < getChildCount(); i++) {
105
+ View child = getChildAt(i);
106
+ child.measure(
107
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
108
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
109
+ );
110
+ child.layout(0, 0, width, height);
111
+ }
106
112
  }
107
113
  });
114
+
115
+ Choreographer.getInstance().postFrameCallback(frameCallback);
108
116
  }
109
117
 
110
118
  void manuallyLayoutChildren() {
@@ -119,13 +127,38 @@ public class YotiFaceCaptureView extends LinearLayout {
119
127
  @Override
120
128
  public void requestLayout() {
121
129
  super.requestLayout();
130
+ // This is needed to ensure layout happens properly in React Native 0.7x+
131
+ // Without this, the view may have 0 width/height causing black screen
132
+ post(measureAndLayout);
133
+ }
134
+
135
+ private final Runnable measureAndLayout = () -> {
136
+ measure(
137
+ MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
138
+ MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
139
+ layout(getLeft(), getTop(), getRight(), getBottom());
140
+ };
141
+
142
+ /**
143
+ * Send events to React Native using the modern EventDispatcher API.
144
+ * This is compatible with both the old architecture and the new Fabric architecture.
145
+ */
146
+ private void sendEvent(String eventName, @Nullable WritableMap event) {
147
+ ReactContext reactContext = (ReactContext) getContext();
148
+ int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
149
+ EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
150
+ if (eventDispatcher != null) {
151
+ eventDispatcher.dispatchEvent(
152
+ new YotiFaceCaptureEvent(surfaceId, getId(), eventName, event)
153
+ );
154
+ }
122
155
  }
123
156
 
124
- public void setFaceCenter(ReadableArray faceCenter) throws Exception {
157
+ public void setFaceCenter(ReadableArray faceCenter) {
125
158
  mFaceCenter = faceCenter;
126
159
  }
127
160
 
128
- public void setImageQuality(String imageQuality) throws Exception {
161
+ public void setImageQuality(String imageQuality) {
129
162
  if (imageQuality.equals(ImageQuality.LOW.toString())) {
130
163
  mImageQuality = ImageQuality.LOW;
131
164
  return;
@@ -168,13 +201,21 @@ public class YotiFaceCaptureView extends LinearLayout {
168
201
  public void startAnalyzing() {
169
202
  PointF faceCenter = new PointF((float) mFaceCenter.getDouble(0), (float) mFaceCenter.getDouble(1));
170
203
 
204
+ // Configuration flags not available for FCM variant
205
+ final boolean isSelfCheckoutMode = false;
206
+ final boolean provideLandmarks = false;
207
+ final boolean provideSmileScore = false;
208
+
171
209
  FaceCaptureConfiguration configuration = new FaceCaptureConfiguration(
172
- faceCenter,
173
- mImageQuality,
174
- mRequireValidAngle,
175
- mRequireEyesOpen,
176
- mRequireBrightEnvironment,
177
- mRequiredStableFrames
210
+ faceCenter,
211
+ mImageQuality,
212
+ mRequireValidAngle,
213
+ mRequireEyesOpen,
214
+ mRequireBrightEnvironment,
215
+ mRequiredStableFrames,
216
+ provideLandmarks,
217
+ provideSmileScore,
218
+ isSelfCheckoutMode
178
219
  );
179
220
  mFaceCapture.startAnalysing(configuration, mFaceCaptureListener);
180
221
  }
@@ -182,4 +223,19 @@ public class YotiFaceCaptureView extends LinearLayout {
182
223
  public void stopAnalyzing() {
183
224
  mFaceCapture.stopAnalysing();
184
225
  }
226
+
227
+ /**
228
+ * Clean up resources when the view is dropped.
229
+ * Called from ViewManager.onDropViewInstance()
230
+ */
231
+ public void cleanup() {
232
+ isCleanedUp = true;
233
+ Choreographer.getInstance().removeFrameCallback(frameCallback);
234
+ try {
235
+ mFaceCapture.stopAnalysing();
236
+ mFaceCapture.stopCamera();
237
+ } catch (Exception e) {
238
+ // Ignore cleanup errors
239
+ }
240
+ }
185
241
  }
@@ -1,6 +1,7 @@
1
1
  package com.yoti.reactnative.facecapture;
2
2
 
3
3
  import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
4
5
 
5
6
  import com.facebook.react.bridge.ReadableArray;
6
7
  import com.facebook.react.common.MapBuilder;
@@ -21,10 +22,12 @@ public class YotiFaceCaptureViewManager extends SimpleViewManager<YotiFaceCaptur
21
22
 
22
23
  @Override
23
24
  @NonNull
24
- public YotiFaceCaptureView createViewInstance(ThemedReactContext reactContext) {
25
+ public YotiFaceCaptureView createViewInstance(@NonNull ThemedReactContext reactContext) {
25
26
  return new YotiFaceCaptureView(reactContext);
26
27
  }
27
28
 
29
+ @Override
30
+ @Nullable
28
31
  public Map getExportedCustomBubblingEventTypeConstants() {
29
32
  return MapBuilder.builder()
30
33
  .put(
@@ -40,6 +43,12 @@ public class YotiFaceCaptureViewManager extends SimpleViewManager<YotiFaceCaptur
40
43
  .build();
41
44
  }
42
45
 
46
+ @Override
47
+ public void onDropViewInstance(@NonNull YotiFaceCaptureView view) {
48
+ view.cleanup();
49
+ super.onDropViewInstance(view);
50
+ }
51
+
43
52
  @ReactProp(name = "faceCenter")
44
53
  public void setFaceCenter(YotiFaceCaptureView view, ReadableArray faceCenter) throws Exception {
45
54
  try {