@umituz/react-native-design-system 2.6.111 → 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 (60) 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/image/domain/entities/EditorTypes.ts +23 -0
  6. package/src/image/domain/entities/ImageConstants.ts +38 -0
  7. package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
  8. package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
  9. package/src/image/domain/entities/ImageTypes.ts +86 -0
  10. package/src/image/domain/entities/ValidationResult.ts +15 -0
  11. package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
  12. package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
  13. package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
  14. package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
  15. package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
  16. package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
  17. package/src/image/domain/utils/ImageUtils.ts +103 -0
  18. package/src/image/index.ts +123 -0
  19. package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
  20. package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
  21. package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
  22. package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
  23. package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
  24. package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
  25. package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
  26. package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
  27. package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
  28. package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
  29. package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
  30. package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
  31. package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
  32. package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
  33. package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
  34. package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
  35. package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
  36. package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
  37. package/src/image/infrastructure/utils/LayerManager.ts +77 -0
  38. package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
  39. package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
  40. package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
  41. package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
  42. package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
  43. package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
  44. package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
  45. package/src/image/presentation/components/GalleryHeader.tsx +126 -0
  46. package/src/image/presentation/components/ImageGallery.tsx +138 -0
  47. package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
  48. package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
  49. package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
  50. package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
  51. package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
  52. package/src/image/presentation/hooks/useImage.ts +39 -0
  53. package/src/image/presentation/hooks/useImageBatch.ts +28 -0
  54. package/src/image/presentation/hooks/useImageConversion.ts +29 -0
  55. package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
  56. package/src/image/presentation/hooks/useImageGallery.ts +90 -0
  57. package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
  58. package/src/image/presentation/hooks/useImageOperation.ts +37 -0
  59. package/src/image/presentation/hooks/useImageTransform.ts +42 -0
  60. package/src/index.ts +5 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.6.111",
4
- "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll and UUID utilities",
3
+ "version": "2.6.112",
4
+ "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID and image utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "exports": {
@@ -19,6 +19,7 @@
19
19
  "./responsive": "./src/responsive/index.ts",
20
20
  "./safe-area": "./src/safe-area/index.ts",
21
21
  "./device": "./src/device/index.ts",
22
+ "./image": "./src/image/index.ts",
22
23
  "./package.json": "./package.json"
23
24
  },
24
25
  "scripts": {
@@ -40,7 +41,8 @@
40
41
  "theme",
41
42
  "typography",
42
43
  "responsive",
43
- "safe-area"
44
+ "safe-area",
45
+ "image"
44
46
  ],
45
47
  "author": "Ümit UZ <umit@umituz.com>",
46
48
  "license": "MIT",
@@ -64,6 +66,7 @@
64
66
  "expo-crypto": ">=13.0.0",
65
67
  "expo-device": ">=5.0.0",
66
68
  "expo-font": ">=12.0.0",
69
+ "expo-image": ">=3.0.0",
67
70
  "expo-sharing": ">=12.0.0",
68
71
  "react": ">=19.0.0",
69
72
  "react-native": ">=0.81.0",
@@ -113,6 +116,7 @@
113
116
  "expo-file-system": "^19.0.21",
114
117
  "expo-font": "~13.0.0",
115
118
  "expo-haptics": "~14.0.0",
119
+ "expo-image": "~3.0.11",
116
120
  "expo-localization": "~16.0.1",
117
121
  "expo-sharing": "~14.0.8",
118
122
  "react": "19.1.0",
@@ -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
+ };
@@ -118,3 +118,6 @@ export { AtomicKeyboardAvoidingView, type AtomicKeyboardAvoidingViewProps } from
118
118
 
119
119
  // GlassView
120
120
  export { GlassView, type GlassViewProps } from './GlassView';
