@transfergratis/react-native-sdk 0.1.13 → 0.1.14

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 (38) hide show
  1. package/android/build.gradle +1 -1
  2. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  3. package/build/components/KYCElements/IDCardCapture.js +2 -18
  4. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  5. package/build/components/KYCElements/VerificationProgressTemplate.js +1 -1
  6. package/build/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  7. package/build/components/TemplateKYCExample.js +0 -64
  8. package/build/components/TemplateKYCExample.js.map +1 -1
  9. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  10. package/build/hooks/useTemplateKYCFlow.js +6 -12
  11. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  12. package/build/i18n/en/index.d.ts +1 -0
  13. package/build/i18n/en/index.d.ts.map +1 -1
  14. package/build/i18n/en/index.js +2 -1
  15. package/build/i18n/en/index.js.map +1 -1
  16. package/build/i18n/fr/index.d.ts +1 -0
  17. package/build/i18n/fr/index.d.ts.map +1 -1
  18. package/build/i18n/fr/index.js +2 -1
  19. package/build/i18n/fr/index.js.map +1 -1
  20. package/build/i18n/types.d.ts +1 -0
  21. package/build/i18n/types.d.ts.map +1 -1
  22. package/build/i18n/types.js.map +1 -1
  23. package/build/modules/camera/VisionCameraModule.d.ts.map +1 -1
  24. package/build/modules/camera/VisionCameraModule.js +24 -5
  25. package/build/modules/camera/VisionCameraModule.js.map +1 -1
  26. package/build/utils/pathToBase64.d.ts.map +1 -1
  27. package/build/utils/pathToBase64.js +8 -3
  28. package/build/utils/pathToBase64.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/KYCElements/IDCardCapture.tsx +2 -19
  31. package/src/components/KYCElements/VerificationProgressTemplate.tsx +1 -1
  32. package/src/components/TemplateKYCExample.tsx +0 -65
  33. package/src/hooks/useTemplateKYCFlow.tsx +7 -12
  34. package/src/i18n/en/index.ts +2 -1
  35. package/src/i18n/fr/index.ts +2 -1
  36. package/src/i18n/types.ts +1 -0
  37. package/src/modules/camera/VisionCameraModule.ts +26 -7
  38. package/src/utils/pathToBase64.ts +8 -3
@@ -8,9 +8,14 @@ export async function pathToBase64(uri) {
8
8
  }
9
9
  // Try Expo FileSystem if available (React Native + Expo)
