@umituz/react-native-video-editor 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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",
@@ -31,14 +31,6 @@ export interface Subtitle {
31
31
 
32
32
  export type TransitionType = "fade" | "slide" | "zoom" | "wipe" | "none";
33
33
 
34
- export type AnimationType =
35
- | "fade"
36
- | "slide"
37
- | "bounce"
38
- | "zoom"
39
- | "rotate"
40
- | "none";
41
-
42
34
  export interface Position {
43
35
  x: number;
44
36
  y: number;
@@ -49,13 +41,6 @@ export interface Size {
49
41
  height: number;
50
42
  }
51
43
 
52
- export interface Animation {
53
- type: AnimationType;
54
- duration: number;
55
- delay?: number;
56
- easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
57
- }
58
-
59
44
  export interface Transition {
60
45
  type: TransitionType;
61
46
  duration: number;
@@ -90,7 +75,6 @@ export interface TextLayer {
90
75
  | "900";
91
76
  color: string;
92
77
  textAlign: "left" | "center" | "right";
93
- animation?: Animation;
94
78
  }
95
79
 
96
80
  export interface ImageLayer {
@@ -101,7 +85,6 @@ export interface ImageLayer {
101
85
  size: Size;
102
86
  rotation: number;
103
87
  opacity: number;
104
- animation?: Animation;
105
88
  }
106
89
 
107
90
  export interface VideoLayer {
@@ -115,7 +98,6 @@ export interface VideoLayer {
115
98
  startTime: number;
116
99
  endTime: number;
117
100
  volume: number;
118
- animation?: Animation;
119
101
  }
120
102
 
121
103
  export interface ShapeLayer {
@@ -129,7 +111,6 @@ export interface ShapeLayer {
129
111
  fillColor: string;
130
112
  borderColor?: string;
131
113
  borderWidth?: number;
132
- animation?: Animation;
133
114
  }
134
115
 
135
116
  export type Layer = TextLayer | ImageLayer | VideoLayer | ShapeLayer;
@@ -20,11 +20,6 @@ class ImageLayerOperationsService extends BaseLayerOperationsService<ImageLayer>
20
20
  size: { width: 70, height: 40 },
21
21
  rotation: 0,
22
22
  opacity: layerData.opacity ?? 1,
23
- animation: {
24
- type: "fade",
25
- duration: 500,
26
- easing: "ease-in-out",
27
- },
28
23
  };
29
24
  }
30
25
 
@@ -3,7 +3,7 @@
3
3
  * Orchestrator service that delegates to specialized layer operation services
4
4
  */
5
5
 
6
- import type { Scene, Animation } from "../../domain/entities/video-project.types";
6
+ import type { Scene } from "../../domain/entities/video-project.types";
7
7
  import type { LayerOperationResult, LayerOrderAction } from "../../domain/entities/video-project.types";
8
8
  import { layerDeleteService } from "./layer-operations/layer-delete.service";
9
9
  import { layerOrderService } from "./layer-operations/layer-order.service";
@@ -72,20 +72,6 @@ class LayerManipulationService {
72
72
  height,
73
73
  );
74
74
  }
75
-
76
- updateLayerAnimation(
77
- scenes: Scene[],
78
- sceneIndex: number,
79
- layerId: string,
80
- animation: Animation | undefined,
81
- ): LayerOperationResult {
82
- return layerTransformService.updateLayerAnimation(
83
- scenes,
84
- sceneIndex,
85
- layerId,
86
- animation,
87
- );
88
- }
89
75
  }
90
76
 
91
77
  export const layerManipulationService = new LayerManipulationService();
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Layer Transform Service
3
- * Single Responsibility: Handle layer position, size, and animation updates
3
+ * Single Responsibility: Handle layer position and size updates
4
4
  */
5
5
 
6
- import type { Scene, Animation } from "../../../domain/entities/video-project.types";
6
+ import type { Scene } from "../../../domain/entities/video-project.types";
7
7
  import type { LayerOperationResult } from "../../../domain/entities/video-project.types";
8
8
 
9
9
  class LayerTransformService {
@@ -114,56 +114,6 @@ class LayerTransformService {
114
114
  };
115
115
  }
116
116
  }
117
-
118
- /**
119
- * Update layer animation
120
- */
121
- updateLayerAnimation(
122
- scenes: Scene[],
123
- sceneIndex: number,
124
- layerId: string,
125
- animation: Animation | undefined,
126
- ): LayerOperationResult {
127
- try {
128
- if (sceneIndex < 0 || sceneIndex >= scenes.length) {
129
- return {
130
- success: false,
131
- updatedScenes: scenes,
132
- error: "Invalid scene index",
133
- };
134
- }
135
-
136
- const updatedScenes = [...scenes];
137
- const layerIndex = updatedScenes[sceneIndex].layers.findIndex(
138
- (l) => l.id === layerId,
139
- );
140
-
141
- if (layerIndex === -1) {
142
- return {
143
- success: false,
144
- updatedScenes: scenes,
145
- error: "Layer not found",
146
- };
147
- }
148
-
149
- const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
150
- updatedScenes[sceneIndex].layers[layerIndex] = {
151
- ...existingLayer,
152
- animation,
153
- };
154
-
155
- return { success: true, updatedScenes };
156
- } catch (error) {
157
- return {
158
- success: false,
159
- updatedScenes: scenes,
160
- error:
161
- error instanceof Error
162
- ? error.message
163
- : "Failed to update layer animation",
164
- };
165
- }
166
- }
167
117
  }