121
+
122
+ // Image
123
+ export { AtomicImage, type AtomicImageProps } from './image/AtomicImage';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Image Exports
3
+ *
4
+ * Image utilities and components for React Native
5
+ */
6
+
7
+ export * from '../image';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Domain - Editor Types (Main Export)
3
+ *
4
+ * Central export point for all editor-related types
5
+ */
6
+
7
+ // Tools
8
+ export * from './editor/EditorToolTypes';
9
+
10
+ // Elements
11
+ export * from './editor/EditorElementTypes';
12
+
13
+ // Layers & History
14
+ export * from './editor/EditorLayerTypes';
15
+
16
+ // State
17
+ export * from './editor/EditorStateTypes';
18
+
19
+ // Configuration
20
+ export * from './editor/EditorConfigTypes';
21
+
22
+ // Filters
23
+ export * from './editor/EditorFilterTypes';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Image Domain - Configuration Constants
3
+ */
4
+ import { SaveFormat } from "./ImageTypes";
5
+
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,
32
+ },
33
+ format: {
34
+ jpeg: 'jpeg' as SaveFormat,
35
+ png: 'png' as SaveFormat,
36
+ webp: 'webp' as SaveFormat,
37
+ },
38
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Image Domain - Filter Types
3
+ */
4
+
5
+ export enum ImageFilterType {
6
+ BLUR = 'blur',
7
+ SHARPEN = 'sharpen',
8
+ BRIGHTNESS = 'brightness',
9
+ CONTRAST = 'contrast',
10
+ SATURATION = 'saturation',
11
+ SEPIA = 'sepia',
12
+ GRAYSCALE = 'grayscale',
13
+ VINTAGE = 'vintage',
14
+ VIGNETTE = 'vignette',
15
+ }
16
+
17
+ export interface ImageFilterOptions {
18
+ intensity?: number;
19
+ radius?: number;
20
+ amount?: number;
21
+ }
22
+
23
+ export interface ImageFilter {
24
+ type: ImageFilterType;
25
+ options?: ImageFilterOptions;
26
+ }
27
+
28
+ export interface ImageColorAdjustment {
29
+ brightness?: number;
30
+ contrast?: number;
31
+ saturation?: number;
32
+ hue?: number;
33
+ }
34
+
35
+ export interface ImageQualityMetrics {
36
+ sharpness: number;
37
+ brightness: number;
38
+ contrast: number;
39
+ colorfulness: number;
40
+ overallQuality: number;
41
+ }
42
+
43
+ export interface ImageColorPalette {
44
+ dominant: string[];
45
+ palette: Array<{
46
+ color: string;
47
+ percentage: number;
48
+ population: number;
49
+ }>;
50
+ }
51
+
52
+ export interface ImageMetadataExtended {
53
+ format: string;
54
+ size: number;
55
+ dimensions: { width: number; height: number };
56
+ colorSpace: string;
57
+ hasAlpha: boolean;
58
+ orientation: number;
59
+ dpi?: number;
60
+ creationDate?: Date;
61
+ modificationDate?: Date;
62
+ gps?: { latitude: number; longitude: number };
63
+ camera?: {
64
+ make?: string;
65
+ model?: string;
66
+ iso?: number;
67
+ flash?: boolean;
68
+ focalLength?: number;
69
+ };
70
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Image Template Types
3
+ */
4
+
5
+ export interface ImageTemplate {
6
+ id: string;
7
+ name: string;
8
+ url: string;
9
+ }
10
+
11
+ export interface MemeTemplateOptions {
12
+ templateKey: string;
13
+ topText?: string;
14
+ bottomText?: string;
15
+ style?: string; // e.g. 'black' for black text, or custom style
16
+ width?: number;
17
+ height?: number;
18
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Image Domain - Core Type Definitions
3
+ */
4
+
5
+ export enum ImageFormat {
6
+ JPEG = 'jpeg',
7
+ PNG = 'png',
8
+ WEBP = 'webp',
9
+ }
10
+
11
+ export type SaveFormat = 'jpeg' | 'png' | 'webp';
12
+
13
+ export enum ImageOrientation {
14
+ PORTRAIT = 'portrait',
15
+ LANDSCAPE = 'landscape',
16
+ SQUARE = 'square',
17
+ }
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
+
36
+ export interface ImageManipulateAction {
37
+ resize?: Partial<ImageDimensions>;
38
+ crop?: ImageCropArea;
39
+ rotate?: number;
40
+ flip?: ImageFlipOptions;
41
+ }
42
+
43
+ export interface ImageSaveOptions {
44
+ format?: SaveFormat;
45
+ compress?: number;
46
+ base64?: boolean;
47
+ }
48
+
49
+ export interface ImageManipulationResult {
50
+ uri: string;
51
+ width: number;
52
+ height: number;
53
+ base64?: string;
54
+ }
55
+
56
+ export interface ImageMetadata extends ImageDimensions {
57
+ uri: string;
58
+ format?: ImageFormat;
59
+ size?: number;
60
+ orientation?: ImageOrientation;
61
+ }
62
+
63
+ export interface ImageViewerItem {
64
+ uri: string;
65
+ title?: string;
66
+ description?: string;
67
+ width?: number;
68
+ height?: number;
69
+ }
70
+
71
+ export interface ImageGalleryOptions {
72
+ index?: number;
73
+ backgroundColor?: string;
74
+ swipeToCloseEnabled?: boolean;
75
+ doubleTapToZoomEnabled?: boolean;
76
+ onDismiss?: () => void;
77
+ onIndexChange?: (index: number) => void;
78
+ }
79
+
80
+ export interface ImageOperationResult {
81
+ success: boolean;
82
+ uri?: string;
83
+ error?: string;
84
+ width?: number;
85
+ height?: number;
86
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Validation Result Interface
3
+ * Represents the outcome of a validation operation
4
+ */
5
+ export interface ValidationResult {
6
+ /**
7
+ * Whether the validation passed
8
+ */
9
+ isValid: boolean;
10
+
11
+ /**
12
+ * Error message if validation failed
13
+ */
14
+ error?: string;
15
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Domain - Editor Configuration Types
3
+ */
4
+
5
+ export interface EditorOptions {
6
+ maxLayers?: number;
7
+ maxHistory?: number;
8
+ enableUndo?: boolean;
9
+ enableRedo?: boolean;
10
+ enableFilters?: boolean;
11
+ enableShapes?: boolean;
12
+ enableText?: boolean;
13
+ enableStickers?: boolean;
14
+ enableCrop?: boolean;
15
+ brushSizeRange?: [number, number];
16
+ strokeWidthRange?: [number, number];
17
+ fontSizeRange?: [number, number];
18
+ defaultColors?: string[];
19
+ stickerPacks?: string[];
20
+ customFonts?: string[];
21
+ }
22
+
23
+ export interface EditorExportOptions {
24
+ format: 'jpeg' | 'png' | 'webp';
25
+ quality: number;
26
+ backgroundColor?: string;
27
+ includeHiddenLayers?: boolean;
28
+ flattenLayers?: boolean;
29
+ maxSize?: number;
30
+ }
31
+
32
+ export interface EditorEvent {
33
+ type: 'toolChange' | 'layerAdd' | 'layerRemove' | 'layerUpdate' | 'selectionChange' | 'historyChange';
34
+ data: unknown;
35
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Domain - Editor Element Types
3
+ */
4
+
5
+ import type { ShapeType } from './EditorToolTypes';
6
+
7
+ export interface EditorPoint {
8
+ x: number;
9
+ y: number;
10
+ }
11
+
12
+ export interface EditorDimensions {
13
+ width: number;
14
+ height: number;
15
+ }
16
+
17
+ export interface EditorStroke {
18
+ points: EditorPoint[];
19
+ color: string;
20
+ size: number;
21
+ style: string;
22
+ opacity: number;
23
+ }
24
+
25
+ export interface EditorShape {
26
+ type: ShapeType;
27
+ startPoint: EditorPoint;
28
+ endPoint: EditorPoint;
29
+ color: string;
30
+ strokeWidth: number;
31
+ fillColor?: string;
32
+ opacity: number;
33
+ rotation?: number;
34
+ }
35
+
36
+ export interface EditorText {
37
+ id: string;
38
+ text: string;
39
+ position: EditorPoint;
40
+ fontSize: number;
41
+ fontFamily: string;
42
+ color: string;
43
+ backgroundColor?: string;
44
+ rotation: number;
45
+ opacity: number;
46
+ maxWidth?: number;
47
+ textAlign: 'left' | 'center' | 'right';
48
+ fontWeight: 'normal' | 'bold';
49
+ fontStyle: 'normal' | 'italic';
50
+ }
51
+
52
+ export interface EditorSticker {
53
+ id: string;
54
+ uri: string;
55
+ position: EditorPoint;
56
+ size: EditorDimensions;
57
+ rotation: number;
58
+ opacity: number;
59
+ scale: number;
60
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Domain - Editor Filter Types
3
+ */
4
+
5
+ export interface EditorFilter {
6
+ type: string;
7
+ intensity: number;
8
+ preview?: string;
9
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Domain - Editor Layer Types
3
+ */
4
+
5
+ import type { EditorStroke, EditorShape, EditorText, EditorSticker } from './EditorElementTypes';
6
+
7
+ export interface EditorLayer {
8
+ id: string;
9
+ name: string;
10
+ visible: boolean;
11
+ opacity: number;
12
+ locked: boolean;
13
+ elements: Array<{
14
+ type: 'stroke' | 'shape' | 'text' | 'sticker';
15
+ data: EditorStroke | EditorShape | EditorText | EditorSticker;
16
+ }>;
17
+ }
18
+
19
+ export interface EditorHistory {
20
+ id: string;
21
+ timestamp: Date;
22
+ layers: EditorLayer[];
23
+ thumbnail?: string;
24
+ }
25
+
26
+ export interface EditorSelection {
27
+ bounds: {
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ elements: string[];
34
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Domain - Editor State Types
3
+ */
4
+
5
+ import type { EditorTool } from './EditorToolTypes';
6
+ import type { EditorLayer } from './EditorLayerTypes';
7
+ import type { EditorSelection } from './EditorLayerTypes';
8
+ import type { EditorHistory } from './EditorLayerTypes';
9
+ import type { EditorPoint, EditorDimensions } from './EditorElementTypes';
10
+ import type { EditorFilter } from './EditorFilterTypes';
11
+
12
+ export interface EditorCropArea {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ aspectRatio?: number;
18
+ }
19
+
20
+ export interface EditorState {
21
+ originalUri: string;
22
+ currentUri?: string;
23
+ tool: EditorTool;
24
+ selectedLayer?: string;
25
+ layers: EditorLayer[];
26
+ history: EditorHistory[];
27
+ historyIndex: number;
28
+ selection?: EditorSelection;
29
+ cropArea?: EditorCropArea;
30
+ activeFilter?: EditorFilter;
31
+ isDirty: boolean;
32
+ dimensions: EditorDimensions;
33
+ zoom: number;
34
+ pan: EditorPoint;
35
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Domain - Editor Tool Types
3
+ */
4
+
5
+ export enum EditorTool {
6
+ MOVE = 'move',
7
+ BRUSH = 'brush',
8
+ ERASER = 'eraser',
9
+ TEXT = 'text',
10
+ SHAPE = 'shape',
11
+ CROP = 'crop',
12
+ FILTER = 'filter',
13
+ STICKER = 'sticker',
14
+ SELECT = 'select',
15
+ }
16
+
17
+ export enum ShapeType {
18
+ RECTANGLE = 'rectangle',
19
+ CIRCLE = 'circle',
20
+ LINE = 'line',
21
+ ARROW = 'arrow',
22
+ TRIANGLE = 'triangle',
23
+ STAR = 'star',
24
+ HEART = 'heart',
25
+ }
26
+
27
+ export enum BrushStyle {
28
+ NORMAL = 'normal',
29
+ MARKER = 'marker',
30
+ SPRAY = 'spray',
31
+ PENCIL = 'pencil',
32
+ CALIGRAPHY = 'caligraphy',
33
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Image Domain - Utility Functions
3
+ */
4
+ import { ImageFormat, ImageOrientation, ImageDimensions, ImageCropArea } from "../entities/ImageTypes";
5
+ import { IMAGE_CONSTANTS } from "../entities/ImageConstants";
6
+
7
+ export class ImageUtils {
8
+ static getOrientation(width: number, height: number): ImageOrientation {
9
+ if (width > height) return ImageOrientation.LANDSCAPE;
10
+ if (height > width) return ImageOrientation.PORTRAIT;
11
+ return ImageOrientation.SQUARE;
12
+ }
13
+
14
+ static getAspectRatio(width: number, height: number): number {
15
+ return width / height;
16
+ }
17
+
18
+ static fitToSize(
19
+ width: number,
20
+ height: number,
21
+ maxWidth: number,
22
+ maxHeight: number
23
+ ): ImageDimensions {
24
+ const aspectRatio = ImageUtils.getAspectRatio(width, height);
25
+ let newWidth = width;
26
+ let newHeight = height;
27
+
28
+ if (width > maxWidth) {
29
+ newWidth = maxWidth;
30
+ newHeight = newWidth / aspectRatio;
31
+ }
32
+
33
+ if (newHeight > maxHeight) {
34
+ newHeight = maxHeight;
35
+ newWidth = newHeight * aspectRatio;
36
+ }
37
+
38
+ return {
39
+ width: Math.round(newWidth),
40
+ height: Math.round(newHeight),
41
+ };
42
+ }
43
+
44
+ static getThumbnailSize(
45
+ width: number,
46
+ height: number,
47
+ thumbnailSize: number = IMAGE_CONSTANTS.thumbnailSize
48
+ ): ImageDimensions {
49
+ return ImageUtils.fitToSize(width, height, thumbnailSize, thumbnailSize);
50
+ }
51
+
52
+ static isValidImageUri(uri: string): boolean {
53
+ if (!uri) return false;
54
+ return (
55
+ uri.startsWith('file://') ||
56
+ uri.startsWith('content://') ||
57
+ uri.startsWith('http://') ||
58
+ uri.startsWith('https://') ||
59
+ uri.startsWith('data:image/')
60
+ );
61
+ }
62
+
63
+ static getFormatFromUri(uri: string): ImageFormat | null {
64
+ const lowerUri = uri.toLowerCase();
65
+ if (lowerUri.includes('.jpg') || lowerUri.includes('.jpeg')) return ImageFormat.JPEG;
66
+ if (lowerUri.includes('.png')) return ImageFormat.PNG;
67
+ if (lowerUri.includes('.webp')) return ImageFormat.WEBP;
68
+ return null;
69
+ }
70
+
71
+ static getExtensionFromFormat(format: ImageFormat): string {
72
+ switch (format) {
73
+ case ImageFormat.JPEG: return 'jpg';
74
+ case ImageFormat.PNG: return 'png';
75
+ case ImageFormat.WEBP: return 'webp';
76
+ default: return 'jpg';
77
+ }
78
+ }
79
+
80
+ static getSquareCrop(width: number, height: number): ImageCropArea {
81
+ const size = Math.min(width, height);
82
+ const originX = (width - size) / 2;
83
+ const originY = (height - size) / 2;
84
+
85
+ return {
86
+ originX: Math.round(originX),
87
+ originY: Math.round(originY),
88
+ width: Math.round(size),
89
+ height: Math.round(size),
90
+ };
91
+ }
92
+
93
+ static formatFileSize(bytes: number): string {
94
+ if (bytes < 1024) return `${bytes} B`;
95
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
96
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
97
+ }
98
+
99
+ static needsCompression(bytes: number, maxSizeMB: number = 2): boolean {
100
+ const maxBytes = maxSizeMB * 1024 * 1024;
101
+ return bytes > maxBytes;
102
+ }
103
+ }