@umituz/react-native-design-system 2.6.110 → 2.6.112

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 (81) hide show
  1. package/package.json +7 -3
  2. package/src/atoms/image/AtomicImage.tsx +29 -0
  3. package/src/atoms/index.ts +3 -0
  4. package/src/exports/image.ts +7 -0
  5. package/src/exports/infinite-scroll.ts +7 -0
  6. package/src/exports/uuid.ts +7 -0
  7. package/src/image/domain/entities/EditorTypes.ts +23 -0
  8. package/src/image/domain/entities/ImageConstants.ts +38 -0
  9. package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
  10. package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
  11. package/src/image/domain/entities/ImageTypes.ts +86 -0
  12. package/src/image/domain/entities/ValidationResult.ts +15 -0
  13. package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
  14. package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
  15. package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
  16. package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
  17. package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
  18. package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
  19. package/src/image/domain/utils/ImageUtils.ts +103 -0
  20. package/src/image/index.ts +123 -0
  21. package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
  22. package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
  23. package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
  24. package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
  25. package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
  26. package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
  27. package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
  28. package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
  29. package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
  30. package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
  31. package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
  32. package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
  33. package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
  34. package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
  35. package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
  36. package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
  37. package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
  38. package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
  39. package/src/image/infrastructure/utils/LayerManager.ts +77 -0
  40. package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
  41. package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
  42. package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
  43. package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
  44. package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
  45. package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
  46. package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
  47. package/src/image/presentation/components/GalleryHeader.tsx +126 -0
  48. package/src/image/presentation/components/ImageGallery.tsx +138 -0
  49. package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
  50. package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
  51. package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
  52. package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
  53. package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
  54. package/src/image/presentation/hooks/useImage.ts +39 -0
  55. package/src/image/presentation/hooks/useImageBatch.ts +28 -0
  56. package/src/image/presentation/hooks/useImageConversion.ts +29 -0
  57. package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
  58. package/src/image/presentation/hooks/useImageGallery.ts +90 -0
  59. package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
  60. package/src/image/presentation/hooks/useImageOperation.ts +37 -0
  61. package/src/image/presentation/hooks/useImageTransform.ts +42 -0
  62. package/src/index.ts +15 -0
  63. package/src/infinite-scroll/domain/interfaces/infinite-scroll-list-props.ts +67 -0
  64. package/src/infinite-scroll/domain/types/infinite-scroll-config.ts +108 -0
  65. package/src/infinite-scroll/domain/types/infinite-scroll-return.ts +40 -0
  66. package/src/infinite-scroll/domain/types/infinite-scroll-state.ts +58 -0
  67. package/src/infinite-scroll/domain/utils/pagination-utils.ts +63 -0
  68. package/src/infinite-scroll/domain/utils/type-guards.ts +53 -0
  69. package/src/infinite-scroll/index.ts +62 -0
  70. package/src/infinite-scroll/presentation/components/empty.tsx +44 -0
  71. package/src/infinite-scroll/presentation/components/error.tsx +66 -0
  72. package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +120 -0
  73. package/src/infinite-scroll/presentation/components/loading-more.tsx +38 -0
  74. package/src/infinite-scroll/presentation/components/loading.tsx +40 -0
  75. package/src/infinite-scroll/presentation/components/types.ts +124 -0
  76. package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +83 -0
  77. package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +327 -0
  78. package/src/uuid/index.ts +15 -0
  79. package/src/uuid/infrastructure/utils/UUIDUtils.ts +75 -0
  80. package/src/uuid/package-lock.json +14255 -0
  81. package/src/uuid/types/UUID.ts +36 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Presentation - Text Editor Tabs