168
118
 
169
119
  export const layerTransformService = new LayerTransformService();
@@ -7,7 +7,7 @@ import { textLayerOperationsService } from "./text-layer-operations.service";
7
7
  import { imageLayerOperationsService } from "./image-layer-operations.service";
8
8
  import { shapeLayerOperationsService } from "./shape-layer-operations.service";
9
9
  import { layerManipulationService } from "./layer-manipulation.service";
10
- import type { Scene, TextLayer, ImageLayer, Animation } from "../../domain/entities/video-project.types";
10
+ import type { Scene, TextLayer, ImageLayer } from "../../domain/entities/video-project.types";
11
11
  import type {
12
12
  LayerOperationResult,
13
13
  LayerOrderAction,
@@ -176,23 +176,6 @@ class LayerOperationsService {
176
176
  height,
177
177
  );
178
178
  }
179
-
180
- /**
181
- * Update layer animation
182
- */
183
- updateLayerAnimation(
184
- scenes: Scene[],
185
- sceneIndex: number,
186
- layerId: string,
187
- animation: Animation | undefined,
188
- ): LayerOperationResult {
189
- return layerManipulationService.updateLayerAnimation(
190
- scenes,
191
- sceneIndex,
192
- layerId,
193
- animation,
194
- );
195
- }
196
179
  }
197
180
 
198
181
  export const layerOperationsService = new LayerOperationsService();
@@ -27,11 +27,6 @@ class ShapeLayerOperationsService extends BaseLayerOperationsService<ShapeLayer>
27
27
  fillColor: layerData.fillColor ?? defaultColor,
28
28
  borderColor: layerData.borderColor,
29
29
  borderWidth: layerData.borderWidth,
30
- animation: {
31
- type: "fade",
32
- duration: 500,
33
- easing: "ease-in-out",
34
- },
35
30
  };
36
31
  }
37
32
 
@@ -29,11 +29,6 @@ class TextLayerOperationsService extends BaseLayerOperationsService<TextLayer> {
29
29
  fontWeight: (layerData.fontWeight as TextLayer["fontWeight"]) || "bold",
30
30
  color: layerData.color || defaultColor,
31
31
  textAlign: layerData.textAlign || "center",
32
- animation: {
33
- type: "fade",
34
- duration: 500,
35
- easing: "ease-in-out",
36
- },
37
32
  };
38
33
  }
39
34
 
