react-native-timacare 3.3.66 → 3.3.67
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 +0 -19
- package/lib/commonjs/components/AlertLoan.js.map +1 -1
- package/lib/commonjs/screens/toan-trinh-so/TTSSelfie.js +1 -1
- package/lib/commonjs/screens/toan-trinh-so/TTSSelfie.js.flow +189 -138
- package/lib/commonjs/screens/toan-trinh-so/TTSSelfie.js.map +1 -1
- package/lib/module/components/AlertLoan.js +1 -1
- package/lib/module/components/AlertLoan.js.map +1 -1
- package/lib/module/screens/toan-trinh-so/TTSSelfie.js +1 -1
- package/lib/module/screens/toan-trinh-so/TTSSelfie.js.map +1 -1
- package/lib/typescript/components/AlertLoan.d.ts.map +1 -1
- package/lib/typescript/screens/toan-trinh-so/TTSSelfie.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AlertLoan.tsx +0 -19
- package/src/screens/toan-trinh-so/TTSSelfie.tsx +189 -138
|
@@ -1,41 +1,57 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
Dimensions,
|
|
6
|
+
Platform,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
4
10
|
import { useNavigation, useIsFocused } from '@react-navigation/native';
|
|
11
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
12
|
import {
|
|
6
13
|
request,
|
|
7
14
|
PERMISSIONS,
|
|
8
15
|
RESULTS,
|
|
9
16
|
openSettings,
|
|
10
17
|
} from 'react-native-permissions';
|
|
18
|
+
import {
|
|
19
|
+
Camera,
|
|
20
|
+
useCameraDevice,
|
|
21
|
+
useCameraFormat,
|
|
22
|
+
} from 'react-native-vision-camera';
|
|
23
|
+
import { Svg, Ellipse, Path } from 'react-native-svg';
|
|
11
24
|
import { MText } from '../../components/MText';
|
|
12
25
|
import MButton from '../../components/MButton';
|
|
13
26
|
import { commonStyles } from '../CommonStyles';
|
|
14
|
-
import {
|
|
27
|
+
import { IconBack, TakePhotoSvg } from '../../assets/icons';
|
|
15
28
|
import Loading from '../../components/Loading';
|
|
16
29
|
import { Api } from '../../services/api';
|
|
17
30
|
import { ScreenNames } from '../../navigation';
|
|
18
31
|
import appStore from '../../AppStore';
|
|
19
32
|
import { logEvent, logEventFb } from '../../utils';
|
|
20
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
21
|
-
import {
|
|
22
|
-
Camera,
|
|
23
|
-
useCameraDevice,
|
|
24
|
-
useCameraFormat,
|
|
25
|
-
} from 'react-native-vision-camera';
|
|
26
33
|
import { AlertGlobal } from '../../components/ModalNotification/AlertGlobal';
|
|
27
34
|
|
|
35
|
+
const ORANGE = '#F05123';
|
|
36
|
+
const SCREEN_WIDTH = Dimensions.get('window').width;
|
|
37
|
+
// 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)
|
|
38
|
+
const OVAL_WIDTH = Math.round(SCREEN_WIDTH * 0.72);
|
|
39
|
+
const OVAL_HEIGHT = Math.round(OVAL_WIDTH * 1.33);
|
|
40
|
+
|
|
28
41
|
export default function TTSSelfie(props: any) {
|
|
29
42
|
const insets = useSafeAreaInsets();
|
|
30
43
|
const isFocused = useIsFocused();
|
|
31
|
-
const [shouldRenderCamera, setShouldRenderCamera] = useState(false);
|
|
32
44
|
const navigation = useNavigation();
|
|
45
|
+
const myCamera = useRef(null);
|
|
46
|
+
const takingRef = useRef(false);
|
|
47
|
+
const [passPermission, setPassPermission] = useState(false);
|
|
48
|
+
const [shouldRenderCamera, setShouldRenderCamera] = useState(false);
|
|
33
49
|
const [taking, setTaking] = useState(false);
|
|
34
50
|
const [isLoading, setIsLoading] = useState(false);
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const myCamera = useRef(null);
|
|
51
|
+
// Kích thước vùng preview để vẽ mặt nạ oval đúng tâm
|
|
52
|
+
const [stage, setStage] = useState({ width: SCREEN_WIDTH, height: 0 });
|
|
38
53
|
|
|
54
|
+
const device = useCameraDevice('front');
|
|
39
55
|
const format = useCameraFormat(device, [
|
|
40
56
|
{ photoResolution: { width: 1280, height: 1280 } },
|
|
41
57
|
]);
|
|
@@ -48,9 +64,6 @@ export default function TTSSelfie(props: any) {
|
|
|
48
64
|
).then((result) => {
|
|
49
65
|
switch (result) {
|
|
50
66
|
case RESULTS.UNAVAILABLE:
|
|
51
|
-
console.log(
|
|
52
|
-
'This feature is not available (on this device / in this context)'
|
|
53
|
-
);
|
|
54
67
|
AlertGlobal.show({
|
|
55
68
|
type: 'warning',
|
|
56
69
|
title: 'Thông báo',
|
|
@@ -60,9 +73,6 @@ export default function TTSSelfie(props: any) {
|
|
|
60
73
|
});
|
|
61
74
|
break;
|
|
62
75
|
case RESULTS.DENIED:
|
|
63
|
-
console.log(
|
|
64
|
-
'The permission has not been requested / is denied but requestable'
|
|
65
|
-
);
|
|
66
76
|
AlertGlobal.show({
|
|
67
77
|
type: 'warning',
|
|
68
78
|
title: 'Thông báo',
|
|
@@ -79,7 +89,6 @@ export default function TTSSelfie(props: any) {
|
|
|
79
89
|
});
|
|
80
90
|
break;
|
|
81
91
|
case RESULTS.LIMITED:
|
|
82
|
-
console.log('The permission is limited: some actions are possible');
|
|
83
92
|
AlertGlobal.show({
|
|
84
93
|
type: 'warning',
|
|
85
94
|
title: 'Thông báo',
|
|
@@ -96,24 +105,16 @@ export default function TTSSelfie(props: any) {
|
|
|
96
105
|
});
|
|
97
106
|
break;
|
|
98
107
|
case RESULTS.GRANTED:
|
|
99
|
-
console.log('The permission is granted');
|
|
100
108
|
setPassPermission(true);
|
|
101
109
|
break;
|
|
102
110
|
case RESULTS.BLOCKED:
|
|
103
|
-
console.log('The permission is denied and not requestable anymore');
|
|
104
111
|
AlertGlobal.show({
|
|
105
112
|
type: 'warning',
|
|
106
113
|
title: 'Thông báo',
|
|
107
114
|
message:
|
|
108
115
|
'Bạn đã từ chối quyền máy ảnh. Vui lòng cấp quyền máy ảnh để tiếp tục!',
|
|
109
116
|
primaryText: 'Đồng ý',
|
|
110
|
-
onPrimaryPress: () =>
|
|
111
|
-
if (Platform.OS === 'ios') {
|
|
112
|
-
openSettings();
|
|
113
|
-
} else {
|
|
114
|
-
openSettings();
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
+
onPrimaryPress: () => openSettings(),
|
|
117
118
|
});
|
|
118
119
|
break;
|
|
119
120
|
}
|
|
@@ -137,30 +138,34 @@ export default function TTSSelfie(props: any) {
|
|
|
137
138
|
|
|
138
139
|
useEffect(() => {
|
|
139
140
|
let timeout: NodeJS.Timeout;
|
|
140
|
-
|
|
141
141
|
if (isFocused) {
|
|
142
142
|
// Delay mounting camera to give Android time to release the previous one
|
|
143
143
|
timeout = setTimeout(() => {
|
|
144
144
|
setShouldRenderCamera(true);
|
|
145
|
-
}, 500);
|
|
145
|
+
}, 500);
|
|
146
146
|
} else {
|
|
147
147
|
setShouldRenderCamera(false);
|
|
148
148
|
}
|
|
149
|
-
|
|
150
149
|
return () => clearTimeout(timeout);
|
|
151
150
|
}, [isFocused]);
|
|
152
151
|
|
|
153
152
|
const takePhoto = async () => {
|
|
154
|
-
if (myCamera.current == null ||
|
|
153
|
+
if (myCamera.current == null || takingRef.current) return;
|
|
154
|
+
takingRef.current = true;
|
|
155
155
|
setTaking(true);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
try {
|
|
157
|
+
const data = await myCamera.current.takePhoto({
|
|
158
|
+
qualityPrioritization: 'speed',
|
|
159
|
+
});
|
|
160
|
+
setTaking(false);
|
|
161
|
+
if (!data) {
|
|
162
|
+
takingRef.current = false;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
161
165
|
setIsLoading(true);
|
|
166
|
+
const loan = props?.route?.params?.loan;
|
|
162
167
|
const formData = new FormData();
|
|
163
|
-
formData.append('LoanBriefId',
|
|
168
|
+
formData.append('LoanBriefId', loan?.id);
|
|
164
169
|
formData.append('TypeDocumentId', 125); // Anh selfie
|
|
165
170
|
formData.append('image', {
|
|
166
171
|
uri: 'file://' + data.path,
|
|
@@ -169,23 +174,17 @@ export default function TTSSelfie(props: any) {
|
|
|
169
174
|
});
|
|
170
175
|
const response = await Api.getInstance().uploadImageAutoDE(formData);
|
|
171
176
|
setIsLoading(false);
|
|
177
|
+
takingRef.current = false;
|
|
172
178
|
if (response.kind === 'ok') {
|
|
173
179
|
if (response.data?.meta.errorCode === 200) {
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
} else if (props?.route?.params?.loan?.typeLoan === 8) {
|
|
182
|
-
navigation.push(ScreenNames.InfoLoanMotobike, {
|
|
183
|
-
loan: props?.route?.params?.loan,
|
|
184
|
-
});
|
|
180
|
+
if (loan?.typeLoan === 6 || loan?.typeLoan === 4) {
|
|
181
|
+
navigation.push(ScreenNames.TopupLoanInfo, { loan });
|
|
182
|
+
} else if (loan?.typeLoan === 8) {
|
|
183
|
+
navigation.push(ScreenNames.InfoLoanMotobike, { loan });
|
|
184
|
+
} else if (loan?.typeLoan === 7) {
|
|
185
|
+
navigation.push(ScreenNames.ReviewInfoLoanBuyCar, { loan });
|
|
185
186
|
} else {
|
|
186
|
-
navigation.push(ScreenNames.ReviewInformation, {
|
|
187
|
-
loan: props?.route?.params?.loan,
|
|
188
|
-
});
|
|
187
|
+
navigation.push(ScreenNames.ReviewInformation, { loan });
|
|
189
188
|
}
|
|
190
189
|
} else {
|
|
191
190
|
AlertGlobal.show({
|
|
@@ -203,109 +202,161 @@ export default function TTSSelfie(props: any) {
|
|
|
203
202
|
primaryText: 'Đóng',
|
|
204
203
|
});
|
|
205
204
|
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.log('takePhoto error', err);
|
|
207
|
+
setTaking(false);
|
|
208
|
+
setIsLoading(false);
|
|
209
|
+
takingRef.current = false;
|
|
206
210
|
}
|
|
207
211
|
};
|
|
208
212
|
|
|
213
|
+
// Mặt nạ: phủ trắng toàn bộ vùng preview, khoét một lỗ hình oval ở giữa
|
|
214
|
+
// (fillRule evenodd) -> chỉ nhìn thấy camera bên trong oval.
|
|
215
|
+
const renderOvalMask = () => {
|
|
216
|
+
const { width: w, height: h } = stage;
|
|
217
|
+
if (!w || !h) return null;
|
|
218
|
+
const cx = w / 2;
|
|
219
|
+
const cy = h / 2;
|
|
220
|
+
const rx = OVAL_WIDTH / 2;
|
|
221
|
+
const ry = Math.min(OVAL_HEIGHT, h - 16) / 2;
|
|
222
|
+
const hole = `M ${cx - rx} ${cy} a ${rx} ${ry} 0 1 0 ${
|
|
223
|
+
rx * 2
|
|
224
|
+
} 0 a ${rx} ${ry} 0 1 0 ${-rx * 2} 0 Z`;
|
|
225
|
+
return (
|
|
226
|
+
<Svg
|
|
227
|
+
pointerEvents="none"
|
|
228
|
+
style={StyleSheet.absoluteFill}
|
|
229
|
+
width={w}
|
|
230
|
+
height={h}
|
|
231
|
+
>
|
|
232
|
+
<Path
|
|
233
|
+
d={`M 0 0 H ${w} V ${h} H 0 Z ${hole}`}
|
|
234
|
+
fill="#FFFFFF"
|
|
235
|
+
fillRule="evenodd"
|
|
236
|
+
/>
|
|
237
|
+
{/* 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 */}
|
|
238
|
+
<Ellipse
|
|
239
|
+
cx={cx}
|
|
240
|
+
cy={cy}
|
|
241
|
+
rx={rx + 8}
|
|
242
|
+
ry={ry + 8}
|
|
243
|
+
stroke={ORANGE}
|
|
244
|
+
strokeWidth={1}
|
|
245
|
+
strokeOpacity={0.25}
|
|
246
|
+
fill="none"
|
|
247
|
+
/>
|
|
248
|
+
<Ellipse
|
|
249
|
+
cx={cx}
|
|
250
|
+
cy={cy}
|
|
251
|
+
rx={rx}
|
|
252
|
+
ry={ry}
|
|
253
|
+
stroke={ORANGE}
|
|
254
|
+
strokeWidth={2}
|
|
255
|
+
strokeDasharray="6 5"
|
|
256
|
+
strokeLinecap="round"
|
|
257
|
+
fill="none"
|
|
258
|
+
/>
|
|
259
|
+
</Svg>
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
|
|
209
263
|
return (
|
|
210
|
-
<View
|
|
211
|
-
style={{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<View
|
|
216
|
-
style={
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
width: Dimensions.get('window').width,
|
|
220
|
-
height: Dimensions.get('window').height,
|
|
221
|
-
},
|
|
222
|
-
]}
|
|
223
|
-
>
|
|
224
|
-
<View
|
|
225
|
-
style={{
|
|
226
|
-
marginVertical: 24,
|
|
227
|
-
flexDirection: 'row',
|
|
228
|
-
alignItems: 'center',
|
|
229
|
-
justifyContent: 'center',
|
|
230
|
-
marginTop: insets.top + 16,
|
|
231
|
-
}}
|
|
232
|
-
>
|
|
233
|
-
<MButton
|
|
234
|
-
onPress={() => {
|
|
235
|
-
navigation.goBack();
|
|
236
|
-
}}
|
|
237
|
-
style={{
|
|
238
|
-
position: 'absolute',
|
|
239
|
-
left: 16,
|
|
240
|
-
}}
|
|
241
|
-
>
|
|
242
|
-
<IconBackWhite />
|
|
243
|
-
</MButton>
|
|
244
|
-
<MText
|
|
245
|
-
style={{
|
|
246
|
-
color: 'white',
|
|
247
|
-
fontWeight: 'bold',
|
|
248
|
-
textAlgin: 'center',
|
|
249
|
-
}}
|
|
250
|
-
>
|
|
251
|
-
Xác thực khuôn mặt
|
|
252
|
-
</MText>
|
|
253
|
-
</View>
|
|
264
|
+
<View style={styles.container}>
|
|
265
|
+
<View style={[styles.header, { paddingTop: insets.top + 12 }]}>
|
|
266
|
+
<MButton onPress={() => navigation.goBack()} style={styles.backButton}>
|
|
267
|
+
<IconBack />
|
|
268
|
+
</MButton>
|
|
269
|
+
<View style={styles.titleWrap}>
|
|
270
|
+
<MText style={styles.headerTitle}>Xác thực khuôn mặt</MText>
|
|
271
|
+
</View>
|
|
272
|
+
</View>
|
|
254
273
|
|
|
274
|
+
<View
|
|
275
|
+
style={styles.stage}
|
|
276
|
+
onLayout={(e) => setStage(e.nativeEvent.layout)}
|
|
277
|
+
>
|
|
278
|
+
{passPermission && shouldRenderCamera && device ? (
|
|
255
279
|
<Camera
|
|
256
|
-
style={{
|
|
257
|
-
width: Dimensions.get('window').width,
|
|
258
|
-
height: (Dimensions.get('window').width * 4) / 3.1,
|
|
259
|
-
}}
|
|
260
280
|
ref={myCamera}
|
|
281
|
+
style={StyleSheet.absoluteFill}
|
|
261
282
|
device={device}
|
|
262
|
-
isActive={
|
|
283
|
+
isActive={isFocused}
|
|
263
284
|
photo={true}
|
|
264
285
|
format={format}
|
|
265
286
|
/>
|
|
266
|
-
|
|
287
|
+
) : (
|
|
267
288
|
<View
|
|
268
|
-
style={
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}}
|
|
289
|
+
style={[
|
|
290
|
+
StyleSheet.absoluteFill,
|
|
291
|
+
commonStyles.alignCenter,
|
|
292
|
+
commonStyles.justifyCenter,
|
|
293
|
+
]}
|
|
274
294
|
>
|
|
275
|
-
{
|
|
276
|
-
<>
|
|
277
|
-
<MText
|
|
278
|
-
style={{
|
|
279
|
-
color: 'white',
|
|
280
|
-
textAlign: 'center',
|
|
281
|
-
fontSize: 14,
|
|
282
|
-
paddingHorizontal: 24,
|
|
283
|
-
}}
|
|
284
|
-
>
|
|
285
|
-
Xin vui lòng nhìn thẳng, khuôn mặt ở trong khung hình, ánh
|
|
286
|
-
sáng đủ rõ nét khuôn mặt
|
|
287
|
-
</MText>
|
|
288
|
-
<View
|
|
289
|
-
style={{
|
|
290
|
-
alignItems: 'center',
|
|
291
|
-
marginVertical: 24,
|
|
292
|
-
}}
|
|
293
|
-
>
|
|
294
|
-
<MButton
|
|
295
|
-
onPress={() => {
|
|
296
|
-
takePhoto();
|
|
297
|
-
}}
|
|
298
|
-
>
|
|
299
|
-
<TakePhotoSvg />
|
|
300
|
-
</MButton>
|
|
301
|
-
</View>
|
|
302
|
-
</>
|
|
303
|
-
)}
|
|
295
|
+
<ActivityIndicator color={ORANGE} size="large" />
|
|
304
296
|
</View>
|
|
297
|
+
)}
|
|
298
|
+
{renderOvalMask()}
|
|
299
|
+
</View>
|
|
300
|
+
|
|
301
|
+
<MText style={styles.instruction}>
|
|
302
|
+
Xin vui lòng nhìn thẳng, khuôn mặt ở trong khung hình, ánh sáng đủ rõ
|
|
303
|
+
nét khuôn mặt
|
|
304
|
+
</MText>
|
|
305
|
+
|
|
306
|
+
<View style={[styles.captureWrap, { marginBottom: insets.bottom + 24 }]}>
|
|
307
|
+
{/* Quầng cam nhạt quanh nút chụp: tách nút khỏi nền trắng, đúng thiết kế */}
|
|
308
|
+
<View style={styles.captureHalo}>
|
|
309
|
+
{taking ? (
|
|
310
|
+
<ActivityIndicator color={ORANGE} size="large" />
|
|
311
|
+
) : (
|
|
312
|
+
<MButton onPress={takePhoto}>
|
|
313
|
+
<TakePhotoSvg />
|
|
314
|
+
</MButton>
|
|
315
|
+
)}
|
|
305
316
|
</View>
|
|
306
|
-
|
|
317
|
+
</View>
|
|
307
318
|
|
|
308
319
|
<Loading isLoading={isLoading} />
|
|
309
320
|
</View>
|
|
310
321
|
);
|
|
311
322
|
}
|
|
323
|
+
|
|
324
|
+
const styles = StyleSheet.create({
|
|
325
|
+
container: { flex: 1, backgroundColor: '#FFFFFF' },
|
|
326
|
+
header: {
|
|
327
|
+
flexDirection: 'row',
|
|
328
|
+
alignItems: 'center',
|
|
329
|
+
paddingHorizontal: 16,
|
|
330
|
+
paddingBottom: 8,
|
|
331
|
+
},
|
|
332
|
+
backButton: { width: 32, height: 32, justifyContent: 'center' },
|
|
333
|
+
titleWrap: {
|
|
334
|
+
flex: 1,
|
|
335
|
+
alignItems: 'center',
|
|
336
|
+
justifyContent: 'center',
|
|
337
|
+
marginRight: 32,
|
|
338
|
+
},
|
|
339
|
+
headerTitle: { color: '#333333', fontSize: 16, fontWeight: '600' },
|
|
340
|
+
stage: {
|
|
341
|
+
flex: 1,
|
|
342
|
+
overflow: 'hidden',
|
|
343
|
+
backgroundColor: '#FFFFFF',
|
|
344
|
+
},
|
|
345
|
+
instruction: {
|
|
346
|
+
textAlign: 'center',
|
|
347
|
+
color: '#333333',
|
|
348
|
+
fontSize: 14,
|
|
349
|
+
lineHeight: 20,
|
|
350
|
+
paddingHorizontal: 32,
|
|
351
|
+
marginTop: 16,
|
|
352
|
+
},
|
|
353
|
+
captureWrap: { alignItems: 'center', marginTop: 24 },
|
|
354
|
+
captureHalo: {
|
|
355
|
+
width: 88,
|
|
356
|
+
height: 88,
|
|
357
|
+
borderRadius: 44,
|
|
358
|
+
alignItems: 'center',
|
|
359
|
+
justifyContent: 'center',
|
|
360
|
+
backgroundColor: 'rgba(240, 81, 35, 0.10)',
|
|
361
|
+
},
|
|
362
|
+
});
|