3
+ */
4
+
5
+ import React from 'react';
6
+ import { View, TextInput, ScrollView, TouchableOpacity } from 'react-native';
7
+ import Slider from '@react-native-community/slider';
8
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from '@umituz/react-native-design-system';
9
+
10
+ interface TabProps {
11
+ t: (key: string) => string;
12
+ }
13
+
14
+ export const TextContentTab: React.FC<TabProps & { text: string; onTextChange: (t: string) => void }> = ({ text, onTextChange, t }) => {
15
+ const tokens = useAppDesignTokens();
16
+ return (
17
+ <View style={{ gap: tokens.spacing.lg }}>
18
+ <TextInput
19
+ value={text}
20
+ onChangeText={onTextChange}
21
+ placeholder={t('editor.text_placeholder')}
22
+ style={{
23
+ ...tokens.typography.bodyLarge,
24
+ borderWidth: 1,
25
+ borderColor: tokens.colors.border,
26
+ borderRadius: tokens.radius.md,
27
+ padding: tokens.spacing.md,
28
+ minHeight: 120,
29
+ textAlignVertical: 'top',
30
+ }}
31
+ multiline
32
+ />
33
+ </View>
34
+ );
35
+ };
36
+
37
+ export const TextStyleTab: React.FC<TabProps & {
38
+ fontSize: number; setFontSize: (s: number) => void;
39
+ color: string; setColor: (c: string) => void;
40
+ fontFamily: string; setFontFamily: (f: string) => void;
41
+ }> = ({ fontSize, setFontSize, color, setColor, fontFamily, setFontFamily, t }) => {
42
+ const tokens = useAppDesignTokens();
43
+ const colors = ['#FFFFFF', '#000000', '#FF0000', '#FFFF00', '#0000FF', '#00FF00', '#FF00FF', '#FFA500'];
44
+ const fonts = ['System', 'serif', 'sans-serif', 'monospace'];
45
+
46
+ return (
47
+ <View style={{ gap: tokens.spacing.xl }}>
48
+ <View>
49
+ <AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.sm }}>Font</AtomicText>
50
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: tokens.spacing.sm }}>
51
+ {fonts.map(f => (
52
+ <TouchableOpacity key={f} onPress={() => setFontFamily(f)} style={{
53
+ paddingHorizontal: tokens.spacing.md, paddingVertical: tokens.spacing.xs, borderRadius: tokens.radius.full,
54
+ borderWidth: 1, borderColor: fontFamily === f ? tokens.colors.primary : tokens.colors.border,
55
+ backgroundColor: fontFamily === f ? tokens.colors.primaryContainer : tokens.colors.surface
56
+ }}>
57
+ <AtomicText style={{ fontFamily: f }}>{f}</AtomicText>
58
+ </TouchableOpacity>
59
+ ))}
60
+ </ScrollView>
61
+ </View>
62
+
63
+ <View>
64
+ <AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.sm }}>Color</AtomicText>
65
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: tokens.spacing.sm }}>
66
+ {colors.map(c => (
67
+ <TouchableOpacity key={c} onPress={() => setColor(c)} style={{
68
+ width: 40, height: 40, borderRadius: 20, backgroundColor: c,
69
+ borderWidth: color === c ? 3 : 1, borderColor: tokens.colors.primary
70
+ }} />
71
+ ))}
72
+ </ScrollView>
73
+ </View>
74
+
75
+ <View>
76
+ <AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Size: {fontSize}px</AtomicText>
77
+ <Slider value={fontSize} onValueChange={setFontSize} minimumValue={12} maximumValue={100} step={1} minimumTrackTintColor={tokens.colors.primary} />
78
+ </View>
79
+ </View>
80
+ );
81
+ };
82
+
83
+ export const TextTransformTab: React.FC<TabProps & {
84
+ scale: number; setScale: (s: number) => void;
85
+ rotation: number; setRotation: (r: number) => void;
86
+ opacity: number; setOpacity: (o: number) => void;
87
+ onDelete?: () => void;
88
+ }> = ({ scale, setScale, rotation, setRotation, opacity, setOpacity, onDelete, t }) => {
89
+ const tokens = useAppDesignTokens();
90
+ return (
91
+ <View style={{ gap: tokens.spacing.xl }}>
92
+ <View>
93
+ <AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Scale: {scale.toFixed(2)}x</AtomicText>
94
+ <Slider value={scale} onValueChange={setScale} minimumValue={0.2} maximumValue={3} step={0.1} minimumTrackTintColor={tokens.colors.primary} />
95
+ </View>
96
+ <View>
97
+ <AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Rotation: {Math.round(rotation)}°</AtomicText>
98
+ <Slider value={rotation} onValueChange={setRotation} minimumValue={0} maximumValue={360} step={1} minimumTrackTintColor={tokens.colors.primary} />
99
+ </View>
100
+ {onDelete && (
101
+ <TouchableOpacity onPress={onDelete} style={{
102
+ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: tokens.spacing.sm,
103
+ padding: tokens.spacing.md, borderRadius: tokens.radius.md, borderWidth: 1, borderColor: tokens.colors.error
104
+ }}>
105
+ <AtomicIcon name="trash" size={20} color="error" />
106
+ <AtomicText style={{ ...tokens.typography.labelMedium, color: tokens.colors.error }}>Delete Layer</AtomicText>
107
+ </TouchableOpacity>
108
+ )}
109
+ </View>
110
+ );
111
+ };
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { Image as ExpoImage, ImageProps as ExpoImageProps } from 'expo-image';
3
+ import { StyleSheet, ViewStyle } from 'react-native';
4
+
5
+ export type AtomicImageProps = ExpoImageProps & {
6
+ rounded?: boolean;
7
+ };
8
+
9
+ export const AtomicImage: React.FC<AtomicImageProps> = ({
10
+ style,
11
+ rounded,
12
+ contentFit = 'cover',
13
+ transition = 300,
14
+ ...props
15
+ }) => {
16
+
17
+
18
+ return (
19
+ <ExpoImage
20
+ style={[
21
+ style,
22
+ rounded && { borderRadius: 9999 }
23
+ ]}
24
+ contentFit={contentFit}
25
+ transition={transition}
26
+ {...props}
27
+ />
28
+ );
29
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Presentation - Image Hook
3
+ *
4
+ * Aggregator hook combining transformation and conversion capabilities
5
+ */
6
+
7
+ import { useImageTransform } from './useImageTransform';
8
+ import { useImageConversion } from './useImageConversion';
9
+ import { useImageBatch } from './useImageBatch';
10
+ import { useImageEnhance } from './useImageEnhance';
11
+ import { useImageMetadata } from './useImageMetadata';
12
+
13
+ export const useImage = () => {
14
+ const transform = useImageTransform();
15
+ const conversion = useImageConversion();
16
+ const batch = useImageBatch();
17
+ const enhance = useImageEnhance();
18
+ const metadata = useImageMetadata();
19
+
20
+ return {
21
+ ...transform,
22
+ ...conversion,
23
+ ...batch,
24
+ ...enhance,
25
+ ...metadata,
26
+ isProcessing:
27
+ transform.isTransforming ||
28
+ conversion.isConverting ||
29
+ batch.isBatchProcessing ||
30
+ enhance.isEnhancing ||
31
+ metadata.isExtracting,
32
+ error:
33
+ transform.transformError ||
34
+ conversion.conversionError ||
35
+ batch.batchError ||
36
+ enhance.enhancementError ||
37
+ metadata.metadataError,
38
+ };
39
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Presentation - Image Batch Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageBatchService, type BatchOperation, type BatchProcessingOptions } from '../../infrastructure/services/ImageBatchService';
8
+
9
+ export const useImageBatch = () => {
10
+ const { isProcessing, error, execute } = useImageOperation();
11
+
12
+ const processBatch = useCallback((operations: BatchOperation[], options?: BatchProcessingOptions) =>
13
+ execute(() => ImageBatchService.processBatch(operations, options), 'Failed to process batch'), [execute]);
14
+
15
+ const resizeBatch = useCallback((uris: string[], width?: number, height?: number, options?: BatchProcessingOptions & { saveOptions?: any }) =>
16
+ execute(() => ImageBatchService.resizeBatch(uris, width, height, options), 'Failed to resize batch'), [execute]);
17
+
18
+ const compressBatch = useCallback((uris: string[], quality?: number, options?: BatchProcessingOptions) =>
19
+ execute(() => ImageBatchService.compressBatch(uris, quality, options), 'Failed to compress batch'), [execute]);
20
+
21
+ return {
22
+ processBatch,
23
+ resizeBatch,
24
+ compressBatch,
25
+ isBatchProcessing: isProcessing,
26
+ batchError: error,
27
+ };
28
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Presentation - Image Conversion Hook
3
+ */
4
+ import { useCallback } from 'react';
5
+ import { useImageOperation } from './useImageOperation';
6
+ import { ImageConversionService } from '../../infrastructure/services/ImageConversionService';
7
+ import { ImageStorageService } from '../../infrastructure/services/ImageStorageService';
8
+ import type { ImageSaveOptions, SaveFormat } from '../../domain/entities/ImageTypes';
9
+
10
+ export const useImageConversion = () => {
11
+ const { isProcessing, error, execute } = useImageOperation();
12
+
13
+ const compress = useCallback((uri: string, quality?: number) =>
14
+ execute(() => ImageConversionService.compress(uri, quality), 'Failed to compress'), [execute]);
15
+
16
+ const convertFormat = useCallback((uri: string, format: SaveFormat, quality?: number) =>
17
+ execute(() => ImageConversionService.convertFormat(uri, format, quality), 'Failed to convert format'), [execute]);
18
+
19
+ const createThumbnail = useCallback((uri: string, size?: number, options?: ImageSaveOptions) =>
20
+ execute(() => ImageConversionService.createThumbnail(uri, size, options), 'Failed to create thumbnail'), [execute]);
21
+
22
+ const saveImage = useCallback((uri: string, filename?: string) =>
23
+ execute(() => ImageStorageService.saveImage(uri, filename), 'Failed to save image'), [execute]);
24
+
25
+ return {
26
+ compress, convertFormat, createThumbnail, saveImage,
27
+ isConverting: isProcessing, conversionError: error,
28
+ };
29
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Presentation - Image Enhance Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageEnhanceService, type AutoEnhancementOptions } from '../../infrastructure/services/ImageEnhanceService';
8
+
9
+ export const useImageEnhance = () => {
10
+ const { isProcessing, error, execute } = useImageOperation();
11
+
12
+ const autoEnhance = useCallback((uri: string, options?: AutoEnhancementOptions) =>
13
+ execute(() => ImageEnhanceService.autoEnhance(uri, options), 'Failed to auto enhance'), [execute]);
14
+
15
+ const enhancePortrait = useCallback((uri: string) =>
16
+ execute(() => ImageEnhanceService.enhancePortrait(uri), 'Failed to enhance portrait'), [execute]);
17
+
18
+ const enhanceLandscape = useCallback((uri: string) =>
19
+ execute(() => ImageEnhanceService.enhanceLandscape(uri), 'Failed to enhance landscape'), [execute]);
20
+
21
+ const analyzeImage = useCallback((uri: string) =>
22
+ execute(() => ImageEnhanceService.analyzeImage(uri), 'Failed to analyze image'), [execute]);
23
+
24
+ return {
25
+ autoEnhance,
26
+ enhancePortrait,
27
+ enhanceLandscape,
28
+ analyzeImage,
29
+ isEnhancing: isProcessing,
30
+ enhancementError: error,
31
+ };
32
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Presentation - Image Gallery Hook
3
+ */
4
+
5
+ import { useState, useCallback, useMemo } from 'react';
6
+ import { ImageViewerService } from '../../infrastructure/services/ImageViewerService';
7
+ import type {
8
+ ImageViewerItem,
9
+ ImageGalleryOptions,
10
+ } from '../../domain/entities/ImageTypes';
11
+
12
+ export interface UseImageGalleryReturn {
13
+ visible: boolean;
14
+ currentIndex: number;
15
+ images: ImageViewerItem[];
16
+ open: (images: ImageViewerItem[] | string[], startIndex?: number, options?: ImageGalleryOptions) => void;
17
+ close: () => void;
18
+ setIndex: (index: number) => void;
19
+ options: ImageGalleryOptions;
20
+ }
21
+
22
+ export const useImageGallery = (
23
+ defaultOptions?: ImageGalleryOptions
24
+ ): UseImageGalleryReturn => {
25
+ const [visible, setVisible] = useState(false);
26
+ const [currentIndex, setCurrentIndex] = useState(0);
27
+ const [images, setImages] = useState<ImageViewerItem[]>([]);
28
+ const [galleryOptions, setGalleryOptions] = useState<ImageGalleryOptions>(
29
+ defaultOptions || ImageViewerService.getDefaultOptions()
30
+ );
31
+
32
+ const open = useCallback(
33
+ (
34
+ imageData: ImageViewerItem[] | string[],
35
+ startIndex: number = 0,
36
+ options?: ImageGalleryOptions
37
+ ) => {
38
+ const preparedImages =
39
+ typeof imageData[0] === 'string'
40
+ ? ImageViewerService.prepareImages(imageData as string[])
41
+ : ImageViewerService.prepareImagesWithMetadata(imageData as ImageViewerItem[]);
42
+
43
+ setImages(preparedImages);
44
+ setCurrentIndex(options?.index ?? startIndex);
45
+
46
+ if (options) {
47
+ setGalleryOptions({
48
+ ...galleryOptions,
49
+ ...options,
50
+ });
51
+ }
52
+
53
+ setVisible(true);
54
+ },
55
+ [galleryOptions]
56
+ );
57
+
58
+ const close = useCallback(() => {
59
+ setVisible(false);
60
+
61
+ if (galleryOptions.onDismiss) {
62
+ galleryOptions.onDismiss();
63
+ }
64
+ }, [galleryOptions]);
65
+
66
+ const setIndex = useCallback((index: number) => {
67
+ setCurrentIndex(index);
68
+
69
+ if (galleryOptions.onIndexChange) {
70
+ galleryOptions.onIndexChange(index);
71
+ }
72
+ }, [galleryOptions]);
73
+
74
+ const options = useMemo(() => ({
75
+ backgroundColor: galleryOptions.backgroundColor || '#000000',
76
+ swipeToCloseEnabled: galleryOptions.swipeToCloseEnabled ?? true,
77
+ doubleTapToZoomEnabled: galleryOptions.doubleTapToZoomEnabled ?? true,
78
+ }), [galleryOptions]);
79
+
80
+ return {
81
+ visible,
82
+ currentIndex,
83
+ images,
84
+ open,
85
+ close,
86
+ setIndex,
87
+ options,
88
+ };
89
+ };
90
+
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Presentation - Image Metadata Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageMetadataService, type ImageMetadataExtractionOptions } from '../../infrastructure/services/ImageMetadataService';
8
+
9
+ export const useImageMetadata = () => {
10
+ const { isProcessing, error, execute } = useImageOperation();
11
+
12
+ const extractMetadata = useCallback((uri: string, options?: ImageMetadataExtractionOptions) =>
13
+ execute(() => ImageMetadataService.extractMetadata(uri, options), 'Failed to extract metadata'), [execute]);
14
+
15
+ const getBasicInfo = useCallback((uri: string) =>
16
+ execute(() => ImageMetadataService.getBasicInfo(uri), 'Failed to get basic info'), [execute]);
17
+
18
+ const hasMetadata = useCallback((uri: string) =>
19
+ execute(() => ImageMetadataService.hasMetadata(uri), 'Failed to check metadata'), [execute]);
20
+
21
+ return {
22
+ extractMetadata,
23
+ getBasicInfo,
24
+ hasMetadata,
25
+ isExtracting: isProcessing,
26
+ metadataError: error,
27
+ };
28
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Presentation - Image Operation Hook
3
+ *
4
+ * Generic state management for async image operations
5
+ */
6
+
7
+ import { useState, useCallback } from 'react';
8
+ import { ImageError } from '../../infrastructure/utils/ImageErrorHandler';
9
+
10
+ export const useImageOperation = () => {
11
+ const [isProcessing, setIsProcessing] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+
14
+ const execute = useCallback(async <T>(
15
+ operation: () => Promise<T>,
16
+ errorMessage: string
17
+ ): Promise<T | null> => {
18
+ setIsProcessing(true);
19
+ setError(null);
20
+
21
+ try {
22
+ const result = await operation();
23
+ return result;
24
+ } catch (err) {
25
+ if (err instanceof ImageError) {
26
+ setError(err.message);
27
+ } else {
28
+ setError(err instanceof Error ? err.message : errorMessage);
29
+ }
30
+ return null;
31
+ } finally {
32
+ setIsProcessing(false);
33
+ }
34
+ }, []);
35
+
36
+ return { isProcessing, error, execute };
37
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Presentation - Image Transform Hook
3
+ */
4
+ import { useCallback } from 'react';
5
+ import { useImageOperation } from './useImageOperation';
6
+ import { ImageTransformService } from '../../infrastructure/services/ImageTransformService';
7
+ import type {
8
+ ImageManipulateAction,
9
+ ImageSaveOptions,
10
+ ImageCropArea,
11
+ ImageFlipOptions,
12
+ } from '../../domain/entities/ImageTypes';
13
+
14
+ export const useImageTransform = () => {
15
+ const { isProcessing, error, execute } = useImageOperation();
16
+
17
+ const resize = useCallback((uri: string, width?: number, height?: number, options?: ImageSaveOptions) =>
18
+ execute(() => ImageTransformService.resize(uri, width, height, options), 'Failed to resize'), [execute]);
19
+
20
+ const crop = useCallback((uri: string, cropArea: ImageCropArea, options?: ImageSaveOptions) =>
21
+ execute(() => ImageTransformService.crop(uri, cropArea, options), 'Failed to crop'), [execute]);
22
+
23
+ const rotate = useCallback((uri: string, degrees: number, options?: ImageSaveOptions) =>
24
+ execute(() => ImageTransformService.rotate(uri, degrees, options), 'Failed to rotate'), [execute]);
25
+
26
+ const flip = useCallback((uri: string, flipParams: ImageFlipOptions, options?: ImageSaveOptions) =>
27
+ execute(() => ImageTransformService.flip(uri, flipParams, options), 'Failed to flip'), [execute]);
28
+
29
+ const manipulate = useCallback((uri: string, action: ImageManipulateAction, options?: ImageSaveOptions) =>
30
+ execute(() => ImageTransformService.manipulate(uri, action, options), 'Failed to manipulate'), [execute]);
31
+
32
+ const resizeToFit = useCallback((uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions) =>
33
+ execute(() => ImageTransformService.resizeToFit(uri, maxWidth, maxHeight, options), 'Failed to resize to fit'), [execute]);
34
+
35
+ const cropToSquare = useCallback((uri: string, width: number, height: number, options?: ImageSaveOptions) =>
36
+ execute(() => ImageTransformService.cropToSquare(uri, width, height, options), 'Failed to crop square'), [execute]);
37
+
38
+ return {
39
+ resize, crop, rotate, flip, manipulate, resizeToFit, cropToSquare,
40
+ isTransforming: isProcessing, transformError: error,
41
+ };
42
+ };
package/src/index.ts CHANGED
@@ -62,6 +62,21 @@ export * from './exports/safe-area';
62
62
  // =============================================================================
63
63
  export * from './exports/exception';
64
64
 
65
+ // =============================================================================
66
+ // INFINITE SCROLL EXPORTS
67
+ // =============================================================================
68
+ export * from './exports/infinite-scroll';
69
+
70
+ // =============================================================================
71
+ // UUID EXPORTS
72
+ // =============================================================================
73
+ export * from './exports/uuid';
74
+
75
+ // =============================================================================
76
+ // IMAGE EXPORTS
77
+ // =============================================================================
78
+ export * from './exports/image';
79
+
65
80
  // =============================================================================
66
81
  // VARIANT UTILITIES
67
82
  // =============================================================================
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Infinite Scroll List Props Interface
3
+ *
4
+ * Domain interface for component props
5
+ * Follows SOLID, DRY, KISS principles
6
+ */
7
+
8
+ import type React from "react";
9
+ import type { InfiniteScrollConfig } from "../types/infinite-scroll-config";
10
+
11
+ export interface InfiniteScrollListProps<T> {
12
+ /**
13
+ * Configuration for infinite scroll
14
+ */
15
+ config: InfiniteScrollConfig<T>;
16
+
17
+ /**
18
+ * Render function for each item
19
+ */
20
+ renderItem: (item: T, index: number) => React.ReactElement;
21
+
22
+ /**
23
+ * Optional: Custom loading component
24
+ */
25
+ loadingComponent?: React.ReactElement;
26
+
27
+ /**
28
+ * Optional: Custom loading more component
29
+ */
30
+ loadingMoreComponent?: React.ReactElement;
31
+
32
+ /**
33
+ * Optional: Custom empty component
34
+ */
35
+ emptyComponent?: React.ReactElement;
36
+
37
+ /**
38
+ * Optional: Custom error component
39
+ */
40
+ errorComponent?: (error: string, retry: () => void) => React.ReactElement;
41
+
42
+ /**
43
+ * Optional: List header component
44
+ */
45
+ ListHeaderComponent?: React.ReactElement;
46
+
47
+ /**
48
+ * Optional: List footer component
49
+ */
50
+ ListFooterComponent?: React.ReactElement;
51
+
52
+ /**
53
+ * Optional: Additional FlatList props
54
+ */
55
+ flatListProps?: Omit<
56
+ React.ComponentProps<typeof import("react-native").FlatList<T>>,
57
+ | "data"
58
+ | "renderItem"
59
+ | "keyExtractor"
60
+ | "onEndReached"
61
+ | "onEndReachedThreshold"
62
+ | "onRefresh"
63
+ | "refreshing"
64
+ | "ListHeaderComponent"
65
+ | "ListFooterComponent"
66
+ >;
67
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Infinite Scroll Configuration Types
3
+ *
4
+ * Domain types for infinite scroll configuration
5
+ * Follows SOLID, DRY, KISS principles
6
+ */
7
+
8
+ /**
9
+ * Paginated result for cursor-based pagination
10
+ */
11
+ export interface PaginatedResult<T> {
12
+ items: T[];
13
+ nextCursor: string | null;
14
+ hasMore: boolean;
15
+ }
16
+
17
+ /**
18
+ * Base configuration shared by all pagination modes
19
+ */
20
+ interface BaseConfig<T> {
21
+ /**
22
+ * Total number of items available (optional, for progress tracking)
23
+ */
24
+ totalItems?: number;
25
+
26
+ /**
27
+ * Number of items to load per page
28
+ * Default: 20
29
+ */
30
+ pageSize?: number;
31
+
32
+ /**
33
+ * Number of items from the end to trigger loading more
34
+ * Default: 5 (loads more when 5 items from bottom)
35
+ */
36
+ threshold?: number;
37
+
38
+ /**
39
+ * Enable automatic loading when threshold is reached
40
+ * Default: true
41
+ */
42
+ autoLoad?: boolean;
43
+
44
+ /**
45
+ * Optional: Function to get unique key for each item
46
+ * If not provided, uses array index
47
+ * @param item - Item to get key for
48
+ * @param index - Item index
49
+ * @returns Unique key string
50
+ */
51
+ getItemKey?: (item: T, index: number) => string;
52
+ }
53
+
54
+ /**
55
+ * Page-based pagination configuration (default, backward compatible)
56
+ */
57
+ export interface PageBasedConfig<T> extends BaseConfig<T> {
58
+ /**
59
+ * Initial page number (0-indexed)
60
+ * Default: 0
61
+ */
62
+ initialPage?: number;
63
+
64
+ /**
65
+ * Function to fetch data for a specific page
66
+ * @param page - Page number (0-indexed)
67
+ * @param pageSize - Number of items per page
68
+ * @returns Promise resolving to array of items
69
+ */
70
+ fetchData: (page: number, pageSize: number) => Promise<T[]>;
71
+
72
+ /**
73
+ * Optional: Function to check if there are more items
74
+ * If not provided, checks if last page has fewer items than pageSize
75
+ * @param lastPage - Last fetched page of items
76
+ * @param allPages - All fetched pages
77
+ * @returns true if there are more items to load
78
+ */
79
+ hasMore?: (lastPage: T[], allPages: T[][]) => boolean;
80
+ }
81
+
82
+ /**
83
+ * Cursor-based pagination configuration (new, for Firestore)
84
+ */
85
+ export interface CursorBasedConfig<T> extends BaseConfig<T> {
86
+ /**
87
+ * Discriminator for cursor-based mode
88
+ */
89
+ paginationMode: "cursor";
90
+
91
+ /**
92
+ * Function to fetch data using cursor
93
+ * @param cursor - Cursor for next page (undefined for first page)
94
+ * @param pageSize - Number of items per page
95
+ * @returns Promise resolving to paginated result with cursor
96
+ */
97
+ fetchCursor: (
98
+ cursor: string | undefined,
99
+ pageSize: number,
100
+ ) => Promise<PaginatedResult<T>>;
101
+ }
102
+
103
+ /**
104
+ * Infinite scroll configuration (discriminated union)
105
+ */
106
+ export type InfiniteScrollConfig<T> =
107
+ | PageBasedConfig<T>
108
+ | CursorBasedConfig<T>;