@rick427/react-native-liveness 0.1.7 → 0.1.9
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.
- package/lib/module/LivenessCamera.js +26 -16
- package/lib/module/LivenessCamera.js.map +1 -1
- package/lib/module/LivenessCameraModal.js +117 -0
- package/lib/module/LivenessCameraModal.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts.map +1 -1
- package/lib/typescript/src/LivenessCameraModal.d.ts +15 -0
- package/lib/typescript/src/LivenessCameraModal.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +35 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LivenessCamera.tsx +28 -14
- package/src/LivenessCameraModal.tsx +116 -0
- package/src/index.ts +2 -0
- package/src/types.ts +42 -3
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
5
|
-
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
5
|
+
import { Camera, useCameraDevice, useCameraFormat, useCameraPermission } from 'react-native-vision-camera';
|
|
6
6
|
import { Circle, Path, Svg } from 'react-native-svg';
|
|
7
7
|
import { useLivenessCamera } from "./useLivenessCamera.js";
|
|
8
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
+
const DEFAULT_FONT = 'Baloo-Medium';
|
|
10
|
+
|
|
9
11
|
// Circle diameter = 82 % of container width — large enough to fit any face
|
|
10
12
|
// comfortably without the user needing to fiddle with distance.
|
|
11
13
|
const CIRCLE_DIAMETER_RATIO = 0.82;
|
|
@@ -51,13 +53,9 @@ function CircleOverlay({
|
|
|
51
53
|
}) {
|
|
52
54
|
if (width === 0 || height === 0) return null;
|
|
53
55
|
const cx = width / 2;
|
|
54
|
-
// Centre the circle slightly above midpoint so the face sits naturally
|
|
55
56
|
const cy = height * 0.42;
|
|
56
57
|
const r = width * CIRCLE_DIAMETER_RATIO / 2;
|
|
57
58
|
const color = getCircleColor(state, score);
|
|
58
|
-
|
|
59
|
-
// Compound path: full-screen rect + circle cutout.
|
|
60
|
-
// evenodd fill rule makes the circle area transparent.
|
|
61
59
|
const scrimD = `M0 0H${width}V${height}H0Z ${circlePath(cx, cy, r)}`;
|
|
62
60
|
return /*#__PURE__*/_jsxs(Svg, {
|
|
63
61
|
style: StyleSheet.absoluteFill,
|
|
@@ -78,10 +76,9 @@ function CircleOverlay({
|
|
|
78
76
|
});
|
|
79
77
|
}
|
|
80
78
|
function CountdownBubble({
|
|
81
|
-
value
|
|
79
|
+
value,
|
|
80
|
+
fontFamily
|
|
82
81
|
}) {
|
|
83
|
-
// key={countdown} in the parent remounts this component on every tick,
|
|
84
|
-
// so [] deps are correct — each mount runs a fresh animation.
|
|
85
82
|
const scale = useRef(new Animated.Value(0)).current;
|
|
86
83
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
87
84
|
useEffect(() => {
|
|
@@ -117,7 +114,9 @@ function CountdownBubble({
|
|
|
117
114
|
}]
|
|
118
115
|
}],
|
|
119
116
|
children: /*#__PURE__*/_jsx(Text, {
|
|
120
|
-
style: styles.countdownText,
|
|
117
|
+
style: [styles.countdownText, {
|
|
118
|
+
fontFamily
|
|
119
|
+
}],
|
|
121
120
|
children: value
|
|
122
121
|
})
|
|
123
122
|
});
|
|
@@ -130,6 +129,7 @@ export function LivenessCamera({
|
|
|
130
129
|
livenessThreshold = 0.75,
|
|
131
130
|
confirmFrames = 10,
|
|
132
131
|
soundEnabled = true,
|
|
132
|
+
fontFamily = DEFAULT_FONT,
|
|
133
133
|
style
|
|
134
134
|
}) {
|
|
135
135
|
const {
|
|
@@ -137,6 +137,10 @@ export function LivenessCamera({
|
|
|
137
137
|
requestPermission
|
|
138
138
|
} = useCameraPermission();
|
|
139
139
|
const device = useCameraDevice('front');
|
|
140
|
+
const format = useCameraFormat(device, [{
|
|
141
|
+
fps: 60
|
|
142
|
+
}]);
|
|
143
|
+
const fps = Math.min(format?.maxFps ?? 30, 60);
|
|
140
144
|
const cameraRef = useRef(null);
|
|
141
145
|
const [containerSize, setContainerSize] = useState({
|
|
142
146
|
width: 0,
|
|
@@ -179,7 +183,9 @@ export function LivenessCamera({
|
|
|
179
183
|
return /*#__PURE__*/_jsx(View, {
|
|
180
184
|
style: [styles.root, style, styles.centered],
|
|
181
185
|
children: /*#__PURE__*/_jsx(Text, {
|
|
182
|
-
style: styles.permissionText,
|
|
186
|
+
style: [styles.permissionText, {
|
|
187
|
+
fontFamily
|
|
188
|
+
}],
|
|
183
189
|
children: "Camera permission required"
|
|
184
190
|
})
|
|
185
191
|
});
|
|
@@ -188,7 +194,9 @@ export function LivenessCamera({
|
|
|
188
194
|
return /*#__PURE__*/_jsx(View, {
|
|
189
195
|
style: [styles.root, style, styles.centered],
|
|
190
196
|
children: /*#__PURE__*/_jsx(Text, {
|
|
191
|
-
style: styles.permissionText,
|
|
197
|
+
style: [styles.permissionText, {
|
|
198
|
+
fontFamily
|
|
199
|
+
}],
|
|
192
200
|
children: "No front camera found"
|
|
193
201
|
})
|
|
194
202
|
});
|
|
@@ -204,7 +212,8 @@ export function LivenessCamera({
|
|
|
204
212
|
frameProcessor: frameProcessor,
|
|
205
213
|
photo: true,
|
|
206
214
|
pixelFormat: "yuv",
|
|
207
|
-
|
|
215
|
+
format: format,
|
|
216
|
+
fps: fps
|
|
208
217
|
}), /*#__PURE__*/_jsx(CircleOverlay, {
|
|
209
218
|
width: containerSize.width,
|
|
210
219
|
height: containerSize.height,
|
|
@@ -213,13 +222,16 @@ export function LivenessCamera({
|
|
|
213
222
|
}), livenessState !== 'done' && /*#__PURE__*/_jsx(View, {
|
|
214
223
|
style: styles.feedbackContainer,
|
|
215
224
|
children: /*#__PURE__*/_jsx(Text, {
|
|
216
|
-
style: styles.feedbackText,
|
|
225
|
+
style: [styles.feedbackText, {
|
|
226
|
+
fontFamily
|
|
227
|
+
}],
|
|
217
228
|
children: feedback
|
|
218
229
|
})
|
|
219
230
|
}), livenessState === 'countdown' && countdown !== null && /*#__PURE__*/_jsx(View, {
|
|
220
231
|
style: styles.countdownContainer,
|
|
221
232
|
children: /*#__PURE__*/_jsx(CountdownBubble, {
|
|
222
|
-
value: countdown
|
|
233
|
+
value: countdown,
|
|
234
|
+
fontFamily: fontFamily
|
|
223
235
|
}, countdown)
|
|
224
236
|
}), livenessState === 'capturing' && /*#__PURE__*/_jsx(View, {
|
|
225
237
|
style: styles.captureFlash,
|
|
@@ -254,7 +266,6 @@ const styles = StyleSheet.create({
|
|
|
254
266
|
feedbackText: {
|
|
255
267
|
color: '#fff',
|
|
256
268
|
fontSize: 16,
|
|
257
|
-
fontWeight: '600',
|
|
258
269
|
textAlign: 'center',
|
|
259
270
|
textShadowColor: 'rgba(0,0,0,0.8)',
|
|
260
271
|
textShadowOffset: {
|
|
@@ -281,7 +292,6 @@ const styles = StyleSheet.create({
|
|
|
281
292
|
countdownText: {
|
|
282
293
|
color: '#fff',
|
|
283
294
|
fontSize: 52,
|
|
284
|
-
fontWeight: '700',
|
|
285
295
|
lineHeight: 60
|
|
286
296
|
},
|
|
287
297
|
captureFlash: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraPermission","Circle","Path","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","CIRCLE_DIAMETER_RATIO","STROKE_WIDTH","K","getCircleColor","state","score","circlePath","cx","cy","r","join","CircleOverlay","width","height","color","scrimD","style","absoluteFill","children","d","fill","fillRule","stroke","strokeWidth","CountdownBubble","value","scale","Value","current","opacity","parallel","sequence","spring","toValue","stiffness","damping","useNativeDriver","timing","duration","start","styles","countdownBubble","transform","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","hasPermission","requestPermission","device","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","livenessScore","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","isActive","photo","pixelFormat","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraFormat","useCameraPermission","Circle","Path","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","DEFAULT_FONT","CIRCLE_DIAMETER_RATIO","STROKE_WIDTH","K","getCircleColor","state","score","circlePath","cx","cy","r","join","CircleOverlay","width","height","color","scrimD","style","absoluteFill","children","d","fill","fillRule","stroke","strokeWidth","CountdownBubble","value","fontFamily","scale","Value","current","opacity","parallel","sequence","spring","toValue","stiffness","damping","useNativeDriver","timing","duration","start","styles","countdownBubble","transform","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","hasPermission","requestPermission","device","format","fps","Math","min","maxFps","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","livenessScore","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","isActive","photo","pixelFormat","feedbackContainer","feedbackText","countdownContainer","captureFlash","pointerEvents","create","flex","backgroundColor","overflow","justifyContent","alignItems","fontSize","textAlign","paddingHorizontal","position","bottom","left","right","textShadowColor","textShadowOffset","textShadowRadius","absoluteFillObject","borderRadius","borderWidth","borderColor","lineHeight"],"sourceRoot":"../../src","sources":["LivenessCamera.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import { Path, Svg } from 'react-native-svg';
|
|
5
|
+
import { LivenessCamera } from "./LivenessCamera.js";
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
/** × icon drawn with SVG — no external icon library required. */
|
|
8
|
+
function CloseIcon({
|
|
9
|
+
color = '#fff',
|
|
10
|
+
size = 18
|
|
11
|
+
}) {
|
|
12
|
+
const pad = size * 0.1;
|
|
13
|
+
const end = size - pad;
|
|
14
|
+
return /*#__PURE__*/_jsx(Svg, {
|
|
15
|
+
width: size,
|
|
16
|
+
height: size,
|
|
17
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
18
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
19
|
+
d: `M${pad} ${pad} L${end} ${end} M${end} ${pad} L${pad} ${end}`,
|
|
20
|
+
stroke: color,
|
|
21
|
+
strokeWidth: size * 0.14,
|
|
22
|
+
strokeLinecap: "round"
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Drop-in liveness detection modal.
|
|
29
|
+
*
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <LivenessCameraModal
|
|
32
|
+
* visible={showLiveness}
|
|
33
|
+
* onClose={() => setShowLiveness(false)}
|
|
34
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
35
|
+
* animationType="slide"
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function LivenessCameraModal({
|
|
40
|
+
visible,
|
|
41
|
+
onClose,
|
|
42
|
+
animationType = 'slide',
|
|
43
|
+
closeButtonStyle,
|
|
44
|
+
closeButtonIconColor = '#fff',
|
|
45
|
+
closeButtonIconSize = 18,
|
|
46
|
+
// forward all LivenessCamera props
|
|
47
|
+
onCapture,
|
|
48
|
+
onLivenessConfirmed,
|
|
49
|
+
onError,
|
|
50
|
+
countdownFrom,
|
|
51
|
+
livenessThreshold,
|
|
52
|
+
confirmFrames,
|
|
53
|
+
soundEnabled,
|
|
54
|
+
fontFamily
|
|
55
|
+
}) {
|
|
56
|
+
const handleCapture = result => {
|
|
57
|
+
onCapture(result);
|
|
58
|
+
// Modal stays open — consumer decides when to close via onCapture callback
|
|
59
|
+
};
|
|
60
|
+
return /*#__PURE__*/_jsx(Modal, {
|
|
61
|
+
visible: visible,
|
|
62
|
+
animationType: animationType,
|
|
63
|
+
statusBarTranslucent: true,
|
|
64
|
+
onRequestClose: onClose,
|
|
65
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
66
|
+
style: styles.container,
|
|
67
|
+
children: [/*#__PURE__*/_jsx(LivenessCamera, {
|
|
68
|
+
style: styles.camera,
|
|
69
|
+
onCapture: handleCapture,
|
|
70
|
+
onLivenessConfirmed: onLivenessConfirmed,
|
|
71
|
+
onError: onError,
|
|
72
|
+
countdownFrom: countdownFrom,
|
|
73
|
+
livenessThreshold: livenessThreshold,
|
|
74
|
+
confirmFrames: confirmFrames,
|
|
75
|
+
soundEnabled: soundEnabled,
|
|
76
|
+
fontFamily: fontFamily
|
|
77
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
78
|
+
style: [styles.closeButton, closeButtonStyle],
|
|
79
|
+
onPress: onClose,
|
|
80
|
+
activeOpacity: 0.7,
|
|
81
|
+
hitSlop: {
|
|
82
|
+
top: 8,
|
|
83
|
+
bottom: 8,
|
|
84
|
+
left: 8,
|
|
85
|
+
right: 8
|
|
86
|
+
},
|
|
87
|
+
children: /*#__PURE__*/_jsx(CloseIcon, {
|
|
88
|
+
color: closeButtonIconColor,
|
|
89
|
+
size: closeButtonIconSize
|
|
90
|
+
})
|
|
91
|
+
})]
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
backgroundColor: '#000'
|
|
99
|
+
},
|
|
100
|
+
camera: {
|
|
101
|
+
flex: 1
|
|
102
|
+
},
|
|
103
|
+
closeButton: {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: 52,
|
|
106
|
+
left: 16,
|
|
107
|
+
width: 48,
|
|
108
|
+
height: 48,
|
|
109
|
+
borderRadius: 24,
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: 'rgba(255,255,255,0.3)'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=LivenessCameraModal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Modal","StyleSheet","TouchableOpacity","View","Path","Svg","LivenessCamera","jsx","_jsx","jsxs","_jsxs","CloseIcon","color","size","pad","end","width","height","viewBox","children","d","stroke","strokeWidth","strokeLinecap","LivenessCameraModal","visible","onClose","animationType","closeButtonStyle","closeButtonIconColor","closeButtonIconSize","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","fontFamily","handleCapture","result","statusBarTranslucent","onRequestClose","style","styles","container","camera","closeButton","onPress","activeOpacity","hitSlop","top","bottom","left","right","create","flex","backgroundColor","position","borderRadius","justifyContent","alignItems","borderWidth","borderColor"],"sourceRoot":"../../src","sources":["LivenessCameraModal.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["LivenessCamera","useLivenessCamera"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,cAAc,QAAQ,qBAAkB;AACjD,SAASC,iBAAiB,QAAQ,wBAAqB","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["LivenessCamera","LivenessCameraModal","useLivenessCamera"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,cAAc,QAAQ,qBAAkB;AACjD,SAASC,mBAAmB,QAAQ,0BAAuB;AAC3D,SAASC,iBAAiB,QAAQ,wBAAqB","ignoreList":[]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { LivenessCameraProps } from './types';
|
|
2
|
-
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, fontFamily, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=LivenessCamera.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAuIlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,UAAyB,EACzB,KAAK,GACN,EAAE,mBAAmB,2CA8FrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LivenessCameraModalProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Drop-in liveness detection modal.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <LivenessCameraModal
|
|
7
|
+
* visible={showLiveness}
|
|
8
|
+
* onClose={() => setShowLiveness(false)}
|
|
9
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
10
|
+
* animationType="slide"
|
|
11
|
+
* />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function LivenessCameraModal({ visible, onClose, animationType, closeButtonStyle, closeButtonIconColor, closeButtonIconSize, onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, fontFamily, }: LivenessCameraModalProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=LivenessCameraModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LivenessCameraModal.d.ts","sourceRoot":"","sources":["../../../src/LivenessCameraModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAwBxD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,aAAuB,EACvB,gBAAgB,EAChB,oBAA6B,EAC7B,mBAAwB,EAExB,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,UAAU,GACX,EAAE,wBAAwB,2CAqC1B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { LivenessCamera } from './LivenessCamera';
|
|
2
|
+
export { LivenessCameraModal } from './LivenessCameraModal';
|
|
2
3
|
export { useLivenessCamera } from './useLivenessCamera';
|
|
3
|
-
export type { CaptureResult, FaceData, FeedbackMessage, LivenessCameraProps, LivenessState, } from './types';
|
|
4
|
+
export type { CaptureResult, FaceData, FeedbackMessage, LivenessCameraModalProps, LivenessCameraProps, LivenessState, } from './types';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ViewStyle } from 'react-native';
|
|
1
|
+
import type { ModalProps, ViewStyle } from 'react-native';
|
|
2
2
|
import type { PhotoFile } from 'react-native-vision-camera';
|
|
3
3
|
export type FaceData = {
|
|
4
4
|
detected: boolean;
|
|
@@ -53,9 +53,41 @@ export type LivenessCameraProps = {
|
|
|
53
53
|
*/
|
|
54
54
|
style?: ViewStyle;
|
|
55
55
|
/**
|
|
56
|
-
* Whether to play a shutter sound on capture.
|
|
57
|
-
* to be installed. Defaults to true.
|
|
56
|
+
* Whether to play a shutter sound on capture. Defaults to true.
|
|
58
57
|
*/
|
|
59
58
|
soundEnabled?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Font family applied to all text inside the component.
|
|
61
|
+
* Defaults to 'Baloo-Medium'. Set to undefined to use the system font.
|
|
62
|
+
*/
|
|
63
|
+
fontFamily?: string;
|
|
64
|
+
};
|
|
65
|
+
export type LivenessCameraModalProps = Omit<LivenessCameraProps, 'style'> & {
|
|
66
|
+
/**
|
|
67
|
+
* Controls modal visibility — pass your own boolean state.
|
|
68
|
+
*/
|
|
69
|
+
visible: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Called when the close button is pressed or the Android back button fires.
|
|
72
|
+
* Use this to set your visible state to false.
|
|
73
|
+
*/
|
|
74
|
+
onClose: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Modal entrance/exit animation. Defaults to 'slide'.
|
|
77
|
+
*/
|
|
78
|
+
animationType?: ModalProps['animationType'];
|
|
79
|
+
/**
|
|
80
|
+
* Override styles on the close button container.
|
|
81
|
+
* Useful for adjusting position or size to match your design system.
|
|
82
|
+
*/
|
|
83
|
+
closeButtonStyle?: ViewStyle;
|
|
84
|
+
/**
|
|
85
|
+
* Colour of the × icon inside the close button. Defaults to '#fff'.
|
|
86
|
+
*/
|
|
87
|
+
closeButtonIconColor?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Size of the × icon in dp. Defaults to 18.
|
|
90
|
+
*/
|
|
91
|
+
closeButtonIconSize?: number;
|
|
60
92
|
};
|
|
61
93
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,eAAe,GACvB,kCAAkC,GAClC,aAAa,GACb,mBAAmB,GACnB,qBAAqB,GACrB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,EAAE,CAAC;AAEP,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,GAAG;IAC1E;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAE5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAE7B;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rick427/react-native-liveness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Liveness detection library for React Native using Vision Camera v4 and ML Kit",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/LivenessCamera.tsx
CHANGED
|
@@ -3,12 +3,15 @@ import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
|
3
3
|
import {
|
|
4
4
|
Camera,
|
|
5
5
|
useCameraDevice,
|
|
6
|
+
useCameraFormat,
|
|
6
7
|
useCameraPermission,
|
|
7
8
|
} from 'react-native-vision-camera';
|
|
8
9
|
import { Circle, Path, Svg } from 'react-native-svg';
|
|
9
10
|
import { useLivenessCamera } from './useLivenessCamera';
|
|
10
11
|
import type { LivenessCameraProps, LivenessState } from './types';
|
|
11
12
|
|
|
13
|
+
const DEFAULT_FONT = 'Baloo-Medium';
|
|
14
|
+
|
|
12
15
|
// Circle diameter = 82 % of container width — large enough to fit any face
|
|
13
16
|
// comfortably without the user needing to fiddle with distance.
|
|
14
17
|
const CIRCLE_DIAMETER_RATIO = 0.82;
|
|
@@ -68,13 +71,10 @@ function CircleOverlay({
|
|
|
68
71
|
if (width === 0 || height === 0) return null;
|
|
69
72
|
|
|
70
73
|
const cx = width / 2;
|
|
71
|
-
// Centre the circle slightly above midpoint so the face sits naturally
|
|
72
74
|
const cy = height * 0.42;
|
|
73
75
|
const r = (width * CIRCLE_DIAMETER_RATIO) / 2;
|
|
74
76
|
const color = getCircleColor(state, score);
|
|
75
77
|
|
|
76
|
-
// Compound path: full-screen rect + circle cutout.
|
|
77
|
-
// evenodd fill rule makes the circle area transparent.
|
|
78
78
|
const scrimD = `M0 0H${width}V${height}H0Z ${circlePath(cx, cy, r)}`;
|
|
79
79
|
|
|
80
80
|
return (
|
|
@@ -92,9 +92,13 @@ function CircleOverlay({
|
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function CountdownBubble({
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
function CountdownBubble({
|
|
96
|
+
value,
|
|
97
|
+
fontFamily,
|
|
98
|
+
}: {
|
|
99
|
+
value: number;
|
|
100
|
+
fontFamily: string;
|
|
101
|
+
}) {
|
|
98
102
|
const scale = useRef(new Animated.Value(0)).current;
|
|
99
103
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
100
104
|
|
|
@@ -134,7 +138,7 @@ function CountdownBubble({ value }: { value: number }) {
|
|
|
134
138
|
<Animated.View
|
|
135
139
|
style={[styles.countdownBubble, { opacity, transform: [{ scale }] }]}
|
|
136
140
|
>
|
|
137
|
-
<Text style={styles.countdownText}>{value}</Text>
|
|
141
|
+
<Text style={[styles.countdownText, { fontFamily }]}>{value}</Text>
|
|
138
142
|
</Animated.View>
|
|
139
143
|
);
|
|
140
144
|
}
|
|
@@ -147,10 +151,13 @@ export function LivenessCamera({
|
|
|
147
151
|
livenessThreshold = 0.75,
|
|
148
152
|
confirmFrames = 10,
|
|
149
153
|
soundEnabled = true,
|
|
154
|
+
fontFamily = DEFAULT_FONT,
|
|
150
155
|
style,
|
|
151
156
|
}: LivenessCameraProps) {
|
|
152
157
|
const { hasPermission, requestPermission } = useCameraPermission();
|
|
153
158
|
const device = useCameraDevice('front');
|
|
159
|
+
const format = useCameraFormat(device, [{ fps: 60 }]);
|
|
160
|
+
const fps = Math.min(format?.maxFps ?? 30, 60);
|
|
154
161
|
const cameraRef = useRef<Camera>(null);
|
|
155
162
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
156
163
|
|
|
@@ -185,7 +192,9 @@ export function LivenessCamera({
|
|
|
185
192
|
if (!hasPermission) {
|
|
186
193
|
return (
|
|
187
194
|
<View style={[styles.root, style, styles.centered]}>
|
|
188
|
-
<Text style={styles.permissionText
|
|
195
|
+
<Text style={[styles.permissionText, { fontFamily }]}>
|
|
196
|
+
Camera permission required
|
|
197
|
+
</Text>
|
|
189
198
|
</View>
|
|
190
199
|
);
|
|
191
200
|
}
|
|
@@ -193,7 +202,9 @@ export function LivenessCamera({
|
|
|
193
202
|
if (!device) {
|
|
194
203
|
return (
|
|
195
204
|
<View style={[styles.root, style, styles.centered]}>
|
|
196
|
-
<Text style={styles.permissionText
|
|
205
|
+
<Text style={[styles.permissionText, { fontFamily }]}>
|
|
206
|
+
No front camera found
|
|
207
|
+
</Text>
|
|
197
208
|
</View>
|
|
198
209
|
);
|
|
199
210
|
}
|
|
@@ -208,7 +219,8 @@ export function LivenessCamera({
|
|
|
208
219
|
frameProcessor={frameProcessor}
|
|
209
220
|
photo
|
|
210
221
|
pixelFormat="yuv"
|
|
211
|
-
|
|
222
|
+
format={format}
|
|
223
|
+
fps={fps}
|
|
212
224
|
/>
|
|
213
225
|
<CircleOverlay
|
|
214
226
|
width={containerSize.width}
|
|
@@ -218,12 +230,16 @@ export function LivenessCamera({
|
|
|
218
230
|
/>
|
|
219
231
|
{livenessState !== 'done' && (
|
|
220
232
|
<View style={styles.feedbackContainer}>
|
|
221
|
-
<Text style={styles.feedbackText}>{feedback}</Text>
|
|
233
|
+
<Text style={[styles.feedbackText, { fontFamily }]}>{feedback}</Text>
|
|
222
234
|
</View>
|
|
223
235
|
)}
|
|
224
236
|
{livenessState === 'countdown' && countdown !== null && (
|
|
225
237
|
<View style={styles.countdownContainer}>
|
|
226
|
-
<CountdownBubble
|
|
238
|
+
<CountdownBubble
|
|
239
|
+
key={countdown}
|
|
240
|
+
value={countdown}
|
|
241
|
+
fontFamily={fontFamily}
|
|
242
|
+
/>
|
|
227
243
|
</View>
|
|
228
244
|
)}
|
|
229
245
|
{livenessState === 'capturing' && (
|
|
@@ -260,7 +276,6 @@ const styles = StyleSheet.create({
|
|
|
260
276
|
feedbackText: {
|
|
261
277
|
color: '#fff',
|
|
262
278
|
fontSize: 16,
|
|
263
|
-
fontWeight: '600',
|
|
264
279
|
textAlign: 'center',
|
|
265
280
|
textShadowColor: 'rgba(0,0,0,0.8)',
|
|
266
281
|
textShadowOffset: { width: 0, height: 1 },
|
|
@@ -284,7 +299,6 @@ const styles = StyleSheet.create({
|
|
|
284
299
|
countdownText: {
|
|
285
300
|
color: '#fff',
|
|
286
301
|
fontSize: 52,
|
|
287
|
-
fontWeight: '700',
|
|
288
302
|
lineHeight: 60,
|
|
289
303
|
},
|
|
290
304
|
captureFlash: {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
2
|
+
import { Path, Svg } from 'react-native-svg';
|
|
3
|
+
import { LivenessCamera } from './LivenessCamera';
|
|
4
|
+
import type { LivenessCameraModalProps } from './types';
|
|
5
|
+
|
|
6
|
+
/** × icon drawn with SVG — no external icon library required. */
|
|
7
|
+
function CloseIcon({
|
|
8
|
+
color = '#fff',
|
|
9
|
+
size = 18,
|
|
10
|
+
}: {
|
|
11
|
+
color?: string;
|
|
12
|
+
size?: number;
|
|
13
|
+
}) {
|
|
14
|
+
const pad = size * 0.1;
|
|
15
|
+
const end = size - pad;
|
|
16
|
+
return (
|
|
17
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
18
|
+
<Path
|
|
19
|
+
d={`M${pad} ${pad} L${end} ${end} M${end} ${pad} L${pad} ${end}`}
|
|
20
|
+
stroke={color}
|
|
21
|
+
strokeWidth={size * 0.14}
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
/>
|
|
24
|
+
</Svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Drop-in liveness detection modal.
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <LivenessCameraModal
|
|
33
|
+
* visible={showLiveness}
|
|
34
|
+
* onClose={() => setShowLiveness(false)}
|
|
35
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
36
|
+
* animationType="slide"
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function LivenessCameraModal({
|
|
41
|
+
visible,
|
|
42
|
+
onClose,
|
|
43
|
+
animationType = 'slide',
|
|
44
|
+
closeButtonStyle,
|
|
45
|
+
closeButtonIconColor = '#fff',
|
|
46
|
+
closeButtonIconSize = 18,
|
|
47
|
+
// forward all LivenessCamera props
|
|
48
|
+
onCapture,
|
|
49
|
+
onLivenessConfirmed,
|
|
50
|
+
onError,
|
|
51
|
+
countdownFrom,
|
|
52
|
+
livenessThreshold,
|
|
53
|
+
confirmFrames,
|
|
54
|
+
soundEnabled,
|
|
55
|
+
fontFamily,
|
|
56
|
+
}: LivenessCameraModalProps) {
|
|
57
|
+
const handleCapture: LivenessCameraModalProps['onCapture'] = (result) => {
|
|
58
|
+
onCapture(result);
|
|
59
|
+
// Modal stays open — consumer decides when to close via onCapture callback
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Modal
|
|
64
|
+
visible={visible}
|
|
65
|
+
animationType={animationType}
|
|
66
|
+
statusBarTranslucent
|
|
67
|
+
onRequestClose={onClose}
|
|
68
|
+
>
|
|
69
|
+
<View style={styles.container}>
|
|
70
|
+
<LivenessCamera
|
|
71
|
+
style={styles.camera}
|
|
72
|
+
onCapture={handleCapture}
|
|
73
|
+
onLivenessConfirmed={onLivenessConfirmed}
|
|
74
|
+
onError={onError}
|
|
75
|
+
countdownFrom={countdownFrom}
|
|
76
|
+
livenessThreshold={livenessThreshold}
|
|
77
|
+
confirmFrames={confirmFrames}
|
|
78
|
+
soundEnabled={soundEnabled}
|
|
79
|
+
fontFamily={fontFamily}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
style={[styles.closeButton, closeButtonStyle]}
|
|
84
|
+
onPress={onClose}
|
|
85
|
+
activeOpacity={0.7}
|
|
86
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
87
|
+
>
|
|
88
|
+
<CloseIcon color={closeButtonIconColor} size={closeButtonIconSize} />
|
|
89
|
+
</TouchableOpacity>
|
|
90
|
+
</View>
|
|
91
|
+
</Modal>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
backgroundColor: '#000',
|
|
99
|
+
},
|
|
100
|
+
camera: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
},
|
|
103
|
+
closeButton: {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: 52,
|
|
106
|
+
left: 16,
|
|
107
|
+
width: 48,
|
|
108
|
+
height: 48,
|
|
109
|
+
borderRadius: 24,
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: 'rgba(255,255,255,0.3)',
|
|
115
|
+
},
|
|
116
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { LivenessCamera } from './LivenessCamera';
|
|
2
|
+
export { LivenessCameraModal } from './LivenessCameraModal';
|
|
2
3
|
export { useLivenessCamera } from './useLivenessCamera';
|
|
3
4
|
export type {
|
|
4
5
|
CaptureResult,
|
|
5
6
|
FaceData,
|
|
6
7
|
FeedbackMessage,
|
|
8
|
+
LivenessCameraModalProps,
|
|
7
9
|
LivenessCameraProps,
|
|
8
10
|
LivenessState,
|
|
9
11
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ViewStyle } from 'react-native';
|
|
1
|
+
import type { ModalProps, ViewStyle } from 'react-native';
|
|
2
2
|
import type { PhotoFile } from 'react-native-vision-camera';
|
|
3
3
|
|
|
4
4
|
export type FaceData = {
|
|
@@ -81,8 +81,47 @@ export type LivenessCameraProps = {
|
|
|
81
81
|
style?: ViewStyle;
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* Whether to play a shutter sound on capture.
|
|
85
|
-
* to be installed. Defaults to true.
|
|
84
|
+
* Whether to play a shutter sound on capture. Defaults to true.
|
|
86
85
|
*/
|
|
87
86
|
soundEnabled?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Font family applied to all text inside the component.
|
|
90
|
+
* Defaults to 'Baloo-Medium'. Set to undefined to use the system font.
|
|
91
|
+
*/
|
|
92
|
+
fontFamily?: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type LivenessCameraModalProps = Omit<LivenessCameraProps, 'style'> & {
|
|
96
|
+
/**
|
|
97
|
+
* Controls modal visibility — pass your own boolean state.
|
|
98
|
+
*/
|
|
99
|
+
visible: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Called when the close button is pressed or the Android back button fires.
|
|
103
|
+
* Use this to set your visible state to false.
|
|
104
|
+
*/
|
|
105
|
+
onClose: () => void;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Modal entrance/exit animation. Defaults to 'slide'.
|
|
109
|
+
*/
|
|
110
|
+
animationType?: ModalProps['animationType'];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Override styles on the close button container.
|
|
114
|
+
* Useful for adjusting position or size to match your design system.
|
|
115
|
+
*/
|
|
116
|
+
closeButtonStyle?: ViewStyle;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Colour of the × icon inside the close button. Defaults to '#fff'.
|
|
120
|
+
*/
|
|
121
|
+
closeButtonIconColor?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Size of the × icon in dp. Defaults to 18.
|
|
125
|
+
*/
|
|
126
|
+
closeButtonIconSize?: number;
|
|
88
127
|
};
|