@umituz/react-native-video-editor 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +92 -0
  2. package/package.json +48 -0
  3. package/src/domain/entities/index.ts +50 -0
  4. package/src/domain/entities/video-project.types.ts +153 -0
  5. package/src/index.ts +100 -0
  6. package/src/infrastructure/constants/animation-layer.constants.ts +32 -0
  7. package/src/infrastructure/constants/audio-layer.constants.ts +14 -0
  8. package/src/infrastructure/constants/export.constants.ts +28 -0
  9. package/src/infrastructure/constants/image-layer.constants.ts +12 -0
  10. package/src/infrastructure/constants/index.ts +11 -0
  11. package/src/infrastructure/constants/shape-layer.constants.ts +29 -0
  12. package/src/infrastructure/constants/text-layer.constants.ts +40 -0
  13. package/src/infrastructure/services/export-orchestrator.service.ts +122 -0
  14. package/src/infrastructure/services/image-layer-operations.service.ts +108 -0
  15. package/src/infrastructure/services/layer-manipulation.service.ts +93 -0
  16. package/src/infrastructure/services/layer-operations/index.ts +9 -0
  17. package/src/infrastructure/services/layer-operations/layer-delete.service.ts +47 -0
  18. package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +66 -0
  19. package/src/infrastructure/services/layer-operations/layer-order.service.ts +82 -0
  20. package/src/infrastructure/services/layer-operations/layer-transform.service.ts +160 -0
  21. package/src/infrastructure/services/layer-operations.service.ts +198 -0
  22. package/src/infrastructure/services/scene-operations.service.ts +166 -0
  23. package/src/infrastructure/services/shape-layer-operations.service.ts +65 -0
  24. package/src/infrastructure/services/text-layer-operations.service.ts +114 -0
  25. package/src/presentation/components/AnimationEditor.tsx +103 -0
  26. package/src/presentation/components/AudioEditor.tsx +144 -0
  27. package/src/presentation/components/DraggableLayer.tsx +110 -0
  28. package/src/presentation/components/EditorHeader.tsx +107 -0
  29. package/src/presentation/components/EditorPreviewArea.tsx +221 -0
  30. package/src/presentation/components/EditorTimeline.tsx +136 -0
  31. package/src/presentation/components/EditorToolPanel.tsx +180 -0
  32. package/src/presentation/components/ExportDialog.tsx +135 -0
  33. package/src/presentation/components/ImageLayerEditor.tsx +95 -0
  34. package/src/presentation/components/LayerActionsMenu.tsx +197 -0
  35. package/src/presentation/components/SceneActionsMenu.tsx +69 -0
  36. package/src/presentation/components/ShapeLayerEditor.tsx +108 -0
  37. package/src/presentation/components/TextLayerEditor.tsx +104 -0
  38. package/src/presentation/components/animation-layer/AnimationEditorActions.tsx +104 -0
  39. package/src/presentation/components/animation-layer/AnimationInfoBanner.tsx +43 -0
  40. package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +105 -0
  41. package/src/presentation/components/animation-layer/index.ts +8 -0
  42. package/src/presentation/components/audio-layer/AudioEditorActions.tsx +115 -0
  43. package/src/presentation/components/audio-layer/AudioFileSelector.tsx +126 -0
  44. package/src/presentation/components/audio-layer/FadeEffectsSelector.tsx +151 -0
  45. package/src/presentation/components/audio-layer/InfoBanner.tsx +43 -0
  46. package/src/presentation/components/audio-layer/VolumeSelector.tsx +98 -0
  47. package/src/presentation/components/audio-layer/index.ts +10 -0
  48. package/src/presentation/components/draggable-layer/LayerContent.tsx +106 -0
  49. package/src/presentation/components/draggable-layer/ResizeHandles.tsx +97 -0
  50. package/src/presentation/components/draggable-layer/index.ts +7 -0
  51. package/src/presentation/components/export/ExportActions.tsx +101 -0
  52. package/src/presentation/components/export/ExportInfoBanner.tsx +44 -0
  53. package/src/presentation/components/export/ExportProgress.tsx +114 -0
  54. package/src/presentation/components/export/OptionSelectorRow.tsx +101 -0
  55. package/src/presentation/components/export/ProjectInfoBox.tsx +61 -0
  56. package/src/presentation/components/export/WatermarkToggle.tsx +87 -0
  57. package/src/presentation/components/export/index.ts +11 -0
  58. package/src/presentation/components/image-layer/ImagePreview.tsx +70 -0
  59. package/src/presentation/components/image-layer/ImageSelectionButtons.tsx +82 -0
  60. package/src/presentation/components/image-layer/OpacitySelector.tsx +91 -0
  61. package/src/presentation/components/image-layer/index.ts +8 -0
  62. package/src/presentation/components/index.ts +17 -0
  63. package/src/presentation/components/shape-layer/ColorPickerHorizontal.tsx +92 -0
  64. package/src/presentation/components/shape-layer/ShapePreview.tsx +57 -0
  65. package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +102 -0
  66. package/src/presentation/components/shape-layer/ValueSelector.tsx +106 -0
  67. package/src/presentation/components/shape-layer/index.ts +9 -0
  68. package/src/presentation/components/text-layer/ColorPicker.tsx +91 -0
  69. package/src/presentation/components/text-layer/EditorActions.tsx +95 -0
  70. package/src/presentation/components/text-layer/FontSizeSelector.tsx +86 -0
  71. package/src/presentation/components/text-layer/OptionSelector.tsx +98 -0
  72. package/src/presentation/components/text-layer/TextAlignSelector.tsx +87 -0
  73. package/src/presentation/components/text-layer/TextInputSection.tsx +70 -0
  74. package/src/presentation/components/text-layer/TextPreview.tsx +71 -0
  75. package/src/presentation/components/text-layer/index.ts +12 -0
  76. package/src/presentation/hooks/useAnimationLayerForm.ts +72 -0
  77. package/src/presentation/hooks/useAudioLayerForm.ts +76 -0
  78. package/src/presentation/hooks/useDraggableLayerGestures.ts +166 -0
  79. package/src/presentation/hooks/useEditorActions.tsx +93 -0
  80. package/src/presentation/hooks/useEditorBottomSheet.ts +43 -0
  81. package/src/presentation/hooks/useEditorHistory.ts +80 -0
  82. package/src/presentation/hooks/useEditorLayers.ts +97 -0
  83. package/src/presentation/hooks/useEditorPlayback.ts +90 -0
  84. package/src/presentation/hooks/useEditorScenes.ts +106 -0
  85. package/src/presentation/hooks/useExport.ts +67 -0
  86. package/src/presentation/hooks/useExportActions.tsx +51 -0
  87. package/src/presentation/hooks/useExportForm.ts +96 -0
  88. package/src/presentation/hooks/useImageLayerForm.ts +57 -0
  89. package/src/presentation/hooks/useImageLayerOperations.ts +71 -0
  90. package/src/presentation/hooks/useLayerActions.tsx +162 -0
  91. package/src/presentation/hooks/useLayerManipulation.ts +178 -0
  92. package/src/presentation/hooks/useMenuActions.tsx +92 -0
  93. package/src/presentation/hooks/useSceneActions.tsx +81 -0
  94. package/src/presentation/hooks/useShapeLayerForm.ts +84 -0
  95. package/src/presentation/hooks/useShapeLayerOperations.ts +52 -0
  96. package/src/presentation/hooks/useTextLayerForm.ts +100 -0
  97. package/src/presentation/hooks/useTextLayerOperations.ts +74 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * useEditorActions Hook
