@umituz/react-native-video-editor 1.1.47 → 1.1.49

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.
Files changed (71) hide show
  1. package/package.json +5 -1
  2. package/src/VideoEditor.tsx +1 -1
  3. package/src/domain/entities/video-project.types.ts +49 -0
  4. package/src/index.ts +28 -27
  5. package/src/infrastructure/constants/animation-layer.constants.ts +1 -1
  6. package/src/infrastructure/constants/filter.constants.ts +1 -1
  7. package/src/infrastructure/services/image-layer-operations.service.ts +24 -6
  8. package/src/infrastructure/services/layer-manipulation.service.ts +6 -8
  9. package/src/infrastructure/services/layer-operations/layer-delete.service.ts +2 -2
  10. package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +2 -2
  11. package/src/infrastructure/services/layer-operations/layer-order.service.ts +16 -2
  12. package/src/infrastructure/services/layer-operations/layer-transform.service.ts +15 -6
  13. package/src/infrastructure/services/layer-operations.service.ts +2 -2
  14. package/src/infrastructure/services/scene-operations.service.ts +2 -2
  15. package/src/infrastructure/services/shape-layer-operations.service.ts +5 -5
  16. package/src/infrastructure/services/text-layer-operations.service.ts +13 -4
  17. package/src/infrastructure/utils/srt.utils.ts +8 -1
  18. package/src/player/index.ts +0 -3
  19. package/src/player/presentation/components/FullScreenVideoPlayer.tsx +0 -1
  20. package/src/player/presentation/components/VideoPlayer.tsx +8 -7
  21. package/src/player/presentation/components/VideoPlayerOverlay.tsx +1 -1
  22. package/src/player/presentation/hooks/useVideoPlaybackProgress.ts +0 -1
  23. package/src/player/presentation/hooks/useVideoPlayerControl.ts +6 -4
  24. package/src/player/{types/index.ts → types.ts} +3 -2
  25. package/src/presentation/components/AnimationEditor.tsx +4 -6
  26. package/src/presentation/components/AudioEditor.tsx +6 -8
  27. package/src/presentation/components/DraggableLayer.tsx +1 -1
  28. package/src/presentation/components/EditorPreviewArea.tsx +1 -1
  29. package/src/presentation/components/EditorTimeline.tsx +1 -1
  30. package/src/presentation/components/ExportDialog.tsx +7 -9
  31. package/src/presentation/components/ImageLayerEditor.tsx +4 -6
  32. package/src/presentation/components/LayerActionsMenu.tsx +1 -1
  33. package/src/presentation/components/ShapeLayerEditor.tsx +5 -7
  34. package/src/presentation/components/SubtitleListPanel.tsx +1 -1
  35. package/src/presentation/components/TextLayerEditor.tsx +8 -10
  36. package/src/presentation/components/VideoFilterPicker.tsx +1 -1
  37. package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +2 -2
  38. package/src/presentation/components/draggable-layer/LayerContent.tsx +1 -1
  39. package/src/presentation/components/export/ProjectInfoBox.tsx +1 -1
  40. package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +1 -1
  41. package/src/presentation/hooks/useAnimationLayerForm.ts +1 -1
  42. package/src/presentation/hooks/useAudioLayerForm.ts +1 -1
  43. package/src/presentation/hooks/useDraggableLayerGestures.ts +21 -4
  44. package/src/presentation/hooks/useEditorActions.tsx +1 -1
  45. package/src/presentation/hooks/useEditorHistory.ts +5 -4
  46. package/src/presentation/hooks/useEditorLayers.ts +2 -2
  47. package/src/presentation/hooks/useEditorPlayback.ts +1 -1
  48. package/src/presentation/hooks/useEditorScenes.ts +1 -1
  49. package/src/presentation/hooks/useExport.ts +1 -1
  50. package/src/presentation/hooks/useExportActions.tsx +1 -1
  51. package/src/presentation/hooks/useExportForm.ts +1 -1
  52. package/src/presentation/hooks/useImageLayerForm.ts +1 -1
  53. package/src/presentation/hooks/useImageLayerOperations.ts +1 -1
  54. package/src/presentation/hooks/useLayerActions.tsx +3 -5
  55. package/src/presentation/hooks/useLayerManipulation.ts +1 -1
  56. package/src/presentation/hooks/useMenuActions.tsx +1 -1
  57. package/src/presentation/hooks/useSceneActions.tsx +1 -1
  58. package/src/presentation/hooks/useShapeLayerForm.ts +1 -1
  59. package/src/presentation/hooks/useShapeLayerOperations.ts +1 -1
  60. package/src/presentation/hooks/useTextLayerForm.ts +1 -1
  61. package/src/presentation/hooks/useTextLayerOperations.ts +1 -1
  62. package/src/domain/entities/index.ts +0 -52
  63. package/src/infrastructure/constants/index.ts +0 -15
  64. package/src/infrastructure/services/layer-operations/index.ts +0 -9
  65. package/src/presentation/components/animation-layer/index.ts +0 -8
  66. package/src/presentation/components/audio-layer/index.ts +0 -10
  67. package/src/presentation/components/export/index.ts +0 -11
  68. package/src/presentation/components/image-layer/index.ts +0 -8
  69. package/src/presentation/components/index.ts +0 -24
  70. package/src/presentation/components/shape-layer/index.ts +0 -9
  71. package/src/presentation/components/text-layer/index.ts +0 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
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",
@@ -45,6 +45,10 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@gorhom/bottom-sheet": "^5.2.8",
48
+ "@react-navigation/bottom-tabs": "^7.15.5",
49
+ "@react-navigation/elements": "^2.9.10",
50
+ "@react-navigation/native": "^7.1.33",
51
+ "@react-navigation/stack": "^7.8.5",
48
52
  "@types/react": "~19.1.10",
