@umituz/react-native-video-editor 1.0.16 → 1.0.18

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,11 +1,11 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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",
7
7
  "scripts": {
8
- "typecheck": "echo 'TypeScript validation passed'",
8
+ "typecheck": "npx tsc --noEmit",
9
9
  "lint": "echo 'Lint passed'",
10
10
  "version:patch": "npm version patch -m 'chore: release v%s'",
11
11
  "version:minor": "npm version minor -m 'chore: release v%s'",
@@ -26,22 +26,31 @@
26
26
  "url": "https://github.com/umituz/react-native-video-editor"
27
27
  },
28
28
  "peerDependencies": {
29
- "react": ">=18.2.0",
30
- "react-native": ">=0.74.0",
31
29
  "@umituz/react-native-design-system": ">=1.0.0",
32
30
  "expo-document-picker": ">=14.0.0",
31
+ "expo-file-system": ">=17.0.0",
33
32
  "expo-image": ">=1.0.0",
34
33
  "expo-video": ">=3.0.0",
34
+ "react": ">=18.2.0",
35
+ "react-native": ">=0.74.0",
35
36
  "zustand": ">=4.0.0"
36
37
  },
37
38
  "devDependencies": {
39
+ "@gorhom/bottom-sheet": "^5.2.8",
38
40
  "@types/react": "~19.1.10",
39
41
  "@umituz/react-native-design-system": "latest",
42
+ "@umituz/react-native-filesystem": "latest",
43
+ "expo-application": "^7.0.8",
44
+ "expo-device": "^8.0.10",
40
45
  "expo-document-picker": "^14.0.8",
46
+ "expo-file-system": "^19.0.0",
41
47
  "expo-image": "^3.0.11",
48
+ "expo-linear-gradient": "^15.0.7",
42
49
  "expo-video": "^3.0.15",
43
50
  "react": "19.1.0",
44
51
  "react-native": "0.81.5",
52
+ "react-native-gesture-handler": "^2.30.0",
53
+ "react-native-reanimated": "^4.2.1",
45
54
  "typescript": "~5.9.2"
46
55
  },
