@umituz/react-native-photo-editor 1.1.1 → 2.0.1

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.
@@ -1,65 +1,121 @@
1
- import React, { useState, useRef, useCallback } from "react";
1
+ import React, { useState, useRef, useCallback, useEffect } from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
- import {
5
- AtomicText,
6
- useAppDesignTokens,
7
- } from "@umituz/react-native-design-system";
4
+ import { Image } from "expo-image";
5
+ import { AtomicText } from "@umituz/react-native-design-system/atoms";
6
+ import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
7
+ import type { LayerTransform } from "./DraggableText";
8
8
 
9
9
  interface DraggableStickerProps {
10
10
  uri: string;
11
11
  initialX: number;
12
12
  initialY: number;
13
- onDragEnd: (x: number, y: number) => void;
14
- onPress: () => void;
15
- isSelected?: boolean;
16
13
  rotation?: number;
17
14
  scale?: number;
18
15
  opacity?: number;
16
+ onTransformEnd: (transform: LayerTransform) => void;
17
+ onPress: () => void;
18
+ isSelected?: boolean;
19
19
  }
20
20
 
21
+ const isEmojiString = (str: string) =>
22
+ str.length <= 4 && !/^https?:\/\//i.test(str) && !str.startsWith("/");
23
+
21
24
  export const DraggableSticker: React.FC<DraggableStickerProps> = ({
22
25
  uri,
23
26
  initialX,
24
27
  initialY,
25
- onDragEnd,
28
+ rotation: rotationProp = 0,
29
+ scale: scaleProp = 1,
30
+ opacity = 1,
31
+ onTransformEnd,
26
32
  onPress,
27
33
  isSelected,
28
- rotation = 0,
29
- scale = 1,
30
- opacity = 1,
31
34
  }) => {
32
35
  const tokens = useAppDesignTokens();
33
36
  const [position, setPosition] = useState({ x: initialX, y: initialY });
37
+ const [scale, setScale] = useState(scaleProp);
38
+ const [rotation, setRotation] = useState(rotationProp);
39
+
40
+ // Sync when props change (e.g., undo/redo)
41
+ useEffect(() => { setPosition({ x: initialX, y: initialY }); }, [initialX, initialY]);
42
+ useEffect(() => { setScale(scaleProp); }, [scaleProp]);
43
+ useEffect(() => { setRotation(rotationProp); }, [rotationProp]);
44
+
45
+ const positionRef = useRef(position);
46
+ positionRef.current = position;
47
+ const scaleRef = useRef(scale);
48
+ scaleRef.current = scale;
49
+ const rotationRef = useRef(rotation);
50
+ rotationRef.current = rotation;
51
+ const onTransformEndRef = useRef(onTransformEnd);
52
+ onTransformEndRef.current = onTransformEnd;
53
+ const onPressRef = useRef(onPress);
54
+ onPressRef.current = onPress;
55
+
34
56
  const offsetRef = useRef({ x: initialX, y: initialY });
57
+ const scaleStartRef = useRef(scaleProp);
58
+ const rotationStartRef = useRef(rotationProp);
35
59
 
36
- const handleDragEnd = useCallback(() => {
37
- onDragEnd(position.x, position.y);
38
- }, [position.x, position.y, onDragEnd]);
60
+ const emitTransform = useCallback(() => {
61
+ onTransformEndRef.current({
62
+ x: positionRef.current.x,
63
+ y: positionRef.current.y,
64
+ scale: scaleRef.current,
65
+ rotation: rotationRef.current,
66
+ });
67
+ }, []);
39
68
 
40
69
  const panGesture = Gesture.Pan()
70
+ .runOnJS(true)
71
+ .averageTouches(true)
41
72
  .onStart(() => {
42
- offsetRef.current = { x: position.x, y: position.y };
73
+ offsetRef.current = { x: positionRef.current.x, y: positionRef.current.y };
43
74
  })
44
- .onUpdate((event) => {
75
+ .onUpdate((e) => {
45
76
  setPosition({
46
- x: offsetRef.current.x + event.translationX,
47
- y: offsetRef.current.y + event.translationY,
77
+ x: offsetRef.current.x + e.translationX,
78
+ y: offsetRef.current.y + e.translationY,
48
79
  });
49
80
  })
50
- .onEnd(() => {
51
- handleDragEnd();
52
- });
81
+ .onEnd(emitTransform);
82
+
83
+ const pinchGesture = Gesture.Pinch()
84
+ .runOnJS(true)
85
+ .onStart(() => {
86
+ scaleStartRef.current = scaleRef.current;
87
+ })
88
+ .onUpdate((e) => {
89
+ setScale(Math.max(0.2, Math.min(6, scaleStartRef.current * e.scale)));
90
+ })
91
+ .onEnd(emitTransform);
53
92
 
54
- const tapGesture = Gesture.Tap().onEnd(() => {
55
- onPress();
56
- });
93
+ const rotationGesture = Gesture.Rotation()
94
+ .runOnJS(true)
95
+ .onStart(() => {
96
+ rotationStartRef.current = rotationRef.current;
97
+ })
98
+ .onUpdate((e) => {
99
+ setRotation(rotationStartRef.current + (e.rotation * 180) / Math.PI);
100
+ })
101
+ .onEnd(emitTransform);
102
+
103
+ const tapGesture = Gesture.Tap()
104
+ .runOnJS(true)
105
+ .onEnd(() => onPressRef.current());
106
+
107
+ const composed = Gesture.Exclusive(
108
+ Gesture.Simultaneous(panGesture, pinchGesture, rotationGesture),
109
+ tapGesture,
110
+ );
57
111
 
58
- const isEmoji = uri.length <= 4 && !uri.startsWith("http");
112
+ const isEmoji = isEmojiString(uri);
59
113
 
60
114
  return (
61
- <GestureDetector gesture={Gesture.Exclusive(panGesture, tapGesture)}>
115
+ <GestureDetector gesture={composed}>
62
116
  <View
117
+ accessibilityLabel={isEmoji ? `Sticker ${uri}` : "Image sticker"}
118
+ accessibilityRole="button"
63
119
  style={[
64
120
  styles.container,
65
121
  {
@@ -86,7 +142,14 @@ export const DraggableSticker: React.FC<DraggableStickerProps> = ({
86
142
  >
87
143
  {isEmoji ? (
88
144
  <AtomicText style={{ fontSize: 48 }}>{uri}</AtomicText>
89
- ) : null}
145
+ ) : (
146
+ <Image
147
+ source={{ uri }}
148
+ style={{ width: 80, height: 80 }}
149
+ contentFit="contain"
150
+ accessibilityIgnoresInvertColors
151
+ />
152
+ )}
90
153
  </View>
91
154
  </View>
92
155
  </GestureDetector>
@@ -94,7 +157,5 @@ export const DraggableSticker: React.FC<DraggableStickerProps> = ({
94
157
  };
95
158
 
96
159
  const styles = StyleSheet.create({
97
- container: {
98
- position: "absolute",
99
- },
160
+ container: { position: "absolute" },
100
161
  });
@@ -1,10 +1,15 @@
1
- import React, { useState, useRef, useCallback } from "react";
1
+ import React, { useState, useRef, useCallback, useEffect } from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
- import {
5
- AtomicText,
6
- useAppDesignTokens,
7
- } from "@umituz/react-native-design-system";
4
+ import { AtomicText } from "@umituz/react-native-design-system/atoms";
5
+ import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
6
+
7
+ export interface LayerTransform {
8
+ x: number;
9
+ y: number;
10
+ scale: number;
11
+ rotation: number;
12
+ }
8
13
 
9
14
  interface DraggableTextProps {
10
15
  text: string;
@@ -13,16 +18,16 @@ interface DraggableTextProps {
13
18
  fontFamily?: string;
14
19
  initialX: number;
15
20
  initialY: number;
16
- onDragEnd: (x: number, y: number) => void;
17
- onPress: () => void;
18
- isSelected?: boolean;
19
21
  rotation?: number;
20
22
  scale?: number;
21
23
  opacity?: number;
22
24
  textAlign?: "center" | "left" | "right";
23
25
  backgroundColor?: string;
24
- _strokeColor?: string;
25
- _strokeWidth?: number;
26
+ isBold?: boolean;
27
+ isItalic?: boolean;
28
+ onTransformEnd: (transform: LayerTransform) => void;
29
+ onPress: () => void;
30
+ isSelected?: boolean;
26
31
  }
27
32
 
28
33
  export const DraggableText: React.FC<DraggableTextProps> = ({
@@ -32,44 +37,101 @@ export const DraggableText: React.FC<DraggableTextProps> = ({
32
37
  fontFamily = "System",
33
38
  initialX,
34
39
  initialY,
35
- onDragEnd,
36
- onPress,
37
- isSelected,
38
- rotation = 0,
39
- scale = 1,
40
+ rotation: rotationProp = 0,
41
+ scale: scaleProp = 1,
40
42
  opacity = 1,
41
43
  textAlign = "center",
42
44
  backgroundColor = "transparent",
45
+ isBold = false,
46
+ isItalic = false,
47
+ onTransformEnd,
48
+ onPress,
49
+ isSelected,
43
50
  }) => {
44
51
  const tokens = useAppDesignTokens();
45
52
  const [position, setPosition] = useState({ x: initialX, y: initialY });
53
+ const [scale, setScale] = useState(scaleProp);
54
+ const [rotation, setRotation] = useState(rotationProp); // degrees
55
+
56
+ // Sync when props change (e.g., undo/redo)
57
+ useEffect(() => { setPosition({ x: initialX, y: initialY }); }, [initialX, initialY]);
58
+ useEffect(() => { setScale(scaleProp); }, [scaleProp]);
59
+ useEffect(() => { setRotation(rotationProp); }, [rotationProp]);
60
+
61
+ // Refs for gesture callbacks to avoid stale closures
62
+ const positionRef = useRef(position);
63
+ positionRef.current = position;
64
+ const scaleRef = useRef(scale);
65
+ scaleRef.current = scale;
66
+ const rotationRef = useRef(rotation);
67
+ rotationRef.current = rotation;
68
+ const onTransformEndRef = useRef(onTransformEnd);
69
+ onTransformEndRef.current = onTransformEnd;
70
+ const onPressRef = useRef(onPress);
71
+ onPressRef.current = onPress;
72
+
73
+ // Start-of-gesture saved values
46
74
  const offsetRef = useRef({ x: initialX, y: initialY });
75
+ const scaleStartRef = useRef(scale);
76
+ const rotationStartRef = useRef(rotation); // degrees
47
77
 
48
- const handleDragEnd = useCallback(() => {
49
- onDragEnd(position.x, position.y);
50
- }, [position.x, position.y, onDragEnd]);
78
+ const emitTransform = useCallback(() => {
79
+ onTransformEndRef.current({
80
+ x: positionRef.current.x,
81
+ y: positionRef.current.y,
82
+ scale: scaleRef.current,
83
+ rotation: rotationRef.current,
84
+ });
85
+ }, []);
51
86
 
52
87
  const panGesture = Gesture.Pan()
88
+ .runOnJS(true)
89
+ .averageTouches(true)
53
90
  .onStart(() => {
54
- offsetRef.current = { x: position.x, y: position.y };
91
+ offsetRef.current = { x: positionRef.current.x, y: positionRef.current.y };
55
92
  })
56
- .onUpdate((event) => {
93
+ .onUpdate((e) => {
57
94
  setPosition({
58
- x: offsetRef.current.x + event.translationX,
59
- y: offsetRef.current.y + event.translationY,
95
+ x: offsetRef.current.x + e.translationX,
96
+ y: offsetRef.current.y + e.translationY,
60
97
  });
61
98
  })
62
- .onEnd(() => {
63
- handleDragEnd();
64
- });
99
+ .onEnd(emitTransform);
100
+
101
+ const pinchGesture = Gesture.Pinch()
102
+ .runOnJS(true)
103
+ .onStart(() => {
104
+ scaleStartRef.current = scaleRef.current;
105
+ })
106
+ .onUpdate((e) => {
107
+ setScale(Math.max(0.2, Math.min(6, scaleStartRef.current * e.scale)));
108
+ })
109
+ .onEnd(emitTransform);
65
110
 
66
- const tapGesture = Gesture.Tap().onEnd(() => {
67
- onPress();
68
- });
111
+ const rotationGesture = Gesture.Rotation()
112
+ .runOnJS(true)
113
+ .onStart(() => {
114
+ rotationStartRef.current = rotationRef.current;
115
+ })
116
+ .onUpdate((e) => {
117
+ setRotation(rotationStartRef.current + (e.rotation * 180) / Math.PI);
118
+ })
119
+ .onEnd(emitTransform);
120
+
121
+ const tapGesture = Gesture.Tap()
122
+ .runOnJS(true)
123
+ .onEnd(() => onPressRef.current());
124
+
125
+ const composed = Gesture.Exclusive(
126
+ Gesture.Simultaneous(panGesture, pinchGesture, rotationGesture),
127
+ tapGesture,
128
+ );
69
129
 
70
130
  return (
71
- <GestureDetector gesture={Gesture.Exclusive(panGesture, tapGesture)}>
131
+ <GestureDetector gesture={composed}>
72
132
  <View
133
+ accessibilityLabel={text || "Text layer"}
134
+ accessibilityRole="button"
73
135
  style={[
74
136
  styles.container,
75
137
  {
@@ -91,16 +153,19 @@ export const DraggableText: React.FC<DraggableTextProps> = ({
91
153
  borderWidth: isSelected ? 2 : 0,
92
154
  borderColor: tokens.colors.primary,
93
155
  borderStyle: "dashed",
94
- backgroundColor: isSelected ? tokens.colors.primary + "10" : backgroundColor,
156
+ backgroundColor: isSelected
157
+ ? tokens.colors.primary + "10"
158
+ : backgroundColor,
95
159
  }}
96
160
  >
97
161
  <AtomicText
98
- fontWeight="900"
99
162
  style={{
100
163
  fontSize,
101
- fontFamily,
164
+ fontFamily: fontFamily === "System" ? undefined : fontFamily,
102
165
  color,
103
166
  textAlign,
167
+ fontWeight: isBold ? "900" : "normal",
168
+ fontStyle: isItalic ? "italic" : "normal",
104
169
  }}
105
170
  >
106
171
  {text || "TAP TO EDIT"}
@@ -112,7 +177,5 @@ export const DraggableText: React.FC<DraggableTextProps> = ({
112
177
  };
113
178
 
114
179
  const styles = StyleSheet.create({
115
- container: {
116
- position: "absolute",
117
- },
180
+ container: { position: "absolute" },
118
181
  });
@@ -1,16 +1,17 @@
1
1
  import React from "react";
2
- import { View } from "react-native";
2
+ import { View, StyleSheet } from "react-native";
3
3
  import { Image } from "expo-image";
4
- import { DraggableText } from "./DraggableText";
4
+ import { DraggableText, LayerTransform } from "./DraggableText";
5
5
  import { DraggableSticker } from "./DraggableSticker";
6
- import { Layer, TextLayer, StickerLayer } from "../types";
6
+ import { Layer, TextLayer, StickerLayer, ImageFilters } from "../types";
7
7
 
8
8
  interface EditorCanvasProps {
9
9
  imageUrl: string;
10
10
  layers: Layer[];
11
11
  activeLayerId: string | null;
12
+ filters: ImageFilters;
12
13
  onLayerTap: (layerId: string) => void;
13
- onLayerMove: (layerId: string, x: number, y: number) => void;
14
+ onLayerTransform: (layerId: string, transform: LayerTransform) => void;
14
15
  styles: {
15
16
  canvas: object;
16
17
  canvasImage: object;
@@ -21,17 +22,42 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
21
22
  imageUrl,
22
23
  layers,
23
24
  activeLayerId,
25
+ filters,
24
26
  onLayerTap,
25
- onLayerMove,
26
- styles,
27
+ onLayerTransform,
28
+ styles: externalStyles,
27
29
  }) => {
30
+ // Basic brightness preview: dark overlay for < 1, light for > 1
31
+ const brightness = filters.brightness ?? 1;
32
+ const brightnessOverlay =
33
+ brightness < 1
34
+ ? { color: "black", opacity: Math.min(0.6, 1 - brightness) }
35
+ : brightness > 1
36
+ ? { color: "white", opacity: Math.min(0.4, brightness - 1) }
37
+ : null;
38
+
28
39
  return (
29
- <View style={styles.canvas}>
40
+ <View style={externalStyles.canvas}>
30
41
  <Image
31
42
  source={{ uri: imageUrl }}
32
- style={styles.canvasImage}
43
+ style={externalStyles.canvasImage}
33
44
  contentFit="cover"
34
45
  />
46
+
47
+ {/* Brightness visual overlay */}
48
+ {brightnessOverlay && (
49
+ <View
50
+ style={[
51
+ StyleSheet.absoluteFill,
52
+ {
53
+ backgroundColor: brightnessOverlay.color,
54
+ opacity: brightnessOverlay.opacity,
55
+ },
56
+ ]}
57
+ pointerEvents="none"
58
+ />
59
+ )}
60
+
35
61
  {layers.map((layer) => {
36
62
  if (layer.type === "text") {
37
63
  const textLayer = layer as TextLayer;
@@ -47,11 +73,11 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
47
73
  scale={textLayer.scale}
48
74
  opacity={textLayer.opacity}
49
75
  backgroundColor={textLayer.backgroundColor}
50
- _strokeColor={textLayer.strokeColor}
51
- _strokeWidth={textLayer.strokeWidth}
76
+ isBold={textLayer.isBold}
77
+ isItalic={textLayer.isItalic}
52
78
  initialX={textLayer.x}
53
79
  initialY={textLayer.y}
54
- onDragEnd={(x, y) => onLayerMove(layer.id, x, y)}
80
+ onTransformEnd={(t) => onLayerTransform(layer.id, t)}
55
81
  onPress={() => onLayerTap(layer.id)}
56
82
  isSelected={activeLayerId === layer.id}
57
83
  />
@@ -67,7 +93,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
67
93
  rotation={stickerLayer.rotation}
68
94
  scale={stickerLayer.scale}
69
95
  opacity={stickerLayer.opacity}
70
- onDragEnd={(x, y) => onLayerMove(layer.id, x, y)}
96
+ onTransformEnd={(t) => onLayerTransform(layer.id, t)}
71
97
  onPress={() => onLayerTap(layer.id)}
72
98
  isSelected={activeLayerId === layer.id}
73
99
  />
@@ -1,80 +1,164 @@
1
1
  import React from "react";
2
2
  import { View, TouchableOpacity } from "react-native";
3
- import {
4
- AtomicText,
5
- AtomicIcon,
6
- useAppDesignTokens
7
- } from "@umituz/react-native-design-system";
3
+ import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
4
+ import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
8
5
 
9
6
  interface EditorToolbarProps {
10
7
  onAddText: () => void;
11
8
  onAddSticker?: () => void;
12
- onAIMagic?: () => void;
13
9
  onOpenFilters?: () => void;
10
+ onOpenAdjustments?: () => void;
14
11
  onOpenLayers: () => void;
15
- styles: Record<string, object>;
12
+ onAIMagic?: () => void;
13
+ onUndo?: () => void;
14
+ onRedo?: () => void;
15
+ canUndo?: boolean;
16
+ canRedo?: boolean;
16
17
  t: (key: string) => string;
18
+ styles: {
19
+ bottomToolbar: object;
20
+ toolButton: object;
21
+ toolButtonActive: object;
22
+ aiMagicButton: object;
23
+ [key: string]: object;
24
+ };
17
25
  }
18
26
 
27
+ const ToolButton = ({
28
+ icon,
29
+ label,
30
+ onPress,
31
+ isActive,
32
+ disabled,
33
+ tokens,
34
+ parentStyles,
35
+ }: {
36
+ icon: string;
37
+ label: string;
38
+ onPress: () => void;
39
+ isActive?: boolean;
40
+ disabled?: boolean;
41
+ tokens: ReturnType<typeof useAppDesignTokens>;
42
+ parentStyles: EditorToolbarProps["styles"];
43
+ }) => (
44
+ <TouchableOpacity
45
+ style={[parentStyles.toolButton, isActive && parentStyles.toolButtonActive]}
46
+ onPress={onPress}
47
+ disabled={disabled}
48
+ accessibilityLabel={label}
49
+ accessibilityRole="button"
50
+ accessibilityState={{ selected: isActive, disabled }}
51
+ >
52
+ <AtomicIcon
53
+ name={icon as "edit"}
54
+ size="md"
55
+ color={disabled ? "textSecondary" : isActive ? "primary" : "textSecondary"}
56
+ />
57
+ <AtomicText
58
+ type="labelSmall"
59
+ color={disabled ? "textSecondary" : isActive ? "primary" : "textSecondary"}
60
+ >
61
+ {label}
62
+ </AtomicText>
63
+ </TouchableOpacity>
64
+ );
65
+
19
66
  export const EditorToolbar: React.FC<EditorToolbarProps> = ({
20
67
  onAddText,
21
68
  onAddSticker,
22
- onAIMagic,
23
69
  onOpenFilters,
70
+ onOpenAdjustments,
24
71
  onOpenLayers,
25
- styles,
72
+ onAIMagic,
73
+ onUndo,
74
+ onRedo,
75
+ canUndo = false,
76
+ canRedo = false,
77
+ styles: parentStyles,
26
78
  t,
27
79
  }) => {
28
80
  const tokens = useAppDesignTokens();
29
81
 
30
- const ToolButton = ({
31
- icon,
32
- label,
33
- onPress,
34
- isActive
35
- }: {
36
- icon: string;
37
- label: string;
38
- onPress: () => void;
39
- isActive?: boolean;
40
- }) => (
41
- <TouchableOpacity
42
- style={[styles.toolButton, isActive && styles.toolButtonActive]}
43
- onPress={onPress}
44
- >
45
- <AtomicIcon
46
- name={icon}
47
- size="md"
48
- color={isActive ? "primary" : "textSecondary"}
82
+ return (
83
+ <View style={parentStyles.bottomToolbar}>
84
+ {onUndo && (
85
+ <ToolButton
86
+ icon="arrow-back"
87
+ label={t("editor.undo") || "Undo"}
88
+ onPress={onUndo}
89
+ disabled={!canUndo}
90
+ tokens={tokens}
91
+ parentStyles={parentStyles}
92
+ />
93
+ )}
94
+
95
+ <ToolButton
96
+ icon="edit"
97
+ label={t("editor.text") || "Text"}
98
+ onPress={onAddText}
99
+ tokens={tokens}
100
+ parentStyles={parentStyles}
49
101
  />
50
- <AtomicText
51
- type="labelSmall"
52
- color={isActive ? "primary" : "textSecondary"}
53
- >
54
- {label}
55
- </AtomicText>
56
- </TouchableOpacity>
57
- );
58
102
 
59
- return (
60
- <View style={styles.bottomToolbar}>
61
- <ToolButton icon="text" label="Text" onPress={onAddText} />
62
-
63
103
  {onAddSticker && (
64
- <ToolButton icon="happy" label={t("editor.sticker")} onPress={onAddSticker} />
104
+ <ToolButton
105
+ icon="sparkles"
106
+ label={t("editor.sticker") || "Sticker"}
107
+ onPress={onAddSticker}
108
+ tokens={tokens}
109
+ parentStyles={parentStyles}
110
+ />
65
111
  )}
66
-
112
+
67
113
  {onAIMagic && (
68
- <TouchableOpacity style={styles.aiMagicButton} onPress={onAIMagic}>
114
+ <TouchableOpacity
115
+ style={parentStyles.aiMagicButton}
116
+ onPress={onAIMagic}
117
+ accessibilityLabel="AI Magic"
118
+ accessibilityRole="button"
119
+ >
69
120
  <AtomicIcon name="sparkles" size="lg" customColor={tokens.colors.onPrimary} />
70
121
  </TouchableOpacity>
71
122
  )}
72
123
 
124
+ {onOpenAdjustments && (
125
+ <ToolButton
126
+ icon="flash"
127
+ label={t("editor.adjust") || "Adjust"}
128
+ onPress={onOpenAdjustments}
129
+ tokens={tokens}
130
+ parentStyles={parentStyles}
131
+ />
132
+ )}
133
+
73
134
  {onOpenFilters && (
74
- <ToolButton icon="color-filter" label={t("editor.filters")} onPress={onOpenFilters} />
135
+ <ToolButton
136
+ icon="brush"
137
+ label={t("editor.filters") || "Filters"}
138
+ onPress={onOpenFilters}
139
+ tokens={tokens}
140
+ parentStyles={parentStyles}
141
+ />
142
+ )}
143
+
144
+ <ToolButton
145
+ icon="copy"
146
+ label={t("editor.layers") || "Layers"}
147
+ onPress={onOpenLayers}
148
+ tokens={tokens}
149
+ parentStyles={parentStyles}
150
+ />
151
+
152
+ {onRedo && (
153
+ <ToolButton
154
+ icon="chevron-forward"
155
+ label={t("editor.redo") || "Redo"}
156
+ onPress={onRedo}
157
+ disabled={!canRedo}
158
+ tokens={tokens}
159
+ parentStyles={parentStyles}
160
+ />
75
161
  )}
76
-
77
- <ToolButton icon="layers" label="Layers" onPress={onOpenLayers} />
78
162
  </View>
79
163
  );
80
164
  };