@umituz/react-native-design-system 4.23.68 → 4.23.70
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/atoms/EmptyState.tsx +2 -2
- package/src/atoms/icon/AtomicIcon.tsx +1 -1
- package/src/atoms/icon/utils/iconUtils.ts +1 -1
- package/src/exception/infrastructure/services/ExceptionService.ts +29 -17
- package/src/exception/presentation/components/ExceptionEmptyState.tsx +1 -1
- package/src/exception/presentation/components/ExceptionErrorState.tsx +1 -1
- package/src/image/infrastructure/services/ImageBatchService.ts +1 -7
- package/src/image/infrastructure/types/BatchTypes.ts +11 -0
- package/src/image/infrastructure/utils/BatchProcessor.ts +1 -1
- package/src/image/infrastructure/utils/ImageErrorHandler.ts +1 -0
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +2 -2
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +3 -3
- package/src/media/presentation/hooks/useCardMediaGeneration.ts +4 -4
- package/src/media/presentation/hooks/useCardMediaUpload.ts +4 -4
- package/src/media/presentation/hooks/useCardMediaValidation.ts +4 -4
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +5 -5
- package/src/media/presentation/hooks/useMediaGeneration.ts +4 -4
- package/src/media/presentation/hooks/useMediaUpload.ts +4 -4
- package/src/media/presentation/hooks/useMediaValidation.ts +4 -4
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +5 -5
- package/src/molecules/BaseModal.tsx +1 -1
- package/src/molecules/ConfirmationModalContent.tsx +2 -2
- package/src/molecules/ConfirmationModalMain.tsx +2 -2
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +2 -2
- package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +1 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +2 -1
- package/src/molecules/calendar/presentation/components/CalendarWeekdayHeader.tsx +1 -1
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +6 -6
- package/src/molecules/countdown/components/Countdown.tsx +2 -2
- package/src/molecules/splash/components/SplashScreen.tsx +9 -23
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +1 -1
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +2 -2
- package/src/organisms/FormContainer.tsx +2 -2
- package/src/responsive/validation.ts +1 -0
- package/src/storage/cache/domain/ErrorHandler.ts +1 -0
- package/src/storage/domain/errors/StorageError.ts +6 -0
- package/src/storage/infrastructure/repositories/AsyncStorageRepository.ts +31 -16
- package/src/tanstack/domain/repositories/IBaseRepository.ts +34 -0
- package/src/tanstack/domain/repositories/mixins/repositoryInvalidationMethods.ts +4 -4
- package/src/tanstack/domain/repositories/mixins/repositoryQueryMethods.ts +3 -3
- package/src/tanstack/infrastructure/providers/TanstackProvider.tsx +3 -3
- package/src/theme/index.ts +0 -3
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +15 -4
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.70",
|
|
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",
|
package/src/atoms/EmptyState.tsx
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Purpose: Empty state indication across all apps
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
11
11
|
import { View, StyleSheet, TouchableOpacity, ViewStyle } from 'react-native';
|
|
12
12
|
import { AtomicIcon } from './icon';
|
|
13
13
|
import { AtomicText } from './AtomicText';
|
|
@@ -39,7 +39,7 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
|
|
|
39
39
|
const tokens = useAppDesignTokens();
|
|
40
40
|
const displayDescription = description || subtitle;
|
|
41
41
|
|
|
42
|
-
const themedStyles =
|
|
42
|
+
const themedStyles = useMemo(
|
|
43
43
|
() =>
|
|
44
44
|
StyleSheet.create({
|
|
45
45
|
container: {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import React from 'react';
|
|
24
24
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
25
|
-
import { useAppDesignTokens } from '../../theme';
|
|
25
|
+
import { useAppDesignTokens } from '../../theme/hooks/useAppDesignTokens';
|
|
26
26
|
import { useIconRenderer } from './iconStore';
|
|
27
27
|
import {
|
|
28
28
|
type IconSize as BaseIconSize,
|
|
@@ -21,17 +21,22 @@ import { ExceptionLogger } from './ExceptionLogger';
|
|
|
21
21
|
import { useExceptionStore } from '../storage/ExceptionStore';
|
|
22
22
|
|
|
23
23
|
export class ExceptionService {
|
|
24
|
-
private logger: ExceptionLogger;
|
|
25
|
-
private reporter: ExceptionReporter;
|
|
24
|
+
private logger: ExceptionLogger | null = null;
|
|
25
|
+
private reporter: ExceptionReporter | null = null;
|
|
26
|
+
private reporterConfig: ExceptionReporter['config'];
|
|
26
27
|
|
|
27
28
|
constructor(reporterConfig?: ExceptionReporter['config']) {
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
this.reporterConfig = reporterConfig || {
|
|
30
|
+
enabled: false,
|
|
31
|
+
environment: 'development'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private ensureInitialized() {
|
|
36
|
+
if (!this.logger) {
|
|
37
|
+
this.logger = new ExceptionLogger();
|
|
38
|
+
this.reporter = new ExceptionReporter(this.reporterConfig);
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
@@ -43,17 +48,19 @@ export class ExceptionService {
|
|
|
43
48
|
category: ExceptionCategory = 'unknown',
|
|
44
49
|
context: ExceptionContext = {},
|
|
45
50
|
): Promise<void> {
|
|
51
|
+
this.ensureInitialized();
|
|
52
|
+
|
|
46
53
|
const exception = ExceptionHandler.createException(error, severity, category, context);
|
|
47
54
|
|
|
48
55
|
// Add to store
|
|
49
56
|
useExceptionStore.getState().addException(exception);
|
|
50
57
|
|
|
51
58
|
// Log locally
|
|
52
|
-
await this.logger
|
|
59
|
+
await this.logger!.logException(exception);
|
|
53
60
|
|
|
54
61
|
// Report to external service if needed
|
|
55
62
|
if (ExceptionHandler.shouldReportException(exception)) {
|
|
56
|
-
await this.reporter
|
|
63
|
+
await this.reporter!.reportException(exception);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// Mark as handled
|
|
@@ -92,35 +99,40 @@ export class ExceptionService {
|
|
|
92
99
|
* Get stored exceptions
|
|
93
100
|
*/
|
|
94
101
|
async getStoredExceptions(): Promise<ExceptionEntity[]> {
|
|
95
|
-
|
|
102
|
+
this.ensureInitialized();
|
|
103
|
+
return this.logger!.getStoredExceptions();
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
/**
|
|
99
107
|
* Get exception statistics
|
|
100
108
|
*/
|
|
101
109
|
async getExceptionStats() {
|
|
102
|
-
|
|
110
|
+
this.ensureInitialized();
|
|
111
|
+
return this.logger!.getExceptionStats();
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
/**
|
|
106
115
|
* Clear stored exceptions
|
|
107
116
|
*/
|
|
108
117
|
async clearStoredExceptions(): Promise<void> {
|
|
109
|
-
|
|
118
|
+
this.ensureInitialized();
|
|
119
|
+
await this.logger!.clearStoredExceptions();
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
/**
|
|
113
123
|
* Update reporter configuration
|
|
114
124
|
*/
|
|
115
125
|
updateReporterConfig(config: Partial<ExceptionReporter['config']>): void {
|
|
116
|
-
this.
|
|
126
|
+
this.ensureInitialized();
|
|
127
|
+
this.reporter!.updateConfig(config);
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
/**
|
|
120
131
|
* Set max stored exceptions
|
|
121
132
|
*/
|
|
122
133
|
setMaxStoredExceptions(limit: number): void {
|
|
123
|
-
this.
|
|
134
|
+
this.ensureInitialized();
|
|
135
|
+
this.logger!.setMaxStoredExceptions(limit);
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
/**
|
|
@@ -145,7 +157,7 @@ export class ExceptionService {
|
|
|
145
157
|
}
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
// Export default instance
|
|
160
|
+
// Export default instance - lazy initialization
|
|
149
161
|
export const exceptionService = new ExceptionService();
|
|
150
162
|
|
|
151
163
|
|
|
@@ -44,7 +44,7 @@ export const ExceptionErrorState: React.FC<ExceptionErrorStateProps> = ({
|
|
|
44
44
|
const refreshIcon = useIconName('refresh');
|
|
45
45
|
const displayIcon = icon || alertCircleIcon;
|
|
46
46
|
|
|
47
|
-
const styles =
|
|
47
|
+
const styles = useMemo(
|
|
48
48
|
() =>
|
|
49
49
|
StyleSheet.create({
|
|
50
50
|
actionButton: {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
|
|
8
|
+
import type { BatchOperation } from '../types/BatchTypes';
|
|
8
9
|
import { BatchProcessor } from '../utils/BatchProcessor';
|
|
9
10
|
|
|
10
11
|
export interface BatchProcessingOptions {
|
|
@@ -27,13 +28,6 @@ export interface BatchProcessingResult {
|
|
|
27
28
|
failureCount: number;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
export interface BatchOperation {
|
|
31
|
-
uri: string;
|
|
32
|
-
type: 'resize' | 'crop' | 'filter' | 'compress' | 'convert';
|
|
33
|
-
params: Record<string, unknown>;
|
|
34
|
-
options?: Record<string, unknown>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
31
|
export class ImageBatchService {
|
|
38
32
|
static async processBatch(
|
|
39
33
|
operations: BatchOperation[],
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Operation Types
|
|
3
|
+
* Shared types for batch processing operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface BatchOperation {
|
|
7
|
+
uri: string;
|
|
8
|
+
type: 'resize' | 'crop' | 'filter' | 'compress' | 'convert';
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
options?: Record<string, unknown>;
|
|
11
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ImageManipulationResult, ImageCropArea, SaveFormat } from '../../domain/entities/ImageTypes';
|
|
8
|
-
import type { BatchOperation } from '../
|
|
8
|
+
import type { BatchOperation } from '../types/BatchTypes';
|
|
9
9
|
import { ImageTransformService } from '../services/ImageTransformService';
|
|
10
10
|
import { ImageConversionService } from '../services/ImageConversionService';
|
|
11
11
|
import { ImageValidator } from './ImageValidator';
|
|
@@ -62,13 +62,13 @@ function InfiniteScrollListComponent<T>({
|
|
|
62
62
|
}: InfiniteScrollListProps<T>): React.ReactElement {
|
|
63
63
|
const { items, state, loadMore, refresh, canLoadMore } = useInfiniteScroll(config);
|
|
64
64
|
|
|
65
|
-
const handleEndReached =
|
|
65
|
+
const handleEndReached = useCallback(() => {
|
|
66
66
|
if (canLoadMore && config.autoLoad !== false) {
|
|
67
67
|
loadMore();
|
|
68
68
|
}
|
|
69
69
|
}, [canLoadMore, loadMore, config.autoLoad]);
|
|
70
70
|
|
|
71
|
-
const getItemKey =
|
|
71
|
+
const getItemKey = useCallback(
|
|
72
72
|
(item: T, index: number): string => {
|
|
73
73
|
if (config.getItemKey) {
|
|
74
74
|
return config.getItemKey(item, index);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Fully configurable for general purpose use
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import React from 'react';
|
|
15
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
16
16
|
import { View, TouchableOpacity, ViewStyle } from 'react-native';
|
|
17
17
|
import { AtomicIcon, AtomicText } from '../../atoms';
|
|
18
18
|
import { useAppDesignTokens } from '../../theme';
|
|
@@ -71,7 +71,7 @@ const ScreenHeaderBackButton: React.FC<{
|
|
|
71
71
|
backIconColor?: 'primary' | 'secondary' | 'error' | 'warning' | 'success' | 'surfaceVariant';
|
|
72
72
|
testID?: string;
|
|
73
73
|
}> = ({ hideBackButton, onBackPress, backIconName, backIconColor, testID }) => {
|
|
74
|
-
const handleBackPress =
|
|
74
|
+
const handleBackPress = useCallback(() => {
|
|
75
75
|
if (onBackPress) {
|
|
76
76
|
onBackPress();
|
|
77
77
|
}
|
|
@@ -136,7 +136,7 @@ export const ScreenHeader: React.FC<ScreenHeaderProps> = ({
|
|
|
136
136
|
}) => {
|
|
137
137
|
const tokens = useAppDesignTokens();
|
|
138
138
|
|
|
139
|
-
const headerStyle =
|
|
139
|
+
const headerStyle = useMemo(() => [
|
|
140
140
|
{
|
|
141
141
|
flexDirection: 'row' as const,
|
|
142
142
|
alignItems: 'center' as const,
|
|
@@ -12,12 +12,12 @@ import type {
|
|
|
12
12
|
} from "../../domain/entities/CardMultimedia.types";
|
|
13
13
|
|
|
14
14
|
export const useCardMediaGeneration = (): UseCardMediaGenerationResult => {
|
|
15
|
-
const [isGenerating, setIsGenerating] =
|
|
15
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
16
16
|
const [generationResult, setGenerationResult] =
|
|
17
|
-
|
|
18
|
-
const [error, setError] =
|
|
17
|
+
useState<CardMediaGenerationResult | null>(null);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
19
|
|
|
20
|
-
const generateMedia =
|
|
20
|
+
const generateMedia = useCallback(
|
|
21
21
|
async (
|
|
22
22
|
request: CardMediaGenerationRequest,
|
|
23
23
|
): Promise<CardMediaGenerationResult> => {
|
|
@@ -15,12 +15,12 @@ import type {
|
|
|
15
15
|
} from "../../domain/entities/CardMultimedia.types";
|
|
16
16
|
|
|
17
17
|
export const useCardMediaUpload = (): UseCardMediaUploadResult => {
|
|
18
|
-
const [isUploading, setIsUploading] =
|
|
18
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
19
19
|
const [uploadProgress, setUploadProgress] =
|
|
20
|
-
|
|
21
|
-
const [error, setError] =
|
|
20
|
+
useState<CardMediaUploadProgress | null>(null);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
22
|
|
|
23
|
-
const uploadMedia =
|
|
23
|
+
const uploadMedia = useCallback(
|
|
24
24
|
async (file: CardMediaFile, _options?: CardMediaCompressionOptions) => {
|
|
25
25
|
try {
|
|
26
26
|
setIsUploading(true);
|
|
@@ -9,12 +9,12 @@ import type { UseCardMediaValidationResult } from "./card-multimedia.types";
|
|
|
9
9
|
import type { CardMediaValidation, CardMediaFile } from "../../domain/entities/CardMultimedia.types";
|
|
10
10
|
|
|
11
11
|
export const useCardMediaValidation = (): UseCardMediaValidationResult => {
|
|
12
|
-
const [isValidating, setIsValidating] =
|
|
12
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
13
13
|
const [validation, setValidation] =
|
|
14
|
-
|
|
15
|
-
const [error, setError] =
|
|
14
|
+
useState<CardMediaValidation | null>(null);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
16
|
|
|
17
|
-
const validateMedia =
|
|
17
|
+
const validateMedia = useCallback(
|
|
18
18
|
async (file: CardMediaFile): Promise<CardMediaValidation> => {
|
|
19
19
|
try {
|
|
20
20
|
setIsValidating(true);
|
|
@@ -30,10 +30,10 @@ export type {
|
|
|
30
30
|
*/
|
|
31
31
|
export const useCardMultimediaFlashcard =
|
|
32
32
|
(): UseCardMultimediaFlashcardResult => {
|
|
33
|
-
const [isProcessing, setIsProcessing] =
|
|
34
|
-
const [error, setError] =
|
|
33
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
34
|
+
const [error, setError] = useState<string | null>(null);
|
|
35
35
|
|
|
36
|
-
const createCardMultimedia =
|
|
36
|
+
const createCardMultimedia = useCallback(
|
|
37
37
|
async (cardData: CreateCardMultimediaData): Promise<CardMultimediaFlashcard> => {
|
|
38
38
|
try {
|
|
39
39
|
setIsProcessing(true);
|
|
@@ -72,7 +72,7 @@ export const useCardMultimediaFlashcard =
|
|
|
72
72
|
[],
|
|
73
73
|
);
|
|
74
74
|
|
|
75
|
-
const updateCardMedia =
|
|
75
|
+
const updateCardMedia = useCallback(
|
|
76
76
|
async (
|
|
77
77
|
_cardId: string,
|
|
78
78
|
_media: CardMediaAttachment[],
|
|
@@ -84,7 +84,7 @@ export const useCardMultimediaFlashcard =
|
|
|
84
84
|
[],
|
|
85
85
|
);
|
|
86
86
|
|
|
87
|
-
const deleteCardMedia =
|
|
87
|
+
const deleteCardMedia = useCallback(
|
|
88
88
|
async (_attachmentId: string): Promise<void> => {
|
|
89
89
|
// Mock implementation
|
|
90
90
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -12,12 +12,12 @@ import type {
|
|
|
12
12
|
} from "../../domain/entities/MultimediaFlashcardTypes";
|
|
13
13
|
|
|
14
14
|
export const useMediaGeneration = (): UseMediaGenerationResult => {
|
|
15
|
-
const [isGenerating, setIsGenerating] =
|
|
15
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
16
16
|
const [generationResult, setGenerationResult] =
|
|
17
|
-
|
|
18
|
-
const [error, setError] =
|
|
17
|
+
useState<MediaGenerationResult | null>(null);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
19
|
|
|
20
|
-
const generateMedia =
|
|
20
|
+
const generateMedia = useCallback(
|
|
21
21
|
async (request: MediaGenerationRequest): Promise<MediaGenerationResult> => {
|
|
22
22
|
try {
|
|
23
23
|
setIsGenerating(true);
|
|
@@ -15,12 +15,12 @@ import type {
|
|
|
15
15
|
} from "../../domain/entities/MultimediaFlashcardTypes";
|
|
16
16
|
|
|
17
17
|
export const useMediaUpload = (): UseMediaUploadResult => {
|
|
18
|
-
const [isUploading, setIsUploading] =
|
|
18
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
19
19
|
const [uploadProgress, setUploadProgress] =
|
|
20
|
-
|
|
21
|
-
const [error, setError] =
|
|
20
|
+
useState<MediaUploadProgress | null>(null);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
22
|
|
|
23
|
-
const uploadMedia =
|
|
23
|
+
const uploadMedia = useCallback(
|
|
24
24
|
async (file: MediaFile, _options?: MediaCompressionOptions) => {
|
|
25
25
|
try {
|
|
26
26
|
setIsUploading(true);
|
|
@@ -9,13 +9,13 @@ import type { UseMediaValidationResult } from "./multimedia.types";
|
|
|
9
9
|
import type { MediaValidation, MediaFile } from "../../domain/entities/MultimediaFlashcardTypes";
|
|
10
10
|
|
|
11
11
|
export const useMediaValidation = (): UseMediaValidationResult => {
|
|
12
|
-
const [isValidating, setIsValidating] =
|
|
13
|
-
const [validation, setValidation] =
|
|
12
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
13
|
+
const [validation, setValidation] = useState<MediaValidation | null>(
|
|
14
14
|
null,
|
|
15
15
|
);
|
|
16
|
-
const [error, setError] =
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
17
|
|
|
18
|
-
const validateMedia =
|
|
18
|
+
const validateMedia = useCallback(
|
|
19
19
|
async (file: MediaFile): Promise<MediaValidation> => {
|
|
20
20
|
try {
|
|
21
21
|
setIsValidating(true);
|
|
@@ -29,10 +29,10 @@ export type {
|
|
|
29
29
|
* Main hook for multimedia flashcard operations
|
|
30
30
|
*/
|
|
31
31
|
export const useMultimediaFlashcard = (): UseMultimediaFlashcardResult => {
|
|
32
|
-
const [isProcessing, setIsProcessing] =
|
|
33
|
-
const [error, setError] =
|
|
32
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
33
|
+
const [error, setError] = useState<string | null>(null);
|
|
34
34
|
|
|
35
|
-
const createMultimediaCard =
|
|
35
|
+
const createMultimediaCard = useCallback(
|
|
36
36
|
async (cardData: CreateMultimediaCardData): Promise<MultimediaFlashcard> => {
|
|
37
37
|
try {
|
|
38
38
|
setIsProcessing(true);
|
|
@@ -71,7 +71,7 @@ export const useMultimediaFlashcard = (): UseMultimediaFlashcardResult => {
|
|
|
71
71
|
[],
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
const updateMedia =
|
|
74
|
+
const updateMedia = useCallback(
|
|
75
75
|
async (
|
|
76
76
|
_cardId: string,
|
|
77
77
|
_media: MediaAttachment[],
|
|
@@ -83,7 +83,7 @@ export const useMultimediaFlashcard = (): UseMultimediaFlashcardResult => {
|
|
|
83
83
|
[],
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
-
const deleteMedia =
|
|
86
|
+
const deleteMedia = useCallback(
|
|
87
87
|
async (_attachmentId: string): Promise<void> => {
|
|
88
88
|
// Mock implementation
|
|
89
89
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -47,7 +47,7 @@ export const BaseModal: React.FC<BaseModalProps> = ({
|
|
|
47
47
|
}
|
|
48
48
|
}, [visible, testID, modalLayout.width, modalLayout.height]);
|
|
49
49
|
|
|
50
|
-
const handleBackdropPress =
|
|
50
|
+
const handleBackdropPress = useCallback(() => {
|
|
51
51
|
if (dismissOnBackdrop) {
|
|
52
52
|
onClose();
|
|
53
53
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Content component for confirmation modal
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React from 'react';
|
|
7
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
8
8
|
import { View, ViewStyle, StyleProp } from 'react-native';
|
|
9
9
|
import { useAppDesignTokens } from '../theme';
|
|
10
10
|
import { ConfirmationModalVariant } from './confirmation-modal/types/';
|
|
@@ -27,7 +27,7 @@ const useConfirmButtonStyle = (
|
|
|
27
27
|
variant: ConfirmationModalVariant,
|
|
28
28
|
tokens: ReturnType<typeof useAppDesignTokens>
|
|
29
29
|
) => {
|
|
30
|
-
return
|
|
30
|
+
return useCallback(() => {
|
|
31
31
|
const baseStyle = getButtonStyle();
|
|
32
32
|
const variantStyles: ViewStyle[] = [];
|
|
33
33
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Main confirmation modal component
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React from 'react';
|
|
7
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
8
8
|
import { View, Modal, TouchableOpacity } from 'react-native';
|
|
9
9
|
import { useAppDesignTokens } from '../theme';
|
|
10
10
|
import { ConfirmationModalProps } from './confirmation-modal/types/';
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { ConfirmationModalContent } from './ConfirmationModalContent';
|
|
16
16
|
|
|
17
17
|
const useBackdropHandler = (backdropDismissible: boolean, onCancel: () => void) => {
|
|
18
|
-
return
|
|
18
|
+
return useCallback(() => {
|
|
19
19
|
if (backdropDismissible) {
|
|
20
20
|
onCancel();
|
|
21
21
|
}
|
|
@@ -54,7 +54,7 @@ export const FilterBottomSheet = forwardRef<BottomSheetModalRef, FilterBottomShe
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const styles =
|
|
57
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
58
58
|
container: {
|
|
59
59
|
flex: 1,
|
|
60
60
|
padding: 16,
|
|
@@ -151,7 +151,7 @@ export const FilterBottomSheet = forwardRef<BottomSheetModalRef, FilterBottomShe
|
|
|
151
151
|
|
|
152
152
|
const hasActiveFilters = FilterUtils.hasActiveFilter(safeSelectedIds, defaultId);
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
useEffect(() => {
|
|
155
155
|
if (__DEV__) {
|
|
156
156
|
console.log('[FilterBottomSheet] useEffect - Component ready', {
|
|
157
157
|
refCurrent: !!internalRef.current,
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
import React from 'react';
|
|
34
34
|
import { View, StyleProp, ViewStyle } from 'react-native';
|
|
35
|
-
import { useAppDesignTokens } from '../../../../
|
|
35
|
+
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
36
36
|
import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
|
|
37
37
|
import { CalendarService } from '../../infrastructure/services/CalendarService';
|
|
38
38
|
import { calendarStyles } from './calendarStyles';
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { TouchableOpacity, View, StyleProp, ViewStyle } from 'react-native';
|
|
7
|
-
import { AtomicText
|
|
7
|
+
import { AtomicText } from '../../../../atoms';
|
|
8
|
+
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
8
9
|
import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
|
|
9
10
|
|
|
10
11
|
import { calendarStyles } from './calendarStyles';
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Hook for managing confirmation modal state
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React from 'react';
|
|
7
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
8
8
|
import { ConfirmationModalProps, ConfirmationModalVariant } from './types';
|
|
9
9
|
|
|
10
10
|
const useConfirmationModalState = () => {
|
|
11
|
-
const [visible, setVisible] =
|
|
11
|
+
const [visible, setVisible] = useState(false);
|
|
12
12
|
|
|
13
|
-
const showConfirmation =
|
|
14
|
-
const hideConfirmation =
|
|
13
|
+
const showConfirmation = useCallback(() => setVisible(true), []);
|
|
14
|
+
const hideConfirmation = useCallback(() => setVisible(false), []);
|
|
15
15
|
|
|
16
16
|
return { visible, showConfirmation, hideConfirmation };
|
|
17
17
|
};
|
|
@@ -26,12 +26,12 @@ export const useConfirmationModal = (config: {
|
|
|
26
26
|
}) => {
|
|
27
27
|
const { visible, showConfirmation, hideConfirmation } = useConfirmationModalState();
|
|
28
28
|
|
|
29
|
-
const handleConfirm =
|
|
29
|
+
const handleConfirm = useCallback(() => {
|
|
30
30
|
config.onConfirm();
|
|
31
31
|
hideConfirmation();
|
|
32
32
|
}, [config, hideConfirmation]);
|
|
33
33
|
|
|
34
|
-
const confirmationProps: ConfirmationModalProps =
|
|
34
|
+
const confirmationProps: ConfirmationModalProps = useMemo(() => ({
|
|
35
35
|
visible,
|
|
36
36
|
title: config.title,
|
|
37
37
|
message: config.message,
|
|
@@ -37,7 +37,7 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
37
37
|
showSeconds = true,
|
|
38
38
|
} = displayConfig;
|
|
39
39
|
|
|
40
|
-
const [currentTargetIndex, setCurrentTargetIndex] =
|
|
40
|
+
const [currentTargetIndex, setCurrentTargetIndex] = useState(0);
|
|
41
41
|
const allTargets = useMemo(
|
|
42
42
|
() => [target, ...alternateTargets],
|
|
43
43
|
[target, alternateTargets]
|
|
@@ -49,7 +49,7 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
49
49
|
onExpire,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
useEffect(() => {
|
|
53
53
|
if (currentTarget) {
|
|
54
54
|
updateTarget(currentTarget);
|
|
55
55
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useEffect, useState, useCallback } from "react";
|
|
3
|
-
import { View, Image, StyleSheet } from "react-native";
|
|
3
|
+
import { View, Image, StyleSheet, Text, ActivityIndicator } from "react-native";
|
|
4
4
|
import { initialWindowMetrics } from "../../../safe-area";
|
|
5
|
-
import {
|
|
6
|
-
import { useAppDesignTokens } from "../../../theme";
|
|
5
|
+
import { useAppDesignTokens } from "../../../theme/hooks/useAppDesignTokens";
|
|
7
6
|
import type { SplashScreenProps, SplashColors } from "../types";
|
|
8
7
|
import { SPLASH_CONSTANTS } from "../constants";
|
|
9
8
|
|
|
@@ -100,39 +99,26 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
100
99
|
)}
|
|
101
100
|
|
|
102
101
|
{appName ? (
|
|
103
|
-
<
|
|
104
|
-
type="displaySmall"
|
|
105
|
-
style={[styles.title, { color: colors.text }]}
|
|
106
|
-
>
|
|
102
|
+
<Text style={[styles.title, { color: colors.text, fontSize: 30, fontWeight: '800' }]}>
|
|
107
103
|
{appName}
|
|
108
|
-
</
|
|
104
|
+
</Text>
|
|
109
105
|
) : null}
|
|
110
106
|
|
|
111
107
|
{tagline ? (
|
|
112
|
-
<
|
|
113
|
-
type="bodyLarge"
|
|
114
|
-
style={[styles.tagline, { color: colors.text }]}
|
|
115
|
-
>
|
|
108
|
+
<Text style={[styles.tagline, { color: colors.text, fontSize: 16 }]}>
|
|
116
109
|
{tagline}
|
|
117
|
-
</
|
|
110
|
+
</Text>
|
|
118
111
|
) : null}
|
|
119
112
|
|
|
120
113
|
{/* Always show loading indicator during initialization */}
|
|
121
114
|
<View style={styles.loadingContainer}>
|
|
122
|
-
<
|
|
123
|
-
size="lg"
|
|
124
|
-
color={colors.text}
|
|
125
|
-
style={styles.loadingIndicator}
|
|
126
|
-
/>
|
|
115
|
+
<ActivityIndicator size="large" color={colors.text} />
|
|
127
116
|
</View>
|
|
128
117
|
|
|
129
118
|
{timedOut && __DEV__ ? (
|
|
130
|
-
<
|
|
131
|
-
type="labelSmall"
|
|
132
|
-
style={[styles.timeoutText, { color: colors.text }]}
|
|
133
|
-
>
|
|
119
|
+
<Text style={[styles.timeoutText, { color: colors.text, fontSize: 12 }]}>
|
|
134
120
|
Initialization timeout
|
|
135
|
-
</
|
|
121
|
+
</Text>
|
|
136
122
|
) : null}
|
|
137
123
|
</View>
|
|
138
124
|
</View>
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import React from 'react';
|
|
12
12
|
import { StyleSheet, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
13
|
-
import { AtomicText, AtomicIcon } from '../../../../
|
|
14
|
-
import { useAppDesignTokens } from '../../../../
|
|
13
|
+
import { AtomicText, AtomicIcon } from '../../../../atoms';
|
|
14
|
+
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
15
15
|
import { HapticService } from '../../../../haptics';
|
|
16
16
|
import type { SwipeActionConfig } from '../../domain/entities/SwipeAction';
|
|
17
17
|
import { SwipeActionUtils } from '../../domain/entities/SwipeAction';
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* @module FormContainer
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
import React from 'react';
|
|
46
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
47
47
|
import {
|
|
48
48
|
ScrollView,
|
|
49
49
|
View,
|
|
@@ -106,7 +106,7 @@ export const FormContainer: React.FC<FormContainerProps> = ({
|
|
|
106
106
|
const formElementSpacing = tokens.spacing.lg;
|
|
107
107
|
|
|
108
108
|
// Create styles for form container (memoized to avoid re-creation on every render)
|
|
109
|
-
const styles =
|
|
109
|
+
const styles = useMemo(
|
|
110
110
|
() =>
|
|
111
111
|
StyleSheet.create({
|
|
112
112
|
container: {
|
|
@@ -12,6 +12,7 @@ export class StorageError extends Error {
|
|
|
12
12
|
constructor(message: string, public readonly key?: string) {
|
|
13
13
|
super(message);
|
|
14
14
|
this.name = 'StorageError';
|
|
15
|
+
Object.setPrototypeOf(this, StorageError.prototype);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -25,6 +26,7 @@ export class StorageReadError extends StorageError {
|
|
|
25
26
|
super(`Failed to read from storage: ${key}`, key);
|
|
26
27
|
this.name = 'StorageReadError';
|
|
27
28
|
this.cause = cause;
|
|
29
|
+
Object.setPrototypeOf(this, StorageReadError.prototype);
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -38,6 +40,7 @@ export class StorageWriteError extends StorageError {
|
|
|
38
40
|
super(`Failed to write to storage: ${key}`, key);
|
|
39
41
|
this.name = 'StorageWriteError';
|
|
40
42
|
this.cause = cause;
|
|
43
|
+
Object.setPrototypeOf(this, StorageWriteError.prototype);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -51,6 +54,7 @@ export class StorageDeleteError extends StorageError {
|
|
|
51
54
|
super(`Failed to delete from storage: ${key}`, key);
|
|
52
55
|
this.name = 'StorageDeleteError';
|
|
53
56
|
this.cause = cause;
|
|
57
|
+
Object.setPrototypeOf(this, StorageDeleteError.prototype);
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -64,6 +68,7 @@ export class StorageSerializationError extends StorageError {
|
|
|
64
68
|
super(`Failed to serialize data for key: ${key}`, key);
|
|
65
69
|
this.name = 'StorageSerializationError';
|
|
66
70
|
this.cause = cause;
|
|
71
|
+
Object.setPrototypeOf(this, StorageSerializationError.prototype);
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -77,5 +82,6 @@ export class StorageDeserializationError extends StorageError {
|
|
|
77
82
|
super(`Failed to deserialize data for key: ${key}`, key);
|
|
78
83
|
this.name = 'StorageDeserializationError';
|
|
79
84
|
this.cause = cause;
|
|
85
|
+
Object.setPrototypeOf(this, StorageDeserializationError.prototype);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -16,63 +16,76 @@ import { BatchStorageOperations } from './BatchStorageOperations';
|
|
|
16
16
|
* Uses composition to follow Single Responsibility Principle
|
|
17
17
|
*/
|
|
18
18
|
export class AsyncStorageRepository implements IStorageRepository {
|
|
19
|
-
private baseOps: BaseStorageOperations;
|
|
20
|
-
private stringOps: StringStorageOperations;
|
|
21
|
-
private batchOps: BatchStorageOperations;
|
|
19
|
+
private baseOps: BaseStorageOperations | null = null;
|
|
20
|
+
private stringOps: StringStorageOperations | null = null;
|
|
21
|
+
private batchOps: BatchStorageOperations | null = null;
|
|
22
22
|
|
|
23
23
|
constructor() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// Lazy initialization - defer object creation
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private ensureInitialized() {
|
|
28
|
+
if (!this.baseOps) {
|
|
29
|
+
this.baseOps = new BaseStorageOperations();
|
|
30
|
+
this.stringOps = new StringStorageOperations();
|
|
31
|
+
this.batchOps = new BatchStorageOperations();
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Get item from AsyncStorage with type safety
|
|
31
37
|
*/
|
|
32
38
|
async getItem<T>(key: string, defaultValue: T): Promise<StorageResult<T>> {
|
|
33
|
-
|
|
39
|
+
this.ensureInitialized();
|
|
40
|
+
return this.baseOps!.getItem(key, defaultValue);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
37
44
|
* Set item in AsyncStorage with automatic JSON serialization
|
|
38
45
|
*/
|
|
39
46
|
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
|
|
40
|
-
|
|
47
|
+
this.ensureInitialized();
|
|
48
|
+
return this.baseOps!.setItem(key, value);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
/**
|
|
44
52
|
* Get string value (no JSON parsing)
|
|
45
53
|
*/
|
|
46
54
|
async getString(key: string, defaultValue: string): Promise<StorageResult<string>> {
|
|
47
|
-
|
|
55
|
+
this.ensureInitialized();
|
|
56
|
+
return this.stringOps!.getString(key, defaultValue);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/**
|
|
51
60
|
* Set string value (no JSON serialization)
|
|
52
61
|
*/
|
|
53
62
|
async setString(key: string, value: string): Promise<StorageResult<string>> {
|
|
54
|
-
|
|
63
|
+
this.ensureInitialized();
|
|
64
|
+
return this.stringOps!.setString(key, value);
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/**
|
|
58
68
|
* Remove item from AsyncStorage
|
|
59
69
|
*/
|
|
60
70
|
async removeItem(key: string): Promise<StorageResult<void>> {
|
|
61
|
-
|
|
71
|
+
this.ensureInitialized();
|
|
72
|
+
return this.baseOps!.removeItem(key);
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
/**
|
|
65
76
|
* Check if key exists in storage
|
|
66
77
|
*/
|
|
67
78
|
async hasItem(key: string): Promise<boolean> {
|
|
68
|
-
|
|
79
|
+
this.ensureInitialized();
|
|
80
|
+
return this.baseOps!.hasItem(key);
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
/**
|
|
72
84
|
* Clear all AsyncStorage data
|
|
73
85
|
*/
|
|
74
86
|
async clearAll(): Promise<StorageResult<void>> {
|
|
75
|
-
|
|
87
|
+
this.ensureInitialized();
|
|
88
|
+
return this.baseOps!.clearAll();
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
/**
|
|
@@ -81,18 +94,20 @@ export class AsyncStorageRepository implements IStorageRepository {
|
|
|
81
94
|
async getMultiple(
|
|
82
95
|
keys: string[]
|
|
83
96
|
): Promise<StorageResult<Record<string, string | null>>> {
|
|
84
|
-
|
|
97
|
+
this.ensureInitialized();
|
|
98
|
+
return this.batchOps!.getMultiple(keys);
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
/**
|
|
88
102
|
* Get all keys from storage
|
|
89
103
|
*/
|
|
90
104
|
async getAllKeys(): Promise<StorageResult<string[]>> {
|
|
91
|
-
|
|
105
|
+
this.ensureInitialized();
|
|
106
|
+
return this.batchOps!.getAllKeys();
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
/**
|
|
96
|
-
* Singleton instance
|
|
111
|
+
* Singleton instance - lazy initialization
|
|
97
112
|
*/
|
|
98
113
|
export const storageRepository = new AsyncStorageRepository();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Repository Interface
|
|
3
|
+
* Defines the contract for repository implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
7
|
+
import type { QueryKeyFactory } from '../utils/QueryKeyFactory';
|
|
8
|
+
import type {
|
|
9
|
+
CreateParams,
|
|
10
|
+
UpdateParams,
|
|
11
|
+
ListParams,
|
|
12
|
+
RepositoryOptions,
|
|
13
|
+
} from './RepositoryTypes';
|
|
14
|
+
|
|
15
|
+
export interface IBaseRepository<TData, TCreateVariables, TUpdateVariables> {
|
|
16
|
+
/** Query client instance */
|
|
17
|
+
getClient(): QueryClient;
|
|
18
|
+
|
|
19
|
+
/** Resource name */
|
|
20
|
+
readonly resource: string;
|
|
21
|
+
|
|
22
|
+
/** Query key factory */
|
|
23
|
+
readonly keys: QueryKeyFactory;
|
|
24
|
+
|
|
25
|
+
/** Cache options */
|
|
26
|
+
getCacheOptions(): RepositoryOptions;
|
|
27
|
+
|
|
28
|
+
/** Abstract methods to be implemented by subclasses */
|
|
29
|
+
fetchAll(params?: ListParams): Promise<TData[]>;
|
|
30
|
+
fetchById(id: string | number): Promise<TData>;
|
|
31
|
+
create(data: CreateParams<TCreateVariables>): Promise<TData>;
|
|
32
|
+
update(id: string | number, data: UpdateParams<TUpdateVariables>): Promise<TData>;
|
|
33
|
+
remove(id: string | number): Promise<void>;
|
|
34
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { QueryClient } from '@tanstack/react-query';
|
|
7
|
-
import type {
|
|
7
|
+
import type { IBaseRepository } from '../IBaseRepository';
|
|
8
8
|
import { matchesResource } from '../helpers/repositoryHelpers';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -13,7 +13,7 @@ import { matchesResource } from '../helpers/repositoryHelpers';
|
|
|
13
13
|
* @param repository - Repository instance
|
|
14
14
|
*/
|
|
15
15
|
export function invalidateAll<TData>(
|
|
16
|
-
repository:
|
|
16
|
+
repository: IBaseRepository<TData, unknown, unknown>
|
|
17
17
|
): Promise<void> {
|
|
18
18
|
const client = (repository as any).getClient() as QueryClient;
|
|
19
19
|
const resource = (repository as any).resource;
|
|
@@ -31,7 +31,7 @@ export function invalidateAll<TData>(
|
|
|
31
31
|
* @param repository - Repository instance
|
|
32
32
|
*/
|
|
33
33
|
export function invalidateLists<TData>(
|
|
34
|
-
repository:
|
|
34
|
+
repository: IBaseRepository<TData, unknown, unknown>
|
|
35
35
|
): Promise<void> {
|
|
36
36
|
const client = (repository as any).getClient() as QueryClient;
|
|
37
37
|
return client.invalidateQueries({
|
|
@@ -46,7 +46,7 @@ export function invalidateLists<TData>(
|
|
|
46
46
|
* @param id - Item ID
|
|
47
47
|
*/
|
|
48
48
|
export function invalidateDetail<TData>(
|
|
49
|
-
repository:
|
|
49
|
+
repository: IBaseRepository<TData, unknown, unknown>,
|
|
50
50
|
id: string | number
|
|
51
51
|
): Promise<void> {
|
|
52
52
|
const client = (repository as any).getClient() as QueryClient;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { QueryClient, QueryKey } from '@tanstack/react-query';
|
|
7
7
|
import type { ListParams } from '../RepositoryTypes';
|
|
8
|
-
import type {
|
|
8
|
+
import type { IBaseRepository } from '../IBaseRepository';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Query all items with caching
|
|
@@ -15,7 +15,7 @@ import type { BaseRepository } from '../BaseRepository';
|
|
|
15
15
|
* @returns Promise of data array
|
|
16
16
|
*/
|
|
17
17
|
export async function queryAll<TData>(
|
|
18
|
-
repository:
|
|
18
|
+
repository: IBaseRepository<TData, unknown, unknown>,
|
|
19
19
|
params?: ListParams
|
|
20
20
|
): Promise<TData[]> {
|
|
21
21
|
const client = (repository as any).getClient() as QueryClient;
|
|
@@ -39,7 +39,7 @@ export async function queryAll<TData>(
|
|
|
39
39
|
* @returns Promise of data or undefined if not found
|
|
40
40
|
*/
|
|
41
41
|
export async function queryById<TData>(
|
|
42
|
-
repository:
|
|
42
|
+
repository: IBaseRepository<TData, unknown, unknown>,
|
|
43
43
|
id: string | number
|
|
44
44
|
): Promise<TData | undefined> {
|
|
45
45
|
const client = (repository as any).getClient() as QueryClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
3
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
|
|
4
4
|
import type { Persister } from '@tanstack/react-query-persist-client';
|
|
@@ -78,7 +78,7 @@ export function TanstackProvider({
|
|
|
78
78
|
onPersistError,
|
|
79
79
|
}: TanstackProviderProps): React.ReactElement {
|
|
80
80
|
// Create QueryClient if not provided and set as global singleton
|
|
81
|
-
const [queryClient] =
|
|
81
|
+
const [queryClient] = useState(() => {
|
|
82
82
|
const client = providedQueryClient ?? createQueryClient(queryClientOptions);
|
|
83
83
|
setGlobalQueryClient(client);
|
|
84
84
|
|
|
@@ -90,7 +90,7 @@ export function TanstackProvider({
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
// Create persister if persistence is enabled
|
|
93
|
-
const [persister] =
|
|
93
|
+
const [persister] = useState(() => {
|
|
94
94
|
if (!enablePersistence) return undefined;
|
|
95
95
|
return providedPersister ?? createPersister(persisterOptions);
|
|
96
96
|
});
|
package/src/theme/index.ts
CHANGED
|
@@ -76,9 +76,6 @@ export { useCommonStyles } from './hooks/useCommonStyles';
|
|
|
76
76
|
|
|
77
77
|
export { DesignSystemProvider } from './infrastructure/providers/DesignSystemProvider';
|
|
78
78
|
|
|
79
|
-
// Re-export icon types for convenience
|
|
80
|
-
export type { IconRenderer, IconRenderProps, IconNames } from '../atoms/icon';
|
|
81
|
-
|
|
82
79
|
// =============================================================================
|
|
83
80
|
// THEME OBJECTS
|
|
84
81
|
// =============================================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState, ReactNode } from 'react';
|
|
1
|
+
import React, { useEffect, useState, ReactNode, lazy, Suspense } from 'react';
|
|
2
2
|
import { ActivityIndicator, View, StyleSheet } from 'react-native';
|
|
3
3
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import { useFonts } from 'expo-font';
|
|
@@ -6,9 +6,12 @@ import { SafeAreaProvider, initialWindowMetrics } from '../../../safe-area';
|
|
|
6
6
|
import { useTheme } from '../stores/themeStore';
|
|
7
7
|
import { useDesignSystemTheme, type ThemeMode } from '../globalThemeStore';
|
|
8
8
|
import type { CustomThemeColors } from '../../core/CustomColors';
|
|
9
|
-
import { SplashScreen } from '../../../molecules/splash';
|
|
10
9
|
import type { SplashScreenProps } from '../../../molecules/splash/types';
|
|
11
|
-
import { useIconStore
|
|
10
|
+
import { useIconStore } from '../../../atoms/icon/iconStore';
|
|
11
|
+
import type { IconRenderer, IconNames } from '../../../atoms/icon/iconStore';
|
|
12
|
+
|
|
13
|
+
// Lazy load SplashScreen to avoid circular dependency
|
|
14
|
+
const SplashScreen = lazy(() => import('../../../molecules/splash').then(m => ({ default: m.SplashScreen })));
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
interface DesignSystemProviderProps {
|
|
@@ -97,7 +100,15 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
|
|
|
97
100
|
if (loadingComponent) {
|
|
98
101
|
content = loadingComponent;
|
|
99
102
|
} else if (splashConfig) {
|
|
100
|
-
content =
|
|
103
|
+
content = (
|
|
104
|
+
<Suspense fallback={
|
|
105
|
+
<View style={styles.loadingContainer}>
|
|
106
|
+
<ActivityIndicator size="large" />
|
|
107
|
+
</View>
|
|
108
|
+
}>
|
|
109
|
+
<SplashScreen {...splashConfig} visible={true} />
|
|
110
|
+
</Suspense>
|
|
111
|
+
);
|
|
101
112
|
} else {
|
|
102
113
|
content = (
|
|
103
114
|
<View style={styles.loadingContainer}>
|