react-native-timacare 3.3.56-alert → 3.3.58-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 (211) hide show
  1. package/lib/commonjs/assets/icons/index.js +1 -1
  2. package/lib/commonjs/assets/icons/index.js.flow +2 -0
  3. package/lib/commonjs/assets/icons/index.js.map +1 -1
  4. package/lib/commonjs/assets/icons/modal_confirm.svg +5 -5
  5. package/lib/commonjs/assets/icons/modal_notice.svg +32 -32
  6. package/lib/commonjs/assets/icons/modal_success.svg +26 -26
  7. package/lib/commonjs/assets/icons/modal_warning.svg +21 -21
  8. package/lib/commonjs/assets/icons/user_security.svg +10 -0
  9. package/lib/commonjs/assets/tts/Frame1000011947.png +0 -0
  10. package/lib/commonjs/components/AlertLoan.js +1 -1
  11. package/lib/commonjs/components/AlertLoan.js.flow +1 -1
  12. package/lib/commonjs/components/AlertLoan.js.map +1 -1
  13. package/lib/commonjs/components/AppModal.js +1 -1
  14. package/lib/commonjs/components/AppModal.js.flow +40 -2
  15. package/lib/commonjs/components/AppModal.js.map +1 -1
  16. package/lib/commonjs/components/ItemTimaOne.js +1 -1
  17. package/lib/commonjs/components/ItemTimaOne.js.flow +1 -1
  18. package/lib/commonjs/components/ItemTimaOne.js.map +1 -1
  19. package/lib/commonjs/components/ModalNotification/AlertGlobal.js +1 -1
  20. package/lib/commonjs/components/ModalNotification/AlertGlobal.js.flow +92 -33
  21. package/lib/commonjs/components/ModalNotification/AlertGlobal.js.map +1 -1
  22. package/lib/commonjs/components/ModalNotification/index.js +1 -1
  23. package/lib/commonjs/components/ModalNotification/index.js.flow +7 -0
  24. package/lib/commonjs/components/ModalNotification/index.js.map +1 -1
  25. package/lib/commonjs/components/ModalOptionLoan.js +1 -1
  26. package/lib/commonjs/components/ModalOptionLoan.js.flow +1 -1
  27. package/lib/commonjs/components/ModalOptionLoan.js.map +1 -1
  28. package/lib/commonjs/components/ModalOptionLoanDigital.js +1 -1
  29. package/lib/commonjs/components/ModalOptionLoanDigital.js.flow +1 -1
  30. package/lib/commonjs/components/ModalOptionLoanDigital.js.map +1 -1
  31. package/lib/commonjs/components/modalStack.js +2 -0
  32. package/lib/commonjs/components/modalStack.js.flow +47 -0
  33. package/lib/commonjs/components/modalStack.js.map +1 -0
  34. package/lib/commonjs/navigation/primary-navigator.js +1 -1
  35. package/lib/commonjs/navigation/primary-navigator.js.flow +7 -0
  36. package/lib/commonjs/navigation/primary-navigator.js.map +1 -1
  37. package/lib/commonjs/screens/NFC/OcrStart.js +2 -0
  38. package/lib/commonjs/screens/NFC/OcrStart.js.flow +171 -0
  39. package/lib/commonjs/screens/NFC/OcrStart.js.map +1 -0
  40. package/lib/commonjs/screens/NFC/ScanMRZ.js +2 -0
  41. package/lib/commonjs/screens/NFC/ScanMRZ.js.flow +563 -0
  42. package/lib/commonjs/screens/NFC/ScanMRZ.js.map +1 -0
  43. package/lib/commonjs/screens/camera/CCCDCameraScreen.js +1 -1
  44. package/lib/commonjs/screens/camera/CCCDCameraScreen.js.flow +203 -133
  45. package/lib/commonjs/screens/camera/CCCDCameraScreen.js.map +1 -1
  46. package/lib/commonjs/screens/camera/index.js +1 -1
  47. package/lib/commonjs/screens/camera/index.js.flow +1 -1
  48. package/lib/commonjs/screens/camera/index.js.map +1 -1
  49. package/lib/commonjs/screens/detail-loan/PaymentInfo.js +1 -1
  50. package/lib/commonjs/screens/detail-loan/PaymentInfo.js.flow +1 -1
  51. package/lib/commonjs/screens/detail-loan/PaymentInfo.js.map +1 -1
  52. package/lib/commonjs/screens/detail-loan/ShowQrCode.js +1 -1
  53. package/lib/commonjs/screens/detail-loan/ShowQrCode.js.flow +1 -1
  54. package/lib/commonjs/screens/detail-loan/ShowQrCode.js.map +1 -1
  55. package/lib/commonjs/screens/digital-lending/Step6.js +1 -1
  56. package/lib/commonjs/screens/digital-lending/Step6.js.flow +1 -1
  57. package/lib/commonjs/screens/digital-lending/Step6.js.map +1 -1
  58. package/lib/commonjs/screens/eSign/index.js +1 -1
  59. package/lib/commonjs/screens/eSign/index.js.flow +1 -1
  60. package/lib/commonjs/screens/eSign/index.js.map +1 -1
  61. package/lib/commonjs/screens/home/DigitalTopupItem.js +1 -1
  62. package/lib/commonjs/screens/home/DigitalTopupItem.js.flow +1 -1
  63. package/lib/commonjs/screens/home/DigitalTopupItem.js.map +1 -1
  64. package/lib/commonjs/screens/home/ItemMotoBikeLoan.js +1 -1
  65. package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.flow +1 -1
  66. package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.map +1 -1
  67. package/lib/commonjs/screens/home/index.js +1 -1
  68. package/lib/commonjs/screens/home/index.js.flow +8 -113
  69. package/lib/commonjs/screens/home/index.js.map +1 -1
  70. package/lib/commonjs/screens/nationalID/index.js +1 -1
  71. package/lib/commonjs/screens/nationalID/index.js.flow +123 -218
  72. package/lib/commonjs/screens/nationalID/index.js.map +1 -1
  73. package/lib/commonjs/screens/nationalIDBack/index.js +1 -1
  74. package/lib/commonjs/screens/nationalIDBack/index.js.flow +129 -209
  75. package/lib/commonjs/screens/nationalIDBack/index.js.map +1 -1
  76. package/lib/commonjs/screens/payment-evidence/ModalGuideline.js +1 -1
  77. package/lib/commonjs/screens/payment-evidence/ModalGuideline.js.flow +1 -1
  78. package/lib/commonjs/screens/payment-evidence/ModalGuideline.js.map +1 -1
  79. package/lib/commonjs/screens/toan-trinh-so/OCR.js +1 -1
  80. package/lib/commonjs/screens/toan-trinh-so/OCR.js.flow +270 -556
  81. package/lib/commonjs/screens/toan-trinh-so/OCR.js.map +1 -1
  82. package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js +1 -1
  83. package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js.flow +1 -1
  84. package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js.map +1 -1
  85. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js +1 -1
  86. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.flow +226 -288
  87. package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
  88. package/lib/commonjs/utils/cameraCrop.js +2 -0
  89. package/lib/commonjs/utils/cameraCrop.js.flow +96 -0
  90. package/lib/commonjs/utils/cameraCrop.js.map +1 -0
  91. package/lib/module/assets/icons/index.js +1 -1
  92. package/lib/module/assets/icons/index.js.map +1 -1
  93. package/lib/module/assets/icons/modal_confirm.svg +5 -5
  94. package/lib/module/assets/icons/modal_notice.svg +32 -32
  95. package/lib/module/assets/icons/modal_success.svg +26 -26
  96. package/lib/module/assets/icons/modal_warning.svg +21 -21
  97. package/lib/module/assets/icons/user_security.svg +10 -0
  98. package/lib/module/assets/tts/Frame1000011947.png +0 -0
  99. package/lib/module/components/AlertLoan.js +1 -1
  100. package/lib/module/components/AlertLoan.js.map +1 -1
  101. package/lib/module/components/AppModal.js +1 -1
  102. package/lib/module/components/AppModal.js.map +1 -1
  103. package/lib/module/components/ItemTimaOne.js +1 -1
  104. package/lib/module/components/ItemTimaOne.js.map +1 -1
  105. package/lib/module/components/ModalNotification/AlertGlobal.js +1 -1
  106. package/lib/module/components/ModalNotification/AlertGlobal.js.map +1 -1
  107. package/lib/module/components/ModalNotification/index.js +1 -1
  108. package/lib/module/components/ModalNotification/index.js.map +1 -1
  109. package/lib/module/components/ModalOptionLoan.js +1 -1
  110. package/lib/module/components/ModalOptionLoan.js.map +1 -1
  111. package/lib/module/components/ModalOptionLoanDigital.js +1 -1
  112. package/lib/module/components/ModalOptionLoanDigital.js.map +1 -1
  113. package/lib/module/components/modalStack.js +2 -0
  114. package/lib/module/components/modalStack.js.map +1 -0
  115. package/lib/module/navigation/primary-navigator.js +1 -1
  116. package/lib/module/navigation/primary-navigator.js.map +1 -1
  117. package/lib/module/screens/NFC/OcrStart.js +2 -0
  118. package/lib/module/screens/NFC/OcrStart.js.map +1 -0
  119. package/lib/module/screens/NFC/ScanMRZ.js +2 -0
  120. package/lib/module/screens/NFC/ScanMRZ.js.map +1 -0
  121. package/lib/module/screens/camera/CCCDCameraScreen.js +1 -1
  122. package/lib/module/screens/camera/CCCDCameraScreen.js.map +1 -1
  123. package/lib/module/screens/camera/index.js +1 -1
  124. package/lib/module/screens/camera/index.js.map +1 -1
  125. package/lib/module/screens/detail-loan/PaymentInfo.js +1 -1
  126. package/lib/module/screens/detail-loan/PaymentInfo.js.map +1 -1
  127. package/lib/module/screens/detail-loan/ShowQrCode.js +1 -1
  128. package/lib/module/screens/detail-loan/ShowQrCode.js.map +1 -1
  129. package/lib/module/screens/digital-lending/Step6.js +1 -1
  130. package/lib/module/screens/digital-lending/Step6.js.map +1 -1
  131. package/lib/module/screens/eSign/index.js +1 -1
  132. package/lib/module/screens/eSign/index.js.map +1 -1
  133. package/lib/module/screens/home/DigitalTopupItem.js +1 -1
  134. package/lib/module/screens/home/DigitalTopupItem.js.map +1 -1
  135. package/lib/module/screens/home/ItemMotoBikeLoan.js +1 -1
  136. package/lib/module/screens/home/ItemMotoBikeLoan.js.map +1 -1
  137. package/lib/module/screens/home/index.js +1 -1
  138. package/lib/module/screens/home/index.js.map +1 -1
  139. package/lib/module/screens/nationalID/index.js +1 -1
  140. package/lib/module/screens/nationalID/index.js.map +1 -1
  141. package/lib/module/screens/nationalIDBack/index.js +1 -1
  142. package/lib/module/screens/nationalIDBack/index.js.map +1 -1
  143. package/lib/module/screens/payment-evidence/ModalGuideline.js +1 -1
  144. package/lib/module/screens/payment-evidence/ModalGuideline.js.map +1 -1
  145. package/lib/module/screens/toan-trinh-so/OCR.js +1 -1
  146. package/lib/module/screens/toan-trinh-so/OCR.js.map +1 -1
  147. package/lib/module/screens/toan-trinh-so/RegisterCamera.js +1 -1
  148. package/lib/module/screens/toan-trinh-so/RegisterCamera.js.map +1 -1
  149. package/lib/module/screens/toan-trinh-so/VehicleCamera.js +1 -1
  150. package/lib/module/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
  151. package/lib/module/utils/cameraCrop.js +2 -0
  152. package/lib/module/utils/cameraCrop.js.map +1 -0
  153. package/lib/typescript/assets/icons/index.d.ts +2 -1
  154. package/lib/typescript/assets/icons/index.d.ts.map +1 -1
  155. package/lib/typescript/components/AppModal.d.ts +6 -1
  156. package/lib/typescript/components/AppModal.d.ts.map +1 -1
  157. package/lib/typescript/components/ModalNotification/AlertGlobal.d.ts +20 -4
  158. package/lib/typescript/components/ModalNotification/AlertGlobal.d.ts.map +1 -1
  159. package/lib/typescript/components/ModalNotification/index.d.ts +6 -1
  160. package/lib/typescript/components/ModalNotification/index.d.ts.map +1 -1
  161. package/lib/typescript/components/modalStack.d.ts +11 -0
  162. package/lib/typescript/components/modalStack.d.ts.map +1 -0
  163. package/lib/typescript/navigation/primary-navigator.d.ts +3 -0
  164. package/lib/typescript/navigation/primary-navigator.d.ts.map +1 -1
  165. package/lib/typescript/screens/NFC/OcrStart.d.ts +2 -0
  166. package/lib/typescript/screens/NFC/OcrStart.d.ts.map +1 -0
  167. package/lib/typescript/screens/NFC/ScanMRZ.d.ts +15 -0
  168. package/lib/typescript/screens/NFC/ScanMRZ.d.ts.map +1 -0
  169. package/lib/typescript/screens/camera/CCCDCameraScreen.d.ts.map +1 -1
  170. package/lib/typescript/screens/home/index.d.ts.map +1 -1
  171. package/lib/typescript/screens/nationalID/index.d.ts.map +1 -1
  172. package/lib/typescript/screens/nationalIDBack/index.d.ts.map +1 -1
  173. package/lib/typescript/screens/toan-trinh-so/OCR.d.ts.map +1 -1
  174. package/lib/typescript/screens/toan-trinh-so/VehicleCamera.d.ts.map +1 -1
  175. package/lib/typescript/utils/cameraCrop.d.ts +43 -0
  176. package/lib/typescript/utils/cameraCrop.d.ts.map +1 -0
  177. package/package.json +2 -1
  178. package/src/assets/icons/index.tsx +2 -0
  179. package/src/assets/icons/modal_confirm.svg +5 -5
  180. package/src/assets/icons/modal_notice.svg +32 -32
  181. package/src/assets/icons/modal_success.svg +26 -26
  182. package/src/assets/icons/modal_warning.svg +21 -21
  183. package/src/assets/icons/user_security.svg +10 -0
  184. package/src/assets/tts/Frame1000011947.png +0 -0
  185. package/src/components/AlertLoan.tsx +1 -1
  186. package/src/components/AppModal.tsx +40 -2
  187. package/src/components/ItemTimaOne.tsx +1 -1
  188. package/src/components/ModalNotification/AlertGlobal.tsx +92 -33
  189. package/src/components/ModalNotification/index.tsx +7 -0
  190. package/src/components/ModalOptionLoan.tsx +1 -1
  191. package/src/components/ModalOptionLoanDigital.tsx +1 -1
  192. package/src/components/modalStack.ts +47 -0
  193. package/src/navigation/primary-navigator.tsx +7 -0
  194. package/src/screens/NFC/OcrStart.tsx +171 -0
  195. package/src/screens/NFC/ScanMRZ.tsx +563 -0
  196. package/src/screens/camera/CCCDCameraScreen.tsx +203 -133
  197. package/src/screens/camera/index.tsx +1 -1
  198. package/src/screens/detail-loan/PaymentInfo.tsx +1 -1
  199. package/src/screens/detail-loan/ShowQrCode.tsx +1 -1
  200. package/src/screens/digital-lending/Step6.tsx +1 -1
  201. package/src/screens/eSign/index.tsx +1 -1
  202. package/src/screens/home/DigitalTopupItem.tsx +1 -1
  203. package/src/screens/home/ItemMotoBikeLoan.tsx +1 -1
  204. package/src/screens/home/index.tsx +8 -113
  205. package/src/screens/nationalID/index.tsx +123 -218
  206. package/src/screens/nationalIDBack/index.tsx +129 -209
  207. package/src/screens/payment-evidence/ModalGuideline.tsx +1 -1
  208. package/src/screens/toan-trinh-so/OCR.tsx +270 -556
  209. package/src/screens/toan-trinh-so/RegisterCamera.tsx +1 -1
  210. package/src/screens/toan-trinh-so/VehicleCamera.tsx +226 -288
  211. package/src/utils/cameraCrop.ts +96 -0
