@umituz/react-native-design-system 4.23.97 → 4.23.100

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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/atoms/button/AtomicButton.tsx +7 -0
  3. package/src/atoms/button/types/index.ts +4 -0
  4. package/src/atoms/input/hooks/useInputState.ts +3 -7
  5. package/src/haptics/infrastructure/services/HapticService.ts +1 -1
  6. package/src/media/infrastructure/hooks/useGenericMediaGeneration.ts +170 -0
  7. package/src/media/presentation/hooks/useCardMediaGeneration.ts +9 -113
  8. package/src/media/presentation/hooks/useCardMediaUpload.ts +5 -5
  9. package/src/media/presentation/hooks/useCardMediaValidation.ts +4 -1
  10. package/src/media/presentation/hooks/useMediaGeneration.ts +4 -87
  11. package/src/molecules/navigation/components/NavigationHeader.tsx +3 -3
  12. package/src/offline/index.ts +1 -0
  13. package/src/offline/infrastructure/storage/OfflineConfigStore.ts +34 -0
  14. package/src/offline/presentation/hooks/useOffline.ts +8 -4
  15. package/src/storage/domain/utils/devUtils.ts +0 -24
  16. package/src/storage/index.ts +1 -1
  17. package/src/storage/infrastructure/adapters/StorageService.ts +2 -7
  18. package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +0 -3
  19. package/src/storage/presentation/hooks/CacheStorageOperations.ts +2 -8
  20. package/src/storage/presentation/hooks/useStore.ts +14 -5
  21. package/src/utilities/sharing/presentation/hooks/useSharing.ts +3 -3
  22. package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +0 -92
  23. package/src/media/domain/entities/CardMultimedia.types.ts +0 -120
  24. package/src/media/infrastructure/services/CardMediaGenerationService.README.md +0 -99
  25. package/src/media/infrastructure/services/CardMediaGenerationService.ts +0 -101
  26. package/src/media/infrastructure/services/CardMediaOptimizerService.README.md +0 -167
  27. package/src/media/infrastructure/services/CardMediaOptimizerService.ts +0 -36
  28. package/src/media/infrastructure/services/CardMediaUploadService.README.md +0 -123
  29. package/src/media/infrastructure/services/CardMediaUploadService.ts +0 -62
  30. package/src/media/infrastructure/services/CardMediaValidationService.README.md +0 -134
  31. package/src/media/infrastructure/services/CardMediaValidationService.ts +0 -81
  32. package/src/media/presentation/hooks/useCardMediaGeneration.README.md +0 -164
  33. package/src/media/presentation/hooks/useCardMediaUpload.README.md +0 -153
  34. package/src/media/presentation/hooks/useCardMediaValidation.README.md +0 -176
  35. package/src/storage/domain/utils/__tests__/devUtils.test.ts +0 -97
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "4.23.97",
3
+ "version": "4.23.100",
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",
@@ -29,6 +29,9 @@ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
29
29
  textStyle,
30
30
  activeOpacity = 0.8,
31
31
  testID,
