@umituz/react-native-image 1.1.3 → 1.1.5

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.3",
3
+ "version": "1.1.5",
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
+ }
@@ -1,21 +1,38 @@
1
1
  /**
2
- * Image Constants
2
+ * Image Domain - Configuration Constants
3
3
  */
4
4
  import { SaveFormat } from "./ImageTypes";
5
5
 
6
- export const IMAGE_CONSTANTS = {
7
- MAX_WIDTH: 2048,
8
- MAX_HEIGHT: 2048,
9
- DEFAULT_QUALITY: 0.8,
10
- THUMBNAIL_SIZE: 200,
11
- COMPRESS_QUALITY: {
12
- LOW: 0.5,
13
- MEDIUM: 0.7,
14
- HIGH: 0.9,
6
+ export interface ImageConfiguration {
7
+ readonly maxWidth: number;
8
+ readonly maxHeight: number;
9
+ readonly defaultQuality: number;
10
+ readonly thumbnailSize: number;
11
+ readonly compressQuality: {
12
+ readonly low: number;
13
+ readonly medium: number;
14
+ readonly high: number;
15
+ };
16
+ readonly format: {
17
+ readonly jpeg: SaveFormat;
18
+ readonly png: SaveFormat;
19
+ readonly webp: SaveFormat;
20
+ };
21
+ }
22
+
23
+ export const IMAGE_CONSTANTS: ImageConfiguration = {
24
+ maxWidth: 2048,
25
+ maxHeight: 2048,
26
+ defaultQuality: 0.8,
27
+ thumbnailSize: 200,
28
+ compressQuality: {
29
+ low: 0.5,
30
+ medium: 0.7,
31
+ high: 0.9,
15
32
  },
16
- FORMAT: {
17
- JPEG: 'jpeg' as SaveFormat,
18
- PNG: 'png' as SaveFormat,
19
- WEBP: 'webp' as SaveFormat,
33
+ format: {
34
+ jpeg: 'jpeg' as SaveFormat,
35
+ png: 'png' as SaveFormat,
36
+ webp: 'webp' as SaveFormat,
20
37
  },
21
- } as const;
38
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Image Type Definitions
2
+ * Image Domain - Core Type Definitions
3
3
  */
4
4
 
5
5
  export enum ImageFormat {
@@ -16,11 +16,28 @@ export enum ImageOrientation {
16
16
  SQUARE = 'square',
17
17
  }
18
18
 
19
+ export interface ImageDimensions {
20
+ width: number;
21
+ height: number;
22
+ }
23
+
24
+ export interface ImageCropArea {
25
+ originX: number;
26
+ originY: number;
27
+ width: number;
28
+ height: number;
29
+ }
30
+
31
+ export interface ImageFlipOptions {
32
+ vertical?: boolean;
33
+ horizontal?: boolean;
34
+ }
35
+
19
36
  export interface ImageManipulateAction {
20
- resize?: { width?: number; height?: number };
21
- crop?: { originX: number; originY: number; width: number; height: number };
37
+ resize?: Partial<ImageDimensions>;
38
+ crop?: ImageCropArea;
22
39
  rotate?: number;
23
- flip?: { vertical?: boolean; horizontal?: boolean };
40
+ flip?: ImageFlipOptions;
24
41
  }
25
42
 
26
43
  export interface ImageSaveOptions {
@@ -36,10 +53,8 @@ export interface ImageManipulationResult {
36
53
  base64?: string;
37
54
  }
38
55
 
39
- export interface ImageMetadata {
56
+ export interface ImageMetadata extends ImageDimensions {
40
57
  uri: string;
41
- width: number;
42
- height: number;
43
58
  format?: ImageFormat;
44
59
  size?: number;
45
60
  orientation?: ImageOrientation;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Image Utilities
2
+ * Image Domain - Utility Functions
3
3
  */
4
- import { ImageFormat, ImageOrientation, ImageManipulateAction } from "../entities/ImageTypes";
4
+ import { ImageFormat, ImageOrientation, ImageDimensions, ImageCropArea } from "../entities/ImageTypes";
5
5
  import { IMAGE_CONSTANTS } from "../entities/ImageConstants";
6
6
 
7
7
  export class ImageUtils {
@@ -20,7 +20,7 @@ export class ImageUtils {
20
20
  height: number,
21
21
  maxWidth: number,
22
22
  maxHeight: number
23
- ): { width: number; height: number } {
23
+ ): ImageDimensions {
24
24
  const aspectRatio = ImageUtils.getAspectRatio(width, height);
25
25
  let newWidth = width;
26
26
  let newHeight = height;
@@ -44,8 +44,8 @@ export class ImageUtils {
44
44
  static getThumbnailSize(
45
45
  width: number,
46
46
  height: number,
47
- thumbnailSize: number = IMAGE_CONSTANTS.THUMBNAIL_SIZE
48
- ): { width: number; height: number } {
47
+ thumbnailSize: number = IMAGE_CONSTANTS.thumbnailSize
48
+ ): ImageDimensions {
49
49
  return ImageUtils.fitToSize(width, height, thumbnailSize, thumbnailSize);
50
50
  }
51
51
 
@@ -77,7 +77,7 @@ export class ImageUtils {
77
77
  }
78
78
  }
79
79
 
80
- static getSquareCrop(width: number, height: number): { originX: number; originY: number; width: number; height: number } {
80
+ static getSquareCrop(width: number, height: number): ImageCropArea {
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
@@ -21,6 +21,9 @@ export type {
21
21
  ImageGalleryOptions,
22
22
  ImageOperationResult,
23
23
  SaveFormat,
24
+ ImageDimensions,
25
+ ImageCropArea,
26
+ ImageFlipOptions,
24
27
  } from './domain/entities/ImageTypes';
25
28
 
26
29
  export {
@@ -36,6 +39,7 @@ export { ImageUtils } from './domain/utils/ImageUtils';
36
39
  // =============================================================================
37
40
 
38
41
  export { ImageTransformService } from './infrastructure/services/ImageTransformService';
42
+ export { ImageAdvancedTransformService } from './infrastructure/services/ImageAdvancedTransformService';
39
43
  export { ImageConversionService } from './infrastructure/services/ImageConversionService';
40
44
  export { ImageStorageService } from './infrastructure/services/ImageStorageService';
41
45
  export {
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Image Infrastructure - Advanced Transform Service
3
+ */
4
+
5
+ import * as ImageManipulator from 'expo-image-manipulator';
6
+ import type {
7
+ ImageManipulateAction,
8
+ ImageSaveOptions,
9
+ ImageManipulationResult,
10
+ } from '../../domain/entities/ImageTypes';
11
+ import { ImageTransformService } from './ImageTransformService';
12
+ import { ImageUtils } from '../../domain/utils/ImageUtils';
13
+ import { ImageValidator } from '../utils/ImageValidator';
14
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
15
+
16
+ export class ImageAdvancedTransformService {
17
+ static async manipulate(
18
+ uri: string,
19
+ action: ImageManipulateAction,
20
+ options?: ImageSaveOptions
21
+ ): Promise<ImageManipulationResult> {
22
+ try {
23
+ const uriValidation = ImageValidator.validateUri(uri);
24
+ if (!uriValidation.isValid) {
25
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'manipulate');
26
+ }
27
+
28
+ const actions: ImageManipulator.Action[] = [];
29
+
30
+ if (action.resize) {
31
+ const dimValidation = ImageValidator.validateDimensions(action.resize);
32
+ if (!dimValidation.isValid) {
33
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
34
+ }
35
+ actions.push({ resize: action.resize });
36
+ }
37
+
38
+ if (action.crop) {
39
+ const dimValidation = ImageValidator.validateDimensions(action.crop);
40
+ if (!dimValidation.isValid) {
41
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
42
+ }
43
+ actions.push({ crop: action.crop });
44
+ }
45
+
46
+ if (action.rotate) {
47
+ const rotationValidation = ImageValidator.validateRotation(action.rotate);
48
+ if (!rotationValidation.isValid) {
49
+ throw ImageErrorHandler.createError(rotationValidation.error!, IMAGE_ERROR_CODES.VALIDATION_ERROR, 'manipulate');
50
+ }
51
+ actions.push({ rotate: action.rotate });
52
+ }
53
+
54
+ if (action.flip) {
55
+ if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
56
+ if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
57
+ }
58
+
59
+ return await ImageManipulator.manipulateAsync(
60
+ uri,
61
+ actions,
62
+ ImageTransformService['buildSaveOptions'](options)
63
+ );
64
+ } catch (error) {
65
+ throw ImageErrorHandler.handleUnknownError(error, 'manipulate');
66
+ }
67
+ }
68
+
69
+ static async resizeToFit(
70
+ uri: string,
71
+ maxWidth: number,
72
+ maxHeight: number,
73
+ options?: ImageSaveOptions
74
+ ): Promise<ImageManipulationResult> {
75
+ try {
76
+ const dimValidation = ImageValidator.validateDimensions({ width: maxWidth, height: maxHeight });
77
+ if (!dimValidation.isValid) {
78
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'resizeToFit');
79
+ }
80
+
81
+ const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
82
+ return ImageTransformService.resize(uri, dimensions.width, dimensions.height, options);
83
+ } catch (error) {
84
+ throw ImageErrorHandler.handleUnknownError(error, 'resizeToFit');
85
+ }
86
+ }
87
+
88
+ static async cropToSquare(
89
+ uri: string,
90
+ width: number,
91
+ height: number,
92
+ options?: ImageSaveOptions
93
+ ): Promise<ImageManipulationResult> {
94
+ try {
95
+ const dimValidation = ImageValidator.validateDimensions({ width, height });
96
+ if (!dimValidation.isValid) {
97
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'cropToSquare');
98
+ }
99
+
100
+ const cropArea = ImageUtils.getSquareCrop(width, height);
101
+ return ImageTransformService.crop(uri, cropArea, options);
102
+ } catch (error) {
103
+ throw ImageErrorHandler.handleUnknownError(error, 'cropToSquare');
104
+ }
105
+ }
106
+ }
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Image Conversion Service
2
+ * Image Infrastructure - Conversion Service
3
3
  *
4
- * Handles format conversion, compression, and thumbnail generation.
5
- * (Thumbnail is treated as a specialized compression/resize)
4
+ * Handles format conversion, compression, and thumbnail generation
6
5
  */
7
6
 
8
7
  import * as ImageManipulator from 'expo-image-manipulator';
@@ -13,20 +12,33 @@ import type {
13
12
  } from '../../domain/entities/ImageTypes';
14
13
  import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
15
14
  import { ImageTransformService } from './ImageTransformService';
15
+ import { ImageAdvancedTransformService } from './ImageAdvancedTransformService';
16
+ import { ImageValidator } from '../utils/ImageValidator';
17
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
16
18
 
17
19
  export class ImageConversionService {
18
20
  static async compress(
19
21
  uri: string,
20
- quality: number = IMAGE_CONSTANTS.DEFAULT_QUALITY
21
- ): Promise<ImageManipulationResult | null> {
22
+ quality: number = IMAGE_CONSTANTS.defaultQuality
23
+ ): Promise<ImageManipulationResult> {
22
24
  try {
25
+ const uriValidation = ImageValidator.validateUri(uri);
26
+ if (!uriValidation.isValid) {
27
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'compress');
28
+ }
29
+
30
+ const qualityValidation = ImageValidator.validateQuality(quality);
31
+ if (!qualityValidation.isValid) {
32
+ throw ImageErrorHandler.createError(qualityValidation.error!, IMAGE_ERROR_CODES.INVALID_QUALITY, 'compress');
33
+ }
34
+
23
35
  return await ImageManipulator.manipulateAsync(
24
36
  uri,
25
37
  [],
26
38
  { compress: quality }
27
39
  );
28
- } catch {
29
- return null;
40
+ } catch (error) {
41
+ throw ImageErrorHandler.handleUnknownError(error, 'compress');
30
42
  }
31
43
  }
32
44
 
@@ -34,33 +46,54 @@ export class ImageConversionService {
34
46
  uri: string,
35
47
  format: SaveFormat,
36
48
  quality?: number
37
- ): Promise<ImageManipulationResult | null> {
49
+ ): Promise<ImageManipulationResult> {
38
50
  try {
51
+ const uriValidation = ImageValidator.validateUri(uri);
52
+ if (!uriValidation.isValid) {
53
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'convertFormat');
54
+ }
55
+
56
+ const compressQuality = quality ?? IMAGE_CONSTANTS.defaultQuality;
57
+ const qualityValidation = ImageValidator.validateQuality(compressQuality);
58
+ if (!qualityValidation.isValid) {
59
+ throw ImageErrorHandler.createError(qualityValidation.error!, IMAGE_ERROR_CODES.INVALID_QUALITY, 'convertFormat');
60
+ }
61
+
39
62
  return await ImageManipulator.manipulateAsync(
40
63
  uri,
41
64
  [],
42
65
  {
43
- compress: quality || IMAGE_CONSTANTS.DEFAULT_QUALITY,
44
- format: ImageTransformService.mapFormat(format),
66
+ compress: compressQuality,
67
+ format: ImageTransformService['mapFormat'](format),
45
68
  }
46
69
  );
47
- } catch {
48
- return null;
70
+ } catch (error) {
71
+ throw ImageErrorHandler.handleUnknownError(error, 'convertFormat');
49
72
  }
50
73
  }
51
74
 
52
75
  static async createThumbnail(
53
76
  uri: string,
54
- size: number = IMAGE_CONSTANTS.THUMBNAIL_SIZE,
77
+ size: number = IMAGE_CONSTANTS.thumbnailSize,
55
78
  options?: ImageSaveOptions
56
- ): Promise<ImageManipulationResult | null> {
79
+ ): Promise<ImageManipulationResult> {
57
80
  try {
58
- return ImageTransformService.resizeToFit(uri, size, size, {
81
+ const uriValidation = ImageValidator.validateUri(uri);
82
+ if (!uriValidation.isValid) {
83
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'createThumbnail');
84
+ }
85
+
86
+ const dimValidation = ImageValidator.validateDimensions({ width: size, height: size });
87
+ if (!dimValidation.isValid) {
88
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'createThumbnail');
89
+ }
90
+
91
+ return await ImageAdvancedTransformService.resizeToFit(uri, size, size, {
59
92
  ...options,
60
- compress: options?.compress || IMAGE_CONSTANTS.COMPRESS_QUALITY.MEDIUM,
93
+ compress: options?.compress ?? IMAGE_CONSTANTS.compressQuality.medium,
61
94
  });
62
- } catch {
63
- return null;
95
+ } catch (error) {
96
+ throw ImageErrorHandler.handleUnknownError(error, 'createThumbnail');
64
97
  }
65
98
  }
66
99
  }
@@ -1,22 +1,37 @@
1
1
  /**
2
- * Image Storage Service
2
+ * Image Infrastructure - Storage Service
3
3
  *
4
- * Handles saving images to the device filesystem.
5
- * Wraps @umituz/react-native-filesystem.
4
+ * Handles saving images to the device filesystem
6
5
  */
7
6
 
8
7
  import { FileSystemService } from '@umituz/react-native-filesystem';
8
+ import { ImageValidator } from '../utils/ImageValidator';
9
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
9
10
 
10
11
  export class ImageStorageService {
11
12
  static async saveImage(
12
13
  uri: string,
13
14
  filename?: string
14
- ): Promise<string | null> {
15
+ ): Promise<string> {
15
16
  try {
17
+ const uriValidation = ImageValidator.validateUri(uri);
18
+ if (!uriValidation.isValid) {
19
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'saveImage');
20
+ }
21
+
16
22
  const result = await FileSystemService.copyToDocuments(uri, filename);
17
- return result.success && result.uri ? result.uri : null;
18
- } catch {
19
- return null;
23
+
24
+ if (!result.success || !result.uri) {
25
+ throw ImageErrorHandler.createError(
26
+ 'Failed to save image to filesystem',
27
+ IMAGE_ERROR_CODES.STORAGE_FAILED,
28
+ 'saveImage'
29
+ );
30
+ }
31
+
32
+ return result.uri;
33
+ } catch (error) {
34
+ throw ImageErrorHandler.handleUnknownError(error, 'saveImage');
20
35
  }
21
36
  }
22
37
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Image Transform Service
2
+ * Image Infrastructure - Transform Service
3
3
  *
4
- * Handles basic geometric transformations: resize, crop, rotate, flip.
5
- * Also handles combined manipulations.
4
+ * Handles geometric transformations: resize, crop, rotate, flip
6
5
  */
7
6
 
8
7
  import * as ImageManipulator from 'expo-image-manipulator';
@@ -11,58 +10,79 @@ import type {
11
10
  ImageSaveOptions,
12
11
  ImageManipulationResult,
13
12
  SaveFormat,
13
+ ImageCropArea,
14
+ ImageFlipOptions,
14
15
  } from '../../domain/entities/ImageTypes';
15
16
  import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
16
17
  import { ImageUtils } from '../../domain/utils/ImageUtils';
18
+ import { ImageValidator } from '../utils/ImageValidator';
19
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
17
20
 
18
21
  export class ImageTransformService {
19
- /**
20
- * Helper to map SaveFormat to Manipulator format
21
- */
22
- static mapFormat(format?: SaveFormat): ImageManipulator.SaveFormat {
22
+ private static mapFormat(format?: SaveFormat): ImageManipulator.SaveFormat {
23
23
  if (format === 'png') return ImageManipulator.SaveFormat.PNG;
24
24
  if (format === 'webp') return ImageManipulator.SaveFormat.WEBP;
25
25
  return ImageManipulator.SaveFormat.JPEG;
26
26
  }
27
27
 
28
+ private static buildSaveOptions(options?: ImageSaveOptions): ImageManipulator.SaveOptions {
29
+ return {
30
+ compress: options?.compress ?? IMAGE_CONSTANTS.defaultQuality,
31
+ format: this.mapFormat(options?.format),
32
+ base64: options?.base64,
33
+ };
34
+ }
35
+
28
36
  static async resize(
29
37
  uri: string,
30
38
  width?: number,
31
39
  height?: number,
32
40
  options?: ImageSaveOptions
33
- ): Promise<ImageManipulationResult | null> {
41
+ ): Promise<ImageManipulationResult> {
34
42
  try {
43
+ const uriValidation = ImageValidator.validateUri(uri);
44
+ if (!uriValidation.isValid) {
45
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'resize');
46
+ }
47
+
48
+ const dimValidation = ImageValidator.validateDimensions({ width, height });
49
+ if (!dimValidation.isValid) {
50
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'resize');
51
+ }
52
+
35
53
  return await ImageManipulator.manipulateAsync(
36
54
  uri,
37
55
  [{ resize: { width, height } }],
38
- {
39
- compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
40
- format: ImageTransformService.mapFormat(options?.format),
41
- base64: options?.base64,
42
- }
56
+ this.buildSaveOptions(options)
43
57
  );
44
- } catch {
45
- return null;
58
+ } catch (error) {
59
+ throw ImageErrorHandler.handleUnknownError(error, 'resize');
46
60
  }
47
61
  }
48
62
 
49
63
  static async crop(
50
64
  uri: string,
51
- cropArea: { originX: number; originY: number; width: number; height: number },
65
+ cropArea: ImageCropArea,
52
66
  options?: ImageSaveOptions
53
- ): Promise<ImageManipulationResult | null> {
67
+ ): Promise<ImageManipulationResult> {
54
68
  try {
69
+ const uriValidation = ImageValidator.validateUri(uri);
70
+ if (!uriValidation.isValid) {
71
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'crop');
72
+ }
73
+
74
+ const dimValidation = ImageValidator.validateDimensions(cropArea);
75
+ if (!dimValidation.isValid) {
76
+ throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'crop');
77
+ }
78
+
55
79
  return await ImageManipulator.manipulateAsync(
56
80
  uri,
57
81
  [{ crop: cropArea }],
58
- {
59
- compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
60
- format: ImageTransformService.mapFormat(options?.format),
61
- base64: options?.base64,
62
- }
82
+ this.buildSaveOptions(options)
63
83
  );
64
- } catch {
65
- return null;
84
+ } catch (error) {
85
+ throw ImageErrorHandler.handleUnknownError(error, 'crop');
66
86
  }
67
87
  }
68
88
 
@@ -70,28 +90,39 @@ export class ImageTransformService {
70
90
  uri: string,
71
91
  degrees: number,
72
92
  options?: ImageSaveOptions
73
- ): Promise<ImageManipulationResult | null> {
93
+ ): Promise<ImageManipulationResult> {
74
94
  try {
95
+ const uriValidation = ImageValidator.validateUri(uri);
96
+ if (!uriValidation.isValid) {
97
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'rotate');
98
+ }
99
+
100
+ const rotationValidation = ImageValidator.validateRotation(degrees);
101
+ if (!rotationValidation.isValid) {
102
+ throw ImageErrorHandler.createError(rotationValidation.error!, IMAGE_ERROR_CODES.VALIDATION_ERROR, 'rotate');
103
+ }
104
+
75
105
  return await ImageManipulator.manipulateAsync(
76
106
  uri,
77
107
  [{ rotate: degrees }],
78
- {
79
- compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
80
- format: ImageTransformService.mapFormat(options?.format),
81
- base64: options?.base64,
82
- }
108
+ this.buildSaveOptions(options)
83
109
  );
84
- } catch {
85
- return null;
110
+ } catch (error) {
111
+ throw ImageErrorHandler.handleUnknownError(error, 'rotate');
86
112
  }
87
113
  }
88
114
 
89
115
  static async flip(
90
116
  uri: string,
91
- flip: { horizontal?: boolean; vertical?: boolean },
117
+ flip: ImageFlipOptions,
92
118
  options?: ImageSaveOptions
93
- ): Promise<ImageManipulationResult | null> {
119
+ ): Promise<ImageManipulationResult> {
94
120
  try {
121
+ const uriValidation = ImageValidator.validateUri(uri);
122
+ if (!uriValidation.isValid) {
123
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'flip');
124
+ }
125
+
95
126
  const actions: ImageManipulator.Action[] = [];
96
127
  if (flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
97
128
  if (flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
@@ -99,74 +130,10 @@ export class ImageTransformService {
99
130
  return await ImageManipulator.manipulateAsync(
100
131
  uri,
101
132
  actions,
102
- {
103
- compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
104
- format: ImageTransformService.mapFormat(options?.format),
105
- base64: options?.base64,
106
- }
107
- );
108
- } catch {
109
- return null;
110
- }
111
- }
112
-
113
- static async manipulate(
114
- uri: string,
115
- action: ImageManipulateAction,
116
- options?: ImageSaveOptions
117
- ): Promise<ImageManipulationResult | null> {
118
- try {
119
- const actions: ImageManipulator.Action[] = [];
120
- if (action.resize) actions.push({ resize: action.resize });
121
- if (action.crop) {
122
- // @ts-ignore - guarded by check above
123
- actions.push({ crop: action.crop });
124
- }
125
- if (action.rotate) actions.push({ rotate: action.rotate });
126
- if (action.flip) {
127
- if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
128
- if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
129
- }
130
-
131
- return await ImageManipulator.manipulateAsync(
132
- uri,
133
- actions,
134
- {
135
- compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
136
- format: ImageTransformService.mapFormat(options?.format),
137
- base64: options?.base64,
138
- }
133
+ this.buildSaveOptions(options)
139
134
  );
140
- } catch {
141
- return null;
142
- }
143
- }
144
-
145
- static async resizeToFit(
146
- uri: string,
147
- maxWidth: number,
148
- maxHeight: number,
149
- options?: ImageSaveOptions
150
- ): Promise<ImageManipulationResult | null> {
151
- try {
152
- const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
153
- return ImageTransformService.resize(uri, dimensions.width, dimensions.height, options);
154
- } catch {
155
- return null;
156
- }
157
- }
158
-
159
- static async cropToSquare(
160
- uri: string,
161
- width: number,
162
- height: number,
163
- options?: ImageSaveOptions
164
- ): Promise<ImageManipulationResult | null> {
165
- try {
166
- const cropArea = ImageUtils.getSquareCrop(width, height);
167
- return ImageTransformService.crop(uri, cropArea, options);
168
- } catch {
169
- return null;
135
+ } catch (error) {
136
+ throw ImageErrorHandler.handleUnknownError(error, 'flip');
170
137
  }
171
138
  }
172
139
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Image Domain - Image Viewer Service
2
+ * Image Infrastructure - Viewer Service
3
3
  *
4
- * Service for image viewing and gallery using react-native-image-viewing.
5
- * Provides full-screen image viewer with zoom, swipe, and gallery features.
4
+ * Provides configuration for react-native-image-viewing component
6
5
  */
7
6
 
8
7
  import type {
@@ -10,9 +9,6 @@ import type {
10
9
  ImageGalleryOptions,
11
10
  } from '../../domain/entities/ImageTypes';
12
11
 
13
- /**
14
- * Image viewer configuration
15
- */
16
12
  export interface ImageViewerConfig {
17
13
  images: ImageViewerItem[];
18
14
  index?: number;
@@ -21,26 +17,11 @@ export interface ImageViewerConfig {
21
17
  options?: ImageGalleryOptions;
22
18
  }
23
19
 
24
- /**
25
- * Image viewer service
26
- *
27
- * NOTE: This service provides configuration for react-native-image-viewing component.
28
- * The actual viewer component is rendered in the presentation layer.
29
- */
30
20
  export class ImageViewerService {
31
- /**
32
- * Prepare images for viewer
33
- * Converts image URIs to viewer format
34
- */
35
21
  static prepareImages(uris: string[]): ImageViewerItem[] {
36
- return uris.map(uri => ({
37
- uri,
38
- }));
22
+ return uris.map(uri => ({ uri }));
39
23
  }
40
24
 
41
- /**
42
- * Prepare images with metadata
43
- */
44
25
  static prepareImagesWithMetadata(items: ImageViewerItem[]): ImageViewerItem[] {
45
26
  return items.map(item => ({
46
27
  uri: item.uri,
@@ -51,9 +32,6 @@ export class ImageViewerService {
51
32
  }));
52
33
  }
53
34
 
54
- /**
55
- * Create viewer configuration
56
- */
57
35
  static createViewerConfig(
58
36
  images: ImageViewerItem[],
59
37
  startIndex: number = 0,
@@ -74,9 +52,6 @@ export class ImageViewerService {
74
52
  };
75
53
  }
76
54
 
77
- /**
78
- * Get default gallery options
79
- */
80
55
  static getDefaultOptions(): ImageGalleryOptions {
81
56
  return {
82
57
  index: 0,
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Image Infrastructure - Error Handler
3
+ */
4
+ export class ImageError extends Error {
5
+ constructor(
6
+ message: string,
7
+ public readonly code: string,
8
+ public readonly operation?: string
9
+ ) {
10
+ super(message);
11
+ this.name = 'ImageError';
12
+ }
13
+ }
14
+
15
+ export const IMAGE_ERROR_CODES = {
16
+ INVALID_URI: 'INVALID_URI',
17
+ INVALID_DIMENSIONS: 'INVALID_DIMENSIONS',
18
+ INVALID_QUALITY: 'INVALID_QUALITY',
19
+ MANIPULATION_FAILED: 'MANIPULATION_FAILED',
20
+ CONVERSION_FAILED: 'CONVERSION_FAILED',
21
+ STORAGE_FAILED: 'STORAGE_FAILED',
22
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
23
+ } as const;
24
+
25
+ export type ImageErrorCode = typeof IMAGE_ERROR_CODES[keyof typeof IMAGE_ERROR_CODES];
26
+
27
+ export class ImageErrorHandler {
28
+ static createError(message: string, code: ImageErrorCode, operation?: string): ImageError {
29
+ return new ImageError(message, code, operation);
30
+ }
31
+
32
+ static handleUnknownError(error: unknown, operation?: string): ImageError {
33
+ if (error instanceof ImageError) {
34
+ return error;
35
+ }
36
+
37
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
38
+ return new ImageError(message, IMAGE_ERROR_CODES.MANIPULATION_FAILED, operation);
39
+ }
40
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Image Infrastructure - Validation Utilities
3
+ */
4
+ import { ImageDimensions } from '../../domain/entities/ImageTypes';
5
+
6
+ export interface ValidationResult {
7
+ isValid: boolean;
8
+ error?: string;
9
+ }
10
+
11
+ export class ImageValidator {
12
+ static validateUri(uri: string): ValidationResult {
13
+ if (!uri || typeof uri !== 'string') {
14
+ return { isValid: false, error: 'URI is required and must be a string' };
15
+ }
16
+
17
+ if (!uri.startsWith('file://') &&
18
+ !uri.startsWith('content://') &&
19
+ !uri.startsWith('http://') &&
20
+ !uri.startsWith('https://') &&
21
+ !uri.startsWith('data:image/')) {
22
+ return { isValid: false, error: 'Invalid URI format' };
23
+ }
24
+
25
+ return { isValid: true };
26
+ }
27
+
28
+ static validateDimensions(dimensions: Partial<ImageDimensions>): ValidationResult {
29
+ if (dimensions.width !== undefined) {
30
+ if (typeof dimensions.width !== 'number' || dimensions.width <= 0) {
31
+ return { isValid: false, error: 'Width must be a positive number' };
32
+ }
33
+ }
34
+
35
+ if (dimensions.height !== undefined) {
36
+ if (typeof dimensions.height !== 'number' || dimensions.height <= 0) {
37
+ return { isValid: false, error: 'Height must be a positive number' };
38
+ }
39
+ }
40
+
41
+ return { isValid: true };
42
+ }
43
+
44
+ static validateQuality(quality: number): ValidationResult {
45
+ if (typeof quality !== 'number' || quality < 0 || quality > 1) {
46
+ return { isValid: false, error: 'Quality must be a number between 0 and 1' };
47
+ }
48
+
49
+ return { isValid: true };
50
+ }
51
+
52
+ static validateRotation(degrees: number): ValidationResult {
53
+ if (typeof degrees !== 'number') {
54
+ return { isValid: false, error: 'Degrees must be a number' };
55
+ }
56
+
57
+ return { isValid: true };
58
+ }
59
+ }
@@ -1,25 +1,28 @@
1
1
  /**
2
- * GalleryHeader Component
3
- * Header for ImageGallery with edit and close buttons
2
+ * Presentation - Gallery Header Component
4
3
  *
5
- * This component should be implemented by the consumer app
6
- * using their own design system and safe area handling.
4
+ * NOTE: This component should be implemented by consumer app
5
+ * using their design system and safe area handling
7
6
  */
8
7
 
9
8
  import React from 'react';
10
9
  import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';
11
10
 
12
11
  interface GalleryHeaderProps {
13
- onEdit: () => void;
12
+ onEdit?: () => void;
14
13
  onClose: () => void;
15
14
  }
16
15
 
17
16
  export function GalleryHeader({ onEdit, onClose }: GalleryHeaderProps) {
18
17
  return (
19
18
  <View style={styles.container}>
20
- <TouchableOpacity style={styles.button} onPress={onEdit}>
21
- <Text style={styles.buttonText}>Edit</Text>
22
- </TouchableOpacity>
19
+ {onEdit ? (
20
+ <TouchableOpacity style={styles.button} onPress={onEdit}>
21
+ <Text style={styles.buttonText}>Edit</Text>
22
+ </TouchableOpacity>
23
+ ) : (
24
+ <View style={styles.spacer} />
25
+ )}
23
26
 
24
27
  <TouchableOpacity style={styles.button} onPress={onClose}>
25
28
  <Text style={styles.buttonText}>✕</Text>
@@ -42,6 +45,9 @@ const styles = StyleSheet.create({
42
45
  alignItems: 'center',
43
46
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
44
47
  },
48
+ spacer: {
49
+ width: 48,
50
+ },
45
51
  button: {
46
52
  flexDirection: 'row',
47
53
  alignItems: 'center',
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Image Gallery Component
2
+ * Presentation - Image Gallery Component
3
3
  *
4
- * A wrapper around react-native-image-viewing that provides
5
- * theme integration, standard configuration, and optional editing.
4
+ * Wrapper around react-native-image-viewing with theme integration
6
5
  */
7
6
 
8
7
  import React, { useCallback } from 'react';
9
8
  import ImageViewing from 'react-native-image-viewing';
10
- import * as ImageManipulator from 'expo-image-manipulator';
11
- import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
9
+ // import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
12
10
  import type { ImageViewerItem, ImageGalleryOptions } from '../../domain/entities/ImageTypes';
13
11
  import { GalleryHeader } from './GalleryHeader';
14
12
 
@@ -33,14 +31,14 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
33
31
  onImageChange,
34
32
  enableEditing = false,
35
33
  }) => {
36
- const tokens = useAppDesignTokens();
34
+ // const tokens = useAppDesignTokens();
37
35
  const [currentIndex, setCurrentIndex] = React.useState(index);
38
36
 
39
37
  React.useEffect(() => {
40
38
  setCurrentIndex(index);
41
39
  }, [index]);
42
40
 
43
- const bg = backgroundColor || tokens.colors.backgroundPrimary;
41
+ const bg = backgroundColor || '#000000';
44
42
 
45
43
  const viewerImages = React.useMemo(
46
44
  () => images.map((img) => ({ uri: img.uri })),
@@ -52,20 +50,9 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
52
50
  if (!currentImage || !onImageChange) return;
53
51
 
54
52
  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
- }
53
+ await onImageChange(currentImage.uri, currentIndex);
67
54
  } catch (error) {
68
- // Silent fail
55
+ // Consumer should handle editing logic
69
56
  }
70
57
  }, [images, currentIndex, onImageChange]);
71
58
 
@@ -78,10 +65,9 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
78
65
  );
79
66
 
80
67
  const headerComponent = useCallback(() => {
81
- if (!enableEditing) return null;
82
68
  return (
83
69
  <GalleryHeader
84
- onEdit={handleEdit}
70
+ onEdit={enableEditing ? handleEdit : undefined}
85
71
  onClose={onDismiss}
86
72
  />
87
73
  );
@@ -1,8 +1,7 @@
1
1
  /**
2
- * useImage Hook
2
+ * Presentation - Image Hook
3
3
  *
4
- * Aggregator hook that combines transformation and conversion capabilities.
5
- * Kept simple and modular.
4
+ * Aggregator hook combining transformation and conversion capabilities
6
5
  */
7
6
 
8
7
  import { useImageTransform } from './useImageTransform';
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Presentation - Image Conversion Hook
3
+ */
1
4
  import { useCallback } from 'react';
2
5
  import { useImageOperation } from './useImageOperation';
3
6
  import { ImageConversionService } from '../../infrastructure/services/ImageConversionService';
@@ -7,17 +10,17 @@ import type { ImageSaveOptions, SaveFormat } from '../../domain/entities/ImageTy
7
10
  export const useImageConversion = () => {
8
11
  const { isProcessing, error, execute } = useImageOperation();
9
12
 
10
- const compress = useCallback((uri: string, quality: number = 0.8) =>
13
+ const compress = useCallback((uri: string, quality?: number) =>
11
14
  execute(() => ImageConversionService.compress(uri, quality), 'Failed to compress'), [execute]);
12
15
 
13
16
  const convertFormat = useCallback((uri: string, format: SaveFormat, quality?: number) =>
14
- execute(() => ImageConversionService.convertFormat(uri, format, quality), 'Failed to convert'), [execute]);
17
+ execute(() => ImageConversionService.convertFormat(uri, format, quality), 'Failed to convert format'), [execute]);
15
18
 
16
19
  const createThumbnail = useCallback((uri: string, size?: number, options?: ImageSaveOptions) =>
17
20
  execute(() => ImageConversionService.createThumbnail(uri, size, options), 'Failed to create thumbnail'), [execute]);
18
21
 
19
22
  const saveImage = useCallback((uri: string, filename?: string) =>
20
- execute(() => ImageStorageService.saveImage(uri, filename), 'Failed to save'), [execute]);
23
+ execute(() => ImageStorageService.saveImage(uri, filename), 'Failed to save image'), [execute]);
21
24
 
22
25
  return {
23
26
  compress, convertFormat, createThumbnail, saveImage,
@@ -1,10 +1,10 @@
1
1
  /**
2
- * useImageEditor Hook
3
- * Provides image editing functionality with crop, rotate, flip
2
+ * Presentation - Image Editor Hook
3
+ *
4
+ * NOTE: This hook is deprecated - use useImageTransform instead
4
5
  */
5
6
 
6
7
  import { useState, useCallback } from 'react';
7
- import * as ImageManipulator from 'expo-image-manipulator';
8
8
  import type { Action } from 'expo-image-manipulator';
9
9
 
10
10
  interface UseImageEditorOptions {
@@ -30,20 +30,14 @@ export function useImageEditor({ onSave }: UseImageEditorOptions = {}) {
30
30
  if (!currentUri) return;
31
31
 
32
32
  try {
33
- const result = await ImageManipulator.manipulateAsync(
34
- currentUri,
35
- actions,
36
- { compress: 0.9, format: ImageManipulator.SaveFormat.JPEG }
37
- );
38
-
39
33
  if (onSave) {
40
- await onSave(result.uri);
34
+ await onSave(currentUri);
41
35
  }
42
36
 
43
37
  setIsEditing(false);
44
38
  setCurrentUri(null);
45
39
 
46
- return result.uri;
40
+ return currentUri;
47
41
  } catch (error) {
48
42
  throw error;
49
43
  }
@@ -1,8 +1,5 @@
1
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.
2
+ * Presentation - Image Gallery Hook
6
3
  */
7
4
 
8
5
  import { useState, useCallback, useMemo } from 'react';
@@ -12,51 +9,16 @@ import type {
12
9
  ImageGalleryOptions,
13
10
  } from '../../domain/entities/ImageTypes';
14
11
 
15
- /**
16
- * useImageGallery hook return type
17
- */
18
12
  export interface UseImageGalleryReturn {
19
- // State
20
13
  visible: boolean;
21
14
  currentIndex: number;
22
15
  images: ImageViewerItem[];
23
-
24
- // Actions
25
16
  open: (images: ImageViewerItem[] | string[], startIndex?: number, options?: ImageGalleryOptions) => void;
26
17
  close: () => void;
27
18
  setIndex: (index: number) => void;
28
-
29
- // Gallery options
30
19
  options: ImageGalleryOptions;
31
20
  }
32
21
 
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
22
  export const useImageGallery = (
61
23
  defaultOptions?: ImageGalleryOptions
62
24
  ): UseImageGalleryReturn => {
@@ -67,16 +29,12 @@ export const useImageGallery = (
67
29
  defaultOptions || ImageViewerService.getDefaultOptions()
68
30
  );
69
31
 
70
- /**
71
- * Open gallery with images
72
- */
73
32
  const open = useCallback(
74
33
  (
75
34
  imageData: ImageViewerItem[] | string[],
76
35
  startIndex: number = 0,
77
36
  options?: ImageGalleryOptions
78
37
  ) => {
79
- // Prepare images based on input type
80
38
  const preparedImages =
81
39
  typeof imageData[0] === 'string'
82
40
  ? ImageViewerService.prepareImages(imageData as string[])
@@ -85,7 +43,6 @@ export const useImageGallery = (
85
43
  setImages(preparedImages);
86
44
  setCurrentIndex(options?.index ?? startIndex);
87
45
 
88
- // Merge options with defaults
89
46
  if (options) {
90
47
  setGalleryOptions({
91
48
  ...galleryOptions,
@@ -98,33 +55,22 @@ export const useImageGallery = (
98
55
  [galleryOptions]
99
56
  );
100
57
 
101
- /**
102
- * Close gallery
103
- */
104
58
  const close = useCallback(() => {
105
59
  setVisible(false);
106
60
 
107
- // Call onDismiss if provided
108
61
  if (galleryOptions.onDismiss) {
109
62
  galleryOptions.onDismiss();
110
63
  }
111
64
  }, [galleryOptions]);
112
65
 
113
- /**
114
- * Set current image index
115
- */
116
66
  const setIndex = useCallback((index: number) => {
117
67
  setCurrentIndex(index);
118
68
 
119
- // Call onIndexChange if provided
120
69
  if (galleryOptions.onIndexChange) {
121
70
  galleryOptions.onIndexChange(index);
122
71
  }
123
72
  }, [galleryOptions]);
124
73
 
125
- /**
126
- * Memoized options for ImageViewing component
127
- */
128
74
  const options = useMemo(() => ({
129
75
  backgroundColor: galleryOptions.backgroundColor || '#000000',
130
76
  swipeToCloseEnabled: galleryOptions.swipeToCloseEnabled ?? true,
@@ -132,17 +78,12 @@ export const useImageGallery = (
132
78
  }), [galleryOptions]);
133
79
 
134
80
  return {
135
- // State
136
81
  visible,
137
82
  currentIndex,
138
83
  images,
139
-
140
- // Actions
141
84
  open,
142
85
  close,
143
86
  setIndex,
144
-
145
- // Gallery options
146
87
  options,
147
88
  };
148
89
  };
@@ -1,28 +1,32 @@
1
- import { useState, useCallback } from 'react';
2
-
3
1
  /**
4
- * Generic hook to handle image operation states (loading, error)
5
- * adhering to DRY principles.
2
+ * Presentation - Image Operation Hook
3
+ *
4
+ * Generic state management for async image operations
6
5
  */
6
+
7
+ import { useState, useCallback } from 'react';
8
+ import { ImageError } from '../../infrastructure/utils/ImageErrorHandler';
9
+
7
10
  export const useImageOperation = () => {
8
11
  const [isProcessing, setIsProcessing] = useState(false);
9
12
  const [error, setError] = useState<string | null>(null);
10
13
 
11
14
  const execute = useCallback(async <T>(
12
- operation: () => Promise<T | null>,
15
+ operation: () => Promise<T>,
13
16
  errorMessage: string
14
17
  ): Promise<T | null> => {
15
18
  setIsProcessing(true);
16
19
  setError(null);
20
+
17
21
  try {
18
22
  const result = await operation();
19
- if (!result) {
20
- setError(errorMessage);
21
- return null;
22
- }
23
23
  return result;
24
24
  } catch (err) {
25
- setError(err instanceof Error ? err.message : errorMessage);
25
+ if (err instanceof ImageError) {
26
+ setError(err.message);
27
+ } else {
28
+ setError(err instanceof Error ? err.message : errorMessage);
29
+ }
26
30
  return null;
27
31
  } finally {
28
32
  setIsProcessing(false);
@@ -1,9 +1,15 @@
1
+ /**
2
+ * Presentation - Image Transform Hook
3
+ */
1
4
  import { useCallback } from 'react';
2
5
  import { useImageOperation } from './useImageOperation';
3
6
  import { ImageTransformService } from '../../infrastructure/services/ImageTransformService';
7
+ import { ImageAdvancedTransformService } from '../../infrastructure/services/ImageAdvancedTransformService';
4
8
  import type {
5
9
  ImageManipulateAction,
6
10
  ImageSaveOptions,
11
+ ImageCropArea,
12
+ ImageFlipOptions,
7
13
  } from '../../domain/entities/ImageTypes';
8
14
 
9
15
  export const useImageTransform = () => {
@@ -12,23 +18,23 @@ export const useImageTransform = () => {
12
18
  const resize = useCallback((uri: string, width?: number, height?: number, options?: ImageSaveOptions) =>
13
19
  execute(() => ImageTransformService.resize(uri, width, height, options), 'Failed to resize'), [execute]);
14
20
 
15
- const crop = useCallback((uri: string, cropArea: any, options?: ImageSaveOptions) =>
21
+ const crop = useCallback((uri: string, cropArea: ImageCropArea, options?: ImageSaveOptions) =>
16
22
  execute(() => ImageTransformService.crop(uri, cropArea, options), 'Failed to crop'), [execute]);
17
23
 
18
24
  const rotate = useCallback((uri: string, degrees: number, options?: ImageSaveOptions) =>
19
25
  execute(() => ImageTransformService.rotate(uri, degrees, options), 'Failed to rotate'), [execute]);
20
26
 
21
- const flip = useCallback((uri: string, flipParams: any, options?: ImageSaveOptions) =>
27
+ const flip = useCallback((uri: string, flipParams: ImageFlipOptions, options?: ImageSaveOptions) =>
22
28
  execute(() => ImageTransformService.flip(uri, flipParams, options), 'Failed to flip'), [execute]);
23
29
 
24
30
  const manipulate = useCallback((uri: string, action: ImageManipulateAction, options?: ImageSaveOptions) =>
25
- execute(() => ImageTransformService.manipulate(uri, action, options), 'Failed to manipulate'), [execute]);
31
+ execute(() => ImageAdvancedTransformService.manipulate(uri, action, options), 'Failed to manipulate'), [execute]);
26
32
 
27
- const resizeToFit = useCallback((uri: string, w: number, h: number, opts?: ImageSaveOptions) =>
28
- execute(() => ImageTransformService.resizeToFit(uri, w, h, opts), 'Failed to resize to fit'), [execute]);
33
+ const resizeToFit = useCallback((uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions) =>
34
+ execute(() => ImageAdvancedTransformService.resizeToFit(uri, maxWidth, maxHeight, options), 'Failed to resize to fit'), [execute]);
29
35
 
30
- const cropToSquare = useCallback((uri: string, w: number, h: number, opts?: ImageSaveOptions) =>
31
- execute(() => ImageTransformService.cropToSquare(uri, w, h, opts), 'Failed to crop square'), [execute]);
36
+ const cropToSquare = useCallback((uri: string, width: number, height: number, options?: ImageSaveOptions) =>
37
+ execute(() => ImageAdvancedTransformService.cropToSquare(uri, width, height, options), 'Failed to crop square'), [execute]);
32
38
 
33
39
  return {
34
40
  resize, crop, rotate, flip, manipulate, resizeToFit, cropToSquare,