Binary file
@@ -1,7 +1,7 @@
1
1
  //@ts-nocheck
2
2
  import React from 'react';
3
3
  import { Image, SafeAreaView, TouchableOpacity, View } from 'react-native';
4
- import Modal from 'react-native-modal';
4
+ import Modal from './AppModal';
5
5
  import { Close2Icon, CloseIcon, IconNavClose } from '../assets/icons';
6
6
  import { MText } from './MText';
7
7
  interface IModalOptionLoan {
@@ -1,6 +1,8 @@
1
1
  //@ts-nocheck
2
- import React from 'react';
2
+ import React, { useEffect, useRef } from 'react';
3
3
  import ReactNativeModal from 'react-native-modal';
4
+ import { acquireModalId, popModal, pushModal } from './modalStack';
5
+ import { AlertGlobalOutlet } from './ModalNotification/AlertGlobal';
4
6
 
5
7
  /**
6
8
  * Backdrop dùng chung cho toàn bộ Modal của app.
@@ -18,17 +20,53 @@ type AppModalProps = Omit<Partial<RNModalProps>, 'children'> &
18
20
  /**
19
21
  * Wrapper mỏng quanh react-native-modal, áp dụng backdrop chung.
20
22
  * Mọi prop truyền vào sẽ override default (ví dụ backdropOpacity, customBackdrop).
23
+ *
24
+ * Ngoài ra mỗi AppModal:
25
+ * - đăng ký vào modalStack trong lúc hiển thị (để AlertGlobal biết modal nào trên cùng)
26
+ * - render <AlertGlobalOutlet/> bên trong, để alert global hiển thị đè lên được
27
+ * modal đang mở trên iOS mà không phải present native modal thứ 2 (gây treo app).
21
28
  */
22
29
  export default function AppModal({
23
30
  backdropColor = MODAL_BACKDROP_COLOR,
24
31
  backdropOpacity = MODAL_BACKDROP_OPACITY,
32
+ isVisible,
33
+ coverScreen,
34
+ onModalHide,
35
+ children,
25
36
  ...rest
26
37
  }: AppModalProps) {
38
+ const modalIdRef = useRef(0);
39
+ if (modalIdRef.current === 0) {
40
+ modalIdRef.current = acquireModalId();
41
+ }
42
+ const modalId = modalIdRef.current;
43
+ // coverScreen=false => render bằng View thường, không chiếm native layer
44
+ const isNativeModal = coverScreen !== false;
45
+
46
+ // Modal "chiếm" native layer từ lúc mở tới khi chạy xong animation đóng
47
+ // (nhả ở onModalHide bên dưới, hoặc khi unmount)
48
+ useEffect(() => {
49
+ if (isVisible && isNativeModal) {
50
+ pushModal(modalId);
51
+ }
52
+ }, [isVisible, isNativeModal, modalId]);
53
+
54
+ useEffect(() => () => popModal(modalId), [modalId]);
55
+
27
56
  return (
28
57
  <ReactNativeModal
29
58
  backdropColor={backdropColor}
30
59
  backdropOpacity={backdropOpacity}
60
+ isVisible={isVisible}
61
+ coverScreen={coverScreen}
62
+ onModalHide={() => {
63
+ popModal(modalId);
64
+ onModalHide?.();
65
+ }}
31
66
  {...rest}
32
- />
67
+ >
68
+ {children}
69
+ <AlertGlobalOutlet modalId={modalId} />
70
+ </ReactNativeModal>
33
71
  );
34
72
  }
@@ -36,7 +36,7 @@ import {
36
36
  } from '../assets/v3';
37
37
  import { Close2Icon, Info } from '../assets/icons';
38
38
  import { ScreenNames } from '../navigation';
39
- import Modal from 'react-native-modal';
39
+ import Modal from './AppModal';
40
40
  import { commonStyles } from '../screens/CommonStyles';
41
41
 
42
42
  interface IProps {
@@ -1,10 +1,12 @@
1
1
  //@ts-nocheck
2
- import React, { useEffect, useRef, useState } from 'react';
2
+ import React, { useEffect, useState } from 'react';
3
+ import { Dimensions, Platform, StyleSheet, View } from 'react-native';
4
+ import { addModalStackListener, getTopModalId } from '../modalStack';
3
5
  import ModalNotification, { ModalNotificationProps } from './index';
4
6
 
5
7
  export type AlertGlobalOptions = Omit<
6
8
  ModalNotificationProps,
7
- 'open' | 'onModalHide'
9
+ 'open' | 'onModalHide' | 'coverScreen'
8
10
  > & {
9
11
  /** false => bấm nút không tự đóng modal (mặc định true) */
10
12
  autoDismiss?: boolean;
@@ -12,15 +14,19 @@ export type AlertGlobalOptions = Omit<
12
14
  dismissable?: boolean;
13
15
  };
14
16
 
15
- interface AlertController {
16
- show: (options: AlertGlobalOptions) => void;
17
- hide: () => void;
18
- }
17
+ // ===== State giữ ở module-level để mọi outlet cùng đọc chung =====
18
+ let alertVisible = false;
19
+ let alertOptions: AlertGlobalOptions = { title: '' };
20
+ // Callback chờ modal đóng xong animation mới được gọi
21
+ let pendingCallback: (() => void) | undefined;
22
+ const listeners = new Set<() => void>();
19
23
 
20
- let alertController: AlertController | null = null;
24
+ function notify() {
25
+ listeners.forEach((listener) => listener());
26
+ }
21
27
 
22
28
  /**
23
- * Alert dùng chung toàn app, gọi được từ mọi nơi (kể cả ngoài component):
29
+ * Alert dùng chung toàn SDK, gọi được từ mọi nơi (kể cả ngoài component):
24
30
  *
25
31
  * AlertGlobal.show({
26
32
  * type: 'confirm',
@@ -33,38 +39,73 @@ let alertController: AlertController | null = null;
33
39
  *
34
40
  * Mặc định bấm nút / backdrop sẽ tự đóng modal rồi mới chạy callback
35
41
  * (callback chạy sau khi animation đóng xong => có thể mở tiếp modal khác).
36
- * Yêu cầu <AlertGlobalHost /> đã được mount trong App.tsx.
42
+ * Yêu cầu <AlertGlobalHost /> đã được mount root (RNTimacare).
37
43
  */
38
44
  export const AlertGlobal = {
39
45
  show(options: AlertGlobalOptions) {
40
- alertController?.show(options);
46
+ pendingCallback = undefined;
47
+ alertOptions = options;
48
+ alertVisible = true;
49
+ notify();
41
50
  },
42
51
  hide() {
43
- alertController?.hide();
52
+ alertVisible = false;
53
+ notify();
44
54
  },
45
55
  };
46
56
 
47
- /** Mount 1 lần duy nhất App.tsx */
48
- export function AlertGlobalHost() {
49
- const [visible, setVisible] = useState(false);
50
- const [options, setOptions] = useState<AlertGlobalOptions>({ title: '' });
51
- // Callback chờ modal đóng xong animation mới được gọi
52
- const pendingCallback = useRef<(() => void) | undefined>(undefined);
53
-
57
+ /** Re-render khi alert state hoặc modal stack thay đổi */
58
+ function useAlertGlobalState() {
59
+ const [, setTick] = useState(0);
54
60
  useEffect(() => {
55
- alertController = {
56
- show: (opts) => {
57
- pendingCallback.current = undefined;
58
- setOptions(opts);
59
- setVisible(true);
60
- },
61
- hide: () => setVisible(false),
62
- };
61
+ const rerender = () => setTick((tick) => tick + 1);
62
+ listeners.add(rerender);
63
+ const unsubscribeStack = addModalStackListener(rerender);
63
64
  return () => {
64
- alertController = null;
65
+ listeners.delete(rerender);
66
+ unsubscribeStack();
65
67
  };
66
68
  }, []);
69
+ }
70
+
71
+ interface OutletProps {
72
+ /** id của AppModal chứa outlet này; không truyền = outlet gốc ở root */
73
+ modalId?: number | null;
74
+ }
75
+
76
+ /**
77
+ * Điểm render thực tế của alert global. Có mặt ở 2 nơi:
78
+ * - AlertGlobalHost (root RNTimacare) — dùng khi không có modal nào đang mở
79
+ * - bên trong mỗi AppModal — dùng khi modal đó đang mở trên cùng (chỉ iOS)
80
+ *
81
+ * iOS không present được 2 native modal chồng nhau (treo app), nên khi có modal
82
+ * khác đang mở, alert được render dạng View thường (coverScreen=false) bên trong
83
+ * modal trên cùng => luôn nằm trên nội dung modal và bấm được bình thường.
84
+ * Android cho phép các Dialog chồng nhau nên luôn dùng native modal ở outlet gốc.
85
+ */
86
+ export function AlertGlobalOutlet({ modalId = null }: OutletProps) {
87
+ useAlertGlobalState();
67
88
 
89
+ if (Platform.OS !== 'ios') {
90
+ return modalId === null ? <AlertRenderer coverScreen /> : null;
91
+ }
92
+
93
+ if (getTopModalId() !== modalId) {
94
+ return null;
95
+ }
96
+ if (modalId === null) {
97
+ return <AlertRenderer coverScreen={false} />;
98
+ }
99
+ // Nội dung modal của react-native-modal có margin mặc định (5% chiều ngang),
100
+ // nới ngược lại để backdrop của alert phủ kín màn hình
101
+ return (
102
+ <View style={styles.insideModalWrapper} pointerEvents="box-none">
103
+ <AlertRenderer coverScreen={false} />
104
+ </View>
105
+ );
106
+ }
107
+
108
+ function AlertRenderer({ coverScreen }: { coverScreen: boolean }) {
68
109
  const {
69
110
  autoDismiss = true,
70
111
  dismissable = true,
@@ -72,29 +113,47 @@ export function AlertGlobalHost() {
72
113
  onPrimaryPress,
73
114
  onSecondaryPress,
74
115
  ...rest
75
- } = options;
116
+ } = alertOptions;
76
117
 
77
118
  const dismissThen = (callback?: () => void) => {
78
119
  if (!autoDismiss) {
79
120
  callback?.();
80
121
  return;
81
122
  }
82
- pendingCallback.current = callback;
83
- setVisible(false);
123
+ pendingCallback = callback;
124
+ AlertGlobal.hide();
84
125
  };
85
126
 
86
127
  return (
87
128
  <ModalNotification
88
129
  {...rest}
89
- open={visible}
130
+ open={alertVisible}
131
+ coverScreen={coverScreen}
90
132
  onClose={dismissable ? () => dismissThen(onClose) : undefined}
91
133
  onPrimaryPress={() => dismissThen(onPrimaryPress)}
92
134
  onSecondaryPress={() => dismissThen(onSecondaryPress)}
93
135
  onModalHide={() => {
94
- const callback = pendingCallback.current;
95
- pendingCallback.current = undefined;
136
+ const callback = pendingCallback;
137
+ pendingCallback = undefined;
96
138
  callback?.();
97
139
  }}
98
140
  />
99
141
  );
100
142
  }
143
+
144
+ /** Mount 1 lần duy nhất ở root (RNTimacare) */
145
+ export function AlertGlobalHost() {
146
+ return <AlertGlobalOutlet />;
147
+ }
148
+
149
+ const DEFAULT_MODAL_MARGIN = Dimensions.get('window').width * 0.05;
150
+
151
+ const styles = StyleSheet.create({
152
+ insideModalWrapper: {
153
+ position: 'absolute',
154
+ top: -DEFAULT_MODAL_MARGIN,
155
+ left: -DEFAULT_MODAL_MARGIN,
156
+ right: -DEFAULT_MODAL_MARGIN,
157
+ bottom: -DEFAULT_MODAL_MARGIN,
158
+ },
159
+ });
@@ -57,6 +57,11 @@ export interface ModalNotificationProps {
57
57
  onSecondaryPress?: () => void;
58
58
  /** Gọi sau khi modal đã đóng xong animation (dùng cho AlertGlobal) */
59
59
  onModalHide?: () => void;
60
+ /**
61
+ * false => render bằng View thường thay vì native Modal
62
+ * (dùng cho AlertGlobal trên iOS để tránh xung đột khi đã có modal khác mở)
63
+ */
64
+ coverScreen?: boolean;
60
65
  }
61
66
 
62
67
  /**
@@ -81,6 +86,7 @@ export default function ModalNotification({
81
86
  secondaryText,
82
87
  onSecondaryPress,
83
88
  onModalHide,
89
+ coverScreen = true,
84
90
  }: ModalNotificationProps) {
85
91
  const IconByType = ICON_BY_TYPE[type];
86
92
  const iconGradient = ICON_GRADIENT_BY_TYPE[type];
@@ -94,6 +100,7 @@ export default function ModalNotification({
94
100
  onBackdropPress={onClose}
95
101
  onBackButtonPress={onClose}
96
102
  onModalHide={onModalHide}
103
+ coverScreen={coverScreen}
97
104
  >
98
105
  <View style={styles.container}>
99
106
  {!!iconGradient && (
@@ -8,7 +8,7 @@ import {
8
8
  View,
9
9
  ViewStyle,
10
10
  } from 'react-native';
11
- import Modal from 'react-native-modal';
11
+ import Modal from './AppModal';
12
12
  import { AlertGlobal } from './ModalNotification/AlertGlobal';
13
13
  import { MText } from './MText';
14
14
  import MButton from './MButton';
@@ -8,7 +8,7 @@ import {
8
8
  View,
9
9
  ViewStyle,
10
10
  } from 'react-native';
11
- import Modal from 'react-native-modal';
11
+ import Modal from './AppModal';
12
12
  import { AlertGlobal } from './ModalNotification/AlertGlobal';
13
13
  import { MText } from './MText';
14
14
  import MButton from './MButton';
@@ -0,0 +1,47 @@
1
+ //@ts-nocheck
2
+ /**
3
+ * Theo dõi các native modal (dựng qua AppModal) đang hiển thị trên toàn SDK.
4
+ * AlertGlobal dựa vào đây để quyết định render alert ở đâu:
5
+ * bên trong modal đang mở trên cùng (iOS) hay ở root khi không có modal nào.
6
+ */
7
+ let nextModalId = 1;
8
+ let stack: number[] = [];
9
+ const listeners = new Set<() => void>();
10
+
11
+ function notify() {
12
+ listeners.forEach((listener) => listener());
13
+ }
14
+
15
+ /** Cấp id duy nhất cho mỗi instance AppModal */
16
+ export function acquireModalId(): number {
17
+ return nextModalId++;
18
+ }
19
+
20
+ /** Modal bắt đầu hiển thị (chiếm native layer) */
21
+ export function pushModal(id: number) {
22
+ if (!stack.includes(id)) {
23
+ stack = [...stack, id];
24
+ notify();
25
+ }
26
+ }
27
+
28
+ /** Modal đã đóng xong animation hoặc unmount */
29
+ export function popModal(id: number) {
30
+ if (stack.includes(id)) {
31
+ stack = stack.filter((modalId) => modalId !== id);
32
+ notify();
33
+ }
34
+ }
35
+
36
+ /** id của modal đang hiển thị trên cùng, null nếu không có modal nào mở */
37
+ export function getTopModalId(): number | null {
38
+ return stack.length > 0 ? stack[stack.length - 1] : null;
39
+ }
40
+
41
+ /** Lắng nghe thay đổi stack. Trả về hàm unsubscribe. */
42
+ export function addModalStackListener(listener: () => void) {
43
+ listeners.add(listener);
44
+ return () => {
45
+ listeners.delete(listener);
46
+ };
47
+ }
@@ -83,6 +83,8 @@ import LoanBuyCar from '../screens/v3/LoanBuyCar';
83
83
  import InfoLoanMotobike from '../screens/motobike/InfoLoanMotobike';
84
84
  import ReviewLoanMotobike from '../screens/motobike/ReviewLoanMotobike';
85
85
  import PolicyF88 from '../screens/motobike/PolicyF88';
86
+ import OcrStart from '../screens/NFC/OcrStart';
87
+ import ScanMRZ from '../screens/NFC/ScanMRZ';
86
88
 
87
89
  export const ScreenNames = {
88
90
  Main: 'HomeTimaCare',
@@ -157,6 +159,9 @@ export const ScreenNames = {
157
159
  InfoLoanMotobike: 'InfoLoanMotobike',
158
160
  ReviewLoanMotobike: 'ReviewLoanMotobike',
159
161
  PolicyF88: 'PolicyF88',
162
+ OcrStart: 'OcrStart',
163
+ ScanMRZ: 'ScanMRZ',
164
+ CameraVehicleRegistration: 'CameraVehicleRegistration',
160
165
  };
161
166
 
162
167
  const Stack = createNativeStackNavigator();
@@ -332,6 +337,8 @@ export function PrimaryNavigator(props) {
332
337
  component={ReviewLoanMotobike}
333
338
  />
334
339
  <Stack.Screen name={ScreenNames.PolicyF88} component={PolicyF88} />
340
+ <Stack.Screen name={ScreenNames.OcrStart} component={OcrStart} />
341
+ <Stack.Screen name={ScreenNames.ScanMRZ} component={ScanMRZ} />
335
342
  </Stack.Navigator>
336
343
  );
337
344
  }
@@ -0,0 +1,171 @@
1
+ //@ts-nocheck
2
+ import React from 'react';
3
+ import {
4
+ Image,
5
+ ScrollView,
6
+ StyleSheet,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
11
+ import { useNavigation } from '@react-navigation/native';
12
+ import { IconBack, IconUserSecurity, TimaSmall } from '../../assets/icons';
13
+ import { MText } from '../../components/MText';
14
+ import LinearGradient from 'react-native-linear-gradient';
15
+ import { ScreenNames } from '../../navigation';
16
+
17
+ export default function OcrStart(props: any) {
18
+ const insets = useSafeAreaInsets();
19
+ const navigation = useNavigation();
20
+ return (
21
+ <View style={styles.container}>
22
+ <View style={[styles.header, { paddingTop: insets.top + 16 }]}>
23
+ <TouchableOpacity
24
+ onPress={() => navigation.goBack()}
25
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
26
+ >
27
+ <IconBack />
28
+ </TouchableOpacity>
29
+ </View>
30
+ <ScrollView style={{ flex: 1 }}>
31
+ <View style={styles.column_top}>
32
+ <IconUserSecurity />
33
+ <MText style={styles.title}>Xác thực giấy tờ để tiếp tục</MText>
34
+ </View>
35
+ <View style={styles.content}>
36
+ <MText style={styles.label}>
37
+ Cách 1: NFC (Áp dụng cho CCCD gắn chip)
38
+ </MText>
39
+ <View style={styles.row}>
40
+ <TimaSmall />
41
+ <MText style={styles.description}>
42
+ Đưa phần chip của CCCD đến vị trí đọc NFC trên mặt lưng điện
43
+ thoại.
44
+ </MText>
45
+ </View>
46
+ <View style={styles.row}>
47
+ <TimaSmall />
48
+ <MText style={styles.description}>
49
+ Giữ nguyên để hệ thống đọc giữ liệu trong 5-10 giây
50
+ </MText>
51
+ </View>
52
+ <Image
53
+ source={require('../../assets/tts/Frame1000011953.png')}
54
+ style={{
55
+ width: '100%',
56
+ height: 82,
57
+ resizeMode: 'contain',
58
+ marginTop: 16,
59
+ }}
60
+ />
61
+ <MText style={[styles.label, { marginTop: 16 }]}>
62
+ Cách 2: Chụp 2 mặt
63
+ </MText>
64
+ <View style={styles.row}>
65
+ <TimaSmall />
66
+ <MText style={styles.description}>
67
+ Nên đặt CCCD/ CC lên mặt phẳng để chụp
68
+ </MText>
69
+ </View>
70
+ <View style={styles.row}>
71
+ <TimaSmall />
72
+ <MText style={styles.description}>
73
+ Đặt CCCD/ CC nằm gọn trong khung hình (Không quá nhỏ hay quá to
74
+ làm mất chi tiết)
75
+ </MText>
76
+ </View>
77
+ <View style={styles.row}>
78
+ <TimaSmall />
79
+ <MText style={styles.description}>
80
+ Chọn nơi ánh sáng tốt, tránh lóa/ chói hình chụp
81
+ </MText>
82
+ </View>
83
+ <Image
84
+ source={require('../../assets/tts/Frame1000011947.png')}
85
+ style={{
86
+ width: '100%',
87
+ height: 82,
88
+ resizeMode: 'contain',
89
+ marginTop: 16,
90
+ }}
91
+ />
92
+ </View>
93
+ </ScrollView>
94
+ <TouchableOpacity
95
+ onPress={() => {
96
+ navigation.navigate(ScreenNames.ScanMRZ, props);
97
+ }}
98
+ style={{
99
+ marginHorizontal: 16,
100
+ marginBottom: 24,
101
+ }}
102
+ >
103
+ <LinearGradient
104
+ start={{ x: 1, y: 0 }}
105
+ end={{ x: 0, y: 1 }}
106
+ style={{
107
+ height: 40,
108
+ marginVertical: 16,
109
+ borderRadius: 30,
110
+ alignItems: 'center',
111
+ justifyContent: 'center',
112
+ }}
113
+ colors={['#FF7A00', '#EF4123']}
114
+ >
115
+ <MText
116
+ style={{
117
+ fontSize: 16,
118
+ color: 'white',
119
+ }}
120
+ >
121
+ Xác thực ngay
122
+ </MText>
123
+ </LinearGradient>
124
+ </TouchableOpacity>
125
+ </View>
126
+ );
127
+ }
128
+
129
+ const styles = StyleSheet.create({
130
+ container: {
131
+ flex: 1,
132
+ backgroundColor: '#FFFFFF',
133
+ },
134
+ header: {
135
+ flexDirection: 'row',
136
+ alignItems: 'center',
137
+ paddingHorizontal: 16,
138
+ backgroundColor: '#FFF3E9',
139
+ },
140
+ title: {
141
+ fontFamily: 'BeVietnamPro-SemiBold',
142
+ fontWeight: '600',
143
+ textAlign: 'center',
144
+ fontSize: 16,
145
+ },
146
+ column_top: {
147
+ alignItems: 'center',
148
+ flexDirection: 'column',
149
+ gap: 16,
150
+ backgroundColor: '#FFF3E9',
151
+ paddingBottom: 16,
152
+ },
153
+ content: {
154
+ flex: 1,
155
+ padding: 16,
156
+ },
157
+ label: {
158
+ fontFamily: 'BeVietnamPro-SemiBold',
159
+ fontWeight: '600',
160
+ },
161
+ row: {
162
+ flexDirection: 'row',
163
+ gap: 10,
164
+ alignItems: 'center',
165
+ marginTop: 10,
166
+ },
167
+ description: {
168
+ fontSize: 12,
169
+ flex: 1,
170
+ },
171
+ });