@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,108 @@
1
+ /**
2
+ * ShapeLayerEditor Component
3
+ * Main component for editing shape layers
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { ShapeLayer } from "@domains/video";
14
+ import { useShapeLayerForm } from "../../hooks/useShapeLayerForm";
15
+ import {
16
+ BORDER_WIDTHS,
17
+ OPACITY_OPTIONS,
18
+ } from "../../constants/shape-layer.constants";
19
+ import {
20
+ ShapeTypeSelector,
21
+ ColorPickerHorizontal,
22
+ ValueSelector,
23
+ ShapePreview,
24
+ } from "./shape-layer";
25
+ import { EditorActions } from "./text-layer/EditorActions";
26
+
27
+ interface ShapeLayerEditorProps {
28
+ layer?: ShapeLayer;
29
+ onSave: (layerData: Partial<ShapeLayer>) => void;
30
+ onCancel: () => void;
31
+ }
32
+
33
+ export const ShapeLayerEditor: React.FC<ShapeLayerEditorProps> = ({
34
+ layer,
35
+ onSave,
36
+ onCancel,
37
+ }) => {
38
+ const tokens = useAppDesignTokens();
39
+ const {
40
+ formState,
41
+ setShape,
42
+ setFillColor,
43
+ setBorderColor,
44
+ setBorderWidth,
45
+ setOpacity,
46
+ buildLayerData,
47
+ } = useShapeLayerForm(layer);
48
+
49
+ const handleSave = () => {
50
+ onSave(buildLayerData());
51
+ };
52
+
53
+ return (
54
+ <View style={styles.container}>
55
+ <ScrollView showsVerticalScrollIndicator={false}>
56
+ <ShapeTypeSelector
57
+ selectedShape={formState.shape}
58
+ onShapeChange={setShape}
59
+ />
60
+
61
+ <ColorPickerHorizontal
62
+ title="Fill Color"
63
+ selectedColor={formState.fillColor}
64
+ onColorChange={setFillColor}
65
+ />
66
+
67
+ <ValueSelector
68
+ title="Border Width"
69
+ value={formState.borderWidth}
70
+ options={BORDER_WIDTHS}
71
+ formatValue={(val) => `${val}px`}
72
+ onValueChange={setBorderWidth}
73
+ />
74
+
75
+ {formState.borderWidth > 0 && (
76
+ <ColorPickerHorizontal
77
+ title="Border Color"
78
+ selectedColor={formState.borderColor}
79
+ onColorChange={setBorderColor}
80
+ />
81
+ )}
82
+
83
+ <ValueSelector
84
+ title="Opacity"
85
+ value={formState.opacity}
86
+ options={OPACITY_OPTIONS}
87
+ formatValue={(val) => `${Math.round(val * 100)}%`}
88
+ onValueChange={setOpacity}
89
+ />
90
+
91
+ <ShapePreview formState={formState} />
92
+ </ScrollView>
93
+
94
+ <EditorActions
95
+ onCancel={onCancel}
96
+ onSave={handleSave}
97
+ saveLabel="Add Shape"
98
+ isValid={true}
99
+ />
100
+ </View>
101
+ );
102
+ };
103
+
104
+ const styles = StyleSheet.create({
105
+ container: {
106
+ paddingVertical: 16,
107
+ },
108
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * TextLayerEditor Component
3
+ * Main component for editing text layers
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, StyleSheet } from "react-native";
8
+ import { useLocalization } from "@umituz/react-native-localization";
9
+ import type { TextLayer } from "@domains/video";
10
+ import { useTextLayerForm } from "../../hooks/useTextLayerForm";
11
+ import {
12
+ FONT_FAMILIES,
13
+ FONT_WEIGHTS,
14
+ } from "../../constants/text-layer.constants";
15
+ import {
16
+ TextInputSection,
17
+ FontSizeSelector,
18
+ OptionSelector,
19
+ TextAlignSelector,
20
+ ColorPicker,
21
+ TextPreview,
22
+ EditorActions,
23
+ } from "./text-layer";
24
+
25
+ interface TextLayerEditorProps {
26
+ layer?: TextLayer;
27
+ onSave: (layerData: Partial<TextLayer>) => void;
28
+ onCancel: () => void;
29
+ }
30
+
31
+ export const TextLayerEditor: React.FC<TextLayerEditorProps> = ({
32
+ layer,
33
+ onSave,
34
+ onCancel,
35
+ }) => {
36
+ const { t } = useLocalization();
37
+ const {
38
+ formState,
39
+ setText,
40
+ setFontSize,
41
+ setFontFamily,
42
+ setFontWeight,
43
+ setColor,
44
+ setTextAlign,
45
+ buildLayerData,
46
+ isValid,
47
+ } = useTextLayerForm(layer);
48
+
49
+ const handleSave = () => {
50
+ if (!isValid) return;
51
+ onSave(buildLayerData());
52
+ };
53
+
54
+ return (
55
+ <View style={styles.container}>
56
+ <ScrollView showsVerticalScrollIndicator={false}>
57
+ <TextInputSection text={formState.text} onChangeText={setText} />
58
+
59
+ <FontSizeSelector
60
+ fontSize={formState.fontSize}
61
+ onFontSizeChange={setFontSize}
62
+ />
63
+
64
+ <OptionSelector
65
+ title={t("editor.properties.font_family")}
66
+ options={FONT_FAMILIES.map((f) => ({ label: f, value: f }))}
67
+ selectedValue={formState.fontFamily}
68
+ onValueChange={setFontFamily}
69
+ />
70
+
71
+ <OptionSelector
72
+ title="Font Weight"
73
+ options={FONT_WEIGHTS}
74
+ selectedValue={formState.fontWeight}
75
+ onValueChange={(value) =>
76
+ setFontWeight(value as "normal" | "bold" | "300" | "700")
77
+ }
78
+ />
79
+
80
+ <TextAlignSelector
81
+ textAlign={formState.textAlign}
82
+ onTextAlignChange={setTextAlign}
83
+ />
84
+
85
+ <ColorPicker selectedColor={formState.color} onColorChange={setColor} />
86
+
87
+ <TextPreview formState={formState} />
88
+ </ScrollView>
89
+
90
+ <EditorActions
91
+ onCancel={onCancel}
92
+ onSave={handleSave}
93
+ saveLabel={layer ? "Update" : "Add Text"}
94
+ isValid={isValid}
95
+ />
96
+ </View>
97
+ );
98
+ };
99
+
100
+ const styles = StyleSheet.create({
101
+ container: {
102
+ maxHeight: "80%",
103
+ },
104
+ });
@@ -0,0 +1,104 @@
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 {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ interface AnimationEditorActionsProps {
15
+ hasAnimation: boolean;
16
+ onRemove?: () => void;
17
+ onCancel: () => void;
18
+ onSave: () => void;
19
+ }
20
+
21
+ export const AnimationEditorActions: React.FC<AnimationEditorActionsProps> = ({
22
+ hasAnimation,
23
+ onRemove,
24
+ onCancel,
25
+ onSave,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+
29
+ return (
30
+ <View style={styles.actions}>
31
+ {hasAnimation && onRemove && (
32
+ <TouchableOpacity
33
+ style={[
34
+ styles.actionButton,
35
+ styles.removeButton,
36
+ { borderColor: tokens.colors.error },
37
+ ]}
38
+ onPress={onRemove}
39
+ >
40
+ <AtomicIcon name="trash-outline" size="sm" color="error" />
41
+ </TouchableOpacity>
42
+ )}
43
+
44
+ <TouchableOpacity
45
+ style={[
46
+ styles.actionButton,
47
+ styles.cancelButton,
48
+ { borderColor: tokens.colors.borderLight },
49
+ ]}
50
+ onPress={onCancel}
51
+ >
52
+ <AtomicText
53
+ type="bodyMedium"
54
+ style={{ color: tokens.colors.textSecondary }}
55
+ >
56
+ Cancel
57
+ </AtomicText>
58
+ </TouchableOpacity>
59
+
60
+ <TouchableOpacity
61
+ style={[
62
+ styles.actionButton,
63
+ styles.saveButton,
64
+ { backgroundColor: tokens.colors.primary },
65
+ ]}
66
+ onPress={onSave}
67
+ >
68
+ <AtomicIcon name="Check" size="sm" color="onSurface" />
69
+ <AtomicText
70
+ type="bodyMedium"
71
+ style={{ color: "#FFFFFF", fontWeight: "600", marginLeft: 6 }}
72
+ >
73
+ Apply
74
+ </AtomicText>
75
+ </TouchableOpacity>
76
+ </View>
77
+ );
78
+ };
79
+
80
+ const styles = StyleSheet.create({
81
+ actions: {
82
+ flexDirection: "row",
83
+ gap: 8,
84
+ },
85
+ actionButton: {
86
+ flex: 1,
87
+ flexDirection: "row",
88
+ paddingVertical: 12,
89
+ borderRadius: 12,
90
+ alignItems: "center",
91
+ justifyContent: "center",
92
+ },
93
+ removeButton: {
94
+ flex: 0,
95
+ paddingHorizontal: 16,
96
+ borderWidth: 1,
97
+ },
98
+ cancelButton: {
99
+ borderWidth: 1,
100
+ },
101
+ saveButton: {
102
+ // backgroundColor set dynamically
103
+ },
104
+ });
@@ -0,0 +1,43 @@
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 {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ export const AnimationInfoBanner: React.FC = () => {
15
+ const tokens = useAppDesignTokens();
16
+
17
+ return (
18
+ <View
19
+ style={[
20
+ styles.infoBanner,
21
+ { backgroundColor: tokens.colors.primary + "20" },
22
+ ]}
23
+ >
24
+ <AtomicIcon name="Info" size="sm" color="primary" />
25
+ <AtomicText
26
+ type="labelSmall"
27
+ style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
28
+ >
29
+ Animation will play when the layer first appears in the scene
30
+ </AtomicText>
31
+ </View>
32
+ );
33
+ };
34
+
35
+ const styles = StyleSheet.create({
36
+ infoBanner: {
37
+ flexDirection: "row",
38
+ alignItems: "center",
39
+ padding: 12,
40
+ borderRadius: 8,
41
+ marginBottom: 8,
42
+ },
43
+ });
@@ -0,0 +1,105 @@
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 {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import { ANIMATION_TYPES } from "../../../constants/animation-layer.constants";
14
+ import type { AnimationType } from "@domains/video";
15
+
16
+ interface AnimationTypeSelectorProps {
17
+ selectedType: AnimationType;
18
+ onTypeChange: (type: AnimationType) => void;
19
+ }
20
+
21
+ export const AnimationTypeSelector: React.FC<AnimationTypeSelectorProps> = ({
22
+ selectedType,
23
+ onTypeChange,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+
27
+ return (
28
+ <View style={styles.section}>
29
+ <AtomicText
30
+ type="bodyMedium"
31
+ style={{
32
+ color: tokens.colors.textPrimary,
33
+ fontWeight: "600",
34
+ marginBottom: 12,
35
+ }}
36
+ >
37
+ Animation Type
38
+ </AtomicText>
39
+
40
+ <ScrollView
41
+ horizontal
42
+ showsHorizontalScrollIndicator={false}
43
+ style={styles.animationTypesScroll}
44
+ >
45
+ {ANIMATION_TYPES.map((anim) => (
46
+ <TouchableOpacity
47
+ key={anim.type}
48
+ style={[
49
+ styles.animationTypeCard,
50
+ {
51
+ backgroundColor:
52
+ selectedType === anim.type
53
+ ? tokens.colors.primary
54
+ : tokens.colors.surface,
55
+ borderColor:
56
+ selectedType === anim.type
57
+ ? tokens.colors.primary
58
+ : tokens.colors.borderLight,
59
+ },
60
+ ]}
61
+ onPress={() => onTypeChange(anim.type)}
62
+ >
63
+ <AtomicIcon
64
+ name={anim.icon as any}
65
+ size="md"
66
+ color={selectedType === anim.type ? "onSurface" : "primary"}
67
+ />
68
+ <AtomicText
69
+ type="labelSmall"
70
+ style={{
71
+ color:
72
+ selectedType === anim.type
73
+ ? "#FFFFFF"
74
+ : tokens.colors.textPrimary,
75
+ marginTop: 6,
76
+ fontWeight: selectedType === anim.type ? "600" : "400",
77
+ }}
78
+ >
79
+ {anim.label}
80
+ </AtomicText>
81
+ </TouchableOpacity>
82
+ ))}
83
+ </ScrollView>
84
+ </View>
85
+ );
86
+ };
87
+
88
+ const styles = StyleSheet.create({
89
+ section: {
90
+ marginBottom: 24,
91
+ },
92
+ animationTypesScroll: {
93
+ marginHorizontal: -16,
94
+ paddingHorizontal: 16,
95
+ },
96
+ animationTypeCard: {
97
+ width: 90,
98
+ padding: 12,
99
+ borderRadius: 12,
100
+ borderWidth: 2,
101
+ alignItems: "center",
102
+ justifyContent: "center",
103
+ marginRight: 12,
104
+ },
105
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Animation Layer Editor Components
3
+ * Barrel file for animation layer editor components
4
+ */
5
+
6
+ export { AnimationTypeSelector } from "./AnimationTypeSelector";
7
+ export { AnimationEditorActions } from "./AnimationEditorActions";
8
+ export { AnimationInfoBanner } from "./AnimationInfoBanner";
@@ -0,0 +1,115 @@
1
+ /**
2
+ * AudioEditorActions Component
3
+ * Action buttons for audio editor
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ interface AudioEditorActionsProps {
15
+ hasAudio: boolean;
16
+ onRemove?: () => void;
17
+ onCancel: () => void;
18
+ onSave: () => void;
19
+ isValid: boolean;
20
+ }
21
+
22
+ export const AudioEditorActions: React.FC<AudioEditorActionsProps> = ({
23
+ hasAudio,
24
+ onRemove,
25
+ onCancel,
26
+ onSave,
27
+ isValid,
28
+ }) => {
29
+ const tokens = useAppDesignTokens();
30
+
31
+ return (
32
+ <View style={styles.actions}>
33
+ {hasAudio && onRemove && (
34
+ <TouchableOpacity
35
+ style={[
36
+ styles.actionButton,
37
+ styles.removeButton,
38
+ { borderColor: tokens.colors.error },
39
+ ]}
40
+ onPress={onRemove}
41
+ >
42
+ <AtomicIcon name="trash-outline" size="sm" color="error" />
43
+ <AtomicText
44
+ type="bodyMedium"
45
+ style={{ color: tokens.colors.error, marginLeft: 6 }}
46
+ >
47
+ Remove
48
+ </AtomicText>
49
+ </TouchableOpacity>
50
+ )}
51
+
52
+ <TouchableOpacity
53
+ style={[
54
+ styles.actionButton,
55
+ styles.cancelButton,
56
+ { borderColor: tokens.colors.borderLight },
57
+ ]}
58
+ onPress={onCancel}
59
+ >
60
+ <AtomicText
61
+ type="bodyMedium"
62
+ style={{ color: tokens.colors.textSecondary }}
63
+ >
64
+ Cancel
65
+ </AtomicText>
66
+ </TouchableOpacity>
67
+
68
+ <TouchableOpacity
69
+ style={[
70
+ styles.actionButton,
71
+ styles.saveButton,
72
+ {
73
+ backgroundColor: isValid
74
+ ? tokens.colors.primary
75
+ : tokens.colors.borderLight,
76
+ },
77
+ ]}
78
+ onPress={onSave}
79
+ disabled={!isValid}
80
+ >
81
+ <AtomicIcon name="Check" size="sm" color="onSurface" />
82
+ <AtomicText
83
+ type="bodyMedium"
84
+ style={{ color: "#FFFFFF", fontWeight: "600", marginLeft: 6 }}
85
+ >
86
+ Save
87
+ </AtomicText>
88
+ </TouchableOpacity>
89
+ </View>
90
+ );
91
+ };
92
+
93
+ const styles = StyleSheet.create({
94
+ actions: {
95
+ flexDirection: "row",
96
+ gap: 8,
97
+ },
98
+ actionButton: {
99
+ flex: 1,
100
+ flexDirection: "row",
101
+ paddingVertical: 12,
102
+ borderRadius: 12,
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ },
106
+ removeButton: {
107
+ borderWidth: 1,
108
+ },
109
+ cancelButton: {
110
+ borderWidth: 1,
111
+ },
112
+ saveButton: {
113
+ // backgroundColor set dynamically
114
+ },
115
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * AudioFileSelector Component
3
+ * Audio file selection for audio layer editor
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import { SUPPORTED_AUDIO_FORMATS } from "../../../constants/audio-layer.constants";
14
+
15
+ interface AudioFileSelectorProps {
16
+ audioUri: string;
17
+ onPickAudio: () => void;
18
+ getFileName: (uri: string) => string;
19
+ }
20
+
21
+ export const AudioFileSelector: React.FC<AudioFileSelectorProps> = ({
22
+ audioUri,
23
+ onPickAudio,
24
+ getFileName,
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+
28
+ if (audioUri) {
29
+ return (
30
+ <View
31
+ style={[styles.fileCard, { backgroundColor: tokens.colors.surface }]}
32
+ >
33
+ <View style={styles.fileInfo}>
34
+ <AtomicIcon name="Music" size="md" color="primary" />
35
+ <View style={{ marginLeft: 12, flex: 1 }}>
36
+ <AtomicText
37
+ type="bodySmall"
38
+ style={{
39
+ color: tokens.colors.textPrimary,
40
+ fontWeight: "500",
41
+ }}
42
+ numberOfLines={1}
43
+ >
44
+ {getFileName(audioUri)}
45
+ </AtomicText>
46
+ <AtomicText
47
+ type="labelSmall"
48
+ style={{ color: tokens.colors.textSecondary, marginTop: 2 }}
49
+ >
50
+ {audioUri.includes("file://") ? "Local file" : "Imported"}
51
+ </AtomicText>
52
+ </View>
53
+ </View>
54
+ <TouchableOpacity
55
+ style={[styles.changeButton, { borderColor: tokens.colors.primary }]}
56
+ onPress={onPickAudio}
57
+ >
58
+ <AtomicText
59
+ type="labelSmall"
60
+ style={{ color: tokens.colors.primary, fontWeight: "600" }}
61
+ >
62
+ Change
63
+ </AtomicText>
64
+ </TouchableOpacity>
65
+ </View>
66
+ );
67
+ }
68
+
69
+ return (
70
+ <TouchableOpacity
71
+ style={[styles.pickButton, { backgroundColor: tokens.colors.surface }]}
72
+ onPress={onPickAudio}
73
+ >
74
+ <AtomicIcon name="Upload" size="md" color="primary" />
75
+ <AtomicText
76
+ type="bodyMedium"
77
+ style={{
78
+ color: tokens.colors.primary,
79
+ fontWeight: "600",
80
+ marginTop: 8,
81
+ }}
82
+ >
83
+ Select Audio File
84
+ </AtomicText>
85
+ <AtomicText
86
+ type="labelSmall"
87
+ style={{ color: tokens.colors.textSecondary, marginTop: 4 }}
88
+ >
89
+ {SUPPORTED_AUDIO_FORMATS}
90
+ </AtomicText>
91
+ </TouchableOpacity>
92
+ );
93
+ };
94
+
95
+ const styles = StyleSheet.create({
96
+ section: {
97
+ marginBottom: 24,
98
+ },
99
+ pickButton: {
100
+ padding: 32,
101
+ borderRadius: 12,
102
+ alignItems: "center",
103
+ justifyContent: "center",
104
+ borderWidth: 2,
105
+ borderColor: "rgba(0, 0, 0, 0.1)",
106
+ borderStyle: "dashed",
107
+ },
108
+ fileCard: {
109
+ padding: 16,
110
+ borderRadius: 12,
111
+ flexDirection: "row",
112
+ alignItems: "center",
113
+ justifyContent: "space-between",
114
+ },
115
+ fileInfo: {
116
+ flexDirection: "row",
117
+ alignItems: "center",
118
+ flex: 1,
119
+ },
120
+ changeButton: {
121
+ paddingHorizontal: 16,
122
+ paddingVertical: 8,
123
+ borderRadius: 8,
124
+ borderWidth: 1,
125
+ },
126
+ });