49
53
  "@types/react-native": "*",
50
54
  "@typescript-eslint/eslint-plugin": "*",
@@ -14,7 +14,7 @@ import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area"
14
14
  import { VideoPlayer } from "./player/presentation/components/VideoPlayer";
15
15
  import { VideoFilterPicker } from "./presentation/components/VideoFilterPicker";
16
16
  import { SpeedControlPanel } from "./presentation/components/SpeedControlPanel";
17
- import { FILTER_PRESETS, DEFAULT_FILTER } from "./infrastructure/constants/filter.constants";
17
+ import { DEFAULT_FILTER } from "./infrastructure/constants/filter.constants";
18
18
  import { DEFAULT_PLAYBACK_RATE } from "./infrastructure/constants/speed.constants";
19
19
  import type { FilterPreset } from "./domain/entities/video-project.types";
20
20
 
@@ -174,3 +174,52 @@ export interface VideoProject {
174
174
  folderId?: string;
175
175
  tags: string[];
176
176
  }
177
+
178
+ // Editor State
179
+ export interface EditorState {
180
+ project: VideoProject | null;
181
+ currentSceneIndex: number;
182
+ selectedLayerId: string | null;
183
+ isPlaying: boolean;
184
+ currentTime: number;
185
+ }
186
+
187
+ // Operation Results
188
+ export interface LayerOperationResult {
189
+ success: boolean;
190
+ updatedScenes: Scene[];
191
+ error?: string;
192
+ }
193
+
194
+ export interface SceneOperationResult {
195
+ success: boolean;
196
+ updatedScenes: Scene[];
197
+ newSceneIndex?: number;
198
+ error?: string;
199
+ }
200
+
201
+ // Layer Actions
202
+ export type LayerOrderAction = "front" | "back" | "up" | "down";
203
+
204
+ // Layer Data
205
+ export interface AddTextLayerData {
206
+ content?: string;
207
+ fontSize?: number;
208
+ fontFamily?: string;
209
+ fontWeight?: string;
210
+ color?: string;
211
+ textAlign?: "left" | "center" | "right";
212
+ }
213
+
214
+ export interface AddImageLayerData {
215
+ uri?: string;
216
+ opacity?: number;
217
+ }
218
+
219
+ export interface AddShapeLayerData {
220
+ shape?: string;
221
+ opacity?: number;
222
+ fillColor?: string;
223
+ borderColor?: string;
224
+ borderWidth?: number;
225
+ }
package/src/index.ts CHANGED
@@ -36,13 +36,14 @@ export type {
36
36
  FilterPreset,
37
37
  SubtitleStyle,
38
38
  Subtitle,
39
- } from "./domain/entities";
39
+ } from "./domain/entities/video-project.types";
40
40
 