47
56
  "publishConfig": {
@@ -12,6 +12,9 @@ export type {
12
12
  VideoVisibilityConfig,
13
13
  VideoPlayerProps,
14
14
  VideoPlayer as VideoPlayerType,
15
+ VideoDownloadProgressCallback,
16
+ VideoCacheResult,
17
+ VideoCachingState,
15
18
  } from "./types";
16
19
 
17
20
  // Services
@@ -23,9 +26,19 @@ export {
23
26
  configurePlayer,
24
27
  } from "./infrastructure/services/player-control.service";
25
28
 
29
+ export {
30
+ isVideoCached,
31
+ getCachedVideoUri,
32
+ downloadVideo,
33
+ getOrDownloadVideo,
34
+ clearVideoCache,
35
+ deleteSpecificCachedVideo,
36
+ } from "./infrastructure/services/video-cache.service";
37
+
26
38
  // Hooks
27
39
  export { useVideoPlayerControl } from "./presentation/hooks/useVideoPlayerControl";
28
40
  export { useVideoVisibility } from "./presentation/hooks/useVideoVisibility";
41
+ export { useVideoCaching } from "./presentation/hooks/useVideoCaching";
29
42
 
30
43
  // Components
31
44
  export { VideoPlayer } from "./presentation/components/VideoPlayer";
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Video Cache Service
3
+ * Downloads and caches remote videos for playback
4
+ * Bypasses CDN Range request issues by downloading entire file first
5
+ */
6
+
7
+ import {
8
+ downloadFileWithProgress,
9
+ getCachedFileUri,
10
+ isUrlCached,
11
+ deleteCachedFile,
12
+ getCacheDirectory,
13
+ clearCache,
14
+ type DownloadProgressCallback,
15
+ type DownloadProgress,
16
+ } from "@umituz/react-native-filesystem";
17
+
18
+ declare const __DEV__: boolean;
19
+
20
+ const VIDEO_CACHE_SUBDIR = "video-cache";
21
+
22
+ /** Cache result type */
23
+ export interface VideoCacheResult {
24
+ readonly uri: string;
25
+ readonly fromCache: boolean;
26
+ }
27
+
28
+ /** Re-export progress callback type */
29
+ export type VideoDownloadProgressCallback = (progress: number) => void;
30
+
31
+ /**
32
+ * Get video cache directory path
33
+ */
34
+ const getVideoCacheDir = (): string => {
35
+ const cacheDir = getCacheDirectory();
36
+ return `${cacheDir}${VIDEO_CACHE_SUBDIR}/`;
37
+ };
38
+
39
+ /**
40
+ * Check if video is already cached
41
+ */
42
+ export const isVideoCached = (url: string): boolean => {
43
+ if (!url || url.startsWith("file://")) return false;
44
+ return isUrlCached(url, getVideoCacheDir());
45
+ };
46
+
47
+ /**
48
+ * Get cached video URI if exists
49
+ */
50
+ export const getCachedVideoUri = (url: string): string | null => {
51
+ if (!url) return null;
52
+ if (url.startsWith("file://")) return url;
53
+ return getCachedFileUri(url, getVideoCacheDir());
54
+ };
55
+
56
+ /**
57
+ * Download video to cache with progress tracking
58
+ */
59
+ export const downloadVideo = async (
60
+ url: string,
61
+ onProgress?: VideoDownloadProgressCallback,
62
+ ): Promise<string> => {
63
+ if (!url) throw new Error("URL is required");
64
+ if (url.startsWith("file://")) return url;
65
+
66
+ if (__DEV__) {
67
+ console.log("[VideoCache] Downloading:", url);
68
+ }
69
+
70
+ const progressCallback: DownloadProgressCallback | undefined = onProgress
71
+ ? (progress: DownloadProgress) => {
72
+ const percent = progress.totalBytesExpectedToWrite > 0
73
+ ? progress.totalBytesWritten / progress.totalBytesExpectedToWrite
74
+ : 0;
75
+ onProgress(percent);
76
+ }
77
+ : undefined;
78
+
79
+ const result = await downloadFileWithProgress(
80
+ url,
81
+ getVideoCacheDir(),
82
+ progressCallback,
83
+ );
84
+
85
+ if (!result.success || !result.uri) {
86
+ throw new Error(result.error || "Download failed");
87
+ }
88
+
89
+ if (__DEV__) {
90
+ console.log("[VideoCache] Download complete:", result.uri);
91
+ }
92
+
93
+ return result.uri;
94
+ };
95
+
96
+ /**
97
+ * Get video from cache or download if not cached
98
+ */
99
+ export const getOrDownloadVideo = async (
100
+ url: string,
101
+ onProgress?: VideoDownloadProgressCallback,
102
+ ): Promise<VideoCacheResult> => {
103
+ if (!url) throw new Error("URL is required");
104
+
105
+ if (url.startsWith("file://")) {
106
+ return { uri: url, fromCache: true };
107
+ }
108
+
109
+ // Check cache first
110
+ const cachedUri = getCachedVideoUri(url);
111
+ if (cachedUri) {
112
+ if (__DEV__) {
113
+ console.log("[VideoCache] Using cached video:", cachedUri);
114
+ }
115
+ return { uri: cachedUri, fromCache: true };
116
+ }
117
+
118
+ // Download if not cached
119
+ const downloadedUri = await downloadVideo(url, onProgress);
120
+ return { uri: downloadedUri, fromCache: false };
121
+ };
122
+
123
+ /**
124
+ * Clear all cached videos
125
+ */
126
+ export const clearVideoCache = async (): Promise<void> => {
127
+ await clearCache();
128
+ };
129
+
130
+ /**
131
+ * Delete a specific cached video
132
+ */
133
+ export const deleteSpecificCachedVideo = (url: string): boolean => {
134
+ if (!url || url.startsWith("file://")) return false;
135
+ return deleteCachedFile(url, getVideoCacheDir());
136
+ };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * VideoPlayer Component
3
- * Reusable video player with thumbnail and controls
3
+ * Reusable video player with caching, thumbnail and controls
4
4
  */
5
5
 
6
6
  import React, { useState, useCallback, useMemo, useEffect } from "react";
@@ -11,10 +11,12 @@ import {
11
11
  useResponsive,
12
12
  useAppDesignTokens,
13
13
  AtomicIcon,
14
+ AtomicText,
14
15
  } from "@umituz/react-native-design-system";
15
16
 
16
17
  import type { VideoPlayerProps } from "../../types";
17
18
  import { useVideoPlayerControl } from "../hooks/useVideoPlayerControl";
19
+ import { useVideoCaching } from "../hooks/useVideoCaching";
18
20
 
19
21
  declare const __DEV__: boolean;
20
22
 
@@ -38,14 +40,17 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
38
40
  contentFit = "cover",
39
41
  style,
40
42
  }) => {
41
- // IMPORTANT: Call useResponsive BEFORE useAppDesignTokens to maintain consistent hook order
42
- // useAppDesignTokens internally calls useResponsive, so order matters
43
+ // IMPORTANT: Call useResponsive BEFORE useAppDesignTokens to maintain hook order
43
44
  const { width: screenWidth, horizontalPadding } = useResponsive();
44
45
  const tokens = useAppDesignTokens();
45
46
  const [showVideo, setShowVideo] = useState(autoPlay);
46
47
 
48
+ // Cache the video first (downloads if needed)
49
+ const { localUri, isDownloading, downloadProgress, error } = useVideoCaching(source);
50
+
51
+ // Use cached local URI for player
47
52
  const { player, state, controls } = useVideoPlayerControl({
48
- source,
53
+ source: localUri,
49
54
  loop,
50
55
  muted,
51
56
  autoPlay: false,
@@ -53,23 +58,21 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
53
58
 
54
59
  useEffect(() => {
55
60
  if (showVideo && state.isPlayerValid && player) {
56
- if (typeof __DEV__ !== "undefined" && __DEV__) {
57
- // eslint-disable-next-line no-console
58
- console.log("[VideoPlayer] Starting playback...");
61
+ if (__DEV__) {
62
+ console.log("[VideoPlayer] Starting playback from:", localUri);
59
63
  }
60
64
  controls.play();
61
65
  }
62
- }, [showVideo, state.isPlayerValid, player, controls]);
66
+ }, [showVideo, state.isPlayerValid, player, controls, localUri]);
63
67
 
64
68
  const handlePlay = useCallback(() => {
65
- if (typeof __DEV__ !== "undefined" && __DEV__) {
66
- // eslint-disable-next-line no-console
67
- console.log("[VideoPlayer] handlePlay called, source:", source);
69
+ if (__DEV__) {
70
+ console.log("[VideoPlayer] handlePlay, localUri:", localUri);
68
71
  }
69
72
  setShowVideo(true);
70
- }, [source]);
73
+ }, [localUri]);
71
74
 
72
- // Calculate absolute dimensions for VideoView (expo-video requires pixel values)
75
+ // Calculate dimensions
73
76
  const videoWidth = getWidthFromStyle(style as ViewStyle) ?? (screenWidth - horizontalPadding * 2);
74
77
  const videoHeight = videoWidth / ASPECT_RATIO;
75
78
 
@@ -81,67 +84,63 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
81
84
  overflow: "hidden" as const,
82
85
  }), [tokens.colors.surface, videoWidth, videoHeight]);
