@umituz/react-native-image 1.1.0

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.
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Image Domain - useImage Hook
3
+ *
4
+ * React hook for image manipulation operations.
5
+ * Provides image resizing, cropping, rotating, and format conversion.
6
+ */
7
+
8
+ import { useState, useCallback } from 'react';
9
+ import { ImageService } from '../../infrastructure/services/ImageService';
10
+ import type {
11
+ ImageManipulateAction,
12
+ ImageSaveOptions,
13
+ ImageManipulationResult,
14
+ SaveFormat,
15
+ } from '../../domain/entities/Image';
16
+
17
+ /**
18
+ * useImage hook for image manipulation
19
+ *
20
+ * USAGE:
21
+ * ```typescript
22
+ * const { resize, crop, rotate, compress, isProcessing } = useImage();
23
+ *
24
+ * // Resize image
25
+ * const resized = await resize(imageUri, 800, 600);
26
+ *
27
+ * // Crop image
28
+ * const cropped = await crop(imageUri, { originX: 0, originY: 0, width: 500, height: 500 });
29
+ *
30
+ * // Compress image
31
+ * const compressed = await compress(imageUri, 0.7);
32
+ * ```
33
+ */
34
+ export const useImage = () => {
35
+ const [isProcessing, setIsProcessing] = useState(false);
36
+ const [error, setError] = useState<string | null>(null);
37
+
38
+ /**
39
+ * Resize image
40
+ */
41
+ const resize = useCallback(
42
+ async (
43
+ uri: string,
44
+ width?: number,
45
+ height?: number,
46
+ options?: ImageSaveOptions
47
+ ): Promise<ImageManipulationResult | null> => {
48
+ setIsProcessing(true);
49
+ setError(null);
50
+
51
+ try {
52
+ const result = await ImageService.resize(uri, width, height, options);
53
+
54
+ if (!result) {
55
+ setError('Failed to resize image');
56
+ }
57
+
58
+ return result;
59
+ } catch (err) {
60
+ const errorMessage = err instanceof Error ? err.message : 'Failed to resize image';
61
+ setError(errorMessage);
62
+ return null;
63
+ } finally {
64
+ setIsProcessing(false);
65
+ }
66
+ },
67
+ []
68
+ );
69
+
70
+ /**
71
+ * Crop image
72
+ */
73
+ const crop = useCallback(
74
+ async (
75
+ uri: string,
76
+ cropArea: { originX: number; originY: number; width: number; height: number },
77
+ options?: ImageSaveOptions
78
+ ): Promise<ImageManipulationResult | null> => {
79
+ setIsProcessing(true);
80
+ setError(null);
81
+
82
+ try {
83
+ const result = await ImageService.crop(uri, cropArea, options);
84
+
85
+ if (!result) {
86
+ setError('Failed to crop image');
87
+ }
88
+
89
+ return result;
90
+ } catch (err) {
91
+ const errorMessage = err instanceof Error ? err.message : 'Failed to crop image';
92
+ setError(errorMessage);
93
+ return null;
94
+ } finally {
95
+ setIsProcessing(false);
96
+ }
97
+ },
98
+ []
99
+ );
100
+
101
+ /**
102
+ * Rotate image
103
+ */
104
+ const rotate = useCallback(
105
+ async (
106
+ uri: string,
107
+ degrees: number,
108
+ options?: ImageSaveOptions
109
+ ): Promise<ImageManipulationResult | null> => {
110
+ setIsProcessing(true);
111
+ setError(null);
112
+
113
+ try {
114
+ const result = await ImageService.rotate(uri, degrees, options);
115
+
116
+ if (!result) {
117
+ setError('Failed to rotate image');
118
+ }
119
+
120
+ return result;
121
+ } catch (err) {
122
+ const errorMessage = err instanceof Error ? err.message : 'Failed to rotate image';
123
+ setError(errorMessage);
124
+ return null;
125
+ } finally {
126
+ setIsProcessing(false);
127
+ }
128
+ },
129
+ []
130
+ );
131
+
132
+ /**
133
+ * Flip image
134
+ */
135
+ const flip = useCallback(
136
+ async (
137
+ uri: string,
138
+ flipOptions: { horizontal?: boolean; vertical?: boolean },
139
+ saveOptions?: ImageSaveOptions
140
+ ): Promise<ImageManipulationResult | null> => {
141
+ setIsProcessing(true);
142
+ setError(null);
143
+
144
+ try {
145
+ const result = await ImageService.flip(uri, flipOptions, saveOptions);
146
+
147
+ if (!result) {
148
+ setError('Failed to flip image');
149
+ }
150
+
151
+ return result;
152
+ } catch (err) {
153
+ const errorMessage = err instanceof Error ? err.message : 'Failed to flip image';
154
+ setError(errorMessage);
155
+ return null;
156
+ } finally {
157
+ setIsProcessing(false);
158
+ }
159
+ },
160
+ []
161
+ );
162
+
163
+ /**
164
+ * Perform multiple manipulations
165
+ */
166
+ const manipulate = useCallback(
167
+ async (
168
+ uri: string,
169
+ action: ImageManipulateAction,
170
+ options?: ImageSaveOptions
171
+ ): Promise<ImageManipulationResult | null> => {
172
+ setIsProcessing(true);
173
+ setError(null);
174
+
175
+ try {
176
+ const result = await ImageService.manipulate(uri, action, options);
177
+
178
+ if (!result) {
179
+ setError('Failed to manipulate image');
180
+ }
181
+
182
+ return result;
183
+ } catch (err) {
184
+ const errorMessage = err instanceof Error ? err.message : 'Failed to manipulate image';
185
+ setError(errorMessage);
186
+ return null;
187
+ } finally {
188
+ setIsProcessing(false);
189
+ }
190
+ },
191
+ []
192
+ );
193
+
194
+ /**
195
+ * Compress image
196
+ */
197
+ const compress = useCallback(
198
+ async (uri: string, quality: number = 0.8): Promise<ImageManipulationResult | null> => {
199
+ setIsProcessing(true);
200
+ setError(null);
201
+
202
+ try {
203
+ const result = await ImageService.compress(uri, quality);
204
+
205
+ if (!result) {
206
+ setError('Failed to compress image');
207
+ }
208
+
209
+ return result;
210
+ } catch (err) {
211
+ const errorMessage = err instanceof Error ? err.message : 'Failed to compress image';
212
+ setError(errorMessage);
213
+ return null;
214
+ } finally {
215
+ setIsProcessing(false);
216
+ }
217
+ },
218
+ []
219
+ );
220
+
221
+ /**
222
+ * Resize to fit within max dimensions
223
+ */
224
+ const resizeToFit = useCallback(
225
+ async (
226
+ uri: string,
227
+ maxWidth: number,
228
+ maxHeight: number,
229
+ options?: ImageSaveOptions
230
+ ): Promise<ImageManipulationResult | null> => {
231
+ setIsProcessing(true);
232
+ setError(null);
233
+
234
+ try {
235
+ const result = await ImageService.resizeToFit(uri, maxWidth, maxHeight, options);
236
+
237
+ if (!result) {
238
+ setError('Failed to resize image to fit');
239
+ }
240
+
241
+ return result;
242
+ } catch (err) {
243
+ const errorMessage = err instanceof Error ? err.message : 'Failed to resize image to fit';
244
+ setError(errorMessage);
245
+ return null;
246
+ } finally {
247
+ setIsProcessing(false);
248
+ }
249
+ },
250
+ []
251
+ );
252
+
253
+ /**
254
+ * Create thumbnail
255
+ */
256
+ const createThumbnail = useCallback(
257
+ async (
258
+ uri: string,
259
+ size: number = 200,
260
+ options?: ImageSaveOptions
261
+ ): Promise<ImageManipulationResult | null> => {
262
+ setIsProcessing(true);
263
+ setError(null);
264
+
265
+ try {
266
+ const result = await ImageService.createThumbnail(uri, size, options);
267
+
268
+ if (!result) {
269
+ setError('Failed to create thumbnail');
270
+ }
271
+
272
+ return result;
273
+ } catch (err) {
274
+ const errorMessage = err instanceof Error ? err.message : 'Failed to create thumbnail';
275
+ setError(errorMessage);
276
+ return null;
277
+ } finally {
278
+ setIsProcessing(false);
279
+ }
280
+ },
281
+ []
282
+ );
283
+
284
+ /**
285
+ * Crop to square
286
+ */
287
+ const cropToSquare = useCallback(
288
+ async (
289
+ uri: string,
290
+ width: number,
291
+ height: number,
292
+ options?: ImageSaveOptions
293
+ ): Promise<ImageManipulationResult | null> => {
294
+ setIsProcessing(true);
295
+ setError(null);
296
+
297
+ try {
298
+ const result = await ImageService.cropToSquare(uri, width, height, options);
299
+
300
+ if (!result) {
301
+ setError('Failed to crop to square');
302
+ }
303
+
304
+ return result;
305
+ } catch (err) {
306
+ const errorMessage = err instanceof Error ? err.message : 'Failed to crop to square';
307
+ setError(errorMessage);
308
+ return null;
309
+ } finally {
310
+ setIsProcessing(false);
311
+ }
312
+ },
313
+ []
314
+ );
315
+
316
+ /**
317
+ * Convert image format
318
+ */
319
+ const convertFormat = useCallback(
320
+ async (
321
+ uri: string,
322
+ format: SaveFormat,
323
+ quality?: number
324
+ ): Promise<ImageManipulationResult | null> => {
325
+ setIsProcessing(true);
326
+ setError(null);
327
+
328
+ try {
329
+ const result = await ImageService.convertFormat(uri, format, quality);
330
+
331
+ if (!result) {
332
+ setError('Failed to convert image format');
333
+ }
334
+
335
+ return result;
336
+ } catch (err) {
337
+ const errorMessage = err instanceof Error ? err.message : 'Failed to convert image format';
338
+ setError(errorMessage);
339
+ return null;
340
+ } finally {
341
+ setIsProcessing(false);
342
+ }
343
+ },
344
+ []
345
+ );
346
+
347
+ /**
348
+ * Save image to device
349
+ */
350
+ const saveImage = useCallback(
351
+ async (uri: string, filename?: string): Promise<string | null> => {
352
+ setIsProcessing(true);
353
+ setError(null);
354
+
355
+ try {
356
+ const savedUri = await ImageService.saveImage(uri, filename);
357
+
358
+ if (!savedUri) {
359
+ setError('Failed to save image');
360
+ }
361
+
362
+ return savedUri;
363
+ } catch (err) {
364
+ const errorMessage = err instanceof Error ? err.message : 'Failed to save image';
365
+ setError(errorMessage);
366
+ return null;
367
+ } finally {
368
+ setIsProcessing(false);
369
+ }
370
+ },
371
+ []
372
+ );
373
+
374
+ return {
375
+ // Functions
376
+ resize,
377
+ crop,
378
+ rotate,
379
+ flip,
380
+ manipulate,
381
+ compress,
382
+ resizeToFit,
383
+ createThumbnail,
384
+ cropToSquare,
385
+ convertFormat,
386
+ saveImage,
387
+
388
+ // State
389
+ isProcessing,
390
+ error,
391
+ };
392
+ };
393
+
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Image Domain - useImageGallery Hook
3
+ *
4
+ * React hook for image gallery and viewer using react-native-image-viewing.
5
+ * Provides full-screen image viewer with zoom, swipe, and gallery features.
6
+ */
7
+
8
+ import { useState, useCallback, useMemo } from 'react';
9
+ import { ImageViewerService } from '../../infrastructure/services/ImageViewerService';
10
+ import type {
11
+ ImageViewerItem,
12
+ ImageGalleryOptions,
13
+ } from '../../domain/entities/Image';
14
+
15
+ /**
16
+ * useImageGallery hook return type
17
+ */
18
+ export interface UseImageGalleryReturn {
19
+ // State
20
+ visible: boolean;
21
+ currentIndex: number;
22
+ images: ImageViewerItem[];
23
+
24
+ // Actions
25
+ open: (images: ImageViewerItem[] | string[], startIndex?: number, options?: ImageGalleryOptions) => void;
26
+ close: () => void;
27
+ setIndex: (index: number) => void;
28
+
29
+ // Gallery options
30
+ options: ImageGalleryOptions;
31
+ }
32
+
33
+ /**
34
+ * useImageGallery hook for full-screen image viewer
35
+ *
36
+ * USAGE:
37
+ * ```typescript
38
+ * const { visible, currentIndex, images, open, close, options } = useImageGallery();
39
+ *
40
+ * // Open gallery with image URIs
41
+ * open(['uri1', 'uri2', 'uri3']);
42
+ *
43
+ * // Open gallery with metadata
44
+ * open([
45
+ * { uri: 'uri1', title: 'Photo 1' },
46
+ * { uri: 'uri2', title: 'Photo 2' },
47
+ * ], 0, { backgroundColor: '#000000' });
48
+ *
49
+ * // Render ImageViewing component
50
+ * <ImageViewing
51
+ * images={images}
52
+ * imageIndex={currentIndex}
53
+ * visible={visible}
54
+ * onRequestClose={close}
55
+ * onIndexChange={setIndex}
56
+ * {...options}
57
+ * />
58
+ * ```
59
+ */
60
+ export const useImageGallery = (
61
+ defaultOptions?: ImageGalleryOptions
62
+ ): UseImageGalleryReturn => {
63
+ const [visible, setVisible] = useState(false);
64
+ const [currentIndex, setCurrentIndex] = useState(0);
65
+ const [images, setImages] = useState<ImageViewerItem[]>([]);
66
+ const [galleryOptions, setGalleryOptions] = useState<ImageGalleryOptions>(
67
+ defaultOptions || ImageViewerService.getDefaultOptions()
68
+ );
69
+
70
+ /**
71
+ * Open gallery with images
72
+ */
73
+ const open = useCallback(
74
+ (
75
+ imageData: ImageViewerItem[] | string[],
76
+ startIndex: number = 0,
77
+ options?: ImageGalleryOptions
78
+ ) => {
79
+ // Prepare images based on input type
80
+ const preparedImages =
81
+ typeof imageData[0] === 'string'
82
+ ? ImageViewerService.prepareImages(imageData as string[])
83
+ : ImageViewerService.prepareImagesWithMetadata(imageData as ImageViewerItem[]);
84
+
85
+ setImages(preparedImages);
86
+ setCurrentIndex(options?.index ?? startIndex);
87
+
88
+ // Merge options with defaults
89
+ if (options) {
90
+ setGalleryOptions({
91
+ ...galleryOptions,
92
+ ...options,
93
+ });
94
+ }
95
+
96
+ setVisible(true);
97
+ },
98
+ [galleryOptions]
99
+ );
100
+
101
+ /**
102
+ * Close gallery
103
+ */
104
+ const close = useCallback(() => {
105
+ setVisible(false);
106
+
107
+ // Call onDismiss if provided
108
+ if (galleryOptions.onDismiss) {
109
+ galleryOptions.onDismiss();
110
+ }
111
+ }, [galleryOptions]);
112
+
113
+ /**
114
+ * Set current image index
115
+ */
116
+ const setIndex = useCallback((index: number) => {
117
+ setCurrentIndex(index);
118
+
119
+ // Call onIndexChange if provided
120
+ if (galleryOptions.onIndexChange) {
121
+ galleryOptions.onIndexChange(index);
122
+ }
123
+ }, [galleryOptions]);
124
+
125
+ /**
126
+ * Memoized options for ImageViewing component
127
+ */
128
+ const options = useMemo(() => ({
129
+ backgroundColor: galleryOptions.backgroundColor || '#000000',
130
+ swipeToCloseEnabled: galleryOptions.swipeToCloseEnabled ?? true,
131
+ doubleTapToZoomEnabled: galleryOptions.doubleTapToZoomEnabled ?? true,
132
+ }), [galleryOptions]);
133
+
134
+ return {
135
+ // State
136
+ visible,
137
+ currentIndex,
138
+ images,
139
+
140
+ // Actions
141
+ open,
142
+ close,
143
+ setIndex,
144
+
145
+ // Gallery options
146
+ options,
147
+ };
148
+ };
149
+