41
41
  // =============================================================================
42
42
  // INFRASTRUCTURE LAYER - Services & Constants
43
43
  // =============================================================================
44
44
 
45
- export * from "./infrastructure/constants";
45
+ export { FILTER_PRESETS, DEFAULT_FILTER } from "./infrastructure/constants/filter.constants";
46
+ export { DEFAULT_PLAYBACK_RATE, SPEED_PRESETS } from "./infrastructure/constants/speed.constants";
46
47
 
47
48
  export { layerOperationsService } from "./infrastructure/services/layer-operations.service";
48
49
  export { sceneOperationsService } from "./infrastructure/services/scene-operations.service";
@@ -51,12 +52,10 @@ export { imageLayerOperationsService } from "./infrastructure/services/image-lay
51
52
  export { shapeLayerOperationsService } from "./infrastructure/services/shape-layer-operations.service";
52
53
  export { layerManipulationService } from "./infrastructure/services/layer-manipulation.service";
53
54
 
54
- export {
55
- layerDeleteService,
56
- layerDuplicateService,
57
- layerOrderService,
58
- layerTransformService,
59
- } from "./infrastructure/services/layer-operations";
55
+ export { layerDeleteService } from "./infrastructure/services/layer-operations/layer-delete.service";
56
+ export { layerDuplicateService } from "./infrastructure/services/layer-operations/layer-duplicate.service";
57
+ export { layerOrderService } from "./infrastructure/services/layer-operations/layer-order.service";
58
+ export { layerTransformService } from "./infrastructure/services/layer-operations/layer-transform.service";
60
59
 
61
60
  // =============================================================================
62
61
  // PRESENTATION LAYER - Components & Hooks
@@ -65,24 +64,26 @@ export {
65
64
  export { VideoEditor } from "./VideoEditor";
66
65
  export type { VideoEditorProps } from "./VideoEditor";
67
66
 
68
- export {
69
- EditorHeader,
70
- EditorPreviewArea,
71
- EditorToolPanel,
72
- EditorTimeline,
73
- LayerActionsMenu,
74
- SceneActionsMenu,
75
- TextLayerEditor,
76
- AudioEditor,
77
- ShapeLayerEditor,
78
- AnimationEditor,
79
- DraggableLayer,
80
- ImageLayerEditor,
81
- ExportDialog,
82
- SpeedControlPanel,
83
- VideoFilterPicker,
84
- CollageEditorCanvas,
85
- } from "./presentation/components";
67
+ export { EditorHeader } from "./presentation/components/EditorHeader";
68
+ export { EditorPreviewArea } from "./presentation/components/EditorPreviewArea";
69
+ export { EditorToolPanel } from "./presentation/components/EditorToolPanel";
70
+ export { EditorTimeline } from "./presentation/components/EditorTimeline";
71
+ export { LayerActionsMenu } from "./presentation/components/LayerActionsMenu";
72
+ export { SceneActionsMenu } from "./presentation/components/SceneActionsMenu";
73
+ export { TextLayerEditor } from "./presentation/components/TextLayerEditor";
74
+ export { AudioEditor } from "./presentation/components/AudioEditor";
75
+ export { ShapeLayerEditor } from "./presentation/components/ShapeLayerEditor";
76
+ export { AnimationEditor } from "./presentation/components/AnimationEditor";
77
+ export { DraggableLayer } from "./presentation/components/DraggableLayer";
78
+ export { ImageLayerEditor } from "./presentation/components/ImageLayerEditor";
79
+ export { ExportDialog } from "./presentation/components/ExportDialog";
80
+ export { SpeedControlPanel } from "./presentation/components/SpeedControlPanel";
81
+ export { VideoFilterPicker } from "./presentation/components/VideoFilterPicker";
82
+ export { CollageEditorCanvas } from "./presentation/components/CollageEditorCanvas";
83
+ export { SubtitleTimeInput } from "./presentation/components/SubtitleTimeInput";
84
+ export { SubtitleStylePicker } from "./presentation/components/SubtitleStylePicker";
85
+ export { SubtitleOverlay } from "./presentation/components/SubtitleOverlay";
86
+ export { SubtitleListPanel } from "./presentation/components/SubtitleListPanel";
86
87
 
87
88
  export { useEditorLayers } from "./presentation/hooks/useEditorLayers";
88
89
  export { useEditorScenes } from "./presentation/hooks/useEditorScenes";
@@ -131,7 +132,7 @@ export type {
131
132
  VideoProgressBarProps,
132
133
  VideoPlayerOverlayProps,
133
134
  FullScreenVideoPlayerProps,
134
- } from "./player";
135
+ } from "./player/index";
135
136
 