3
+ * Single Responsibility: Compose editor action handlers
4
+ */
5
+
6
+ import type { VideoProject } from "@domains/video";
7
+ import type { UseEditorLayersReturn } from "./useEditorLayers";
8
+ import type { UseEditorScenesReturn } from "./useEditorScenes";
9
+ import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
10
+ import { useLayerActions } from "./useLayerActions";
11
+ import { useSceneActions } from "./useSceneActions";
12
+ import { useMenuActions } from "./useMenuActions";
13
+ import { useExportActions } from "./useExportActions";
14
+
15
+ export interface UseEditorActionsParams {
16
+ project: VideoProject | undefined;
17
+ selectedLayerId: string | null;
18
+ currentScene: any;
19
+ layers: UseEditorLayersReturn;
20
+ scenes: UseEditorScenesReturn;
21
+ bottomSheet: UseEditorBottomSheetReturn;
22
+ onExportComplete: (settings: any, uri?: string) => void;
23
+ }
24
+
25
+ export interface UseEditorActionsReturn {
26
+ handleAddText: () => void;
27
+ handleEditLayer: () => void;
28
+ handleAddImage: () => void;
29
+ handleEditImageLayer: (layerId: string) => void;
30
+ handleAddShape: () => void;
31
+ handleAudio: () => void;
32
+ handleAnimate: (layerId: string) => void;
33
+ handleLayerActionsPress: (layer: any) => void;
34
+ handleSceneLongPress: (index: number) => void;
35
+ handleExport: () => void;
36
+ handleSave: () => void;
37
+ }
38
+
39
+ export function useEditorActions({
40
+ project,
41
+ selectedLayerId,
42
+ currentScene,
43
+ layers,
44
+ scenes,
45
+ bottomSheet,
46
+ onExportComplete,
47
+ }: UseEditorActionsParams): UseEditorActionsReturn {
48
+ const layerActions = useLayerActions({
49
+ selectedLayerId,
50
+ currentScene,
51
+ layers,
52
+ bottomSheet,
53
+ });
54
+
55
+ const sceneActions = useSceneActions({
56
+ currentScene,
57
+ scenes,
58
+ bottomSheet,
59
+ });
60
+
61
+ const menuActions = useMenuActions({
62
+ layers,
63
+ bottomSheet,
64
+ handleEditLayer: layerActions.handleEditLayer,
65
+ handleEditImageLayer: layerActions.handleEditImageLayer,
66
+ handleAnimate: layerActions.handleAnimate,
67
+ });
68
+
69
+ const exportActions = useExportActions({
70
+ project,
71
+ bottomSheet,
72
+ onExportComplete,
73
+ });
74
+
75
+ const handleSceneLongPress = (index: number) => {
76
+ if (!project) return;
77
+ sceneActions.handleSceneLongPress(index, project.scenes.length > 1);
78
+ };
79
+
80
+ return {
81
+ handleAddText: layerActions.handleAddText,
82
+ handleEditLayer: layerActions.handleEditLayer,
83
+ handleAddImage: layerActions.handleAddImage,
84
+ handleEditImageLayer: layerActions.handleEditImageLayer,
85
+ handleAddShape: layerActions.handleAddShape,
86
+ handleAudio: sceneActions.handleAudio,
87
+ handleAnimate: layerActions.handleAnimate,
88
+ handleLayerActionsPress: menuActions.handleLayerActionsPress,
89
+ handleSceneLongPress,
90
+ handleExport: exportActions.handleExport,
91
+ handleSave: exportActions.handleSave,
92
+ };
93
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * useEditorBottomSheet Hook
3
+ * Single Responsibility: Bottom sheet state management
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+
9
+ export interface BottomSheetContent {
10
+ title: string;
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ export interface UseEditorBottomSheetReturn {
15
+ isOpen: boolean;
16
+ content: BottomSheetContent | null;
17
+ openBottomSheet: (content: BottomSheetContent) => void;
18
+ closeBottomSheet: () => void;
19
+ }
20
+
21
+ export function useEditorBottomSheet(): UseEditorBottomSheetReturn {
22
+ const [isOpen, setIsOpen] = useState(false);
23
+ const [content, setContent] = useState<BottomSheetContent | null>(null);
24
+
25
+ const openBottomSheet = useCallback((sheetContent: BottomSheetContent) => {
26
+ setContent(sheetContent);
27
+ setIsOpen(true);
28
+ // TODO: Implement actual bottom sheet
29
+ // For now, using Alert as fallback
30
+ }, []);
31
+
32
+ const closeBottomSheet = useCallback(() => {
33
+ setIsOpen(false);
34
+ setContent(null);
35
+ }, []);
36
+
37
+ return {
38
+ isOpen,
39
+ content,
40
+ openBottomSheet,
41
+ closeBottomSheet,
42
+ };
43
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * useEditorHistory Hook
3
+ * Single Responsibility: History operations for editor
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+ // TODO: Refactor to use TanStack Query instead of store
9
+ // Temporary stub until refactor
10
+ const useHistoryStore = () => ({
11
+ addToHistory: () => {},
12
+ pushHistory: (_project: any, _action: string) => {},
13
+ undo: () => undefined,
14
+ redo: () => undefined,
15
+ canUndo: () => false,
16
+ canRedo: () => false,
17
+ });
18
+ import type { VideoProject } from "@domains/video";
19
+
20
+ export interface UseEditorHistoryParams {
21
+ project: VideoProject | undefined;
22
+ projectId: string;
23
+ onUpdateProject: (updates: Partial<VideoProject>) => void;
24
+ }
25
+
26
+ export interface UseEditorHistoryReturn {
27
+ undo: () => void;
28
+ redo: () => void;
29
+ canUndo: boolean;
30
+ canRedo: boolean;
31
+ updateWithHistory: (updates: Partial<VideoProject>, action: string) => void;
32
+ }
33
+
34
+ export function useEditorHistory({
35
+ project,
36
+ projectId,
37
+ onUpdateProject,
38
+ }: UseEditorHistoryParams): UseEditorHistoryReturn {
39
+ const {
40
+ pushHistory,
41
+ undo: historyUndo,
42
+ redo: historyRedo,
43
+ canUndo,
44
+ canRedo,
45
+ } = useHistoryStore();
46
+
47
+ const updateWithHistory = useCallback(
48
+ (updates: Partial<VideoProject>, action: string) => {
49
+ if (project) {
50
+ pushHistory(project, action);
51
+ onUpdateProject(updates);
52
+ }
53
+ },
54
+ [project, pushHistory, onUpdateProject],
55
+ );
56
+
57
+ const undo = useCallback(() => {
58
+ const previousState = historyUndo();
59
+ if (previousState) {
60
+ onUpdateProject(previousState);
61
+ Alert.alert("Undo", "Action undone");
62
+ }
63
+ }, [historyUndo, onUpdateProject]);
64
+
65
+ const redo = useCallback(() => {
66
+ const nextState = historyRedo();
67
+ if (nextState) {
68
+ onUpdateProject(nextState);
69
+ Alert.alert("Redo", "Action redone");
70
+ }
71
+ }, [historyRedo, onUpdateProject]);
72
+
73
+ return {
74
+ undo,
75
+ redo,
76
+ canUndo: canUndo(),
77
+ canRedo: canRedo(),
78
+ updateWithHistory,
79
+ };
80
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * useEditorLayers Hook
3
+ * Single Responsibility: Compose layer operations
4
+ */
5
+
6
+ import { useTextLayerOperations } from "./useTextLayerOperations";
7
+ import { useImageLayerOperations } from "./useImageLayerOperations";
8
+ import { useShapeLayerOperations } from "./useShapeLayerOperations";
9
+ import { useLayerManipulation } from "./useLayerManipulation";
10
+ import type {
11
+ AddTextLayerData,
12
+ AddImageLayerData,
13
+ AddShapeLayerData,
14
+ LayerOrderAction,
15
+ } from "../types";
16
+ import type { TextLayer, ImageLayer, Animation } from "@domains/video";
17
+
18
+ export interface UseEditorLayersParams {
19
+ projectId: string;
20
+ scenes: any[];
21
+ sceneIndex: number;
22
+ onUpdateScenes: (scenes: any[]) => void;
23
+ onCloseBottomSheet: () => void;
24
+ defaultColor: string;
25
+ onLayerDeleted?: () => void;
26
+ }
27
+
28
+ export interface UseEditorLayersReturn {
29
+ addTextLayer: (data: AddTextLayerData) => void;
30
+ editTextLayer: (layerId: string, data: Partial<TextLayer>) => void;
31
+ addImageLayer: (data: AddImageLayerData) => void;
32
+ editImageLayer: (layerId: string, data: Partial<ImageLayer>) => void;
33
+ addShapeLayer: (data: AddShapeLayerData) => void;
34
+ deleteLayer: (layerId: string) => void;
35
+ changeLayerOrder: (layerId: string, action: LayerOrderAction) => void;
36
+ duplicateLayer: (layerId: string) => void;
37
+ updateLayerPosition: (layerId: string, x: number, y: number) => void;
38
+ updateLayerSize: (layerId: string, width: number, height: number) => void;
39
+ updateLayerAnimation: (
40
+ layerId: string,
41
+ animation: Animation | undefined,
42
+ ) => void;
43
+ }
44
+
45
+ export function useEditorLayers({
46
+ scenes,
47
+ sceneIndex,
48
+ onUpdateScenes,
49
+ onCloseBottomSheet,
50
+ defaultColor,
51
+ onLayerDeleted,
52
+ }: UseEditorLayersParams): UseEditorLayersReturn {
53
+ const textLayerOps = useTextLayerOperations({
54
+ scenes,
55
+ sceneIndex,
56
+ onUpdateScenes,
57
+ onCloseBottomSheet,
58
+ defaultColor,
59
+ });
60
+
61
+ const imageLayerOps = useImageLayerOperations({
62
+ scenes,
63
+ sceneIndex,
64
+ onUpdateScenes,
65
+ onCloseBottomSheet,
66
+ });
67
+
68
+ const shapeLayerOps = useShapeLayerOperations({
69
+ scenes,
70
+ sceneIndex,
71
+ onUpdateScenes,
72
+ onCloseBottomSheet,
73
+ defaultColor,
74
+ });
75
+
76
+ const layerManipulation = useLayerManipulation({
77
+ scenes,
78
+ sceneIndex,
79
+ onUpdateScenes,
80
+ onCloseBottomSheet,
81
+ onLayerDeleted,
82
+ });
83
+
84
+ return {
85
+ addTextLayer: textLayerOps.addTextLayer,
86
+ editTextLayer: textLayerOps.editTextLayer,
87
+ addImageLayer: imageLayerOps.addImageLayer,
88
+ editImageLayer: imageLayerOps.editImageLayer,
89
+ addShapeLayer: shapeLayerOps.addShapeLayer,
90
+ deleteLayer: layerManipulation.deleteLayer,
91
+ changeLayerOrder: layerManipulation.changeLayerOrder,
92
+ duplicateLayer: layerManipulation.duplicateLayer,
93
+ updateLayerPosition: layerManipulation.updateLayerPosition,
94
+ updateLayerSize: layerManipulation.updateLayerSize,
95
+ updateLayerAnimation: layerManipulation.updateLayerAnimation,
96
+ };
97
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * useEditorPlayback Hook
3
+ * Single Responsibility: Playback control for editor
4
+ */
5
+
6
+ import { useState, useEffect, useCallback } from "react";
7
+ import type { Scene } from "@domains/video";
8
+
9
+ export interface UseEditorPlaybackParams {
10
+ currentScene: Scene | undefined;
11
+ }
12
+
13
+ export interface UseEditorPlaybackReturn {
14
+ isPlaying: boolean;
15
+ currentTime: number;
16
+ playPause: () => void;
17
+ reset: () => void;
18
+ setCurrentTime: (time: number) => void;
19
+ }
20
+
21
+ export function useEditorPlayback({
22
+ currentScene,
23
+ }: UseEditorPlaybackParams): UseEditorPlaybackReturn {
24
+ const [isPlaying, setIsPlaying] = useState(false);
25
+ const [currentTime, setCurrentTime] = useState(0);
26
+
27
+ // Video playback animation loop
28
+ useEffect(() => {
29
+ if (!currentScene) return;
30
+ let animationFrameId: number;
31
+ let lastTimestamp = 0;
32
+
33
+ const animate = (timestamp: number) => {
34
+ if (!isPlaying || !currentScene) return;
35
+
36
+ if (lastTimestamp === 0) {
37
+ lastTimestamp = timestamp;
38
+ }
39
+ const deltaTime = timestamp - lastTimestamp;
40
+ lastTimestamp = timestamp;
41
+
42
+ setCurrentTime((prevTime) => {
43
+ const newTime = prevTime + deltaTime;
44
+ const sceneDuration = currentScene.duration;
45
+
46
+ if (newTime >= sceneDuration) {
47
+ setIsPlaying(false);
48
+ return sceneDuration;
49
+ }
50
+
51
+ return newTime;
52
+ });
53
+
54
+ animationFrameId = requestAnimationFrame(animate);
55
+ };
56
+
57
+ if (isPlaying) {
58
+ lastTimestamp = 0;
59
+ animationFrameId = requestAnimationFrame(animate);
60
+ }
61
+
62
+ return () => {
63
+ if (animationFrameId) {
64
+ cancelAnimationFrame(animationFrameId);
65
+ }
66
+ };
67
+ }, [isPlaying, currentScene?.duration]);
68
+
69
+ const playPause = useCallback(() => {
70
+ if (!currentScene) return;
71
+
72
+ if (currentTime >= currentScene.duration) {
73
+ setCurrentTime(0);
74
+ }
75
+ setIsPlaying(!isPlaying);
76
+ }, [currentScene, currentTime, isPlaying]);
77
+
78
+ const reset = useCallback(() => {
79
+ setCurrentTime(0);
80
+ setIsPlaying(false);
81
+ }, []);
82
+
83
+ return {
84
+ isPlaying,
85
+ currentTime,
86
+ playPause,
87
+ reset,
88
+ setCurrentTime,
89
+ };
90
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * useEditorScenes Hook
3
+ * Single Responsibility: Scene operations for editor
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+ import { sceneOperationsService } from "../infrastructure/services/scene-operations.service";
9
+ import type { Audio } from "@domains/video";
10
+
11
+ export interface UseEditorScenesParams {
12
+ scenes: any[];
13
+ currentSceneIndex: number;
14
+ onUpdateScenes: (scenes: any[]) => void;
15
+ onSceneIndexChange: (index: number) => void;
16
+ }
17
+
18
+ export interface UseEditorScenesReturn {
19
+ addScene: () => void;
20
+ duplicateScene: (sceneIndex: number) => void;
21
+ deleteScene: (sceneIndex: number) => void;
22
+ updateSceneAudio: (audio: Audio | undefined) => void;
23
+ }
24
+
25
+ export function useEditorScenes({
26
+ scenes,
27
+ currentSceneIndex,
28
+ onUpdateScenes,
29
+ onSceneIndexChange,
30
+ }: UseEditorScenesParams): UseEditorScenesReturn {
31
+ const addScene = useCallback(() => {
32
+ const result = sceneOperationsService.addScene(scenes);
33
+ if (result.success) {
34
+ onUpdateScenes(result.updatedScenes);
35
+ if (result.newSceneIndex !== undefined) {
36
+ onSceneIndexChange(result.newSceneIndex);
37
+ }
38
+ Alert.alert("Success", "New scene added!");
39
+ } else {
40
+ Alert.alert("Error", result.error || "Failed to add scene");
41
+ }
42
+ }, [scenes, onUpdateScenes, onSceneIndexChange]);
43
+
44
+ const duplicateScene = useCallback(
45
+ (sceneIndex: number) => {
46
+ const result = sceneOperationsService.duplicateScene(scenes, sceneIndex);
47
+ if (result.success) {
48
+ onUpdateScenes(result.updatedScenes);
49
+ if (result.newSceneIndex !== undefined) {
50
+ onSceneIndexChange(result.newSceneIndex);
51
+ }
52
+ Alert.alert("Success", "Scene duplicated!");
53
+ } else {
54
+ Alert.alert("Error", result.error || "Failed to duplicate scene");
55
+ }
56
+ },
57
+ [scenes, onUpdateScenes, onSceneIndexChange],
58
+ );
59
+
60
+ const deleteScene = useCallback(
61
+ (sceneIndex: number) => {
62
+ const result = sceneOperationsService.deleteScene(
63
+ scenes,
64
+ sceneIndex,
65
+ currentSceneIndex,
66
+ );
67
+ if (result.success) {
68
+ onUpdateScenes(result.updatedScenes);
69
+ if (result.newSceneIndex !== undefined) {
70
+ onSceneIndexChange(result.newSceneIndex);
71
+ }
72
+ Alert.alert("Success", "Scene deleted");
73
+ } else {
74
+ Alert.alert("Error", result.error || "Failed to delete scene");
75
+ }
76
+ },
77
+ [scenes, currentSceneIndex, onUpdateScenes, onSceneIndexChange],
78
+ );
79
+
80
+ const updateSceneAudio = useCallback(
81
+ (audio: Audio | undefined) => {
82
+ const result = sceneOperationsService.updateSceneAudio(
83
+ scenes,
84
+ currentSceneIndex,
85
+ audio,
86
+ );
87
+ if (result.success) {
88
+ onUpdateScenes(result.updatedScenes);
89
+ Alert.alert(
90
+ "Success",
91
+ audio ? "Audio added to scene!" : "Audio removed from scene",
92
+ );
93
+ } else {
94
+ Alert.alert("Error", result.error || "Failed to update scene audio");
95
+ }
96
+ },
97
+ [scenes, currentSceneIndex, onUpdateScenes],
98
+ );
99
+
100
+ return {
101
+ addScene,
102
+ duplicateScene,
103
+ deleteScene,
104
+ updateSceneAudio,
105
+ };
106
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * useExport Hook
3
+ * Manages export state and operations
4
+ */
5
+
6
+ import { useState, useCallback, useEffect } from "react";
7
+ import type { ExportSettings, VideoProject } from "@domains/video";
8
+ import type { ExportProgress } from "@domains/video/infrastructure/services/video-export.service";
9
+ import { exportOrchestratorService } from "../infrastructure/services/export-orchestrator.service";
10
+
11
+ export interface UseExportReturn {
12
+ isExporting: boolean;
13
+ exportProgress: ExportProgress | null;
14
+ exportVideo: (
15
+ project: VideoProject,
16
+ settings: ExportSettings,
17
+ ) => Promise<{ success: boolean; uri?: string; error?: string }>;
18
+ resetExport: () => void;
19
+ }
20
+
21
+ /**
22
+ * Hook for managing export operations
23
+ */
24
+ export function useExport(): UseExportReturn {
25
+ const [isExporting, setIsExporting] = useState(false);
26
+ const [exportProgress, setExportProgress] = useState<ExportProgress | null>(
27
+ null,
28
+ );
29
+
30
+ useEffect(() => {
31
+ exportOrchestratorService.requestNotificationPermissions();
32
+ }, []);
33
+
34
+ const exportVideo = useCallback(
35
+ async (
36
+ project: VideoProject,
37
+ settings: ExportSettings,
38
+ ): Promise<{ success: boolean; uri?: string; error?: string }> => {
39
+ setIsExporting(true);
40
+ setExportProgress(null);
41
+
42
+ const result = await exportOrchestratorService.exportVideo(
43
+ project,
44
+ settings,
45
+ (progress) => {
46
+ setExportProgress(progress);
47
+ },
48
+ );
49
+
50
+ setIsExporting(false);
51
+ return result;
52
+ },
53
+ [],
54
+ );
55
+
56
+ const resetExport = useCallback(() => {
57
+ setIsExporting(false);
58
+ setExportProgress(null);
59
+ }, []);
60
+
61
+ return {
62
+ isExporting,
63
+ exportProgress,
64
+ exportVideo,
65
+ resetExport,
66
+ };
67
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * useExportActions Hook
3
+ * Single Responsibility: Export action handlers
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { ExportDialog } from "../presentation/components/ExportDialog";
8
+ import type { VideoProject, ExportSettings } from "@domains/video";
9
+ import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
10
+
11
+ export interface UseExportActionsParams {
12
+ project: VideoProject | undefined;
13
+ bottomSheet: UseEditorBottomSheetReturn;
14
+ onExportComplete: (settings: ExportSettings, uri?: string) => void;
15
+ }
16
+
17
+ export interface UseExportActionsReturn {
18
+ handleExport: () => void;
19
+ handleSave: () => void;
20
+ }
21
+
22
+ export function useExportActions({
23
+ project,
24
+ bottomSheet,
25
+ onExportComplete,
26
+ }: UseExportActionsParams): UseExportActionsReturn {
27
+ const { openBottomSheet, closeBottomSheet } = bottomSheet;
28
+
29
+ const handleExport = useCallback(() => {
30
+ if (!project) return;
31
+ openBottomSheet({
32
+ title: "Export Video",
33
+ children: (
34
+ <ExportDialog
35
+ project={project}
36
+ onExport={onExportComplete}
37
+ onCancel={closeBottomSheet}
38
+ />
39
+ ),
40
+ });
41
+ }, [project, onExportComplete, openBottomSheet, closeBottomSheet]);
42
+
43
+ const handleSave = useCallback(() => {
44
+ // Project is automatically saved via updateProject calls
45
+ }, []);
46
+
47
+ return {
48
+ handleExport,
49
+ handleSave,
50
+ };
51
+ }