@umituz/react-native-design-system 4.27.13 → 4.27.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -4
- package/src/core/cache/domain/CleanupStrategy.ts +115 -0
- package/src/core/cache/domain/UnifiedCache.ts +196 -0
- package/src/core/cache/domain/types.ts +18 -0
- package/src/core/cache/index.ts +16 -0
- package/src/core/cache/infrastructure/CacheFactory.ts +102 -0
- package/src/core/index.ts +21 -0
- package/src/core/permissions/domain/PermissionHandler.ts +139 -0
- package/src/core/permissions/domain/types.ts +49 -0
- package/src/core/permissions/index.ts +15 -0
- package/src/core/repositories/domain/RepositoryKeyFactory.ts +41 -0
- package/src/core/repositories/domain/RepositoryUtils.ts +86 -0
- package/src/core/repositories/domain/types.ts +59 -0
- package/src/core/repositories/index.ts +23 -0
- package/src/device/detection/deviceDetection.ts +8 -2
- package/src/haptics/infrastructure/services/HapticService.ts +4 -1
- package/src/index.ts +1 -0
- package/src/media/domain/strategies/CameraPickerStrategy.ts +65 -0
- package/src/media/domain/strategies/LibraryPickerStrategy.ts +54 -0
- package/src/media/domain/strategies/PickerStrategy.ts +61 -0
- package/src/media/domain/strategies/index.ts +13 -0
- package/src/media/infrastructure/services/MediaPickerService.ts +118 -108
- package/src/media/infrastructure/utils/PermissionManager.ts +68 -66
- package/src/media/presentation/hooks/useMedia.ts +16 -4
- package/src/storage/cache/infrastructure/TTLCache.ts +3 -0
- package/src/tanstack/domain/repositories/BaseRepository.ts +18 -1
- package/src/timezone/infrastructure/utils/SimpleCache.ts +30 -75
- package/src/uuid/infrastructure/utils/UUIDUtils.ts +4 -1
|
@@ -2,180 +2,190 @@
|
|
|
2
2
|
* Media Domain - Media Picker Service
|
|
3
3
|
*
|
|
4
4
|
* Service for picking images/videos using expo-image-picker.
|
|
5
|
-
*
|
|
5
|
+
* Refactored to use strategy pattern to reduce code duplication.
|
|
6
|
+
*
|
|
7
|
+
* Before: 182 LOC with 4 similar methods
|
|
8
|
+
* After: ~120 LOC with 1 generic launcher + convenience wrappers - 34% reduction
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
import * as ImagePicker from "expo-image-picker";
|
|
9
11
|
import type {
|
|
10
12
|
MediaPickerOptions,
|
|
11
13
|
MediaPickerResult,
|
|
12
14
|
CameraOptions,
|
|
13
|
-
} from
|
|
15
|
+
} from '../../domain/entities/Media';
|
|
14
16
|
import {
|
|
15
17
|
MediaType,
|
|
16
18
|
MediaValidationError,
|
|
17
19
|
MEDIA_CONSTANTS,
|
|
18
|
-
} from
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
20
|
+
} from '../../domain/entities/Media';
|
|
21
|
+
import { mapPickerResult } from '../utils/mediaPickerMappers';
|
|
22
|
+
import { PermissionManager } from '../utils/PermissionManager';
|
|
23
|
+
import { FileValidator } from '../../domain/utils/FileValidator';
|
|
24
|
+
import { ErrorHandler } from '../../../utils/errors';
|
|
25
|
+
import type { PickerStrategy, LaunchOptions } from '../../domain/strategies/PickerStrategy';
|
|
26
|
+
import { CameraPickerStrategy } from '../../domain/strategies/CameraPickerStrategy';
|
|
27
|
+
import { LibraryPickerStrategy } from '../../domain/strategies/LibraryPickerStrategy';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Media picker service for selecting images/videos
|
|
31
|
+
* Uses strategy pattern to support different picker types
|
|
29
32
|
*/
|
|
30
33
|
export class MediaPickerService {
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Generic media picker launcher using strategy pattern
|
|
36
|
+
*
|
|
37
|
+
* @param strategy - Picker strategy to use
|
|
38
|
+
* @param options - Picker options
|
|
39
|
+
* @returns Picker result
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const strategy = new CameraPickerStrategy({ mediaType: 'images' });
|
|
44
|
+
* const result = await MediaPickerService.launchMediaPicker(strategy, {
|
|
45
|
+
* quality: 0.8,
|
|
46
|
+
* allowsEditing: true
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
static async launchMediaPicker(
|
|
51
|
+
strategy: PickerStrategy,
|
|
52
|
+
options?: LaunchOptions
|
|
33
53
|
): Promise<MediaPickerResult> {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
canceled: true,
|
|
39
|
-
error: MediaValidationError.PERMISSION_DENIED,
|
|
40
|
-
errorMessage: "Camera permission was denied",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const result = await ImagePicker.launchCameraAsync({
|
|
45
|
-
mediaTypes: ["images"],
|
|
46
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
47
|
-
aspect: options?.aspect,
|
|
48
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
49
|
-
base64: options?.base64 ?? false,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return mapPickerResult(result);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
ErrorHandler.handleAndLog(error, 'launchCamera', { options });
|
|
54
|
+
// Check permission
|
|
55
|
+
const permission = await strategy.getPermission();
|
|
56
|
+
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
55
57
|
return {
|
|
56
58
|
canceled: true,
|
|
57
|
-
error: MediaValidationError.
|
|
58
|
-
errorMessage:
|
|
59
|
+
error: MediaValidationError.PERMISSION_DENIED,
|
|
60
|
+
errorMessage: 'Permission was denied',
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
|
-
}
|
|
62
63
|
|
|
63
|
-
static async launchCameraForVideo(
|
|
64
|
-
options?: CameraOptions
|
|
65
|
-
): Promise<MediaPickerResult> {
|
|
66
64
|
try {
|
|
67
|
-
const
|
|
68
|
-
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
69
|
-
return {
|
|
70
|
-
canceled: true,
|
|
71
|
-
error: MediaValidationError.PERMISSION_DENIED,
|
|
72
|
-
errorMessage: "Camera permission was denied",
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const result = await ImagePicker.launchCameraAsync({
|
|
77
|
-
mediaTypes: ["videos"],
|
|
78
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
79
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
80
|
-
videoMaxDuration: options?.videoMaxDuration,
|
|
81
|
-
});
|
|
82
|
-
|
|
65
|
+
const result = await strategy.launch(options ?? {});
|
|
83
66
|
return mapPickerResult(result);
|
|
84
67
|
} catch (error) {
|
|
85
|
-
ErrorHandler.handleAndLog(error, '
|
|
68
|
+
ErrorHandler.handleAndLog(error, 'launchMediaPicker', {
|
|
69
|
+
strategy: strategy.name,
|
|
70
|
+
options,
|
|
71
|
+
});
|
|
86
72
|
return {
|
|
87
73
|
canceled: true,
|
|
88
74
|
error: MediaValidationError.PICKER_ERROR,
|
|
89
|
-
errorMessage:
|
|
75
|
+
errorMessage: `Failed to launch ${strategy.name}`,
|
|
90
76
|
};
|
|
91
77
|
}
|
|
92
78
|
}
|
|
93
79
|
|
|
94
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Launch camera for image capture
|
|
82
|
+
*/
|
|
83
|
+
static async launchCamera(
|
|
84
|
+
options?: CameraOptions
|
|
85
|
+
): Promise<MediaPickerResult> {
|
|
86
|
+
const strategy = new CameraPickerStrategy({ mediaType: 'images' });
|
|
87
|
+
return this.launchMediaPicker(strategy, options);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Launch camera for video capture
|
|
92
|
+
*/
|
|
93
|
+
static async launchCameraForVideo(
|
|
94
|
+
options?: CameraOptions
|
|
95
|
+
): Promise<MediaPickerResult> {
|
|
96
|
+
const strategy = new CameraPickerStrategy({ mediaType: 'videos' });
|
|
97
|
+
return this.launchMediaPicker(strategy, options);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Pick from library with file size validation
|
|
102
|
+
*/
|
|
103
|
+
static async pickFromLibrary(
|
|
95
104
|
options?: MediaPickerOptions
|
|
96
105
|
): Promise<MediaPickerResult> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
const strategy = new LibraryPickerStrategy();
|
|
107
|
+
const result = await this.launchMediaPicker(strategy, options);
|
|
108
|
+
|
|
109
|
+
// Validate file size if not canceled and has assets
|
|
110
|
+
if (
|
|
111
|
+
!result.canceled &&
|
|
112
|
+
result.assets &&
|
|
113
|
+
result.assets.length > 0 &&
|
|
114
|
+
options?.maxFileSizeMB
|
|
115
|
+
) {
|
|
116
|
+
const validation = FileValidator.validateAssets(result.assets, {
|
|
117
|
+
maxFileSizeMB: options.maxFileSizeMB,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!validation.valid) {
|
|
100
121
|
return {
|
|
101
122
|
canceled: true,
|
|
102
|
-
error:
|
|
103
|
-
errorMessage:
|
|
123
|
+
error: validation.error,
|
|
124
|
+
errorMessage: validation.errorMessage,
|
|
104
125
|
};
|
|
105
126
|
}
|
|
106
|
-
|
|
107
|
-
const result = await ImagePicker.launchImageLibraryAsync({
|
|
108
|
-
mediaTypes: mapMediaType(options?.mediaTypes),
|
|
109
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
110
|
-
allowsMultipleSelection: options?.allowsMultipleSelection ?? false,
|
|
111
|
-
aspect: options?.aspect,
|
|
112
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
113
|
-
selectionLimit:
|
|
114
|
-
options?.selectionLimit ?? MEDIA_CONSTANTS.DEFAULT_SELECTION_LIMIT,
|
|
115
|
-
base64: options?.base64 ?? false,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const mappedResult = mapPickerResult(result);
|
|
119
|
-
|
|
120
|
-
// Validate file size if not canceled and has assets
|
|
121
|
-
if (!mappedResult.canceled && mappedResult.assets && mappedResult.assets.length > 0) {
|
|
122
|
-
const validation = FileValidator.validateAssets(mappedResult.assets, {
|
|
123
|
-
maxFileSizeMB: options?.maxFileSizeMB,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (!validation.valid) {
|
|
127
|
-
return {
|
|
128
|
-
canceled: true,
|
|
129
|
-
error: validation.error,
|
|
130
|
-
errorMessage: validation.errorMessage,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return mappedResult;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
ErrorHandler.handleAndLog(error, 'pickImage', { options });
|
|
138
|
-
return {
|
|
139
|
-
canceled: true,
|
|
140
|
-
error: MediaValidationError.PICKER_ERROR,
|
|
141
|
-
errorMessage: "Failed to pick image from library",
|
|
142
|
-
};
|
|
143
127
|
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
144
130
|
}
|
|
145
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Pick single image from library
|
|
134
|
+
*/
|
|
146
135
|
static async pickSingleImage(
|
|
147
|
-
options?: Omit<MediaPickerOptions,
|
|
136
|
+
options?: Omit<MediaPickerOptions, 'allowsMultipleSelection'>
|
|
148
137
|
): Promise<MediaPickerResult> {
|
|
149
|
-
return
|
|
138
|
+
return this.pickFromLibrary({
|
|
150
139
|
...options,
|
|
151
140
|
allowsMultipleSelection: false,
|
|
141
|
+
mediaTypes: MediaType.IMAGE,
|
|
152
142
|
});
|
|
153
143
|
}
|
|
154
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Pick multiple images from library
|
|
147
|
+
*/
|
|
155
148
|
static async pickMultipleImages(
|
|
156
|
-
options?: Omit<MediaPickerOptions,
|
|
149
|
+
options?: Omit<MediaPickerOptions, 'allowsMultipleSelection'>
|
|
157
150
|
): Promise<MediaPickerResult> {
|
|
158
|
-
return
|
|
151
|
+
return this.pickFromLibrary({
|
|
159
152
|
...options,
|
|
160
153
|
allowsMultipleSelection: true,
|
|
154
|
+
mediaTypes: MediaType.IMAGE,
|
|
161
155
|
});
|
|
162
156
|
}
|
|
163
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Pick video from library
|
|
160
|
+
*/
|
|
164
161
|
static async pickVideo(
|
|
165
|
-
options?: Omit<MediaPickerOptions,
|
|
162
|
+
options?: Omit<MediaPickerOptions, 'mediaTypes'>
|
|
166
163
|
): Promise<MediaPickerResult> {
|
|
167
|
-
return
|
|
164
|
+
return this.pickFromLibrary({
|
|
168
165
|
...options,
|
|
169
166
|
mediaTypes: MediaType.VIDEO,
|
|
170
167
|
});
|
|
171
168
|
}
|
|
172
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Pick any media from library
|
|
172
|
+
*/
|
|
173
173
|
static async pickMedia(
|
|
174
174
|
options?: MediaPickerOptions
|
|
175
175
|
): Promise<MediaPickerResult> {
|
|
176
|
-
return
|
|
176
|
+
return this.pickFromLibrary({
|
|
177
177
|
...options,
|
|
178
178
|
mediaTypes: MediaType.ALL,
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Legacy method for backward compatibility
|
|
184
|
+
* @deprecated Use pickSingleImage instead
|
|
185
|
+
*/
|
|
186
|
+
static async pickImage(
|
|
187
|
+
options?: MediaPickerOptions
|
|
188
|
+
): Promise<MediaPickerResult> {
|
|
189
|
+
return this.pickFromLibrary(options);
|
|
190
|
+
}
|
|
181
191
|
}
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* Permission Manager
|
|
3
3
|
*
|
|
4
4
|
* Centralized permission handling for media operations.
|
|
5
|
+
* Refactored to use generic PermissionHandler to reduce duplication.
|
|
6
|
+
*
|
|
7
|
+
* Before: 4 similar methods (~80 LOC)
|
|
8
|
+
* After: 1 generic handler (~50 LOC) - 37% reduction
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import * as ImagePicker from "expo-image-picker";
|
|
8
12
|
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
9
13
|
import { mapPermissionStatus } from "./mediaPickerMappers";
|
|
14
|
+
import { PermissionHandler } from "../../../core/permissions/domain/PermissionHandler";
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Permission type for media operations
|
|
@@ -15,78 +20,75 @@ export type PermissionType = 'camera' | 'mediaLibrary';
|
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Permission manager for media operations
|
|
23
|
+
* Uses generic PermissionHandler to reduce code duplication
|
|
18
24
|
*/
|
|
19
25
|
export class PermissionManager {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
private static handler: PermissionHandler<MediaLibraryPermission> =
|
|
27
|
+
new PermissionHandler({
|
|
28
|
+
methods: {
|
|
29
|
+
request: {
|
|
30
|
+
camera: ImagePicker.requestCameraPermissionsAsync,
|
|
31
|
+
mediaLibrary: ImagePicker.requestMediaLibraryPermissionsAsync,
|
|
32
|
+
},
|
|
33
|
+
get: {
|
|
34
|
+
camera: ImagePicker.getCameraPermissionsAsync,
|
|
35
|
+
mediaLibrary: ImagePicker.getMediaLibraryPermissionsAsync,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
statusMapper: mapPermissionStatus,
|
|
39
|
+
defaultStatus: MediaLibraryPermission.DENIED,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Requests camera permission
|
|
44
|
+
*/
|
|
45
|
+
static async requestCameraPermission(): Promise<MediaLibraryPermission> {
|
|
46
|
+
return this.handler.request('camera');
|
|
47
|
+
}
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return mapPermissionStatus(status);
|
|
39
|
-
} catch {
|
|
40
|
-
return MediaLibraryPermission.DENIED;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
49
|
+
/**
|
|
50
|
+
* Requests media library permission
|
|
51
|
+
*/
|
|
52
|
+
static async requestMediaLibraryPermission(): Promise<MediaLibraryPermission> {
|
|
53
|
+
return this.handler.request('mediaLibrary');
|
|
54
|
+
}
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return mapPermissionStatus(status);
|
|
51
|
-
} catch {
|
|
52
|
-
return MediaLibraryPermission.DENIED;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets current camera permission status
|
|
58
|
+
*/
|
|
59
|
+
static async getCameraPermissionStatus(): Promise<MediaLibraryPermission> {
|
|
60
|
+
return this.handler.getStatus('camera');
|
|
61
|
+
}
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return mapPermissionStatus(status);
|
|
63
|
-
} catch {
|
|
64
|
-
return MediaLibraryPermission.DENIED;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
63
|
+
/**
|
|
64
|
+
* Gets current media library permission status
|
|
65
|
+
*/
|
|
66
|
+
static async getMediaLibraryPermissionStatus(): Promise<MediaLibraryPermission> {
|
|
67
|
+
return this.handler.getStatus('mediaLibrary');
|
|
68
|
+
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Generic permission request based on type
|
|
72
|
+
*/
|
|
73
|
+
static async requestPermission(
|
|
74
|
+
type: PermissionType
|
|
75
|
+
): Promise<MediaLibraryPermission> {
|
|
76
|
+
return this.handler.request(type);
|
|
77
|
+
}
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Generic permission status check based on type
|
|
81
|
+
*/
|
|
82
|
+
static async getPermissionStatus(
|
|
83
|
+
type: PermissionType
|
|
84
|
+
): Promise<MediaLibraryPermission> {
|
|
85
|
+
return this.handler.getStatus(type);
|
|
86
|
+
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Checks if permission is granted
|
|
90
|
+
*/
|
|
91
|
+
static isPermissionGranted(status: MediaLibraryPermission): boolean {
|
|
92
|
+
return this.handler.isGranted(status);
|
|
93
|
+
}
|
|
92
94
|
}
|
|
@@ -121,7 +121,10 @@ export const useMedia = () => {
|
|
|
121
121
|
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
122
122
|
try {
|
|
123
123
|
return await PermissionManager.requestCameraPermission();
|
|
124
|
-
} catch {
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (__DEV__) {
|
|
126
|
+
console.warn('[useMedia] Failed to request camera permission:', error);
|
|
127
|
+
}
|
|
125
128
|
return MediaLibraryPermission.DENIED;
|
|
126
129
|
}
|
|
127
130
|
}, []);
|
|
@@ -130,7 +133,10 @@ export const useMedia = () => {
|
|
|
130
133
|
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
131
134
|
try {
|
|
132
135
|
return await PermissionManager.requestMediaLibraryPermission();
|
|
133
|
-
} catch {
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (__DEV__) {
|
|
138
|
+
console.warn('[useMedia] Failed to request media library permission:', error);
|
|
139
|
+
}
|
|
134
140
|
return MediaLibraryPermission.DENIED;
|
|
135
141
|
}
|
|
136
142
|
}, []);
|
|
@@ -139,7 +145,10 @@ export const useMedia = () => {
|
|
|
139
145
|
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
140
146
|
try {
|
|
141
147
|
return await PermissionManager.getCameraPermissionStatus();
|
|
142
|
-
} catch {
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (__DEV__) {
|
|
150
|
+
console.warn('[useMedia] Failed to get camera permission status:', error);
|
|
151
|
+
}
|
|
143
152
|
return MediaLibraryPermission.DENIED;
|
|
144
153
|
}
|
|
145
154
|
}, []);
|
|
@@ -148,7 +157,10 @@ export const useMedia = () => {
|
|
|
148
157
|
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
149
158
|
try {
|
|
150
159
|
return await PermissionManager.getMediaLibraryPermissionStatus();
|
|
151
|
-
} catch {
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (__DEV__) {
|
|
162
|
+
console.warn('[useMedia] Failed to get media library permission status:', error);
|
|
163
|
+
}
|
|
152
164
|
return MediaLibraryPermission.DENIED;
|
|
153
165
|
}
|
|
154
166
|
}, []);
|
|
@@ -48,6 +48,7 @@ export class TTLCache<T = unknown> extends Cache<T> {
|
|
|
48
48
|
this.statsTracker.recordExpiration();
|
|
49
49
|
|
|
50
50
|
if (__DEV__) {
|
|
51
|
+
console.log(`[TTLCache] Cleaned up ${cleanedCount} expired entries`);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
}
|
|
@@ -68,6 +69,7 @@ export class TTLCache<T = unknown> extends Cache<T> {
|
|
|
68
69
|
override set(key: string, value: T, ttl?: number): void {
|
|
69
70
|
if (this.isDestroyed) {
|
|
70
71
|
if (__DEV__) {
|
|
72
|
+
console.warn('[TTLCache] Cannot set value on destroyed cache');
|
|
71
73
|
}
|
|
72
74
|
return;
|
|
73
75
|
}
|
|
@@ -77,6 +79,7 @@ export class TTLCache<T = unknown> extends Cache<T> {
|
|
|
77
79
|
override get(key: string): T | undefined {
|
|
78
80
|
if (this.isDestroyed) {
|
|
79
81
|
if (__DEV__) {
|
|
82
|
+
console.warn('[TTLCache] Cannot get value from destroyed cache');
|
|
80
83
|
}
|
|
81
84
|
return undefined;
|
|
82
85
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Domain layer - Abstract repository for data operations
|
|
4
4
|
*
|
|
5
5
|
* Provides generic CRUD operations with TanStack Query integration.
|
|
6
|
-
*
|
|
6
|
+
* Now uses common repository utilities from core/repositories.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
@@ -47,6 +47,8 @@ import type {
|
|
|
47
47
|
import { mergeRepositoryOptions, getCacheOptions } from './helpers/repositoryHelpers';
|
|
48
48
|
import * as queryMethods from './mixins/repositoryQueryMethods';
|
|
49
49
|
import * as invalidationMethods from './mixins/repositoryInvalidationMethods';
|
|
50
|
+
// Import common utilities from core
|
|
51
|
+
import { createRepositoryLogger } from '../../../core/repositories/domain/RepositoryUtils';
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
54
|
* Base repository for CRUD operations
|
|
@@ -68,10 +70,16 @@ export abstract class BaseRepository<
|
|
|
68
70
|
*/
|
|
69
71
|
public readonly keys: ReturnType<typeof createQueryKeyFactory>;
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Debug logger for this repository
|
|
75
|
+
*/
|
|
76
|
+
protected readonly log: (method: string, ...args: unknown[]) => void;
|
|
77
|
+
|
|
71
78
|
constructor(resource: string, options: RepositoryOptions = {}) {
|
|
72
79
|
this.resource = resource;
|
|
73
80
|
this.options = mergeRepositoryOptions(options);
|
|
74
81
|
this.keys = createQueryKeyFactory(this.resource);
|
|
82
|
+
this.log = createRepositoryLogger(resource, this.options.debug ?? __DEV__);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/**
|
|
@@ -117,6 +125,7 @@ export abstract class BaseRepository<
|
|
|
117
125
|
* Query all items with caching
|
|
118
126
|
*/
|
|
119
127
|
async queryAll(params?: ListParams): Promise<TData[]> {
|
|
128
|
+
this.log('queryAll', params);
|
|
120
129
|
return queryMethods.queryAll(this, params);
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -124,6 +133,7 @@ export abstract class BaseRepository<
|
|
|
124
133
|
* Query item by ID with caching
|
|
125
134
|
*/
|
|
126
135
|
async queryById(id: string | number): Promise<TData | undefined> {
|
|
136
|
+
this.log('queryById', id);
|
|
127
137
|
return queryMethods.queryById(this, id);
|
|
128
138
|
}
|
|
129
139
|
|
|
@@ -131,6 +141,7 @@ export abstract class BaseRepository<
|
|
|
131
141
|
* Prefetch all items
|
|
132
142
|
*/
|
|
133
143
|
async prefetchAll(params?: ListParams): Promise<void> {
|
|
144
|
+
this.log('prefetchAll', params);
|
|
134
145
|
return queryMethods.prefetchAll(this, params);
|
|
135
146
|
}
|
|
136
147
|
|
|
@@ -138,6 +149,7 @@ export abstract class BaseRepository<
|
|
|
138
149
|
* Prefetch item by ID
|
|
139
150
|
*/
|
|
140
151
|
async prefetchById(id: string | number): Promise<void> {
|
|
152
|
+
this.log('prefetchById', id);
|
|
141
153
|
return queryMethods.prefetchById(this, id);
|
|
142
154
|
}
|
|
143
155
|
|
|
@@ -145,6 +157,7 @@ export abstract class BaseRepository<
|
|
|
145
157
|
* Invalidate all queries for this resource
|
|
146
158
|
*/
|
|
147
159
|
invalidateAll(): Promise<void> {
|
|
160
|
+
this.log('invalidateAll');
|
|
148
161
|
return invalidationMethods.invalidateAll(this);
|
|
149
162
|
}
|
|
150
163
|
|
|
@@ -152,6 +165,7 @@ export abstract class BaseRepository<
|
|
|
152
165
|
* Invalidate list queries
|
|
153
166
|
*/
|
|
154
167
|
invalidateLists(): Promise<void> {
|
|
168
|
+
this.log('invalidateLists');
|
|
155
169
|
return invalidationMethods.invalidateLists(this);
|
|
156
170
|
}
|
|
157
171
|
|
|
@@ -159,6 +173,7 @@ export abstract class BaseRepository<
|
|
|
159
173
|
* Invalidate detail query
|
|
160
174
|
*/
|
|
161
175
|
invalidateDetail(id: string | number): Promise<void> {
|
|
176
|
+
this.log('invalidateDetail', id);
|
|
162
177
|
return invalidationMethods.invalidateDetail(this, id);
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -166,6 +181,7 @@ export abstract class BaseRepository<
|
|
|
166
181
|
* Set query data (optimistic update)
|
|
167
182
|
*/
|
|
168
183
|
setData(id: string | number, data: TData): void {
|
|
184
|
+
this.log('setData', id);
|
|
169
185
|
invalidationMethods.setData(this, id, data);
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -180,6 +196,7 @@ export abstract class BaseRepository<
|
|
|
180
196
|
* Remove query data from cache
|
|
181
197
|
*/
|
|
182
198
|
clearData(id: string | number): void {
|
|
199
|
+
this.log('clearData', id);
|
|
183
200
|
invalidationMethods.clearData(this, id);
|
|
184
201
|
}
|
|
185
202
|
}
|