Skip to content

Commit e24bf31

Browse files
committed
version 1.1.0
1 parent 9d70fef commit e24bf31

25 files changed

Lines changed: 621 additions & 15 deletions

android/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ dependencies {
7777
implementation 'com.facebook.react:react-native:+' // From node_modules
7878

7979
implementation 'id.vouched.android:vouched-sdk:1.3.0'
80-
implementation 'com.google.mlkit:face-detection:16.1.2'
80+
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
81+
implementation 'com.google.mlkit:face-detection:16.1.4'
8182
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
8283
}
8384

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package id.vouched.rn;
2+
3+
import android.annotation.SuppressLint;
4+
import android.app.Activity;
5+
import android.os.Handler;
6+
import android.os.HandlerThread;
7+
import android.util.Log;
8+
import android.util.Size;
9+
import android.view.Choreographer;
10+
import android.view.LayoutInflater;
11+
import android.view.View;
12+
13+
import androidx.annotation.NonNull;
14+
import androidx.camera.core.CameraSelector;
15+
import androidx.camera.core.ImageAnalysis;
16+
import androidx.camera.core.ImageProxy;
17+
import androidx.camera.core.Preview;
18+
import androidx.camera.lifecycle.ProcessCameraProvider;
19+
import androidx.camera.view.PreviewView;
20+
import androidx.constraintlayout.widget.ConstraintLayout;
21+
import androidx.core.content.ContextCompat;
22+
import androidx.lifecycle.LifecycleOwner;
23+
24+
import com.facebook.react.bridge.Arguments;
25+
import com.facebook.react.bridge.LifecycleEventListener;
26+
import com.facebook.react.bridge.WritableMap;
27+
import com.facebook.react.uimanager.ThemedReactContext;
28+
import com.facebook.react.uimanager.events.RCTEventEmitter;
29+
import com.google.common.util.concurrent.ListenableFuture;
30+
31+
import java.util.concurrent.ExecutionException;
32+
33+
import id.vouched.android.BarcodeDetect;
34+
import id.vouched.android.BarcodeResult;
35+
import id.vouched.android.exception.VouchedAssetsMissingException;
36+
37+
public class BarcodeCameraView extends ConstraintLayout
38+
implements LifecycleEventListener, BarcodeDetect.OnBarcodeResultListener {
39+
40+
public static final String ON_BARCODE_STREAM_EVENT = "onBarcodeStream";
41+
private static final Size DESIRED_PREVIEW_BARCODE_SIZE = new Size(1080, 1920);
42+
43+
private final ThemedReactContext mThemedReactContext;
44+
private final CameraSelector cameraSelector;
45+
private final Activity activity;
46+
private PreviewView previewView;
47+
private ProcessCameraProvider cameraProvider;
48+
private Preview previewUseCase;
49+
private ImageAnalysis analysisUseCase;
50+
51+
private Handler handler;
52+
private HandlerThread handlerThread;
53+
54+
private BarcodeDetect barcodeDetect;
55+
56+
private boolean isRendered = false;
57+
private boolean isStopped = false;
58+
59+
public BarcodeCameraView(@NonNull ThemedReactContext themedReactContext) {
60+
super(themedReactContext);
61+
mThemedReactContext = themedReactContext;
62+
activity = themedReactContext.getCurrentActivity();
63+
barcodeDetect = new BarcodeDetect(this);
64+
cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
65+
66+
ConstraintLayout layout = (ConstraintLayout) LayoutInflater.from(themedReactContext).inflate(R.layout.id_camera, this, true);
67+
previewView = layout.findViewById(R.id.preview_view);
68+
if (previewView == null) {
69+
System.out.println("previewView is null");
70+
}
71+
72+
handlerThread = new HandlerThread("inference");
73+
handlerThread.start();
74+
handler = new Handler(handlerThread.getLooper());
75+
76+
startCamera();
77+
setupLayoutHack();
78+
}
79+
80+
@Override
81+
public void onHostResume() {
82+
handlerThread = new HandlerThread("inference");
83+
handlerThread.start();
84+
handler = new Handler(handlerThread.getLooper());
85+
bindAllCameraUseCases();
86+
}
87+
88+
@Override
89+
public void onHostPause() {
90+
if (handlerThread != null) {
91+
handlerThread.quitSafely();
92+
try {
93+
handlerThread.join();
94+
handlerThread = null;
95+
handler = null;
96+
} catch (final InterruptedException e) {
97+
e.printStackTrace();
98+
}
99+
}
100+
}
101+
102+
@Override
103+
public void onHostDestroy() {
104+
if (cameraProvider != null) {
105+
cameraProvider.unbindAll();
106+
}
107+
}
108+
109+
private void startCamera() {
110+
final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(mThemedReactContext);
111+
cameraProviderFuture.addListener(new Runnable() {
112+
@Override
113+
public void run() {
114+
try {
115+
cameraProvider = cameraProviderFuture.get();
116+
bindAllCameraUseCases();
117+
} catch (ExecutionException | InterruptedException e) {
118+
e.printStackTrace();
119+
}
120+
}
121+
}, ContextCompat.getMainExecutor(mThemedReactContext));
122+
}
123+
124+
private void bindAllCameraUseCases() {
125+
if (cameraProvider != null) {
126+
// As required by CameraX API, unbinds all use cases before trying to re-bind any of them.
127+
cameraProvider.unbindAll();
128+
bindPreviewUseCase();
129+
bindAnalysisUseCase();
130+
}
131+
}
132+
133+
private void bindPreviewUseCase() {
134+
if (cameraProvider == null) {
135+
return;
136+
}
137+
if (previewUseCase != null) {
138+
cameraProvider.unbind(previewUseCase);
139+
}
140+
141+
Preview.Builder builder = new Preview.Builder();
142+
builder.setTargetResolution(DESIRED_PREVIEW_BARCODE_SIZE);
143+
previewUseCase = builder.build();
144+
previewUseCase.setSurfaceProvider(previewView.getSurfaceProvider());
145+
cameraProvider.bindToLifecycle((LifecycleOwner) activity, cameraSelector, previewUseCase);
146+
}
147+
148+
private void bindAnalysisUseCase() {
149+
if (cameraProvider == null) {
150+
return;
151+
}
152+
if (analysisUseCase != null) {
153+
cameraProvider.unbind(analysisUseCase);
154+
}
155+
156+
ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
157+
builder.setTargetResolution( DESIRED_PREVIEW_BARCODE_SIZE);
158+
analysisUseCase = builder.build();
159+
160+
analysisUseCase.setAnalyzer(
161+
ContextCompat.getMainExecutor(activity),
162+
new ImageAnalysis.Analyzer() {
163+
@SuppressLint("UnsafeExperimentalUsageError")
164+
@Override
165+
public void analyze(@NonNull ImageProxy imageProxy) {
166+
try {
167+
if (barcodeDetect != null) {
168+
barcodeDetect.findBarcode(imageProxy);
169+
}
170+
} catch (Exception e) {
171+
System.out.println("Failed to process image. Error: " + e.getLocalizedMessage());
172+
}
173+
}
174+
});
175+
176+
cameraProvider.bindToLifecycle((LifecycleOwner) activity, cameraSelector, analysisUseCase);
177+
}
178+
179+
@Override
180+
public void onBarcodeResult(BarcodeResult barcodeResult) {
181+
if (barcodeResult != null) {
182+
isRendered = true;
183+
sendBarcodeDetectEvent(barcodeResult);
184+
}
185+
}
186+
187+
private void sendBarcodeDetectEvent(BarcodeResult barcodeResult) {
188+
WritableMap event = Arguments.createMap();
189+
event.putString("value", barcodeResult.getValue());
190+
event.putString("image", barcodeResult.getImage());
191+
mThemedReactContext
192+
.getJSModule(RCTEventEmitter.class)
193+
.receiveEvent(getId(), ON_BARCODE_STREAM_EVENT, event);
194+
}
195+
196+
private void setupLayoutHack() {
197+
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
198+
@Override
199+
public void doFrame(long frameTimeNanos) {
200+
if (isRendered) {
201+
Choreographer.getInstance().postFrameCallback(this);
202+
return;
203+
}
204+
205+
manuallyLayoutChildren();
206+
getViewTreeObserver().dispatchOnGlobalLayout();
207+
Choreographer.getInstance().postFrameCallback(this);
208+
}
209+
});
210+
}
211+
212+
private void manuallyLayoutChildren() {
213+
for (int i = 0; i < getChildCount(); i++) {
214+
View child = getChildAt(i);
215+
child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
216+
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
217+
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
218+
}
219+
}
220+
221+
public void stop() {
222+
if (isStopped) return;
223+
isStopped = true;
224+
225+
if (cameraProvider != null) {
226+
cameraProvider.unbindAll();
227+
}
228+
}
229+
230+
public void start() {
231+
bindAllCameraUseCases();
232+
isStopped = false;
233+
isRendered = false;
234+
}
235+
236+
}
237+

