@umituz/react-native-design-system 4.23.116 → 4.23.118
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 -1
- package/src/device/presentation/hooks/useAnonymousUser.ts +25 -31
- package/src/device/presentation/hooks/useDeviceInfo.ts +47 -91
- package/src/init/useAppInitialization.ts +24 -57
- package/src/media/domain/entities/Media.ts +1 -0
- package/src/media/infrastructure/utils/media-collection-utils.ts +9 -6
- package/src/media/presentation/hooks/useMedia.ts +73 -128
- package/src/molecules/alerts/AlertBanner.tsx +24 -46
- package/src/molecules/alerts/AlertInline.tsx +8 -9
- package/src/molecules/alerts/AlertModal.tsx +23 -14
- package/src/molecules/alerts/AlertToast.tsx +25 -53
- package/src/molecules/alerts/components/AlertContent.tsx +79 -0
- package/src/molecules/alerts/components/AlertIcon.tsx +31 -0
- package/src/molecules/alerts/components/index.ts +6 -0
- package/src/molecules/alerts/hooks/index.ts +6 -0
- package/src/molecules/alerts/hooks/useAlertAutoDismiss.ts +26 -0
- package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +21 -0
- package/src/molecules/alerts/utils/alertUtils.ts +0 -21
- package/src/storage/cache/presentation/useCachedValue.ts +24 -65
- package/src/storage/presentation/hooks/useStorageState.ts +20 -29
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +75 -140
- package/src/utils/errors/DesignSystemError.ts +57 -1
- package/src/utils/errors/ErrorHandler.ts +105 -1
- package/src/utils/errors/adapters/CacheErrorAdapter.ts +68 -0
- package/src/utils/errors/adapters/ImageErrorAdapter.ts +91 -0
- package/src/utils/errors/adapters/StorageErrorAdapter.ts +107 -0
- package/src/utils/errors/index.ts +5 -1
- package/src/utils/errors/types/Result.ts +64 -0
- package/src/utils/hooks/index.ts +12 -0
- package/src/utils/hooks/types/AsyncOperationTypes.ts +75 -0
- package/src/utils/hooks/useAsyncOperation.ts +223 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.118",
|
|
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, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @layer presentation/hooks
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { useMemo, useCallback } from 'react';
|
|
15
15
|
import { PersistentDeviceIdService } from '../../infrastructure/services/PersistentDeviceIdService';
|
|
16
16
|
import { DeviceService } from '../../infrastructure/services/DeviceService';
|
|
17
17
|
import type {
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
UseAnonymousUserOptions,
|
|
20
20
|
UseAnonymousUserResult,
|
|
21
21
|
} from '../../domain/types/AnonymousUserTypes';
|
|
22
|
+
import { useAsyncOperation } from '../../../utils/hooks';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* useAnonymousUser hook for persistent device-based user identification
|
|
@@ -43,56 +44,49 @@ import type {
|
|
|
43
44
|
export const useAnonymousUser = (
|
|
44
45
|
options?: UseAnonymousUserOptions
|
|
45
46
|
): UseAnonymousUserResult => {
|
|
46
|
-
const [anonymousUser, setAnonymousUser] = useState<AnonymousUser | null>(null);
|
|
47
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
48
|
-
const [error, setError] = useState<string | null>(null);
|
|
49
|
-
|
|
50
47
|
const {
|
|
51
48
|
anonymousDisplayName = 'Anonymous',
|
|
52
49
|
fallbackUserId = 'anonymous_fallback',
|
|
53
50
|
} = options || {};
|
|
54
51
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
setError(null);
|
|
58
|
-
|
|
59
|
-
try {
|
|
52
|
+
const { data: anonymousUser, isLoading, error, execute } = useAsyncOperation<AnonymousUser, string>(
|
|
53
|
+
async () => {
|
|
60
54
|
const [userId, deviceName] = await Promise.all([
|
|
61
55
|
PersistentDeviceIdService.getDeviceId(),
|
|
62
56
|
DeviceService.getUserFriendlyId(),
|
|
63
57
|
]);
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
return {
|
|
66
60
|
userId: userId || fallbackUserId,
|
|
67
61
|
deviceName: deviceName || 'Unknown Device',
|
|
68
62
|
displayName: anonymousDisplayName,
|
|
69
63
|
isAnonymous: true,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
immediate: true,
|
|
68
|
+
initialData: null,
|
|
69
|
+
errorHandler: () => 'Failed to generate device ID',
|
|
70
|
+
onError: () => {
|
|
71
|
+
// Fallback on error - set default anonymous user
|
|
72
|
+
return {
|
|
73
|
+
userId: fallbackUserId,
|
|
74
|
+
deviceName: 'Unknown Device',
|
|
75
|
+
displayName: anonymousDisplayName,
|
|
76
|
+
isAnonymous: true,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
81
79
|
}
|
|
82
|
-
|
|
80
|
+
);
|
|
83
81
|
|
|
84
82
|
const refresh = useCallback(async () => {
|
|
85
|
-
await
|
|
86
|
-
}, [
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
loadAnonymousUser();
|
|
90
|
-
}, [loadAnonymousUser]);
|
|
83
|
+
await execute();
|
|
84
|
+
}, [execute]);
|
|
91
85
|
|
|
92
|
-
return {
|
|
86
|
+
return useMemo(() => ({
|
|
93
87
|
anonymousUser,
|
|
94
88
|
isLoading,
|
|
95
89
|
error,
|
|
96
90
|
refresh,
|
|
97
|
-
};
|
|
91
|
+
}), [anonymousUser, isLoading, error, refresh]);
|
|
98
92
|
};
|
|
@@ -8,108 +8,57 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
8
8
|
import { DeviceService } from '../../infrastructure/services/DeviceService';
|
|
9
9
|
import { PersistentDeviceIdService } from '../../infrastructure/services/PersistentDeviceIdService';
|
|
10
10
|
import type { DeviceInfo, ApplicationInfo, SystemInfo } from '../../domain/entities/Device';
|
|
11
|
-
|
|
12
|
-
function useAsyncData<T, E = string>(
|
|
13
|
-
fetchFn: () => Promise<T>,
|
|
14
|
-
initialData: T | null = null
|
|
15
|
-
) {
|
|
16
|
-
const [data, setData] = useState<T | null>(initialData);
|
|
17
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
18
|
-
const [error, setError] = useState<E | null>(null);
|
|
19
|
-
const isMountedRef = useRef(true);
|
|
20
|
-
|
|
21
|
-
const execute = useCallback(async () => {
|
|
22
|
-
if (!isMountedRef.current) return;
|
|
23
|
-
|
|
24
|
-
setIsLoading(true);
|
|
25
|
-
setError(null);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const result = await fetchFn();
|
|
29
|
-
if (isMountedRef.current) setData(result);
|
|
30
|
-
} catch (err) {
|
|
31
|
-
if (isMountedRef.current) {
|
|
32
|
-
setError(err instanceof Error ? (err.message as E) : ('Failed to load data' as E));
|
|
33
|
-
}
|
|
34
|
-
} finally {
|
|
35
|
-
if (isMountedRef.current) setIsLoading(false);
|
|
36
|
-
}
|
|
37
|
-
}, [fetchFn]);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
isMountedRef.current = true;
|
|
41
|
-
execute();
|
|
42
|
-
return () => {
|
|
43
|
-
isMountedRef.current = false;
|
|
44
|
-
};
|
|
45
|
-
}, [execute]);
|
|
46
|
-
|
|
47
|
-
return { data, isLoading, error, execute };
|
|
48
|
-
}
|
|
11
|
+
import { useAsyncOperation } from '../../../utils/hooks';
|
|
49
12
|
|
|
50
13
|
export const useDeviceInfo = () => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!isMountedRef.current) return;
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const system = await DeviceService.getSystemInfo();
|
|
61
|
-
if (isMountedRef.current) {
|
|
62
|
-
setSystemInfo(system);
|
|
63
|
-
setDeviceInfo(system.device);
|
|
64
|
-
setAppInfo(system.application);
|
|
65
|
-
}
|
|
66
|
-
} catch {
|
|
67
|
-
// Silent error handling
|
|
14
|
+
const systemOp = useAsyncOperation(
|
|
15
|
+
() => DeviceService.getSystemInfo(),
|
|
16
|
+
{
|
|
17
|
+
immediate: true,
|
|
18
|
+
initialData: null,
|
|
19
|
+
errorHandler: () => null, // Silent error handling
|
|
68
20
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Silent error handling
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const deviceOp = useAsyncOperation(
|
|
24
|
+
() => DeviceService.getDeviceInfo(),
|
|
25
|
+
{
|
|
26
|
+
immediate: false,
|
|
27
|
+
initialData: null,
|
|
28
|
+
errorHandler: () => null,
|
|
78
29
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Silent error handling
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const appOp = useAsyncOperation(
|
|
33
|
+
() => DeviceService.getApplicationInfo(),
|
|
34
|
+
{
|
|
35
|
+
immediate: false,
|
|
36
|
+
initialData: null,
|
|
37
|
+
errorHandler: () => null,
|
|
88
38
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
isMountedRef.current = true;
|
|
93
|
-
loadInfo();
|
|
94
|
-
return () => {
|
|
95
|
-
isMountedRef.current = false;
|
|
96
|
-
};
|
|
97
|
-
}, [loadInfo]);
|
|
39
|
+
);
|
|
98
40
|
|
|
99
41
|
return {
|
|
100
|
-
deviceInfo,
|
|
101
|
-
appInfo,
|
|
102
|
-
systemInfo,
|
|
103
|
-
isLoading:
|
|
42
|
+
deviceInfo: systemOp.data?.device ?? deviceOp.data,
|
|
43
|
+
appInfo: systemOp.data?.application ?? appOp.data,
|
|
44
|
+
systemInfo: systemOp.data,
|
|
45
|
+
isLoading: systemOp.isLoading || deviceOp.isLoading || appOp.isLoading,
|
|
104
46
|
error: null,
|
|
105
|
-
refresh:
|
|
106
|
-
loadDeviceInfo,
|
|
107
|
-
loadAppInfo,
|
|
47
|
+
refresh: systemOp.execute,
|
|
48
|
+
loadDeviceInfo: deviceOp.execute,
|
|
49
|
+
loadAppInfo: appOp.execute,
|
|
108
50
|
};
|
|
109
51
|
};
|
|
110
52
|
|
|
111
53
|
export const useDeviceCapabilities = () => {
|
|
112
|
-
const { data: capabilities, isLoading } =
|
|
54
|
+
const { data: capabilities, isLoading } = useAsyncOperation(
|
|
55
|
+
() => DeviceService.getDeviceCapabilities(),
|
|
56
|
+
{
|
|
57
|
+
immediate: true,
|
|
58
|
+
initialData: null,
|
|
59
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to load device capabilities',
|
|
60
|
+
}
|
|
61
|
+
);
|
|
113
62
|
|
|
114
63
|
return {
|
|
115
64
|
isDevice: capabilities?.isDevice ?? false,
|
|
@@ -121,7 +70,14 @@ export const useDeviceCapabilities = () => {
|
|
|
121
70
|
};
|
|
122
71
|
|
|
123
72
|
export const useDeviceId = () => {
|
|
124
|
-
const { data: deviceId, isLoading } =
|
|
73
|
+
const { data: deviceId, isLoading } = useAsyncOperation(
|
|
74
|
+
() => PersistentDeviceIdService.getDeviceId(),
|
|
75
|
+
{
|
|
76
|
+
immediate: true,
|
|
77
|
+
initialData: null,
|
|
78
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to load device ID',
|
|
79
|
+
}
|
|
80
|
+
);
|
|
125
81
|
|
|
126
82
|
return { deviceId, isLoading };
|
|
127
83
|
};
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Manages app initialization state in React components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { useMemo } from "react";
|
|
7
7
|
import type {
|
|
8
8
|
UseAppInitializationOptions,
|
|
9
9
|
UseAppInitializationReturn,
|
|
10
10
|
AppInitializerResult,
|
|
11
11
|
} from "./types";
|
|
12
|
+
import { useAsyncOperation } from "../utils/hooks";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Hook to manage app initialization
|
|
@@ -29,64 +30,30 @@ export function useAppInitialization(
|
|
|
29
30
|
): UseAppInitializationReturn {
|
|
30
31
|
const { skip = false, onReady, onError } = options;
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const { data, isLoading, error } = useAsyncOperation<AppInitializerResult, Error>(
|
|
34
|
+
async () => {
|
|
35
|
+
const result = await initializer();
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
onReadyRef.current = onReady;
|
|
42
|
-
onErrorRef.current = onError;
|
|
43
|
-
}, [onReady, onError]);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (skip) {
|
|
47
|
-
setIsReady(true);
|
|
48
|
-
setIsLoading(false);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let cancelled = false;
|
|
53
|
-
|
|
54
|
-
const initialize = async () => {
|
|
55
|
-
try {
|
|
56
|
-
setIsLoading(true);
|
|
57
|
-
setError(null);
|
|
58
|
-
|
|
59
|
-
const result = await initializer();
|
|
60
|
-
|
|
61
|
-
if (cancelled) return;
|
|
62
|
-
|
|
63
|
-
if (!result.success && result.failedModules.length > 0) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Initialization failed: ${result.failedModules.join(", ")}`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
setIsReady(true);
|
|
70
|
-
onReadyRef.current?.();
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (cancelled) return;
|
|
73
|
-
|
|
74
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
75
|
-
setError(error);
|
|
76
|
-
onErrorRef.current?.(error);
|
|
77
|
-
} finally {
|
|
78
|
-
if (!cancelled) {
|
|
79
|
-
setIsLoading(false);
|
|
80
|
-
}
|
|
37
|
+
if (!result.success && result.failedModules.length > 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Initialization failed: ${result.failedModules.join(", ")}`
|
|
40
|
+
);
|
|
81
41
|
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
initialize();
|
|
85
42
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
43
|
+
return result;
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
immediate: !skip,
|
|
47
|
+
skip,
|
|
48
|
+
errorHandler: (err) => err instanceof Error ? err : new Error(String(err)),
|
|
49
|
+
onSuccess: () => onReady?.(),
|
|
50
|
+
onError: (err) => onError?.(err),
|
|
51
|
+
}
|
|
52
|
+
);
|
|
90
53
|
|
|
91
|
-
return
|
|
54
|
+
return useMemo(() => ({
|
|
55
|
+
isReady: data?.success ?? false,
|
|
56
|
+
isLoading,
|
|
57
|
+
error,
|
|
58
|
+
}), [data, isLoading, error]);
|
|
92
59
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Generic utilities for working with media collections
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { formatFileSize as formatFileSizeUtil } from "../../../utils/formatters/stringFormatter";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type MediaType = "image" | "audio" | "video";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Interface for media items with type property
|
|
@@ -35,9 +35,12 @@ export function calculateTotalSize(
|
|
|
35
35
|
return media.reduce((total, m) => total + m.fileSize, 0);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Format file size in bytes to human-readable format
|
|
40
|
+
*
|
|
41
|
+
* @param bytes - File size in bytes
|
|
42
|
+
* @returns Formatted file size string (e.g., "1.5 MB")
|
|
43
|
+
*/
|
|
38
44
|
export function formatFileSize(bytes: number): string {
|
|
39
|
-
|
|
40
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
41
|
-
const size = Math.round((bytes / Math.pow(1024, i)) * 100) / 100;
|
|
42
|
-
return `${size} ${FILE_SIZE_UNITS[i]}`;
|
|
45
|
+
return formatFileSizeUtil(bytes, { decimals: 2 });
|
|
43
46
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides camera, gallery picking functionality.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { useCallback } from "react";
|
|
9
9
|
import { MediaPickerService } from "../../infrastructure/services/MediaPickerService";
|
|
10
10
|
import { PermissionManager } from "../../infrastructure/utils/PermissionManager";
|
|
11
11
|
import type {
|
|
@@ -14,166 +14,107 @@ import type {
|
|
|
14
14
|
CameraOptions,
|
|
15
15
|
} from "../../domain/entities/Media";
|
|
16
16
|
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
17
|
+
import { useAsyncOperation } from "../../../utils/hooks";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* useMedia hook for complete media workflow
|
|
20
|
-
*
|
|
21
|
-
* USAGE:
|
|
22
|
-
* ```typescript
|
|
23
|
-
* const {
|
|
24
|
-
* pickImage,
|
|
25
|
-
* pickMultipleImages,
|
|
26
|
-
* launchCamera,
|
|
27
|
-
* isLoading,
|
|
28
|
-
* error,
|
|
29
|
-
* } = useMedia();
|
|
30
|
-
*
|
|
31
|
-
* const handlePickImage = async () => {
|
|
32
|
-
* const result = await pickImage({ allowsEditing: true });
|
|
33
|
-
* if (!result.canceled && result.assets) {
|
|
34
|
-
* }
|
|
35
|
-
* };
|
|
36
|
-
* ```
|
|
37
21
|
*/
|
|
38
22
|
export const useMedia = () => {
|
|
39
|
-
|
|
40
|
-
const
|
|
23
|
+
// Create async operations for each media picker function
|
|
24
|
+
const pickImageOp = useAsyncOperation<MediaPickerResult, string>(
|
|
25
|
+
(options?: MediaPickerOptions) => MediaPickerService.pickSingleImage(options),
|
|
26
|
+
{
|
|
27
|
+
immediate: false,
|
|
28
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to pick image',
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const pickMultipleImagesOp = useAsyncOperation<MediaPickerResult, string>(
|
|
33
|
+
(options?: MediaPickerOptions) => MediaPickerService.pickMultipleImages(options),
|
|
34
|
+
{
|
|
35
|
+
immediate: false,
|
|
36
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to pick images',
|
|
37
|
+
}
|
|
38
|
+
);
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
40
|
+
const pickVideoOp = useAsyncOperation<MediaPickerResult, string>(
|
|
41
|
+
(options?: MediaPickerOptions) => MediaPickerService.pickVideo(options),
|
|
42
|
+
{
|
|
43
|
+
immediate: false,
|
|
44
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to pick video',
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const launchCameraOp = useAsyncOperation<MediaPickerResult, string>(
|
|
49
|
+
(options?: CameraOptions) => MediaPickerService.launchCamera(options),
|
|
50
|
+
{
|
|
51
|
+
immediate: false,
|
|
52
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to launch camera',
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const launchCameraVideoOp = useAsyncOperation<MediaPickerResult, string>(
|
|
57
|
+
(options?: CameraOptions) => MediaPickerService.launchCameraForVideo(options),
|
|
58
|
+
{
|
|
59
|
+
immediate: false,
|
|
60
|
+
errorHandler: (err) => err instanceof Error ? err.message : 'Failed to record video',
|
|
61
|
+
}
|
|
62
|
+
);
|
|
49
63
|
|
|
64
|
+
// Wrap execute calls to handle MediaPickerResult validation
|
|
50
65
|
const pickImage = useCallback(
|
|
51
66
|
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
setError(
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
const result = await MediaPickerService.pickSingleImage(options);
|
|
58
|
-
// Set error from validation result if present
|
|
59
|
-
if (result.errorMessage && isMountedRef.current) {
|
|
60
|
-
setError(result.errorMessage);
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
} catch (err) {
|
|
64
|
-
const errorMessage =
|
|
65
|
-
err instanceof Error ? err.message : "Failed to pick image";
|
|
66
|
-
if (isMountedRef.current) {
|
|
67
|
-
setError(errorMessage);
|
|
68
|
-
}
|
|
69
|
-
return { canceled: true };
|
|
70
|
-
} finally {
|
|
71
|
-
if (isMountedRef.current) {
|
|
72
|
-
setIsLoading(false);
|
|
73
|
-
}
|
|
67
|
+
const result = await pickImageOp.execute(options);
|
|
68
|
+
if (result?.errorMessage) {
|
|
69
|
+
pickImageOp.setError(result.errorMessage);
|
|
74
70
|
}
|
|
71
|
+
return result ?? { canceled: true };
|
|
75
72
|
},
|
|
76
|
-
[]
|
|
73
|
+
[pickImageOp]
|
|
77
74
|
);
|
|
78
75
|
|
|
79
76
|
const pickMultipleImages = useCallback(
|
|
80
77
|
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setError(
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const result = await MediaPickerService.pickMultipleImages(options);
|
|
87
|
-
return result;
|
|
88
|
-
} catch (err) {
|
|
89
|
-
const errorMessage =
|
|
90
|
-
err instanceof Error ? err.message : "Failed to pick images";
|
|
91
|
-
if (isMountedRef.current) {
|
|
92
|
-
setError(errorMessage);
|
|
93
|
-
}
|
|
94
|
-
return { canceled: true };
|
|
95
|
-
} finally {
|
|
96
|
-
if (isMountedRef.current) {
|
|
97
|
-
setIsLoading(false);
|
|
98
|
-
}
|
|
78
|
+
const result = await pickMultipleImagesOp.execute(options);
|
|
79
|
+
if (result?.errorMessage) {
|
|
80
|
+
pickMultipleImagesOp.setError(result.errorMessage);
|
|
99
81
|
}
|
|
82
|
+
return result ?? { canceled: true };
|
|
100
83
|
},
|
|
101
|
-
[]
|
|
84
|
+
[pickMultipleImagesOp]
|
|
102
85
|
);
|
|
103
86
|
|
|
104
87
|
const pickVideo = useCallback(
|
|
105
88
|
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
setError(
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const result = await MediaPickerService.pickVideo(options);
|
|
112
|
-
return result;
|
|
113
|
-
} catch (err) {
|
|
114
|
-
const errorMessage =
|
|
115
|
-
err instanceof Error ? err.message : "Failed to pick video";
|
|
116
|
-
if (isMountedRef.current) {
|
|
117
|
-
setError(errorMessage);
|
|
118
|
-
}
|
|
119
|
-
return { canceled: true };
|
|
120
|
-
} finally {
|
|
121
|
-
if (isMountedRef.current) {
|
|
122
|
-
setIsLoading(false);
|
|
123
|
-
}
|
|
89
|
+
const result = await pickVideoOp.execute(options);
|
|
90
|
+
if (result?.errorMessage) {
|
|
91
|
+
pickVideoOp.setError(result.errorMessage);
|
|
124
92
|
}
|
|
93
|
+
return result ?? { canceled: true };
|
|
125
94
|
},
|
|
126
|
-
[]
|
|
95
|
+
[pickVideoOp]
|
|
127
96
|
);
|
|
128
97
|
|
|
129
98
|
const launchCamera = useCallback(
|
|
130
99
|
async (options?: CameraOptions): Promise<MediaPickerResult> => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
setError(
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
const result = await MediaPickerService.launchCamera(options);
|
|
137
|
-
return result;
|
|
138
|
-
} catch (err) {
|
|
139
|
-
const errorMessage =
|
|
140
|
-
err instanceof Error ? err.message : "Failed to launch camera";
|
|
141
|
-
if (isMountedRef.current) {
|
|
142
|
-
setError(errorMessage);
|
|
143
|
-
}
|
|
144
|
-
return { canceled: true };
|
|
145
|
-
} finally {
|
|
146
|
-
if (isMountedRef.current) {
|
|
147
|
-
setIsLoading(false);
|
|
148
|
-
}
|
|
100
|
+
const result = await launchCameraOp.execute(options);
|
|
101
|
+
if (result?.errorMessage) {
|
|
102
|
+
launchCameraOp.setError(result.errorMessage);
|
|
149
103
|
}
|
|
104
|
+
return result ?? { canceled: true };
|
|
150
105
|
},
|
|
151
|
-
[]
|
|
106
|
+
[launchCameraOp]
|
|
152
107
|
);
|
|
153
108
|
|
|
154
109
|
const launchCameraForVideo = useCallback(
|
|
155
110
|
async (options?: CameraOptions): Promise<MediaPickerResult> => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
setError(
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const result = await MediaPickerService.launchCameraForVideo(options);
|
|
162
|
-
return result;
|
|
163
|
-
} catch (err) {
|
|
164
|
-
const errorMessage =
|
|
165
|
-
err instanceof Error ? err.message : "Failed to record video";
|
|
166
|
-
if (isMountedRef.current) {
|
|
167
|
-
setError(errorMessage);
|
|
168
|
-
}
|
|
169
|
-
return { canceled: true };
|
|
170
|
-
} finally {
|
|
171
|
-
if (isMountedRef.current) {
|
|
172
|
-
setIsLoading(false);
|
|
173
|
-
}
|
|
111
|
+
const result = await launchCameraVideoOp.execute(options);
|
|
112
|
+
if (result?.errorMessage) {
|
|
113
|
+
launchCameraVideoOp.setError(result.errorMessage);
|
|
174
114
|
}
|
|
115
|
+
return result ?? { canceled: true };
|
|
175
116
|
},
|
|
176
|
-
[]
|
|
117
|
+
[launchCameraVideoOp]
|
|
177
118
|
);
|
|
178
119
|
|
|
179
120
|
const requestCameraPermission =
|
|
@@ -222,7 +163,11 @@ export const useMedia = () => {
|
|
|
222
163
|
requestMediaLibraryPermission,
|
|
223
164
|
getCameraPermissionStatus,
|
|
224
165
|
getMediaLibraryPermissionStatus,
|
|
225
|
-
isLoading
|
|
226
|
-
|
|
166
|
+
isLoading: pickImageOp.isLoading || pickMultipleImagesOp.isLoading ||
|
|
167
|
+
pickVideoOp.isLoading || launchCameraOp.isLoading ||
|
|
168
|
+
launchCameraVideoOp.isLoading,
|
|
169
|
+
error: pickImageOp.error || pickMultipleImagesOp.error ||
|
|
170
|
+
pickVideoOp.error || launchCameraOp.error ||
|
|
171
|
+
launchCameraVideoOp.error,
|
|
227
172
|
};
|
|
228
173
|
};
|