10
10
  try {
11
- // Dynamically import to avoid hard dependency when not available
12
- // eslint-disable-next-line @typescript-eslint/no-var-requires
13
- const FileSystem = require('expo-file-system');
11
+ // Try legacy API first (Expo SDK 54+) to avoid deprecation warnings
12
+ // @ts-ignore - legacy export might not be in types
13
+ let FileSystem = require('expo-file-system/legacy');
14
+ if (!FileSystem?.readAsStringAsync) {
15
+ // Fall back to regular import for older versions
16
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
17
+ FileSystem = require('expo-file-system');
18
+ }
14
19
  if (FileSystem?.readAsStringAsync) {
15
20
  const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
16
21
  if (base64)
@@ -1 +1 @@
1
- {"version":3,"file":"pathToBase64.js","sourceRoot":"","sources":["../../src/utils/pathToBase64.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE3D,gDAAgD;IAChD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjE,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,iEAAiE;QACjE,8DAA8D;QAC9D,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC/C,IAAI,UAAU,EAAE,iBAAiB,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;YACrG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;QAC5B,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,yCAAyC;IAC3C,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAChC,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;gBACtB,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;oBAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;;oBACzD,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAC;YAC/D,CAAC,CAAC;YACF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC;YACxB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0BAA0B;IAC5B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACnE,CAAC;AAED,eAAe,YAAY,CAAC","sourcesContent":["export async function pathToBase64(uri: string): Promise<string> {\n if (!uri) throw new Error('pathToBase64: uri is required');\n\n // If already a data URI, return the base64 part\n if (uri.startsWith('data:')) {\n const commaIndex = uri.indexOf(',');\n return commaIndex !== -1 ? uri.substring(commaIndex + 1) : uri;\n }\n\n // Try Expo FileSystem if available (React Native + Expo)\n try {\n // Dynamically import to avoid hard dependency when not available\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const FileSystem = require('expo-file-system');\n if (FileSystem?.readAsStringAsync) {\n const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });\n if (base64) return base64;\n }\n } catch (_) {\n // ignore - fall back to other strategies\n }\n\n // Generic fetch + FileReader strategy (works on web; often works on RN for file:// URIs)\n try {\n const response = await fetch(uri);\n const blob = await response.blob();\n const dataUrl = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n if (typeof reader.result === 'string') resolve(reader.result);\n else reject(new Error('Failed to convert blob to data URL'));\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n const commaIndex = dataUrl.indexOf(',');\n return commaIndex !== -1 ? dataUrl.substring(commaIndex + 1) : dataUrl;\n } catch (err) {\n // continue to final error\n }\n\n throw new Error('pathToBase64: unable to convert uri to base64');\n}\n\nexport default pathToBase64;\n\n\n"]}
1
+ {"version":3,"file":"pathToBase64.js","sourceRoot":"","sources":["../../src/utils/pathToBase64.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE3D,gDAAgD;IAChD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjE,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,oEAAoE;QACpE,mDAAmD;QACnD,IAAI,UAAU,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,EAAE,iBAAiB,EAAE,CAAC;YACnC,iDAAiD;YACjD,8DAA8D;YAC9D,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,UAAU,EAAE,iBAAiB,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;YACrG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;QAC5B,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,yCAAyC;IAC3C,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAChC,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;gBACtB,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;oBAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;;oBACzD,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAC;YAC/D,CAAC,CAAC;YACF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC;YACxB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0BAA0B;IAC5B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACnE,CAAC;AAED,eAAe,YAAY,CAAC","sourcesContent":["export async function pathToBase64(uri: string): Promise<string> {\n if (!uri) throw new Error('pathToBase64: uri is required');\n\n // If already a data URI, return the base64 part\n if (uri.startsWith('data:')) {\n const commaIndex = uri.indexOf(',');\n return commaIndex !== -1 ? uri.substring(commaIndex + 1) : uri;\n }\n\n // Try Expo FileSystem if available (React Native + Expo)\n try {\n // Try legacy API first (Expo SDK 54+) to avoid deprecation warnings\n // @ts-ignore - legacy export might not be in types\n let FileSystem = require('expo-file-system/legacy');\n if (!FileSystem?.readAsStringAsync) {\n // Fall back to regular import for older versions\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n FileSystem = require('expo-file-system');\n }\n if (FileSystem?.readAsStringAsync) {\n const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });\n if (base64) return base64;\n }\n } catch (_) {\n // ignore - fall back to other strategies\n }\n\n // Generic fetch + FileReader strategy (works on web; often works on RN for file:// URIs)\n try {\n const response = await fetch(uri);\n const blob = await response.blob();\n const dataUrl = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n if (typeof reader.result === 'string') resolve(reader.result);\n else reject(new Error('Failed to convert blob to data URL'));\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n const commaIndex = dataUrl.indexOf(',');\n return commaIndex !== -1 ? dataUrl.substring(commaIndex + 1) : dataUrl;\n } catch (err) {\n // continue to final error\n }\n\n throw new Error('pathToBase64: unable to convert uri to base64');\n}\n\nexport default pathToBase64;\n\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transfergratis/react-native-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "transfergratis react native sdk",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -339,30 +339,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
339
339
 
340
340
 
341
341
 
342
- // const renderBboxOverlay = () => {
343
- // const bbox: any = (silentCaptureResult as any)?.bbox;
344
- // if (!bbox || !imageNaturalSize) return null;
345
- // const containerWidth = Dimensions.get('window').width - 24 - 24; // approximate inner width accounting paddings/margins
346
- // const containerHeight = 200;
347
- // const scaleX = 1;
348
- // const scaleY = 1;
349
- // const left = Math.max(0, bbox.minX * scaleX);
350
- // const top = Math.max(0, bbox.minY * scaleY);
351
- // const width = Math.max(1, bbox.width * scaleX);
352
- // const height = Math.max(1, bbox.height * scaleY);
353
- // return (
354
- // <View
355
- // pointerEvents="none"
356
- // style={{ position: 'absolute', left, top, width, height, borderColor: '#2DBD60', borderWidth: 2, borderStyle: 'solid', borderRadius: 6 }}
357
- // />
358
- // );
359
- // };
360
-
361
342
 
362
343
 
363
344
 
364
345
  const handleDocumentTypeSelection = (docType: GovernmentDocumentType) => {
365
346
  setSelectedDocumentType({ type: docType, region: hasRegions(docType) ? '' : 'root' });
347
+ setSilentCaptureResult((prev) => ({ ...prev, templatePath: '', bbox: undefined, success: false, isAnalyzing: false, error: '', path: '', mrz: '', country: '', documentType: '' }));
348
+ setCapturedImages((prev) => ({ ...prev, front: { dir: '', file: '', mrz: '' }, back: { dir: '', file: '', mrz: '' } }));
366
349
  };
367
350
  const handleRegionSelection = (region: string) => {
368
351
  setSelectedDocumentType((prev) => ({ ...prev, region: region }));
@@ -155,7 +155,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
155
155
  <Text key={idx} style={styles.issueItem}>• {issue}</Text>
156
156
  ))}
157
157
  </View>
158
- <TouchableOpacity style={[styles.button, styles.primary]} onPress={() => actions.goToComponent(state.template.components.find(c => c.type !== 'review_submit' && c.type !== 'verification_progress')?.id || 0)}>
158
+ <TouchableOpacity style={[styles.button, styles.primary]} onPress={() => actions.resetTemplate()}>
159
159
  <Text style={styles.buttonText}>{t('kyc.verificationProgress.status.retry')}</Text>
160
160
  </TouchableOpacity>
161
161
  <TouchableOpacity style={[styles.button, styles.secondary]}>
@@ -260,68 +260,3 @@ const styles = StyleSheet.create({
260
260
  flex: 1,
261
261
  },
262
262
  });
263
-
264
- // Exemples de différents types de documents gouvernementaux supportés :
265
- /*
266
- export const documentTypeExamples = {
267
- // Carte d'identité nationale
268
- idCard: {
269
- document_type: "id_card" as const,
270
- labels: { en: "Upload your ID card", fr: "Téléversez votre carte d'identité" }
271
- },
272
-
273
- // Passeport
274
- passport: {
275
- document_type: "passport" as const,
276
- labels: { en: "Upload your passport", fr: "Téléversez votre passeport" }
277
- },
278
-
279
- // Permis de conduire
280
- driversLicense: {
281
- document_type: "drivers_license" as const,
282
- labels: { en: "Upload your driver's license", fr: "Téléversez votre permis de conduire" }
283
- },
284
-
285
- // Permis de séjour
286
- residencePermit: {
287
- document_type: "residence_permit" as const,
288
- labels: { en: "Upload your residence permit", fr: "Téléversez votre permis de séjour" }
289
- },
290
-
291
- // Carte nationale d'identité
292
- nationalId: {
293
- document_type: "national_id" as const,
294
- labels: { en: "Upload your national ID", fr: "Téléversez votre carte nationale d'identité" }
295
- },
296
-
297
- // Carte d'électeur
298
- voterId: {
299
- document_type: "voter_id" as const,
300
- labels: { en: "Upload your voter ID", fr: "Téléversez votre carte d'électeur" }
301
- },
302
-
303
- // Carte militaire
304
- militaryId: {
305
- document_type: "military_id" as const,
306
- labels: { en: "Upload your military ID", fr: "Téléversez votre carte militaire" }
307
- },
308
-
309
- // Carte d'étudiant
310
- studentId: {
311
- document_type: "student_id" as const,
312
- labels: { en: "Upload your student ID", fr: "Téléversez votre carte d'étudiant" }
313
- },
314
-
315
- // Permis de travail
316
- workPermit: {
317
- document_type: "work_permit" as const,
318
- labels: { en: "Upload your work permit", fr: "Téléversez votre permis de travail" }
319
- },
320
-
321
- // Autre document gouvernemental
322
- other: {
323
- document_type: "other" as const,
324
- labels: { en: "Upload your government document", fr: "Téléversez votre document gouvernemental" }
325
- }
326
- };
327
- */
@@ -130,8 +130,8 @@ export const useTemplateKYCFlow = (
130
130
  const templateWithReview = useMemo(() => ensureReviewSubmitStep(template), [template, ensureReviewSubmitStep]);
131
131
  const templateWithReviewAndVerification = useMemo(() => ensureVerificationProgressStep(templateWithReview), [templateWithReview, ensureVerificationProgressStep]);
132
132
 
133
- // État du flux
134
- const [state, setState] = useState<TemplateState>({
133
+ // État initial du flux
134
+ const buildInitialState = (): TemplateState => ({
135
135
  template: templateWithReviewAndVerification,
136
136
  currentComponentIndex: 0,
137
137
  completedComponents: [],
@@ -151,6 +151,9 @@ export const useTemplateKYCFlow = (
151
151
  status: 'idle',
152
152
  },
153
153
  });
154
+
155
+ // État du flux
156
+ const [state, setState] = useState<TemplateState>(() => buildInitialState());
154
157
  const mapComponentTypeToAction = useCallback((type: TemplateComponent['type']): string | null => {
155
158
  switch (type) {
156
159
  case 'id_card':
@@ -594,16 +597,8 @@ export const useTemplateKYCFlow = (
594
597
 
595
598
  // Réinitialiser le template
596
599
  resetTemplate: useCallback(() => {
597
- setState(prev => ({
598
- ...prev,
599
- currentComponentIndex: 0,
600
- completedComponents: [],
601
- componentData: {},
602
- errors: {},
603
- isProcessing: false,
604
- verification: { status: 'idle', result: undefined },
605
- }));
606
- }, []),
600
+ setState(buildInitialState());
601
+ }, [buildInitialState]),
607
602
 
608
603
  // Changer la langue
609
604
  setLanguage: useCallback((language: string) => {
@@ -140,7 +140,8 @@ export const en = {
140
140
  pending: 'Pending',
141
141
  processing: 'Processing',
142
142
  completed: 'Completed',
143
- failed: 'Failed'
143
+ failed: 'Failed',
144
+ retry: 'Retry'
144
145
  }
145
146
  },
146
147
 
@@ -140,7 +140,8 @@ export const fr = {
140
140
  pending: 'En attente',
141
141
  processing: 'En cours',
142
142
  completed: 'Terminé',
143
- failed: 'Échoué'
143
+ failed: 'Échoué',
144
+ retry: 'Réessayer'
144
145
  }
145
146
  },
146
147
 
package/src/i18n/types.ts CHANGED
@@ -98,6 +98,7 @@ export interface KYCTranslations {
98
98
  processing: string;
99
99
  completed: string;
100
100
  failed: string;
101
+ retry: string;
101
102
  };
102
103
  };
103
104
 
@@ -2,6 +2,29 @@ import { Platform } from 'react-native';
2
2
  import { Camera, CameraDevice, CameraPermissionStatus, PhotoFile } from 'react-native-vision-camera';
3
3
  import * as FileSystem from "expo-file-system";
4
4
 
5
+ /**
6
+ * Backward-compatible file copy function
7
+ * Uses legacy API from expo-file-system/legacy if available (Expo SDK 54+)
8
+ * Falls back to regular copyAsync for older versions
9
+ */
10
+ async function copyFileCompat(from: string, to: string): Promise<void> {
11
+ try {
12
+ // Try legacy API from expo-file-system/legacy (Expo SDK 54+)
13
+ // This avoids deprecation warnings while maintaining backward compatibility
14
+ // @ts-ignore - legacy export might not be in types
15
+ const LegacyFileSystem = require('expo-file-system/legacy');
16
+ if (LegacyFileSystem?.copyAsync) {
17
+ await LegacyFileSystem.copyAsync({ from, to });
18
+ return;
19
+ }
20
+ } catch (_) {
21
+ // Legacy export not available, fall through to regular import
22
+ }
23
+
24
+ // Fall back to regular copyAsync (for older Expo SDK versions < 54)
25
+ await FileSystem.copyAsync({ from, to });
26
+ }
27
+
5
28
  export interface CameraCaptureResult {
6
29
  success: boolean;
7
30
  path?: string;
@@ -163,15 +186,11 @@ export class VisionCameraModule {
163
186
  * Process photo capture result
164
187
  */
165
188
  async processPhotoResult(photo: PhotoFile): Promise<CameraCaptureResult> {
189
+ try {
190
+ const newPath = FileSystem.documentDirectory + `photo_${Date.now()}.jpg`;
166
191
 
167
- const newPath = FileSystem.documentDirectory + `photo_${Date.now()}.jpg`;
168
-
169
- await FileSystem.copyAsync({
170
- from: `file://${photo.path}`,
171
- to: newPath,
172
- });
192
+ await copyFileCompat(`file://${photo.path}`, newPath);
173
193
 
174
- try {
175
194
  return {
176
195
  success: true,
177
196
  path: newPath,
@@ -9,9 +9,14 @@ export async function pathToBase64(uri: string): Promise<string> {
9
9
 
10
10
  // Try Expo FileSystem if available (React Native + Expo)
11
11
  try {
12
- // Dynamically import to avoid hard dependency when not available
13
- // eslint-disable-next-line @typescript-eslint/no-var-requires
14
- const FileSystem = require('expo-file-system');
12
+ // Try legacy API first (Expo SDK 54+) to avoid deprecation warnings
13
+ // @ts-ignore - legacy export might not be in types
14
+ let FileSystem = require('expo-file-system/legacy');
15
+ if (!FileSystem?.readAsStringAsync) {
16
+ // Fall back to regular import for older versions
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ FileSystem = require('expo-file-system');
19
+ }
15
20
  if (FileSystem?.readAsStringAsync) {
16
21
  const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
17
22
  if (base64) return base64;