@@ -13,7 +13,6 @@ interface LayerActionsMenuProps {
13
13
  layer: Layer;
14
14
  onEditText: () => void;
15
15
  onEditImage: () => void;
16
- onAnimate: () => void;
17
16
  onDuplicate: () => void;
18
17
  onMoveFront: () => void;
19
18
  onMoveUp: () => void;
@@ -26,7 +25,6 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
26
25
  layer,
27
26
  onEditText,
28
27
  onEditImage,
29
- onAnimate,
30
28
  onDuplicate,
31
29
  onMoveFront,
32
30
  onMoveUp,
@@ -59,13 +57,6 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
59
57
 
60
58
  // Common actions
61
59
  items.push(
62
- {
63
- id: "animate",
64
- label: layer.animation
65
- ? (t("editor.layers.actions.editAnimation") || "Edit Animation")
66
- : (t("editor.layers.actions.addAnimation") || "Add Animation"),
67
- icon: "sparkles-outline",
68
- },
69
60
  {
70
61
  id: "duplicate",
71
62
  label: t("editor.layers.actions.duplicate") || "Duplicate",
@@ -111,9 +102,6 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
111
102
  case "edit-image":
112
103
  onEditImage();
113
104
  break;
114
- case "animate":
115
- onAnimate();
116
- break;
117
105
  case "duplicate":
118
106
  onDuplicate();
119
107
  break;
@@ -133,7 +121,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
133
121
  onDelete();
134
122
  break;
135
123
  }
136
- }, [onEditText, onEditImage, onAnimate, onDuplicate, onMoveFront, onMoveUp, onMoveDown, onMoveBack, onDelete]);
124
+ }, [onEditText, onEditImage, onDuplicate, onMoveFront, onMoveUp, onMoveDown, onMoveBack, onDelete]);
137
125
 
138
126
  return <ActionMenu actions={menuItems} onSelect={handleSelect} testID="layer-actions-menu" />;
139
127
  };
@@ -3,7 +3,6 @@
3
3
  * All hooks for the video editor presentation layer
4
4
  */
5
5
 
6
- export { useAnimationForm } from "./useAnimationForm";
7
6
  export { useAudioLayerForm } from "./useAudioLayerForm";
8
7
  export { useCollageEditor } from "./useCollageEditor";
9
8
  export { useDraggableLayerGestures } from "./useDraggableLayerGestures";
