@simasfin/smmf-sdk 0.0.0-stage → 0.2.3

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 (53) hide show
  1. package/README.md +84 -2
  2. package/android/build.gradle +51 -0
  3. package/android/maven/id/co/onepass/onepass-sdk/0.2.3/onepass-sdk-0.2.3.aar +0 -0
  4. package/android/maven/id/co/onepass/onepass-sdk/0.2.3/onepass-sdk-0.2.3.pom +52 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/id/co/onepass/expo/OnePassSignatureExpoView.kt +25 -0
  7. package/android/src/main/java/id/co/onepass/expo/OnepassExpoModule.kt +134 -0
  8. package/app.plugin.js +1 -0
  9. package/build/OnePassSignatureView.d.ts +19 -0
  10. package/build/OnePassSignatureView.d.ts.map +1 -0
  11. package/build/OnePassSignatureView.js +15 -0
  12. package/build/OnePassSignatureView.js.map +1 -0
  13. package/build/index.d.ts +21 -0
  14. package/build/index.d.ts.map +1 -0
  15. package/build/index.js +10 -0
  16. package/build/index.js.map +1 -0
  17. package/expo-module.config.json +9 -0
  18. package/ios/OnePassSDK/DesignSystem.swift +139 -0
  19. package/ios/OnePassSDK/IconView.swift +54 -0
  20. package/ios/OnePassSDK/IconsaxPath.swift +216 -0
  21. package/ios/OnePassSDK/Liveness/FaceAnalyzer.swift +114 -0
  22. package/ios/OnePassSDK/Liveness/FaceLandmarkMetrics.swift +28 -0
  23. package/ios/OnePassSDK/Liveness/FaceSnapshotMapper.swift +37 -0
  24. package/ios/OnePassSDK/Liveness/LivenessGate.swift +155 -0
  25. package/ios/OnePassSDK/Liveness/LivenessImagePreparer.swift +44 -0
  26. package/ios/OnePassSDK/Liveness/LivenessViewController.swift +724 -0
  27. package/ios/OnePassSDK/Ocr/CoverFitCrop.swift +37 -0
  28. package/ios/OnePassSDK/Ocr/KtpFrameOverlayView.swift +98 -0
  29. package/ios/OnePassSDK/Ocr/KtpImagePreparer.swift +90 -0
  30. package/ios/OnePassSDK/Ocr/KtpOcrViewController.swift +703 -0
  31. package/ios/OnePassSDK/Ocr/KtpPhotoInspector.swift +93 -0
  32. package/ios/OnePassSDK/Ocr/KtpQualityGate.swift +185 -0
  33. package/ios/OnePassSDK/OnePass.swift +28 -0
  34. package/ios/OnePassSDK/Resources/hindari_pemakaian.png +0 -0
  35. package/ios/OnePassSDK/Resources/inter_bold.ttf +0 -0
  36. package/ios/OnePassSDK/Resources/inter_medium.ttf +0 -0
  37. package/ios/OnePassSDK/Resources/inter_regular.ttf +0 -0
  38. package/ios/OnePassSDK/Resources/inter_semibold.ttf +0 -0
  39. package/ios/OnePassSDK/Resources/ktp_illustration.webp +0 -0
  40. package/ios/OnePassSDK/Resources/logo_smmf.png +0 -0
  41. package/ios/OnePassSDK/Resources/placeholder_head.png +0 -0
  42. package/ios/OnePassSDK/Signature/SignaturePadView.swift +144 -0
  43. package/ios/OnePassSignatureExpoView.swift +22 -0
  44. package/ios/OnepassExpo.podspec +23 -0
  45. package/ios/OnepassExpoModule.swift +77 -0
  46. package/licenses/Iconsax-MIT.txt +26 -0
  47. package/licenses/Inter-OFL-1.1.txt +93 -0
  48. package/package.json +49 -4
  49. package/plugin/src/inject.js +31 -0
  50. package/plugin/withOnePass.js +32 -0
  51. package/scripts/sync-native.js +84 -0
  52. package/src/OnePassSignatureView.tsx +34 -0
  53. package/src/index.ts +44 -0
