@faceaisdk/react-native-face-sdk 0.1.1

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/LICENSE +1 -0
  2. package/README.md +252 -0
  3. package/android/build.gradle +53 -0
  4. package/android/libs/FaceSDKLib-release.aar +0 -0
  5. package/android/proguard-rules.pro +3 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/java/com/faceaisdk/reactnative/FaceRNModule.kt +383 -0
  8. package/android/src/main/java/com/faceaisdk/reactnative/FaceRNPackage.kt +16 -0
  9. package/ios/FaceAISDK/CustomToastView.swift +34 -0
  10. package/ios/FaceAISDK/FaceAINaviView.swift +212 -0
  11. package/ios/FaceAISDK/FaceSDKCameraView.swift +40 -0
  12. package/ios/FaceAISDK/FaceSDKLocalizer.swift +21 -0
  13. package/ios/FaceAISDK/LivenessDetectView.swift +317 -0
  14. package/ios/FaceAISDK/ScreenBrightnessHelper.swift +100 -0
  15. package/ios/FaceAISDK/TTSPlayer.swift +357 -0
  16. package/ios/FaceAISDK/VerifyFaceView.swift +284 -0
  17. package/ios/FaceAISDK/addFace/AddFaceByCamera.swift +207 -0
  18. package/ios/FaceAISDK/addFace/AddFaceByImage.swift +174 -0
  19. package/ios/FaceAISDK/addFace/ImagePicker.swift +52 -0
  20. package/ios/FaceAISDK/addFace/VerifyTwoFaceSimiView.swift +210 -0
  21. package/ios/FaceColorExtensions.swift +10 -0
  22. package/ios/FaceRNModule.h +9 -0
  23. package/ios/FaceRNModule.m +197 -0
  24. package/ios/FaceSDKSwiftManager.swift +277 -0
  25. package/ios/Resources/en.lproj/Localizable.strings +51 -0
  26. package/ios/Resources/light_too_high.png +0 -0
  27. package/ios/Resources/zh-Hans.lproj/Localizable.strings +51 -0
  28. package/lib/index.d.ts +22 -0
  29. package/lib/index.js +112 -0
  30. package/lib/types.d.ts +39 -0
  31. package/lib/types.js +2 -0
  32. package/package.json +88 -0
  33. package/react-native-face-sdk.podspec +28 -0
  34. package/src/index.ts +184 -0
  35. package/src/types.ts +90 -0