@@ -29,7 +29,6 @@ interface UseEditorActionsReturn {
29
29
  handleEditImageLayer: (layerId: string) => void;
30
30
  handleAddShape: () => void;
31
31
  handleAudio: () => void;
32
- handleAnimate: (layerId: string) => void;
33
32
  handleLayerActionsPress: (layer: Layer) => void;
34
33
  handleSceneLongPress: (index: number) => void;
35
34
  handleExport: () => void;
@@ -63,7 +62,6 @@ export function useEditorActions({
63
62
  bottomSheet,
64
63
  handleEditLayer: layerActions.handleEditLayer,
65
64
  handleEditImageLayer: layerActions.handleEditImageLayer,
66
- handleAnimate: layerActions.handleAnimate,
67
65
  });
68
66
 
69
67
  const exportActions = useExportActions({
@@ -84,7 +82,6 @@ export function useEditorActions({
84
82
  handleEditImageLayer: layerActions.handleEditImageLayer,
85
83
  handleAddShape: layerActions.handleAddShape,
86
84
  handleAudio: sceneActions.handleAudio,
87
- handleAnimate: layerActions.handleAnimate,
88
85
  handleLayerActionsPress: menuActions.handleLayerActionsPress,
89
86
  handleSceneLongPress,
90
87
  handleExport: exportActions.handleExport,
@@ -14,7 +14,7 @@ import type {
14
14
  LayerOrderAction,
15
15
  Scene,
16
16
  } from "../../domain/entities/video-project.types";
17
- import type { TextLayer, ImageLayer, Animation } from "../../domain/entities/video-project.types";
17
+ import type { TextLayer, ImageLayer } from "../../domain/entities/video-project.types";
18
18
 
19
19
  interface UseEditorLayersParams {
20
20
  projectId: string;
@@ -37,10 +37,6 @@ export interface UseEditorLayersReturn {
37
37
  duplicateLayer: (layerId: string) => void;
38
38
  updateLayerPosition: (layerId: string, x: number, y: number) => void;
39
39
  updateLayerSize: (layerId: string, width: number, height: number) => void;
40
- updateLayerAnimation: (
41
- layerId: string,
42
- animation: Animation | undefined,
43
- ) => void;
44
40
  }
45
41
 
46
42
  export function useEditorLayers({
@@ -93,6 +89,5 @@ export function useEditorLayers({
93
89
  duplicateLayer: layerManipulation.duplicateLayer,
94
90
  updateLayerPosition: layerManipulation.updateLayerPosition,
95
91
  updateLayerSize: layerManipulation.updateLayerSize,
96
- updateLayerAnimation: layerManipulation.updateLayerAnimation,
97
92
  };
98
93
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * useLayerActions Hook
3
- * Single Responsibility: Layer action handlers (add, edit, animate)
3
+ * Single Responsibility: Layer action handlers (add, edit)
4
4
  */
5
5
 
6
6
  import { useCallback } from "react";
@@ -8,7 +8,6 @@ import { useLocalization } from "@umituz/react-native-settings";
8
8
  import { TextLayerEditor } from "../components/TextLayerEditor";
9
9
  import { ImageLayerEditor } from "../components/ImageLayerEditor";
10
10
  import { ShapeLayerEditor } from "../components/ShapeLayerEditor";
11
- import { AnimationEditor } from "../components/AnimationEditor";
12
11
  import type { Scene, Layer } from "../../domain/entities/video-project.types";
13
12
  import type { UseEditorLayersReturn } from "./useEditorLayers";
14
13
  import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
@@ -26,7 +25,6 @@ interface UseLayerActionsReturn {
26
25
  handleAddImage: () => void;
27
26
  handleEditImageLayer: (layerId: string) => void;
28
27
  handleAddShape: () => void;
29
- handleAnimate: (layerId: string) => void;
30
28
  }
31
29
 
32
30
  export function useLayerActions({
@@ -120,47 +118,11 @@ export function useLayerActions({
120
118
  });
121
119
  }, [layers.addShapeLayer, openBottomSheet, closeBottomSheet, t]);
122
120
 
123
- const handleAnimate = useCallback(
124
- (layerId: string) => {
125
- if (!currentScene) return;
126
- const layer = currentScene.layers.find((l: Layer) => l.id === layerId);
127
- if (!layer) return;
128
-
129
- openBottomSheet({
130
- title: layer.animation
131
- ? t("editor.layers.animation.edit")
132
- : t("editor.layers.animation.add"),
133
- children: (
134
- <AnimationEditor
135
- animation={layer.animation}
136
- onSave={(animation) =>
137
- layers.updateLayerAnimation(layerId, animation)
138
- }
139
- onRemove={
140
- layer.animation
141
- ? () => layers.updateLayerAnimation(layerId, undefined)
142
- : undefined
143
- }
144
- onCancel={closeBottomSheet}
145
- />
146
- ),
147
- });
148
- },
149
- [
150
- currentScene,
151
- layers.updateLayerAnimation,
152
- openBottomSheet,
153
- closeBottomSheet,
154
- t,
155
- ],
156
- );
157
-
158
121
  return {
159
122
  handleAddText,
160
123
  handleEditLayer,
161
124
  handleAddImage,
162
125
  handleEditImageLayer,
163
126
  handleAddShape,
164
- handleAnimate,
165
127
  };
166
128
  }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * useLayerManipulation Hook
3
- * Single Responsibility: Layer manipulation operations (delete, order, duplicate, position, size, animation)
3
+ * Single Responsibility: Layer manipulation operations (delete, order, duplicate, position, size)
4
4
  */
5
5
 
6
6
  import { useCallback } from "react";
7
7
  import { Alert } from "react-native";
8
8
  import { useLocalization } from "@umituz/react-native-settings";
9
9
  import { layerOperationsService } from "../../infrastructure/services/layer-operations.service";
10
- import type { Scene, LayerOrderAction, Animation } from "../../domain/entities/video-project.types";
10
+ import type { Scene, LayerOrderAction } from "../../domain/entities/video-project.types";
11
11
 
12
12
  interface UseLayerManipulationParams {
13
13
  scenes: Scene[];
@@ -23,10 +23,6 @@ interface UseLayerManipulationReturn {
23
23
  duplicateLayer: (layerId: string) => void;
24
24
  updateLayerPosition: (layerId: string, x: number, y: number) => void;
25
25
  updateLayerSize: (layerId: string, width: number, height: number) => void;
26
- updateLayerAnimation: (
27
- layerId: string,
28
- animation: Animation | undefined,
29
- ) => void;
30
26
  }
31
27
 
32
28
  export function useLayerManipulation({
@@ -136,36 +132,11 @@ export function useLayerManipulation({
136
132
  [scenes, sceneIndex, onUpdateScenes],
137
133
  );
138
134
 
139
- const updateLayerAnimation = useCallback(
140
- (layerId: string, animation: Animation | undefined) => {
141
- const result = layerOperationsService.updateLayerAnimation(
142
- scenes,
143
- sceneIndex,
144
- layerId,
145
- animation,
146
- );
147
- if (result.success) {
148
- onUpdateScenes(result.updatedScenes);
149
- onCloseBottomSheet();
150
- Alert.alert(
151
- t("editor.layers.animation.success"),
152
- t(animation
153
- ? "editor.layers.animation.applied"
154
- : "editor.layers.animation.removed"),
155
- );
156
- } else {
157
- Alert.alert(t("editor.layers.animation.error"));
158
- }
159
- },
160
- [scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet, t],
161
- );
162
-
163
135
  return {
164
136
  deleteLayer,
165
137
  changeLayerOrder,
166
138
  duplicateLayer,
167
139
  updateLayerPosition,
168
140
  updateLayerSize,
169
- updateLayerAnimation,
170
141
  };
171
142
  }
@@ -15,7 +15,6 @@ interface UseMenuActionsParams {
15
15
  bottomSheet: UseEditorBottomSheetReturn;
16
16
  handleEditLayer: () => void;
17
17
  handleEditImageLayer: (layerId: string) => void;
18
- handleAnimate: (layerId: string) => void;
19
18
  }
20
19
 
21
20
  interface UseMenuActionsReturn {
@@ -27,7 +26,6 @@ export function useMenuActions({
27
26
  bottomSheet,
28
27
  handleEditLayer,
29
28
  handleEditImageLayer,
30
- handleAnimate,
31
29
  }: UseMenuActionsParams): UseMenuActionsReturn {
32
30
  const { openBottomSheet, closeBottomSheet } = bottomSheet;
33
31
 
@@ -59,11 +57,6 @@ export function useMenuActions({
59
57
  const timer = setTimeout(() => handleEditImageLayer(layer.id), 300);
60
58
  timersRef.current.push(timer);
61
59
  }}
62
- onAnimate={() => {
63
- closeBottomSheet();
64
- const timer = setTimeout(() => handleAnimate(layer.id), 300);
65
- timersRef.current.push(timer);
66
- }}
67
60
  onDuplicate={() => {
68
61
  closeBottomSheet();
69
62
  layers.duplicateLayer(layer.id);
@@ -96,7 +89,6 @@ export function useMenuActions({
96
89
  layers,
97
90
  handleEditLayer,
98
91
  handleEditImageLayer,
99
- handleAnimate,
100
92
  openBottomSheet,
101
93
  closeBottomSheet,
102
94
  ],
@@ -1,273 +0,0 @@
1
- /**
2
- * AnimationEditor Component
3
- * Main component for editing layer animations
4
- */
5
-
6
- import React, { useCallback } from "react";
7
- import { View, ScrollView, StyleSheet } from "react-native";
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";
11
- import type { Animation } from "../../domain/entities/video-project.types";
12
- import { useAnimationForm } from "../hooks/useAnimationForm";
13
- import { EditorActions } from "./text-layer/EditorActions";
14
-
15
- interface AnimationEditorProps {
16
- readonly animation?: Animation;
17
- readonly onSave: (animation: Animation) => void;
18
- readonly onRemove?: () => void;
19
- readonly onCancel: () => void;
20
- }
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
-
38
- export const AnimationEditor: React.FC<AnimationEditorProps> = ({
39
- animation,
40
- onSave,
41
- onRemove,
42
- onCancel,
43
- }) => {
44
- const { t } = useLocalization();
45
- const tokens = useAppDesignTokens();
46
- const {
47
- formState,
48
- setAnimationType,
49
- setDuration,
50
- setDelay,
51
- setEasing,
52
- buildAnimationData,
53
- isValid,
54
- } = useAnimationForm(animation);
55
-
56
- const handleSave = useCallback(() => {
57
- if (!isValid) return;
58
- onSave(buildAnimationData());
59
- }, [isValid, buildAnimationData, onSave]);
60
-
61
- return (
62
- <View style={styles.container}>
63
- <ScrollView showsVerticalScrollIndicator={false}>
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>
99
-
100
- {/* Duration Selector */}
101
- {formState.type !== "none" && (
102
- <>
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>
142
-
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>
183
-
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>
219
- </>
220
- )}
221
- </ScrollView>
222
-
223
- <EditorActions
224
- onCancel={onCancel}
225
- onSave={handleSave}
226
- saveLabel={animation ? "Update Animation" : "Add Animation"}
227
- isValid={isValid}
228
- onRemove={onRemove}
229
- removeLabel="Remove Animation"
230
- />
231
- </View>
232
- );
233
- };
234
-
235
- const styles = StyleSheet.create({
236
- container: {
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",
272
- },
273
- });
@@ -1,98 +0,0 @@
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
- }