react-native-timacare 3.3.59-cccd → 3.3.61-cccd

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 (80) hide show
  1. package/lib/commonjs/AppStore.js +1 -1
  2. package/lib/commonjs/AppStore.js.flow +0 -119
  3. package/lib/commonjs/AppStore.js.map +1 -1
  4. package/lib/commonjs/navigation/primary-navigator.js +1 -1
  5. package/lib/commonjs/navigation/primary-navigator.js.flow +0 -3
  6. package/lib/commonjs/navigation/primary-navigator.js.map +1 -1
  7. package/lib/commonjs/screens/camera/CCCDCameraScreen.js +1 -1
  8. package/lib/commonjs/screens/camera/CCCDCameraScreen.js.flow +17 -6
  9. package/lib/commonjs/screens/camera/CCCDCameraScreen.js.map +1 -1
  10. package/lib/commonjs/screens/main/index.js +1 -1
  11. package/lib/commonjs/screens/main/index.js.flow +0 -1
  12. package/lib/commonjs/screens/main/index.js.map +1 -1
  13. package/lib/commonjs/screens/toan-trinh-so/OCR.js +1 -1
  14. package/lib/commonjs/screens/toan-trinh-so/OCR.js.flow +30 -8
  15. package/lib/commonjs/screens/toan-trinh-so/OCR.js.map +1 -1
  16. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js +1 -1
  17. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.flow +28 -8
  18. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
  19. package/lib/commonjs/utils/cameraCrop.js +1 -1
  20. package/lib/commonjs/utils/cameraCrop.js.flow +29 -30
  21. package/lib/commonjs/utils/cameraCrop.js.map +1 -1
  22. package/lib/module/AppStore.js +1 -1
  23. package/lib/module/AppStore.js.map +1 -1
  24. package/lib/module/navigation/primary-navigator.js +1 -1
  25. package/lib/module/navigation/primary-navigator.js.map +1 -1
  26. package/lib/module/screens/camera/CCCDCameraScreen.js +1 -1
  27. package/lib/module/screens/camera/CCCDCameraScreen.js.map +1 -1
  28. package/lib/module/screens/main/index.js +1 -1
  29. package/lib/module/screens/main/index.js.map +1 -1
  30. package/lib/module/screens/toan-trinh-so/OCR.js +1 -1
  31. package/lib/module/screens/toan-trinh-so/OCR.js.map +1 -1
  32. package/lib/module/screens/toan-trinh-so/VehicleCamera.js +1 -1
  33. package/lib/module/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
  34. package/lib/module/utils/cameraCrop.js +1 -1
  35. package/lib/module/utils/cameraCrop.js.map +1 -1
  36. package/lib/typescript/AppStore.d.ts +0 -5
  37. package/lib/typescript/AppStore.d.ts.map +1 -1
  38. package/lib/typescript/navigation/primary-navigator.d.ts +0 -1
  39. package/lib/typescript/navigation/primary-navigator.d.ts.map +1 -1
  40. package/lib/typescript/screens/camera/CCCDCameraScreen.d.ts.map +1 -1
  41. package/lib/typescript/screens/main/index.d.ts.map +1 -1
  42. package/lib/typescript/screens/toan-trinh-so/OCR.d.ts.map +1 -1
  43. package/lib/typescript/screens/toan-trinh-so/VehicleCamera.d.ts.map +1 -1
  44. package/lib/typescript/utils/cameraCrop.d.ts +13 -19
  45. package/lib/typescript/utils/cameraCrop.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/AppStore.tsx +0 -119
  48. package/src/navigation/primary-navigator.tsx +0 -3
  49. package/src/screens/camera/CCCDCameraScreen.tsx +17 -6
  50. package/src/screens/main/index.tsx +0 -1
  51. package/src/screens/toan-trinh-so/OCR.tsx +30 -8
  52. package/src/screens/toan-trinh-so/VehicleCamera.tsx +28 -8
  53. package/src/utils/cameraCrop.ts +29 -30
  54. package/lib/commonjs/screens/personal/bg.png +0 -0
  55. package/lib/commonjs/screens/personal/index.js +0 -2
  56. package/lib/commonjs/screens/personal/index.js.flow +0 -112
  57. package/lib/commonjs/screens/personal/index.js.map +0 -1
  58. package/lib/commonjs/screens/qualify/Store.js +0 -2
  59. package/lib/commonjs/screens/qualify/Store.js.flow +0 -49
  60. package/lib/commonjs/screens/qualify/Store.js.map +0 -1
  61. package/lib/commonjs/screens/qualify/index.js +0 -2
  62. package/lib/commonjs/screens/qualify/index.js.flow +0 -220
  63. package/lib/commonjs/screens/qualify/index.js.map +0 -1
  64. package/lib/module/screens/personal/bg.png +0 -0
  65. package/lib/module/screens/personal/index.js +0 -2
  66. package/lib/module/screens/personal/index.js.map +0 -1
  67. package/lib/module/screens/qualify/Store.js +0 -2
  68. package/lib/module/screens/qualify/Store.js.map +0 -1
  69. package/lib/module/screens/qualify/index.js +0 -2
  70. package/lib/module/screens/qualify/index.js.map +0 -1
  71. package/lib/typescript/screens/personal/index.d.ts +0 -2
  72. package/lib/typescript/screens/personal/index.d.ts.map +0 -1
  73. package/lib/typescript/screens/qualify/Store.d.ts +0 -8
  74. package/lib/typescript/screens/qualify/Store.d.ts.map +0 -1
  75. package/lib/typescript/screens/qualify/index.d.ts +0 -2
  76. package/lib/typescript/screens/qualify/index.d.ts.map +0 -1
  77. package/src/screens/personal/bg.png +0 -0
  78. package/src/screens/personal/index.tsx +0 -112
  79. package/src/screens/qualify/Store.tsx +0 -49
  80. package/src/screens/qualify/index.tsx +0 -220
