@umituz/react-native-video-editor 1.1.71 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.1.71",
3
+ "version": "1.2.1",
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",
@@ -43,7 +43,6 @@
43
43
  "react": ">=18.2.0",
44
44
  "react-native": ">=0.74.0",
45
45
  "react-native-gesture-handler": ">=2.0.0",
46
- "react-native-reanimated": ">=3.0.0",
47
46
  "zustand": ">=4.0.0"
48
47
  },
49
48
  "peerDependenciesMeta": {
@@ -71,7 +70,6 @@
71
70
  "react": "19.1.0",
72
71
  "react-native": "0.81.5",
73
72
  "react-native-gesture-handler": "^2.30.0",
74
- "react-native-reanimated": "^3.16.0",
75
73
  "typescript": "~5.9.2"
76
74
  },
77
75
  "publishConfig": {
package/src/index.ts CHANGED
@@ -15,13 +15,11 @@ export type {
15
15
  ImageLayer,
16
16
  VideoLayer,
17
17
  ShapeLayer,
18
- Animation,
19
18
  Audio,
20
19
  ExportSettings,
21
20
  AspectRatio,
22
21
  LayerType,
23
22
  TransitionType,
24
- AnimationType,
25
23
  Position,
26
24
  Size,
27
25
  Transition,
@@ -73,7 +71,6 @@ export { SceneActionsMenu } from "./presentation/components/SceneActionsMenu";
73
71
  export { TextLayerEditor } from "./presentation/components/TextLayerEditor";
74
72
  export { AudioEditor } from "./presentation/components/AudioEditor";
75
73
  export { ShapeLayerEditor } from "./presentation/components/ShapeLayerEditor";
76
- export { AnimationEditor } from "./presentation/components/AnimationEditor";
77
74
  export { DraggableLayer } from "./presentation/components/DraggableLayer";
78
75
  export { ImageLayerEditor } from "./presentation/components/ImageLayerEditor";
79
76
  export { ExportDialog } from "./presentation/components/ExportDialog";
@@ -94,7 +91,6 @@ export { useEditorActions } from "./presentation/hooks/useEditorActions";
94
91
  export { useTextLayerForm } from "./presentation/hooks/useTextLayerForm";
95
92
  export { useImageLayerForm } from "./presentation/hooks/useImageLayerForm";
96
93
  export { useShapeLayerForm } from "./presentation/hooks/useShapeLayerForm";
97
- export { useAnimationLayerForm } from "./presentation/hooks/useAnimationLayerForm";
98
94
  export { useAudioLayerForm } from "./presentation/hooks/useAudioLayerForm";
99
95
  export { useTextLayerOperations } from "./presentation/hooks/useTextLayerOperations";
100
96
  export { useImageLayerOperations } from "./presentation/hooks/useImageLayerOperations";
@@ -1,38 +1,48 @@
1
1
  /**
2
2
  * AnimationEditor Component
3
- * Main component for editing animation layers
3
+ * Main component for editing layer animations
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useCallback } from "react";
7
7
  import { View, ScrollView, StyleSheet } from "react-native";
8
-
8
+ import { useLocalization } from "@umituz/react-native-settings";
9
+ import { AtomicText } from "@umituz/react-native-design-system/atoms";
10
+ import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
9
11
  import type { Animation } from "../../domain/entities/video-project.types";
10
- import { useAnimationLayerForm } from "../hooks/useAnimationLayerForm";
11
- import {
12
- DURATIONS,
13
- DELAYS,
14
- EASINGS,
15
- type Easing,
16
- } from "../../infrastructure/constants/animation-layer.constants";
17
- import { ValueSelector } from "./shape-layer/ValueSelector";
18
- import { OptionSelector } from "./text-layer/OptionSelector";
19
- import { AnimationTypeSelector } from "./animation-layer/AnimationTypeSelector";
20
- import { AnimationEditorActions } from "./animation-layer/AnimationEditorActions";
21
- import { AnimationInfoBanner } from "./animation-layer/AnimationInfoBanner";
12
+ import { useAnimationForm } from "../hooks/useAnimationForm";
13
+ import { EditorActions } from "./text-layer/EditorActions";
22
14
 
23
15
  interface AnimationEditorProps {
24
- animation?: Animation;
25
- onSave: (animation: Animation) => void;
26
- onRemove?: () => void;
27
- onCancel: () => void;
16
+ readonly animation?: Animation;
17
+ readonly onSave: (animation: Animation) => void;
18
+ readonly onRemove?: () => void;
19
+ readonly onCancel: () => void;
28
20
  }
29
21
 
22
+ const ANIMATION_TYPES = [
23
+ { label: "None", value: "none" as const },
24
+ { label: "Fade", value: "fade" as const },
25
+ { label: "Slide", value: "slide" as const },
26
+ { label: "Bounce", value: "bounce" as const },
27
+ { label: "Zoom", value: "zoom" as const },
28
+ { label: "Rotate", value: "rotate" as const },
29
+ ] as const;
30
+
31
+ const EASING_OPTIONS = [
32
+ { label: "Linear", value: "linear" as const },
33
+ { label: "Ease In", value: "ease-in" as const },
34
+ { label: "Ease Out", value: "ease-out" as const },
35
+ { label: "Ease In Out", value: "ease-in-out" as const },
36
+ ] as const;
37
+
30
38
  export const AnimationEditor: React.FC<AnimationEditorProps> = ({
31
39
  animation,
32
40
  onSave,
33
41
  onRemove,
34
42
  onCancel,
35
43
  }) => {
44
+ const { t } = useLocalization();
45
+ const tokens = useAppDesignTokens();
36
46
  const {
37
47
  formState,
38
48
  setAnimationType,
@@ -40,55 +50,183 @@ export const AnimationEditor: React.FC<AnimationEditorProps> = ({
40
50
  setDelay,
41
51
  setEasing,
42
52
  buildAnimationData,
43
- } = useAnimationLayerForm(animation);
53
+ isValid,
54
+ } = useAnimationForm(animation);
44
55
 
45
- const handleSave = () => {
56
+ const handleSave = useCallback(() => {
57
+ if (!isValid) return;
46
58
  onSave(buildAnimationData());
47
- };
59
+ }, [isValid, buildAnimationData, onSave]);
48
60
 
49
61
  return (
50
62
  <View style={styles.container}>
51
63
  <ScrollView showsVerticalScrollIndicator={false}>
52
- <AnimationTypeSelector
53
- selectedType={formState.animationType}
54
- onTypeChange={setAnimationType}
55
- />
64
+ {/* Animation Type Selector */}
65
+ <View style={styles.section}>
66
+ <AtomicText
67
+ type="labelMedium"
68
+ color="textSecondary"
69
+ style={{ marginBottom: tokens.spacing.sm }}
70
+ >
71
+ {t("editor.properties.animation_type") || "Animation Type"}
72
+ </AtomicText>
73
+ <View style={styles.optionsContainer}>
74
+ {ANIMATION_TYPES.map((option) => (
75
+ <View
76
+ key={option.value}
77
+ style={[
78
+ styles.optionButton,
79
+ formState.type === option.value && {
80
+ backgroundColor: tokens.colors.primary,
81
+ },
82
+ ]}
83
+ >
84
+ <AtomicText
85
+ fontWeight="medium"
86
+ color={
87
+ formState.type === option.value
88
+ ? "onPrimary"
89
+ : "textPrimary"
90
+ }
91
+ onPress={() => setAnimationType(option.value)}
92
+ >
93
+ {option.label}
94
+ </AtomicText>
95
+ </View>
96
+ ))}
97
+ </View>
98
+ </View>
56
99
 
57
- {formState.animationType !== "none" && (
100
+ {/* Duration Selector */}
101
+ {formState.type !== "none" && (
58
102
  <>
59
- <ValueSelector
60
- title="Duration"
61
- value={formState.duration}
62
- options={DURATIONS}
63
- formatValue={(val) => `${val}ms`}
64
- onValueChange={setDuration}
65
- />
103
+ <View style={styles.section}>
104
+ <AtomicText
105
+ type="labelMedium"
106
+ color="textSecondary"
107
+ style={{ marginBottom: tokens.spacing.sm }}
108
+ >
109
+ {t("editor.properties.duration") || "Duration"}
110
+ </AtomicText>
111
+ <View style={styles.sliderContainer}>
112
+ <AtomicText type="bodySmall" color="textSecondary">
113
+ {formState.duration}ms
114
+ </AtomicText>
115
+ </View>
116
+ <View style={styles.valueButtons}>
117
+ {[300, 500, 1000, 1500, 2000].map((value) => (
118
+ <View
119
+ key={value}
120
+ style={[
121
+ styles.valueButton,
122
+ formState.duration === value && {
123
+ backgroundColor: tokens.colors.primary,
124
+ },
125
+ ]}
126
+ >
127
+ <AtomicText
128
+ type="labelSmall"
129
+ color={
130
+ formState.duration === value
131
+ ? "onPrimary"
132
+ : "textPrimary"
133
+ }
134
+ onPress={() => setDuration(value)}
135
+ >
136
+ {value}ms
137
+ </AtomicText>
138
+ </View>
139
+ ))}
140
+ </View>
141
+ </View>
66
142
 
67
- <ValueSelector
68
- title="Delay"
69
- value={formState.delay}
70
- options={DELAYS}
71
- formatValue={(val) => `${val}ms`}
72
- onValueChange={setDelay}
73
- />
143
+ {/* Delay Selector */}
144
+ <View style={styles.section}>
145
+ <AtomicText
146
+ type="labelMedium"
147
+ color="textSecondary"
148
+ style={{ marginBottom: tokens.spacing.sm }}
149
+ >
150
+ {t("editor.properties.delay") || "Delay"}
151
+ </AtomicText>
152
+ <View style={styles.sliderContainer}>
153
+ <AtomicText type="bodySmall" color="textSecondary">
154
+ {formState.delay || 0}ms
155
+ </AtomicText>
156
+ </View>
157
+ <View style={styles.valueButtons}>
158
+ {[0, 100, 200, 500, 1000].map((value) => (
159
+ <View
160
+ key={value}
161
+ style={[
162
+ styles.valueButton,
163
+ (formState.delay || 0) === value && {
164
+ backgroundColor: tokens.colors.primary,
165
+ },
166
+ ]}
167
+ >
168
+ <AtomicText
169
+ type="labelSmall"
170
+ color={
171
+ (formState.delay || 0) === value
172
+ ? "onPrimary"
173
+ : "textPrimary"
174
+ }
175
+ onPress={() => setDelay(value)}
176
+ >
177
+ {value}ms
178
+ </AtomicText>
179
+ </View>
180
+ ))}
181
+ </View>
182
+ </View>
74
183
 
75
- <OptionSelector
76
- title="Easing"
77
- options={EASINGS}
78
- selectedValue={formState.easing}
79
- onValueChange={(value) => setEasing(value as Easing)}
80
- />
81
-
82
- <AnimationInfoBanner />
184
+ {/* Easing Selector */}
185
+ <View style={styles.section}>
186
+ <AtomicText
187
+ type="labelMedium"
188
+ color="textSecondary"
189
+ style={{ marginBottom: tokens.spacing.sm }}
190
+ >
191
+ {t("editor.properties.easing") || "Easing"}
192
+ </AtomicText>
193
+ <View style={styles.optionsContainer}>
194
+ {EASING_OPTIONS.map((option) => (
195
+ <View
196
+ key={option.value}
197
+ style={[
198
+ styles.optionButton,
199
+ formState.easing === option.value && {
200
+ backgroundColor: tokens.colors.primary,
201
+ },
202
+ ]}
203
+ >
204
+ <AtomicText
205
+ fontWeight="medium"
206
+ color={
207
+ formState.easing === option.value
208
+ ? "onPrimary"
209
+ : "textPrimary"
210
+ }
211
+ onPress={() => setEasing(option.value)}
212
+ >
213
+ {option.label}
214
+ </AtomicText>
215
+ </View>
216
+ ))}
217
+ </View>
218
+ </View>
83
219
  </>
84
220
  )}
85
221
  </ScrollView>
86
222
 
87
- <AnimationEditorActions
88
- hasAnimation={!!animation}
89
- onRemove={onRemove}
223
+ <EditorActions
90
224
  onCancel={onCancel}
91
225
  onSave={handleSave}
226
+ saveLabel={animation ? "Update Animation" : "Add Animation"}
227
+ isValid={isValid}
228
+ onRemove={onRemove}
229
+ removeLabel="Remove Animation"
92
230
  />
93
231
  </View>
94
232
  );
@@ -96,6 +234,40 @@ export const AnimationEditor: React.FC<AnimationEditorProps> = ({
96
234
 
97
235
  const styles = StyleSheet.create({
98
236
  container: {
99
- paddingVertical: 16,
237
+ maxHeight: "80%",
238
+ },
239
+ section: {
240
+ marginBottom: 20,
241
+ },
242
+ optionsContainer: {
243
+ flexDirection: "row",
244
+ flexWrap: "wrap",
245
+ gap: 8,
246
+ },
247
+ optionButton: {
248
+ paddingHorizontal: 12,
249
+ paddingVertical: 8,
250
+ borderRadius: 8,
251
+ borderWidth: 1,
252
+ borderColor: "#ccc",
253
+ minWidth: 80,
254
+ alignItems: "center",
255
+ },
256
+ sliderContainer: {
257
+ marginBottom: 12,
258
+ },
259
+ valueButtons: {
260
+ flexDirection: "row",
261
+ flexWrap: "wrap",
262
+ gap: 8,
263
+ },
264
+ valueButton: {
265
+ paddingHorizontal: 12,
266
+ paddingVertical: 8,
267
+ borderRadius: 8,
268
+ borderWidth: 1,
269
+ borderColor: "#ccc",
270
+ minWidth: 60,
271
+ alignItems: "center",
100
272
  },
101
273
  });
@@ -14,6 +14,8 @@ interface EditorActionsProps {
14
14
  onSave: () => void;
15
15
  saveLabel: string;
16
16
  isValid: boolean;
17
+ onRemove?: () => void;
18
+ removeLabel?: string;
17
19
  }
18
20
 
19
21
  export const EditorActions: React.FC<EditorActionsProps> = ({
@@ -21,6 +23,8 @@ export const EditorActions: React.FC<EditorActionsProps> = ({
21
23
  onSave,
22
24
  saveLabel,
23
25
  isValid,
26
+ onRemove,
27
+ removeLabel,
24
28
  }) => {
25
29
  const tokens = useAppDesignTokens();
26
30
  const { t } = useLocalization();
@@ -29,6 +33,24 @@ export const EditorActions: React.FC<EditorActionsProps> = ({
29
33
  <View
30
34
  style={[styles.actions, { borderTopColor: tokens.colors.borderLight }]}
31
35
  >
36
+ {onRemove && (
37
+ <TouchableOpacity
38
+ style={[
39
+ styles.actionButton,
40
+ styles.removeButton,
41
+ { borderColor: tokens.colors.error },
42
+ ]}
43
+ onPress={onRemove}
44
+ >
45
+ <AtomicText
46
+ type="bodyMedium"
47
+ style={{ color: tokens.colors.error }}
48
+ >
49
+ {removeLabel || t("common.buttons.remove")}
50
+ </AtomicText>
51
+ </TouchableOpacity>
52
+ )}
53
+
32
54
  <TouchableOpacity
33
55
  style={[
34
56
  styles.actionButton,
@@ -83,6 +105,9 @@ const styles = StyleSheet.create({
83
105
  alignItems: "center",
84
106
  justifyContent: "center",
85
107
  },
108
+ removeButton: {
109
+ borderWidth: 1,
110
+ },
86
111
  cancelButton: {
87
112
  borderWidth: 1,
88
113
  },
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Presentation Hooks
3
+ * All hooks for the video editor presentation layer
4
+ */
5
+
6
+ export { useAnimationForm } from "./useAnimationForm";
7
+ export { useAudioLayerForm } from "./useAudioLayerForm";
8
+ export { useCollageEditor } from "./useCollageEditor";
9
+ export { useDraggableLayerGestures } from "./useDraggableLayerGestures";
10
+ export { useEditorActions } from "./useEditorActions";
11
+ export { useEditorBottomSheet } from "./useEditorBottomSheet";
12
+ export { useEditorHistory } from "./useEditorHistory";
13
+ export { useEditorLayers } from "./useEditorLayers";
14
+ export { useEditorPlayback } from "./useEditorPlayback";
15
+ export { useEditorScenes } from "./useEditorScenes";
16
+ export { useExport } from "./useExport";
17
+ export { useExportActions } from "./useExportActions";
18
+ export { useExportForm } from "./useExportForm";
19
+ export { useImageLayerForm } from "./useImageLayerForm";
20
+ export { useImageLayerOperations } from "./useImageLayerOperations";
21
+ export { useLayerActions } from "./useLayerActions";
22
+ export { useLayerManipulation } from "./useLayerManipulation";
23
+ export { useMenuActions } from "./useMenuActions";
24
+ export { useSceneActions } from "./useSceneActions";
25
+ export { useShapeLayerForm } from "./useShapeLayerForm";
26
+ export { useShapeLayerOperations } from "./useShapeLayerOperations";
27
+ export { useSubtitleEditor } from "./useSubtitleEditor";
28
+ export { useTextLayerForm } from "./useTextLayerForm";
29
+ export { useTextLayerOperations } from "./useTextLayerOperations";
@@ -0,0 +1,98 @@
1
+ /**
2
+ * useAnimationForm Hook
3
+ * Manages form state for animation editing
4
+ */
5
+
6
+ import { useCallback, useMemo } from "react";
7
+ import type { Animation, AnimationType } from "../../domain/entities/video-project.types";
8
+
9
+ interface AnimationFormState {
10
+ type: AnimationType;
11
+ duration: number;
12
+ delay?: number;
13
+ easing: "linear" | "ease-in" | "ease-out" | "ease-in-out";
14
+ }
15
+
16
+ interface UseAnimationFormParams {
17
+ animation?: Animation;
18
+ }
19
+
20
+ interface UseAnimationFormReturn {
21
+ formState: AnimationFormState;
22
+ setAnimationType: (type: AnimationType) => void;
23
+ setDuration: (duration: number) => void;
24
+ setDelay: (delay: number) => void;
25
+ setEasing: (easing: "linear" | "ease-in" | "ease-out" | "ease-in-out") => void;
26
+ buildAnimationData: () => Animation;
27
+ isValid: boolean;
28
+ }
29
+
30
+ const DEFAULT_ANIMATION: AnimationFormState = {
31
+ type: "none",
32
+ duration: 500,
33
+ delay: 0,
34
+ easing: "ease-in-out",
35
+ };
36
+
37
+ export function useAnimationForm({
38
+ animation,
39
+ }: UseAnimationFormParams = {}): UseAnimationFormReturn {
40
+ const formState = useMemo<AnimationFormState>(() => {
41
+ if (!animation) {
42
+ return DEFAULT_ANIMATION;
43
+ }
44
+
45
+ return {
46
+ type: animation.type,
47
+ duration: animation.duration,
48
+ delay: animation.delay,
49
+ easing: animation.easing || "ease-in-out",
50
+ };
51
+ }, [animation]);
52
+
53
+ const setAnimationType = useCallback((type: AnimationType) => {
54
+ (formState as any).type = type;
55
+ }, [formState]);
56
+
57
+ const setDuration = useCallback((duration: number) => {
58
+ (formState as any).duration = duration;
59
+ }, [formState]);
60
+
61
+ const setDelay = useCallback((delay: number) => {
62
+ (formState as any).delay = delay;
63
+ }, [formState]);
64
+
65
+ const setEasing = useCallback((
66
+ easing: "linear" | "ease-in" | "ease-out" | "ease-in-out"
67
+ ) => {
68
+ (formState as any).easing = easing;
69
+ }, [formState]);
70
+
71
+ const buildAnimationData = useCallback((): Animation => {
72
+ const data: Animation = {
73
+ type: formState.type,
74
+ duration: formState.duration,
75
+ easing: formState.easing,
76
+ };
77
+
78
+ if (formState.delay && formState.delay > 0) {
79
+ data.delay = formState.delay;
80
+ }
81
+
82
+ return data;
83
+ }, [formState]);
84
+
85
+ const isValid = useMemo(() => {
86
+ return formState.type !== "none" && formState.duration > 0;
87
+ }, [formState]);
88
+
89
+ return {
90
+ formState,
91
+ setAnimationType,
92
+ setDuration,
93
+ setDelay,
94
+ setEasing,
95
+ buildAnimationData,
96
+ isValid,
97
+ };
98
+ }
@@ -6,8 +6,6 @@
6
6
 
7
7
  import { useState, useRef, useCallback, useEffect } from "react";
8
8
  import { Gesture } from "react-native-gesture-handler";
9
- // @ts-ignore - react-native-reanimated is an optional peer dependency
10
- import { runOnJS } from "react-native-reanimated";
11
9
 
12
10
  interface UseDraggableLayerGesturesParams {
13
11
  initialX: number;
@@ -77,23 +75,20 @@ export function useDraggableLayerGestures({
77
75
  const gestureHandler = Gesture.Pan()
78
76
  .onStart(() => {
79
77
  startRef.current = { ...state, x: state.x, y: state.y };
80
- runOnJS(onSelect)();
78
+ onSelect();
81
79
  })
82
80
  .onUpdate((event) => {
83
- 'worklet';
84
81
  const newX = startRef.current.x + event.translationX;
85
82
  const newY = startRef.current.y + event.translationY;
86
- // Use runOnJS for state updates to prevent blocking UI thread
87
- runOnJS(setState)({ x: newX, y: newY, width: state.width, height: state.height });
83
+ setState({ x: newX, y: newY, width: state.width, height: state.height });
88
84
  })
89
85
  .onEnd(() => {
90
- 'worklet';
91
86
  const clampedX = clamp(state.x, 0, canvasWidth - state.width);
92
87
  const clampedY = clamp(state.y, 0, canvasHeight - state.height);
93
88
  const newX = (clampedX / canvasWidth) * 100;
94
89
  const newY = (clampedY / canvasHeight) * 100;
95
- runOnJS(setState)({ x: clampedX, y: clampedY, width: state.width, height: state.height });
96
- runOnJS(onPositionChange)(newX, newY);
90
+ setState({ x: clampedX, y: clampedY, width: state.width, height: state.height });
91
+ onPositionChange(newX, newY);
97
92
  });
98
93
 
99
94
  const createResizeHandler = (
@@ -102,12 +97,10 @@ export function useDraggableLayerGestures({
102
97
  ) => {
103
98
  return Gesture.Pan()
104
99
  .onStart(() => {
105
- 'worklet';
106
100
  startRef.current = { ...state };
107
- runOnJS(onSelect)();
101
+ onSelect();
108
102
  })
109
103
  .onUpdate((event) => {
110
- 'worklet';
111
104
  const newWidth = Math.max(MIN_SIZE, startRef.current.width + deltaX(event.translationX));
112
105
  const newHeight = Math.max(MIN_SIZE, startRef.current.height + deltaY(event.translationY));
113
106
  const clampedWidth = Math.min(newWidth, canvasWidth - startRef.current.x);
@@ -123,10 +116,9 @@ export function useDraggableLayerGestures({
123
116
  newY = Math.max(0, startRef.current.y + (startRef.current.height - clampedHeight));
124
117
  }
125
118
 
126
- runOnJS(setState)({ x: newX, y: newY, width: clampedWidth, height: clampedHeight });
119
+ setState({ x: newX, y: newY, width: clampedWidth, height: clampedHeight });
127
120
  })
128
121
  .onEnd(() => {
129
- 'worklet';
130
122
  // Clamp position to canvas bounds
131
123
  const clampedX = Math.max(0, Math.min(state.x, canvasWidth - state.width));
132
124
  const clampedY = Math.max(0, Math.min(state.y, canvasHeight - state.height));
@@ -136,9 +128,9 @@ export function useDraggableLayerGestures({
136
128
  const newX = (clampedX / canvasWidth) * 100;
137
129
  const newY = (clampedY / canvasHeight) * 100;
138
130
 
139
- runOnJS(onSizeChange)(newWidth, newHeight);
140
- runOnJS(onPositionChange)(newX, newY);
141
- runOnJS(setState)({ x: clampedX, y: clampedY, width: state.width, height: state.height });
131
+ onSizeChange(newWidth, newHeight);
132
+ onPositionChange(newX, newY);
133
+ setState({ x: clampedX, y: clampedY, width: state.width, height: state.height });
142
134
  });
143
135
  };
144
136
 
@@ -1,32 +0,0 @@
1
- /**
2
- * Animation Layer Constants
3
- * Centralized constants for animation layer editor
4
- */
5
-
6
- import type { AnimationType } from "../../domain/entities/video-project.types";
7
-
8
- export type Easing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
9
-
10
- export const ANIMATION_TYPES: {
11
- type: AnimationType;
12
- label: string;
13
- icon: string;
14
- }[] = [
15
- { type: "none", label: "None", icon: "Ban" },
16
- { type: "fade", label: "Fade", icon: "Eye" },
17
- { type: "slide", label: "Slide", icon: "MoveRight" },
18
- { type: "bounce", label: "Bounce", icon: "ArrowUp" },
19
- { type: "zoom", label: "Zoom", icon: "Maximize2" },
20
- { type: "rotate", label: "Rotate", icon: "RotateCw" },
21
- ];
22
-
23
- export const DURATIONS = [300, 500, 800, 1000, 1500, 2000];
24
-
25
- export const DELAYS = [0, 200, 500, 1000];
26
-
27
- export const EASINGS: { value: Easing; label: string }[] = [
28
- { value: "linear", label: "Linear" },
29
- { value: "ease-in", label: "Ease In" },
30
- { value: "ease-out", label: "Ease Out" },
31
- { value: "ease-in-out", label: "Ease In-Out" },
32
- ];
@@ -1,101 +0,0 @@
1
- /**
2
- * AnimationEditorActions Component
3
- * Action buttons for animation editor
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet, TouchableOpacity } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
-
11
- interface AnimationEditorActionsProps {
12
- hasAnimation: boolean;
13
- onRemove?: () => void;
14
- onCancel: () => void;
15
- onSave: () => void;
16
- }
17
-
18
- export const AnimationEditorActions: React.FC<AnimationEditorActionsProps> = ({
19
- hasAnimation,
20
- onRemove,
21
- onCancel,
22
- onSave,
23
- }) => {
24
- const tokens = useAppDesignTokens();
25
-
26
- return (
27
- <View style={styles.actions}>
28
- {hasAnimation && onRemove && (
29
- <TouchableOpacity
30
- style={[
31
- styles.actionButton,
32
- styles.removeButton,
33
- { borderColor: tokens.colors.error },
34
- ]}
35
- onPress={onRemove}
36
- >
37
- <AtomicIcon name="trash-outline" size="sm" color="error" />
38
- </TouchableOpacity>
39
- )}
40
-
41
- <TouchableOpacity
42
- style={[
43
- styles.actionButton,
44
- styles.cancelButton,
45
- { borderColor: tokens.colors.borderLight },
46
- ]}
47
- onPress={onCancel}
48
- >
49
- <AtomicText
50
- type="bodyMedium"
51
- style={{ color: tokens.colors.textSecondary }}
52
- >
53
- Cancel
54
- </AtomicText>
55
- </TouchableOpacity>
56
-
57
- <TouchableOpacity
58
- style={[
59
- styles.actionButton,
60
- styles.saveButton,
61
- { backgroundColor: tokens.colors.primary },
62
- ]}
63
- onPress={onSave}
64
- >
65
- <AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
66
- <AtomicText
67
- type="bodyMedium"
68
- style={{ color: tokens.colors.onPrimary, fontWeight: "600", marginLeft: 6 }}
69
- >
70
- Apply
71
- </AtomicText>
72
- </TouchableOpacity>
73
- </View>
74
- );
75
- };
76
-
77
- const styles = StyleSheet.create({
78
- actions: {
79
- flexDirection: "row",
80
- gap: 8,
81
- },
82
- actionButton: {
83
- flex: 1,
84
- flexDirection: "row",
85
- paddingVertical: 12,
86
- borderRadius: 12,
87
- alignItems: "center",
88
- justifyContent: "center",
89
- },
90
- removeButton: {
91
- flex: 0,
92
- paddingHorizontal: 16,
93
- borderWidth: 1,
94
- },
95
- cancelButton: {
96
- borderWidth: 1,
97
- },
98
- saveButton: {
99
- // backgroundColor set dynamically
100
- },
101
- });
@@ -1,40 +0,0 @@
1
- /**
2
- * AnimationInfoBanner Component
3
- * Info banner for animation editor
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
-
11
- export const AnimationInfoBanner: React.FC = () => {
12
- const tokens = useAppDesignTokens();
13
-
14
- return (
15
- <View
16
- style={[
17
- styles.infoBanner,
18
- { backgroundColor: tokens.colors.primary + "20" },
19
- ]}
20
- >
21
- <AtomicIcon name="information-circle-outline" size="sm" color="primary" />
22
- <AtomicText
23
- type="labelSmall"
24
- style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
25
- >
26
- Animation will play when the layer first appears in the scene
27
- </AtomicText>
28
- </View>
29
- );
30
- };
31
-
32
- const styles = StyleSheet.create({
33
- infoBanner: {
34
- flexDirection: "row",
35
- alignItems: "center",
36
- padding: 12,
37
- borderRadius: 8,
38
- marginBottom: 8,
39
- },
40
- });
@@ -1,102 +0,0 @@
1
- /**
2
- * AnimationTypeSelector Component
3
- * Animation type selector for animation layer
4
- */
5
-
6
- import React from "react";
7
- import { View, ScrollView, StyleSheet, TouchableOpacity } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
- import { ANIMATION_TYPES } from "../../../infrastructure/constants/animation-layer.constants";
11
- import type { AnimationType } from "../../../domain/entities/video-project.types";
12
-
13
- interface AnimationTypeSelectorProps {
14
- selectedType: AnimationType;
15
- onTypeChange: (type: AnimationType) => void;
16
- }
17
-
18
- export const AnimationTypeSelector: React.FC<AnimationTypeSelectorProps> = ({
19
- selectedType,
20
- onTypeChange,
21
- }) => {
22
- const tokens = useAppDesignTokens();
23
-
24
- return (
25
- <View style={styles.section}>
26
- <AtomicText
27
- type="bodyMedium"
28
- style={{
29
- color: tokens.colors.textPrimary,
30
- fontWeight: "600",
31
- marginBottom: 12,
32
- }}
33
- >
34
- Animation Type
35
- </AtomicText>
36
-
37
- <ScrollView
38
- horizontal
39
- showsHorizontalScrollIndicator={false}
40
- style={styles.animationTypesScroll}
41
- >
42
- {ANIMATION_TYPES.map((anim) => (
43
- <TouchableOpacity
44
- key={anim.type}
45
- style={[
46
- styles.animationTypeCard,
47
- {
48
- backgroundColor:
49
- selectedType === anim.type
50
- ? tokens.colors.primary
51
- : tokens.colors.surface,
52
- borderColor:
53
- selectedType === anim.type
54
- ? tokens.colors.primary
55
- : tokens.colors.borderLight,
56
- },
57
- ]}
58
- onPress={() => onTypeChange(anim.type)}
59
- >
60
- <AtomicIcon
61
- name={anim.icon as "Ban" | "Eye" | "MoveRight" | "ArrowUp" | "Maximize2" | "RotateCw"}
62
- size="md"
63
- color={selectedType === anim.type ? "onSurface" : "primary"}
64
- />
65
- <AtomicText
66
- type="labelSmall"
67
- style={{
68
- color:
69
- selectedType === anim.type
70
- ? tokens.colors.onPrimary
71
- : tokens.colors.textPrimary,
72
- marginTop: 6,
73
- fontWeight: selectedType === anim.type ? "600" : "400",
74
- }}
75
- >
76
- {anim.label}
77
- </AtomicText>
78
- </TouchableOpacity>
79
- ))}
80
- </ScrollView>
81
- </View>
82
- );
83
- };
84
-
85
- const styles = StyleSheet.create({
86
- section: {
87
- marginBottom: 24,
88
- },
89
- animationTypesScroll: {
90
- marginHorizontal: -16,
91
- paddingHorizontal: 16,
92
- },
93
- animationTypeCard: {
94
- width: 90,
95
- padding: 12,
96
- borderRadius: 12,
97
- borderWidth: 2,
98
- alignItems: "center",
99
- justifyContent: "center",
100
- marginRight: 12,
101
- },
102
- });
@@ -1,72 +0,0 @@
1
- /**
2
- * useAnimationLayerForm Hook
3
- * Manages form state for animation layer editor
4
- */
5
-
6
- import { useState, useCallback } from "react";
7
- import type { Animation, AnimationType } from "../../domain/entities/video-project.types";
8
- import type { Easing } from "../../infrastructure/constants/animation-layer.constants";
9
-
10
- interface AnimationLayerFormState {
11
- animationType: AnimationType;
12
- duration: number;
13
- delay: number;
14
- easing: Easing;
15
- }
16
-
17
- interface UseAnimationLayerFormReturn {
18
- formState: AnimationLayerFormState;
19
- setAnimationType: (type: AnimationType) => void;
20
- setDuration: (duration: number) => void;
21
- setDelay: (delay: number) => void;
22
- setEasing: (easing: Easing) => void;
23
- buildAnimationData: () => Animation;
24
- }
25
-
26
- /**
27
- * Hook for managing animation layer form state
28
- */
29
- export function useAnimationLayerForm(
30
- initialAnimation?: Animation,
31
- ): UseAnimationLayerFormReturn {
32
- const [formState, setFormState] = useState<AnimationLayerFormState>({
33
- animationType: initialAnimation?.type || "fade",
34
- duration: initialAnimation?.duration || 500,
35
- delay: initialAnimation?.delay || 0,
36
- easing: initialAnimation?.easing || "ease-in-out",
37
- });
38
-
39
- const setAnimationType = useCallback((type: AnimationType) => {
40
- setFormState((prev) => ({ ...prev, animationType: type }));
41
- }, []);
42
-
43
- const setDuration = useCallback((duration: number) => {
44
- setFormState((prev) => ({ ...prev, duration }));
45
- }, []);
46
-
47
- const setDelay = useCallback((delay: number) => {
48
- setFormState((prev) => ({ ...prev, delay }));
49
- }, []);
50
-
51
- const setEasing = useCallback((easing: Easing) => {
52
- setFormState((prev) => ({ ...prev, easing }));
53
- }, []);
54
-
55
- const buildAnimationData = useCallback((): Animation => {
56
- return {
57
- type: formState.animationType,
58
- duration: formState.duration,
59
- delay: formState.delay,
60
- easing: formState.easing,
61
- };
62
- }, [formState]);
63
-
64
- return {
65
- formState,
66
- setAnimationType,
67
- setDuration,
68
- setDelay,
69
- setEasing,
70
- buildAnimationData,
71
- };
72
- }