@@ -0,0 +1,52 @@
1
+ import SwiftUI
2
+ import PhotosUI
3
+
4
+ // Select image from album
5
+ struct ImagePicker: UIViewControllerRepresentable {
6
+ @Binding var selectedImage: UIImage?
7
+ @Environment(\.dismiss) private var dismiss
8
+
9
+ var onImagePicked: ((UIImage) -> Void)?
10
+
11
+ func makeUIViewController(context: Context) -> PHPickerViewController {
12
+ var config = PHPickerConfiguration()
13
+ config.filter = .images
14
+ config.selectionLimit = 1
15
+
16
+ let picker = PHPickerViewController(configuration: config)
17
+ picker.delegate = context.coordinator
18
+ return picker
19
+ }
20
+
21
+ func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
22
+
23
+ func makeCoordinator() -> Coordinator {
24
+ Coordinator(self)
25
+ }
26
+
27
+ class Coordinator: NSObject, PHPickerViewControllerDelegate {
28
+ let parent: ImagePicker
29
+
30
+ init(_ parent: ImagePicker) {
31
+ self.parent = parent
32
+ }
33
+
34
+ func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
35
+ parent.dismiss()
36
+
37
+ guard let provider = results.first?.itemProvider,
38
+ provider.canLoadObject(ofClass: UIImage.self) else {
39
+ return
40
+ }
41
+
42
+ provider.loadObject(ofClass: UIImage.self) { image, error in
43
+ if let uiImage = image as? UIImage {
44
+ DispatchQueue.main.async {
45
+ self.parent.selectedImage = uiImage
46
+ self.parent.onImagePicked?(uiImage)
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,210 @@
1
+ import SwiftUI
2
+ import PhotosUI
3
+ import FaceAISDK_Core
4
+
5
+ // 定义单侧人脸的数据模型
6
+ struct FaceSlot {
7
+ var originalImage: UIImage?
8
+ var croppedImage: UIImage?
9
+ var feature: String?
10
+ var isLoading: Bool = false
11
+ }
12
+
13
+ //SDK API viewModel.evaluateSimilarity(f1: f1, f2: f2)
14
+ public struct VerifyTwoFaceSimiView: View {
15
+ // 恢复 dismiss 以支持自定义导航栏返回
16
+ @Environment(\.dismiss) private var dismiss
17
+
18
+ @State private var leftSlot = FaceSlot()
19
+ @State private var rightSlot = FaceSlot()
20
+
21
+ @StateObject private var viewModel = VerifyTwoFaceSimiModel()
22
+ @State private var similarityResult: String = ""
23
+ @State private var activePicker: PickerType?
24
+
25
+ // CustomToastView 相关状态
26
+ @State private var showToast = false
27
+ @State private var toastMessage = ""
28
+ @State private var toastStyle: ToastStyle = .success
29
+
30
+ enum PickerType: Identifiable {
31
+ case left, right
32
+ var id: Int { hashValue }
33
+ }
34
+
35
+ // 干净的初始化方法
36
+ public init() {}
37
+
38
+ public var body: some View {
39
+ ZStack {
40
+ VStack(spacing: 0) {
41
+ HStack {
42
+ Button(action: {
43
+ dismiss()
44
+ }) {
45
+ Image(systemName: "chevron.left")
46
+ .font(.system(size: 16, weight: .semibold))
47
+ .foregroundColor(.black)
48
+ .padding(10)
49
+ .background(Color.gray.opacity(0.1))
50
+ .clipShape(Circle())
51
+ }
52
+ Text("Verify Two Face Similarity")
53
+ .font(.headline)
54
+ Spacer()
55
+ }
56
+ .padding(.horizontal, 20)
57
+ .padding(.top, 10)
58
+ .padding(.bottom, 20)
59
+
60
+ ScrollView {
61
+ VStack(spacing: 30) {
62
+ HStack(spacing: 20) {
63
+ faceBox(slot: leftSlot) { activePicker = .left }
64
+ faceBox(slot: rightSlot) { activePicker = .right }
65
+ }
66
+ .padding(.horizontal, 20)
67
+ .padding(.top, 25)
68
+
69
+ if !similarityResult.isEmpty {
70
+ VStack(spacing: 8) {
71
+ Text(similarityResult)
72
+ .font(.system(size: 28, weight: .heavy))
73
+ .foregroundColor(.blue)
74
+ }
75
+ .frame(maxWidth: .infinity)
76
+ .padding(.vertical, 12)
77
+ .padding(.horizontal, 22)
78
+ .background(Color.blue.opacity(0.1))
79
+ .cornerRadius(16)
80
+ .padding(.horizontal, 33)
81
+ }
82
+
83
+ // MARK: - 4. 操作按钮
84
+ Button(action: runComparison) {
85
+ Text("Verify Two Face Similarity")
86
+ .font(.headline).foregroundColor(.white)
87
+ .frame(maxWidth: .infinity).frame(height: 55)
88
+ .background(canCompare ? Color.blue : Color.gray)
89
+ .cornerRadius(12)
90
+ }
91
+ .disabled(!canCompare)
92
+ .padding(.horizontal, 33)
93
+ }
94
+ }
95
+ }
96
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
97
+ .background(Color.white.ignoresSafeArea())
98
+ .navigationBarBackButtonHidden(true)
99
+ .navigationBarHidden(true)
100
+
101
+ if showToast {
102
+ VStack {
103
+ Spacer()
104
+ CustomToastView(
105
+ message: toastMessage,
106
+ style: toastStyle
107
+ )
108
+ .padding(.bottom, 77)
109
+ }
110
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
111
+ .transition(.move(edge: .bottom).combined(with: .opacity))
112
+ .zIndex(1)
113
+ }
114
+ }
115
+ .sheet(item: $activePicker) { type in
116
+ ImagePicker(selectedImage: .constant(nil)) { uiImage in
117
+ handleImageSelected(uiImage, for: type)
118
+ }
119
+ }
120
+ // 监听 Model 提示状态改变,弹出 Toast
121
+ .onChange(of: viewModel.sdkInterfaceTips.code) { code in
122
+ if code != 0 {
123
+ let msg = NSLocalizedString("Face_Tips_Code_\(code)", comment: "")
124
+ toastMessage = msg
125
+
126
+ // 简单约定:如果检测到人脸(Code为确认录入等)视为 success,否则视为 failure
127
+ toastStyle = (code == FaceTipsCode.CONFIRM_ADD_FACE) ? .success : .failure
128
+
129
+ withAnimation {
130
+ showToast = true
131
+ }
132
+
133
+ // 2秒后自动隐藏 Toast
134
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
135
+ withAnimation {
136
+ showToast = false
137
+ }
138
+ }
139
+ }
140
+ }
141
+ .animation(.easeInOut(duration: 0.3), value: showToast)
142
+ }
143
+
144
+ private var canCompare: Bool {
145
+ leftSlot.feature != nil && rightSlot.feature != nil
146
+ }
147
+
148
+ // 处理图片选择后的初始化与直接回调闭包
149
+ private func handleImageSelected(_ image: UIImage, for type: PickerType) {
150
+ // 选择图片后,先重置当前 Slot 的状态
151
+ if type == .left {
152
+ leftSlot.originalImage = image
153
+ leftSlot.isLoading = true
154
+ leftSlot.feature = nil
155
+ } else {
156
+ rightSlot.originalImage = image
157
+ rightSlot.isLoading = true
158
+ rightSlot.feature = nil
159
+ }
160
+
161
+ // 调用 Model 闭包处理人脸
162
+ viewModel.processImage(image) { croppedImage, feature in
163
+ // 无论成功还是失败,都会走到这里,从而安全地关闭 Loading 状态
164
+ if type == .left {
165
+ leftSlot.isLoading = false
166
+ leftSlot.croppedImage = croppedImage
167
+ leftSlot.feature = feature
168
+ } else {
169
+ rightSlot.isLoading = false
170
+ rightSlot.croppedImage = croppedImage
171
+ rightSlot.feature = feature
172
+ }
173
+ similarityResult = "" //清空之前的结果
174
+ }
175
+ }
176
+
177
+ private func runComparison() {
178
+ guard let f1 = leftSlot.feature, let f2 = rightSlot.feature else { return }
179
+ let score = viewModel.evaluateSimilarity(f1: f1, f2: f2)
180
+ similarityResult = String(format: "%.2f%%", score * 100)
181
+ }
182
+
183
+ // 复用 UI 组件
184
+ @ViewBuilder
185
+ private func faceBox(slot: FaceSlot, action: @escaping () -> Void) -> some View {
186
+ Button(action: action) {
187
+ ZStack {
188
+ if let displayImg = slot.croppedImage ?? slot.originalImage {
189
+ Image(uiImage: displayImg).resizable().scaledToFill()
190
+ } else {
191
+ VStack {
192
+ Image(systemName: "person.crop.rectangle.badge.plus")
193
+ .font(.largeTitle)
194
+ }.foregroundColor(.gray)
195
+ }
196
+
197
+ if slot.isLoading {
198
+ ZStack {
199
+ Color.black.opacity(0.3)
200
+ ProgressView().tint(.white)
201
+ }
202
+ }
203
+ }
204
+ .frame(width: 150, height: 150)
205
+ .background(Color.gray.opacity(0.1))
206
+ .cornerRadius(16)
207
+ .clipped()
208
+ }.buttonStyle(PlainButtonStyle())
209
+ }
210
+ }
@@ -0,0 +1,10 @@
1
+ import Foundation
2
+ import SwiftUI
3
+
4
+ // 1. 如果想改变主题颜色
5
+ extension Color {
6
+ /// 主题颜色 #347738 ,用AI换算一下
7
+ static var faceMain: Color {
8
+ return Color(red: 52 / 255.0, green: 119 / 255.0, blue: 56 / 255.0)
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ //
2
+ // FaceRNModule.h
3
+ // FaceRN
4
+ //
5
+
6
+ #import <React/RCTBridgeModule.h>
7
+
8
+ @interface FaceRNModule : NSObject <RCTBridgeModule>
9
+ @end
@@ -0,0 +1,197 @@
1
+ //
2
+ // FaceRNModule.m
3
+ // FaceRN
4
+ //
5
+ // This file is a duplicate. See FaceRN/FaceRNModule.m for the actual implementation.
6
+ // 统一 iOS/Android 桥接 API,参考 FaceAISDK_uniapp_UTS
7
+
8
+ #import "FaceRNModule.h"
9
+ #import <UIKit/UIKit.h>
10
+
11
+ #import "FaceAISDKReactNative-Swift.h"
12
+
13
+ @implementation FaceRNModule
14
+
15
+ RCT_EXPORT_MODULE();
16
+
17
+ + (BOOL)requiresMainQueueSetup {
18
+ return YES;
19
+ }
20
+
21
+ - (dispatch_queue_t)methodQueue {
22
+ return dispatch_get_main_queue();
23
+ }
24
+
25
+ //结果Code 对应含义message 返回给插件调用方
26
+ - (NSString *)getMsgByCode:(NSInteger)code {
27
+ switch (code) {
28
+ case 0: return @"User canceled/interrupted";
29
+ case 1: return @"Operation succeeded";
30
+ case 2: return @"Low face similarity (verification failed)";
31
+ case 3: return @"Motion liveness passed";
32
+ case 4: return @"Motion liveness timeout";
33
+ case 5: return @"No face detected repeatedly";
34
+ case 6: return @"No local face feature found";
35
+ case 7: return @"Color liveness passed";
36
+ case 8: return @"Color liveness failed";
37
+ case 9: return @"Ambient light too strong (color failed)";
38
+ case 10: return @"All liveness checks passed";
39
+ case 11: return @"Silent liveness failed";
40
+ case 12: return @"No enrolled face information";
41
+ case 13: return @"Multiple faces detected";
42
+ default: return @"Unknown status code";
43
+ }
44
+ }
45
+
46
+ RCT_EXPORT_METHOD(addFaceBySDKCamera:(NSString *)faceID
47
+ addFacePerformanceMode:(nonnull NSNumber *)performanceMode
48
+ needShowConfirmDialog:(BOOL)needConfirm
49
+ callback:(RCTResponseSenderBlock)callback) {
50
+ dispatch_async(dispatch_get_main_queue(), ^{
51
+ [FaceSDKSwiftManager showAddFaceByCamera:faceID :performanceMode :needConfirm :^(NSNumber * _Nonnull resultCode, NSString * _Nonnull feature) {
52
+ NSString *msg = [resultCode integerValue] == 1
53
+ ? @"Face enrollment succeeded"
54
+ : @"User canceled enrollment";
55
+ NSString *base64Str = @"";
56
+ if ([resultCode integerValue] == 1) {
57
+ base64Str = [FaceSDKSwiftManager getFaceImageBase64:faceID] ?: @"";
58
+ }
59
+ NSString *faceFeature = feature.length > 0 ? feature : ([FaceSDKSwiftManager getiOSFaceFeature:faceID] ?: @"");
60
+ NSDictionary *result = @{
61
+ @"code": resultCode,
62
+ @"msg": msg,
63
+ @"faceID": faceID ?: @"",
64
+ @"similarity": @(0),
65
+ @"liveness": @(0),
66
+ @"faceFeature": faceFeature,
67
+ @"faceBase64": base64Str
68
+ };
69
+ callback(@[result]);
70
+ }];
71
+ });
72
+ }
73
+
74
+ RCT_EXPORT_METHOD(faceVerify:(NSString *)faceID
75
+ threshold:(nonnull NSNumber *)threshold
76
+ faceLivenessType:(nonnull NSNumber *)faceLivenessType
77
+ motionLivenessTypes:(NSString *)motionLivenessTypes
78
+ motionLivenessTimeOut:(nonnull NSNumber *)motionLivenessTimeOut
79
+ motionLivenessSteps:(nonnull NSNumber *)motionLivenessSteps
80
+ allowMultiFaces:(BOOL)allowMultiFaces
81
+ callback:(RCTResponseSenderBlock)callback) {
82
+ dispatch_async(dispatch_get_main_queue(), ^{
83
+ [FaceSDKSwiftManager showFaceVerify:faceID :threshold :faceLivenessType :motionLivenessTypes :motionLivenessTimeOut :motionLivenessSteps :^(NSNumber * _Nonnull resultCode, NSNumber * _Nonnull similarity, NSNumber * _Nonnull liveness) {
84
+ NSString *base64Str = @"";
85
+ NSInteger code = [resultCode integerValue];
86
+ if (code == 1 || code == 10) {
87
+ base64Str = [FaceSDKSwiftManager getFaceImageBase64:faceID] ?: @"";
88
+ }
89
+ NSDictionary *result = @{
90
+ @"code": resultCode,
91
+ @"msg": [self getMsgByCode:code],
92
+ @"faceID": faceID ?: @"",
93
+ @"similarity": similarity,
94
+ @"liveness": liveness,
95
+ @"faceFeature": @"",
96
+ @"faceBase64": base64Str
97
+ };
98
+ callback(@[result]);
99
+ }];
100
+ });
101
+ }
102
+
103
+ RCT_EXPORT_METHOD(livenessVerify:(nonnull NSNumber *)faceLivenessType
104
+ motionLivenessTypes:(NSString *)motionLivenessTypes
105
+ motionLivenessTimeOut:(nonnull NSNumber *)motionLivenessTimeOut
106
+ motionLivenessSteps:(nonnull NSNumber *)motionLivenessSteps
107
+ allowMultiFaces:(BOOL)allowMultiFaces
108
+ showResultTips:(BOOL)showResultTips
109
+ callback:(RCTResponseSenderBlock)callback) {
110
+ dispatch_async(dispatch_get_main_queue(), ^{
111
+ [FaceSDKSwiftManager showLivenessVerify:faceLivenessType :motionLivenessTypes :motionLivenessTimeOut :motionLivenessSteps :showResultTips :^(NSNumber * _Nonnull resultCode, NSNumber * _Nonnull liveness) {
112
+ NSString *base64Str = @"";
113
+ NSInteger code = [resultCode integerValue];
114
+ if (code == 1 || code == 10) {
115
+ base64Str = [FaceSDKSwiftManager getFaceImageBase64:@"Liveness"] ?: @"";
116
+ }
117
+ NSDictionary *result = @{
118
+ @"code": resultCode,
119
+ @"msg": [self getMsgByCode:code],
120
+ @"faceID": @"",
121
+ @"similarity": @(0),
122
+ @"liveness": liveness,
123
+ @"faceFeature": @"",
124
+ @"faceBase64": base64Str
125
+ };
126
+ callback(@[result]);
127
+ }];
128
+ });
129
+ }
130
+
131
+ RCT_EXPORT_METHOD(getFaceFeature:(NSString *)faceID
132
+ callback:(RCTResponseSenderBlock)callback) {
133
+ NSString *faceFeature = [FaceSDKSwiftManager getiOSFaceFeature:faceID] ?: @"";
134
+ [FaceSDKSwiftManager isFaceFeatureExist:faceID :^(NSNumber * _Nonnull resultCode, NSString * _Nonnull msg) {
135
+ NSDictionary *result = @{
136
+ @"code": resultCode,
137
+ @"msg": msg,
138
+ @"faceID": faceID ?: @"",
139
+ @"similarity": @(0),
140
+ @"liveness": @(0),
141
+ @"faceFeature": faceFeature,
142
+ @"faceBase64": @""
143
+ };
144
+ callback(@[result]);
145
+ }];
146
+ }
147
+
148
+ RCT_EXPORT_METHOD(insertFaceFeature:(NSString *)faceID
149
+ faceFeature:(NSString *)faceFeature
150
+ callback:(RCTResponseSenderBlock)callback) {
151
+ [FaceSDKSwiftManager insertFaceFeature:faceID :faceFeature :^(NSNumber * _Nonnull resultCode, NSString * _Nonnull msg) {
152
+ NSDictionary *result = @{
153
+ @"code": resultCode,
154
+ @"msg": msg,
155
+ @"faceID": faceID ?: @"",
156
+ @"similarity": @(0),
157
+ @"liveness": @(0),
158
+ @"faceFeature": @"",
159
+ @"faceBase64": @""
160
+ };
161
+ callback(@[result]);
162
+ }];
163
+ }
164
+
165
+ RCT_EXPORT_METHOD(addFaceBySDKImage:(NSString *)faceID
166
+ base64FaceImage:(NSString *)base64FaceImage
167
+ callback:(RCTResponseSenderBlock)callback) {
168
+ [FaceSDKSwiftManager addFaceByBase64:faceID :base64FaceImage :^(NSNumber * _Nonnull resultCode, NSString * _Nonnull feature, NSString * _Nonnull msg) {
169
+ NSDictionary *result = @{
170
+ @"code": resultCode,
171
+ @"msg": msg,
172
+ @"faceID": faceID ?: @"",
173
+ @"similarity": @(0),
174
+ @"liveness": @(0),
175
+ @"faceFeature": feature ?: @"",
176
+ @"faceBase64": @""
177
+ };
178
+ callback(@[result]);
179
+ }];
180
+ }
181
+
182
+ RCT_EXPORT_METHOD(deleteFaceFeature:(NSString *)faceID
183
+ callback:(RCTResponseSenderBlock)callback) {
184
+ [FaceSDKSwiftManager deleteFaceFeature:faceID];
185
+ NSDictionary *result = @{
186
+ @"code": @(1),
187
+ @"msg": @"Delete Success",
188
+ @"faceID": faceID ?: @"",
189
+ @"similarity": @(0),
190
+ @"liveness": @(0),
191
+ @"faceFeature": @"",
192
+ @"faceBase64": @""
193
+ };
194
+ callback(@[result]);
195
+ }
196
+
197
+ @end