@umituz/react-native-design-system 4.23.117 → 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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/device/presentation/hooks/useAnonymousUser.ts +25 -31
  3. package/src/device/presentation/hooks/useDeviceInfo.ts +47 -91
  4. package/src/init/useAppInitialization.ts +24 -57
  5. package/src/media/domain/entities/Media.ts +1 -0
  6. package/src/media/infrastructure/utils/media-collection-utils.ts +9 -6
  7. package/src/media/presentation/hooks/useMedia.ts +73 -128
  8. package/src/molecules/alerts/AlertBanner.tsx +24 -46
  9. package/src/molecules/alerts/AlertInline.tsx +8 -9
  10. package/src/molecules/alerts/AlertModal.tsx +23 -14
  11. package/src/molecules/alerts/AlertToast.tsx +25 -53
  12. package/src/molecules/alerts/components/AlertContent.tsx +79 -0
  13. package/src/molecules/alerts/components/AlertIcon.tsx +31 -0
  14. package/src/molecules/alerts/components/index.ts +6 -0
  15. package/src/molecules/alerts/hooks/index.ts +6 -0
  16. package/src/molecules/alerts/hooks/useAlertAutoDismiss.ts +26 -0
  17. package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +21 -0
  18. package/src/molecules/alerts/utils/alertUtils.ts +0 -21
  19. package/src/storage/cache/presentation/useCachedValue.ts +24 -65
  20. package/src/storage/presentation/hooks/useStorageState.ts +20 -29
  21. package/src/utilities/sharing/presentation/hooks/useSharing.ts +75 -140
  22. package/src/utils/errors/DesignSystemError.ts +57 -1
  23. package/src/utils/errors/ErrorHandler.ts +105 -1
  24. package/src/utils/errors/adapters/CacheErrorAdapter.ts +68 -0
  25. package/src/utils/errors/adapters/ImageErrorAdapter.ts +91 -0
  26. package/src/utils/errors/adapters/StorageErrorAdapter.ts +107 -0
  27. package/src/utils/errors/index.ts +5 -1
  28. package/src/utils/errors/types/Result.ts +64 -0
  29. package/src/utils/hooks/index.ts +12 -0
  30. package/src/utils/hooks/types/AsyncOperationTypes.ts +75 -0
  31. 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.117",
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 { useState, useEffect, useCallback } from 'react';
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 loadAnonymousUser = useCallback(async () => {
56
- setIsLoading(true);
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
- setAnonymousUser({
59
+ return {
66
60
  userId: userId || fallbackUserId,
67
61
  deviceName: deviceName || 'Unknown Device',
68
62
  displayName: anonymousDisplayName,
69
63
  isAnonymous: true,
70
- });
71
- } catch {
72
- setAnonymousUser({
73
- userId: fallbackUserId,
74
- deviceName: 'Unknown Device',
75
- displayName: anonymousDisplayName,
76
- isAnonymous: true,
77
- });
78
- setError('Failed to generate device ID');
79
- } finally {
80
- setIsLoading(false);
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
- }, [anonymousDisplayName, fallbackUserId]);
80
+ );
83
81
 
84
82
  const refresh = useCallback(async () => {
85
- await loadAnonymousUser();
86
- }, [loadAnonymousUser]);
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 [deviceInfo, setDeviceInfo] = useState<DeviceInfo | null>(null);
52
- const [appInfo, setAppInfo] = useState<ApplicationInfo | null>(null);
53
- const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
54
- const isMountedRef = useRef(true);
55
-
56
- const loadInfo = useCallback(async () => {
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 loadDeviceInfo = useCallback(async () => {
72
- if (!isMountedRef.current) return;
73
- try {
74
- const info = await DeviceService.getDeviceInfo();
75
- if (isMountedRef.current) setDeviceInfo(info);
76
- } catch {
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 loadAppInfo = useCallback(async () => {
82
- if (!isMountedRef.current) return;
83
- try {
84
- const info = await DeviceService.getApplicationInfo();
85
- if (isMountedRef.current) setAppInfo(info);
86
- } catch {
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: !deviceInfo && !appInfo && !systemInfo,
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: loadInfo,
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 } = useAsyncData(DeviceService.getDeviceCapabilities, null);
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 } = useAsyncData(PersistentDeviceIdService.getDeviceId, null);
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 { useState, useEffect, useRef } from "react";
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 [isReady, setIsReady] = useState(false);
33
- const [isLoading, setIsLoading] = useState(!skip);
34
- const [error, setError] = useState<Error | null>(null);
33
+ const { data, isLoading, error } = useAsyncOperation<AppInitializerResult, Error>(
34
+ async () => {
35
+ const result = await initializer();
35
36
 
36
- // Store callbacks in refs to avoid re-running effect
37
- const onReadyRef = useRef(onReady);
38
- const onErrorRef = useRef(onError);
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
- return () => {
87
- cancelled = true;
88
- };
89
- }, [initializer, skip]);
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 { isReady, isLoading, error };
54
+ return useMemo(() => ({
55
+ isReady: data?.success ?? false,
56
+ isLoading,
57
+ error,
58
+ }), [data, isLoading, error]);
92
59
  }
@@ -98,6 +98,7 @@ export enum MediaValidationError {
98
98
  FILE_TOO_LARGE = "FILE_TOO_LARGE",
99
99
  INVALID_FORMAT = "INVALID_FORMAT",
100
100
  PERMISSION_DENIED = "PERMISSION_DENIED",
101
+ PICKER_ERROR = "PICKER_ERROR",
101
102
  }
102
103
 
103
104
  /**
@@ -3,9 +3,9 @@
3
3
  * Generic utilities for working with media collections
4
4
  */
5
5
 
6
- type MediaType = "image" | "audio" | "video";
6
+ import { formatFileSize as formatFileSizeUtil } from "../../../utils/formatters/stringFormatter";
7
7
 
8
- const FILE_SIZE_UNITS = ["Bytes", "KB", "MB", "GB"] as const;
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
- if (bytes === 0) return "0 Bytes";
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 { useState, useCallback, useEffect, useRef } from "react";
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
- const [isLoading, setIsLoading] = useState(false);
40
- const [error, setError] = useState<string | null>(null);
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
- // Track mounted state to prevent setState on unmounted component
43
- const isMountedRef = useRef(true);
44
- useEffect(() => {
45
- return () => {
46
- isMountedRef.current = false;
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
- if (isMountedRef.current) {
53
- setIsLoading(true);
54
- setError(null);
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
- if (isMountedRef.current) {
82
- setIsLoading(true);
83
- setError(null);
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
- if (isMountedRef.current) {
107
- setIsLoading(true);
108
- setError(null);
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
- if (isMountedRef.current) {
132
- setIsLoading(true);
133
- setError(null);
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
- if (isMountedRef.current) {
157
- setIsLoading(true);
158
- setError(null);
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
- error,
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
  };