@umituz/react-native-design-system 4.23.104 → 4.23.106
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/filesystem/infrastructure/services/download.service.ts +29 -15
- package/src/image/presentation/hooks/useImageGallery.ts +18 -11
- package/src/init/useAppInitialization.ts +1 -1
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +9 -3
- package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +14 -8
- package/src/onboarding/presentation/hooks/useOnboardingGestures.ts +22 -1
- package/src/tanstack/presentation/hooks/usePrefetch.ts +4 -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.106",
|
|
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",
|
|
@@ -30,7 +30,12 @@ export async function downloadFile(url: string, dest?: string): Promise<FileOper
|
|
|
30
30
|
} catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export async function downloadFileWithProgress(
|
|
33
|
+
export async function downloadFileWithProgress(
|
|
34
|
+
url: string,
|
|
35
|
+
cacheDir: string,
|
|
36
|
+
onProgress?: DownloadProgressCallback,
|
|
37
|
+
signal?: AbortSignal
|
|
38
|
+
): Promise<DownloadWithProgressResult> {
|
|
34
39
|
try {
|
|
35
40
|
const dir = new Directory(cacheDir);
|
|
36
41
|
if (!dir.exists) dir.create({ intermediates: true, idempotent: true });
|
|
@@ -38,9 +43,9 @@ export async function downloadFileWithProgress(url: string, cacheDir: string, on
|
|
|
38
43
|
const destUri = getCacheUri(url, cacheDir);
|
|
39
44
|
if (new File(destUri).exists) return { success: true, uri: destUri, fromCache: true };
|
|
40
45
|
|
|
41
|
-
const response = await fetch(url);
|
|
46
|
+
const response = await fetch(url, { signal });
|
|
42
47
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
43
|
-
|
|
48
|
+
|
|
44
49
|
const totalBytes = parseInt(response.headers.get("content-length") || "0", 10);
|
|
45
50
|
if (!response.body) return { ...(await downloadFile(url, destUri)), fromCache: false };
|
|
46
51
|
|
|
@@ -48,20 +53,29 @@ export async function downloadFileWithProgress(url: string, cacheDir: string, on
|
|
|
48
53
|
const chunks: Uint8Array[] = [];
|
|
49
54
|
let received = 0;
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
56
|
+
try {
|
|
57
|
+
while (true) {
|
|
58
|
+
if (signal?.aborted) {
|
|
59
|
+
await reader.cancel();
|
|
60
|
+
throw new Error("Download aborted");
|
|
61
|
+
}
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const { done, value } = await reader.read();
|
|
64
|
+
if (done) break;
|
|
65
|
+
chunks.push(value);
|
|
66
|
+
received += value.length;
|
|
67
|
+
onProgress?.({ totalBytesWritten: received, totalBytesExpectedToWrite: totalBytes || received });
|
|
68
|
+
}
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
const all = new Uint8Array(received);
|
|
71
|
+
let pos = 0;
|
|
72
|
+
for (const c of chunks) { all.set(c, pos); pos += c.length; }
|
|
73
|
+
new File(destUri).write(all);
|
|
74
|
+
|
|
75
|
+
return { success: true, uri: destUri, fromCache: false };
|
|
76
|
+
} finally {
|
|
77
|
+
reader.releaseLock();
|
|
78
|
+
}
|
|
65
79
|
} catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
|
|
66
80
|
}
|
|
67
81
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Presentation - Image Gallery Hook
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useState, useCallback, useMemo } from 'react';
|
|
5
|
+
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
6
6
|
import { ImageViewerService } from '../../infrastructure/services/ImageViewerService';
|
|
7
7
|
import type {
|
|
8
8
|
ImageViewerItem,
|
|
@@ -29,6 +29,13 @@ export const useImageGallery = (
|
|
|
29
29
|
defaultOptions || ImageViewerService.getDefaultOptions()
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
+
// Use ref to track latest options and avoid stale closures
|
|
33
|
+
const optionsRef = useRef(galleryOptions);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
optionsRef.current = galleryOptions;
|
|
37
|
+
}, [galleryOptions]);
|
|
38
|
+
|
|
32
39
|
const open = useCallback(
|
|
33
40
|
(
|
|
34
41
|
imageData: ImageViewerItem[] | string[],
|
|
@@ -44,32 +51,32 @@ export const useImageGallery = (
|
|
|
44
51
|
setCurrentIndex(options?.index ?? startIndex);
|
|
45
52
|
|
|
46
53
|
if (options) {
|
|
47
|
-
setGalleryOptions({
|
|
48
|
-
...
|
|
54
|
+
setGalleryOptions(prev => ({
|
|
55
|
+
...prev,
|
|
49
56
|
...options,
|
|
50
|
-
});
|
|
57
|
+
}));
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
setVisible(true);
|
|
54
61
|
},
|
|
55
|
-
[
|
|
62
|
+
[]
|
|
56
63
|
);
|
|
57
64
|
|
|
58
65
|
const close = useCallback(() => {
|
|
59
66
|
setVisible(false);
|
|
60
67
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
68
|
+
if (optionsRef.current.onDismiss) {
|
|
69
|
+
optionsRef.current.onDismiss();
|
|
63
70
|
}
|
|
64
|
-
}, [
|
|
71
|
+
}, []);
|
|
65
72
|
|
|
66
73
|
const setIndex = useCallback((index: number) => {
|
|
67
74
|
setCurrentIndex(index);
|
|
68
75
|
|
|
69
|
-
if (
|
|
70
|
-
|
|
76
|
+
if (optionsRef.current.onIndexChange) {
|
|
77
|
+
optionsRef.current.onIndexChange(index);
|
|
71
78
|
}
|
|
72
|
-
}, [
|
|
79
|
+
}, []);
|
|
73
80
|
|
|
74
81
|
const options = useMemo(() => ({
|
|
75
82
|
backgroundColor: galleryOptions.backgroundColor || '#000000',
|
|
@@ -88,18 +88,24 @@ export const useCalendarPresentation = (): UseCalendarReturn => {
|
|
|
88
88
|
// Load events on mount
|
|
89
89
|
useEffect(() => {
|
|
90
90
|
actions.loadEvents();
|
|
91
|
-
}, []);
|
|
91
|
+
}, [actions.loadEvents]);
|
|
92
92
|
|
|
93
93
|
// Get events for selected date
|
|
94
94
|
const selectedDateEvents = useMemo(() => {
|
|
95
|
-
return
|
|
95
|
+
return events.filter(event => {
|
|
96
|
+
const eventDate = new Date(event.date);
|
|
97
|
+
return eventDate.toDateString() === selectedDate.toDateString();
|
|
98
|
+
});
|
|
96
99
|
}, [selectedDate, events]);
|
|
97
100
|
|
|
98
101
|
// Get events for current month
|
|
99
102
|
const currentMonthEvents = useMemo(() => {
|
|
100
103
|
const year = currentMonth.getFullYear();
|
|
101
104
|
const month = currentMonth.getMonth();
|
|
102
|
-
return
|
|
105
|
+
return events.filter(event => {
|
|
106
|
+
const eventDate = new Date(event.date);
|
|
107
|
+
return eventDate.getFullYear() === year && eventDate.getMonth() === month;
|
|
108
|
+
});
|
|
103
109
|
}, [currentMonth, events]);
|
|
104
110
|
|
|
105
111
|
return {
|
|
@@ -33,16 +33,22 @@ export const useOnboardingNavigation = (
|
|
|
33
33
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
34
34
|
|
|
35
35
|
const goToNext = useCallback(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
setCurrentIndex(prev => {
|
|
37
|
+
if (prev < totalSlides - 1) {
|
|
38
|
+
return prev + 1;
|
|
39
|
+
}
|
|
40
|
+
return prev;
|
|
41
|
+
});
|
|
42
|
+
}, [totalSlides]);
|
|
40
43
|
|
|
41
44
|
const goToPrevious = useCallback(() => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
setCurrentIndex(prev => {
|
|
46
|
+
if (prev > 0) {
|
|
47
|
+
return prev - 1;
|
|
48
|
+
}
|
|
49
|
+
return prev;
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
46
52
|
|
|
47
53
|
const complete = useCallback(async () => {
|
|
48
54
|
if (onComplete) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles swipe gestures for onboarding navigation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useRef } from "react";
|
|
6
|
+
import { useRef, useEffect } from "react";
|
|
7
7
|
import { PanResponder } from "react-native";
|
|
8
8
|
|
|
9
9
|
interface UseOnboardingGesturesProps {
|
|
@@ -19,6 +19,24 @@ export const useOnboardingGestures = ({
|
|
|
19
19
|
onNext,
|
|
20
20
|
onBack,
|
|
21
21
|
}: UseOnboardingGesturesProps) => {
|
|
22
|
+
// Use refs to track latest values and avoid stale closures
|
|
23
|
+
const latestPropsRef = useRef({
|
|
24
|
+
isFirstSlide,
|
|
25
|
+
isAnswerValid,
|
|
26
|
+
onNext,
|
|
27
|
+
onBack,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Update refs on every render to ensure PanResponder has fresh values
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
latestPropsRef.current = {
|
|
33
|
+
isFirstSlide,
|
|
34
|
+
isAnswerValid,
|
|
35
|
+
onNext,
|
|
36
|
+
onBack,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
22
40
|
const panResponder = useRef(
|
|
23
41
|
PanResponder.create({
|
|
24
42
|
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
@@ -26,6 +44,9 @@ export const useOnboardingGestures = ({
|
|
|
26
44
|
return Math.abs(gestureState.dx) > 20 && Math.abs(gestureState.dy) < 40;
|
|
27
45
|
},
|
|
28
46
|
onPanResponderRelease: (_, gestureState) => {
|
|
47
|
+
// Read from ref to get latest values
|
|
48
|
+
const { isFirstSlide, isAnswerValid, onNext, onBack } = latestPropsRef.current;
|
|
49
|
+
|
|
29
50
|
if (gestureState.dx > 50) {
|
|
30
51
|
// Swipe Right -> Previous
|
|
31
52
|
if (!isFirstSlide) {
|
|
@@ -86,8 +86,12 @@ export function usePrefetchOnMount<TData = unknown>(
|
|
|
86
86
|
options: PrefetchOptions = {},
|
|
87
87
|
) {
|
|
88
88
|
const queryClient = useQueryClient();
|
|
89
|
+
const hasPrefetchedRef = useRef(false);
|
|
89
90
|
|
|
90
91
|
useEffect(() => {
|
|
92
|
+
if (hasPrefetchedRef.current) return;
|
|
93
|
+
|
|
94
|
+
hasPrefetchedRef.current = true;
|
|
91
95
|
queryClient.prefetchQuery({
|
|
92
96
|
queryKey,
|
|
93
97
|
queryFn,
|