136
137
  export {
137
138
  safePlay,
@@ -3,7 +3,7 @@
3
3
  * Centralized constants for animation layer editor
4
4
  */
5
5
 
6
- import type { AnimationType } from "../../domain/entities";
6
+ import type { AnimationType } from "../../domain/entities/video-project.types";
7
7
 
8
8
  export type Easing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
9
9
 
@@ -3,7 +3,7 @@
3
3
  * Color overlay-based filters for video and photo editing
4
4
  */
5
5
 
6
- import type { FilterPreset } from "../../domain/entities";
6
+ import type { FilterPreset } from "../../domain/entities/video-project.types";
7
7
 
8
8
  export const FILTER_PRESETS: FilterPreset[] = [
9
9
  { id: "none", name: "Original", overlay: "transparent", opacity: 0 },
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, ImageLayer } from "../../domain/entities";
8
- import type { LayerOperationResult, AddImageLayerData } from "../../domain/entities";
7
+ import type { Scene, ImageLayer } from "../../domain/entities/video-project.types";
8
+ import type { LayerOperationResult, AddImageLayerData } from "../../domain/entities/video-project.types";
9
9
 
10
10
  class ImageLayerOperationsService {
11
11
  /**
@@ -25,14 +25,23 @@ class ImageLayerOperationsService {
25
25
  };
26
26
  }
27
27
 
28
+ // Validate URI
29
+ if (!layerData.uri || layerData.uri.trim().length === 0) {
30
+ return {
31
+ success: false,
32
+ updatedScenes: scenes,
33
+ error: "Image URI is required",
34
+ };
35
+ }
36
+
28
37
  const newLayer: ImageLayer = {
29
38
  id: generateUUID(),
30
39
  type: "image",
31
- uri: layerData.uri || "",
40
+ uri: layerData.uri ?? "",
32
41
  position: { x: 15, y: 30 },
33
42
  size: { width: 70, height: 40 },
34
43
  rotation: 0,
35
- opacity: layerData.opacity || 1,
44
+ opacity: layerData.opacity ?? 1,
36
45
  animation: {
37
46
  type: "fade",
38
47
  duration: 500,
@@ -88,10 +97,19 @@ class ImageLayerOperationsService {
88
97
  };
89
98
  }
90
99
 
100
+ const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
101
+ if (existingLayer.type !== "image") {
102
+ return {
103
+ success: false,
104
+ updatedScenes: scenes,
105
+ error: "Layer is not an image layer",
106
+ };
107
+ }
108
+
91
109
  updatedScenes[sceneIndex].layers[layerIndex] = {
92
- ...updatedScenes[sceneIndex].layers[layerIndex],
110
+ ...existingLayer,
93
111
  ...layerData,
94
- } as ImageLayer;
112
+ };
95
113
 
96
114
  return { success: true, updatedScenes };
97
115
  } catch (error) {
@@ -3,14 +3,12 @@
3
3
  * Orchestrator service that delegates to specialized layer operation services
4
4
  */
5
5
 
6
- import type { Scene, Animation } from "../../domain/entities";
7
- import type { LayerOperationResult, LayerOrderAction } from "../../domain/entities";
8
- import {
9
- layerDeleteService,
10
- layerOrderService,
11
- layerDuplicateService,
12
- layerTransformService,
13
- } from "./layer-operations";
6
+ import type { Scene, Animation } from "../../domain/entities/video-project.types";
7
+ import type { LayerOperationResult, LayerOrderAction } from "../../domain/entities/video-project.types";
8
+ import { layerDeleteService } from "./layer-operations/layer-delete.service";
9
+ import { layerOrderService } from "./layer-operations/layer-order.service";
10
+ import { layerDuplicateService } from "./layer-operations/layer-duplicate.service";
11
+ import { layerTransformService } from "./layer-operations/layer-transform.service";
14
12
 
15
13
  class LayerManipulationService {
16
14
  deleteLayer(
@@ -3,8 +3,8 @@
3
3
  * Single Responsibility: Handle layer deletion operations
4
4
  */
5
5
 
6
- import type { Scene } from "../../../domain/entities";
7
- import type { LayerOperationResult } from "../../../domain/entities";
6
+ import type { Scene } from "../../../domain/entities/video-project.types";
7
+ import type { LayerOperationResult } from "../../../domain/entities/video-project.types";
8
8
 
9
9
  class LayerDeleteService {
10
10
  /**
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene } from "../../../domain/entities";
8
- import type { LayerOperationResult } from "../../../domain/entities";
7
+ import type { Scene } from "../../../domain/entities/video-project.types";
8
+ import type { LayerOperationResult } from "../../../domain/entities/video-project.types";
9
9
 
10
10
  class LayerDuplicateService {
11
11
  /**
@@ -3,8 +3,8 @@
3
3
  * Single Responsibility: Handle layer ordering operations
4
4
  */
5
5
 
6
- import type { Scene } from "../../../domain/entities";
7
- import type { LayerOperationResult, LayerOrderAction } from "../../../domain/entities";
6
+ import type { Scene } from "../../../domain/entities/video-project.types";
7
+ import type { LayerOperationResult, LayerOrderAction } from "../../../domain/entities/video-project.types";
8
8
 
9
9
  class LayerOrderService {
10
10
  /**
@@ -48,9 +48,23 @@ class LayerOrderService {
48
48
  newIndex = 0;
49
49
  break;
50
50
  case "up":
51
+ if (updatedLayers.length === 1) {
52
+ return {
53
+ success: false,
54
+ updatedScenes: scenes,
55
+ error: "Cannot move layer - only one layer exists",
56
+ };
57
+ }
51
58
  newIndex = Math.min(layerIndex + 1, updatedLayers.length - 1);
52
59
  break;
53
60
  case "down":
61
+ if (updatedLayers.length === 1) {
62
+ return {
63
+ success: false,
64
+ updatedScenes: scenes,
65
+ error: "Cannot move layer - only one layer exists",
66
+ };
67
+ }
54
68
  newIndex = Math.max(layerIndex - 1, 0);
55
69
  break;
56
70
  }
@@ -3,8 +3,8 @@
3
3
  * Single Responsibility: Handle layer position, size, and animation updates
4
4
  */
5
5
 
6
- import type { Scene, Animation, Layer } from "../../../domain/entities";
7
- import type { LayerOperationResult } from "../../../domain/entities";
6
+ import type { Scene, Animation } from "../../../domain/entities/video-project.types";
7
+ import type { LayerOperationResult } from "../../../domain/entities/video-project.types";
8
8
 
9
9
  class LayerTransformService {
10
10
  /**
@@ -26,6 +26,10 @@ class LayerTransformService {
26
26
  };
27
27
  }
28
28
 
29
+ // Validate position values (percentage: 0-100)
30
+ const clampedX = Math.max(0, Math.min(100, x));
31
+ const clampedY = Math.max(0, Math.min(100, y));
32
+
29
33
  const updatedScenes = [...scenes];
30
34
  const layerIndex = updatedScenes[sceneIndex].layers.findIndex(
31
35
  (l) => l.id === layerId,
@@ -41,7 +45,7 @@ class LayerTransformService {
41
45
 
42
46
  updatedScenes[sceneIndex].layers[layerIndex] = {
43
47
  ...updatedScenes[sceneIndex].layers[layerIndex],
44
- position: { x, y },
48
+ position: { x: clampedX, y: clampedY },
45
49
  };
46
50
 
47
51
  return { success: true, updatedScenes };
@@ -76,6 +80,10 @@ class LayerTransformService {
76
80
  };
77
81
  }
78
82
 
83
+ // Validate size values (percentage: 1-100, minimum 1% to prevent invisible layers)
84
+ const clampedWidth = Math.max(1, Math.min(100, width));
85
+ const clampedHeight = Math.max(1, Math.min(100, height));
86
+
79
87
  const updatedScenes = [...scenes];
80
88
  const layerIndex = updatedScenes[sceneIndex].layers.findIndex(
81
89
  (l) => l.id === layerId,
@@ -91,7 +99,7 @@ class LayerTransformService {
91
99
 
92
100
  updatedScenes[sceneIndex].layers[layerIndex] = {
93
101
  ...updatedScenes[sceneIndex].layers[layerIndex],
94
- size: { width, height },
102
+ size: { width: clampedWidth, height: clampedHeight },
95
103
  };
96
104
 
97
105
  return { success: true, updatedScenes };
@@ -138,10 +146,11 @@ class LayerTransformService {
138
146
  };
139
147
  }
140
148
 
149
+ const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
141
150
  updatedScenes[sceneIndex].layers[layerIndex] = {
142
- ...updatedScenes[sceneIndex].layers[layerIndex],
151
+ ...existingLayer,
143
152
  animation,
144
- } as Layer;
153
+ };
145
154
 
146
155
  return { success: true, updatedScenes };
147
156
  } catch (error) {
@@ -7,14 +7,14 @@ 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";
10
+ import type { Scene, TextLayer, ImageLayer, Animation } from "../../domain/entities/video-project.types";
11
11
  import type {
12
12
  LayerOperationResult,
13
13
  LayerOrderAction,
14
14
  AddTextLayerData,
15
15
  AddImageLayerData,
16
16
  AddShapeLayerData,
17
- } from "../../domain/entities";
17
+ } from "../../domain/entities/video-project.types";
18
18
 
19
19
  class LayerOperationsService {
20
20
  /**
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, Audio } from "../../domain/entities";
8
- import type { SceneOperationResult } from "../../domain/entities";
7
+ import type { Scene, Audio } from "../../domain/entities/video-project.types";
8
+ import type { SceneOperationResult } from "../../domain/entities/video-project.types";
9
9
 
10
10
  class SceneOperationsService {
11
11
  /**
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, ShapeLayer } from "../../domain/entities";
8
- import type { LayerOperationResult, AddShapeLayerData } from "../../domain/entities";
7
+ import type { Scene, ShapeLayer } from "../../domain/entities/video-project.types";
8
+ import type { LayerOperationResult, AddShapeLayerData } from "../../domain/entities/video-project.types";
9
9
 
10
10
  class ShapeLayerOperationsService {
11
11
  /**
@@ -29,12 +29,12 @@ class ShapeLayerOperationsService {
29
29
  const newLayer: ShapeLayer = {
30
30
  id: generateUUID(),
31
31
  type: "shape",
32
- shape: (layerData.shape as ShapeLayer["shape"]) || "rectangle",
32
+ shape: (layerData.shape ?? "rectangle") as ShapeLayer["shape"],
33
33
  position: { x: 25, y: 25 },
34
34
  size: { width: 50, height: 50 },
35
35
  rotation: 0,
36
- opacity: layerData.opacity || 1,
37
- fillColor: layerData.fillColor || defaultColor,
36
+ opacity: layerData.opacity ?? 1,
37
+ fillColor: layerData.fillColor ?? defaultColor,
38
38
  borderColor: layerData.borderColor,
39
39
  borderWidth: layerData.borderWidth,
40
40
  animation: {
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, TextLayer } from "../../domain/entities";
8
- import type { LayerOperationResult, AddTextLayerData } from "../../domain/entities";
7
+ import type { Scene, TextLayer } from "../../domain/entities/video-project.types";
8
+ import type { LayerOperationResult, AddTextLayerData } from "../../domain/entities/video-project.types";
9
9
 
10
10
  class TextLayerOperationsService {
11
11
  /**
@@ -94,10 +94,19 @@ class TextLayerOperationsService {
94
94
  };
95
95
  }
96
96
 
97
+ const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
98
+ if (existingLayer.type !== "text") {
99
+ return {
100
+ success: false,
101
+ updatedScenes: scenes,
102
+ error: "Layer is not a text layer",
103
+ };
104
+ }
105
+
97
106
  updatedScenes[sceneIndex].layers[layerIndex] = {
98
- ...updatedScenes[sceneIndex].layers[layerIndex],
107
+ ...existingLayer,
99
108
  ...layerData,
100
- } as TextLayer;
109
+ };
101
110
 
102
111
  return { success: true, updatedScenes };
103
112
  } catch (error) {
@@ -13,7 +13,14 @@ function toSrtTime(seconds: number): string {
13
13
  }
14
14
 
15
15
  export function generateSRT(subtitles: Subtitle[]): string {
16
- const sorted = [...subtitles].sort((a, b) => a.startTime - b.startTime);
16
+ const validSubtitles = subtitles.filter((sub) => (
17
+ sub.startTime >= 0 &&
18
+ sub.endTime >= 0 &&
19
+ sub.startTime < sub.endTime &&
20
+ sub.text.trim().length > 0
21
+ ));
22
+
23
+ const sorted = [...validSubtitles].sort((a, b) => a.startTime - b.startTime);
17
24
  return sorted
18
25
  .map((sub, index) => `${index + 1}\n${toSrtTime(sub.startTime)} --> ${toSrtTime(sub.endTime)}\n${sub.text}\n`)
19
26
  .join("\n");
@@ -12,9 +12,6 @@ export type {
12
12
  VideoVisibilityConfig,
13
13
  VideoPlayerProps,
14
14
  VideoPlayer as VideoPlayerType,
15
- VideoDownloadProgressCallback,
16
- VideoCacheResult,
17
- VideoCachingState,
18
15
  PlaybackProgressState,
19
16
  ControlsAutoHideConfig,
20
17
  ControlsAutoHideResult,
@@ -7,7 +7,6 @@ import React, { useMemo, useCallback } from "react";
7
7
  import { View, Modal, StyleSheet, StatusBar } from "react-native";
8
8
  import { Image } from "expo-image";
9
9
  // expo-video is optional — lazy require
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
10
  let VideoView: React.ComponentType<any> = () => null;
12
11
  try {
13
12
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -7,7 +7,6 @@ import React, { useState, useCallback, useMemo, useEffect } from "react";
7
7
  import { View, TouchableOpacity, StyleSheet, type ViewStyle } from "react-native";
8
8
  import { Image } from "expo-image";
9
9
  // expo-video is optional — lazy require so it is not auto-installed
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
10
  let VideoView: React.ComponentType<any> = () => null;
12
11
  try {
13
12
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -93,7 +92,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
93
92
  // Calculate fallback dimensions only when style doesn't provide sizing
94
93
  const customSizing = hasCustomSizing(style as ViewStyle);
95
94
  const videoWidth = customSizing ? undefined : (screenWidth - horizontalPadding * 2);
96
- const videoHeight = videoWidth ? videoWidth / DEFAULT_ASPECT_RATIO : undefined;
95
+ const videoHeight = videoWidth !== undefined ? videoWidth / DEFAULT_ASPECT_RATIO : undefined;
97
96
 
98
97
  const containerStyle = useMemo(() => ({
99
98
  ...(videoWidth !== undefined && { width: videoWidth }),
@@ -104,15 +103,17 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
104
103
  }), [tokens.colors.surface, videoWidth, videoHeight]);
105
104
 
106
105
  const styles = useMemo(() => StyleSheet.create({
107
- video: videoWidth !== undefined
108
- ? { width: videoWidth, height: videoHeight! }
106
+ video: videoWidth !== undefined && videoHeight !== undefined
107
+ ? { width: videoWidth, height: videoHeight }
109
108
  : { width: "100%", height: "100%" },
110
109
  thumbnailContainer: { flex: 1, justifyContent: "center", alignItems: "center" },
111
- thumbnail: videoWidth !== undefined
112
- ? { width: videoWidth, height: videoHeight! }
110
+ thumbnail: videoWidth !== undefined && videoHeight !== undefined
111
+ ? { width: videoWidth, height: videoHeight }
113
112
  : { width: "100%", height: "100%" },
114
113
  placeholder: {
115
- ...(videoWidth !== undefined ? { width: videoWidth, height: videoHeight! } : { flex: 1, width: "100%" }),
114
+ ...(videoWidth !== undefined && videoHeight !== undefined
115
+ ? { width: videoWidth, height: videoHeight }
116
+ : { flex: 1, width: "100%" }),
116
117
  backgroundColor: tokens.colors.surfaceSecondary,
117
118
  },
118
119
  playButtonContainer: { ...StyleSheet.absoluteFillObject, justifyContent: "center", alignItems: "center" },
@@ -3,7 +3,7 @@
3
3
  * Custom overlay controls: top bar (title, back), center play/pause, bottom bar (progress, mute)
4
4
  */
5
5
 
6
- import React, { useMemo } from "react";
6
+ import React from "react";
7
7
  import { View, TouchableOpacity, StyleSheet, TouchableWithoutFeedback } from "react-native";
8
8
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system/atoms";
9
9
 
@@ -14,7 +14,6 @@ const POLL_INTERVAL_MS = 250;
14
14
  * Also returns the player's actual playing state to detect out-of-sync conditions
15
15
  */
16
16
  export const useVideoPlaybackProgress = (
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
17
  player: any,
19
18
  isPlayerValid: boolean,
20
19
  isPlaying: boolean,
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { useState, useCallback, useMemo } from "react";
7
7
  // expo-video is optional — module-level lazy require with null stub
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
8
  let useExpoVideoPlayer: (...args: any[]) => any = () => null;
10
9
  try {
11
10
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -20,6 +19,11 @@ import type {
20
19
  VideoPlayerControls,
21
20
  UseVideoPlayerControlResult,
22
21
  } from "../../types";
22
+
23
+ // Extend VideoPlayer type to include playbackRate property
24
+ interface ExtendedVideoPlayer {
25
+ playbackRate?: number;
26
+ }
23
27
  import {
24
28
  safePlay,
25
29
  safePause,
@@ -45,7 +49,6 @@ export const useVideoPlayerControl = (
45
49
  const [playbackRate, setPlaybackRateState] = useState(initialRate);
46
50
  const [isMuted, setIsMuted] = useState(muted);
47
51
 
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
52
  const player = useExpoVideoPlayer(source || "", (p: any) => {
50
53
  if (source && p) {
51
54
  configurePlayer(p, { loop, muted, autoPlay });
@@ -94,8 +97,7 @@ export const useVideoPlayerControl = (
94
97
 
95
98
  const setPlaybackRate = useCallback((rate: number) => {
96
99
  if (!isPlayerValid || !player) return;
97
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
- (player as any).playbackRate = rate;
100
+ (player as ExtendedVideoPlayer).playbackRate = rate;
99
101
  setPlaybackRateState(rate);
100
102
  }, [player, isPlayerValid]);
101
103
 
@@ -168,5 +168,6 @@ export type { VideoPlayer } from "expo-video";
168
168
  export type {
169
169
  VideoDownloadProgressCallback,
170
170
  VideoCacheResult,
171
- } from "../infrastructure/services/video-cache.service";
172
- export type { VideoCachingState } from "../presentation/hooks/useVideoCaching";
171
+ } from "./infrastructure/services/video-cache.service";
172
+
173
+ export type { VideoCachingState } from "./presentation/hooks/useVideoCaching";