@umituz/react-native-video-editor 1.0.26 → 1.0.30
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 -2
- package/src/player/presentation/components/VideoPlayer.tsx +2 -3
- package/src/presentation/components/AudioEditor.tsx +15 -23
- package/src/presentation/components/EditorTimeline.tsx +1 -1
- package/src/presentation/components/EditorToolPanel.tsx +2 -2
- package/src/presentation/components/LayerActionsMenu.tsx +19 -12
- package/src/presentation/components/animation-layer/AnimationEditorActions.tsx +1 -1
- package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +1 -1
- package/src/presentation/components/audio-layer/AudioEditorActions.tsx +1 -1
- package/src/presentation/components/audio-layer/AudioFileSelector.tsx +7 -2
- package/src/presentation/components/audio-layer/FadeEffectsSelector.tsx +2 -2
- package/src/presentation/components/audio-layer/VolumeSelector.tsx +1 -1
- package/src/presentation/components/draggable-layer/LayerContent.tsx +1 -2
- package/src/presentation/components/draggable-layer/ResizeHandles.tsx +16 -5
- package/src/presentation/components/export/ExportActions.tsx +1 -1
- package/src/presentation/components/export/ExportProgress.tsx +6 -2
- package/src/presentation/components/export/OptionSelectorRow.tsx +1 -1
- package/src/presentation/components/export/WatermarkToggle.tsx +1 -1
- package/src/presentation/components/image-layer/OpacitySelector.tsx +1 -1
- package/src/presentation/components/shape-layer/ColorPickerHorizontal.tsx +1 -1
- package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +1 -1
- package/src/presentation/components/shape-layer/ValueSelector.tsx +1 -1
- package/src/presentation/components/text-layer/EditorActions.tsx +1 -1
- package/src/presentation/components/text-layer/FontSizeSelector.tsx +1 -1
- package/src/presentation/hooks/useEditorHistory.ts +7 -5
- package/src/presentation/hooks/useEditorScenes.ts +19 -16
- package/src/presentation/hooks/useImageLayerOperations.ts +12 -10
- package/src/presentation/hooks/useLayerActions.tsx +21 -15
- package/src/presentation/hooks/useLayerManipulation.ts +25 -32
- package/src/presentation/hooks/useShapeLayerForm.ts +1 -1
- package/src/presentation/hooks/useShapeLayerOperations.ts +9 -6
- package/src/presentation/hooks/useTextLayerOperations.ts +12 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-video-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "Professional video editor with layer-based timeline, text/image/shape/audio/animation layers, and export functionality",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
"expo-document-picker": "^14.0.8",
|
|
54
54
|
"expo-file-system": "^19.0.0",
|
|
55
55
|
"expo-image": "^3.0.11",
|
|
56
|
-
"expo-linear-gradient": "^15.0.7",
|
|
57
56
|
"expo-video": "^3.0.15",
|
|
58
57
|
"react": "19.1.0",
|
|
59
58
|
"react-native": "0.81.5",
|
|
@@ -98,9 +98,8 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
|
98
98
|
progressContainer: {
|
|
99
99
|
...StyleSheet.absoluteFillObject,
|
|
100
100
|
justifyContent: "center", alignItems: "center",
|
|
101
|
-
backgroundColor: "rgba(0,0,0,0.5)",
|
|
102
101
|
},
|
|
103
|
-
progressText: { color:
|
|
102
|
+
progressText: { color: tokens.colors.onPrimary, fontSize: 16, fontWeight: "600" },
|
|
104
103
|
errorText: { color: tokens.colors.error, fontSize: 14, textAlign: "center", padding: 16 },
|
|
105
104
|
}), [tokens, videoWidth, videoHeight]);
|
|
106
105
|
|
|
@@ -129,7 +128,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
|
129
128
|
) : (
|
|
130
129
|
<View style={styles.placeholder} />
|
|
131
130
|
)}
|
|
132
|
-
<View style={styles.progressContainer}>
|
|
131
|
+
<View style={[styles.progressContainer, { backgroundColor: "rgba(0,0,0,0.5)" }]}>
|
|
133
132
|
<AtomicText style={styles.progressText}>{progressPercent}%</AtomicText>
|
|
134
133
|
</View>
|
|
135
134
|
</View>
|
|
@@ -5,17 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import React, { useCallback } from "react";
|
|
7
7
|
import { View, ScrollView, StyleSheet, Alert } from "react-native";
|
|
8
|
-
|
|
9
|
-
let DocumentPicker: any;
|
|
10
|
-
try {
|
|
11
|
-
DocumentPicker = require("expo-document-picker");
|
|
12
|
-
} catch (e) {
|
|
13
|
-
// Graceful fail - DocumentPicker will be undefined
|
|
14
|
-
}
|
|
8
|
+
import * as DocumentPicker from "expo-document-picker";
|
|
15
9
|
import {
|
|
16
10
|
AtomicText,
|
|
17
11
|
useAppDesignTokens,
|
|
18
12
|
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
19
14
|
import type { Audio } from "../../domain/entities";
|
|
20
15
|
import { useAudioLayerForm } from "../hooks/useAudioLayerForm";
|
|
21
16
|
import { AUDIO_FILE_TYPES } from "../../infrastructure/constants/audio-layer.constants";
|
|
@@ -41,6 +36,7 @@ export const AudioEditor: React.FC<AudioEditorProps> = ({
|
|
|
41
36
|
onCancel,
|
|
42
37
|
}) => {
|
|
43
38
|
const tokens = useAppDesignTokens();
|
|
39
|
+
const { t } = useLocalization();
|
|
44
40
|
const {
|
|
45
41
|
formState,
|
|
46
42
|
setAudioUri,
|
|
@@ -52,10 +48,6 @@ export const AudioEditor: React.FC<AudioEditorProps> = ({
|
|
|
52
48
|
} = useAudioLayerForm(audio);
|
|
53
49
|
|
|
54
50
|
const handlePickAudio = useCallback(async () => {
|
|
55
|
-
if (!DocumentPicker) {
|
|
56
|
-
Alert.alert("Error", "Audio picker is not available in this environment");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
51
|
try {
|
|
60
52
|
const result = await DocumentPicker.getDocumentAsync({
|
|
61
53
|
type: AUDIO_FILE_TYPES[0],
|
|
@@ -66,26 +58,26 @@ export const AudioEditor: React.FC<AudioEditorProps> = ({
|
|
|
66
58
|
setAudioUri(result.assets[0].uri);
|
|
67
59
|
}
|
|
68
60
|
} catch (error) {
|
|
69
|
-
Alert.alert("
|
|
61
|
+
Alert.alert(t("audio.errors.pickFailed"));
|
|
70
62
|
}
|
|
71
|
-
}, [setAudioUri]);
|
|
63
|
+
}, [setAudioUri, t]);
|
|
72
64
|
|
|
73
65
|
const handleSave = useCallback(() => {
|
|
74
66
|
if (!isValid) {
|
|
75
|
-
Alert.alert("
|
|
67
|
+
Alert.alert(t("audio.errors.noFile"));
|
|
76
68
|
return;
|
|
77
69
|
}
|
|
78
70
|
onSave(buildAudioData());
|
|
79
|
-
}, [isValid, buildAudioData, onSave]);
|
|
71
|
+
}, [isValid, buildAudioData, onSave, t]);
|
|
80
72
|
|
|
81
73
|
const handleRemoveAudio = useCallback(() => {
|
|
82
74
|
Alert.alert(
|
|
83
|
-
"
|
|
84
|
-
"
|
|
75
|
+
t("audio.remove.title"),
|
|
76
|
+
t("audio.remove.message"),
|
|
85
77
|
[
|
|
86
|
-
{ text: "
|
|
78
|
+
{ text: t("common.buttons.cancel"), style: "cancel" },
|
|
87
79
|
{
|
|
88
|
-
text: "
|
|
80
|
+
text: t("audio.remove.confirm"),
|
|
89
81
|
style: "destructive",
|
|
90
82
|
onPress: () => {
|
|
91
83
|
onRemove?.();
|
|
@@ -93,12 +85,12 @@ export const AudioEditor: React.FC<AudioEditorProps> = ({
|
|
|
93
85
|
},
|
|
94
86
|
],
|
|
95
87
|
);
|
|
96
|
-
}, [onRemove]);
|
|
88
|
+
}, [onRemove, t]);
|
|
97
89
|
|
|
98
90
|
const getFileName = useCallback((uri: string) => {
|
|
99
91
|
const parts = uri.split("/");
|
|
100
|
-
return parts[parts.length - 1] || "
|
|
101
|
-
}, []);
|
|
92
|
+
return parts[parts.length - 1] || t("audio.unknownFile");
|
|
93
|
+
}, [t]);
|
|
102
94
|
|
|
103
95
|
return (
|
|
104
96
|
<View style={styles.container}>
|
|
@@ -112,7 +104,7 @@ export const AudioEditor: React.FC<AudioEditorProps> = ({
|
|
|
112
104
|
marginBottom: 8,
|
|
113
105
|
}}
|
|
114
106
|
>
|
|
115
|
-
|
|
107
|
+
{t("audio.file.title")}
|
|
116
108
|
</AtomicText>
|
|
117
109
|
<AudioFileSelector
|
|
118
110
|
audioUri={formState.audioUri}
|
|
@@ -135,8 +135,8 @@ export const EditorToolPanel: React.FC<EditorToolPanelProps> = ({
|
|
|
135
135
|
]}
|
|
136
136
|
onPress={() =>
|
|
137
137
|
Alert.alert(
|
|
138
|
-
t("editor.tools.effects"
|
|
139
|
-
t("editor.tools.effectsComingSoon"
|
|
138
|
+
t("editor.tools.effects"),
|
|
139
|
+
t("editor.tools.effectsComingSoon"),
|
|
140
140
|
)
|
|
141
141
|
}
|
|
142
142
|
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AtomicIcon,
|
|
11
11
|
useAppDesignTokens,
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
13
14
|
import type { Layer, ImageLayer } from "../../domain/entities";
|
|
14
15
|
|
|
15
16
|
export interface LayerActionsMenuProps {
|
|
@@ -38,6 +39,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
38
39
|
onDelete,
|
|
39
40
|
}) => {
|
|
40
41
|
const tokens = useAppDesignTokens();
|
|
42
|
+
const { t } = useLocalization();
|
|
41
43
|
|
|
42
44
|
return (
|
|
43
45
|
<View style={{ paddingVertical: 8 }}>
|
|
@@ -51,7 +53,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
51
53
|
marginLeft: 12,
|
|
52
54
|
}}
|
|
53
55
|
>
|
|
54
|
-
|
|
56
|
+
{t("editor.layers.actions.editText")}
|
|
55
57
|
</AtomicText>
|
|
56
58
|
</TouchableOpacity>
|
|
57
59
|
)}
|
|
@@ -65,7 +67,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
65
67
|
marginLeft: 12,
|
|
66
68
|
}}
|
|
67
69
|
>
|
|
68
|
-
|
|
70
|
+
{t("editor.layers.actions.editImage")}
|
|
69
71
|
</AtomicText>
|
|
70
72
|
</TouchableOpacity>
|
|
71
73
|
)}
|
|
@@ -79,7 +81,9 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
79
81
|
marginLeft: 12,
|
|
80
82
|
}}
|
|
81
83
|
>
|
|
82
|
-
{layer.animation
|
|
84
|
+
{layer.animation
|
|
85
|
+
? t("editor.layers.actions.editAnimation")
|
|
86
|
+
: t("editor.layers.actions.addAnimation")}
|
|
83
87
|
</AtomicText>
|
|
84
88
|
{layer.animation && (
|
|
85
89
|
<View
|
|
@@ -100,11 +104,13 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
100
104
|
marginLeft: 12,
|
|
101
105
|
}}
|
|
102
106
|
>
|
|
103
|
-
|
|
107
|
+
{t("editor.layers.actions.duplicate")}
|
|
104
108
|
</AtomicText>
|
|
105
109
|
</TouchableOpacity>
|
|
106
110
|
|
|
107
|
-
<View
|
|
111
|
+
<View
|
|
112
|
+
style={[styles.divider, { backgroundColor: tokens.colors.border }]}
|
|
113
|
+
/>
|
|
108
114
|
|
|
109
115
|
<TouchableOpacity style={styles.actionMenuItem} onPress={onMoveFront}>
|
|
110
116
|
<AtomicIcon name="chevron-up-outline" size="md" color="secondary" />
|
|
@@ -115,7 +121,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
115
121
|
marginLeft: 12,
|
|
116
122
|
}}
|
|
117
123
|
>
|
|
118
|
-
|
|
124
|
+
{t("editor.layers.actions.bringToFront")}
|
|
119
125
|
</AtomicText>
|
|
120
126
|
</TouchableOpacity>
|
|
121
127
|
|
|
@@ -128,7 +134,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
128
134
|
marginLeft: 12,
|
|
129
135
|
}}
|
|
130
136
|
>
|
|
131
|
-
|
|
137
|
+
{t("editor.layers.actions.moveUp")}
|
|
132
138
|
</AtomicText>
|
|
133
139
|
</TouchableOpacity>
|
|
134
140
|
|
|
@@ -141,7 +147,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
141
147
|
marginLeft: 12,
|
|
142
148
|
}}
|
|
143
149
|
>
|
|
144
|
-
|
|
150
|
+
{t("editor.layers.actions.moveDown")}
|
|
145
151
|
</AtomicText>
|
|
146
152
|
</TouchableOpacity>
|
|
147
153
|
|
|
@@ -154,11 +160,13 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
154
160
|
marginLeft: 12,
|
|
155
161
|
}}
|
|
156
162
|
>
|
|
157
|
-
|
|
163
|
+
{t("editor.layers.actions.sendToBack")}
|
|
158
164
|
</AtomicText>
|
|
159
165
|
</TouchableOpacity>
|
|
160
166
|
|
|
161
|
-
<View
|
|
167
|
+
<View
|
|
168
|
+
style={[styles.divider, { backgroundColor: tokens.colors.border }]}
|
|
169
|
+
/>
|
|
162
170
|
|
|
163
171
|
<TouchableOpacity style={styles.actionMenuItem} onPress={onDelete}>
|
|
164
172
|
<AtomicIcon name="trash-outline" size="md" color="error" />
|
|
@@ -169,7 +177,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
|
|
|
169
177
|
marginLeft: 12,
|
|
170
178
|
}}
|
|
171
179
|
>
|
|
172
|
-
|
|
180
|
+
{t("editor.layers.actions.delete")}
|
|
173
181
|
</AtomicText>
|
|
174
182
|
</TouchableOpacity>
|
|
175
183
|
</View>
|
|
@@ -185,7 +193,6 @@ const styles = StyleSheet.create({
|
|
|
185
193
|
},
|
|
186
194
|
divider: {
|
|
187
195
|
height: 1,
|
|
188
|
-
backgroundColor: "#E5E7EB",
|
|
189
196
|
marginVertical: 8,
|
|
190
197
|
},
|
|
191
198
|
animationBadge: {
|
|
@@ -68,7 +68,7 @@ export const AnimationEditorActions: React.FC<AnimationEditorActionsProps> = ({
|
|
|
68
68
|
<AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
|
|
69
69
|
<AtomicText
|
|
70
70
|
type="bodyMedium"
|
|
71
|
-
style={{ color:
|
|
71
|
+
style={{ color: tokens.colors.onPrimary, fontWeight: "600", marginLeft: 6 }}
|
|
72
72
|
>
|
|
73
73
|
Apply
|
|
74
74
|
</AtomicText>
|
|
@@ -70,7 +70,7 @@ export const AnimationTypeSelector: React.FC<AnimationTypeSelectorProps> = ({
|
|
|
70
70
|
style={{
|
|
71
71
|
color:
|
|
72
72
|
selectedType === anim.type
|
|
73
|
-
?
|
|
73
|
+
? tokens.colors.onPrimary
|
|
74
74
|
: tokens.colors.textPrimary,
|
|
75
75
|
marginTop: 6,
|
|
76
76
|
fontWeight: selectedType === anim.type ? "600" : "400",
|
|
@@ -81,7 +81,7 @@ export const AudioEditorActions: React.FC<AudioEditorActionsProps> = ({
|
|
|
81
81
|
<AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
|
|
82
82
|
<AtomicText
|
|
83
83
|
type="bodyMedium"
|
|
84
|
-
style={{ color:
|
|
84
|
+
style={{ color: tokens.colors.onPrimary, fontWeight: "600", marginLeft: 6 }}
|
|
85
85
|
>
|
|
86
86
|
Save
|
|
87
87
|
</AtomicText>
|
|
@@ -68,7 +68,13 @@ export const AudioFileSelector: React.FC<AudioFileSelectorProps> = ({
|
|
|
68
68
|
|
|
69
69
|
return (
|
|
70
70
|
<TouchableOpacity
|
|
71
|
-
style={[
|
|
71
|
+
style={[
|
|
72
|
+
styles.pickButton,
|
|
73
|
+
{
|
|
74
|
+
backgroundColor: tokens.colors.surface,
|
|
75
|
+
borderColor: tokens.colors.borderLight,
|
|
76
|
+
},
|
|
77
|
+
]}
|
|
72
78
|
onPress={onPickAudio}
|
|
73
79
|
>
|
|
74
80
|
<AtomicIcon name="cloud-upload-outline" size="md" color="primary" />
|
|
@@ -102,7 +108,6 @@ const styles = StyleSheet.create({
|
|
|
102
108
|
alignItems: "center",
|
|
103
109
|
justifyContent: "center",
|
|
104
110
|
borderWidth: 2,
|
|
105
|
-
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
106
111
|
borderStyle: "dashed",
|
|
107
112
|
},
|
|
108
113
|
fileCard: {
|
|
@@ -73,7 +73,7 @@ export const FadeEffectsSelector: React.FC<FadeEffectsSelectorProps> = ({
|
|
|
73
73
|
type="labelSmall"
|
|
74
74
|
style={{
|
|
75
75
|
color:
|
|
76
|
-
fadeIn === val ?
|
|
76
|
+
fadeIn === val ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
77
77
|
fontSize: 11,
|
|
78
78
|
}}
|
|
79
79
|
>
|
|
@@ -114,7 +114,7 @@ export const FadeEffectsSelector: React.FC<FadeEffectsSelectorProps> = ({
|
|
|
114
114
|
type="labelSmall"
|
|
115
115
|
style={{
|
|
116
116
|
color:
|
|
117
|
-
fadeOut === val ?
|
|
117
|
+
fadeOut === val ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
118
118
|
fontSize: 11,
|
|
119
119
|
}}
|
|
120
120
|
>
|
|
@@ -61,7 +61,7 @@ export const VolumeSelector: React.FC<VolumeSelectorProps> = ({
|
|
|
61
61
|
<AtomicText
|
|
62
62
|
type="labelSmall"
|
|
63
63
|
style={{
|
|
64
|
-
color: volume === val ?
|
|
64
|
+
color: volume === val ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
65
65
|
fontWeight: volume === val ? "600" : "400",
|
|
66
66
|
}}
|
|
67
67
|
>
|
|
@@ -51,7 +51,7 @@ export const LayerContent: React.FC<LayerContentProps> = ({ layer }) => {
|
|
|
51
51
|
resizeMode="cover"
|
|
52
52
|
/>
|
|
53
53
|
) : (
|
|
54
|
-
<View style={styles.imagePlaceholder}>
|
|
54
|
+
<View style={[styles.imagePlaceholder, { backgroundColor: tokens.colors.surface }]}>
|
|
55
55
|
<AtomicIcon name="image-outline" size="md" color="secondary" />
|
|
56
56
|
</View>
|
|
57
57
|
);
|
|
@@ -95,7 +95,6 @@ const styles = StyleSheet.create({
|
|
|
95
95
|
imagePlaceholder: {
|
|
96
96
|
width: "100%",
|
|
97
97
|
height: "100%",
|
|
98
|
-
backgroundColor: "rgba(0,0,0,0.1)",
|
|
99
98
|
alignItems: "center",
|
|
100
99
|
justifyContent: "center",
|
|
101
100
|
},
|
|
@@ -31,7 +31,10 @@ export const ResizeHandles: React.FC<ResizeHandlesProps> = ({
|
|
|
31
31
|
style={[
|
|
32
32
|
styles.handle,
|
|
33
33
|
styles.handleTopLeft,
|
|
34
|
-
{
|
|
34
|
+
{
|
|
35
|
+
backgroundColor: tokens.colors.primary,
|
|
36
|
+
borderColor: tokens.colors.onPrimary,
|
|
37
|
+
},
|
|
35
38
|
]}
|
|
36
39
|
/>
|
|
37
40
|
</GestureDetector>
|
|
@@ -41,7 +44,10 @@ export const ResizeHandles: React.FC<ResizeHandlesProps> = ({
|
|
|
41
44
|
style={[
|
|
42
45
|
styles.handle,
|
|
43
46
|
styles.handleTopRight,
|
|
44
|
-
{
|
|
47
|
+
{
|
|
48
|
+
backgroundColor: tokens.colors.primary,
|
|
49
|
+
borderColor: tokens.colors.onPrimary,
|
|
50
|
+
},
|
|
45
51
|
]}
|
|
46
52
|
/>
|
|
47
53
|
</GestureDetector>
|
|
@@ -51,7 +57,10 @@ export const ResizeHandles: React.FC<ResizeHandlesProps> = ({
|
|
|
51
57
|
style={[
|
|
52
58
|
styles.handle,
|
|
53
59
|
styles.handleBottomLeft,
|
|
54
|
-
{
|
|
60
|
+
{
|
|
61
|
+
backgroundColor: tokens.colors.primary,
|
|
62
|
+
borderColor: tokens.colors.onPrimary,
|
|
63
|
+
},
|
|
55
64
|
]}
|
|
56
65
|
/>
|
|
57
66
|
</GestureDetector>
|
|
@@ -61,7 +70,10 @@ export const ResizeHandles: React.FC<ResizeHandlesProps> = ({
|
|
|
61
70
|
style={[
|
|
62
71
|
styles.handle,
|
|
63
72
|
styles.handleBottomRight,
|
|
64
|
-
{
|
|
73
|
+
{
|
|
74
|
+
backgroundColor: tokens.colors.primary,
|
|
75
|
+
borderColor: tokens.colors.onPrimary,
|
|
76
|
+
},
|
|
65
77
|
]}
|
|
66
78
|
/>
|
|
67
79
|
</GestureDetector>
|
|
@@ -76,7 +88,6 @@ const styles = StyleSheet.create({
|
|
|
76
88
|
height: 12,
|
|
77
89
|
borderRadius: 6,
|
|
78
90
|
borderWidth: 2,
|
|
79
|
-
borderColor: "#FFFFFF",
|
|
80
91
|
},
|
|
81
92
|
handleTopLeft: {
|
|
82
93
|
top: -6,
|
|
@@ -70,7 +70,7 @@ export const ExportActions: React.FC<ExportActionsProps> = ({
|
|
|
70
70
|
)}
|
|
71
71
|
<AtomicText
|
|
72
72
|
type="bodyMedium"
|
|
73
|
-
style={{ color:
|
|
73
|
+
style={{ color: tokens.colors.onPrimary, fontWeight: "600", marginLeft: 8 }}
|
|
74
74
|
>
|
|
75
75
|
{isExporting ? "Exporting..." : "Export Video"}
|
|
76
76
|
</AtomicText>
|
|
@@ -57,7 +57,12 @@ export const ExportProgress: React.FC<ExportProgressProps> = ({ progress }) => {
|
|
|
57
57
|
</AtomicText>
|
|
58
58
|
</View>
|
|
59
59
|
|
|
60
|
-
<View
|
|
60
|
+
<View
|
|
61
|
+
style={[
|
|
62
|
+
styles.progressBarContainer,
|
|
63
|
+
{ backgroundColor: tokens.colors.borderLight },
|
|
64
|
+
]}
|
|
65
|
+
>
|
|
61
66
|
<View
|
|
62
67
|
style={[
|
|
63
68
|
styles.progressBar,
|
|
@@ -98,7 +103,6 @@ const styles = StyleSheet.create({
|
|
|
98
103
|
},
|
|
99
104
|
progressBarContainer: {
|
|
100
105
|
height: 8,
|
|
101
|
-
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
|
102
106
|
borderRadius: 4,
|
|
103
107
|
overflow: "hidden",
|
|
104
108
|
marginBottom: 8,
|
|
@@ -67,7 +67,7 @@ export function OptionSelectorRow<T extends string>({
|
|
|
67
67
|
style={{
|
|
68
68
|
color:
|
|
69
69
|
selectedValue === option.value
|
|
70
|
-
?
|
|
70
|
+
? tokens.colors.onPrimary
|
|
71
71
|
: tokens.colors.textPrimary,
|
|
72
72
|
fontWeight: selectedValue === option.value ? "600" : "400",
|
|
73
73
|
textTransform: option.textTransform || "none",
|
|
@@ -59,7 +59,7 @@ export const OpacitySelector: React.FC<OpacitySelectorProps> = ({
|
|
|
59
59
|
type="bodySmall"
|
|
60
60
|
style={{
|
|
61
61
|
color:
|
|
62
|
-
opacity === value ?
|
|
62
|
+
opacity === value ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
63
63
|
fontWeight: opacity === value ? "600" : "400",
|
|
64
64
|
}}
|
|
65
65
|
>
|
|
@@ -53,7 +53,7 @@ export const ColorPickerHorizontal: React.FC<ColorPickerHorizontalProps> = ({
|
|
|
53
53
|
borderColor:
|
|
54
54
|
selectedColor === color.value
|
|
55
55
|
? tokens.colors.primary
|
|
56
|
-
:
|
|
56
|
+
: tokens.colors.borderLight,
|
|
57
57
|
borderWidth: selectedColor === color.value ? 3 : 2,
|
|
58
58
|
},
|
|
59
59
|
]}
|
|
@@ -68,7 +68,7 @@ export const ShapeTypeSelector: React.FC<ShapeTypeSelectorProps> = ({
|
|
|
68
68
|
style={{
|
|
69
69
|
color:
|
|
70
70
|
selectedShape === s.type
|
|
71
|
-
?
|
|
71
|
+
? tokens.colors.onPrimary
|
|
72
72
|
: tokens.colors.textPrimary,
|
|
73
73
|
marginTop: 8,
|
|
74
74
|
fontWeight: selectedShape === s.type ? "600" : "400",
|
|
@@ -66,7 +66,7 @@ export const ValueSelector: React.FC<ValueSelectorProps> = ({
|
|
|
66
66
|
<AtomicText
|
|
67
67
|
type="labelSmall"
|
|
68
68
|
style={{
|
|
69
|
-
color: value === option ?
|
|
69
|
+
color: value === option ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
70
70
|
fontWeight: value === option ? "600" : "400",
|
|
71
71
|
}}
|
|
72
72
|
>
|
|
@@ -59,7 +59,7 @@ export const FontSizeSelector: React.FC<FontSizeSelectorProps> = ({
|
|
|
59
59
|
type="bodySmall"
|
|
60
60
|
style={{
|
|
61
61
|
color:
|
|
62
|
-
fontSize === size ?
|
|
62
|
+
fontSize === size ? tokens.colors.onPrimary : tokens.colors.textPrimary,
|
|
63
63
|
fontWeight: fontSize === size ? "600" : "400",
|
|
64
64
|
}}
|
|
65
65
|
>
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
// TODO: Refactor to use TanStack Query instead of store
|
|
9
10
|
// Temporary stub until refactor
|
|
10
11
|
const useHistoryStore = () => ({
|
|
11
12
|
addToHistory: () => {},
|
|
12
|
-
pushHistory: (_project:
|
|
13
|
+
pushHistory: (_project: VideoProject | undefined, _action: string) => {},
|
|
13
14
|
undo: () => undefined,
|
|
14
15
|
redo: () => undefined,
|
|
15
16
|
canUndo: () => false,
|
|
@@ -36,6 +37,7 @@ export function useEditorHistory({
|
|
|
36
37
|
projectId,
|
|
37
38
|
onUpdateProject,
|
|
38
39
|
}: UseEditorHistoryParams): UseEditorHistoryReturn {
|
|
40
|
+
const { t } = useLocalization();
|
|
39
41
|
const {
|
|
40
42
|
pushHistory,
|
|
41
43
|
undo: historyUndo,
|
|
@@ -58,17 +60,17 @@ export function useEditorHistory({
|
|
|
58
60
|
const previousState = historyUndo();
|
|
59
61
|
if (previousState) {
|
|
60
62
|
onUpdateProject(previousState);
|
|
61
|
-
Alert.alert("
|
|
63
|
+
Alert.alert(t("editor.history.undo.success"));
|
|
62
64
|
}
|
|
63
|
-
}, [historyUndo, onUpdateProject]);
|
|
65
|
+
}, [historyUndo, onUpdateProject, t]);
|
|
64
66
|
|
|
65
67
|
const redo = useCallback(() => {
|
|
66
68
|
const nextState = historyRedo();
|
|
67
69
|
if (nextState) {
|
|
68
70
|
onUpdateProject(nextState);
|
|
69
|
-
Alert.alert("
|
|
71
|
+
Alert.alert(t("editor.history.redo.success"));
|
|
70
72
|
}
|
|
71
|
-
}, [historyRedo, onUpdateProject]);
|
|
73
|
+
}, [historyRedo, onUpdateProject, t]);
|
|
72
74
|
|
|
73
75
|
return {
|
|
74
76
|
undo,
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
import { sceneOperationsService } from "../../infrastructure/services/scene-operations.service";
|
|
9
|
-
import type { Audio } from "../../domain/entities";
|
|
10
|
+
import type { Scene, Audio } from "../../domain/entities";
|
|
10
11
|
|
|
11
12
|
export interface UseEditorScenesParams {
|
|
12
|
-
scenes:
|
|
13
|
+
scenes: Scene[];
|
|
13
14
|
currentSceneIndex: number;
|
|
14
|
-
onUpdateScenes: (scenes:
|
|
15
|
+
onUpdateScenes: (scenes: Scene[]) => void;
|
|
15
16
|
onSceneIndexChange: (index: number) => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -28,6 +29,8 @@ export function useEditorScenes({
|
|
|
28
29
|
onUpdateScenes,
|
|
29
30
|
onSceneIndexChange,
|
|
30
31
|
}: UseEditorScenesParams): UseEditorScenesReturn {
|
|
32
|
+
const { t } = useLocalization();
|
|
33
|
+
|
|
31
34
|
const addScene = useCallback(() => {
|
|
32
35
|
const result = sceneOperationsService.addScene(scenes);
|
|
33
36
|
if (result.success) {
|
|
@@ -35,11 +38,11 @@ export function useEditorScenes({
|
|
|
35
38
|
if (result.newSceneIndex !== undefined) {
|
|
36
39
|
onSceneIndexChange(result.newSceneIndex);
|
|
37
40
|
}
|
|
38
|
-
Alert.alert("
|
|
41
|
+
Alert.alert(t("editor.scenes.add.success"));
|
|
39
42
|
} else {
|
|
40
|
-
Alert.alert("
|
|
43
|
+
Alert.alert(t("editor.scenes.add.error"));
|
|
41
44
|
}
|
|
42
|
-
}, [scenes, onUpdateScenes, onSceneIndexChange]);
|
|
45
|
+
}, [scenes, onUpdateScenes, onSceneIndexChange, t]);
|
|
43
46
|
|
|
44
47
|
const duplicateScene = useCallback(
|
|
45
48
|
(sceneIndex: number) => {
|
|
@@ -49,12 +52,12 @@ export function useEditorScenes({
|
|
|
49
52
|
if (result.newSceneIndex !== undefined) {
|
|
50
53
|
onSceneIndexChange(result.newSceneIndex);
|
|
51
54
|
}
|
|
52
|
-
Alert.alert("
|
|
55
|
+
Alert.alert(t("editor.scenes.duplicate.success"));
|
|
53
56
|
} else {
|
|
54
|
-
Alert.alert("
|
|
57
|
+
Alert.alert(t("editor.scenes.duplicate.error"));
|
|
55
58
|
}
|
|
56
59
|
},
|
|
57
|
-
[scenes, onUpdateScenes, onSceneIndexChange],
|
|
60
|
+
[scenes, onUpdateScenes, onSceneIndexChange, t],
|
|
58
61
|
);
|
|
59
62
|
|
|
60
63
|
const deleteScene = useCallback(
|
|
@@ -69,12 +72,12 @@ export function useEditorScenes({
|
|
|
69
72
|
if (result.newSceneIndex !== undefined) {
|
|
70
73
|
onSceneIndexChange(result.newSceneIndex);
|
|
71
74
|
}
|
|
72
|
-
Alert.alert("
|
|
75
|
+
Alert.alert(t("editor.scenes.delete.success"));
|
|
73
76
|
} else {
|
|
74
|
-
Alert.alert("
|
|
77
|
+
Alert.alert(t("editor.scenes.delete.error"));
|
|
75
78
|
}
|
|
76
79
|
},
|
|
77
|
-
[scenes, currentSceneIndex, onUpdateScenes, onSceneIndexChange],
|
|
80
|
+
[scenes, currentSceneIndex, onUpdateScenes, onSceneIndexChange, t],
|
|
78
81
|
);
|
|
79
82
|
|
|
80
83
|
const updateSceneAudio = useCallback(
|
|
@@ -87,14 +90,14 @@ export function useEditorScenes({
|
|
|
87
90
|
if (result.success) {
|
|
88
91
|
onUpdateScenes(result.updatedScenes);
|
|
89
92
|
Alert.alert(
|
|
90
|
-
"
|
|
91
|
-
audio ? "
|
|
93
|
+
t("editor.scenes.audio.success"),
|
|
94
|
+
t(audio ? "editor.scenes.audio.added" : "editor.scenes.audio.removed"),
|
|
92
95
|
);
|
|
93
96
|
} else {
|
|
94
|
-
Alert.alert("
|
|
97
|
+
Alert.alert(t("editor.scenes.audio.error"));
|
|
95
98
|
}
|
|
96
99
|
},
|
|
97
|
-
[scenes, currentSceneIndex, onUpdateScenes],
|
|
100
|
+
[scenes, currentSceneIndex, onUpdateScenes, t],
|
|
98
101
|
);
|
|
99
102
|
|
|
100
103
|
return {
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
import { layerOperationsService } from "../../infrastructure/services/layer-operations.service";
|
|
9
|
-
import type { AddImageLayerData } from "../../domain/entities";
|
|
10
|
-
import type { ImageLayer } from "../../domain/entities";
|
|
10
|
+
import type { AddImageLayerData, Scene, ImageLayer } from "../../domain/entities";
|
|
11
11
|
|
|
12
12
|
export interface UseImageLayerOperationsParams {
|
|
13
|
-
scenes:
|
|
13
|
+
scenes: Scene[];
|
|
14
14
|
sceneIndex: number;
|
|
15
|
-
onUpdateScenes: (scenes:
|
|
15
|
+
onUpdateScenes: (scenes: Scene[]) => void;
|
|
16
16
|
onCloseBottomSheet: () => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -27,6 +27,8 @@ export function useImageLayerOperations({
|
|
|
27
27
|
onUpdateScenes,
|
|
28
28
|
onCloseBottomSheet,
|
|
29
29
|
}: UseImageLayerOperationsParams): UseImageLayerOperationsReturn {
|
|
30
|
+
const { t } = useLocalization();
|
|
31
|
+
|
|
30
32
|
const addImageLayer = useCallback(
|
|
31
33
|
(data: AddImageLayerData) => {
|
|
32
34
|
const result = layerOperationsService.addImageLayer(
|
|
@@ -37,12 +39,12 @@ export function useImageLayerOperations({
|
|
|
37
39
|
if (result.success) {
|
|
38
40
|
onUpdateScenes(result.updatedScenes);
|
|
39
41
|
onCloseBottomSheet();
|
|
40
|
-
Alert.alert("
|
|
42
|
+
Alert.alert(t("editor.layers.image.add.success"));
|
|
41
43
|
} else {
|
|
42
|
-
Alert.alert("
|
|
44
|
+
Alert.alert(t("editor.layers.image.add.error"));
|
|
43
45
|
}
|
|
44
46
|
},
|
|
45
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
|
|
47
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, t],
|
|
46
48
|
);
|
|
47
49
|
|
|
48
50
|
const editImageLayer = useCallback(
|
|
@@ -56,12 +58,12 @@ export function useImageLayerOperations({
|
|
|
56
58
|
if (result.success) {
|
|
57
59
|
onUpdateScenes(result.updatedScenes);
|
|
58
60
|
onCloseBottomSheet();
|
|
59
|
-
Alert.alert("
|
|
61
|
+
Alert.alert(t("editor.layers.image.update.success"));
|
|
60
62
|
} else {
|
|
61
|
-
Alert.alert("
|
|
63
|
+
Alert.alert(t("editor.layers.image.update.error"));
|
|
62
64
|
}
|
|
63
65
|
},
|
|
64
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
|
|
66
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, t],
|
|
65
67
|
);
|
|
66
68
|
|
|
67
69
|
return {
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
7
8
|
import { TextLayerEditor } from "../components/TextLayerEditor";
|
|
8
9
|
import { ImageLayerEditor } from "../components/ImageLayerEditor";
|
|
9
10
|
import { ShapeLayerEditor } from "../components/ShapeLayerEditor";
|
|
10
11
|
import { AnimationEditor } from "../components/AnimationEditor";
|
|
11
|
-
import type { ImageLayer } from "../../domain/entities";
|
|
12
|
+
import type { Scene, ImageLayer, Layer } from "../../domain/entities";
|
|
12
13
|
import type { UseEditorLayersReturn } from "./useEditorLayers";
|
|
13
14
|
import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
|
|
14
15
|
|
|
15
16
|
export interface UseLayerActionsParams {
|
|
16
17
|
selectedLayerId: string | null;
|
|
17
|
-
currentScene:
|
|
18
|
+
currentScene: Scene | undefined;
|
|
18
19
|
layers: UseEditorLayersReturn;
|
|
19
20
|
bottomSheet: UseEditorBottomSheetReturn;
|
|
20
21
|
}
|
|
@@ -34,11 +35,12 @@ export function useLayerActions({
|
|
|
34
35
|
layers,
|
|
35
36
|
bottomSheet,
|
|
36
37
|
}: UseLayerActionsParams): UseLayerActionsReturn {
|
|
38
|
+
const { t } = useLocalization();
|
|
37
39
|
const { openBottomSheet, closeBottomSheet } = bottomSheet;
|
|
38
40
|
|
|
39
41
|
const handleAddText = useCallback(() => {
|
|
40
42
|
openBottomSheet({
|
|
41
|
-
title: "
|
|
43
|
+
title: t("editor.layers.text.add"),
|
|
42
44
|
children: (
|
|
43
45
|
<TextLayerEditor
|
|
44
46
|
onSave={layers.addTextLayer}
|
|
@@ -46,17 +48,17 @@ export function useLayerActions({
|
|
|
46
48
|
/>
|
|
47
49
|
),
|
|
48
50
|
});
|
|
49
|
-
}, [layers.addTextLayer, openBottomSheet, closeBottomSheet]);
|
|
51
|
+
}, [layers.addTextLayer, openBottomSheet, closeBottomSheet, t]);
|
|
50
52
|
|
|
51
53
|
const handleEditLayer = useCallback(() => {
|
|
52
54
|
if (!selectedLayerId || !currentScene) return;
|
|
53
55
|
const layer = currentScene.layers.find(
|
|
54
|
-
(l:
|
|
56
|
+
(l: Layer) => l.id === selectedLayerId,
|
|
55
57
|
);
|
|
56
58
|
if (!layer || layer.type !== "text") return;
|
|
57
59
|
|
|
58
60
|
openBottomSheet({
|
|
59
|
-
title: "
|
|
61
|
+
title: t("editor.layers.text.edit"),
|
|
60
62
|
children: (
|
|
61
63
|
<TextLayerEditor
|
|
62
64
|
layer={layer}
|
|
@@ -71,11 +73,12 @@ export function useLayerActions({
|
|
|
71
73
|
layers.editTextLayer,
|
|
72
74
|
openBottomSheet,
|
|
73
75
|
closeBottomSheet,
|
|
76
|
+
t,
|
|
74
77
|
]);
|
|
75
78
|
|
|
76
79
|
const handleAddImage = useCallback(() => {
|
|
77
80
|
openBottomSheet({
|
|
78
|
-
title: "
|
|
81
|
+
title: t("editor.layers.image.add"),
|
|
79
82
|
children: (
|
|
80
83
|
<ImageLayerEditor
|
|
81
84
|
onSave={layers.addImageLayer}
|
|
@@ -83,18 +86,18 @@ export function useLayerActions({
|
|
|
83
86
|
/>
|
|
84
87
|
),
|
|
85
88
|
});
|
|
86
|
-
}, [layers.addImageLayer, openBottomSheet, closeBottomSheet]);
|
|
89
|
+
}, [layers.addImageLayer, openBottomSheet, closeBottomSheet, t]);
|
|
87
90
|
|
|
88
91
|
const handleEditImageLayer = useCallback(
|
|
89
92
|
(layerId: string) => {
|
|
90
93
|
if (!currentScene) return;
|
|
91
|
-
const layer = currentScene.layers.find((l:
|
|
94
|
+
const layer = currentScene.layers.find((l: Layer) => l.id === layerId) as
|
|
92
95
|
| ImageLayer
|
|
93
96
|
| undefined;
|
|
94
97
|
if (!layer) return;
|
|
95
98
|
|
|
96
99
|
openBottomSheet({
|
|
97
|
-
title: "
|
|
100
|
+
title: t("editor.layers.image.edit"),
|
|
98
101
|
children: (
|
|
99
102
|
<ImageLayerEditor
|
|
100
103
|
layer={layer}
|
|
@@ -104,12 +107,12 @@ export function useLayerActions({
|
|
|
104
107
|
),
|
|
105
108
|
});
|
|
106
109
|
},
|
|
107
|
-
[currentScene, layers.editImageLayer, openBottomSheet, closeBottomSheet],
|
|
110
|
+
[currentScene, layers.editImageLayer, openBottomSheet, closeBottomSheet, t],
|
|
108
111
|
);
|
|
109
112
|
|
|
110
113
|
const handleAddShape = useCallback(() => {
|
|
111
114
|
openBottomSheet({
|
|
112
|
-
title: "
|
|
115
|
+
title: t("editor.layers.shape.add"),
|
|
113
116
|
children: (
|
|
114
117
|
<ShapeLayerEditor
|
|
115
118
|
onSave={layers.addShapeLayer}
|
|
@@ -117,16 +120,18 @@ export function useLayerActions({
|
|
|
117
120
|
/>
|
|
118
121
|
),
|
|
119
122
|
});
|
|
120
|
-
}, [layers.addShapeLayer, openBottomSheet, closeBottomSheet]);
|
|
123
|
+
}, [layers.addShapeLayer, openBottomSheet, closeBottomSheet, t]);
|
|
121
124
|
|
|
122
125
|
const handleAnimate = useCallback(
|
|
123
126
|
(layerId: string) => {
|
|
124
127
|
if (!currentScene) return;
|
|
125
|
-
const layer = currentScene.layers.find((l:
|
|
128
|
+
const layer = currentScene.layers.find((l: Layer) => l.id === layerId);
|
|
126
129
|
if (!layer) return;
|
|
127
130
|
|
|
128
131
|
openBottomSheet({
|
|
129
|
-
title: layer.animation
|
|
132
|
+
title: layer.animation
|
|
133
|
+
? t("editor.layers.animation.edit")
|
|
134
|
+
: t("editor.layers.animation.add"),
|
|
130
135
|
children: (
|
|
131
136
|
<AnimationEditor
|
|
132
137
|
animation={layer.animation}
|
|
@@ -148,6 +153,7 @@ export function useLayerActions({
|
|
|
148
153
|
layers.updateLayerAnimation,
|
|
149
154
|
openBottomSheet,
|
|
150
155
|
closeBottomSheet,
|
|
156
|
+
t,
|
|
151
157
|
],
|
|
152
158
|
);
|
|
153
159
|
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
import { layerOperationsService } from "../../infrastructure/services/layer-operations.service";
|
|
9
|
-
import type { LayerOrderAction } from "../../domain/entities";
|
|
10
|
-
import type { Animation } from "../../domain/entities";
|
|
10
|
+
import type { Scene, LayerOrderAction, Animation } from "../../domain/entities";
|
|
11
11
|
|
|
12
12
|
export interface UseLayerManipulationParams {
|
|
13
|
-
scenes:
|
|
13
|
+
scenes: Scene[];
|
|
14
14
|
sceneIndex: number;
|
|
15
|
-
onUpdateScenes: (scenes:
|
|
15
|
+
onUpdateScenes: (scenes: Scene[]) => void;
|
|
16
16
|
onCloseBottomSheet: () => void;
|
|
17
17
|
onLayerDeleted?: () => void;
|
|
18
18
|
}
|
|
@@ -36,15 +36,17 @@ export function useLayerManipulation({
|
|
|
36
36
|
onCloseBottomSheet,
|
|
37
37
|
onLayerDeleted,
|
|
38
38
|
}: UseLayerManipulationParams): UseLayerManipulationReturn {
|
|
39
|
+
const { t } = useLocalization();
|
|
40
|
+
|
|
39
41
|
const deleteLayer = useCallback(
|
|
40
42
|
(layerId: string) => {
|
|
41
43
|
Alert.alert(
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
+
t("editor.layers.delete.title"),
|
|
45
|
+
t("editor.layers.delete.message"),
|
|
44
46
|
[
|
|
45
|
-
{ text: "
|
|
47
|
+
{ text: t("common.buttons.cancel"), style: "cancel" },
|
|
46
48
|
{
|
|
47
|
-
text: "
|
|
49
|
+
text: t("editor.layers.delete.confirm"),
|
|
48
50
|
style: "destructive",
|
|
49
51
|
onPress: () => {
|
|
50
52
|
const result = layerOperationsService.deleteLayer(
|
|
@@ -55,16 +57,16 @@ export function useLayerManipulation({
|
|
|
55
57
|
if (result.success) {
|
|
56
58
|
onUpdateScenes(result.updatedScenes);
|
|
57
59
|
onLayerDeleted?.();
|
|
58
|
-
Alert.alert("
|
|
60
|
+
Alert.alert(t("editor.layers.delete.success"));
|
|
59
61
|
} else {
|
|
60
|
-
Alert.alert("
|
|
62
|
+
Alert.alert(t("editor.layers.delete.error"));
|
|
61
63
|
}
|
|
62
64
|
},
|
|
63
65
|
},
|
|
64
66
|
],
|
|
65
67
|
);
|
|
66
68
|
},
|
|
67
|
-
[scenes, sceneIndex, onUpdateScenes, onLayerDeleted],
|
|
69
|
+
[scenes, sceneIndex, onUpdateScenes, onLayerDeleted, t],
|
|
68
70
|
);
|
|
69
71
|
|
|
70
72
|
const changeLayerOrder = useCallback(
|
|
@@ -77,18 +79,12 @@ export function useLayerManipulation({
|
|
|
77
79
|
);
|
|
78
80
|
if (result.success) {
|
|
79
81
|
onUpdateScenes(result.updatedScenes);
|
|
80
|
-
|
|
81
|
-
front: "Layer moved to front",
|
|
82
|
-
back: "Layer moved to back",
|
|
83
|
-
up: "Layer moved up",
|
|
84
|
-
down: "Layer moved down",
|
|
85
|
-
};
|
|
86
|
-
Alert.alert("Success", actionNames[action]);
|
|
82
|
+
Alert.alert(t("editor.layers.order.success"), t(`editor.layers.order.${action}`));
|
|
87
83
|
} else {
|
|
88
|
-
Alert.alert("
|
|
84
|
+
Alert.alert(t("editor.layers.order.error"));
|
|
89
85
|
}
|
|
90
86
|
},
|
|
91
|
-
[scenes, sceneIndex, onUpdateScenes],
|
|
87
|
+
[scenes, sceneIndex, onUpdateScenes, t],
|
|
92
88
|
);
|
|
93
89
|
|
|
94
90
|
const duplicateLayer = useCallback(
|
|
@@ -100,12 +96,12 @@ export function useLayerManipulation({
|
|
|
100
96
|
);
|
|
101
97
|
if (result.success) {
|
|
102
98
|
onUpdateScenes(result.updatedScenes);
|
|
103
|
-
Alert.alert("
|
|
99
|
+
Alert.alert(t("editor.layers.duplicate.success"));
|
|
104
100
|
} else {
|
|
105
|
-
Alert.alert("
|
|
101
|
+
Alert.alert(t("editor.layers.duplicate.error"));
|
|
106
102
|
}
|
|
107
103
|
},
|
|
108
|
-
[scenes, sceneIndex, onUpdateScenes],
|
|
104
|
+
[scenes, sceneIndex, onUpdateScenes, t],
|
|
109
105
|
);
|
|
110
106
|
|
|
111
107
|
const updateLayerPosition = useCallback(
|
|
@@ -152,19 +148,16 @@ export function useLayerManipulation({
|
|
|
152
148
|
onUpdateScenes(result.updatedScenes);
|
|
153
149
|
onCloseBottomSheet();
|
|
154
150
|
Alert.alert(
|
|
155
|
-
"
|
|
156
|
-
animation
|
|
157
|
-
? "
|
|
158
|
-
: "
|
|
151
|
+
t("editor.layers.animation.success"),
|
|
152
|
+
t(animation
|
|
153
|
+
? "editor.layers.animation.applied"
|
|
154
|
+
: "editor.layers.animation.removed"),
|
|
159
155
|
);
|
|
160
156
|
} else {
|
|
161
|
-
Alert.alert(
|
|
162
|
-
"Error",
|
|
163
|
-
result.error || "Failed to update layer animation",
|
|
164
|
-
);
|
|
157
|
+
Alert.alert(t("editor.layers.animation.error"));
|
|
165
158
|
}
|
|
166
159
|
},
|
|
167
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
|
|
160
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, t],
|
|
168
161
|
);
|
|
169
162
|
|
|
170
163
|
return {
|
|
@@ -36,7 +36,7 @@ export function useShapeLayerForm(
|
|
|
36
36
|
|
|
37
37
|
const [formState, setFormState] = useState<ShapeLayerFormState>({
|
|
38
38
|
shape: initialLayer?.shape || "rectangle",
|
|
39
|
-
fillColor: initialLayer?.fillColor ||
|
|
39
|
+
fillColor: initialLayer?.fillColor || tokens.colors.primary,
|
|
40
40
|
borderColor: initialLayer?.borderColor || tokens.colors.textPrimary,
|
|
41
41
|
borderWidth: initialLayer?.borderWidth || 0,
|
|
42
42
|
opacity: initialLayer?.opacity || 1,
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
import { layerOperationsService } from "../../infrastructure/services/layer-operations.service";
|
|
9
|
-
import type { AddShapeLayerData } from "../../domain/entities";
|
|
10
|
+
import type { AddShapeLayerData, Scene } from "../../domain/entities";
|
|
10
11
|
|
|
11
12
|
export interface UseShapeLayerOperationsParams {
|
|
12
|
-
scenes:
|
|
13
|
+
scenes: Scene[];
|
|
13
14
|
sceneIndex: number;
|
|
14
|
-
onUpdateScenes: (scenes:
|
|
15
|
+
onUpdateScenes: (scenes: Scene[]) => void;
|
|
15
16
|
onCloseBottomSheet: () => void;
|
|
16
17
|
defaultColor: string;
|
|
17
18
|
}
|
|
@@ -27,6 +28,8 @@ export function useShapeLayerOperations({
|
|
|
27
28
|
onCloseBottomSheet,
|
|
28
29
|
defaultColor,
|
|
29
30
|
}: UseShapeLayerOperationsParams): UseShapeLayerOperationsReturn {
|
|
31
|
+
const { t } = useLocalization();
|
|
32
|
+
|
|
30
33
|
const addShapeLayer = useCallback(
|
|
31
34
|
(data: AddShapeLayerData) => {
|
|
32
35
|
const result = layerOperationsService.addShapeLayer(
|
|
@@ -38,12 +41,12 @@ export function useShapeLayerOperations({
|
|
|
38
41
|
if (result.success) {
|
|
39
42
|
onUpdateScenes(result.updatedScenes);
|
|
40
43
|
onCloseBottomSheet();
|
|
41
|
-
Alert.alert("
|
|
44
|
+
Alert.alert(t("editor.layers.shape.add.success"));
|
|
42
45
|
} else {
|
|
43
|
-
Alert.alert("
|
|
46
|
+
Alert.alert(t("editor.layers.shape.add.error"));
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, defaultColor],
|
|
49
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, defaultColor, t],
|
|
47
50
|
);
|
|
48
51
|
|
|
49
52
|
return {
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { Alert } from "react-native";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
9
|
import { layerOperationsService } from "../../infrastructure/services/layer-operations.service";
|
|
9
|
-
import type { AddTextLayerData } from "../../domain/entities";
|
|
10
|
-
import type { TextLayer } from "../../domain/entities";
|
|
10
|
+
import type { AddTextLayerData, Scene, TextLayer } from "../../domain/entities";
|
|
11
11
|
|
|
12
12
|
export interface UseTextLayerOperationsParams {
|
|
13
|
-
scenes:
|
|
13
|
+
scenes: Scene[];
|
|
14
14
|
sceneIndex: number;
|
|
15
|
-
onUpdateScenes: (scenes:
|
|
15
|
+
onUpdateScenes: (scenes: Scene[]) => void;
|
|
16
16
|
onCloseBottomSheet: () => void;
|
|
17
17
|
defaultColor: string;
|
|
18
18
|
}
|
|
@@ -29,6 +29,8 @@ export function useTextLayerOperations({
|
|
|
29
29
|
onCloseBottomSheet,
|
|
30
30
|
defaultColor,
|
|
31
31
|
}: UseTextLayerOperationsParams): UseTextLayerOperationsReturn {
|
|
32
|
+
const { t } = useLocalization();
|
|
33
|
+
|
|
32
34
|
const addTextLayer = useCallback(
|
|
33
35
|
(data: AddTextLayerData) => {
|
|
34
36
|
const result = layerOperationsService.addTextLayer(
|
|
@@ -40,12 +42,12 @@ export function useTextLayerOperations({
|
|
|
40
42
|
if (result.success) {
|
|
41
43
|
onUpdateScenes(result.updatedScenes);
|
|
42
44
|
onCloseBottomSheet();
|
|
43
|
-
Alert.alert("
|
|
45
|
+
Alert.alert(t("editor.layers.text.add.success"));
|
|
44
46
|
} else {
|
|
45
|
-
Alert.alert("
|
|
47
|
+
Alert.alert(t("editor.layers.text.add.error"));
|
|
46
48
|
}
|
|
47
49
|
},
|
|
48
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, defaultColor],
|
|
50
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, defaultColor, t],
|
|
49
51
|
);
|
|
50
52
|
|
|
51
53
|
const editTextLayer = useCallback(
|
|
@@ -59,12 +61,12 @@ export function useTextLayerOperations({
|
|
|
59
61
|
if (result.success) {
|
|
60
62
|
onUpdateScenes(result.updatedScenes);
|
|
61
63
|
onCloseBottomSheet();
|
|
62
|
-
Alert.alert("
|
|
64
|
+
Alert.alert(t("editor.layers.text.update.success"));
|
|
63
65
|
} else {
|
|
64
|
-
Alert.alert("
|
|
66
|
+
Alert.alert(t("editor.layers.text.update.error"));
|
|
65
67
|
}
|
|
66
68
|
},
|
|
67
|
-
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
|
|
69
|
+
[scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, t],
|
|
68
70
|
);
|
|
69
71
|
|
|
70
72
|
return {
|