83
86
 
84
- const styles = useMemo(
85
- () =>
86
- StyleSheet.create({
87
- video: {
88
- width: videoWidth,
89
- height: videoHeight,
90
- },
91
- thumbnailContainer: {
92
- flex: 1,
93
- justifyContent: "center",
94
- alignItems: "center",
95
- },
96
- thumbnail: {
97
- width: videoWidth,
98
- height: videoHeight,
99
- },
100
- placeholder: {
101
- width: videoWidth,
102
- height: videoHeight,
103
- backgroundColor: tokens.colors.surfaceSecondary,
104
- },
105
- playButtonContainer: {
106
- ...StyleSheet.absoluteFillObject,
107
- justifyContent: "center",
108
- alignItems: "center",
109
- },
110
- playButton: {
111
- width: 64,
112
- height: 64,
113
- borderRadius: 32,
114
- backgroundColor: tokens.colors.primary,
115
- justifyContent: "center",
116
- alignItems: "center",
117
- paddingLeft: 4,
118
- },
119
- }),
120
- [tokens, videoWidth, videoHeight]
121
- );
87
+ const styles = useMemo(() => StyleSheet.create({
88
+ video: { width: videoWidth, height: videoHeight },
89
+ thumbnailContainer: { flex: 1, justifyContent: "center", alignItems: "center" },
90
+ thumbnail: { width: videoWidth, height: videoHeight },
91
+ placeholder: { width: videoWidth, height: videoHeight, backgroundColor: tokens.colors.surfaceSecondary },
92
+ playButtonContainer: { ...StyleSheet.absoluteFillObject, justifyContent: "center", alignItems: "center" },
93
+ playButton: {
94
+ width: 64, height: 64, borderRadius: 32,
95
+ backgroundColor: tokens.colors.primary,
96
+ justifyContent: "center", alignItems: "center", paddingLeft: 4,
97
+ },
98
+ progressContainer: {
99
+ ...StyleSheet.absoluteFillObject,
100
+ justifyContent: "center", alignItems: "center",
101
+ backgroundColor: "rgba(0,0,0,0.5)",
102
+ },
103
+ progressText: { color: "#fff", fontSize: 16, fontWeight: "600" },
104
+ errorText: { color: tokens.colors.error, fontSize: 14, textAlign: "center", padding: 16 },
105
+ }), [tokens, videoWidth, videoHeight]);
122
106
 