@@ -1 +1 @@
1
- {"version":3,"file":"CCCDCameraScreen.d.ts","sourceRoot":"","sources":["../../../../src/screens/camera/CCCDCameraScreen.tsx"],"names":[],"mappings":"AA0CA,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,KAAA,eAiP7C"}
1
+ {"version":3,"file":"CCCDCameraScreen.d.ts","sourceRoot":"","sources":["../../../../src/screens/camera/CCCDCameraScreen.tsx"],"names":[],"mappings":"AA0CA,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,KAAA,eA4P7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screens/main/index.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,IAAI,KAMf,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screens/main/index.tsx"],"names":[],"mappings":"AAWA,eAAO,MAAM,IAAI,KAMf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OCR.d.ts","sourceRoot":"","sources":["../../../../src/screens/toan-trinh-so/OCR.tsx"],"names":[],"mappings":"AA+CA,eAAO,MAAM,GAAG,KAwYd,CAAC"}
1
+ {"version":3,"file":"OCR.d.ts","sourceRoot":"","sources":["../../../../src/screens/toan-trinh-so/OCR.tsx"],"names":[],"mappings":"AAgDA,eAAO,MAAM,GAAG,KA6Zd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"VehicleCamera.d.ts","sourceRoot":"","sources":["../../../../src/screens/toan-trinh-so/VehicleCamera.tsx"],"names":[],"mappings":"AA8CA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,KAAK,EAAE,GAAG,eAia/C"}
1
+ {"version":3,"file":"VehicleCamera.d.ts","sourceRoot":"","sources":["../../../../src/screens/toan-trinh-so/VehicleCamera.tsx"],"names":[],"mappings":"AA+CA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,KAAK,EAAE,GAAG,eAob/C"}
@@ -1,15 +1,3 @@
1
- /**
2
- * Kích thước hiển thị (upright) của ảnh chụp từ VisionCamera.
3
- *
4
- * photo.width/height là kích thước buffer gốc của cảm biến (luôn landscape).
5
- * photo.orientation cho biết ảnh cần xoay bao nhiêu để hiển thị đúng chiều:
6
- * khi là 'landscape-left'/'landscape-right' (điện thoại cầm dọc) thì ảnh hiển
7
- * thị bị xoay 90° so với buffer -> phải hoán đổi W/H.
8
- */
9
- export declare const getDisplaySize: (photo: any) => {
10
- width: any;
11
- height: any;
12
- };
13
1
  /**
14
2
  * Vùng cắt chính giữa theo đúng tỉ lệ khung (aspect = W/H), tính trong hệ toạ
15
3
  * độ ảnh đã dựng đứng (upright). VisionCamera fill khung kiểu "cover" nên vùng
@@ -22,13 +10,19 @@ export declare const computeCenterCrop: (dispW: any, dispH: any, aspect: any) =>
22
10
  height: any;
23
11
  };
24
12
  /**
25
- * Cắt ảnh chụp cho vừa đúng khung preview (theo tỉ lệ `aspect`). Làm việc hoàn
26
- * toàn trong hệ toạ độ ảnh đã dựng đứng (upright):
27
- * - Lấy kích thước hiển thị từ photo.orientation (không dùng Image.getSize
28
- * trả về khác nhau giữa iOS/Android tuỳ EXIF).
29
- * - Chèn action `resize` để "bung" EXIF orientation về upright: bắt buộc cho
30
- * iOS (thư viện crop trên buffer gốc chưa xoay), no-op trên Android
31
- * (Fresco đã tự xoay khi decode). Sau đó crop trong cùng hệ toạ độ upright.
13
+ * Cắt ảnh chụp cho vừa đúng khung preview (theo tỉ lệ `aspect`).
14
+ *
15
+ * Làm 2 bước để đúng trên CẢ iOS lẫn Android (không méo, không lệch):
16
+ *
17
+ * Bước 1 resize CHỈ theo `width` (không truyền height):
18
+ * - Thư viện tự tính height theo tỉ lệ THẬT của ảnh -> giữ nguyên tỉ lệ, KHÔNG
19
+ * kéo giãn (đây nguyên nhân méo trên Android khi trước đó ép cả W lẫn H).
20
+ * - Trên iOS bước này còn "dựng đứng" ảnh theo EXIF (thư viện crop chạy trên
21
+ * buffer gốc chưa xoay), Android/Fresco thì đã tự xoay khi decode.
22
+ * - Đọc kích thước THẬT (result.width/height) sau khi dựng để tính vùng crop.
23
+ *
24
+ * Bước 2 – crop chính giữa theo đúng tỉ lệ khung, dựa trên kích thước thật đó.
25
+ *
32
26
  * Trả về uri ảnh đã cắt; nếu lỗi thì trả ảnh gốc để vẫn dùng được.
33
27
  */
34
28
  export declare const cropPhotoToAspect: (photo: any, aspect: any) => Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"cameraCrop.d.ts","sourceRoot":"","sources":["../../../src/utils/cameraCrop.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;CAO1B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;CAkB7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,2CAgB7B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,0CAelC,CAAC"}
1
+ {"version":3,"file":"cameraCrop.d.ts","sourceRoot":"","sources":["../../../src/utils/cameraCrop.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;CAkB7B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,2CA0B7B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,0CAelC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-timacare",
3
- "version": "3.3.59-cccd",
3
+ "version": "3.3.61-cccd",
4
4
  "description": "SDK Tima Care",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
package/src/AppStore.tsx CHANGED
@@ -36,125 +36,6 @@ class AppStore {
36
36
  @observable listLoanTime = [];
37
37
  @observable listLoanTimeByType = [];
38
38
 
39
- @action
40
- async checkQualityImage(image, onSuccess?, onFailed?) {
41
- this.isLoading = true;
42
- try {
43
- const response = await axios.post(
44
- 'https://gateway.tima-ai.dev/computer-vision/image-quality-score/predict-score',
45
- {
46
- image: image,
47
- },
48
- {
49
- headers: {
50
- 'Authorization':
51
- 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhaV9zdXBlcl9hZG1pbl90ZXN0IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV8xNDE0X21lc3NhZ2VfdmFsaWRhdGUiLCJST0xFX2FtYXppbmdfY3Jhd2xlciIsIlJPTEVfYXBwcmFpc2FsX2F1dG9fY2hlY2siLCJST0xFX2F1dG9fbW9qX3NlY3VyZWRfdHJhbnNmZXIiLCJST0xFX2NlbnRlci1zZWN1cml0eSIsIlJPTEVfY2hhdC1nYXRld2F5IiwiUk9MRV9jaGVjay1sb2NhdGlvbiIsIlJPTEVfY2l2LXNlcnZpY2UtYXBwbGljYXRpb24iLCJST0xFX2NvbXB1dGVyX3Zpc2lvbiIsIlJPTEVfY29tcHV0ZXJfdmlzaW9uX3Rlc3QiLCJST0xFX2NvbnRyYWN0X2NoZWNrX2VkaXRlZCIsIlJPTEVfY3Jhd2wtc2VydmljZSIsIlJPTEVfY3JlZGl0LXNjb3JlLXNlcnZpY2UiLCJST0xFX2NyZWRpdC1zY29yaW5nLWJ5LXZlaGljbGUiLCJST0xFX2NyZWRpdC1zY29yaW5nLWJ5LXZlaGljbGUtbmV3LWxhYmVsIiwiUk9MRV9lcnAtc3VwcG9ydCIsIlJPTEVfZmFjZWJvb2staW5mb3JtYXRpb24iLCJST0xFX2ZiLWFjdGl2ZS1zY29yaW5nIiwiUk9MRV9mcmF1ZC1zY29yZS1hcGkiLCJST0xFX2ZyYXVkLXNlcnZpY2UiLCJST0xFX2lkZW50aWZ5LXNlcnZpY2UiLCJST0xFX2ltYWdlLXV0aWxzIiwiUk9MRV9sb2FuLWV2ZW50LWxvZyIsIlJPTEVfbW90b3ItcHJpY2UtcmVwb3J0IiwiUk9MRV9wcmVkaWN0aW5nLXZlaGljbGUtcHJpY2UiLCJST0xFX3ByaWNlLXByZWRpY3Rpb24tc2VydmljZSIsIlJPTEVfcHJpY2UtdmVoaWNsZS1kYXRhLWFwaSIsIlJPTEVfc2FudGltYS1sZWFkLXNjb3JpbmciLCJST0xFX3RheC1pbmZvLXNlcnZpY2UiLCJST0xFX3RleHQtZXh0cmFjdG9yIiwiUk9MRV90aW1hLWRhdGEtc2VydmljZSIsIlJPTEVfdG9wLWZyaWVuZHMtc2VydmljZSIsIlJPTEVfdHJhY2stbG9jYXRpb24iLCJST0xFX3VzZXItaW50ZXJlc3RzLXNlcnZpY2UiLCJST0xFX3Zpbi1wYXJzZXIiXSwiaWF0IjoxNjQ4MjYxNzcxLCJleHAiOjMxNTUyNDgyNjE3NzF9.O2Ka5i97gq9JH8sQwqHtmDuYZ7_ufd8KvcjgtLMAWckvzhrzM236aSrHa1FtFutSG1Ap2-9-FK0ggAeR4G8lBg',
52
- 'content-type': 'application/json',
53
- 'Access-Control-Allow-Headers':
54
- 'Origin, X-Requested-With, Content-Type, Accept',
55
- },
56
- timeout: 120000,
57
- }
58
- );
59
- console.log(response, 'check qualify anh');
60
-
61
- this.isLoading = false;
62
- if (response.status === 200) {
63
- if (response.data.Score < 3) {
64
- if (onFailed) onFailed();
65
- } else {
66
- if (onSuccess) onSuccess();
67
- }
68
- }
69
- } catch (error) {
70
- this.isLoading = false;
71
-
72
- console.log(error);
73
- AlertGlobal.show({
74
- type: 'warning',
75
- title: 'Thông báo',
76
- message: 'Có lỗi xảy ra. Vui lòng thử lại sau!',
77
- primaryText: 'Đóng',
78
- });
79
- }
80
- }
81
-
82
- @action
83
- async subscribeTopicFirebase(
84
- tokenFCM: string,
85
- topic: string,
86
- keyFirebase: string
87
- ) {
88
- return await axios
89
- .create({
90
- timeout: 50000,
91
- })
92
- .post(
93
- `https://iid.googleapis.com/iid/v1/${tokenFCM}/rel/topics/${topic}`,
94
- null,
95
- {
96
- headers: {
97
- Authorization: `key=${keyFirebase}`,
98
- },
99
- }
100
- );
101
- }
102
-
103
- @action
104
- async unSubscribeTopicFirebase(
105
- tokenFCM: string,
106
- topic: string,
107
- keyFirebase: string
108
- ) {
109
- return await axios
110
- .create({
111
- timeout: 50000,
112
- })
113
- .post(
114
- 'https://iid.googleapis.com/iid/v1:batchRemove',
115
- {
116
- to: `/topics/${topic}`,
117
- registration_tokens: [tokenFCM],
118
- },
119
- {
120
- headers: {
121
- Authorization: `key=${keyFirebase}`,
122
- },
123
- }
124
- );
125
- }
126
-
127
- @action
128
- async getTopicFirebaseSubscribed(tokenFCM: string, keyFirebase: string) {
129
- return await axios
130
- .create({
131
- timeout: 50000,
132
- })
133
- .post(
134
- `https://iid.googleapis.com/iid/info/${tokenFCM}?details=true`,
135
- null,
136
- {
137
- headers: {
138
- Authorization: `key=${keyFirebase}`,
139
- },
140
- }
141
- );
142
- }
143
-
144
- @action
145
- async getFCMTocken(onSuccess?) {
146
- try {
147
- const resp = await Api.getInstance().getFCMToken();
148
- if (resp.kind === 'ok') {
149
- if (resp.data.meta.errorCode === 200) {
150
- if (onSuccess) onSuccess(resp.data.data);
151
- } else {
152
- }
153
- } else {
154
- }
155
- } catch (error) {}
156
- }
157
-
158
39
  @action
159
40
  async getInfo() {
160
41
  try {
@@ -24,7 +24,6 @@ import { ESign } from '../screens/eSign';
24
24
  import { NationalIDBack } from '../screens/nationalIDBack';
25
25
  import { Contract } from '../screens/contract';
26
26
  import { Video } from '../screens/uploadVideo';
27
- import { Qualify } from '../screens/qualify';
28
27
  import { QuickSubmit } from '../screens/quick-submit';
29
28
  import { CommonSelect } from '../components/select';
30
29
  import { FullSubmit } from '../screens/full-submit';
@@ -100,7 +99,6 @@ export const ScreenNames = {
100
99
  NationalIDBack: 'NationalIDBack',
101
100
  Contract: 'Contract',
102
101
  Video: 'Video',
103
- Qualify: 'Qualify',
104
102
  TopUp: 'TopUp',
105
103
  QuickSubmit: 'QuickSubmit',
106
104
  Introduction: 'Introduction',
@@ -203,7 +201,6 @@ export function PrimaryNavigator(props) {
203
201
  <Stack.Screen name={ScreenNames.DetailLoan} component={DetailLoan} />
204
202
  <Stack.Screen name={ScreenNames.Contract} component={Contract} />
205
203
  <Stack.Screen name={ScreenNames.Video} component={Video} />
206
- <Stack.Screen name={ScreenNames.Qualify} component={Qualify} />
207
204
  <Stack.Screen name={ScreenNames.QuickSubmit} component={QuickSubmit} />
208
205
  <Stack.Screen name={ScreenNames.Introduction} component={Introduction} />
209
206
  <Stack.Screen name={ScreenNames.FullSubmit} component={FullSubmit} />
@@ -51,6 +51,7 @@ export default function CCCDCameraScreen(props) {
51
51
  const [passPermission, setPassPermission] = useState(false);
52
52
  const [shouldRenderCamera, setShouldRenderCamera] = useState(false);
53
53
  const [focusPoint, setFocusPoint] = useState(null);
54
+ const [taking, setTaking] = useState(false); // hiện loading, ẩn nút chụp khi đang xử lý
54
55
 
55
56
  // Chọn ống kính sau lấy nét gần nhất (macro) để chụp cận thẻ mà không mờ
56
57
  const devices = useCameraDevices();
@@ -80,6 +81,7 @@ export default function CCCDCameraScreen(props) {
80
81
  const takePicture = async () => {
81
82
  if (myCamera.current == null || takingRef.current) return;
82
83
  takingRef.current = true;
84
+ setTaking(true);
83
85
  try {
84
86
  // Lấy nét giữa khung trước khi chụp để tài liệu ở gần vẫn nét
85
87
  const { width: fw, height: fh } = frameSize.current;
@@ -100,6 +102,7 @@ export default function CCCDCameraScreen(props) {
100
102
  } catch (err) {
101
103
  console.error(err);
102
104
  takingRef.current = false;
105
+ setTaking(false);
103
106
  }
104
107
  };
105
108
 
@@ -273,12 +276,20 @@ export default function CCCDCameraScreen(props) {
273
276
  Đưa giấy tờ lại gần cho vừa khung, chạm vào màn hình{'\n'}để lấy nét nếu
274
277
  ảnh bị mờ.
275
278
  </MText>
276
- <TouchableOpacity
277
- style={[styles.captureButton, { marginBottom: insets.bottom + 24 }]}
278
- onPress={takePicture}
279
- >
280
- <TakePhotoSvg />
281
- </TouchableOpacity>
279
+ {taking ? (
280
+ <View
281
+ style={[styles.captureButton, { marginBottom: insets.bottom + 24 }]}
282
+ >
283
+ <ActivityIndicator size="large" color={ORANGE} />
284
+ </View>
285
+ ) : (
286
+ <TouchableOpacity
287
+ style={[styles.captureButton, { marginBottom: insets.bottom + 24 }]}
288
+ onPress={takePicture}
289
+ >
290
+ <TakePhotoSvg />
291
+ </TouchableOpacity>
292
+ )}
282
293
  </View>
283
294
  );
284
295
  }
@@ -7,7 +7,6 @@ import { commonStyles } from '../CommonStyles';
7
7
  import TabNavigator from '../../components/tabNavigator/TabNavigator';
8
8
  import { IconHome, IconUser } from '../../assets/icons';
9
9
  import { Home } from '../home';
10
- import { Personal } from '../personal';
11
10
  import appStore from '../../AppStore';
12
11
 
13
12
  export const Main = observer(function Main(props) {
@@ -14,6 +14,7 @@ import {
14
14
  ViewStyle,
15
15
  ImageStyle,
16
16
  Image,
17
+ ActivityIndicator,
17
18
  StyleSheet,
18
19
  Platform,
19
20
  } from 'react-native';
@@ -58,6 +59,9 @@ export const OCR = observer(function OCR(props: any) {
58
59
  const myCamera = useRef(null);
59
60
  const frameSize = useRef({ width: 0, height: 0 });
60
61
  const focusTimer = useRef(null);
62
+ // Cờ đồng bộ chặn bấm chụp nhiều lần (state `taking` cập nhật bất đồng bộ nên
63
+ // 2 lần bấm nhanh trong cùng 1 render vẫn lọt qua). Crop có delay -> cần ref.
64
+ const takingRef = useRef(false);
61
65
 
62
66
  // Chọn ống kính sau lấy nét gần nhất (macro) để chụp cận thẻ mà không mờ
63
67
  const devices = useCameraDevices();
@@ -213,7 +217,8 @@ export const OCR = observer(function OCR(props: any) {
213
217
  };
214
218
 
215
219
  const takePhoto = async () => {
216
- if (myCamera.current == null || taking) return;
220
+ if (myCamera.current == null || takingRef.current) return;
221
+ takingRef.current = true;
217
222
  setTaking(true);
218
223
  try {
219
224
  // Lấy nét giữa khung trước khi chụp để tài liệu ở gần vẫn nét
@@ -228,10 +233,12 @@ export const OCR = observer(function OCR(props: any) {
228
233
  });
229
234
  // Cắt ảnh vừa đúng khung theo tỉ lệ thẻ (xử lý orientation trong util)
230
235
  const uri = await cropPhotoToAspect(data, CARD_ASPECT_RATIO);
236
+ takingRef.current = false;
231
237
  setTaking(false);
232
238
  goNextWithUri(uri);
233
239
  } catch (e) {
234
240
  console.error(e);
241
+ takingRef.current = false;
235
242
  setTaking(false);
236
243
  AlertGlobal.show({
237
244
  type: 'warning',
@@ -243,6 +250,7 @@ export const OCR = observer(function OCR(props: any) {
243
250
  };
244
251
 
245
252
  const pickImageFromGallery = async () => {
253
+ if (takingRef.current) return; // đang chụp/xử lý -> chặn để không điều hướng 2 lần
246
254
  if (Platform.OS === 'android') {
247
255
  const result = await launchImageLibrary({ mediaType: 'photo' });
248
256
  if (result.assets) {
@@ -316,6 +324,7 @@ export const OCR = observer(function OCR(props: any) {
316
324
  <TouchableOpacity
317
325
  style={styles.galleryButton}
318
326
  onPress={pickImageFromGallery}
327
+ disabled={taking}
319
328
  >
320
329
  <IconUpload />
321
330
  <MText style={styles.galleryText}>Tải ảnh từ thư viện</MText>
@@ -326,13 +335,26 @@ export const OCR = observer(function OCR(props: any) {
326
335
  Đưa giấy tờ lại gần cho vừa khung, chạm vào màn hình{'\n'}để lấy nét
327
336
  nếu ảnh bị mờ.
328
337
  </MText>
329
- <TouchableOpacity
330
- style={[styles.captureButton, { marginBottom: insets.bottom + 24 }]}
331
- onPress={takePhoto}
332
- disabled={taking}
333
- >
334
- <TakePhotoSvg />
335
- </TouchableOpacity>
338
+ {taking ? (
339
+ <View
340
+ style={[
341
+ styles.captureButton,
342
+ { marginBottom: insets.bottom + 24 },
343
+ ]}
344
+ >
345
+ <ActivityIndicator size="large" color={ORANGE} />
346
+ </View>
347
+ ) : (
348
+ <TouchableOpacity
349
+ style={[
350
+ styles.captureButton,
351
+ { marginBottom: insets.bottom + 24 },
352
+ ]}
353
+ onPress={takePhoto}
354
+ >
355
+ <TakePhotoSvg />
356
+ </TouchableOpacity>
357
+ )}
336
358
  </>
337
359
  ) : null}
338
360
 
@@ -7,6 +7,7 @@ import React, {
7
7
  useState,
8
8
  } from 'react';
9
9
  import {
10
+ ActivityIndicator,
10
11
  Image,
11
12
  ImageStyle,
12
13
  Platform,
@@ -69,6 +70,9 @@ export default function VehicleCamera(props: any) {
69
70
  const myCamera = useRef(null);
70
71
  const frameSize = useRef({ width: 0, height: 0 });
71
72
  const focusTimer = useRef(null);
73
+ // Cờ đồng bộ chặn bấm chụp nhiều lần (state `taking` cập nhật bất đồng bộ nên
74
+ // 2 lần bấm nhanh trong cùng 1 render vẫn lọt qua). Crop có delay -> cần ref.
75
+ const takingRef = useRef(false);
72
76
 
73
77
  // Chọn ống kính sau lấy nét gần nhất (macro) để chụp cận thẻ mà không mờ
74
78
  const devices = useCameraDevices();
@@ -113,7 +117,8 @@ export default function VehicleCamera(props: any) {
113
117
  };
114
118
 
115
119
  const takePhoto = async () => {
116
- if (myCamera.current == null || taking) return;
120
+ if (myCamera.current == null || takingRef.current) return;
121
+ takingRef.current = true;
117
122
  setTaking(true);
118
123
  try {
119
124
  // Lấy nét giữa khung trước khi chụp để tài liệu ở gần vẫn nét
@@ -128,10 +133,12 @@ export default function VehicleCamera(props: any) {
128
133
  });
129
134
  // Cắt ảnh vừa đúng khung theo tỉ lệ thẻ (xử lý orientation trong util)
130
135
  const uri = await cropPhotoToAspect(data, CARD_ASPECT_RATIO);
136
+ takingRef.current = false;
131
137
  setTaking(false);
132
138
  goNextWithUri(uri);
133
139
  } catch (e) {
134
140
  console.error(e);
141
+ takingRef.current = false;
135
142
  setTaking(false);
136
143
  AlertGlobal.show({
137
144
  type: 'warning',
@@ -329,13 +336,26 @@ export default function VehicleCamera(props: any) {
329
336
  Đưa giấy tờ lại gần cho vừa khung, chạm vào màn hình{'\n'}để lấy nét
330
337
  nếu ảnh bị mờ.
331
338
  </MText>
332
- <TouchableOpacity
333
- style={[styles.captureButton, { marginBottom: insets.bottom + 24 }]}
334
- onPress={takePhoto}
335
- disabled={taking}
336
- >
337
- <TakePhotoSvg />
338
- </TouchableOpacity>
339
+ {taking ? (
340
+ <View
341
+ style={[
342
+ styles.captureButton,
343
+ { marginBottom: insets.bottom + 24 },
344
+ ]}
345
+ >
346
+ <ActivityIndicator size="large" color={ORANGE} />
347
+ </View>
348
+ ) : (
349
+ <TouchableOpacity
350
+ style={[
351
+ styles.captureButton,
352
+ { marginBottom: insets.bottom + 24 },
353
+ ]}
354
+ onPress={takePhoto}
355
+ >
356
+ <TakePhotoSvg />
357
+ </TouchableOpacity>
358
+ )}
339
359
  </>
340
360
  ) : null}
341
361
 
@@ -1,23 +1,6 @@
1
1
  // @ts-nocheck
2
2
  import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator';
3
3
 
4
- /**
5
- * Kích thước hiển thị (upright) của ảnh chụp từ VisionCamera.
6
- *
7
- * photo.width/height là kích thước buffer gốc của cảm biến (luôn landscape).
8
- * photo.orientation cho biết ảnh cần xoay bao nhiêu để hiển thị đúng chiều:
9
- * khi là 'landscape-left'/'landscape-right' (điện thoại cầm dọc) thì ảnh hiển
10
- * thị bị xoay 90° so với buffer -> phải hoán đổi W/H.
11
- */
12
- export const getDisplaySize = (photo) => {
13
- const needsSwap =
14
- photo?.orientation === 'landscape-left' ||
15
- photo?.orientation === 'landscape-right';
16
- return needsSwap
17
- ? { width: photo.height, height: photo.width }
18
- : { width: photo.width, height: photo.height };
19
- };
20
-
21
4
  /**
22
5
  * Vùng cắt chính giữa theo đúng tỉ lệ khung (aspect = W/H), tính trong hệ toạ
23
6
  * độ ảnh đã dựng đứng (upright). VisionCamera fill khung kiểu "cover" nên vùng
@@ -44,25 +27,41 @@ export const computeCenterCrop = (dispW, dispH, aspect) => {
44
27
  };
45
28
 
46
29
  /**
47
- * Cắt ảnh chụp cho vừa đúng khung preview (theo tỉ lệ `aspect`). Làm việc hoàn
48
- * toàn trong hệ toạ độ ảnh đã dựng đứng (upright):
49
- * - Lấy kích thước hiển thị từ photo.orientation (không dùng Image.getSize
50
- * trả về khác nhau giữa iOS/Android tuỳ EXIF).
51
- * - Chèn action `resize` để "bung" EXIF orientation về upright: bắt buộc cho
52
- * iOS (thư viện crop trên buffer gốc chưa xoay), no-op trên Android
53
- * (Fresco đã tự xoay khi decode). Sau đó crop trong cùng hệ toạ độ upright.
30
+ * Cắt ảnh chụp cho vừa đúng khung preview (theo tỉ lệ `aspect`).
31
+ *
32
+ * Làm 2 bước để đúng trên CẢ iOS lẫn Android (không méo, không lệch):
33
+ *
34
+ * Bước 1 resize CHỈ theo `width` (không truyền height):
35
+ * - Thư viện tự tính height theo tỉ lệ THẬT của ảnh -> giữ nguyên tỉ lệ, KHÔNG
36
+ * kéo giãn (đây nguyên nhân méo trên Android khi trước đó ép cả W lẫn H).
37
+ * - Trên iOS bước này còn "dựng đứng" ảnh theo EXIF (thư viện crop chạy trên
38
+ * buffer gốc chưa xoay), Android/Fresco thì đã tự xoay khi decode.
39
+ * - Đọc kích thước THẬT (result.width/height) sau khi dựng để tính vùng crop.
40
+ *
41
+ * Bước 2 – crop chính giữa theo đúng tỉ lệ khung, dựa trên kích thước thật đó.
42
+ *
54
43
  * Trả về uri ảnh đã cắt; nếu lỗi thì trả ảnh gốc để vẫn dùng được.
55
44
  */
56
45
  export const cropPhotoToAspect = async (photo, aspect) => {
57
46
  const uri = 'file://' + photo.path;
58
- const { width: dispW, height: dispH } = getDisplaySize(photo);
59
- if (!dispW || !dispH) return uri;
60
- const crop = computeCenterCrop(dispW, dispH, aspect);
61
47
  try {
62
- const result = await RNImageManipulator.manipulate(
48
+ // Dùng cạnh dài của buffer làm width mục tiêu để không giảm độ phân giải chi
49
+ // tiết; resize giữ nguyên tỉ lệ nên không méo.
50
+ const targetWidth = Math.max(photo?.width || 0, photo?.height || 0) || 1080;
51
+ const normalized = await RNImageManipulator.manipulate(
63
52
  uri,
64
- [{ resize: { width: dispW, height: dispH } }, { crop }],
65
- { compress: 0.9, format: 'jpeg' }
53
+ [{ resize: { width: targetWidth } }],
54
+ { compress: 1, format: 'jpeg' }
55
+ );
56
+ const dispW = normalized.width;
57
+ const dispH = normalized.height;
58
+ if (!dispW || !dispH) return normalized.uri;
59
+
60
+ const crop = computeCenterCrop(dispW, dispH, aspect);
61
+ const result = await RNImageManipulator.manipulate(
62
+ normalized.uri,
63
+ [{ crop }],
64
+ { compress: 1, format: 'jpeg' }
66
65
  );
67
66
  return result.uri;
68
67
  } catch (e) {
@@ -1,2 +0,0 @@
1
- var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.Personal=void 0;var _regenerator=_interopRequireDefault(require("@babel/runtime/regenerator"));var _native=require("@react-navigation/native");var _mobxReactLite=require("mobx-react-lite");var _react=_interopRequireDefault(require("react"));var _reactNative=require("react-native");var _icons=require("../../assets/icons");var _AlertGlobal=require("../../components/ModalNotification/AlertGlobal");var _AppStore=_interopRequireDefault(require("../../AppStore"));var _MText=require("../../components/MText");var _navigation=require("../../navigation");var _api=require("../../services/api");var _CommonStyles=require("../CommonStyles");var _mobx=require("mobx");var _storage=require("../../utils/storage");var _keychain=require("../../utils/keychain");var _jsxFileName="/Users/tima/Tima/react-native-timacare/src/screens/personal/index.tsx";var logoView={position:'absolute',width:'100%',height:'100%',resizeMode:'stretch'};var Personal=exports.Personal=(0,_mobxReactLite.observer)(function Personal(){var _toJS;var navigation=(0,_native.useNavigation)();var logout=function _callee(){return _regenerator.default.async(function(_context2){while(1)switch(_context2.prev=_context2.next){case 0:_AlertGlobal.AlertGlobal.show({type:'confirm',title:'Thông báo',message:'Bạn có chắc chắn muốn đăng xuất?',primaryText:'Đồng ý',onPrimaryPress:function onPrimaryPress(){return _regenerator.default.async(function(_context){while(1)switch(_context.prev=_context.next){case 0:_AppStore.default.logout();(0,_storage.clear)();(0,_keychain.reset)();_AppStore.default.selectedTab='TAB1';_api.Api.getInstance().changeAuth();navigation.dispatch(_native.CommonActions.reset({index:1,routes:[{name:_navigation.ScreenNames.Splash}]}));case 1:case"end":return _context.stop();}},null,null,null,Promise);},secondaryText:'Bỏ qua',onSecondaryPress:function onSecondaryPress(value){}});case 1:case"end":return _context2.stop();}},null,null,null,Promise);};return _react.default.createElement(_reactNative.View,{style:[_CommonStyles.commonStyles.fill,{backgroundColor:'white'}],__self:this,__source:{fileName:_jsxFileName,lineNumber:57,columnNumber:5}},_react.default.createElement(_reactNative.Image,{source:require('./bg.png'),style:logoView,__self:this,__source:{fileName:_jsxFileName,lineNumber:58,columnNumber:7}}),_react.default.createElement(_reactNative.SafeAreaView,{style:{flex:1},__self:this,__source:{fileName:_jsxFileName,lineNumber:59,columnNumber:7}},_react.default.createElement(_reactNative.View,{style:{alignItems:'center',marginTop:16},__self:this,__source:{fileName:_jsxFileName,lineNumber:60,columnNumber:9}},_react.default.createElement(_MText.MText,{style:[_CommonStyles.commonStyles.textNormalBold,{fontSize:16}],__self:this,__source:{fileName:_jsxFileName,lineNumber:61,columnNumber:11}},"Th\xF4ng tin t\xE0i kho\u1EA3n")),_react.default.createElement(_reactNative.ScrollView,{style:{flex:1,margin:16},__self:this,__source:{fileName:_jsxFileName,lineNumber:65,columnNumber:9}},_react.default.createElement(_reactNative.View,{style:{flexDirection:'row',marginBottom:16,alignItems:'center',borderRadius:8,borderWidth:1,padding:10,borderColor:'#E0E0E0'},__self:this,__source:{fileName:_jsxFileName,lineNumber:66,columnNumber:11}},_react.default.createElement(_icons.IconProfile,{__self:this,__source:{fileName:_jsxFileName,lineNumber:77,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:{marginLeft:16},__self:this,__source:{fileName:_jsxFileName,lineNumber:78,columnNumber:13}},_react.default.createElement(_MText.MText,{style:_CommonStyles.commonStyles.textNormalBold,__self:this,__source:{fileName:_jsxFileName,lineNumber:79,columnNumber:15}},(_toJS=(0,_mobx.toJS)(_AppStore.default.info))==null?void 0:_toJS.fullName),_react.default.createElement(_MText.MText,{style:[_CommonStyles.commonStyles.textNormal,{fontSize:12}],__self:this,__source:{fileName:_jsxFileName,lineNumber:82,columnNumber:15}},_AppStore.default.phone))),_react.default.createElement(_reactNative.TouchableOpacity,{onPress:function onPress(){return logout();},style:{flexDirection:'row',alignItems:'center',borderRadius:8,borderWidth:1,padding:10,borderColor:'#E0E0E0'},__self:this,__source:{fileName:_jsxFileName,lineNumber:87,columnNumber:11}},_react.default.createElement(_icons.IconLogout,{width:20,height:20,fill:'#333333',__self:this,__source:{fileName:_jsxFileName,lineNumber:98,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:{marginLeft:10},__self:this,__source:{fileName:_jsxFileName,lineNumber:99,columnNumber:13}},_react.default.createElement(_MText.MText,{style:[_CommonStyles.commonStyles.textNormalBold,{fontSize:16}],__self:this,__source:{fileName:_jsxFileName,lineNumber:100,columnNumber:15}},"\u0110\u0103ng xu\u1EA5t"),_react.default.createElement(_MText.MText,{style:[_CommonStyles.commonStyles.textNormal,{fontWeight:'300'}],__self:this,__source:{fileName:_jsxFileName,lineNumber:103,columnNumber:15}},"B\u1EA1n s\u1EBD c\u1EA7n \u0111\u0103ng nh\u1EADp l\u1EA1i n\u1EBFu mu\u1ED1n s\u1EED d\u1EE5ng ti\u1EBFp"))))));});
2
- //# sourceMappingURL=index.js.map
@@ -1,112 +0,0 @@
1
- //@ts-nocheck
2
- import { CommonActions, useNavigation } from '@react-navigation/native';
3
- import { observer } from 'mobx-react-lite';
4
- import React from 'react';
5
- import {
6
- Image,
7
- ImageStyle,
8
- SafeAreaView,
9
- ScrollView,
10
- TouchableOpacity,
11
- View,
12
- } from 'react-native';
13
- import { IconLogout, IconProfile } from '../../assets/icons';
14
- import { AlertGlobal } from '../../components/ModalNotification/AlertGlobal';
15
- import appStore from '../../AppStore';
16
- import { MText } from '../../components/MText';
17
- import { ScreenNames } from '../../navigation';
18
- import { Api } from '../../services/api';
19
- import { commonStyles } from '../CommonStyles';
20
- import { toJS } from 'mobx';
21
- import { clear } from '../../utils/storage';
22
- import { reset } from '../../utils/keychain';
23
-
24
- const logoView: ImageStyle = {
25
- position: 'absolute',
26
- width: '100%',
27
- height: '100%',
28
- resizeMode: 'stretch',
29
- };
30
- export const Personal = observer(function Personal() {
31
- const navigation = useNavigation();
32
-
33
- const logout = async () => {
34
- AlertGlobal.show({
35
- type: 'confirm',
36
- title: 'Thông báo',
37
- message: 'Bạn có chắc chắn muốn đăng xuất?',
38
- primaryText: 'Đồng ý',
39
- onPrimaryPress: async () => {
40
- appStore.logout();
41
- clear();
42
- reset();
43
- appStore.selectedTab = 'TAB1';
44
- Api.getInstance().changeAuth();
45
- navigation.dispatch(
46
- CommonActions.reset({
47
- index: 1,
48
- routes: [{ name: ScreenNames.Splash }],
49
- })
50
- );
51
- },
52
- secondaryText: 'Bỏ qua',
53
- onSecondaryPress: (value) => {},
54
- });
55
- };
56
- return (
57
- <View style={[commonStyles.fill, { backgroundColor: 'white' }]}>
58
- <Image source={require('./bg.png')} style={logoView} />
59
- <SafeAreaView style={{ flex: 1 }}>
60
- <View style={{ alignItems: 'center', marginTop: 16 }}>
61
- <MText style={[commonStyles.textNormalBold, { fontSize: 16 }]}>
62
- Thông tin tài khoản
63
- </MText>
64
- </View>
65
- <ScrollView style={{ flex: 1, margin: 16 }}>
66
- <View
67
- style={{
68
- flexDirection: 'row',
69
- marginBottom: 16,
70
- alignItems: 'center',
71
- borderRadius: 8,
72
- borderWidth: 1,
73
- padding: 10,
74
- borderColor: '#E0E0E0',
75
- }}
76
- >
77
- <IconProfile />
78
- <View style={{ marginLeft: 16 }}>
79
- <MText style={commonStyles.textNormalBold}>
80
- {toJS(appStore.info)?.fullName}
81
- </MText>
82
- <MText style={[commonStyles.textNormal, { fontSize: 12 }]}>
83
- {appStore.phone}
84
- </MText>
85
- </View>
86
- </View>
87
- <TouchableOpacity
88
- onPress={() => logout()}
89
- style={{
90
- flexDirection: 'row',
91
- alignItems: 'center',
92
- borderRadius: 8,
93
- borderWidth: 1,
94
- padding: 10,
95
- borderColor: '#E0E0E0',
96
- }}
97
- >
98
- <IconLogout width={20} height={20} fill={'#333333'} />
99
- <View style={{ marginLeft: 10 }}>
100
- <MText style={[commonStyles.textNormalBold, { fontSize: 16 }]}>
101
- Đăng xuất
102
- </MText>
103
- <MText style={[commonStyles.textNormal, { fontWeight: '300' }]}>
104
- Bạn sẽ cần đăng nhập lại nếu muốn sử dụng tiếp
105
- </MText>
106
- </View>
107
- </TouchableOpacity>
108
- </ScrollView>
109
- </SafeAreaView>
110
- </View>
111
- );
112
- });