@umituz/react-native-photo-editor 2.0.1 → 2.0.2
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 +1 -1
- package/src/components/AIMagicSheet.tsx +3 -3
- package/src/components/EditorCanvas.tsx +20 -22
- package/src/components/EditorToolbar.tsx +0 -9
- package/src/components/FilterPicker.tsx +3 -3
- package/src/components/FontControls.tsx +3 -3
- package/src/components/LayerManager.tsx +3 -3
- package/src/components/StickerPicker.tsx +3 -3
- package/src/components/TextEditorSheet.tsx +3 -3
- package/src/hooks/useImagePicker.ts +5 -5
- package/src/hooks/usePhotoEditorUI.ts +0 -3
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useState, useMemo } from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicIcon, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -21,7 +21,7 @@ export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
|
|
|
21
21
|
const [selected, setSelected] = useState<string | null>(null);
|
|
22
22
|
const [loading, setLoading] = useState(false);
|
|
23
23
|
|
|
24
|
-
const styles = StyleSheet.create({
|
|
24
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
25
25
|
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
26
26
|
header: { flexDirection: "row", alignItems: "center", gap: tokens.spacing.sm },
|
|
27
27
|
grid: { gap: tokens.spacing.sm },
|
|
@@ -39,7 +39,7 @@ export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
|
|
|
39
39
|
backgroundColor: tokens.colors.primary + "10",
|
|
40
40
|
},
|
|
41
41
|
info: { flex: 1, marginLeft: tokens.spacing.sm },
|
|
42
|
-
});
|
|
42
|
+
}), [tokens]);
|
|
43
43
|
|
|
44
44
|
const handleGenerate = async () => {
|
|
45
45
|
if (!selected || !onGenerateCaption) return;
|
|
@@ -3,7 +3,7 @@ import { View, StyleSheet } from "react-native";
|
|
|
3
3
|
import { Image } from "expo-image";
|
|
4
4
|
import { DraggableText, LayerTransform } from "./DraggableText";
|
|
5
5
|
import { DraggableSticker } from "./DraggableSticker";
|
|
6
|
-
import { Layer,
|
|
6
|
+
import { Layer, ImageFilters } from "../types";
|
|
7
7
|
|
|
8
8
|
interface EditorCanvasProps {
|
|
9
9
|
imageUrl: string;
|
|
@@ -60,39 +60,37 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|
|
60
60
|
|
|
61
61
|
{layers.map((layer) => {
|
|
62
62
|
if (layer.type === "text") {
|
|
63
|
-
const textLayer = layer as TextLayer;
|
|
64
63
|
return (
|
|
65
64
|
<DraggableText
|
|
66
65
|
key={layer.id}
|
|
67
|
-
text={
|
|
68
|
-
color={
|
|
69
|
-
fontSize={
|
|
70
|
-
fontFamily={
|
|
71
|
-
textAlign={
|
|
72
|
-
rotation={
|
|
73
|
-
scale={
|
|
74
|
-
opacity={
|
|
75
|
-
backgroundColor={
|
|
76
|
-
isBold={
|
|
77
|
-
isItalic={
|
|
78
|
-
initialX={
|
|
79
|
-
initialY={
|
|
66
|
+
text={layer.text || "Tap to edit"}
|
|
67
|
+
color={layer.color}
|
|
68
|
+
fontSize={layer.fontSize}
|
|
69
|
+
fontFamily={layer.fontFamily}
|
|
70
|
+
textAlign={layer.textAlign}
|
|
71
|
+
rotation={layer.rotation}
|
|
72
|
+
scale={layer.scale}
|
|
73
|
+
opacity={layer.opacity}
|
|
74
|
+
backgroundColor={layer.backgroundColor}
|
|
75
|
+
isBold={layer.isBold}
|
|
76
|
+
isItalic={layer.isItalic}
|
|
77
|
+
initialX={layer.x}
|
|
78
|
+
initialY={layer.y}
|
|
80
79
|
onTransformEnd={(t) => onLayerTransform(layer.id, t)}
|
|
81
80
|
onPress={() => onLayerTap(layer.id)}
|
|
82
81
|
isSelected={activeLayerId === layer.id}
|
|
83
82
|
/>
|
|
84
83
|
);
|
|
85
84
|
} else if (layer.type === "sticker") {
|
|
86
|
-
const stickerLayer = layer as StickerLayer;
|
|
87
85
|
return (
|
|
88
86
|
<DraggableSticker
|
|
89
87
|
key={layer.id}
|
|
90
|
-
uri={
|
|
91
|
-
initialX={
|
|
92
|
-
initialY={
|
|
93
|
-
rotation={
|
|
94
|
-
scale={
|
|
95
|
-
opacity={
|
|
88
|
+
uri={layer.uri}
|
|
89
|
+
initialX={layer.x}
|
|
90
|
+
initialY={layer.y}
|
|
91
|
+
rotation={layer.rotation}
|
|
92
|
+
scale={layer.scale}
|
|
93
|
+
opacity={layer.opacity}
|
|
96
94
|
onTransformEnd={(t) => onLayerTransform(layer.id, t)}
|
|
97
95
|
onPress={() => onLayerTap(layer.id)}
|
|
98
96
|
isSelected={activeLayerId === layer.id}
|
|
@@ -30,7 +30,6 @@ const ToolButton = ({
|
|
|
30
30
|
onPress,
|
|
31
31
|
isActive,
|
|
32
32
|
disabled,
|
|
33
|
-
tokens,
|
|
34
33
|
parentStyles,
|
|
35
34
|
}: {
|
|
36
35
|
icon: string;
|
|
@@ -38,7 +37,6 @@ const ToolButton = ({
|
|
|
38
37
|
onPress: () => void;
|
|
39
38
|
isActive?: boolean;
|
|
40
39
|
disabled?: boolean;
|
|
41
|
-
tokens: ReturnType<typeof useAppDesignTokens>;
|
|
42
40
|
parentStyles: EditorToolbarProps["styles"];
|
|
43
41
|
}) => (
|
|
44
42
|
<TouchableOpacity
|
|
@@ -87,7 +85,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
87
85
|
label={t("editor.undo") || "Undo"}
|
|
88
86
|
onPress={onUndo}
|
|
89
87
|
disabled={!canUndo}
|
|
90
|
-
tokens={tokens}
|
|
91
88
|
parentStyles={parentStyles}
|
|
92
89
|
/>
|
|
93
90
|
)}
|
|
@@ -96,7 +93,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
96
93
|
icon="edit"
|
|
97
94
|
label={t("editor.text") || "Text"}
|
|
98
95
|
onPress={onAddText}
|
|
99
|
-
tokens={tokens}
|
|
100
96
|
parentStyles={parentStyles}
|
|
101
97
|
/>
|
|
102
98
|
|
|
@@ -105,7 +101,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
105
101
|
icon="sparkles"
|
|
106
102
|
label={t("editor.sticker") || "Sticker"}
|
|
107
103
|
onPress={onAddSticker}
|
|
108
|
-
tokens={tokens}
|
|
109
104
|
parentStyles={parentStyles}
|
|
110
105
|
/>
|
|
111
106
|
)}
|
|
@@ -126,7 +121,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
126
121
|
icon="flash"
|
|
127
122
|
label={t("editor.adjust") || "Adjust"}
|
|
128
123
|
onPress={onOpenAdjustments}
|
|
129
|
-
tokens={tokens}
|
|
130
124
|
parentStyles={parentStyles}
|
|
131
125
|
/>
|
|
132
126
|
)}
|
|
@@ -136,7 +130,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
136
130
|
icon="brush"
|
|
137
131
|
label={t("editor.filters") || "Filters"}
|
|
138
132
|
onPress={onOpenFilters}
|
|
139
|
-
tokens={tokens}
|
|
140
133
|
parentStyles={parentStyles}
|
|
141
134
|
/>
|
|
142
135
|
)}
|
|
@@ -145,7 +138,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
145
138
|
icon="copy"
|
|
146
139
|
label={t("editor.layers") || "Layers"}
|
|
147
140
|
onPress={onOpenLayers}
|
|
148
|
-
tokens={tokens}
|
|
149
141
|
parentStyles={parentStyles}
|
|
150
142
|
/>
|
|
151
143
|
|
|
@@ -155,7 +147,6 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
155
147
|
label={t("editor.redo") || "Redo"}
|
|
156
148
|
onPress={onRedo}
|
|
157
149
|
disabled={!canRedo}
|
|
158
|
-
tokens={tokens}
|
|
159
150
|
parentStyles={parentStyles}
|
|
160
151
|
/>
|
|
161
152
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
2
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -17,7 +17,7 @@ export const FilterPicker: React.FC<FilterPickerProps> = ({
|
|
|
17
17
|
}) => {
|
|
18
18
|
const tokens = useAppDesignTokens();
|
|
19
19
|
|
|
20
|
-
const styles = StyleSheet.create({
|
|
20
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
21
21
|
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
22
22
|
grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
|
|
23
23
|
filter: {
|
|
@@ -34,7 +34,7 @@ export const FilterPicker: React.FC<FilterPickerProps> = ({
|
|
|
34
34
|
borderColor: tokens.colors.primary,
|
|
35
35
|
backgroundColor: tokens.colors.primary + "10",
|
|
36
36
|
},
|
|
37
|
-
});
|
|
37
|
+
}), [tokens]);
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<View style={styles.container}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -32,7 +32,7 @@ export const FontControls: React.FC<FontControlsProps> = ({
|
|
|
32
32
|
}) => {
|
|
33
33
|
const tokens = useAppDesignTokens();
|
|
34
34
|
|
|
35
|
-
const styles = StyleSheet.create({
|
|
35
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
36
36
|
stepRow: {
|
|
37
37
|
flexDirection: "row",
|
|
38
38
|
gap: tokens.spacing.sm,
|
|
@@ -58,7 +58,7 @@ export const FontControls: React.FC<FontControlsProps> = ({
|
|
|
58
58
|
alignItems: "center",
|
|
59
59
|
gap: tokens.spacing.xs,
|
|
60
60
|
},
|
|
61
|
-
});
|
|
61
|
+
}), [tokens]);
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
64
|
<View style={externalStyles.controlsPanel}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -27,7 +27,7 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
|
|
|
27
27
|
}) => {
|
|
28
28
|
const tokens = useAppDesignTokens();
|
|
29
29
|
|
|
30
|
-
const styles = StyleSheet.create({
|
|
30
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
31
31
|
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
32
32
|
item: {
|
|
33
33
|
flexDirection: "row",
|
|
@@ -53,7 +53,7 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
|
|
|
53
53
|
padding: tokens.spacing.xs,
|
|
54
54
|
borderRadius: tokens.borders.radius.sm,
|
|
55
55
|
},
|
|
56
|
-
});
|
|
56
|
+
}), [tokens]);
|
|
57
57
|
|
|
58
58
|
const sortedLayers = [...layers].reverse(); // top layer first in list
|
|
59
59
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -15,7 +15,7 @@ export const StickerPicker: React.FC<StickerPickerProps> = ({
|
|
|
15
15
|
}) => {
|
|
16
16
|
const tokens = useAppDesignTokens();
|
|
17
17
|
|
|
18
|
-
const styles = StyleSheet.create({
|
|
18
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
19
19
|
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
20
20
|
grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
|
|
21
21
|
sticker: {
|
|
@@ -26,7 +26,7 @@ export const StickerPicker: React.FC<StickerPickerProps> = ({
|
|
|
26
26
|
alignItems: "center",
|
|
27
27
|
justifyContent: "center",
|
|
28
28
|
},
|
|
29
|
-
});
|
|
29
|
+
}), [tokens]);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<View style={styles.container}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
2
|
import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
@@ -42,7 +42,7 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
|
|
|
42
42
|
}) => {
|
|
43
43
|
const tokens = useAppDesignTokens();
|
|
44
44
|
|
|
45
|
-
const styles = StyleSheet.create({
|
|
45
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
46
46
|
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
47
47
|
input: {
|
|
48
48
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
@@ -72,7 +72,7 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
|
|
|
72
72
|
borderColor: tokens.colors.primary,
|
|
73
73
|
backgroundColor: tokens.colors.primary + "20",
|
|
74
74
|
},
|
|
75
|
-
});
|
|
75
|
+
}), [tokens]);
|
|
76
76
|
|
|
77
77
|
return (
|
|
78
78
|
<View style={styles.container}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
2
|
import * as ImagePicker from "expo-image-picker";
|
|
3
3
|
|
|
4
4
|
export interface ImagePickerResult {
|
|
@@ -16,7 +16,7 @@ export interface UseImagePickerReturn {
|
|
|
16
16
|
export const useImagePicker = (): UseImagePickerReturn => {
|
|
17
17
|
const [loading, setLoading] = useState(false);
|
|
18
18
|
|
|
19
|
-
const pickFromGallery = async (
|
|
19
|
+
const pickFromGallery = useCallback(async (
|
|
20
20
|
options?: ImagePicker.ImagePickerOptions,
|
|
21
21
|
): Promise<ImagePickerResult | null> => {
|
|
22
22
|
setLoading(true);
|
|
@@ -39,9 +39,9 @@ export const useImagePicker = (): UseImagePickerReturn => {
|
|
|
39
39
|
} finally {
|
|
40
40
|
setLoading(false);
|
|
41
41
|
}
|
|
42
|
-
};
|
|
42
|
+
}, []);
|
|
43
43
|
|
|
44
|
-
const takePhoto = async (
|
|
44
|
+
const takePhoto = useCallback(async (
|
|
45
45
|
options?: ImagePicker.ImagePickerOptions,
|
|
46
46
|
): Promise<ImagePickerResult | null> => {
|
|
47
47
|
setLoading(true);
|
|
@@ -63,7 +63,7 @@ export const useImagePicker = (): UseImagePickerReturn => {
|
|
|
63
63
|
} finally {
|
|
64
64
|
setLoading(false);
|
|
65
65
|
}
|
|
66
|
-
};
|
|
66
|
+
}, []);
|
|
67
67
|
|
|
68
68
|
return { pickFromGallery, takePhoto, loading };
|
|
69
69
|
};
|
|
@@ -40,7 +40,6 @@ export const usePhotoEditorUI = (initialCaption?: string) => {
|
|
|
40
40
|
initialCaptionApplied.current = true;
|
|
41
41
|
editor.addTextLayer(tokens.colors.textPrimary, { text: initialCaption });
|
|
42
42
|
}
|
|
43
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
43
|
}, []);
|
|
45
44
|
|
|
46
45
|
const handleTextLayerTap = useCallback(
|
|
@@ -152,12 +151,10 @@ export const usePhotoEditorUI = (initialCaption?: string) => {
|
|
|
152
151
|
fontFamily: selectedFont,
|
|
153
152
|
});
|
|
154
153
|
textEditorSheetRef.current?.present();
|
|
155
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
154
|
}, [editor, fontSize, selectedFont, tokens.colors.textPrimary]),
|
|
157
155
|
handleSelectSticker: useCallback((uri: string) => {
|
|
158
156
|
editor.addStickerLayer(uri);
|
|
159
157
|
stickerSheetRef.current?.dismiss();
|
|
160
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
161
158
|
}, [editor]),
|
|
162
159
|
};
|
|
163
160
|
};
|