@umituz/react-native-design-system 2.8.7 → 2.8.8

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 (128) hide show
  1. package/package.json +5 -6
  2. package/src/device/infrastructure/repositories/LegacyDeviceIdRepository.ts +1 -1
  3. package/src/device/infrastructure/services/DeviceFeatureService.ts +1 -1
  4. package/src/exception/infrastructure/services/ExceptionLogger.ts +1 -1
  5. package/src/exception/infrastructure/storage/ExceptionStore.ts +1 -1
  6. package/src/exports/filesystem.ts +1 -0
  7. package/src/exports/storage.ts +1 -0
  8. package/src/filesystem/domain/constants/FileConstants.ts +20 -0
  9. package/src/filesystem/domain/entities/File.ts +20 -0
  10. package/src/filesystem/domain/types/FileTypes.ts +43 -0
  11. package/src/filesystem/domain/utils/FileUtils.ts +86 -0
  12. package/src/filesystem/index.ts +23 -0
  13. package/src/filesystem/infrastructure/services/FileSystemService.ts +45 -0
  14. package/src/filesystem/infrastructure/services/cache.service.ts +48 -0
  15. package/src/filesystem/infrastructure/services/directory.service.ts +66 -0
  16. package/src/filesystem/infrastructure/services/download.constants.ts +6 -0
  17. package/src/filesystem/infrastructure/services/download.service.ts +74 -0
  18. package/src/filesystem/infrastructure/services/download.types.ts +7 -0
  19. package/src/filesystem/infrastructure/services/encoding.service.ts +25 -0
  20. package/src/filesystem/infrastructure/services/file-info.service.ts +52 -0
  21. package/src/filesystem/infrastructure/services/file-manager.service.ts +81 -0
  22. package/src/filesystem/infrastructure/services/file-path.service.ts +22 -0
  23. package/src/filesystem/infrastructure/services/file-reader.service.ts +52 -0
  24. package/src/filesystem/infrastructure/services/file-writer.service.ts +32 -0
  25. package/src/filesystem/infrastructure/utils/blob.utils.ts +20 -0
  26. package/src/image/infrastructure/services/ImageStorageService.ts +1 -1
  27. package/src/index.ts +9 -0
  28. package/src/molecules/alerts/AlertStore.ts +1 -1
  29. package/src/molecules/calendar/infrastructure/storage/EventActions.ts +1 -1
  30. package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +1 -1
  31. package/src/offline/infrastructure/storage/OfflineStore.ts +1 -1
  32. package/src/onboarding/infrastructure/storage/OnboardingStore.ts +2 -2
  33. package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +1 -1
  34. package/src/onboarding/infrastructure/storage/actions/answerActions.ts +1 -1
  35. package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +1 -1
  36. package/src/storage/README.md +185 -0
  37. package/src/storage/__tests__/integration.test.ts +391 -0
  38. package/src/storage/__tests__/mocks/asyncStorage.mock.ts +52 -0
  39. package/src/storage/__tests__/performance.test.tsx +352 -0
  40. package/src/storage/__tests__/setup.ts +63 -0
  41. package/src/storage/application/README.md +158 -0
  42. package/src/storage/application/ports/IStorageRepository.ts +61 -0
  43. package/src/storage/application/ports/README.md +127 -0
  44. package/src/storage/cache/README.md +154 -0
  45. package/src/storage/cache/__tests__/PerformanceAndMemory.test.ts +387 -0
  46. package/src/storage/cache/__tests__/setup.ts +19 -0
  47. package/src/storage/cache/domain/Cache.ts +146 -0
  48. package/src/storage/cache/domain/CacheManager.md +83 -0
  49. package/src/storage/cache/domain/CacheManager.ts +48 -0
  50. package/src/storage/cache/domain/CacheStatsTracker.md +169 -0
  51. package/src/storage/cache/domain/CacheStatsTracker.ts +49 -0
  52. package/src/storage/cache/domain/CachedValue.md +97 -0
  53. package/src/storage/cache/domain/ErrorHandler.md +99 -0
  54. package/src/storage/cache/domain/ErrorHandler.ts +42 -0
  55. package/src/storage/cache/domain/PatternMatcher.md +122 -0
  56. package/src/storage/cache/domain/PatternMatcher.ts +30 -0
  57. package/src/storage/cache/domain/README.md +118 -0
  58. package/src/storage/cache/domain/__tests__/Cache.test.ts +293 -0
  59. package/src/storage/cache/domain/__tests__/CacheManager.test.ts +276 -0
  60. package/src/storage/cache/domain/__tests__/ErrorHandler.test.ts +303 -0
  61. package/src/storage/cache/domain/__tests__/PatternMatcher.test.ts +261 -0
  62. package/src/storage/cache/domain/strategies/EvictionStrategy.ts +9 -0
  63. package/src/storage/cache/domain/strategies/FIFOStrategy.ts +12 -0
  64. package/src/storage/cache/domain/strategies/LFUStrategy.ts +22 -0
  65. package/src/storage/cache/domain/strategies/LRUStrategy.ts +22 -0
  66. package/src/storage/cache/domain/strategies/README.md +117 -0
  67. package/src/storage/cache/domain/strategies/TTLStrategy.ts +23 -0
  68. package/src/storage/cache/domain/strategies/__tests__/EvictionStrategies.test.ts +293 -0
  69. package/src/storage/cache/domain/types/Cache.ts +28 -0
  70. package/src/storage/cache/domain/types/README.md +107 -0
  71. package/src/storage/cache/index.ts +28 -0
  72. package/src/storage/cache/infrastructure/README.md +126 -0
  73. package/src/storage/cache/infrastructure/TTLCache.ts +103 -0
  74. package/src/storage/cache/infrastructure/__tests__/TTLCache.test.ts +303 -0
  75. package/src/storage/cache/presentation/README.md +123 -0
  76. package/src/storage/cache/presentation/__tests__/ReactHooks.test.ts +514 -0
  77. package/src/storage/cache/presentation/useCache.ts +76 -0
  78. package/src/storage/cache/presentation/useCachedValue.ts +88 -0
  79. package/src/storage/cache/types.d.ts +3 -0
  80. package/src/storage/domain/README.md +128 -0
  81. package/src/storage/domain/constants/CacheDefaults.ts +64 -0
  82. package/src/storage/domain/constants/README.md +105 -0
  83. package/src/storage/domain/entities/CachedValue.ts +86 -0
  84. package/src/storage/domain/entities/README.md +109 -0
  85. package/src/storage/domain/entities/StorageResult.ts +75 -0
  86. package/src/storage/domain/entities/__tests__/CachedValue.test.ts +149 -0
  87. package/src/storage/domain/entities/__tests__/StorageResult.test.ts +122 -0
  88. package/src/storage/domain/errors/README.md +126 -0
  89. package/src/storage/domain/errors/StorageError.ts +81 -0
  90. package/src/storage/domain/errors/__tests__/StorageError.test.ts +127 -0
  91. package/src/storage/domain/factories/README.md +138 -0
  92. package/src/storage/domain/factories/StoreFactory.ts +59 -0
  93. package/src/storage/domain/types/README.md +522 -0
  94. package/src/storage/domain/types/Store.ts +44 -0
  95. package/src/storage/domain/utils/CacheKeyGenerator.ts +66 -0
  96. package/src/storage/domain/utils/README.md +127 -0
  97. package/src/storage/domain/utils/__tests__/devUtils.test.ts +97 -0
  98. package/src/storage/domain/utils/devUtils.ts +37 -0
  99. package/src/storage/domain/value-objects/README.md +120 -0
  100. package/src/storage/domain/value-objects/StorageKey.ts +60 -0
  101. package/src/storage/index.ts +175 -0
  102. package/src/storage/infrastructure/README.md +165 -0
  103. package/src/storage/infrastructure/adapters/README.md +175 -0
  104. package/src/storage/infrastructure/adapters/StorageService.md +103 -0
  105. package/src/storage/infrastructure/adapters/StorageService.ts +49 -0
  106. package/src/storage/infrastructure/repositories/AsyncStorageRepository.ts +98 -0
  107. package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +100 -0
  108. package/src/storage/infrastructure/repositories/BatchStorageOperations.ts +42 -0
  109. package/src/storage/infrastructure/repositories/README.md +121 -0
  110. package/src/storage/infrastructure/repositories/StringStorageOperations.ts +44 -0
  111. package/src/storage/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +170 -0
  112. package/src/storage/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +201 -0
  113. package/src/storage/presentation/README.md +181 -0
  114. package/src/storage/presentation/hooks/CacheStorageOperations.ts +94 -0
  115. package/src/storage/presentation/hooks/README.md +128 -0
  116. package/src/storage/presentation/hooks/__tests__/usePersistentCache.test.ts +405 -0
  117. package/src/storage/presentation/hooks/__tests__/useStorage.test.ts +247 -0
  118. package/src/storage/presentation/hooks/__tests__/useStorageState.test.ts +293 -0
  119. package/src/storage/presentation/hooks/useCacheState.ts +53 -0
  120. package/src/storage/presentation/hooks/usePersistentCache.ts +154 -0
  121. package/src/storage/presentation/hooks/useStorage.ts +102 -0
  122. package/src/storage/presentation/hooks/useStorageState.ts +71 -0
  123. package/src/storage/presentation/hooks/useStore.ts +15 -0
  124. package/src/storage/types/README.md +103 -0
  125. package/src/theme/infrastructure/globalThemeStore.ts +1 -1
  126. package/src/theme/infrastructure/storage/ThemeStorage.ts +1 -1
  127. package/src/theme/infrastructure/stores/themeStore.ts +1 -1
  128. package/src/utilities/sharing/infrastructure/services/SharingService.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.8.7",