android/src/main/java/id/vouched/rn/IdCameraView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,4 @@ public void start() {
251251
isRendered = false;
252252
}
253253

254-
}
254+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package id.vouched.rn;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
5+
6+
import com.facebook.react.bridge.ReadableArray;
7+
import com.facebook.react.common.MapBuilder;
8+
import com.facebook.react.uimanager.ThemedReactContext;
9+
import com.facebook.react.uimanager.ViewGroupManager;
10+
import com.facebook.react.uimanager.annotations.ReactProp;
11+
12+
import java.util.Map;
13+
14+
import id.vouched.android.exception.VouchedAssetsMissingException;
15+
16+
public class VouchedBarcodeManager extends ViewGroupManager<BarcodeCameraView> {
17+
public static final int COMMAND_BARCODE_CAMERA_STOP = 25;
18+
public static final int COMMAND_BARCODE_CAMERA_START = 624;
19+
20+
@NonNull
21+
@Override
22+
public String getName() {
23+
return "BarcodeCamera";
24+
}
25+
26+
@NonNull
27+
@Override
28+
protected BarcodeCameraView createViewInstance(@NonNull ThemedReactContext reactContext) {
29+
return new BarcodeCameraView(reactContext);
30+
}
31+
32+
@Override
33+
public Map getExportedCustomDirectEventTypeConstants() {
34+
return MapBuilder.of(
35+
BarcodeCameraView.ON_BARCODE_STREAM_EVENT,
36+
MapBuilder.of("registrationName", BarcodeCameraView.ON_BARCODE_STREAM_EVENT)
37+
);
38+
}
39+
40+
@Nullable
41+
@Override
42+
public Map<String, Integer> getCommandsMap() {
43+
return MapBuilder.of(
44+
"stop", COMMAND_BARCODE_CAMERA_STOP,
45+
"restart", COMMAND_BARCODE_CAMERA_START
46+
);
47+
}
48+
49+
@Override
50+
public void receiveCommand(@NonNull BarcodeCameraView root, int commandId, @Nullable ReadableArray args) {
51+
switch (commandId) {
52+
case COMMAND_BARCODE_CAMERA_STOP:
53+
root.stop();
54+
break;
55+
case COMMAND_BARCODE_CAMERA_START:
56+
root.start();
57+
break;
58+
}
59+
}
60+
}
61+

