@umituz/react-native-design-system 4.27.14 → 4.27.16
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 +11 -6
- package/src/atoms/GlassView/GlassView.tsx +0 -2
- 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 +17 -0
- package/src/core/cache/infrastructure/CacheFactory.ts +102 -0
- package/src/core/index.ts +23 -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/hooks/index.ts +22 -25
- package/src/index.ts +1 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +32 -29
- package/src/media/domain/strategies/CameraPickerStrategy.ts +61 -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 +109 -110
- package/src/media/infrastructure/utils/PermissionManager.ts +68 -66
- package/src/media/infrastructure/utils/mediaPickerMappers.ts +29 -6
- package/src/media/presentation/hooks/useMedia.ts +16 -4
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +0 -1
- package/src/offline/index.ts +1 -1
- package/src/offline/presentation/hooks/useOffline.ts +0 -8
- package/src/storage/README.md +0 -1
- package/src/storage/cache/domain/types/README.md +0 -1
- package/src/storage/cache/infrastructure/TTLCache.ts +3 -0
- package/src/storage/cache/presentation/README.md +0 -1
- package/src/storage/cache/presentation/useCachedValue.ts +12 -2
- package/src/storage/domain/constants/README.md +0 -1
- package/src/storage/infrastructure/adapters/README.md +0 -1
- package/src/storage/presentation/hooks/README.md +0 -6
- package/src/storage/presentation/hooks/useStorageState.ts +13 -4
- package/src/tanstack/domain/repositories/BaseRepository.ts +18 -1
- package/src/theme/hooks/useAppDesignTokens.ts +29 -3
- package/src/timezone/infrastructure/services/TimezoneProvider.ts +2 -2
- package/src/uuid/infrastructure/utils/UUIDUtils.ts +4 -1
- package/src/init/index.ts +0 -29
- package/src/layouts/ScreenLayout/index.ts +0 -1
- package/src/layouts/index.ts +0 -5
- package/src/molecules/SearchBar/index.ts +0 -4
- package/src/molecules/StepProgress/index.ts +0 -1
- package/src/molecules/alerts/index.ts +0 -47
- package/src/molecules/bottom-sheet/index.ts +0 -10
- package/src/molecules/circular-menu/index.ts +0 -3
- package/src/molecules/filter-group/index.ts +0 -3
- package/src/molecules/index.ts +0 -38
- package/src/molecules/info-grid/index.ts +0 -3
- package/src/molecules/swipe-actions/index.ts +0 -6
- package/src/presentation/utils/variants/index.ts +0 -6
- package/src/timezone/infrastructure/utils/SimpleCache.ts +0 -109
- package/src/utilities/index.ts +0 -6
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Picker Strategies
|
|
3
|
+
*
|
|
4
|
+
* Strategy pattern implementations for different picker types.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type { PickerStrategy } from './PickerStrategy';
|
|
8
|
+
export type { LaunchOptions, PickerLaunchResult } from './PickerStrategy';
|
|
9
|
+
|
|
10
|
+
export { CameraPickerStrategy } from './CameraPickerStrategy';
|
|
11
|
+
export type { CameraPickerConfig } from './CameraPickerStrategy';
|
|
12
|
+
|
|
13
|
+
export { LibraryPickerStrategy } from './LibraryPickerStrategy';
|
|
@@ -2,178 +2,177 @@
|
|
|
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
|
-
|
|
18
|
-
} from
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { ErrorHandler } from "../../../utils/errors";
|
|
19
|
+
} from '../../domain/entities/Media';
|
|
20
|
+
import { mapPickerResultFromStrategy } from '../utils/mediaPickerMappers';
|
|
21
|
+
import { PermissionManager } from '../utils/PermissionManager';
|
|
22
|
+
import { FileValidator } from '../../domain/utils/FileValidator';
|
|
23
|
+
import { ErrorHandler } from '../../../utils/errors';
|
|
24
|
+
import type { PickerStrategy, LaunchOptions } from '../../domain/strategies/PickerStrategy';
|
|
25
|
+
import { CameraPickerStrategy } from '../../domain/strategies/CameraPickerStrategy';
|
|
26
|
+
import { LibraryPickerStrategy } from '../../domain/strategies/LibraryPickerStrategy';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Media picker service for selecting images/videos
|
|
30
|
+
* Uses strategy pattern to support different picker types
|
|
29
31
|
*/
|
|
30
32
|
export class MediaPickerService {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Generic media picker launcher using strategy pattern
|
|
35
|
+
*
|
|
36
|
+
* @param strategy - Picker strategy to use
|
|
37
|
+
* @param options - Picker options
|
|
38
|
+
* @returns Picker result
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const strategy = new CameraPickerStrategy({ mediaType: 'images' });
|
|
43
|
+
* const result = await MediaPickerService.launchMediaPicker(strategy, {
|
|
44
|
+
* quality: 0.8,
|
|
45
|
+
* allowsEditing: true
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
static async launchMediaPicker(
|
|
50
|
+
strategy: PickerStrategy,
|
|
51
|
+
options?: LaunchOptions
|
|
33
52
|
): 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 });
|
|
53
|
+
// Check permission
|
|
54
|
+
const permission = await strategy.getPermission();
|
|
55
|
+
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
55
56
|
return {
|
|
56
57
|
canceled: true,
|
|
57
|
-
error: MediaValidationError.
|
|
58
|
-
errorMessage:
|
|
58
|
+
error: MediaValidationError.PERMISSION_DENIED,
|
|
59
|
+
errorMessage: 'Permission was denied',
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
62
|
|
|
63
|
-
static async launchCameraForVideo(
|
|
64
|
-
options?: CameraOptions
|
|
65
|
-
): Promise<MediaPickerResult> {
|
|
66
63
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
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
|
-
|
|
83
|
-
return mapPickerResult(result);
|
|
64
|
+
const pickerResult = await strategy.launch(options ?? {});
|
|
65
|
+
return mapPickerResultFromStrategy(pickerResult);
|
|
84
66
|
} catch (error) {
|
|
85
|
-
ErrorHandler.handleAndLog(error, '
|
|
67
|
+
ErrorHandler.handleAndLog(error, 'launchMediaPicker', {
|
|
68
|
+
strategy: strategy.name,
|
|
69
|
+
options,
|
|
70
|
+
});
|
|
86
71
|
return {
|
|
87
72
|
canceled: true,
|
|
88
73
|
error: MediaValidationError.PICKER_ERROR,
|
|
89
|
-
errorMessage:
|
|
74
|
+
errorMessage: `Failed to launch ${strategy.name}`,
|
|
90
75
|
};
|
|
91
76
|
}
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Launch camera for image capture
|
|
81
|
+
*/
|
|
82
|
+
static async launchCamera(
|
|
83
|
+
options?: CameraOptions
|
|
84
|
+
): Promise<MediaPickerResult> {
|
|
85
|
+
const strategy = new CameraPickerStrategy({ mediaType: 'images' });
|
|
86
|
+
return this.launchMediaPicker(strategy, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Launch camera for video capture
|
|
91
|
+
*/
|
|
92
|
+
static async launchCameraForVideo(
|
|
93
|
+
options?: CameraOptions
|
|
94
|
+
): Promise<MediaPickerResult> {
|
|
95
|
+
const strategy = new CameraPickerStrategy({ mediaType: 'videos' });
|
|
96
|
+
return this.launchMediaPicker(strategy, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pick from library with file size validation
|
|
101
|
+
*/
|
|
102
|
+
static async pickFromLibrary(
|
|
95
103
|
options?: MediaPickerOptions
|
|
96
104
|
): Promise<MediaPickerResult> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
const strategy = new LibraryPickerStrategy();
|
|
106
|
+
const result = await this.launchMediaPicker(strategy, options);
|
|
107
|
+
|
|
108
|
+
// Validate file size if not canceled and has assets
|
|
109
|
+
if (
|
|
110
|
+
!result.canceled &&
|
|
111
|
+
result.assets &&
|
|
112
|
+
result.assets.length > 0 &&
|
|
113
|
+
options?.maxFileSizeMB
|
|
114
|
+
) {
|
|
115
|
+
const validation = FileValidator.validateAssets(result.assets, {
|
|
116
|
+
maxFileSizeMB: options.maxFileSizeMB,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!validation.valid) {
|
|
100
120
|
return {
|
|
101
121
|
canceled: true,
|
|
102
|
-
error:
|
|
103
|
-
errorMessage:
|
|
122
|
+
error: validation.error,
|
|
123
|
+
errorMessage: validation.errorMessage,
|
|
104
124
|
};
|
|
105
125
|
}
|
|
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
126
|
}
|
|
127
|
+
|
|
128
|
+
return result;
|
|
144
129
|
}
|
|
145
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Pick single image from library
|
|
133
|
+
*/
|
|
146
134
|
static async pickSingleImage(
|
|
147
|
-
options?: Omit<MediaPickerOptions,
|
|
135
|
+
options?: Omit<MediaPickerOptions, 'allowsMultipleSelection'>
|
|
148
136
|
): Promise<MediaPickerResult> {
|
|
149
|
-
return
|
|
137
|
+
return this.pickFromLibrary({
|
|
150
138
|
...options,
|
|
151
139
|
allowsMultipleSelection: false,
|
|
140
|
+
mediaTypes: MediaType.IMAGE,
|
|
152
141
|
});
|
|
153
142
|
}
|
|
154
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Pick multiple images from library
|
|
146
|
+
*/
|
|
155
147
|
static async pickMultipleImages(
|
|
156
|
-
options?: Omit<MediaPickerOptions,
|
|
148
|
+
options?: Omit<MediaPickerOptions, 'allowsMultipleSelection'>
|
|
157
149
|
): Promise<MediaPickerResult> {
|
|
158
|
-
return
|
|
150
|
+
return this.pickFromLibrary({
|
|
159
151
|
...options,
|
|
160
152
|
allowsMultipleSelection: true,
|
|
153
|
+
mediaTypes: MediaType.IMAGE,
|
|
161
154
|
});
|
|
162
155
|
}
|
|
163
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Pick video from library
|
|
159
|
+
*/
|
|
164
160
|
static async pickVideo(
|
|
165
|
-
options?: Omit<MediaPickerOptions,
|
|
161
|
+
options?: Omit<MediaPickerOptions, 'mediaTypes'>
|
|
166
162
|
): Promise<MediaPickerResult> {
|
|
167
|
-
return
|
|
163
|
+
return this.pickFromLibrary({
|
|
168
164
|
...options,
|
|
169
165
|
mediaTypes: MediaType.VIDEO,
|
|
170
166
|
});
|
|
171
167
|
}
|
|
172
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Pick any media from library
|
|
171
|
+
*/
|
|
173
172
|
static async pickMedia(
|
|
174
173
|
options?: MediaPickerOptions
|
|
175
174
|
): Promise<MediaPickerResult> {
|
|
176
|
-
return
|
|
175
|
+
return this.pickFromLibrary({
|
|
177
176
|
...options,
|
|
178
177
|
mediaTypes: MediaType.ALL,
|
|
179
178
|
});
|
|
@@ -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
|
}
|
|
@@ -15,15 +15,13 @@ import {
|
|
|
15
15
|
* Map expo-image-picker permission status to MediaLibraryPermission
|
|
16
16
|
*/
|
|
17
17
|
export const mapPermissionStatus = (
|
|
18
|
-
status:
|
|
18
|
+
status: string
|
|
19
19
|
): MediaLibraryPermission => {
|
|
20
20
|
switch (status) {
|
|
21
|
-
case
|
|
21
|
+
case 'granted':
|
|
22
22
|
return MediaLibraryPermission.GRANTED;
|
|
23
|
-
case
|
|
24
|
-
|
|
25
|
-
case ImagePicker.PermissionStatus.UNDETERMINED:
|
|
26
|
-
return MediaLibraryPermission.DENIED;
|
|
23
|
+
case 'denied':
|
|
24
|
+
case 'undetermined':
|
|
27
25
|
default:
|
|
28
26
|
return MediaLibraryPermission.DENIED;
|
|
29
27
|
}
|
|
@@ -74,3 +72,28 @@ export const mapPickerResult = (
|
|
|
74
72
|
assets,
|
|
75
73
|
};
|
|
76
74
|
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Map PickerStrategy result to MediaPickerResult
|
|
78
|
+
*/
|
|
79
|
+
export const mapPickerResultFromStrategy = (
|
|
80
|
+
result: { canceled: boolean; assets?: Array<{ uri: string; width?: number; height?: number; type?: 'image' | 'video'; duration?: number; fileSize?: number }> }
|
|
81
|
+
): MediaPickerResult => {
|
|
82
|
+
if (result.canceled) {
|
|
83
|
+
return { canceled: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const assets: MediaAsset[] = (result.assets ?? []).map((asset) => ({
|
|
87
|
+
uri: asset.uri,
|
|
88
|
+
width: asset.width ?? 0,
|
|
89
|
+
height: asset.height ?? 0,
|
|
90
|
+
type: asset.type === 'video' ? MediaType.VIDEO : MediaType.IMAGE,
|
|
91
|
+
fileSize: asset.fileSize,
|
|
92
|
+
duration: asset.duration,
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
canceled: false,
|
|
97
|
+
assets,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -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
|
}, []);
|
package/src/offline/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { useOfflineStore } from './infrastructure/storage/OfflineStore';
|
|
|
17
17
|
export { useOfflineConfigStore } from './infrastructure/storage/OfflineConfigStore';
|
|
18
18
|
|
|
19
19
|
// Hooks
|
|
20
|
-
export { useOffline
|
|
20
|
+
export { useOffline } from './presentation/hooks/useOffline';
|
|
21
21
|
export { useOfflineState } from './presentation/hooks/useOfflineState';
|
|
22
22
|
export { useOfflineWithMutations } from './presentation/hooks/useOfflineWithMutations';
|
|
23
23
|
|
|
@@ -38,14 +38,6 @@ const toNetworkState = (state: ExpoNetworkState): NetworkState => ({
|
|
|
38
38
|
details: null,
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
* Configure offline settings globally
|
|
43
|
-
* This is a facade over the config store for backward compatibility
|
|
44
|
-
*/
|
|
45
|
-
export const configureOffline = (config: OfflineConfig): void => {
|
|
46
|
-
useOfflineConfigStore.getState().setConfig(config);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
41
|
export const useOffline = (config?: OfflineConfig) => {
|
|
50
42
|
const store = useOfflineStore();
|
|
51
43
|
const globalConfig = useOfflineConfigStore((state) => state.config);
|
package/src/storage/README.md
CHANGED
|
@@ -118,7 +118,6 @@ Clean Architecture with DDD principles:
|
|
|
118
118
|
- MUST export from index.ts at root level
|
|
119
119
|
- MUST organize exports by module
|
|
120
120
|
- MUST provide TypeScript types
|
|
121
|
-
- MUST maintain backward compatibility
|
|
122
121
|
- MUST not export internal utilities
|
|
123
122
|
|
|
124
123
|
### File Organization
|
|
@@ -103,5 +103,4 @@ TypeScript type definitions for cache entries, configuration, statistics, and ev
|
|
|
103
103
|
### Export Rules
|
|
104
104
|
- MUST export all public types
|
|
105
105
|
- MUST use `type` keyword for type-only exports
|
|
106
|
-
- MUST maintain backward compatibility
|
|
107
106
|
- MUST document type changes
|
|
@@ -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
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* useCachedValue Hook
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useCallback,
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
6
|
import { cacheManager } from '../domain/CacheManager';
|
|
7
7
|
import type { CacheConfig } from '../domain/types/Cache';
|
|
8
8
|
import { useAsyncOperation } from '../../../utils/hooks';
|
|
@@ -16,6 +16,15 @@ export function useCachedValue<T>(
|
|
|
16
16
|
const fetcherRef = useRef(fetcher);
|
|
17
17
|
const configRef = useRef(config);
|
|
18
18
|
|
|
19
|
+
// Update refs when props change
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
fetcherRef.current = fetcher;
|
|
22
|
+
}, [fetcher]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
configRef.current = config;
|
|
26
|
+
}, [config]);
|
|
27
|
+
|
|
19
28
|
const { data: value, isLoading, error, execute, setData } = useAsyncOperation<T | undefined, Error>(
|
|
20
29
|
async () => {
|
|
21
30
|
const cache = cacheManager.getCache<T>(cacheName, configRef.current);
|
|
@@ -55,8 +64,9 @@ export function useCachedValue<T>(
|
|
|
55
64
|
}, [cacheName, setData]);
|
|
56
65
|
|
|
57
66
|
const refetch = useCallback(() => {
|
|
67
|
+
// Clear data first, then execute on next tick
|
|
58
68
|
setData(undefined);
|
|
59
|
-
execute();
|
|
69
|
+
Promise.resolve().then(() => execute());
|
|
60
70
|
}, [execute, setData]);
|
|
61
71
|
|
|
62
72
|
return useMemo(() => ({
|