123
- if (typeof __DEV__ !== "undefined" && __DEV__) {
124
- // eslint-disable-next-line no-console
125
- console.log("[VideoPlayer] state:", {
126
- showVideo,
127
- isPlayerValid: state.isPlayerValid,
128
- hasPlayer: !!player,
129
- source,
130
- });
131
- // eslint-disable-next-line no-console
132
- console.log("[VideoPlayer] dimensions:", {
133
- screenWidth,
134
- horizontalPadding,
135
- videoWidth,
136
- videoHeight,
137
- styleWidth: getWidthFromStyle(style as ViewStyle),
138
- });
107
+ // Show error state
108
+ if (error) {
109
+ return (
110
+ <View style={[containerStyle, style]}>
111
+ <View style={styles.thumbnailContainer}>
112
+ <View style={styles.placeholder} />
113
+ <View style={styles.progressContainer}>
114
+ <AtomicText style={styles.errorText}>{error}</AtomicText>
115
+ </View>
116
+ </View>
117
+ </View>
118
+ );
119
+ }
120
+
121
+ // Show download progress
122
+ if (isDownloading) {
123
+ const progressPercent = Math.round(downloadProgress * 100);
124
+ return (
125
+ <View style={[containerStyle, style]}>
126
+ <View style={styles.thumbnailContainer}>
127
+ {thumbnailUrl ? (
128
+ <Image source={{ uri: thumbnailUrl }} style={styles.thumbnail} contentFit="cover" />
129
+ ) : (
130
+ <View style={styles.placeholder} />
131
+ )}
132
+ <View style={styles.progressContainer}>
133
+ <AtomicText style={styles.progressText}>{progressPercent}%</AtomicText>
134
+ </View>
135
+ </View>
136
+ </View>
137
+ );
139
138
  }
140
139
 
140
+ // Show video player
141
141
  if (showVideo && state.isPlayerValid && player) {
142
- if (typeof __DEV__ !== "undefined" && __DEV__) {
143
- // eslint-disable-next-line no-console
144
- console.log("[VideoPlayer] Rendering VideoView with dimensions:", { videoWidth, videoHeight });
142
+ if (__DEV__) {
143
+ console.log("[VideoPlayer] Rendering VideoView:", { videoWidth, videoHeight });
145
144
  }
146
145
  return (
147
146
  <View style={[containerStyle, style]}>
@@ -155,19 +154,12 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
155
154
  );
156
155
  }
157
156
 
157
+ // Show thumbnail with play button
158
158
  return (
159
- <TouchableOpacity
160
- style={[containerStyle, style]}
161
- onPress={handlePlay}
162
- activeOpacity={0.8}
163
- >
159
+ <TouchableOpacity style={[containerStyle, style]} onPress={handlePlay} activeOpacity={0.8}>
164
160
  <View style={styles.thumbnailContainer}>
165
161
  {thumbnailUrl ? (
166
- <Image
167
- source={{ uri: thumbnailUrl }}
168
- style={styles.thumbnail}
169
- contentFit="cover"
170
- />
162
+ <Image source={{ uri: thumbnailUrl }} style={styles.thumbnail} contentFit="cover" />
171
163
  ) : (
172
164
  <View style={styles.placeholder} />
173
165
  )}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * useVideoCaching Hook
3
+ * Manages video caching state and download progress
4
+ */
5
+
6
+ import { useState, useEffect, useCallback } from "react";
7
+
8
+ import {
9
+ getOrDownloadVideo,
10
+ isVideoCached,
11
+ type VideoCacheResult,
12
+ } from "../../infrastructure/services/video-cache.service";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ /** Video caching state */
17
+ export interface VideoCachingState {
18
+ readonly localUri: string | null;
19
+ readonly isDownloading: boolean;
20
+ readonly downloadProgress: number;
21
+ readonly fromCache: boolean;
22
+ readonly error: string | null;
23
+ readonly retry: () => void;
24
+ }
25
+
26
+ /**
27
+ * Hook for managing video caching
28
+ * Downloads remote videos to local cache for playback
29
+ */
30
+ export const useVideoCaching = (sourceUrl: string | null): VideoCachingState => {
31
+ const [localUri, setLocalUri] = useState<string | null>(null);
32
+ const [isDownloading, setIsDownloading] = useState(false);
33
+ const [downloadProgress, setDownloadProgress] = useState(0);
34
+ const [fromCache, setFromCache] = useState(false);
35
+ const [error, setError] = useState<string | null>(null);
36
+
37
+ const loadVideo = useCallback(async () => {
38
+ if (!sourceUrl) {
39
+ setLocalUri(null);
40
+ setIsDownloading(false);
41
+ setDownloadProgress(0);
42
+ setFromCache(false);
43
+ setError(null);
44
+ return;
45
+ }
46
+
47
+ if (sourceUrl.startsWith("file://")) {
48
+ setLocalUri(sourceUrl);
49
+ setIsDownloading(false);
50
+ setDownloadProgress(1);
51
+ setFromCache(true);
52
+ setError(null);
53
+ return;
54
+ }
55
+
56
+ const cached = isVideoCached(sourceUrl);
57
+ setIsDownloading(!cached);
58
+ setDownloadProgress(cached ? 1 : 0);
59
+ setError(null);
60
+
61
+ try {
62
+ const result: VideoCacheResult = await getOrDownloadVideo(
63
+ sourceUrl,
64
+ (progress) => setDownloadProgress(progress),
65
+ );
66
+
67
+ setLocalUri(result.uri);
68
+ setFromCache(result.fromCache);
69
+ setIsDownloading(false);
70
+ setDownloadProgress(1);
71
+
72
+ if (__DEV__) {
73
+ console.log("[useVideoCaching] Video ready:", result.uri);
74
+ }
75
+ } catch (err) {
76
+ const message = err instanceof Error ? err.message : "Download failed";
77
+ setError(message);
78
+ setIsDownloading(false);
79
+ setLocalUri(null);
80
+
81
+ if (__DEV__) {
82
+ console.log("[useVideoCaching] Error:", message);
83
+ }
84
+ }
85
+ }, [sourceUrl]);
86
+
87
+ useEffect(() => {
88
+ loadVideo();
89
+ }, [loadVideo]);
90
+
91
+ const retry = useCallback(() => {
92
+ setError(null);
93
+ loadVideo();
94
+ }, [loadVideo]);
95
+
96
+ return {
97
+ localUri,
98
+ isDownloading,
99
+ downloadProgress,
100
+ fromCache,
101
+ error,
102
+ retry,
103
+ };
104
+ };
@@ -72,3 +72,12 @@ export interface VideoPlayerProps {
72
72
  }
73
73
 
74
74
  export type { VideoPlayer } from "expo-video";
75
+
76
+ /**
77
+ * Video caching types
78
+ */
79
+ export type {
80
+ VideoDownloadProgressCallback,
81
+ VideoCacheResult,
82
+ } from "../infrastructure/services/video-cache.service";
83
+ export type { VideoCachingState } from "../presentation/hooks/useVideoCaching";
@@ -60,7 +60,7 @@ export const EditorToolPanel: React.FC<EditorToolPanelProps> = ({
60
60
  ]}
61
61
  onPress={onAddText}
62
62
  >
63
- <AtomicIcon name="Type" size="md" color="primary" />
63
+ <AtomicIcon name="text-outline" size="md" color="primary" />
64
64
  <AtomicText
65
65
  type="labelSmall"
66
66
  style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
@@ -92,7 +92,7 @@ export const EditorToolPanel: React.FC<EditorToolPanelProps> = ({
92
92
  ]}
93
93
  onPress={onAddShape}
94
94
  >
95
- <AtomicIcon name="Square" size="md" color="primary" />
95
+ <AtomicIcon name="square-outline" size="md" color="primary" />
96
96
  <AtomicText
97
97
  type="labelSmall"
98
98
  style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
@@ -108,7 +108,7 @@ export const EditorToolPanel: React.FC<EditorToolPanelProps> = ({
108
108
  ]}
109
109
  onPress={onAudio}
110
110
  >
111
- <AtomicIcon name="Music" size="md" color="primary" />
111
+ <AtomicIcon name="musical-notes-outline" size="md" color="primary" />
112
112
  <AtomicText
113
113
  type="labelSmall"
114
114
  style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
@@ -43,7 +43,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
43
43
  <View style={{ paddingVertical: 8 }}>
44
44
  {layer.type === "text" && (
45
45
  <TouchableOpacity style={styles.actionMenuItem} onPress={onEditText}>
46
- <AtomicIcon name="Edit" size="md" color="primary" />
46
+ <AtomicIcon name="create-outline" size="md" color="primary" />
47
47
  <AtomicText
48
48
  type="bodyMedium"
49
49
  style={{
@@ -57,7 +57,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
57
57
  )}
58
58
  {layer.type === "image" && (
59
59
  <TouchableOpacity style={styles.actionMenuItem} onPress={onEditImage}>
60
- <AtomicIcon name="Edit" size="md" color="primary" />
60
+ <AtomicIcon name="create-outline" size="md" color="primary" />
61
61
  <AtomicText
62
62
  type="bodyMedium"
63
63
  style={{
@@ -107,7 +107,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
107
107
  <View style={styles.divider} />
108
108
 
109
109
  <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveFront}>
110
- <AtomicIcon name="ChevronsUp" size="md" color="secondary" />
110
+ <AtomicIcon name="chevron-up-outline" size="md" color="secondary" />
111
111
  <AtomicText
112
112
  type="bodyMedium"
113
113
  style={{
@@ -120,7 +120,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
120
120
  </TouchableOpacity>
121
121
 
122
122
  <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveUp}>
123
- <AtomicIcon name="ChevronUp" size="md" color="secondary" />
123
+ <AtomicIcon name="chevron-up-outline" size="md" color="secondary" />
124
124
  <AtomicText
125
125
  type="bodyMedium"
126
126
  style={{
@@ -146,7 +146,7 @@ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
146
146
  </TouchableOpacity>
147
147
 
148
148
  <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveBack}>
149
- <AtomicIcon name="ChevronsDown" size="md" color="secondary" />
149
+ <AtomicIcon name="chevron-down-outline" size="md" color="secondary" />
150
150
  <AtomicText
151
151
  type="bodyMedium"
152
152
  style={{
@@ -65,7 +65,7 @@ export const AnimationEditorActions: React.FC<AnimationEditorActionsProps> = ({
65
65
  ]}
66
66
  onPress={onSave}
67
67
  >
68
- <AtomicIcon name="Check" size="sm" color="onSurface" />
68
+ <AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
69
69
  <AtomicText
70
70
  type="bodyMedium"
71
71
  style={{ color: "#FFFFFF", fontWeight: "600", marginLeft: 6 }}
@@ -21,7 +21,7 @@ export const AnimationInfoBanner: React.FC = () => {
21
21
  { backgroundColor: tokens.colors.primary + "20" },
22
22
  ]}
23
23
  >
24
- <AtomicIcon name="Info" size="sm" color="primary" />
24
+ <AtomicIcon name="information-circle-outline" size="sm" color="primary" />
25
25
  <AtomicText
26
26
  type="labelSmall"
27
27
  style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
@@ -78,7 +78,7 @@ export const AudioEditorActions: React.FC<AudioEditorActionsProps> = ({
78
78
  onPress={onSave}
79
79
  disabled={!isValid}
80
80
  >
81
- <AtomicIcon name="Check" size="sm" color="onSurface" />
81
+ <AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
82
82
  <AtomicText
83
83
  type="bodyMedium"
84
84
  style={{ color: "#FFFFFF", fontWeight: "600", marginLeft: 6 }}
@@ -31,7 +31,7 @@ export const AudioFileSelector: React.FC<AudioFileSelectorProps> = ({
31
31
  style={[styles.fileCard, { backgroundColor: tokens.colors.surface }]}
32
32
  >
33
33
  <View style={styles.fileInfo}>
34
- <AtomicIcon name="Music" size="md" color="primary" />
34
+ <AtomicIcon name="musical-notes-outline" size="md" color="primary" />
35
35
  <View style={{ marginLeft: 12, flex: 1 }}>
36
36
  <AtomicText
37
37
  type="bodySmall"
@@ -71,7 +71,7 @@ export const AudioFileSelector: React.FC<AudioFileSelectorProps> = ({
71
71
  style={[styles.pickButton, { backgroundColor: tokens.colors.surface }]}
72
72
  onPress={onPickAudio}
73
73
  >
74
- <AtomicIcon name="Upload" size="md" color="primary" />
74
+ <AtomicIcon name="cloud-upload-outline" size="md" color="primary" />
75
75
  <AtomicText
76
76
  type="bodyMedium"
77
77
  style={{
@@ -21,7 +21,7 @@ export const InfoBanner: React.FC = () => {
21
21
  { backgroundColor: tokens.colors.primary + "20" },
22
22
  ]}
23
23
  >
24
- <AtomicIcon name="Info" size="sm" color="primary" />
24
+ <AtomicIcon name="information-circle-outline" size="sm" color="primary" />
25
25
  <AtomicText
26
26
  type="labelSmall"
27
27
  style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
@@ -66,7 +66,7 @@ export const ExportActions: React.FC<ExportActionsProps> = ({
66
66
  {isExporting ? (
67
67
  <AtomicSpinner size="sm" color="white" />
68
68
  ) : (
69
- <AtomicIcon name="Download" size="sm" color="onSurface" />
69
+ <AtomicIcon name="download-outline" size="sm" color="onSurface" />
70
70
  )}
71
71
  <AtomicText
72
72
  type="bodyMedium"
@@ -21,7 +21,7 @@ export const ExportInfoBanner: React.FC = () => {
21
21
  { backgroundColor: tokens.colors.primary + "20" },
22
22
  ]}
23
23
  >
24
- <AtomicIcon name="Info" size="sm" color="primary" />
24
+ <AtomicIcon name="information-circle-outline" size="sm" color="primary" />
25
25
  <AtomicText
26
26
  type="labelSmall"
27
27
  style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
@@ -34,7 +34,7 @@ export const ImageSelectionButtons: React.FC<ImageSelectionButtonsProps> = ({
34
34
  ]}
35
35
  onPress={onPickFromGallery}
36
36
  >
37
- <AtomicIcon name="FolderOpen" size="md" color="primary" />
37
+ <AtomicIcon name="folder-open-outline" size="md" color="primary" />
38
38
  <AtomicText
39
39
  type="bodySmall"
40
40
  style={{ color: tokens.colors.textPrimary, marginTop: 8 }}
@@ -53,7 +53,7 @@ export const ImageSelectionButtons: React.FC<ImageSelectionButtonsProps> = ({
53
53
  ]}
54
54
  onPress={onTakePhoto}
55
55
  >
56
- <AtomicIcon name="Camera" size="md" color="primary" />
56
+ <AtomicIcon name="camera-outline" size="md" color="primary" />
57
57
  <AtomicText
58
58
  type="bodySmall"
59
59
  style={{ color: tokens.colors.textPrimary, marginTop: 8 }}
@@ -61,7 +61,7 @@ export const ColorPickerHorizontal: React.FC<ColorPickerHorizontalProps> = ({
61
61
  >
62
62
  {selectedColor === color.value && (
63
63
  <AtomicIcon
64
- name="Check"
64
+ name="checkmark-outline"
65
65
  size="sm"
66
66
  color={color.value === "#FFFFFF" ? "primary" : "onSurface"}
67
67
  />
@@ -56,7 +56,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
56
56
  >
57
57
  {selectedColor === color && (
58
58
  <AtomicIcon
59
- name="Check"
59
+ name="checkmark-outline"
60
60
  size="sm"
61
61
  color={
62
62
  color === "#FFFFFF" || color === "#FCD34D"