android/src/main/java/id/vouched/rn/VouchedPackage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
1616

1717
@Override
1818
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
19-
return Arrays.<ViewManager>asList(new VouchedIdManager(), new VouchedFaceManager());
19+
return Arrays.<ViewManager>asList(new VouchedIdManager(), new VouchedFaceManager(), new VouchedBarcodeManager());
2020
}
2121
}

android/src/main/java/id/vouched/rn/VouchedSessionModule.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import java.util.Map;
1616

17+
import id.vouched.android.BarcodeResult;
1718
import id.vouched.android.CardDetectResult;
1819
import id.vouched.android.FaceDetectResult;
1920
import id.vouched.android.VouchedSession;
@@ -27,6 +28,7 @@ public class VouchedSessionModule extends ReactContextBaseJavaModule {
2728

2829
private static final String SESSION_NOT_CONFIGURED = "SESSION_NOT_CONFIGURED";
2930
private static final String POST_FRONT_ID_FAIL = "POST_FRONT_ID_FAIL";
31+
private static final String POST_BARCODE_FAIL = "POST_BARCODE_FAIL";
3032
private static final String POST_BACK_ID_FAIL = "POST_BACK_ID_FAIL";
3133
private static final String POST_FACE_FAIL = "POST_FACE_FAIL";
3234
private static final String CONFIRM_FAIL = "CONFIRM_FAIL";
@@ -134,6 +136,35 @@ public void onJobResponse(JobResponse jobResponse) {
134136
}
135137
}
136138

139+
@ReactMethod
140+
public void postBarcode(ReadableMap detectResult, final Promise promise) {
141+
if (session == null) {
142+
promise.reject(SESSION_NOT_CONFIGURED, "session must be configured");
143+
return;
144+
}
145+
146+
String barcodeText = detectResult.getString("value");
147+
String image = detectResult.getString("image");
148+
149+
BarcodeResult barcodeResult = new BarcodeResult(barcodeText, image);
150+
151+
try {
152+
session.postBackId(getReactApplicationContext(), barcodeResult, new Params.Builder(), new VouchedSession.OnJobResponseListener() {
153+
@Override
154+
public void onJobResponse(JobResponse jobResponse) {
155+
VouchedError jobError = jobResponse.getError();
156+
if (jobError != null) {
157+
promise.reject(POST_BACK_ID_FAIL, jobError.getMessage());
158+
} else {
159+
promise.resolve(jobResponse.getJob().toJson());
160+
}
161+
}
162+
});
163+
} catch (Exception e) {
164+
promise.reject(e);
165+
}
166+
}
167+
137168
@ReactMethod
138169
public void postFace(ReadableMap detectResult, final Promise promise) {
139170
if (session == null) {

example/App.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createStackNavigator } from '@react-navigation/stack';
77
import HomeScreen from 'components/HomeScreen';
88
import IDScreen from 'components/IDScreen';
99
import BackIDScreen from 'components/BackIDScreen';
10+
import BarcodeScreen from 'components/BarcodeScreen';
1011
import FaceScreen from 'components/FaceScreen';
1112
import DoneScreen from 'components/DoneScreen';
1213
import AuthScreen from 'components/AuthScreen';
@@ -75,6 +76,11 @@ const App = () => {
7576
component={BackIDScreen}
7677
options={{ headerLeft: null }}
7778
/>
79+
<Stack.Screen
80+
name="Barcode"
81+
component={BarcodeScreen}
82+
options={{ headerLeft: null }}
83+
/>
7884
<Stack.Screen
7985
name="Face"
8086
component={FaceScreen}

0 commit comments

Comments
 (0)