3
+ "version": "2.8.8",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and onboarding utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -23,6 +23,8 @@
23
23
  "./timezone": "./src/timezone/index.ts",
24
24
  "./offline": "./src/offline/index.ts",
25
25
  "./onboarding": "./src/onboarding/index.ts",
26
+ "./storage": "./src/storage/index.ts",
27
+ "./filesystem": "./src/filesystem/index.ts",
26
28
  "./package.json": "./package.json"
27
29
  },
28
30
  "scripts": {
@@ -59,13 +61,12 @@
59
61
  "@expo/vector-icons": ">=15.0.0",
60
62
  "@react-native-community/datetimepicker": ">=8.0.0",
61
63
  "@react-native-community/slider": ">=4.0.0",
64
+ "@react-native-async-storage/async-storage": ">=1.18.0",
62
65
  "@react-navigation/bottom-tabs": ">=7.0.0",
63
66
  "@react-navigation/native": ">=7.0.0",
64
67
  "@react-navigation/stack": ">=7.0.0",
65
- "@umituz/react-native-filesystem": "*",
66
68
  "@umituz/react-native-haptics": "*",
67
69
  "@umituz/react-native-localization": "*",
68
- "@umituz/react-native-storage": "*",
69
70
  "@umituz/react-native-uuid": "*",
70
71
  "expo-application": ">=5.0.0",
71
72
  "expo-clipboard": ">=8.0.0",
@@ -111,10 +112,8 @@
111
112
  "@types/react-native": "^0.73.0",
112
113
  "@typescript-eslint/eslint-plugin": "^8.50.1",
113
114
  "@typescript-eslint/parser": "^8.50.1",
114
- "@umituz/react-native-filesystem": "^2.1.7",
115
115
  "@umituz/react-native-haptics": "^1.0.2",
116
116
  "@umituz/react-native-localization": "^3.5.34",
117
- "@umituz/react-native-storage": "^2.6.20",
118
117
  "@umituz/react-native-uuid": "*",
119
118
  "eslint": "^9.39.2",
120
119
  "eslint-plugin-react": "^7.37.5",
@@ -125,7 +124,7 @@
125
124
  "expo-crypto": "~14.0.0",
126
125
  "expo-device": "~7.0.2",
127
126
  "expo-file-system": "^19.0.21",
128
- "expo-font": "~13.0.0",
127
+ "expo-font": "~14.0.0",
129
128
  "expo-network": "~8.0.0",
130
129
  "expo-video": "~3.0.0",
131
130
  "expo-haptics": "~14.0.0",
@@ -1,4 +1,4 @@
1
- import { storageRepository, unwrap } from '@umituz/react-native-storage';
1
+ import { storageRepository, unwrap } from '@storage';
2
2
 
3
3
  const LEGACY_DEVICE_ID_KEY = '@device/persistent_id';
4
4
 
@@ -8,7 +8,7 @@
8
8
  * @layer infrastructure/services
9
9
  */
10
10
 
11
- import { storageRepository, unwrap } from '@umituz/react-native-storage';
11
+ import { storageRepository, unwrap } from '@storage';
12
12
  import type {
13
13
  DeviceFeatureConfig,
14
14
  DeviceFeatureUsage,
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
12
12
  import { ExceptionHandler } from './ExceptionHandler';
13
- import { storageRepository } from '@umituz/react-native-storage';
13
+ import { storageRepository } from '@storage';
14
14
 
15
15
  export class ExceptionLogger {
16
16
  private static readonly STORAGE_KEY = '@exceptions';
@@ -3,7 +3,7 @@
3
3
  * Zustand store for exception state management
4
4
  */
5
5
 
6
- import { createStore } from '@umituz/react-native-storage';
6
+ import { createStore } from '@storage';
7
7
  import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
8
8
 
9
9
  interface ExceptionState {
@@ -0,0 +1 @@
1
+ export * from '../filesystem';
@@ -0,0 +1 @@
1
+ export * from '../storage';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * File Constants
3
+ * File-related constants for the filesystem package
4
+ */
5
+
6
+ import type { FileEncoding } from '../types/FileTypes';
7
+
8
+ /**
9
+ * File-related constants
10
+ */
11
+ export const FILE_CONSTANTS = {
12
+ MAX_FILE_SIZE: 100 * 1024 * 1024, // 100 MB
13
+ ALLOWED_EXTENSIONS: ['.jpg', '.jpeg', '.png', '.pdf', '.txt', '.json', '.mp4', '.mp3'] as const,
14
+ DEFAULT_ENCODING: 'utf8' as FileEncoding,
15
+ } as const;
16
+
17
+ /**
18
+ * Allowed file extensions type
19
+ */
20
+ export type AllowedExtension = typeof FILE_CONSTANTS.ALLOWED_EXTENSIONS[number];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * File Entity - Module Index
3
+ * Domain layer exports for file operations
4
+ */
5
+
6
+ // Types
7
+ export type {
8
+ FileEncoding,
9
+ DirectoryType,
10
+ FileOperationResult,
11
+ FileInfo,
12
+ DownloadProgress,
13
+ } from '../types/FileTypes';
14
+
15
+ // Constants
16
+ export { FILE_CONSTANTS } from '../constants/FileConstants';
17
+ export type { AllowedExtension } from '../constants/FileConstants';
18
+
19
+ // Utils
20
+ export { FileUtils } from '../utils/FileUtils';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * File Types
3
+ * Domain layer type definitions for file operations
4
+ */
5
+
6
+ /**
7
+ * File encoding types supported by expo-file-system
8
+ */
9
+ export type FileEncoding = 'utf8' | 'base64';
10
+
11
+ /**
12
+ * Directory types available in expo-file-system
13
+ */
14
+ export type DirectoryType = 'documentDirectory' | 'cacheDirectory';
15
+
16
+ /**
17
+ * Result of a file operation
18
+ */
19
+ export interface FileOperationResult {
20
+ success: boolean;
21
+ uri?: string;
22
+ error?: string;
23
+ }
24
+
25
+ /**
26
+ * File information metadata
27
+ */
28
+ export interface FileInfo {
29
+ uri: string;
30
+ name?: string;
31
+ size: number;
32
+ exists: boolean;
33
+ isDirectory: boolean;
34
+ modificationTime?: number;
35
+ }
36
+
37
+ /**
38
+ * Download progress information
39
+ */
40
+ export interface DownloadProgress {
41
+ totalBytesWritten: number;
42
+ totalBytesExpectedToWrite: number;
43
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * File Utils
3
+ * File utility functions for common operations
4
+ */
5
+
6
+ import { FILE_CONSTANTS, type AllowedExtension } from '../constants/FileConstants';
7
+
8
+ /**
9
+ * File utility functions
10
+ */
11
+ export class FileUtils {
12
+ /**
13
+ * Format file size in bytes to human-readable format
14
+ */
15
+ static formatFileSize(bytes: number): string {
16
+ if (bytes === 0) return '0 Bytes';
17
+ const k = 1024;
18
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
19
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
20
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
21
+ }
22
+
23
+ /**
24
+ * Generate unique filename with timestamp
25
+ */
26
+ static generateUniqueFilename(filename: string): string {
27
+ const timestamp = Date.now();
28
+ const extension = filename.includes('.') ? filename.substring(filename.lastIndexOf('.')) : '';
29
+ const nameWithoutExt = filename.includes('.')
30
+ ? filename.substring(0, filename.lastIndexOf('.'))
31
+ : filename;
32
+ return `${nameWithoutExt}_${timestamp}${extension}`;
33
+ }
34
+
35
+ /**
36
+ * Sanitize filename by removing invalid characters
37
+ */
38
+ static sanitizeFilename(filename: string): string {
39
+ return filename.replace(/[^a-zA-Z0-9._-]/g, '_');
40
+ }
41
+
42
+ /**
43
+ * Join path segments
44
+ */
45
+ static joinPaths(...segments: string[]): string {
46
+ return segments
47
+ .map((segment, index) => {
48
+ if (index === 0) {
49
+ return segment.replace(/\/+$/, '');
50
+ }
51
+ return segment.replace(/^\/+/, '').replace(/\/+$/, '');
52
+ })
53
+ .filter(segment => segment.length > 0)
54
+ .join('/');
55
+ }
56
+
57
+ /**
58
+ * Get file extension from filename
59
+ */
60
+ static getFileExtension(filename: string): string {
61
+ const lastDot = filename.lastIndexOf('.');
62
+ return lastDot > 0 ? filename.substring(lastDot) : '';
63
+ }
64
+
65
+ /**
66
+ * Check if file extension is allowed
67
+ */
68
+ static isAllowedExtension(filename: string): boolean {
69
+ const extension = this.getFileExtension(filename).toLowerCase();
70
+ return FILE_CONSTANTS.ALLOWED_EXTENSIONS.includes(extension as AllowedExtension);
71
+ }
72
+
73
+ /**
74
+ * Get filename from URI
75
+ */
76
+ static getFilenameFromUri(uri: string): string {
77
+ return uri.split('/').pop() || '';
78
+ }
79
+
80
+ /**
81
+ * Validate file size
82
+ */
83
+ static isValidFileSize(size: number): boolean {
84
+ return size > 0 && size <= FILE_CONSTANTS.MAX_FILE_SIZE;
85
+ }
86
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @filesystem
3
+ */
4
+
5
+ // Domain - Types and Utilities
6
+ export type { FileEncoding, DirectoryType, FileOperationResult, FileInfo, DownloadProgress } from "./domain/entities/File";
7
+ export { FILE_CONSTANTS, FileUtils } from "./domain/entities/File";
8
+
9
+ // Services - Download Operations
10
+ export type { DownloadProgressCallback, DownloadWithProgressResult } from "./infrastructure/services/download.types";
11
+ export { downloadFile, downloadFileWithProgress, isUrlCached, getCachedFileUri, deleteCachedFile } from "./infrastructure/services/download.service";
12
+
13
+ // Services - Directory Operations
14
+ export { getCacheDirectory, getDocumentDirectory, createDirectory } from "./infrastructure/services/directory.service";
15
+
16
+ // Services - File Operations
17
+ export { FileSystemService } from "./infrastructure/services/FileSystemService";
18
+ export { fileExists, getFileInfo } from "./infrastructure/services/file-info.service";
19
+ export { deleteFile } from "./infrastructure/services/file-manager.service";
20
+ export { clearCache } from "./infrastructure/services/cache.service";
21
+
22
+ // Services - File Reading
23
+ export { readFile, readFileAsBase64 } from "./infrastructure/services/file-reader.service";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * FileSystem Service - Facade
3
+ */
4
+
5
+ import { readFile, readFileAsBase64 } from "./file-reader.service";
6
+ import { writeFile } from "./file-writer.service";
7
+ import { deleteFile, copyFile, moveFile } from "./file-manager.service";
8
+ import { createDirectory, listDirectory, getDirectoryPath, getDocumentDirectory, getCacheDirectory } from "./directory.service";
9
+ import { getFileInfo, fileExists, getFileSize } from "./file-info.service";
10
+ import { downloadFile } from "./download.service";
11
+ import { clearCache, getDirectorySize } from "./cache.service";
12
+ import { generateFilePath } from "./file-path.service";
13
+ import { FileUtils } from "../../domain/entities/File";
14
+ import type { FileOperationResult } from "../../domain/entities/File";
15
+
16
+ export class FileSystemService {
17
+ static readFile = readFile;
18
+ static readFileAsBase64 = readFileAsBase64;
19
+ static writeFile = writeFile;
20
+ static deleteFile = deleteFile;
21
+ static copyFile = copyFile;
22
+ static moveFile = moveFile;
23
+ static createDirectory = createDirectory;
24
+ static listDirectory = listDirectory;
25
+ static getDirectoryPath = getDirectoryPath;
26
+ static getDocumentDirectory = getDocumentDirectory;
27
+ static getCacheDirectory = getCacheDirectory;
28
+ static getFileInfo = getFileInfo;
29
+ static exists = fileExists;
30
+ static getFileSize = getFileSize;
31
+ static downloadFile = downloadFile;
32
+ static clearCache = clearCache;
33
+ static getDirectorySize = getDirectorySize;
34
+ static generateFilePath = generateFilePath;
35
+
36
+ static async copyToCache(sourceUri: string, filename?: string): Promise<FileOperationResult> {
37
+ const dest = FileUtils.joinPaths(getCacheDirectory(), FileUtils.generateUniqueFilename(filename || sourceUri.split("/").pop() || "file"));
38
+ return copyFile(sourceUri, dest);
39
+ }
40
+
41
+ static async copyToDocuments(sourceUri: string, filename?: string): Promise<FileOperationResult> {
42
+ const dest = FileUtils.joinPaths(getDocumentDirectory(), FileUtils.generateUniqueFilename(filename || sourceUri.split("/").pop() || "file"));
43
+ return copyFile(sourceUri, dest);
44
+ }
45
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cache Service
3
+ * Single Responsibility: Manage cache directory operations
4
+ */
5
+
6
+ import { getCacheDirectory, listDirectory } from "./directory.service";
7
+ import { deleteFile } from "./file-manager.service";
8
+ import { getFileInfo } from "./file-info.service";
9
+ import { FileUtils } from "../../domain/entities/File";
10
+
11
+ /**
12
+ * Clear cache directory
13
+ */
14
+ export async function clearCache(): Promise<boolean> {
15
+ try {
16
+ const cacheDir = getCacheDirectory();
17
+ if (!cacheDir) return false;
18
+
19
+ const files = await listDirectory(cacheDir);
20
+ await Promise.all(
21
+ files.map((file) => deleteFile(FileUtils.joinPaths(cacheDir, file))),
22
+ );
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Get total size of directory
31
+ */
32
+ export async function getDirectorySize(uri: string): Promise<number> {
33
+ try {
34
+ const files = await listDirectory(uri);
35
+ const sizes = await Promise.all(
36
+ files.map(async (file) => {
37
+ const filePath = FileUtils.joinPaths(uri, file);
38
+ const info = await getFileInfo(filePath);
39
+ return info?.size || 0;
40
+ }),
41
+ );
42
+ return sizes.reduce((total, size) => total + size, 0);
43
+ } catch {
44
+ return 0;
45
+ }
46
+ }
47
+
48
+
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Directory Service
3
+ * Single Responsibility: Manage directory operations
4
+ */
5
+
6
+ import { Directory, Paths } from "expo-file-system";
7
+ import type { DirectoryType } from "../../domain/entities/File";
8
+
9
+ /**
10
+ * Create directory
11
+ */
12
+ export async function createDirectory(uri: string): Promise<boolean> {
13
+ try {
14
+ const dir = new Directory(uri);
15
+ dir.create({ intermediates: true, idempotent: true });
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * List directory contents
24
+ */
25
+ export async function listDirectory(uri: string): Promise<string[]> {
26
+ try {
27
+ const dir = new Directory(uri);
28
+ const items = dir.list();
29
+ return items.map((item) => item.uri);
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get directory path by type
37
+ */
38
+ export function getDirectoryPath(type: DirectoryType): string {
39
+ try {
40
+ switch (type) {
41
+ case "documentDirectory":
42
+ return Paths.document.uri;
43
+ case "cacheDirectory":
44
+ return Paths.cache.uri;
45
+ default:
46
+ return "";
47
+ }
48
+ } catch {
49
+ return "";
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Get document directory path
55
+ */
56
+ export function getDocumentDirectory(): string {
57
+ return getDirectoryPath("documentDirectory");
58
+ }
59
+
60
+ /**
61
+ * Get cache directory path
62
+ */
63
+ export function getCacheDirectory(): string {
64
+ return getDirectoryPath("cacheDirectory");
65
+ }
66
+
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Download Service Constants
3
+ */
4
+
5
+ export const SUPPORTED_DOWNLOAD_EXTENSIONS = ["mp4", "mov", "m4v", "webm", "jpg", "png", "pdf"] as const;
6
+ export const DEFAULT_DOWNLOAD_EXTENSION = "mp4" as const;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Download Service
3
+ */
4
+
5
+ import { File, Paths, Directory } from "expo-file-system";
6
+ import type { FileOperationResult } from "../../domain/entities/File";
7
+ import { FileUtils } from "../../domain/entities/File";
8
+ import { SUPPORTED_DOWNLOAD_EXTENSIONS, DEFAULT_DOWNLOAD_EXTENSION } from "./download.constants";
9
+ import type { DownloadProgressCallback, DownloadWithProgressResult } from "./download.types";
10
+
11
+ const hashUrl = (url: string) => {
12
+ let hash = 0;
13
+ for (let i = 0; i < url.length; i++) hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
14
+ return Math.abs(hash).toString(36);
15
+ };
16
+
17
+ const getExt = (url: string) => {
18
+ const parts = url.split("?")[0]?.split(".") || [];
19
+ const ext = parts.length > 1 ? parts.pop()?.toLowerCase() || DEFAULT_DOWNLOAD_EXTENSION : DEFAULT_DOWNLOAD_EXTENSION;
20
+ return (SUPPORTED_DOWNLOAD_EXTENSIONS as readonly string[]).includes(ext) ? ext : DEFAULT_DOWNLOAD_EXTENSION;
21
+ };
22
+
23
+ const getCacheUri = (url: string, dir: string) => FileUtils.joinPaths(dir, `cached_${hashUrl(url)}.${getExt(url)}`);
24
+
25
+ export async function downloadFile(url: string, dest?: string): Promise<FileOperationResult> {
26
+ try {
27
+ const destination = dest ? new File(dest) : new File(Paths.document, FileUtils.generateUniqueFilename("download"));
28
+ const res = await File.downloadFileAsync(url, destination, { idempotent: true });
29
+ return { success: true, uri: res.uri };
30
+ } catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
31
+ }
32
+
33
+ export async function downloadFileWithProgress(url: string, cacheDir: string, onProgress?: DownloadProgressCallback): Promise<DownloadWithProgressResult> {
34
+ try {
35
+ const dir = new Directory(cacheDir);
36
+ if (!dir.exists) dir.create({ intermediates: true, idempotent: true });
37
+
38
+ const destUri = getCacheUri(url, cacheDir);
39
+ if (new File(destUri).exists) return { success: true, uri: destUri, fromCache: true };
40
+
41
+ const response = await fetch(url);
42
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
43
+
44
+ const totalBytes = parseInt(response.headers.get("content-length") || "0", 10);
45
+ if (!response.body) return { ...(await downloadFile(url, destUri)), fromCache: false };
46
+
47
+ const reader = response.body.getReader();
48
+ const chunks: Uint8Array[] = [];
49
+ let received = 0;
50
+
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done) break;
54
+ chunks.push(value);
55
+ received += value.length;
56
+ onProgress?.({ totalBytesWritten: received, totalBytesExpectedToWrite: totalBytes || received });
57
+ }
58
+
59
+ const all = new Uint8Array(received);
60
+ let pos = 0;
61
+ for (const c of chunks) { all.set(c, pos); pos += c.length; }
62
+ new File(destUri).write(all);
63
+
64
+ return { success: true, uri: destUri, fromCache: false };
65
+ } catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
66
+ }
67
+
68
+ export const isUrlCached = (url: string, dir: string) => new File(getCacheUri(url, dir)).exists;
69
+ export const getCachedFileUri = (url: string, dir: string) => isUrlCached(url, dir) ? getCacheUri(url, dir) : null;
70
+ export const deleteCachedFile = (url: string, dir: string) => {
71
+ const f = new File(getCacheUri(url, dir));
72
+ if (f.exists) f.delete();
73
+ return true;
74
+ };
@@ -0,0 +1,7 @@
1
+ import type { FileOperationResult, DownloadProgress } from "../../domain/entities/File";
2
+
3
+ export type DownloadProgressCallback = (progress: DownloadProgress) => void;
4
+
5
+ export interface DownloadWithProgressResult extends FileOperationResult {
6
+ fromCache?: boolean;
7
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Encoding Service
3
+ * Single Responsibility: Handle file encoding/decoding operations
4
+ */
5
+
6
+ import type { FileEncoding } from "../../domain/entities/File";
7
+
8
+ /**
9
+ * Encoding type for Expo FileSystem
10
+ */
11
+ export type ExpoEncodingType = FileEncoding;
12
+
13
+ /**
14
+ * Convert FileEncoding to Expo FileSystem encoding type
15
+ */
16
+ export function getEncodingType(encoding: FileEncoding): ExpoEncodingType {
17
+ return encoding;
18
+ }
19
+
20
+ /**
21
+ * Validate encoding type
22
+ */
23
+ export function isValidEncoding(encoding: string): encoding is FileEncoding {
24
+ return encoding === "utf8" || encoding === "base64";
25
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * File Info Service
3
+ * Single Responsibility: Get file information and metadata
4
+ */
5
+
6
+ import { File } from "expo-file-system";
7
+ import type { FileInfo } from "../../domain/entities/File";
8
+
9
+ /**
10
+ * Get file information
11
+ */
12
+ export async function getFileInfo(uri: string): Promise<FileInfo | null> {
13
+ try {
14
+ const file = new File(uri);
15
+
16
+ if (!file.exists) {
17
+ return null;
18
+ }
19
+
20
+ return {
21
+ uri,
22
+ name: file.name,
23
+ size: file.size || 0,
24
+ exists: file.exists,
25
+ isDirectory: false,
26
+ modificationTime: file.modificationTime || 0,
27
+ };
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Check if file exists
35
+ */
36
+ export async function fileExists(uri: string): Promise<boolean> {
37
+ try {
38
+ const file = new File(uri);
39
+ return file.exists;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Get file size
47
+ */
48
+ export async function getFileSize(uri: string): Promise<number> {
49
+ const info = await getFileInfo(uri);
50
+ return info?.size || 0;
51
+ }
52
+