@@ -0,0 +1,93 @@
1
+ import UIKit
2
+ import Vision
3
+
4
+ final class KtpPhotoInspector {
5
+
6
+ private static let metricSampleWidth = 640
7
+
8
+ struct Inspection {
9
+ let quality: KtpQualityResult
10
+ let textBounds: CGRect?
11
+ }
12
+
13
+ func inspect(image: UIImage, completion: @escaping (Inspection) -> Void) {
14
+ guard let cgImage = image.cgImage else {
15
+ completion(Inspection(quality: passthrough(), textBounds: nil))
16
+ return
17
+ }
18
+ let imageSize = CGSize(width: cgImage.width, height: cgImage.height)
19
+ let request = VNRecognizeTextRequest { request, _ in
20
+ let observations = request.results as? [VNRecognizedTextObservation] ?? []
21
+ let boxes = observations.map { observation in
22
+ CGRect(
23
+ x: observation.boundingBox.minX * imageSize.width,
24
+ y: (1 - observation.boundingBox.maxY) * imageSize.height,
25
+ width: observation.boundingBox.width * imageSize.width,
26
+ height: observation.boundingBox.height * imageSize.height
27
+ )
28
+ }
29
+ let text = observations
30
+ .compactMap { $0.topCandidates(1).first?.string }
31
+ .joined(separator: "\n")
32
+ let bounds = KtpAutoCrop.union(boxes)
33
+ let luma = Self.grayscaleSamples(cgImage)
34
+ let quality = KtpQualityGate.evaluate(
35
+ recognizedText: text,
36
+ textCoverage: bounds.map { KtpAutoCrop.coverage($0, imageSize: imageSize) } ?? 0,
37
+ textTouchesEdge: bounds.map { KtpAutoCrop.touchesEdge($0, imageSize: imageSize) } ?? false,
38
+ sharpness: ImageQualityMetrics.sharpness(
39
+ luma: luma.values,
40
+ width: luma.width,
41
+ height: luma.height
42
+ ),
43
+ glareRatio: ImageQualityMetrics.glareRatio(luma: luma.values)
44
+ )
45
+ completion(Inspection(quality: quality, textBounds: bounds))
46
+ }
47
+ request.recognitionLevel = .accurate
48
+ request.usesLanguageCorrection = false
49
+
50
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
51
+ let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .up)
52
+ do {
53
+ try handler.perform([request])
54
+ } catch {
55
+ completion(Inspection(quality: self?.passthrough() ?? KtpQualityResult(passed: true, issue: nil, message: nil), textBounds: nil))
56
+ }
57
+ }
58
+ }
59
+
60
+ private func passthrough() -> KtpQualityResult {
61
+ KtpQualityResult(passed: true, issue: nil, message: nil)
62
+ }
63
+
64
+ private struct Grayscale {
65
+ let values: [Int]
66
+ let width: Int
67
+ let height: Int
68
+ }
69
+
70
+ private static func grayscaleSamples(_ cgImage: CGImage) -> Grayscale {
71
+ let scale = CGFloat(metricSampleWidth) / CGFloat(cgImage.width)
72
+ let width = scale < 1 ? metricSampleWidth : cgImage.width
73
+ let height = scale < 1 ? Int(CGFloat(cgImage.height) * scale) : cgImage.height
74
+ guard width > 0, height > 0 else { return Grayscale(values: [], width: 0, height: 0) }
75
+
76
+ var pixels = [UInt8](repeating: 0, count: width * height)
77
+ guard
78
+ let context = CGContext(
79
+ data: &pixels,
80
+ width: width,
81
+ height: height,
82
+ bitsPerComponent: 8,
83
+ bytesPerRow: width,
84
+ space: CGColorSpaceCreateDeviceGray(),
85
+ bitmapInfo: CGImageAlphaInfo.none.rawValue
86
+ )
87
+ else {
88
+ return Grayscale(values: [], width: 0, height: 0)
89
+ }
90
+ context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
91
+ return Grayscale(values: pixels.map { Int($0) }, width: width, height: height)
92
+ }
93
+ }
@@ -0,0 +1,185 @@
1
+ import CoreGraphics
2
+ import Foundation
3
+
4
+ public enum KtpQualityIssue {
5
+ case textNotReadable
6
+ case cropped
7
+ case tooSmall
8
+ case glare
9
+ case blurry
10
+ }
11
+
12
+ public struct KtpQualityResult: Equatable {
13
+ public let passed: Bool
14
+ public let issue: KtpQualityIssue?
15
+ public let message: String?
16
+ }
17
+
18
+ public enum KtpQualityGate {
19
+
20
+ public static let minLabelMatches = 3
21
+ public static let minTextCoverage: Float = 0.12
22
+ public static let minSharpness: Double = 60
23
+ public static let maxGlareRatio: Float = 0.12
24
+
25
+ private static let requiredLabels = [
26
+ "PROVINSI",
27
+ "NIK",
28
+ "NAMA",
29
+ "LAHIR",
30
+ "KELAMIN",
31
+ "ALAMAT",
32
+ "AGAMA",
33
+ "PEKERJAAN",
34
+ "KEWARGANEGARAAN",
35
+ "BERLAKU",
36
+ ]
37
+
38
+ public static func evaluate(
39
+ recognizedText: String,
40
+ textCoverage: Float,
41
+ textTouchesEdge: Bool,
42
+ sharpness: Double,
43
+ glareRatio: Float
44
+ ) -> KtpQualityResult {
45
+ guard isReadable(recognizedText) else {
46
+ return failure(
47
+ .textNotReadable,
48
+ "Teks KTP belum terbaca jelas. Pastikan KTP tidak tertutup dan cahaya cukup."
49
+ )
50
+ }
51
+ if textTouchesEdge {
52
+ return failure(.cropped, "KTP terpotong. Masukkan seluruh KTP ke dalam kotak.")
53
+ }
54
+ if textCoverage < minTextCoverage {
55
+ return failure(.tooSmall, "KTP terlalu jauh. Dekatkan KTP hingga memenuhi kotak.")
56
+ }
57
+ if glareRatio > maxGlareRatio {
58
+ return failure(
59
+ .glare,
60
+ "Ada cahaya menyilaukan di KTP. Miringkan sedikit lalu ambil ulang."
61
+ )
62
+ }
63
+ if sharpness < minSharpness {
64
+ return failure(.blurry, "Foto buram. Tahan HP lebih stabil lalu ambil ulang.")
65
+ }
66
+ return KtpQualityResult(passed: true, issue: nil, message: nil)
67
+ }
68
+
69
+ private static func isReadable(_ recognizedText: String) -> Bool {
70
+ let upper = recognizedText.uppercased()
71
+ if containsNik(upper) { return true }
72
+ return requiredLabels.filter { upper.contains($0) }.count >= minLabelMatches
73
+ }
74
+
75
+ private static func containsNik(_ text: String) -> Bool {
76
+ var digitRun = 0
77
+ for character in text {
78
+ if character.isNumber {
79
+ digitRun += 1
80
+ if digitRun == 16 { return true }
81
+ } else {
82
+ digitRun = 0
83
+ }
84
+ }
85
+ return false
86
+ }
87
+
88
+ private static func failure(_ issue: KtpQualityIssue, _ message: String) -> KtpQualityResult {
89
+ KtpQualityResult(passed: false, issue: issue, message: message)
90
+ }
91
+ }
92
+
93
+ public enum KtpAutoCrop {
94
+
95
+ public static let paddingRatio: CGFloat = 0.06
96
+ public static let edgeMarginRatio: CGFloat = 0.02
97
+
98
+ public static func union(_ boxes: [CGRect]) -> CGRect? {
99
+ guard let first = boxes.first else { return nil }
100
+ return boxes.dropFirst().reduce(first) { $0.union($1) }
101
+ }
102
+
103
+ public static func coverage(_ bounds: CGRect, imageSize: CGSize) -> Float {
104
+ let area = imageSize.width * imageSize.height
105
+ guard area > 0 else { return 0 }
106
+ return Float((bounds.width * bounds.height) / area)
107
+ }
108
+
109
+ public static func touchesEdge(_ bounds: CGRect, imageSize: CGSize) -> Bool {
110
+ let marginX = imageSize.width * edgeMarginRatio
111
+ let marginY = imageSize.height * edgeMarginRatio
112
+ return bounds.minX <= marginX
113
+ || bounds.minY <= marginY
114
+ || bounds.maxX >= imageSize.width - marginX
115
+ || bounds.maxY >= imageSize.height - marginY
116
+ }
117
+
118
+ public static func cropTo(
119
+ bounds: CGRect,
120
+ imageSize: CGSize,
121
+ targetRatio: CGFloat
122
+ ) -> CropRegion {
123
+ let padded = bounds.insetBy(
124
+ dx: -bounds.width * paddingRatio,
125
+ dy: -bounds.height * paddingRatio
126
+ )
127
+ var width = padded.width
128
+ var height = padded.height
129
+ if width / height > targetRatio {
130
+ height = width / targetRatio
131
+ } else {
132
+ width = height * targetRatio
133
+ }
134
+ let cappedWidth = min(width, imageSize.width)
135
+ let cappedHeight = min(cappedWidth / targetRatio, imageSize.height)
136
+ let finalWidth = min(cappedWidth, cappedHeight * targetRatio)
137
+ let finalHeight = finalWidth / targetRatio
138
+
139
+ let left = max(0, min(padded.midX - finalWidth / 2, imageSize.width - finalWidth))
140
+ let top = max(0, min(padded.midY - finalHeight / 2, imageSize.height - finalHeight))
141
+
142
+ return CropRegion(
143
+ left: Int(left.rounded()),
144
+ top: Int(top.rounded()),
145
+ width: max(1, Int(finalWidth.rounded())),
146
+ height: max(1, Int(finalHeight.rounded()))
147
+ )
148
+ }
149
+ }
150
+
151
+ public enum ImageQualityMetrics {
152
+
153
+ private static let overexposedLuma = 250
154
+
155
+ public static func sharpness(luma: [Int], width: Int, height: Int) -> Double {
156
+ guard width >= 3, height >= 3, luma.count >= width * height else { return 0 }
157
+ var sum = 0.0
158
+ var sumSquares = 0.0
159
+ var count = 0
160
+ for y in 1..<(height - 1) {
161
+ for x in 1..<(width - 1) {
162
+ let index = y * width + x
163
+ let value = Double(
164
+ 4 * luma[index]
165
+ - luma[index - 1]
166
+ - luma[index + 1]
167
+ - luma[index - width]
168
+ - luma[index + width]
169
+ )
170
+ sum += value
171
+ sumSquares += value * value
172
+ count += 1
173
+ }
174
+ }
175
+ guard count > 0 else { return 0 }
176
+ let mean = sum / Double(count)
177
+ return sumSquares / Double(count) - mean * mean
178
+ }
179
+
180
+ public static func glareRatio(luma: [Int]) -> Float {
181
+ guard !luma.isEmpty else { return 0 }
182
+ let overexposed = luma.filter { $0 >= overexposedLuma }.count
183
+ return Float(overexposed) / Float(luma.count)
184
+ }
185
+ }
@@ -0,0 +1,28 @@
1
+ import UIKit
2
+
3
+ public enum OnePass {
4
+
5
+ public static func startLiveness(
6
+ from presenter: UIViewController,
7
+ input: LivenessInput = LivenessInput(),
8
+ completion: @escaping (LivenessResult) -> Void
9
+ ) {
10
+ let controller = LivenessViewController(input: input) { result in
11
+ presenter.dismiss(animated: true) { completion(result) }
12
+ }
13
+ controller.modalPresentationStyle = .fullScreen
14
+ presenter.present(controller, animated: true)
15
+ }
16
+
17
+ public static func startKtpOcr(
18
+ from presenter: UIViewController,
19
+ input: KtpOcrInput = KtpOcrInput(),
20
+ completion: @escaping (KtpOcrResult) -> Void
21
+ ) {
22
+ let controller = KtpOcrViewController(input: input) { result in
23
+ presenter.dismiss(animated: true) { completion(result) }
24
+ }
25
+ controller.modalPresentationStyle = .fullScreen
26
+ presenter.present(controller, animated: true)
27
+ }
28
+ }
@@ -0,0 +1,144 @@
1
+ import UIKit
2
+
3
+ public final class SignaturePadView: UIView {
4
+
5
+ public var onBegin: (() -> Void)?
6
+ public var onEnd: (() -> Void)?
7
+
8
+ public var penColor: UIColor = .black {
9
+ didSet { setNeedsDisplay() }
10
+ }
11
+
12
+ public var strokeWidth: CGFloat = 2 {
13
+ didSet { setNeedsDisplay() }
14
+ }
15
+
16
+ private var strokes: [UIBezierPath] = []
17
+ private var dots: [CGPoint] = []
18
+ private var current: UIBezierPath?
19
+ private var moved = false
20
+ private var lastPoint: CGPoint = .zero
21
+ private var lockedScrollViews: [UIScrollView] = []
22
+
23
+ public var isEmpty: Bool { strokes.isEmpty && dots.isEmpty }
24
+
25
+ public override init(frame: CGRect) {
26
+ super.init(frame: frame)
27
+ backgroundColor = .clear
28
+ isOpaque = false
29
+ isMultipleTouchEnabled = false
30
+ }
31
+
32
+ @available(*, unavailable)
33
+ required init?(coder: NSCoder) { fatalError("tidak dipakai") }
34
+
35
+ public func clear() {
36
+ strokes.removeAll()
37
+ dots.removeAll()
38
+ current = nil
39
+ setNeedsDisplay()
40
+ }
41
+
42
+ public func toBase64Png() -> String? {
43
+ guard !isEmpty, bounds.width > 0, bounds.height > 0 else { return nil }
44
+ let format = UIGraphicsImageRendererFormat.default()
45
+ format.opaque = false
46
+ let image = UIGraphicsImageRenderer(bounds: bounds, format: format).image { _ in
47
+ drawStrokes()
48
+ }
49
+ return image.pngData()?.base64EncodedString()
50
+ }
51
+
52
+ public override func draw(_ rect: CGRect) {
53
+ drawStrokes()
54
+ }
55
+
56
+ private func drawStrokes() {
57
+ penColor.setStroke()
58
+ penColor.setFill()
59
+ for path in strokes {
60
+ path.lineWidth = strokeWidth
61
+ path.lineCapStyle = .round
62
+ path.lineJoinStyle = .round
63
+ path.stroke()
64
+ }
65
+ for dot in dots {
66
+ UIBezierPath(
67
+ arcCenter: dot,
68
+ radius: strokeWidth / 2,
69
+ startAngle: 0,
70
+ endAngle: .pi * 2,
71
+ clockwise: true
72
+ ).fill()
73
+ }
74
+ }
75
+
76
+ func beginStroke(at point: CGPoint) {
77
+ let path = UIBezierPath()
78
+ path.move(to: point)
79
+ current = path
80
+ moved = false
81
+ lastPoint = point
82
+ lockScrollViews()
83
+ onBegin?()
84
+ setNeedsDisplay()
85
+ }
86
+
87
+ func continueStroke(to point: CGPoint) {
88
+ guard let path = current else { return }
89
+ if !moved {
90
+ strokes.append(path)
91
+ moved = true
92
+ }
93
+ let mid = CGPoint(x: (point.x + lastPoint.x) / 2, y: (point.y + lastPoint.y) / 2)
94
+ path.addQuadCurve(to: mid, controlPoint: lastPoint)
95
+ lastPoint = point
96
+ setNeedsDisplay()
97
+ }
98
+
99
+ func endStroke(at point: CGPoint) {
100
+ guard let path = current else { return }
101
+ if moved { path.addLine(to: point) } else { dots.append(point) }
102
+ current = nil
103
+ unlockScrollViews()
104
+ onEnd?()
105
+ setNeedsDisplay()
106
+ }
107
+
108
+ private func lockScrollViews() {
109
+ var view = superview
110
+ while let candidate = view {
111
+ if let scroll = candidate as? UIScrollView, scroll.isScrollEnabled {
112
+ scroll.isScrollEnabled = false
113
+ lockedScrollViews.append(scroll)
114
+ }
115
+ view = candidate.superview
116
+ }
117
+ }
118
+
119
+ private func unlockScrollViews() {
120
+ lockedScrollViews.forEach { $0.isScrollEnabled = true }
121
+ lockedScrollViews.removeAll()
122
+ }
123
+
124
+ public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
125
+ guard let touch = touches.first else { return }
126
+ beginStroke(at: touch.location(in: self))
127
+ }
128
+
129
+ public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
130
+ guard let touch = touches.first else { return }
131
+ let samples = event?.coalescedTouches(for: touch) ?? [touch]
132
+ samples.forEach { continueStroke(to: $0.location(in: self)) }
133
+ }
134
+
135
+ public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
136
+ guard let touch = touches.first else { return }
137
+ endStroke(at: touch.location(in: self))
138
+ }
139
+
140
+ public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
141
+ guard let touch = touches.first else { return }
142
+ endStroke(at: touch.location(in: self))
143
+ }
144
+ }
@@ -0,0 +1,22 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+
4
+ public final class OnePassSignatureExpoView: ExpoView {
5
+
6
+ let pad = SignaturePadView(frame: .zero)
7
+ let onBegin = EventDispatcher()
8
+ let onEnd = EventDispatcher()
9
+
10
+ public required init(appContext: AppContext? = nil) {
11
+ super.init(appContext: appContext)
12
+ clipsToBounds = true
13
+ addSubview(pad)
14
+ pad.onBegin = { [weak self] in self?.onBegin([:]) }
15
+ pad.onEnd = { [weak self] in self?.onEnd([:]) }
16
+ }
17
+
18
+ public override func layoutSubviews() {
19
+ super.layoutSubviews()
20
+ pad.frame = bounds
21
+ }
22
+ }
@@ -0,0 +1,23 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'OnepassExpo'
3
+ s.version = '0.1.0'
4
+ s.summary = 'Expo module untuk liveness wajah, foto KTP, dan kanvas tanda tangan'
5
+ s.description = 'Binding Expo untuk liveness wajah, foto KTP, dan kanvas tanda tangan.'
6
+ s.author = 'simasfin'
7
+ s.homepage = 'https://www.npmjs.com/package/@simasfin/smmf-sdk'
8
+ s.platforms = { :ios => '13.4' }
9
+ s.source = { :git => '' }
10
+ s.static_framework = true
11
+
12
+ s.dependency 'ExpoModulesCore'
13
+
14
+ s.pod_target_xcconfig = {
15
+ 'DEFINES_MODULE' => 'YES',
16
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
17
+ }
18
+
19
+ s.source_files = 'OnepassExpoModule.swift', 'OnePassSignatureExpoView.swift', 'OnePassSDK/**/*.swift'
20
+ s.resource_bundles = {
21
+ 'OnePassSDK' => ['OnePassSDK/Resources/*']
22
+ }
23
+ end
@@ -0,0 +1,77 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+
4
+ public class OnepassExpoModule: Module {
5
+
6
+ public func definition() -> ModuleDefinition {
7
+ Name("OnepassExpo")
8
+
9
+ AsyncFunction("startLiveness") { (options: [String: Any], promise: Promise) in
10
+ guard let presenter = self.appContext?.utilities?.currentViewController() else {
11
+ promise.reject("ONEPASS_NO_VIEW_CONTROLLER", "Tidak ada view controller aktif")
12
+ return
13
+ }
14
+ let input = LivenessInput(primaryColorHex: options["primaryColorHex"] as? String)
15
+ OnePass.startLiveness(from: presenter, input: input) { result in
16
+ promise.resolve([
17
+ "status": Self.statusName(result.status),
18
+ "imageBase64": result.imageBase64 as Any?,
19
+ "errorMessage": result.errorMessage as Any?,
20
+ ])
21
+ }
22
+ }
23
+ .runOnQueue(.main)
24
+
25
+ AsyncFunction("startKtpOcr") { (options: [String: Any], promise: Promise) in
26
+ guard let presenter = self.appContext?.utilities?.currentViewController() else {
27
+ promise.reject("ONEPASS_NO_VIEW_CONTROLLER", "Tidak ada view controller aktif")
28
+ return
29
+ }
30
+ let input = KtpOcrInput(primaryColorHex: options["primaryColorHex"] as? String)
31
+ OnePass.startKtpOcr(from: presenter, input: input) { result in
32
+ promise.resolve([
33
+ "status": Self.statusName(result.status),
34
+ "imageBase64": result.imageBase64 as Any?,
35
+ "errorMessage": result.errorMessage as Any?,
36
+ ])
37
+ }
38
+ }
39
+ .runOnQueue(.main)
40
+
41
+ View(OnePassSignatureExpoView.self) {
42
+ Events("onBegin", "onEnd")
43
+
44
+ Prop("penColor") { (view: OnePassSignatureExpoView, color: String?) in
45
+ view.pad.penColor = color.map { UIColor(hex: $0) } ?? .black
46
+ }
47
+
48
+ Prop("strokeWidth") { (view: OnePassSignatureExpoView, width: Double?) in
49
+ view.pad.strokeWidth = CGFloat(width ?? 2)
50
+ }
51
+
52
+ AsyncFunction("clear") { (view: OnePassSignatureExpoView) in
53
+ view.pad.clear()
54
+ }
55
+
56
+ AsyncFunction("readSignature") { (view: OnePassSignatureExpoView) -> String? in
57
+ view.pad.toBase64Png()
58
+ }
59
+ }
60
+ }
61
+
62
+ private static func statusName(_ status: LivenessStatus) -> String {
63
+ switch status {
64
+ case .success: return "SUCCESS"
65
+ case .failed: return "FAILED"
66
+ case .cancelled: return "CANCELLED"
67
+ }
68
+ }
69
+
70
+ private static func statusName(_ status: KtpOcrStatusCode) -> String {
71
+ switch status {
72
+ case .success: return "SUCCESS"
73
+ case .failed: return "FAILED"
74
+ case .cancelled: return "CANCELLED"
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,26 @@
1
+ Iconsax icon set
2
+ Copyright (c) Vuesax (https://iconsax.io)
3
+
4
+ Didistribusikan melalui paket npm "iconsax-react-native" yang berlisensi MIT.
5
+ Ikon Camera, Flash, FlashSlash, CloseSquare, Danger, dan User pada SDK ini
6
+ dihasilkan dari data path SVG paket tersebut.
7
+
8
+ MIT License
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.