@umituz/react-native-ai-generation-content 1.17.21 → 1.17.22
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 +1 -1
- package/src/domains/creations/domain/types/creation-filter.ts +1 -3
- package/src/domains/creations/domain/value-objects/CreationsConfig.ts +12 -0
- package/src/domains/creations/presentation/components/FilterSheets.tsx +63 -0
- package/src/domains/creations/presentation/components/GalleryHeader.tsx +95 -93
- package/src/domains/creations/presentation/components/index.ts +1 -0
- package/src/domains/creations/presentation/hooks/index.ts +3 -0
- package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +35 -48
- package/src/domains/creations/presentation/hooks/useGalleryFilters.ts +78 -0
- package/src/domains/creations/presentation/hooks/useMediaFilter.ts +54 -0
- package/src/domains/creations/presentation/hooks/useStatusFilter.ts +54 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +81 -156
- package/src/features/image-to-video/index.ts +90 -3
- package/src/features/image-to-video/presentation/components/AnimationStyleSelector.tsx +135 -0
- package/src/features/image-to-video/presentation/components/DurationSelector.tsx +110 -0
- package/src/features/image-to-video/presentation/components/GenerateButton.tsx +95 -0
- package/src/features/image-to-video/presentation/components/HeroSection.tsx +89 -0
- package/src/features/image-to-video/presentation/components/ImageSelectionGrid.tsx +234 -0
- package/src/features/image-to-video/presentation/components/MusicMoodSelector.tsx +181 -0
- package/src/features/image-to-video/presentation/components/index.ts +30 -0
- package/src/features/image-to-video/presentation/hooks/index.ts +22 -0
- package/src/features/image-to-video/presentation/hooks/useFormState.ts +116 -0
- package/src/features/image-to-video/presentation/hooks/useGeneration.ts +85 -0
- package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +93 -0
- package/src/features/image-to-video/presentation/index.ts +4 -0
- package/src/features/text-to-video/domain/types/callback.types.ts +50 -0
- package/src/features/text-to-video/domain/types/component.types.ts +106 -0
- package/src/features/text-to-video/domain/types/config.types.ts +61 -0
- package/src/features/text-to-video/domain/types/index.ts +48 -4
- package/src/features/text-to-video/domain/types/request.types.ts +36 -0
- package/src/features/text-to-video/domain/types/state.types.ts +53 -0
- package/src/features/text-to-video/index.ts +41 -3
- package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +1 -1
- package/src/features/text-to-video/presentation/components/FrameSelector.tsx +153 -0
- package/src/features/text-to-video/presentation/components/GenerationTabs.tsx +73 -0
- package/src/features/text-to-video/presentation/components/HeroSection.tsx +67 -0
- package/src/features/text-to-video/presentation/components/HintCarousel.tsx +96 -0
- package/src/features/text-to-video/presentation/components/OptionsPanel.tsx +123 -0
- package/src/features/text-to-video/presentation/components/index.ts +10 -0
- package/src/features/text-to-video/presentation/hooks/index.ts +11 -0
- package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +77 -20
- package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +134 -0
- package/src/features/text-to-video/presentation/index.ts +6 -0
- package/src/features/text-to-video/domain/types/text-to-video.types.ts +0 -65
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Button Component
|
|
3
|
+
* Generic generate button with loading state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, TouchableOpacity, ActivityIndicator, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
|
|
14
|
+
export interface GenerateButtonProps {
|
|
15
|
+
onPress: () => void;
|
|
16
|
+
isGenerating: boolean;
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
buttonText: string;
|
|
19
|
+
generatingText: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const GenerateButton: React.FC<GenerateButtonProps> = ({
|
|
23
|
+
onPress,
|
|
24
|
+
isGenerating,
|
|
25
|
+
isDisabled = false,
|
|
26
|
+
buttonText,
|
|
27
|
+
generatingText,
|
|
28
|
+
}) => {
|
|
29
|
+
const tokens = useAppDesignTokens();
|
|
30
|
+
const disabled = isGenerating || isDisabled;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<View style={componentStyles.container}>
|
|
34
|
+
<TouchableOpacity
|
|
35
|
+
style={[
|
|
36
|
+
componentStyles.button,
|
|
37
|
+
{
|
|
38
|
+
backgroundColor: disabled
|
|
39
|
+
? tokens.colors.primary + "60"
|
|
40
|
+
: tokens.colors.primary,
|
|
41
|
+
},
|
|
42
|
+
]}
|
|
43
|
+
onPress={onPress}
|
|
44
|
+
disabled={disabled}
|
|
45
|
+
activeOpacity={0.8}
|
|
46
|
+
>
|
|
47
|
+
{isGenerating ? (
|
|
48
|
+
<>
|
|
49
|
+
<ActivityIndicator
|
|
50
|
+
size="small"
|
|
51
|
+
color={tokens.colors.textInverse}
|
|
52
|
+
style={componentStyles.loader}
|
|
53
|
+
/>
|
|
54
|
+
<AtomicText
|
|
55
|
+
type="bodyMedium"
|
|
56
|
+
style={[componentStyles.text, { color: tokens.colors.textInverse }]}
|
|
57
|
+
>
|
|
58
|
+
{generatingText}
|
|
59
|
+
</AtomicText>
|
|
60
|
+
</>
|
|
61
|
+
) : (
|
|
62
|
+
<>
|
|
63
|
+
<AtomicIcon name="videocam-outline" size="md" color="onSurface" />
|
|
64
|
+
<AtomicText
|
|
65
|
+
type="bodyMedium"
|
|
66
|
+
style={[componentStyles.text, { color: tokens.colors.textInverse }]}
|
|
67
|
+
>
|
|
68
|
+
{buttonText}
|
|
69
|
+
</AtomicText>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
</TouchableOpacity>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const componentStyles = StyleSheet.create({
|
|
78
|
+
container: {
|
|
79
|
+
padding: 16,
|
|
80
|
+
},
|
|
81
|
+
button: {
|
|
82
|
+
flexDirection: "row",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
justifyContent: "center",
|
|
85
|
+
paddingVertical: 16,
|
|
86
|
+
borderRadius: 16,
|
|
87
|
+
},
|
|
88
|
+
loader: {
|
|
89
|
+
marginRight: 8,
|
|
90
|
+
},
|
|
91
|
+
text: {
|
|
92
|
+
fontWeight: "600",
|
|
93
|
+
marginLeft: 8,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hero Section Component
|
|
3
|
+
* Generic hero section with gradient background
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
9
|
+
import {
|
|
10
|
+
AtomicText,
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
useAppDesignTokens,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
|
|
15
|
+
export interface HeroSectionProps {
|
|
16
|
+
title: string;
|
|
17
|
+
subtitle: string;
|
|
18
|
+
iconName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const HeroSection: React.FC<HeroSectionProps> = ({
|
|
22
|
+
title,
|
|
23
|
+
subtitle,
|
|
24
|
+
iconName = "image-outline",
|
|
25
|
+
}) => {
|
|
26
|
+
const tokens = useAppDesignTokens();
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<View style={componentStyles.container}>
|
|
30
|
+
<LinearGradient
|
|
31
|
+
colors={[
|
|
32
|
+
tokens.colors.secondary ?? tokens.colors.info,
|
|
33
|
+
tokens.colors.primary,
|
|
34
|
+
]}
|
|
35
|
+
start={{ x: 0, y: 0 }}
|
|
36
|
+
end={{ x: 1, y: 1 }}
|
|
37
|
+
style={componentStyles.gradient}
|
|
38
|
+
>
|
|
39
|
+
<View style={componentStyles.iconContainer}>
|
|
40
|
+
<AtomicIcon name={iconName as never} size="xl" color="onSurface" />
|
|
41
|
+
</View>
|
|
42
|
+
<AtomicText
|
|
43
|
+
type="headlineSmall"
|
|
44
|
+
style={[componentStyles.title, { color: tokens.colors.textInverse }]}
|
|
45
|
+
>
|
|
46
|
+
{title}
|
|
47
|
+
</AtomicText>
|
|
48
|
+
<AtomicText
|
|
49
|
+
type="bodyMedium"
|
|
50
|
+
style={[componentStyles.subtitle, { color: tokens.colors.textInverse }]}
|
|
51
|
+
>
|
|
52
|
+
{subtitle}
|
|
53
|
+
</AtomicText>
|
|
54
|
+
</LinearGradient>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const componentStyles = StyleSheet.create({
|
|
60
|
+
container: {
|
|
61
|
+
marginHorizontal: 16,
|
|
62
|
+
marginBottom: 24,
|
|
63
|
+
borderRadius: 20,
|
|
64
|
+
overflow: "hidden",
|
|
65
|
+
},
|
|
66
|
+
gradient: {
|
|
67
|
+
padding: 32,
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
justifyContent: "center",
|
|
70
|
+
},
|
|
71
|
+
iconContainer: {
|
|
72
|
+
width: 80,
|
|
73
|
+
height: 80,
|
|
74
|
+
borderRadius: 40,
|
|
75
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
justifyContent: "center",
|
|
78
|
+
},
|
|
79
|
+
title: {
|
|
80
|
+
textAlign: "center",
|
|
81
|
+
marginTop: 16,
|
|
82
|
+
fontWeight: "700",
|
|
83
|
+
},
|
|
84
|
+
subtitle: {
|
|
85
|
+
textAlign: "center",
|
|
86
|
+
marginTop: 8,
|
|
87
|
+
opacity: 0.9,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Selection Grid Component
|
|
3
|
+
* Generic component for image selection display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
ScrollView,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
Image,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
import {
|
|
15
|
+
AtomicText,
|
|
16
|
+
AtomicIcon,
|
|
17
|
+
useAppDesignTokens,
|
|
18
|
+
} from "@umituz/react-native-design-system";
|
|
19
|
+
|
|
20
|
+
export interface ImageSelectionGridTranslations {
|
|
21
|
+
selectedImages: string;
|
|
22
|
+
selectImages: string;
|
|
23
|
+
chooseUpTo: string;
|
|
24
|
+
addMore: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ImageSelectionGridProps {
|
|
28
|
+
images: string[];
|
|
29
|
+
maxImages: number;
|
|
30
|
+
onSelectImages: () => void;
|
|
31
|
+
onRemoveImage: (index: number) => void;
|
|
32
|
+
translations: ImageSelectionGridTranslations;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ImageSelectionGrid: React.FC<ImageSelectionGridProps> = ({
|
|
36
|
+
images,
|
|
37
|
+
maxImages,
|
|
38
|
+
onSelectImages,
|
|
39
|
+
onRemoveImage,
|
|
40
|
+
translations,
|
|
41
|
+
}) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
|
|
44
|
+
if (images.length === 0) {
|
|
45
|
+
return (
|
|
46
|
+
<View style={componentStyles.section}>
|
|
47
|
+
<AtomicText
|
|
48
|
+
type="bodyMedium"
|
|
49
|
+
style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
|
|
50
|
+
>
|
|
51
|
+
{translations.selectedImages} (0/{maxImages})
|
|
52
|
+
</AtomicText>
|
|
53
|
+
<TouchableOpacity
|
|
54
|
+
style={[
|
|
55
|
+
componentStyles.uploadBox,
|
|
56
|
+
{
|
|
57
|
+
backgroundColor: tokens.colors.surface,
|
|
58
|
+
borderColor: tokens.colors.borderLight,
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
onPress={onSelectImages}
|
|
62
|
+
activeOpacity={0.7}
|
|
63
|
+
>
|
|
64
|
+
<AtomicIcon name="Upload" size="xl" color="primary" />
|
|
65
|
+
<AtomicText
|
|
66
|
+
type="bodyMedium"
|
|
67
|
+
style={[
|
|
68
|
+
componentStyles.uploadText,
|
|
69
|
+
{ color: tokens.colors.primary },
|
|
70
|
+
]}
|
|
71
|
+
>
|
|
72
|
+
{translations.selectImages}
|
|
73
|
+
</AtomicText>
|
|
74
|
+
<AtomicText
|
|
75
|
+
type="labelSmall"
|
|
76
|
+
style={{ color: tokens.colors.textSecondary }}
|
|
77
|
+
>
|
|
78
|
+
{translations.chooseUpTo.replace("{max}", String(maxImages))}
|
|
79
|
+
</AtomicText>
|
|
80
|
+
</TouchableOpacity>
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<View style={componentStyles.section}>
|
|
87
|
+
<AtomicText
|
|
88
|
+
type="bodyMedium"
|
|
89
|
+
style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
|
|
90
|
+
>
|
|
91
|
+
{translations.selectedImages} ({images.length}/{maxImages})
|
|
92
|
+
</AtomicText>
|
|
93
|
+
<ScrollView
|
|
94
|
+
horizontal
|
|
95
|
+
showsHorizontalScrollIndicator={false}
|
|
96
|
+
style={componentStyles.scroll}
|
|
97
|
+
>
|
|
98
|
+
{images.map((uri, index) => (
|
|
99
|
+
<View key={index} style={componentStyles.imageCard}>
|
|
100
|
+
<Image
|
|
101
|
+
source={{ uri }}
|
|
102
|
+
style={componentStyles.imagePreview}
|
|
103
|
+
resizeMode="cover"
|
|
104
|
+
/>
|
|
105
|
+
<TouchableOpacity
|
|
106
|
+
style={[
|
|
107
|
+
componentStyles.removeButton,
|
|
108
|
+
{ backgroundColor: tokens.colors.error },
|
|
109
|
+
]}
|
|
110
|
+
onPress={() => onRemoveImage(index)}
|
|
111
|
+
>
|
|
112
|
+
<AtomicIcon name="close-circle" size="sm" color="onSurface" />
|
|
113
|
+
</TouchableOpacity>
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
componentStyles.imageBadge,
|
|
117
|
+
{ backgroundColor: tokens.colors.primary },
|
|
118
|
+
]}
|
|
119
|
+
>
|
|
120
|
+
<AtomicText
|
|
121
|
+
type="labelSmall"
|
|
122
|
+
style={[
|
|
123
|
+
componentStyles.badgeText,
|
|
124
|
+
{ color: tokens.colors.textInverse },
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
{index + 1}
|
|
128
|
+
</AtomicText>
|
|
129
|
+
</View>
|
|
130
|
+
</View>
|
|
131
|
+
))}
|
|
132
|
+
|
|
133
|
+
{images.length < maxImages && (
|
|
134
|
+
<TouchableOpacity
|
|
135
|
+
style={[
|
|
136
|
+
componentStyles.addMoreCard,
|
|
137
|
+
{
|
|
138
|
+
backgroundColor: tokens.colors.surface,
|
|
139
|
+
borderColor: tokens.colors.borderLight,
|
|
140
|
+
},
|
|
141
|
+
]}
|
|
142
|
+
onPress={onSelectImages}
|
|
143
|
+
activeOpacity={0.7}
|
|
144
|
+
>
|
|
145
|
+
<AtomicIcon name="add" size="lg" color="primary" />
|
|
146
|
+
<AtomicText
|
|
147
|
+
type="labelSmall"
|
|
148
|
+
style={[
|
|
149
|
+
componentStyles.addMoreText,
|
|
150
|
+
{ color: tokens.colors.primary },
|
|
151
|
+
]}
|
|
152
|
+
>
|
|
153
|
+
{translations.addMore}
|
|
154
|
+
</AtomicText>
|
|
155
|
+
</TouchableOpacity>
|
|
156
|
+
)}
|
|
157
|
+
</ScrollView>
|
|
158
|
+
</View>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const componentStyles = StyleSheet.create({
|
|
163
|
+
section: {
|
|
164
|
+
padding: 16,
|
|
165
|
+
marginBottom: 8,
|
|
166
|
+
},
|
|
167
|
+
label: {
|
|
168
|
+
fontWeight: "600",
|
|
169
|
+
marginBottom: 12,
|
|
170
|
+
},
|
|
171
|
+
uploadBox: {
|
|
172
|
+
padding: 48,
|
|
173
|
+
borderRadius: 16,
|
|
174
|
+
alignItems: "center",
|
|
175
|
+
justifyContent: "center",
|
|
176
|
+
borderWidth: 2,
|
|
177
|
+
borderStyle: "dashed",
|
|
178
|
+
},
|
|
179
|
+
uploadText: {
|
|
180
|
+
fontWeight: "600",
|
|
181
|
+
marginTop: 12,
|
|
182
|
+
},
|
|
183
|
+
scroll: {
|
|
184
|
+
marginHorizontal: -16,
|
|
185
|
+
paddingHorizontal: 16,
|
|
186
|
+
},
|
|
187
|
+
imageCard: {
|
|
188
|
+
width: 120,
|
|
189
|
+
height: 120,
|
|
190
|
+
borderRadius: 12,
|
|
191
|
+
marginRight: 12,
|
|
192
|
+
position: "relative",
|
|
193
|
+
},
|
|
194
|
+
imagePreview: {
|
|
195
|
+
width: "100%",
|
|
196
|
+
height: "100%",
|
|
197
|
+
borderRadius: 12,
|
|
198
|
+
},
|
|
199
|
+
removeButton: {
|
|
200
|
+
position: "absolute",
|
|
201
|
+
top: 6,
|
|
202
|
+
right: 6,
|
|
203
|
+
width: 24,
|
|
204
|
+
height: 24,
|
|
205
|
+
borderRadius: 12,
|
|
206
|
+
alignItems: "center",
|
|
207
|
+
justifyContent: "center",
|
|
208
|
+
},
|
|
209
|
+
imageBadge: {
|
|
210
|
+
position: "absolute",
|
|
211
|
+
bottom: 6,
|
|
212
|
+
left: 6,
|
|
213
|
+
width: 24,
|
|
214
|
+
height: 24,
|
|
215
|
+
borderRadius: 12,
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
justifyContent: "center",
|
|
218
|
+
},
|
|
219
|
+
badgeText: {
|
|
220
|
+
fontSize: 10,
|
|
221
|
+
},
|
|
222
|
+
addMoreCard: {
|
|
223
|
+
width: 120,
|
|
224
|
+
height: 120,
|
|
225
|
+
borderRadius: 12,
|
|
226
|
+
alignItems: "center",
|
|
227
|
+
justifyContent: "center",
|
|
228
|
+
borderWidth: 2,
|
|
229
|
+
borderStyle: "dashed",
|
|
230
|
+
},
|
|
231
|
+
addMoreText: {
|
|
232
|
+
marginTop: 4,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Music Mood Selector Component
|
|
3
|
+
* Generic component for music mood selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import type { MusicMood, MusicMoodId } from "../../domain/types";
|
|
14
|
+
|
|
15
|
+
export interface MusicMoodSelectorProps {
|
|
16
|
+
moods: MusicMood[];
|
|
17
|
+
selectedMood: MusicMoodId;
|
|
18
|
+
onMoodSelect: (moodId: MusicMoodId) => void;
|
|
19
|
+
label: string;
|
|
20
|
+
hasCustomAudio?: boolean;
|
|
21
|
+
customAudioLabel?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const MusicMoodSelector: React.FC<MusicMoodSelectorProps> = ({
|
|
25
|
+
moods,
|
|
26
|
+
selectedMood,
|
|
27
|
+
onMoodSelect,
|
|
28
|
+
label,
|
|
29
|
+
hasCustomAudio = false,
|
|
30
|
+
customAudioLabel,
|
|
31
|
+
}) => {
|
|
32
|
+
const tokens = useAppDesignTokens();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<View style={componentStyles.section}>
|
|
36
|
+
<AtomicText
|
|
37
|
+
type="bodyMedium"
|
|
38
|
+
style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
|
|
39
|
+
>
|
|
40
|
+
{label}
|
|
41
|
+
</AtomicText>
|
|
42
|
+
<ScrollView
|
|
43
|
+
horizontal
|
|
44
|
+
showsHorizontalScrollIndicator={false}
|
|
45
|
+
style={componentStyles.scroll}
|
|
46
|
+
>
|
|
47
|
+
{moods.map((mood) => {
|
|
48
|
+
const isSelected = selectedMood === mood.id;
|
|
49
|
+
return (
|
|
50
|
+
<TouchableOpacity
|
|
51
|
+
key={mood.id}
|
|
52
|
+
style={[
|
|
53
|
+
componentStyles.card,
|
|
54
|
+
{
|
|
55
|
+
backgroundColor: isSelected
|
|
56
|
+
? tokens.colors.primary
|
|
57
|
+
: tokens.colors.surface,
|
|
58
|
+
borderColor: isSelected
|
|
59
|
+
? tokens.colors.primary
|
|
60
|
+
: tokens.colors.borderLight,
|
|
61
|
+
},
|
|
62
|
+
]}
|
|
63
|
+
onPress={() => onMoodSelect(mood.id)}
|
|
64
|
+
activeOpacity={0.7}
|
|
65
|
+
>
|
|
66
|
+
<View
|
|
67
|
+
style={[
|
|
68
|
+
componentStyles.iconContainer,
|
|
69
|
+
{
|
|
70
|
+
backgroundColor: isSelected
|
|
71
|
+
? "rgba(255, 255, 255, 0.2)"
|
|
72
|
+
: "rgba(0, 0, 0, 0.05)",
|
|
73
|
+
},
|
|
74
|
+
]}
|
|
75
|
+
>
|
|
76
|
+
<AtomicIcon
|
|
77
|
+
name={mood.icon as never}
|
|
78
|
+
size="lg"
|
|
79
|
+
color={isSelected ? "onSurface" : "primary"}
|
|
80
|
+
/>
|
|
81
|
+
</View>
|
|
82
|
+
<AtomicText
|
|
83
|
+
type="bodySmall"
|
|
84
|
+
style={[
|
|
85
|
+
componentStyles.moodName,
|
|
86
|
+
{
|
|
87
|
+
color: isSelected
|
|
88
|
+
? tokens.colors.textInverse
|
|
89
|
+
: tokens.colors.textPrimary,
|
|
90
|
+
},
|
|
91
|
+
]}
|
|
92
|
+
>
|
|
93
|
+
{mood.name}
|
|
94
|
+
</AtomicText>
|
|
95
|
+
<AtomicText
|
|
96
|
+
type="labelSmall"
|
|
97
|
+
style={[
|
|
98
|
+
componentStyles.moodDescription,
|
|
99
|
+
{
|
|
100
|
+
color: isSelected
|
|
101
|
+
? tokens.colors.textInverse
|
|
102
|
+
: tokens.colors.textSecondary,
|
|
103
|
+
opacity: isSelected ? 0.9 : 0.7,
|
|
104
|
+
},
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
{mood.description}
|
|
108
|
+
</AtomicText>
|
|
109
|
+
</TouchableOpacity>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</ScrollView>
|
|
113
|
+
{selectedMood === "custom" && hasCustomAudio && customAudioLabel && (
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
componentStyles.customBadge,
|
|
117
|
+
{ backgroundColor: tokens.colors.primary + "20" },
|
|
118
|
+
]}
|
|
119
|
+
>
|
|
120
|
+
<AtomicIcon name="checkmark-circle-outline" size="sm" color="primary" />
|
|
121
|
+
<AtomicText
|
|
122
|
+
type="labelSmall"
|
|
123
|
+
style={[componentStyles.customBadgeText, { color: tokens.colors.primary }]}
|
|
124
|
+
>
|
|
125
|
+
{customAudioLabel}
|
|
126
|
+
</AtomicText>
|
|
127
|
+
</View>
|
|
128
|
+
)}
|
|
129
|
+
</View>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const componentStyles = StyleSheet.create({
|
|
134
|
+
section: {
|
|
135
|
+
padding: 16,
|
|
136
|
+
marginBottom: 8,
|
|
137
|
+
},
|
|
138
|
+
label: {
|
|
139
|
+
fontWeight: "600",
|
|
140
|
+
marginBottom: 12,
|
|
141
|
+
},
|
|
142
|
+
scroll: {
|
|
143
|
+
marginHorizontal: -16,
|
|
144
|
+
paddingHorizontal: 16,
|
|
145
|
+
},
|
|
146
|
+
card: {
|
|
147
|
+
width: 140,
|
|
148
|
+
padding: 16,
|
|
149
|
+
borderRadius: 16,
|
|
150
|
+
borderWidth: 2,
|
|
151
|
+
marginRight: 12,
|
|
152
|
+
alignItems: "center",
|
|
153
|
+
},
|
|
154
|
+
iconContainer: {
|
|
155
|
+
width: 56,
|
|
156
|
+
height: 56,
|
|
157
|
+
borderRadius: 28,
|
|
158
|
+
alignItems: "center",
|
|
159
|
+
justifyContent: "center",
|
|
160
|
+
},
|
|
161
|
+
moodName: {
|
|
162
|
+
fontWeight: "600",
|
|
163
|
+
marginTop: 8,
|
|
164
|
+
textAlign: "center",
|
|
165
|
+
},
|
|
166
|
+
moodDescription: {
|
|
167
|
+
marginTop: 4,
|
|
168
|
+
textAlign: "center",
|
|
169
|
+
},
|
|
170
|
+
customBadge: {
|
|
171
|
+
flexDirection: "row",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
padding: 12,
|
|
174
|
+
borderRadius: 12,
|
|
175
|
+
marginTop: 12,
|
|
176
|
+
},
|
|
177
|
+
customBadgeText: {
|
|
178
|
+
marginLeft: 8,
|
|
179
|
+
flex: 1,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image-to-Video Components Index
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Animation Style Selector
|
|
6
|
+
export { AnimationStyleSelector as ImageToVideoAnimationStyleSelector } from "./AnimationStyleSelector";
|
|
7
|
+
export type { AnimationStyleSelectorProps as ImageToVideoAnimationStyleSelectorProps } from "./AnimationStyleSelector";
|
|
8
|
+
|
|
9
|
+
// Duration Selector
|
|
10
|
+
export { DurationSelector as ImageToVideoDurationSelector } from "./DurationSelector";
|
|
11
|
+
export type { DurationSelectorProps as ImageToVideoDurationSelectorProps } from "./DurationSelector";
|
|
12
|
+
|
|
13
|
+
// Music Mood Selector
|
|
14
|
+
export { MusicMoodSelector as ImageToVideoMusicMoodSelector } from "./MusicMoodSelector";
|
|
15
|
+
export type { MusicMoodSelectorProps as ImageToVideoMusicMoodSelectorProps } from "./MusicMoodSelector";
|
|
16
|
+
|
|
17
|
+
// Image Selection Grid
|
|
18
|
+
export { ImageSelectionGrid as ImageToVideoSelectionGrid } from "./ImageSelectionGrid";
|
|
19
|
+
export type {
|
|
20
|
+
ImageSelectionGridProps as ImageToVideoSelectionGridProps,
|
|
21
|
+
ImageSelectionGridTranslations as ImageToVideoSelectionGridTranslations,
|
|
22
|
+
} from "./ImageSelectionGrid";
|
|
23
|
+
|
|
24
|
+
// Hero Section
|
|
25
|
+
export { HeroSection as ImageToVideoHeroSection } from "./HeroSection";
|
|
26
|
+
export type { HeroSectionProps as ImageToVideoHeroSectionProps } from "./HeroSection";
|
|
27
|
+
|
|
28
|
+
// Generate Button
|
|
29
|
+
export { GenerateButton as ImageToVideoGenerateButton } from "./GenerateButton";
|
|
30
|
+
export type { GenerateButtonProps as ImageToVideoGenerateButtonProps } from "./GenerateButton";
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
// Form State Hook
|
|
2
|
+
export { useFormState as useImageToVideoFormState } from "./useFormState";
|
|
3
|
+
export type {
|
|
4
|
+
UseFormStateOptions as UseImageToVideoFormStateOptions,
|
|
5
|
+
UseFormStateReturn as UseImageToVideoFormStateReturn,
|
|
6
|
+
} from "./useFormState";
|
|
7
|
+
|
|
8
|
+
// Generation Hook
|
|
9
|
+
export { useGeneration as useImageToVideoGeneration } from "./useGeneration";
|
|
10
|
+
export type {
|
|
11
|
+
UseGenerationOptions as UseImageToVideoGenerationOptions,
|
|
12
|
+
UseGenerationReturn as UseImageToVideoGenerationReturn,
|
|
13
|
+
} from "./useGeneration";
|
|
14
|
+
|
|
15
|
+
// Combined Form Hook
|
|
16
|
+
export { useImageToVideoForm } from "./useImageToVideoForm";
|
|
17
|
+
export type {
|
|
18
|
+
UseImageToVideoFormOptions,
|
|
19
|
+
UseImageToVideoFormReturn,
|
|
20
|
+
} from "./useImageToVideoForm";
|
|
21
|
+
|
|
22
|
+
// Provider-based Feature Hook
|
|
1
23
|
export { useImageToVideoFeature } from "./useImageToVideoFeature";
|
|
2
24
|
export type {
|
|
3
25
|
UseImageToVideoFeatureProps,
|