react-native-timacare 3.3.63 → 3.3.65
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/commonjs/components/AlertLoan.js +1 -1
- package/lib/commonjs/components/AlertLoan.js.flow +11 -2
- package/lib/commonjs/components/AlertLoan.js.map +1 -1
- package/lib/commonjs/components/ItemTimaOne.js +1 -1
- package/lib/commonjs/components/ItemTimaOne.js.flow +3 -0
- package/lib/commonjs/components/ItemTimaOne.js.map +1 -1
- package/lib/commonjs/navigation/primary-navigator.js +1 -1
- package/lib/commonjs/navigation/primary-navigator.js.flow +9 -0
- package/lib/commonjs/navigation/primary-navigator.js.map +1 -1
- package/lib/commonjs/screens/full-submit/selfie.js +1 -1
- package/lib/commonjs/screens/full-submit/selfie.js.flow +257 -114
- package/lib/commonjs/screens/full-submit/selfie.js.map +1 -1
- package/lib/commonjs/screens/home/DigitalTopupItem.js +1 -1
- package/lib/commonjs/screens/home/DigitalTopupItem.js.flow +2 -11
- package/lib/commonjs/screens/home/DigitalTopupItem.js.map +1 -1
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js +1 -1
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.flow +2 -7
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.map +1 -1
- package/lib/commonjs/screens/home/index.js +1 -1
- package/lib/commonjs/screens/home/index.js.flow +1 -1
- package/lib/commonjs/screens/home/index.js.map +1 -1
- package/lib/commonjs/screens/selfie/SelfieCamera.js +2 -0
- package/lib/commonjs/screens/selfie/SelfieCamera.js.flow +302 -0
- package/lib/commonjs/screens/selfie/SelfieCamera.js.map +1 -0
- package/lib/commonjs/screens/selfie/index.js +1 -1
- package/lib/commonjs/screens/selfie/index.js.flow +295 -165
- package/lib/commonjs/screens/selfie/index.js.map +1 -1
- package/lib/commonjs/screens/v3/ReviewInfoLoanBuyCar.js +2 -0
- package/lib/commonjs/screens/v3/ReviewInfoLoanBuyCar.js.flow +521 -0
- package/lib/commonjs/screens/v3/ReviewInfoLoanBuyCar.js.map +1 -0
- package/lib/commonjs/services/api/api.js +1 -1
- package/lib/commonjs/services/api/api.js.flow +41 -7
- package/lib/commonjs/services/api/api.js.map +1 -1
- package/lib/module/components/AlertLoan.js +1 -1
- package/lib/module/components/AlertLoan.js.map +1 -1
- package/lib/module/components/ItemTimaOne.js +1 -1
- package/lib/module/components/ItemTimaOne.js.map +1 -1
- package/lib/module/navigation/primary-navigator.js +1 -1
- package/lib/module/navigation/primary-navigator.js.map +1 -1
- package/lib/module/screens/full-submit/selfie.js +1 -1
- package/lib/module/screens/full-submit/selfie.js.map +1 -1
- package/lib/module/screens/home/DigitalTopupItem.js +1 -1
- package/lib/module/screens/home/DigitalTopupItem.js.map +1 -1
- package/lib/module/screens/home/ItemMotoBikeLoan.js +1 -1
- package/lib/module/screens/home/ItemMotoBikeLoan.js.map +1 -1
- package/lib/module/screens/home/index.js +1 -1
- package/lib/module/screens/home/index.js.map +1 -1
- package/lib/module/screens/selfie/SelfieCamera.js +2 -0
- package/lib/module/screens/selfie/SelfieCamera.js.map +1 -0
- package/lib/module/screens/selfie/index.js +1 -1
- package/lib/module/screens/selfie/index.js.map +1 -1
- package/lib/module/screens/v3/ReviewInfoLoanBuyCar.js +2 -0
- package/lib/module/screens/v3/ReviewInfoLoanBuyCar.js.map +1 -0
- package/lib/module/services/api/api.js +1 -1
- package/lib/module/services/api/api.js.map +1 -1
- package/lib/typescript/components/AlertLoan.d.ts.map +1 -1
- package/lib/typescript/components/ItemTimaOne.d.ts.map +1 -1
- package/lib/typescript/navigation/primary-navigator.d.ts +2 -0
- package/lib/typescript/navigation/primary-navigator.d.ts.map +1 -1
- package/lib/typescript/screens/full-submit/selfie.d.ts.map +1 -1
- package/lib/typescript/screens/home/DigitalTopupItem.d.ts.map +1 -1
- package/lib/typescript/screens/home/ItemMotoBikeLoan.d.ts.map +1 -1
- package/lib/typescript/screens/selfie/SelfieCamera.d.ts +2 -0
- package/lib/typescript/screens/selfie/SelfieCamera.d.ts.map +1 -0
- package/lib/typescript/screens/selfie/index.d.ts.map +1 -1
- package/lib/typescript/screens/v3/ReviewInfoLoanBuyCar.d.ts +2 -0
- package/lib/typescript/screens/v3/ReviewInfoLoanBuyCar.d.ts.map +1 -0
- package/lib/typescript/services/api/api.d.ts +32 -0
- package/lib/typescript/services/api/api.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AlertLoan.tsx +11 -2
- package/src/components/ItemTimaOne.tsx +3 -0
- package/src/navigation/primary-navigator.tsx +9 -0
- package/src/screens/full-submit/selfie.tsx +257 -114
- package/src/screens/home/DigitalTopupItem.tsx +2 -11
- package/src/screens/home/ItemMotoBikeLoan.tsx +2 -7
- package/src/screens/home/index.tsx +1 -1
- package/src/screens/selfie/SelfieCamera.tsx +302 -0
- package/src/screens/selfie/index.tsx +295 -165
- package/src/screens/v3/ReviewInfoLoanBuyCar.tsx +521 -0
- package/src/services/api/api.ts +41 -7
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
Dimensions,
|
|
6
|
+
Platform,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import { useNavigation, useIsFocused } from '@react-navigation/native';
|
|
11
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
12
|
+
import {
|
|
13
|
+
request,
|
|
14
|
+
PERMISSIONS,
|
|
15
|
+
RESULTS,
|
|
16
|
+
openSettings,
|
|
17
|
+
} from 'react-native-permissions';
|
|
18
|
+
import {
|
|
19
|
+
Camera as VisionCamera,
|
|
20
|
+
useCameraDevice,
|
|
21
|
+
} from 'react-native-vision-camera';
|
|
22
|
+
import { Svg, Ellipse, Path } from 'react-native-svg';
|
|
23
|
+
import RNFS from 'react-native-fs';
|
|
24
|
+
import MButton from '../../components/MButton';
|
|
25
|
+
import { MText } from '../../components/MText';
|
|
26
|
+
import { commonStyles } from '../CommonStyles';
|
|
27
|
+
import { AlertGlobal } from '../../components/ModalNotification/AlertGlobal';
|
|
28
|
+
import { IconBack, TakePhotoSvg } from '../../assets/icons';
|
|
29
|
+
|
|
30
|
+
const ORANGE = '#F05123';
|
|
31
|
+
const SCREEN_WIDTH = Dimensions.get('window').width;
|
|
32
|
+
// Khung oval: rộng ~72% màn hình, cao gấp 1.33 lần chiều rộng (tỉ lệ khuôn mặt)
|
|
33
|
+
const OVAL_WIDTH = Math.round(SCREEN_WIDTH * 0.72);
|
|
34
|
+
const OVAL_HEIGHT = Math.round(OVAL_WIDTH * 1.33);
|
|
35
|
+
|
|
36
|
+
export default function SelfieCamera(props: any) {
|
|
37
|
+
const insets = useSafeAreaInsets();
|
|
38
|
+
const navigation = useNavigation();
|
|
39
|
+
const isFocused = useIsFocused();
|
|
40
|
+
const myCamera = useRef(null);
|
|
41
|
+
const takingRef = useRef(false);
|
|
42
|
+
const [passPermission, setPassPermission] = useState(false);
|
|
43
|
+
const [shouldRenderCamera, setShouldRenderCamera] = useState(false);
|
|
44
|
+
const [taking, setTaking] = useState(false);
|
|
45
|
+
// Kích thước vùng preview để vẽ mặt nạ oval đúng tâm
|
|
46
|
+
const [stage, setStage] = useState({ width: SCREEN_WIDTH, height: 0 });
|
|
47
|
+
|
|
48
|
+
const device = useCameraDevice('front');
|
|
49
|
+
|
|
50
|
+
const takePhoto = async () => {
|
|
51
|
+
if (myCamera.current == null || takingRef.current) return;
|
|
52
|
+
takingRef.current = true;
|
|
53
|
+
setTaking(true);
|
|
54
|
+
try {
|
|
55
|
+
const data = await myCamera.current.takePhoto({
|
|
56
|
+
qualityPrioritization: 'quality',
|
|
57
|
+
});
|
|
58
|
+
const uri = 'file://' + data.path;
|
|
59
|
+
navigation.goBack();
|
|
60
|
+
if (props.route?.params?.callback) {
|
|
61
|
+
props.route.params.callback(uri);
|
|
62
|
+
}
|
|
63
|
+
if (props.route?.params?.base64) {
|
|
64
|
+
const base64 = await RNFS.readFile(data.path, 'base64');
|
|
65
|
+
props.route.params.base64(base64);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.log('takePhoto error', err);
|
|
69
|
+
takingRef.current = false;
|
|
70
|
+
setTaking(false);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const requestPermissions = () => {
|
|
75
|
+
request(
|
|
76
|
+
Platform.OS === 'ios'
|
|
77
|
+
? PERMISSIONS.IOS.CAMERA
|
|
78
|
+
: PERMISSIONS.ANDROID.CAMERA
|
|
79
|
+
).then((result) => {
|
|
80
|
+
switch (result) {
|
|
81
|
+
case RESULTS.UNAVAILABLE:
|
|
82
|
+
AlertGlobal.show({
|
|
83
|
+
type: 'warning',
|
|
84
|
+
title: 'Thông báo',
|
|
85
|
+
message:
|
|
86
|
+
'Máy ảnh trên thiết bị không tương thích.\nVui lòng kiểm tra lại thiết bị!',
|
|
87
|
+
primaryText: 'Đồng ý',
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case RESULTS.DENIED:
|
|
91
|
+
AlertGlobal.show({
|
|
92
|
+
type: 'warning',
|
|
93
|
+
title: 'Thông báo',
|
|
94
|
+
message:
|
|
95
|
+
'Bạn đã từ chối quyền máy ảnh. Vui lòng cấp quyền máy ảnh để tiếp tục!',
|
|
96
|
+
primaryText: 'Đồng ý',
|
|
97
|
+
onPrimaryPress: () => {
|
|
98
|
+
if (Platform.OS === 'ios') {
|
|
99
|
+
openSettings();
|
|
100
|
+
} else {
|
|
101
|
+
requestPermissions();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
case RESULTS.LIMITED:
|
|
107
|
+
AlertGlobal.show({
|
|
108
|
+
type: 'warning',
|
|
109
|
+
title: 'Thông báo',
|
|
110
|
+
message:
|
|
111
|
+
'Quyền máy ảnh bị hạn chế. Vui lòng kiểm tra lại để tiếp tục!',
|
|
112
|
+
primaryText: 'Đồng ý',
|
|
113
|
+
onPrimaryPress: () => {
|
|
114
|
+
if (Platform.OS === 'ios') {
|
|
115
|
+
openSettings();
|
|
116
|
+
} else {
|
|
117
|
+
requestPermissions();
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
break;
|
|
122
|
+
case RESULTS.GRANTED:
|
|
123
|
+
setPassPermission(true);
|
|
124
|
+
break;
|
|
125
|
+
case RESULTS.BLOCKED:
|
|
126
|
+
AlertGlobal.show({
|
|
127
|
+
type: 'warning',
|
|
128
|
+
title: 'Thông báo',
|
|
129
|
+
message:
|
|
130
|
+
'Bạn đã từ chối quyền máy ảnh. Vui lòng cấp quyền máy ảnh để tiếp tục!',
|
|
131
|
+
primaryText: 'Đồng ý',
|
|
132
|
+
onPrimaryPress: () => openSettings(),
|
|
133
|
+
});
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
requestPermissions();
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
let timeout: NodeJS.Timeout;
|
|
145
|
+
if (isFocused) {
|
|
146
|
+
// Delay mounting camera to give Android time to release the previous one
|
|
147
|
+
timeout = setTimeout(() => {
|
|
148
|
+
setShouldRenderCamera(true);
|
|
149
|
+
}, 500);
|
|
150
|
+
} else {
|
|
151
|
+
setShouldRenderCamera(false);
|
|
152
|
+
}
|
|
153
|
+
return () => clearTimeout(timeout);
|
|
154
|
+
}, [isFocused]);
|
|
155
|
+
|
|
156
|
+
// Mặt nạ: phủ trắng toàn bộ vùng preview, khoét một lỗ hình oval ở giữa
|
|
157
|
+
// (fillRule evenodd) -> chỉ nhìn thấy camera bên trong oval.
|
|
158
|
+
const renderOvalMask = () => {
|
|
159
|
+
const { width: w, height: h } = stage;
|
|
160
|
+
if (!w || !h) return null;
|
|
161
|
+
const cx = w / 2;
|
|
162
|
+
const cy = h / 2;
|
|
163
|
+
const rx = OVAL_WIDTH / 2;
|
|
164
|
+
const ry = Math.min(OVAL_HEIGHT, h - 16) / 2;
|
|
165
|
+
const hole = `M ${cx - rx} ${cy} a ${rx} ${ry} 0 1 0 ${
|
|
166
|
+
rx * 2
|
|
167
|
+
} 0 a ${rx} ${ry} 0 1 0 ${-rx * 2} 0 Z`;
|
|
168
|
+
return (
|
|
169
|
+
<Svg
|
|
170
|
+
pointerEvents="none"
|
|
171
|
+
style={StyleSheet.absoluteFill}
|
|
172
|
+
width={w}
|
|
173
|
+
height={h}
|
|
174
|
+
>
|
|
175
|
+
<Path
|
|
176
|
+
d={`M 0 0 H ${w} V ${h} H 0 Z ${hole}`}
|
|
177
|
+
fill="#FFFFFF"
|
|
178
|
+
fillRule="evenodd"
|
|
179
|
+
/>
|
|
180
|
+
{/* Vòng ngoài mờ tạo chiều sâu cho khung, vòng trong nét đứt là mốc căn mặt */}
|
|
181
|
+
<Ellipse
|
|
182
|
+
cx={cx}
|
|
183
|
+
cy={cy}
|
|
184
|
+
rx={rx + 8}
|
|
185
|
+
ry={ry + 8}
|
|
186
|
+
stroke={ORANGE}
|
|
187
|
+
strokeWidth={1}
|
|
188
|
+
strokeOpacity={0.25}
|
|
189
|
+
fill="none"
|
|
190
|
+
/>
|
|
191
|
+
<Ellipse
|
|
192
|
+
cx={cx}
|
|
193
|
+
cy={cy}
|
|
194
|
+
rx={rx}
|
|
195
|
+
ry={ry}
|
|
196
|
+
stroke={ORANGE}
|
|
197
|
+
strokeWidth={2}
|
|
198
|
+
strokeDasharray="6 5"
|
|
199
|
+
strokeLinecap="round"
|
|
200
|
+
fill="none"
|
|
201
|
+
/>
|
|
202
|
+
</Svg>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<View style={styles.container}>
|
|
208
|
+
<View style={[styles.header, { paddingTop: insets.top + 12 }]}>
|
|
209
|
+
<MButton onPress={() => navigation.goBack()} style={styles.backButton}>
|
|
210
|
+
<IconBack />
|
|
211
|
+
</MButton>
|
|
212
|
+
<View style={styles.titleWrap}>
|
|
213
|
+
<MText style={styles.headerTitle}>Xác thực khuôn mặt</MText>
|
|
214
|
+
</View>
|
|
215
|
+
</View>
|
|
216
|
+
|
|
217
|
+
<View
|
|
218
|
+
style={styles.stage}
|
|
219
|
+
onLayout={(e) => setStage(e.nativeEvent.layout)}
|
|
220
|
+
>
|
|
221
|
+
{passPermission && shouldRenderCamera && device ? (
|
|
222
|
+
<VisionCamera
|
|
223
|
+
ref={myCamera}
|
|
224
|
+
style={StyleSheet.absoluteFill}
|
|
225
|
+
device={device}
|
|
226
|
+
isActive={isFocused}
|
|
227
|
+
photo={true}
|
|
228
|
+
/>
|
|
229
|
+
) : (
|
|
230
|
+
<View
|
|
231
|
+
style={[
|
|
232
|
+
StyleSheet.absoluteFill,
|
|
233
|
+
commonStyles.alignCenter,
|
|
234
|
+
commonStyles.justifyCenter,
|
|
235
|
+
]}
|
|
236
|
+
>
|
|
237
|
+
<ActivityIndicator color={ORANGE} size="large" />
|
|
238
|
+
</View>
|
|
239
|
+
)}
|
|
240
|
+
{renderOvalMask()}
|
|
241
|
+
</View>
|
|
242
|
+
|
|
243
|
+
<MText style={styles.instruction}>
|
|
244
|
+
Xin vui lòng nhìn thẳng, khuôn mặt ở trong khung hình, ánh sáng đủ rõ
|
|
245
|
+
nét khuôn mặt
|
|
246
|
+
</MText>
|
|
247
|
+
|
|
248
|
+
<View style={[styles.captureWrap, { marginBottom: insets.bottom + 24 }]}>
|
|
249
|
+
{/* Quầng cam nhạt quanh nút chụp: tách nút khỏi nền trắng, đúng thiết kế */}
|
|
250
|
+
<View style={styles.captureHalo}>
|
|
251
|
+
{taking ? (
|
|
252
|
+
<ActivityIndicator color={ORANGE} size="large" />
|
|
253
|
+
) : (
|
|
254
|
+
<MButton onPress={takePhoto}>
|
|
255
|
+
<TakePhotoSvg />
|
|
256
|
+
</MButton>
|
|
257
|
+
)}
|
|
258
|
+
</View>
|
|
259
|
+
</View>
|
|
260
|
+
</View>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const styles = StyleSheet.create({
|
|
265
|
+
container: { flex: 1, backgroundColor: '#FFFFFF' },
|
|
266
|
+
header: {
|
|
267
|
+
flexDirection: 'row',
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
paddingHorizontal: 16,
|
|
270
|
+
paddingBottom: 8,
|
|
271
|
+
},
|
|
272
|
+
backButton: { width: 32, height: 32, justifyContent: 'center' },
|
|
273
|
+
titleWrap: {
|
|
274
|
+
flex: 1,
|
|
275
|
+
alignItems: 'center',
|
|
276
|
+
justifyContent: 'center',
|
|
277
|
+
marginRight: 32,
|
|
278
|
+
},
|
|
279
|
+
headerTitle: { color: '#333333', fontSize: 16, fontWeight: '600' },
|
|
280
|
+
stage: {
|
|
281
|
+
flex: 1,
|
|
282
|
+
overflow: 'hidden',
|
|
283
|
+
backgroundColor: '#FFFFFF',
|
|
284
|
+
},
|
|
285
|
+
instruction: {
|
|
286
|
+
textAlign: 'center',
|
|
287
|
+
color: '#333333',
|
|
288
|
+
fontSize: 14,
|
|
289
|
+
lineHeight: 20,
|
|
290
|
+
paddingHorizontal: 32,
|
|
291
|
+
marginTop: 16,
|
|
292
|
+
},
|
|
293
|
+
captureWrap: { alignItems: 'center', marginTop: 24 },
|
|
294
|
+
captureHalo: {
|
|
295
|
+
width: 88,
|
|
296
|
+
height: 88,
|
|
297
|
+
borderRadius: 44,
|
|
298
|
+
alignItems: 'center',
|
|
299
|
+
justifyContent: 'center',
|
|
300
|
+
backgroundColor: 'rgba(240, 81, 35, 0.10)',
|
|
301
|
+
},
|
|
302
|
+
});
|