@umituz/react-native-image 1.1.1 → 1.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-image",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Image manipulation and viewing for React Native apps - resize, crop, rotate, flip, compress, gallery viewer",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -56,4 +56,4 @@
56
56
  "README.md",
57
57
  "LICENSE"
58
58
  ]
59
- }
59
+ }
@@ -77,7 +77,7 @@ export class ImageUtils {
77
77
  }
78
78
  }
79
79
 
80
- static getSquareCrop(width: number, height: number): ImageManipulateAction['crop'] {
80
+ static getSquareCrop(width: number, height: number): { originX: number; originY: number; width: number; height: number } {
81
81
  const size = Math.min(width, height);
82
82
  const originX = (width - size) / 2;
83
83
  const originY = (height - size) / 2;
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export { ImageGallery, type ImageGalleryProps } from './presentation/components/
52
52
  export { useImage } from './presentation/hooks/useImage';
53
53
  export { useImageTransform } from './presentation/hooks/useImageTransform';
54
54
  export { useImageConversion } from './presentation/hooks/useImageConversion';
55
+ export { useImageEditor } from './presentation/hooks/useImageEditor';
55
56
 
56
57
  export {
57
58
  useImageGallery,
@@ -0,0 +1,58 @@
1
+ /**
2
+ * GalleryHeader Component
3
+ * Header for ImageGallery with edit and close buttons
4
+ *
5
+ * This component should be implemented by the consumer app
6
+ * using their own design system and safe area handling.
7
+ */
8
+
9
+ import React from 'react';
10
+ import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';
11
+
12
+ interface GalleryHeaderProps {
13
+ onEdit: () => void;
14
+ onClose: () => void;
15
+ }
16
+
17
+ export function GalleryHeader({ onEdit, onClose }: GalleryHeaderProps) {
18
+ return (
19
+ <View style={styles.container}>
20
+ <TouchableOpacity style={styles.button} onPress={onEdit}>
21
+ <Text style={styles.buttonText}>Edit</Text>
22
+ </TouchableOpacity>
23
+
24
+ <TouchableOpacity style={styles.button} onPress={onClose}>
25
+ <Text style={styles.buttonText}>✕</Text>
26
+ </TouchableOpacity>
27
+ </View>
28
+ );
29
+ }
30
+
31
+ const styles = StyleSheet.create({
32
+ container: {
33
+ position: 'absolute',
34
+ top: 0,
35
+ left: 0,
36
+ right: 0,
37
+ paddingTop: 48,
38
+ paddingHorizontal: 16,
39
+ paddingBottom: 16,
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-between',
42
+ alignItems: 'center',
43
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
44
+ },
45
+ button: {
46
+ flexDirection: 'row',
47
+ alignItems: 'center',
48
+ gap: 8,
49
+ padding: 12,
50
+ borderRadius: 8,
51
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
52
+ },
53
+ buttonText: {
54
+ fontSize: 14,
55
+ color: '#FFFFFF',
56
+ fontWeight: '600',
57
+ },
58
+ });
@@ -2,19 +2,23 @@
2
2
  * Image Gallery Component
3
3
  *
4
4
  * A wrapper around react-native-image-viewing that provides
5
- * theme integration and standard configuration.
5
+ * theme integration, standard configuration, and optional editing.
6
6
  */
7
7
 
8
- import React from 'react';
8
+ import React, { useCallback } from 'react';
9
9
  import ImageViewing from 'react-native-image-viewing';
10
+ import * as ImageManipulator from 'expo-image-manipulator';
10
11
  import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
11
12
  import type { ImageViewerItem, ImageGalleryOptions } from '../../domain/entities/ImageTypes';
13
+ import { GalleryHeader } from './GalleryHeader';
12
14
 
13
15
  export interface ImageGalleryProps extends ImageGalleryOptions {
14
16
  images: ImageViewerItem[];
15
17
  visible: boolean;
16
18
  onDismiss: () => void;
17
19
  index?: number;
20
+ onImageChange?: (uri: string, index: number) => void | Promise<void>;
21
+ enableEditing?: boolean;
18
22
  }
19
23
 
20
24
  export const ImageGallery: React.FC<ImageGalleryProps> = ({
@@ -26,29 +30,74 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
26
30
  swipeToCloseEnabled = true,
27
31
  doubleTapToZoomEnabled = true,
28
32
  onIndexChange,
33
+ onImageChange,
34
+ enableEditing = false,
29
35
  }) => {
30
36
  const tokens = useAppDesignTokens();
37
+ const [currentIndex, setCurrentIndex] = React.useState(index);
38
+
39
+ React.useEffect(() => {
40
+ setCurrentIndex(index);
41
+ }, [index]);
31
42
 
32
- // Use theme background if not provided
33
43
  const bg = backgroundColor || tokens.colors.backgroundPrimary;
34
44
 
35
- // Map images to structure expected by ImageViewing (uri object)
36
- const viewerImages = React.useMemo(() =>
37
- images.map(img => ({ uri: img.uri })),
45
+ const viewerImages = React.useMemo(
46
+ () => images.map((img) => ({ uri: img.uri })),
38
47
  [images]
39
48
  );
40
49
 
50
+ const handleEdit = useCallback(async () => {
51
+ const currentImage = images[currentIndex];
52
+ if (!currentImage || !onImageChange) return;
53
+
54
+ try {
55
+ const result = await ImageManipulator.manipulateAsync(
56
+ currentImage.uri,
57
+ [],
58
+ {
59
+ compress: 1,
60
+ format: ImageManipulator.SaveFormat.JPEG,
61
+ }
62
+ );
63
+
64
+ if (result.uri) {
65
+ await onImageChange(result.uri, currentIndex);
66
+ }
67
+ } catch (error) {
68
+ // Silent fail
69
+ }
70
+ }, [images, currentIndex, onImageChange]);
71
+
72
+ const handleIndexChange = useCallback(
73
+ (newIndex: number) => {
74
+ setCurrentIndex(newIndex);
75
+ onIndexChange?.(newIndex);
76
+ },
77
+ [onIndexChange]
78
+ );
79
+
80
+ const headerComponent = useCallback(() => {
81
+ if (!enableEditing) return null;
82
+ return (
83
+ <GalleryHeader
84
+ onEdit={handleEdit}
85
+ onClose={onDismiss}
86
+ />
87
+ );
88
+ }, [enableEditing, handleEdit, onDismiss]);
89
+
41
90
  return (
42
91
  <ImageViewing
43
92
  images={viewerImages}
44
- imageIndex={index}
93
+ imageIndex={currentIndex}
45
94
  visible={visible}
46
95
  onRequestClose={onDismiss}
47
- onImageIndexChange={onIndexChange}
96
+ onImageIndexChange={handleIndexChange}
48
97
  backgroundColor={bg}
49
98
  swipeToCloseEnabled={swipeToCloseEnabled}
50
99
  doubleTapToZoomEnabled={doubleTapToZoomEnabled}
51
- // Can add custom Header/Footer here using theme tokens if needed
100
+ HeaderComponent={headerComponent}
52
101
  />
53
102
  );
54
103
  };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * useImageEditor Hook
3
+ * Provides image editing functionality with crop, rotate, flip
4
+ */
5
+
6
+ import { useState, useCallback } from 'react';
7
+ import * as ImageManipulator from 'expo-image-manipulator';
8
+ import type { Action } from 'expo-image-manipulator';
9
+
10
+ interface UseImageEditorOptions {
11
+ onSave?: (uri: string) => void | Promise<void>;
12
+ }
13
+
14
+ export function useImageEditor({ onSave }: UseImageEditorOptions = {}) {
15
+ const [isEditing, setIsEditing] = useState(false);
16
+ const [currentUri, setCurrentUri] = useState<string | null>(null);
17
+
18
+ const startEditing = useCallback((uri: string) => {
19
+ setCurrentUri(uri);
20
+ setIsEditing(true);
21
+ }, []);
22
+
23
+ const cancelEditing = useCallback(() => {
24
+ setIsEditing(false);
25
+ setCurrentUri(null);
26
+ }, []);
27
+
28
+ const saveEdit = useCallback(
29
+ async (actions: Action[]) => {
30
+ if (!currentUri) return;
31
+
32
+ try {
33
+ const result = await ImageManipulator.manipulateAsync(
34
+ currentUri,
35
+ actions,
36
+ { compress: 0.9, format: ImageManipulator.SaveFormat.JPEG }
37
+ );
38
+
39
+ if (onSave) {
40
+ await onSave(result.uri);
41
+ }
42
+
43
+ setIsEditing(false);
44
+ setCurrentUri(null);
45
+
46
+ return result.uri;
47
+ } catch (error) {
48
+ throw error;
49
+ }
50
+ },
51
+ [currentUri, onSave]
52
+ );
53
+
54
+ return {
55
+ isEditing,
56
+ currentUri,
57
+ startEditing,
58
+ cancelEditing,
59
+ saveEdit,
60
+ };
61
+ }