32
+ accessibilityLabel,
33
+ accessibilityHint,
34
+ accessibilityRole = 'button',
32
35
  }) => {
33
36
  const tokens = useAppDesignTokens();
34
37
 
@@ -80,6 +83,10 @@ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
80
83
  activeOpacity={activeOpacity}
81
84
  disabled={isDisabled}
82
85
  testID={testID}
86
+ accessibilityRole={accessibilityRole}
87
+ accessibilityLabel={accessibilityLabel || title || (typeof children === 'string' ? children : 'Button')}
88
+ accessibilityHint={accessibilityHint}
89
+ accessibilityState={{ disabled: isDisabled, busy: loading }}
83
90
  >
84
91
  {loading ? (
85
92
  <AtomicSpinner
@@ -24,6 +24,10 @@ export interface AtomicButtonProps {
24
24
  readonly textStyle?: StyleProp<TextStyle>;
25
25
  readonly activeOpacity?: number;
26
26
  readonly testID?: string;
27
+ // Accessibility props
28
+ readonly accessibilityLabel?: string;
29
+ readonly accessibilityHint?: string;
30
+ readonly accessibilityRole?: 'button' | 'link';
27
31
  }
28
32
 
29
33
  export interface ButtonSizeConfig {
@@ -1,12 +1,10 @@
1
- import { useState, useCallback, useEffect } from 'react';
1
+ import { useState, useCallback, useEffect, useMemo } from 'react';
2
2
 
3
3
  interface UseInputStateProps {
4
4
  value?: string;
5
5
  onChangeText?: (text: string) => void;
6
6
  secureTextEntry?: boolean;
7
- showPasswordToggle?: boolean;
8
7
  maxLength?: number;
9
- showCharacterCount?: boolean;
10
8
  }
11
9
 
12
10
  interface UseInputStateReturn {
@@ -24,9 +22,7 @@ export const useInputState = ({
24
22
  value = '',
25
23
  onChangeText,
26
24
  secureTextEntry = false,
27
- showPasswordToggle: _showPasswordToggle = false,
28
25
  maxLength,
29
- showCharacterCount: _showCharacterCount = false,
30
26
  }: UseInputStateProps = {}): UseInputStateReturn => {
31
27
  const [localValue, setLocalValue] = useState(value);
32
28
  const [isFocused, setIsFocused] = useState(false);
@@ -49,7 +45,7 @@ export const useInputState = ({
49
45
  const characterCount = localValue.length;
50
46
  const isAtMaxLength = maxLength ? characterCount >= maxLength : false;
51
47
 
52
- return {
48
+ return useMemo(() => ({
53
49
  localValue,
54
50
  isFocused,
55
51
  isPasswordVisible,
@@ -58,5 +54,5 @@ export const useInputState = ({
58
54
  setIsFocused,
59
55
  handleTextChange,
60
56
  togglePasswordVisibility,
61
- };
57
+ }), [localValue, isFocused, isPasswordVisible, characterCount, isAtMaxLength, handleTextChange, togglePasswordVisibility]);
62
58
  };
@@ -15,7 +15,7 @@ import type { ImpactStyle, NotificationType, HapticPattern } from '../../domain/
15
15
  * Log error in development mode only
16
16
  */
17
17
  function logError(method: string, error: unknown): void {
18
- if (process.env.NODE_ENV === 'development') {
18
+ if (__DEV__) {
19
19
  console.error(`[DesignSystem] HapticService.${method} error:`, error);
20
20
  }
21
21
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Generic Media Generation Hook
3
+ * Shared implementation for both Media and CardMedia generation
4
+ * Eliminates ~600 LOC duplication
5
+ */
6
+
7
+ import { useState, useCallback } from "react";
8
+
9
+ interface GenericMediaAttachment {
10
+ id: string;
11
+ type: string;
12
+ position: string;
13
+ url: string;
14
+ filename: string;
15
+ fileSize: number;
16
+ mimeType: string;
17
+ duration?: number;
18
+ thumbnailUrl?: string;
19
+ caption?: string;
20
+ isDownloaded: boolean;
21
+ createdAt: string;
22
+ }
23
+
24
+ interface GenericMediaGenerationRequest {
25
+ type: "text_to_image" | "text_to_audio" | "image_search";
26
+ input: {
27
+ text?: string;
28
+ prompt?: string;
29
+ language?: string;
30
+ voice?: "male" | "female" | "neutral";
31
+ style?: "realistic" | "cartoon" | "artistic";
32
+ };
33
+ options: {
34
+ maxResults?: number;
35
+ quality?: "low" | "medium" | "high";
36
+ format?: "jpeg" | "png" | "mp3" | "wav";
37
+ };
38
+ }
39
+
40
+ interface GenericMediaGenerationResult<TAttachment> {
41
+ success: boolean;
42
+ attachments: TAttachment[];
43
+ creditsUsed: number;
44
+ processingTime: number;
45
+ error?: string;
46
+ requestId: string;
47
+ }
48
+
49
+ export interface UseGenericMediaGenerationResult<TAttachment, TRequest> {
50
+ generateMedia: (request: TRequest) => Promise<GenericMediaGenerationResult<TAttachment>>;
51
+ isGenerating: boolean;
52
+ generationResult: GenericMediaGenerationResult<TAttachment> | null;
53
+ error: string | null;
54
+ }
55
+
56
+ /**
57
+ * Generic implementation of media generation logic
58
+ * Type-safe through attachment factory pattern
59
+ */
60
+ export function useGenericMediaGeneration<
61
+ TAttachment extends GenericMediaAttachment,
62
+ TRequest extends GenericMediaGenerationRequest
63
+ >(
64
+ attachmentFactory: (baseAttachment: GenericMediaAttachment) => TAttachment
65
+ ): UseGenericMediaGenerationResult<TAttachment, TRequest> {
66
+ const [isGenerating, setIsGenerating] = useState(false);
67
+ const [generationResult, setGenerationResult] =
68
+ useState<GenericMediaGenerationResult<TAttachment> | null>(null);
69
+ const [error, setError] = useState<string | null>(null);
70
+
71
+ const generateMedia = useCallback(
72
+ async (request: TRequest): Promise<GenericMediaGenerationResult<TAttachment>> => {
73
+ try {
74
+ setIsGenerating(true);
75
+ setError(null);
76
+
77
+ // Simulate generation
78
+ await new Promise((resolve) => setTimeout(resolve, 3000));
79
+
80
+ const baseAttachments: GenericMediaAttachment[] = [];
81
+
82
+ switch (request.type) {
83
+ case "text_to_image":
84
+ for (let i = 0; i < (request.options.maxResults || 1); i++) {
85
+ baseAttachments.push({
86
+ id: `ai_img_${Date.now()}_${i}`,
87
+ type: "image",
88
+ position: "both",
89
+ url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
90
+ filename: `ai_generated_${i}.jpg`,
91
+ fileSize: 150000,
92
+ mimeType: "image/jpeg",
93
+ isDownloaded: false,
94
+ createdAt: new Date().toISOString(),
95
+ });
96
+ }
97
+ break;
98
+
99
+ case "text_to_audio":
100
+ baseAttachments.push({
101
+ id: `ai_audio_${Date.now()}`,
102
+ type: "audio",
103
+ position: "back",
104
+ url: `https://example.com/audio_${Date.now()}.mp3`,
105
+ filename: `ai_generated_${Date.now()}.mp3`,
106
+ fileSize: 80000,
107
+ mimeType: "audio/mp3",
108
+ duration: 10,
109
+ isDownloaded: false,
110
+ createdAt: new Date().toISOString(),
111
+ });
112
+ break;
113
+
114
+ case "image_search":
115
+ for (let i = 0; i < (request.options.maxResults || 5); i++) {
116
+ baseAttachments.push({
117
+ id: `search_img_${Date.now()}_${i}`,
118
+ type: "image",
119
+ position: "both",
120
+ url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
121
+ filename: `search_result_${i}.jpg`,
122
+ fileSize: 120000,
123
+ mimeType: "image/jpeg",
124
+ isDownloaded: false,
125
+ createdAt: new Date().toISOString(),
126
+ });
127
+ }
128
+ break;
129
+ }
130
+
131
+ const attachments = baseAttachments.map(attachmentFactory);
132
+
133
+ const result: GenericMediaGenerationResult<TAttachment> = {
134
+ success: true,
135
+ attachments,
136
+ creditsUsed: request.type === "text_to_image" ? 5 : request.type === "text_to_audio" ? 3 : 2,
137
+ processingTime: 3000,
138
+ requestId: `req_${Date.now()}`,
139
+ };
140
+
141
+ setGenerationResult(result);
142
+ return result;
143
+ } catch (err) {
144
+ const errorMessage =
145
+ err instanceof Error ? err.message : "Generation failed";
146
+ setError(errorMessage);
147
+ setIsGenerating(false);
148
+
149
+ return {
150
+ success: false,
151
+ attachments: [],
152
+ creditsUsed: 0,
153
+ processingTime: 0,
154
+ error: errorMessage,
155
+ requestId: "",
156
+ };
157
+ } finally {
158
+ setIsGenerating(false);
159
+ }
160
+ },
161
+ [attachmentFactory],
162
+ );
163
+
164
+ return {
165
+ generateMedia,
166
+ isGenerating,
167
+ generationResult,
168
+ error,
169
+ };
170
+ }
@@ -1,124 +1,20 @@
1
1
  /**
2
2
  * Card Media Generation Hook
3
3
  * Hook for generating card media with AI
4
+ * Now a thin wrapper around useGenericMediaGeneration
5
+ *
6
+ * Note: CardMedia types are aliases of Media types for backward compatibility
4
7
  */
5
8
 
6
- import { useState, useCallback } from "react";
9
+ import { useGenericMediaGeneration } from "../../infrastructure/hooks/useGenericMediaGeneration";
7
10
  import type { UseCardMediaGenerationResult } from "./card-multimedia.types";
8
11
  import type {
9
- CardMediaAttachment,
10
- CardMediaGenerationRequest,
11
- CardMediaGenerationResult,
12
- } from "../../domain/entities/CardMultimedia.types";
12
+ MediaAttachment as CardMediaAttachment,
13
+ MediaGenerationRequest as CardMediaGenerationRequest,
14
+ } from "../../domain/entities/MultimediaFlashcardTypes";
13
15
 
14
16
  export const useCardMediaGeneration = (): UseCardMediaGenerationResult => {
15
- const [isGenerating, setIsGenerating] = useState(false);
16
- const [generationResult, setGenerationResult] =
17
- useState<CardMediaGenerationResult | null>(null);
18
- const [error, setError] = useState<string | null>(null);
19
-
20
- const generateMedia = useCallback(
21
- async (
22
- request: CardMediaGenerationRequest,
23
- ): Promise<CardMediaGenerationResult> => {
24
- try {
25
- setIsGenerating(true);
26
- setError(null);
27
-
28
- // Simulate generation
29
- await new Promise((resolve) => setTimeout(resolve, 3000));
30
-
31
- const attachments: CardMediaAttachment[] = [];
32
-
33
- switch (request.type) {
34
- case "text_to_image":
35
- for (let i = 0; i < (request.options.maxResults || 1); i++) {
36
- attachments.push({
37
- id: `ai_img_${Date.now()}_${i}`,
38
- type: "image",
39
- position: "both",
40
- url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
41
- filename: `ai_generated_${i}.jpg`,
42
- fileSize: 150000, // 150KB
43
- mimeType: "image/jpeg",
44
- isDownloaded: false,
45
- createdAt: new Date().toISOString(),
46
- });
47
- }
48
- break;
49
-
50
- case "text_to_audio":
51
- attachments.push({
52
- id: `ai_audio_${Date.now()}`,
53
- type: "audio",
54
- position: "back",
55
- url: `https://example.com/audio_${Date.now()}.mp3`,
56
- filename: `ai_generated_${Date.now()}.mp3`,
57
- fileSize: 80000, // 80KB
58
- mimeType: "audio/mp3",
59
- duration: 10, // 10 seconds
60
- isDownloaded: false,
61
- createdAt: new Date().toISOString(),
62
- });
63
- break;
64
-
65
- case "image_search":
66
- for (let i = 0; i < (request.options.maxResults || 5); i++) {
67
- attachments.push({
68
- id: `search_img_${Date.now()}_${i}`,
69
- type: "image",
70
- position: "both",
71
- url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
72
- filename: `search_result_${i}.jpg`,
73
- fileSize: 120000, // 120KB
74
- mimeType: "image/jpeg",
75
- isDownloaded: false,
76
- createdAt: new Date().toISOString(),
77
- });
78
- }
79
- break;
80
- }
81
-
82
- const result: CardMediaGenerationResult = {
83
- success: true,
84
- attachments,
85
- creditsUsed:
86
- request.type === "text_to_image"
87
- ? 5
88
- : request.type === "text_to_audio"
89
- ? 3
90
- : 2,
91
- processingTime: 3000,
92
- requestId: `req_${Date.now()}`,
93
- };
94
-
95
- setGenerationResult(result);
96
- return result;
97
- } catch (err) {
98
- const errorMessage =
99
- err instanceof Error ? err.message : "Generation failed";
100
- setError(errorMessage);
101
- setIsGenerating(false);
102
-
103
- return {
104
- success: false,
105
- attachments: [],
106
- creditsUsed: 0,
107
- processingTime: 0,
108
- error: errorMessage,
109
- requestId: "",
110
- };
111
- } finally {
112
- setIsGenerating(false);
113
- }
114
- },
115
- [],
17
+ return useGenericMediaGeneration<CardMediaAttachment, CardMediaGenerationRequest>(
18
+ (baseAttachment) => baseAttachment as CardMediaAttachment
116
19
  );
117
-
118
- return {
119
- generateMedia,
120
- isGenerating,
121
- generationResult,
122
- error,
123
- };
124
20
  };
@@ -8,11 +8,11 @@ import { generateThumbnail, getMediaDuration } from "../../infrastructure/utils/
8
8
  import { getMediaTypeFromMime } from "../../infrastructure/utils/mime-type-detector";
9
9
  import type { UseCardMediaUploadResult } from "./card-multimedia.types";
10
10
  import type {
11
- CardMediaAttachment,
12
- CardMediaCompressionOptions,
13
- CardMediaFile,
14
- CardMediaUploadProgress,
15
- } from "../../domain/entities/CardMultimedia.types";
11
+ MediaAttachment as CardMediaAttachment,
12
+ MediaCompressionOptions as CardMediaCompressionOptions,
13
+ MediaFile as CardMediaFile,
14
+ MediaUploadProgress as CardMediaUploadProgress,
15
+ } from "../../domain/entities/MultimediaFlashcardTypes";
16
16
 
17
17
  export const useCardMediaUpload = (): UseCardMediaUploadResult => {
18
18
  const [isUploading, setIsUploading] = useState(false);
@@ -6,7 +6,10 @@
6
6
  import { useState, useCallback } from "react";
7
7
  import { formatFileSize } from "../../infrastructure/utils/media-collection-utils";
8
8
  import type { UseCardMediaValidationResult } from "./card-multimedia.types";
9
- import type { CardMediaValidation, CardMediaFile } from "../../domain/entities/CardMultimedia.types";
9
+ import type {
10
+ MediaValidation as CardMediaValidation,
11
+ MediaFile as CardMediaFile,
12
+ } from "../../domain/entities/MultimediaFlashcardTypes";
10
13
 
11
14
  export const useCardMediaValidation = (): UseCardMediaValidationResult => {
12
15
  const [isValidating, setIsValidating] = useState(false);
@@ -1,101 +1,18 @@
1
1
  /**
2
2
  * Media Generation Hook
3
3
  * Hook for generating media with AI
4
+ * Now a thin wrapper around useGenericMediaGeneration
4
5
  */
5
6
 
6
- import { useState, useCallback } from "react";
7
+ import { useGenericMediaGeneration } from "../../infrastructure/hooks/useGenericMediaGeneration";
7
8
  import type { UseMediaGenerationResult } from "./multimedia.types";
8
9
  import type {
9
10
  MediaAttachment,
10
11
  MediaGenerationRequest,
11
- MediaGenerationResult,
12
12
  } from "../../domain/entities/MultimediaFlashcardTypes";
13
13
 
14
14
  export const useMediaGeneration = (): UseMediaGenerationResult => {
15
- const [isGenerating, setIsGenerating] = useState(false);
16
- const [generationResult, setGenerationResult] =
17
- useState<MediaGenerationResult | null>(null);
18
- const [error, setError] = useState<string | null>(null);
19
-
20
- const generateMedia = useCallback(
21
- async (request: MediaGenerationRequest): Promise<MediaGenerationResult> => {
22
- try {
23
- setIsGenerating(true);
24
- setError(null);
25
-
26
- // Simulate generation
27
- await new Promise((resolve) => setTimeout(resolve, 3000));
28
-
29
- const attachments: MediaAttachment[] = [];
30
-
31
- switch (request.type) {
32
- case "text_to_image":
33
- for (let i = 0; i < (request.options.maxResults || 1); i++) {
34
- attachments.push({
35
- id: `ai_img_${Date.now()}_${i}`,
36
- type: "image",
37
- position: "both",
38
- url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
39
- filename: `ai_generated_${i}.jpg`,
40
- fileSize: 150000, // 150KB
41
- mimeType: "image/jpeg",
42
- isDownloaded: false,
43
- createdAt: new Date().toISOString(),
44
- });
45
- }
46
- break;
47
-
48
- case "text_to_audio":
49
- attachments.push({
50
- id: `ai_audio_${Date.now()}`,
51
- type: "audio",
52
- position: "back",
53
- url: `https://example.com/audio_${Date.now()}.mp3`,
54
- filename: `ai_generated_${Date.now()}.mp3`,
55
- fileSize: 80000, // 80KB
56
- mimeType: "audio/mp3",
57
- duration: 10, // 10 seconds
58
- isDownloaded: false,
59
- createdAt: new Date().toISOString(),
60
- });
61
- break;
62
- }
63
-
64
- const result: MediaGenerationResult = {
65
- success: true,
66
- attachments,
67
- creditsUsed: request.type === "text_to_image" ? 5 : 3,
68
- processingTime: 3000,
69
- requestId: `req_${Date.now()}`,
70
- };
71
-
72
- setGenerationResult(result);
73
- return result;
74
- } catch (err) {
75
- const errorMessage =
76
- err instanceof Error ? err.message : "Generation failed";
77
- setError(errorMessage);
78
- setIsGenerating(false);
79
-
80
- return {
81
- success: false,
82
- attachments: [],
83
- creditsUsed: 0,
84
- processingTime: 0,
85
- error: errorMessage,
86
- requestId: "",
87
- };
88
- } finally {
89
- setIsGenerating(false);
90
- }
91
- },
92
- [],
15
+ return useGenericMediaGeneration<MediaAttachment, MediaGenerationRequest>(
16
+ (baseAttachment) => baseAttachment as MediaAttachment
93
17
  );
94
-
95
- return {
96
- generateMedia,
97
- isGenerating,
98
- generationResult,
99
- error,
100
- };
101
18
  };
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { View, StyleSheet, TouchableOpacity } from 'react-native';
3
3
  import { AtomicText } from '../../../atoms';
4
4
  import { AtomicIcon, useIconName } from '../../../atoms';
@@ -20,7 +20,7 @@ export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
20
20
  const insets = useSafeAreaInsets();
21
21
  const arrowLeftIcon = useIconName('arrowLeft');
22
22
 
23
- const styles = StyleSheet.create({
23
+ const styles = useMemo(() => StyleSheet.create({
24
24
  container: {
25
25
  paddingTop: insets.top,
26
26
  paddingHorizontal: tokens.spacing.md,
@@ -45,7 +45,7 @@ export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
45
45
  flex: 1,
46
46
  textAlign: 'left',
47
47
  },
48
- });
48
+ }), [tokens, insets]);
49
49
 
50
50
  return (
51
51
  <View style={styles.container}>
@@ -14,6 +14,7 @@ export type {
14
14
 
15
15
  // Store
16
16
  export { useOfflineStore } from './infrastructure/storage/OfflineStore';
17
+ export { useOfflineConfigStore } from './infrastructure/storage/OfflineConfigStore';
17
18
 
18
19
  // Hooks
19
20
  export { useOffline, configureOffline } from './presentation/hooks/useOffline';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Offline Config Store
3
+ * Centralized configuration for offline functionality
4
+ * Replaces module-level mutable state with Zustand store
5
+ */
6
+
7
+ import { create } from 'zustand';
8
+ import type { OfflineConfig } from '../../types';
9
+
10
+ interface OfflineConfigStore {
11
+ config: OfflineConfig;
12
+ setConfig: (config: OfflineConfig) => void;
13
+ mergeConfig: (partialConfig: OfflineConfig) => void;
14
+ reset: () => void;
15
+ }
16
+
17
+ export const useOfflineConfigStore = create<OfflineConfigStore>((set) => ({
18
+ config: {},
19
+
20
+ setConfig: (config) => set({ config }),
21
+
22
+ mergeConfig: (partialConfig) => set((state) => ({
23
+ config: { ...state.config, ...partialConfig }
24
+ })),
25
+
26
+ reset: () => set({ config: {} }),
27
+ }));
28
+
29
+ /**
30
+ * Get current config (for non-React contexts)
31
+ */
32
+ export const getOfflineConfig = (): OfflineConfig => {
33
+ return useOfflineConfigStore.getState().config;
34
+ };
@@ -10,6 +10,7 @@ import type { NetworkState as ExpoNetworkState } from 'expo-network';
10
10
  import type { NetworkState, OfflineConfig } from '../../types';
11
11
  import { useOfflineStore } from '../../infrastructure/storage/OfflineStore';
12
12
  import { networkEvents } from '../../infrastructure/events/NetworkEvents';
13
+ import { useOfflineConfigStore } from '../../infrastructure/storage/OfflineConfigStore';
13
14
 
14
15
  /**
15
16
  * Convert expo-network state to our internal format
@@ -21,14 +22,17 @@ const toNetworkState = (state: ExpoNetworkState): NetworkState => ({
21
22
  details: null,
22
23
  });
23
24
 
24
- let globalConfig: OfflineConfig = {};
25
-
25
+ /**
26
+ * Configure offline settings globally
27
+ * This is a facade over the config store for backward compatibility
28
+ */
26
29
  export const configureOffline = (config: OfflineConfig): void => {
27
- globalConfig = config;
30
+ useOfflineConfigStore.getState().setConfig(config);
28
31
  };
29
32
 
30
33
  export const useOffline = (config?: OfflineConfig) => {
31
34
  const store = useOfflineStore();
35
+ const globalConfig = useOfflineConfigStore((state) => state.config);
32
36
  const isInitialized = useRef(false);
33
37
  const previousStateRef = useRef<NetworkState | null>(null);
34
38
  const isMountedRef = useRef(true);
@@ -36,7 +40,7 @@ export const useOffline = (config?: OfflineConfig) => {
36
40
  // Memoize merged config to prevent unnecessary effect re-runs
37
41
  const mergedConfig = useMemo(
38
42
  () => ({ ...globalConfig, ...config }),
39
- [config]
43
+ [globalConfig, config]
40
44
  );
41
45
 
42
46
  const handleNetworkStateChange = useCallback((state: ExpoNetworkState) => {
@@ -9,27 +9,3 @@ export const isDev = (): boolean => {
9
9
  return __DEV__;
10
10
  };
11
11
 
12
- /**
13
- * Log warning in development mode only
14
- * All logs are disabled
15
- */
16
- export const devWarn = (_message: string, ..._args: unknown[]): void => {
17
- // Disabled
18
- };
19
-
20
- /**
21
- * Log error in development mode only
22
- * All logs are disabled
23
- */
24
- export const devError = (_message: string, ..._args: unknown[]): void => {
25
- // Disabled
26
- };
27
-
28
- /**
29
- * Log info in development mode only
30
- * All logs are disabled
31
- */
32
- export const devLog = (_message: string, ..._args: unknown[]): void => {
33
- // Disabled
34
- };
35
-
@@ -82,7 +82,7 @@ export { TIME_MS, DEFAULT_TTL, CACHE_VERSION } from './domain/constants/CacheDef
82
82
  // DOMAIN LAYER - Development Utilities
83
83
  // =============================================================================
84
84
 
85
- export { isDev, devWarn, devError, devLog } from './domain/utils/devUtils';
85
+ export { isDev } from './domain/utils/devUtils';
86
86
 
87
87
  // =============================================================================
88
88
  // DOMAIN LAYER - Store Types