@umituz/react-native-ai-generation-content 1.17.21 → 1.17.23
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 +16 -18
- 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,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStatusFilter Hook
|
|
3
|
+
* Handles status filtering (completed, pending, processing, failed)
|
|
4
|
+
* SOLID: Single Responsibility - Only handles status filter state
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback, useMemo } from "react";
|
|
8
|
+
import type { FilterOption } from "../../domain/types/creation-filter";
|
|
9
|
+
|
|
10
|
+
interface UseStatusFilterProps {
|
|
11
|
+
readonly options: FilterOption[];
|
|
12
|
+
readonly t: (key: string) => string;
|
|
13
|
+
readonly defaultId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UseStatusFilterReturn {
|
|
17
|
+
readonly selectedId: string;
|
|
18
|
+
readonly filterOptions: FilterOption[];
|
|
19
|
+
readonly hasActiveFilter: boolean;
|
|
20
|
+
readonly selectFilter: (id: string) => void;
|
|
21
|
+
readonly clearFilter: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useStatusFilter({
|
|
25
|
+
options,
|
|
26
|
+
t,
|
|
27
|
+
defaultId = "all"
|
|
28
|
+
}: UseStatusFilterProps): UseStatusFilterReturn {
|
|
29
|
+
const [selectedId, setSelectedId] = useState(defaultId);
|
|
30
|
+
|
|
31
|
+
const filterOptions = useMemo(() =>
|
|
32
|
+
options.map(opt => ({
|
|
33
|
+
...opt,
|
|
34
|
+
label: opt.labelKey ? t(opt.labelKey) : opt.label
|
|
35
|
+
})),
|
|
36
|
+
[options, t]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const selectFilter = useCallback((id: string) => {
|
|
40
|
+
setSelectedId(id);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const clearFilter = useCallback(() => {
|
|
44
|
+
setSelectedId(defaultId);
|
|
45
|
+
}, [defaultId]);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
selectedId,
|
|
49
|
+
filterOptions,
|
|
50
|
+
hasActiveFilter: selectedId !== defaultId,
|
|
51
|
+
selectFilter,
|
|
52
|
+
clearFilter
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
declare const __DEV__: boolean;
|
|
2
|
-
|
|
3
1
|
import React, { useMemo, useCallback, useState } from "react";
|
|
4
2
|
import { View, FlatList, RefreshControl, StyleSheet } from "react-native";
|
|
5
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -9,21 +7,14 @@ import {
|
|
|
9
7
|
AlertType,
|
|
10
8
|
AlertMode,
|
|
11
9
|
useSharing,
|
|
12
|
-
|
|
13
|
-
type BottomSheetModalRef,
|
|
14
|
-
type DesignTokens,
|
|
10
|
+
FilterSheet,
|
|
15
11
|
} from "@umituz/react-native-design-system";
|
|
16
12
|
import { useFocusEffect } from "@react-navigation/native";
|
|
17
13
|
import { useCreations } from "../hooks/useCreations";
|
|
18
14
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
CreationCard,
|
|
23
|
-
CreationImageViewer,
|
|
24
|
-
GalleryEmptyStates,
|
|
25
|
-
} from "../components";
|
|
26
|
-
import { getFilterCategoriesFromConfig } from "../utils/filterUtils";
|
|
15
|
+
import { useGalleryFilters } from "../hooks/useGalleryFilters";
|
|
16
|
+
import { GalleryHeader, CreationCard, CreationImageViewer, GalleryEmptyStates } from "../components";
|
|
17
|
+
import { MEDIA_FILTER_OPTIONS, STATUS_FILTER_OPTIONS } from "../../domain/types/creation-filter";
|
|
27
18
|
import type { Creation } from "../../domain/entities/Creation";
|
|
28
19
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
29
20
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
@@ -56,7 +47,6 @@ function CreationsGalleryScreenContent({
|
|
|
56
47
|
repository,
|
|
57
48
|
config,
|
|
58
49
|
t,
|
|
59
|
-
locale: _locale = "en-US",
|
|
60
50
|
enableEditing = false,
|
|
61
51
|
onImageEdit,
|
|
62
52
|
onEmptyAction,
|
|
@@ -71,199 +61,134 @@ function CreationsGalleryScreenContent({
|
|
|
71
61
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
72
62
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
73
63
|
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
74
|
-
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
75
64
|
|
|
76
|
-
const { data:
|
|
77
|
-
const creations = creationsData;
|
|
65
|
+
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
78
66
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
79
|
-
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } = useCreationsFilter({ creations });
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
68
|
+
const statusOptions = config.filterConfig?.statusOptions ?? STATUS_FILTER_OPTIONS;
|
|
69
|
+
const mediaOptions = config.filterConfig?.mediaOptions ?? MEDIA_FILTER_OPTIONS;
|
|
70
|
+
const showStatusFilter = config.filterConfig?.showStatusFilter ?? true;
|
|
71
|
+
const showMediaFilter = config.filterConfig?.showMediaFilter ?? true;
|
|
86
72
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
73
|
+
const filters = useGalleryFilters({ creations, statusOptions, mediaOptions, t });
|
|
74
|
+
|
|
75
|
+
useFocusEffect(useCallback(() => { void refetch(); }, [refetch]));
|
|
91
76
|
|
|
92
|
-
const handleShare = useCallback((
|
|
93
|
-
void share(
|
|
77
|
+
const handleShare = useCallback((c: Creation) => {
|
|
78
|
+
void share(c.uri, { dialogTitle: t("common.share") });
|
|
94
79
|
}, [share, t]);
|
|
95
80
|
|
|
96
|
-
const handleDelete = useCallback((
|
|
97
|
-
alert.show(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
id: 'delete',
|
|
107
|
-
label: t("common.delete"),
|
|
108
|
-
style: 'destructive',
|
|
109
|
-
onPress: async () => {
|
|
110
|
-
const success = await deleteMutation.mutateAsync(creation.id);
|
|
111
|
-
if (success) {
|
|
112
|
-
setSelectedCreation(null);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
|
-
}
|
|
118
|
-
);
|
|
81
|
+
const handleDelete = useCallback((c: Creation) => {
|
|
82
|
+
alert.show(AlertType.WARNING, AlertMode.MODAL, t(config.translations.deleteTitle), t(config.translations.deleteMessage), {
|
|
83
|
+
actions: [
|
|
84
|
+
{ id: "cancel", label: t("common.cancel"), onPress: () => {} },
|
|
85
|
+
{ id: "delete", label: t("common.delete"), style: "destructive", onPress: async () => {
|
|
86
|
+
const success = await deleteMutation.mutateAsync(c.id);
|
|
87
|
+
if (success) setSelectedCreation(null);
|
|
88
|
+
}}
|
|
89
|
+
]
|
|
90
|
+
});
|
|
119
91
|
}, [alert, config, deleteMutation, t]);
|
|
120
92
|
|
|
121
|
-
const
|
|
122
|
-
setSelectedCreation(creation);
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
const handleFavorite = useCallback((creation: Creation, isFavorite: boolean) => {
|
|
93
|
+
const handleFavorite = useCallback((c: Creation, isFavorite: boolean) => {
|
|
126
94
|
void (async () => {
|
|
127
95
|
if (!userId) return;
|
|
128
|
-
const success = await repository.updateFavorite(userId,
|
|
129
|
-
if (success)
|
|
130
|
-
void refetch();
|
|
131
|
-
}
|
|
96
|
+
const success = await repository.updateFavorite(userId, c.id, isFavorite);
|
|
97
|
+
if (success) void refetch();
|
|
132
98
|
})();
|
|
133
99
|
}, [userId, repository, refetch]);
|
|
134
100
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
101
|
+
const filterButtons = useMemo(() => {
|
|
102
|
+
const buttons = [];
|
|
103
|
+
if (showStatusFilter) {
|
|
104
|
+
buttons.push({
|
|
105
|
+
id: "status",
|
|
106
|
+
label: t(config.translations.statusFilterTitle ?? "creations.filter.status"),
|
|
107
|
+
icon: "list-outline",
|
|
108
|
+
isActive: filters.statusFilter.hasActiveFilter,
|
|
109
|
+
onPress: filters.openStatusFilter,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (showMediaFilter) {
|
|
113
|
+
buttons.push({
|
|
114
|
+
id: "media",
|
|
115
|
+
label: t(config.translations.mediaFilterTitle ?? "creations.filter.media"),
|
|
116
|
+
icon: "grid-outline",
|
|
117
|
+
isActive: filters.mediaFilter.hasActiveFilter,
|
|
118
|
+
onPress: filters.openMediaFilter,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return buttons;
|
|
122
|
+
}, [showStatusFilter, showMediaFilter, filters, t, config.translations]);
|
|
123
|
+
|
|
124
|
+
const renderItem = useCallback(({ item }: { item: Creation }) => (
|
|
125
|
+
<CreationCard
|
|
126
|
+
creation={item}
|
|
127
|
+
callbacks={{
|
|
128
|
+
onPress: () => setSelectedCreation(item),
|
|
129
|
+
onShare: async () => handleShare(item),
|
|
130
|
+
onDelete: () => handleDelete(item),
|
|
131
|
+
onFavorite: () => handleFavorite(item, !item.isFavorite),
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
), [handleShare, handleDelete, handleFavorite]);
|
|
151
135
|
|
|
152
136
|
const renderHeader = useMemo(() => {
|
|
153
137
|
if ((!creations || creations.length === 0) && !isLoading) return null;
|
|
154
|
-
|
|
155
138
|
return (
|
|
156
|
-
<View style={styles.header}>
|
|
139
|
+
<View style={[styles.header, { paddingTop: insets.top + tokens.spacing.md, backgroundColor: tokens.colors.surface, borderBottomColor: tokens.colors.border }]}>
|
|
157
140
|
<GalleryHeader
|
|
158
141
|
title={t(config.translations.title)}
|
|
159
|
-
count={filtered.length}
|
|
142
|
+
count={filters.filtered.length}
|
|
160
143
|
countLabel={t(config.translations.photoCount)}
|
|
161
|
-
isFiltered={isFiltered}
|
|
162
144
|
showFilter={showFilter}
|
|
163
|
-
|
|
164
|
-
onFilterPress={() => {
|
|
165
|
-
if (__DEV__) {
|
|
166
|
-
// eslint-disable-next-line no-console
|
|
167
|
-
console.log('[CreationsGallery] Filter button pressed');
|
|
168
|
-
}
|
|
169
|
-
filterSheetRef.current?.present();
|
|
170
|
-
}}
|
|
145
|
+
filterButtons={filterButtons}
|
|
171
146
|
/>
|
|
172
147
|
</View>
|
|
173
148
|
);
|
|
174
|
-
}, [creations, isLoading, filtered.length,
|
|
149
|
+
}, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config, insets.top, tokens]);
|
|
175
150
|
|
|
176
151
|
const renderEmpty = useMemo(() => (
|
|
177
152
|
<GalleryEmptyStates
|
|
178
153
|
isLoading={isLoading}
|
|
179
154
|
creations={creations}
|
|
180
|
-
isFiltered={isFiltered}
|
|
155
|
+
isFiltered={filters.isFiltered}
|
|
181
156
|
tokens={tokens}
|
|
182
157
|
t={t}
|
|
183
158
|
emptyTitle={t(config.translations.empty)}
|
|
184
159
|
emptyDescription={t(config.translations.emptyDescription)}
|
|
185
160
|
emptyActionLabel={emptyActionLabel}
|
|
186
161
|
onEmptyAction={onEmptyAction}
|
|
187
|
-
onClearFilters={
|
|
162
|
+
onClearFilters={filters.clearAllFilters}
|
|
188
163
|
/>
|
|
189
|
-
), [isLoading, creations, isFiltered, tokens, t, config, emptyActionLabel, onEmptyAction,
|
|
164
|
+
), [isLoading, creations, filters.isFiltered, tokens, t, config, emptyActionLabel, onEmptyAction, filters.clearAllFilters]);
|
|
190
165
|
|
|
191
166
|
if (selectedCreation) {
|
|
192
|
-
return (
|
|
193
|
-
<CreationDetailScreen
|
|
194
|
-
creation={selectedCreation}
|
|
195
|
-
onClose={() => setSelectedCreation(null)}
|
|
196
|
-
onShare={handleShare}
|
|
197
|
-
onDelete={handleDelete}
|
|
198
|
-
t={t}
|
|
199
|
-
/>
|
|
200
|
-
);
|
|
167
|
+
return <CreationDetailScreen creation={selectedCreation} onClose={() => setSelectedCreation(null)} onShare={handleShare} onDelete={handleDelete} t={t} />;
|
|
201
168
|
}
|
|
202
169
|
|
|
203
170
|
return (
|
|
204
171
|
<View style={[styles.container, { backgroundColor: tokens.colors.background }]}>
|
|
205
172
|
<FlatList
|
|
206
|
-
data={filtered}
|
|
173
|
+
data={filters.filtered}
|
|
207
174
|
renderItem={renderItem}
|
|
208
175
|
keyExtractor={(item) => item.id}
|
|
209
176
|
ListHeaderComponent={renderHeader}
|
|
210
177
|
ListEmptyComponent={renderEmpty}
|
|
211
|
-
contentContainerStyle={[
|
|
212
|
-
styles.listContent,
|
|
213
|
-
(!filtered || filtered.length === 0) && styles.emptyContent,
|
|
214
|
-
]}
|
|
178
|
+
contentContainerStyle={[styles.listContent, { paddingBottom: insets.bottom + 100 }, (!filters.filtered || filters.filtered.length === 0) && styles.emptyContent]}
|
|
215
179
|
showsVerticalScrollIndicator={false}
|
|
216
|
-
refreshControl={
|
|
217
|
-
<RefreshControl
|
|
218
|
-
refreshing={isLoading}
|
|
219
|
-
onRefresh={() => void refetch()}
|
|
220
|
-
tintColor={tokens.colors.primary}
|
|
221
|
-
/>
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
|
|
225
|
-
<CreationImageViewer
|
|
226
|
-
creations={filtered}
|
|
227
|
-
visible={viewerVisible}
|
|
228
|
-
index={viewerIndex}
|
|
229
|
-
onDismiss={() => setViewerVisible(false)}
|
|
230
|
-
onIndexChange={setViewerIndex}
|
|
231
|
-
enableEditing={enableEditing}
|
|
232
|
-
onImageEdit={onImageEdit}
|
|
233
|
-
/>
|
|
234
|
-
|
|
235
|
-
<FilterBottomSheet
|
|
236
|
-
ref={filterSheetRef}
|
|
237
|
-
categories={allCategories}
|
|
238
|
-
selectedIds={selectedIds}
|
|
239
|
-
onFilterPress={(id, catId) => {
|
|
240
|
-
const category = allCategories.find(c => c.id === catId);
|
|
241
|
-
toggleFilter(id, category?.multiSelect);
|
|
242
|
-
}}
|
|
243
|
-
onClearFilters={clearFilters}
|
|
244
|
-
title={t(config.translations.filterTitle)}
|
|
180
|
+
refreshControl={<RefreshControl refreshing={isLoading} onRefresh={() => void refetch()} tintColor={tokens.colors.primary} />}
|
|
245
181
|
/>
|
|
182
|
+
<CreationImageViewer creations={filters.filtered} visible={viewerVisible} index={viewerIndex} onDismiss={() => setViewerVisible(false)} onIndexChange={setViewerIndex} enableEditing={enableEditing} onImageEdit={onImageEdit} />
|
|
183
|
+
<FilterSheet visible={filters.statusFilterVisible} onClose={filters.closeStatusFilter} options={filters.statusFilter.filterOptions} selectedIds={[filters.statusFilter.selectedId]} onFilterPress={filters.statusFilter.selectFilter} onClearFilters={filters.statusFilter.clearFilter} title={t(config.translations.statusFilterTitle ?? "creations.filter.status")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
184
|
+
<FilterSheet visible={filters.mediaFilterVisible} onClose={filters.closeMediaFilter} options={filters.mediaFilter.filterOptions} selectedIds={[filters.mediaFilter.selectedId]} onFilterPress={filters.mediaFilter.selectFilter} onClearFilters={filters.mediaFilter.clearFilter} title={t(config.translations.mediaFilterTitle ?? "creations.filter.media")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
246
185
|
</View>
|
|
247
186
|
);
|
|
248
187
|
}
|
|
249
188
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
paddingTop: insets.top + tokens.spacing.md,
|
|
257
|
-
backgroundColor: tokens.colors.surface,
|
|
258
|
-
borderBottomWidth: 1,
|
|
259
|
-
borderBottomColor: tokens.colors.border,
|
|
260
|
-
},
|
|
261
|
-
listContent: {
|
|
262
|
-
paddingHorizontal: tokens.spacing.md,
|
|
263
|
-
paddingTop: tokens.spacing.md,
|
|
264
|
-
paddingBottom: insets.bottom + 100,
|
|
265
|
-
},
|
|
266
|
-
emptyContent: {
|
|
267
|
-
flexGrow: 1,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
189
|
+
const styles = StyleSheet.create({
|
|
190
|
+
container: { flex: 1 },
|
|
191
|
+
header: { borderBottomWidth: 1 },
|
|
192
|
+
listContent: { paddingHorizontal: 16, paddingTop: 16 },
|
|
193
|
+
emptyContent: { flexGrow: 1 },
|
|
194
|
+
});
|
|
@@ -3,11 +3,39 @@
|
|
|
3
3
|
* Provider-agnostic image-to-video generation feature
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// DOMAIN LAYER - Types
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
// Animation Types
|
|
11
|
+
export type { AnimationStyle, AnimationStyleId } from "./domain";
|
|
12
|
+
|
|
13
|
+
// Music Types
|
|
14
|
+
export type { MusicMood, MusicMoodId } from "./domain";
|
|
15
|
+
|
|
16
|
+
// Duration Types
|
|
17
|
+
export type { VideoDuration, DurationOption } from "./domain";
|
|
18
|
+
|
|
19
|
+
// Form Types
|
|
20
|
+
export type {
|
|
21
|
+
ImageToVideoFormState,
|
|
22
|
+
ImageToVideoFormActions,
|
|
23
|
+
ImageToVideoFormDefaults,
|
|
24
|
+
} from "./domain";
|
|
25
|
+
|
|
26
|
+
// Config Types
|
|
27
|
+
export type {
|
|
28
|
+
ImageToVideoCallbacks,
|
|
29
|
+
ImageToVideoFormConfig,
|
|
30
|
+
ImageToVideoTranslationsExtended,
|
|
31
|
+
} from "./domain";
|
|
32
|
+
|
|
33
|
+
// Core Feature Types
|
|
7
34
|
export type {
|
|
8
35
|
ImageToVideoOptions,
|
|
9
36
|
ImageToVideoRequest,
|
|
10
37
|
ImageToVideoResult,
|
|
38
|
+
ImageToVideoGenerationState,
|
|
11
39
|
ImageToVideoFeatureState,
|
|
12
40
|
ImageToVideoTranslations,
|
|
13
41
|
ImageToVideoInputBuilder,
|
|
@@ -15,13 +43,72 @@ export type {
|
|
|
15
43
|
ImageToVideoFeatureConfig,
|
|
16
44
|
} from "./domain";
|
|
17
45
|
|
|
18
|
-
//
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// DOMAIN LAYER - Constants
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
DEFAULT_ANIMATION_STYLES as IMAGE_TO_VIDEO_ANIMATION_STYLES,
|
|
52
|
+
DEFAULT_ANIMATION_STYLE_ID as IMAGE_TO_VIDEO_DEFAULT_ANIMATION,
|
|
53
|
+
DEFAULT_MUSIC_MOODS as IMAGE_TO_VIDEO_MUSIC_MOODS,
|
|
54
|
+
DEFAULT_MUSIC_MOOD_ID as IMAGE_TO_VIDEO_DEFAULT_MUSIC,
|
|
55
|
+
DEFAULT_DURATION_OPTIONS as IMAGE_TO_VIDEO_DURATION_OPTIONS,
|
|
56
|
+
DEFAULT_VIDEO_DURATION as IMAGE_TO_VIDEO_DEFAULT_DURATION,
|
|
57
|
+
DEFAULT_FORM_VALUES as IMAGE_TO_VIDEO_FORM_DEFAULTS,
|
|
58
|
+
DEFAULT_FORM_CONFIG as IMAGE_TO_VIDEO_CONFIG,
|
|
59
|
+
} from "./domain";
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// INFRASTRUCTURE LAYER
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
19
65
|
export { executeImageToVideo, hasImageToVideoSupport } from "./infrastructure";
|
|
20
66
|
export type { ExecuteImageToVideoOptions } from "./infrastructure";
|
|
21
67
|
|
|
22
|
-
//
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// PRESENTATION LAYER - Hooks
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
useImageToVideoFormState,
|
|
74
|
+
useImageToVideoGeneration,
|
|
75
|
+
useImageToVideoForm,
|
|
76
|
+
} from "./presentation";
|
|
77
|
+
export type {
|
|
78
|
+
UseImageToVideoFormStateOptions,
|
|
79
|
+
UseImageToVideoFormStateReturn,
|
|
80
|
+
UseImageToVideoGenerationOptions,
|
|
81
|
+
UseImageToVideoGenerationReturn,
|
|
82
|
+
UseImageToVideoFormOptions,
|
|
83
|
+
UseImageToVideoFormReturn,
|
|
84
|
+
} from "./presentation";
|
|
85
|
+
|
|
86
|
+
// Provider-based Feature Hook
|
|
23
87
|
export { useImageToVideoFeature } from "./presentation";
|
|
24
88
|
export type {
|
|
25
89
|
UseImageToVideoFeatureProps,
|
|
26
90
|
UseImageToVideoFeatureReturn,
|
|
27
91
|
} from "./presentation";
|
|
92
|
+
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// PRESENTATION LAYER - Components
|
|
95
|
+
// =============================================================================
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
ImageToVideoAnimationStyleSelector,
|
|
99
|
+
ImageToVideoDurationSelector,
|
|
100
|
+
ImageToVideoMusicMoodSelector,
|
|
101
|
+
ImageToVideoSelectionGrid,
|
|
102
|
+
ImageToVideoHeroSection,
|
|
103
|
+
ImageToVideoGenerateButton,
|
|
104
|
+
} from "./presentation";
|
|
105
|
+
|
|
106
|
+
export type {
|
|
107
|
+
ImageToVideoAnimationStyleSelectorProps,
|
|
108
|
+
ImageToVideoDurationSelectorProps,
|
|
109
|
+
ImageToVideoMusicMoodSelectorProps,
|
|
110
|
+
ImageToVideoSelectionGridProps,
|
|
111
|
+
ImageToVideoSelectionGridTranslations,
|
|
112
|
+
ImageToVideoHeroSectionProps,
|
|
113
|
+
ImageToVideoGenerateButtonProps,
|
|
114
|
+
} from "./presentation";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation Style Selector Component
|
|
3
|
+
* Generic component for animation style selection
|
|
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 type { AnimationStyle, AnimationStyleId } from "../../domain/types";
|
|
14
|
+
|
|
15
|
+
export interface AnimationStyleSelectorProps {
|
|
16
|
+
styles: AnimationStyle[];
|
|
17
|
+
selectedStyle: AnimationStyleId;
|
|
18
|
+
onStyleSelect: (styleId: AnimationStyleId) => void;
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const AnimationStyleSelector: React.FC<AnimationStyleSelectorProps> = ({
|
|
23
|
+
styles,
|
|
24
|
+
selectedStyle,
|
|
25
|
+
onStyleSelect,
|
|
26
|
+
label,
|
|
27
|
+
}) => {
|
|
28
|
+
const tokens = useAppDesignTokens();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View style={componentStyles.section}>
|
|
32
|
+
<AtomicText
|
|
33
|
+
type="bodyMedium"
|
|
34
|
+
style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
|
|
35
|
+
>
|
|
36
|
+
{label}
|
|
37
|
+
</AtomicText>
|
|
38
|
+
{styles.map((style) => {
|
|
39
|
+
const isSelected = selectedStyle === style.id;
|
|
40
|
+
return (
|
|
41
|
+
<TouchableOpacity
|
|
42
|
+
key={style.id}
|
|
43
|
+
style={[
|
|
44
|
+
componentStyles.card,
|
|
45
|
+
{
|
|
46
|
+
backgroundColor: isSelected
|
|
47
|
+
? tokens.colors.primary + "20"
|
|
48
|
+
: tokens.colors.surface,
|
|
49
|
+
borderColor: isSelected
|
|
50
|
+
? tokens.colors.primary
|
|
51
|
+
: tokens.colors.borderLight,
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
onPress={() => onStyleSelect(style.id)}
|
|
55
|
+
activeOpacity={0.7}
|
|
56
|
+
>
|
|
57
|
+
<View style={componentStyles.cardContent}>
|
|
58
|
+
<View
|
|
59
|
+
style={[
|
|
60
|
+
componentStyles.iconContainer,
|
|
61
|
+
{
|
|
62
|
+
backgroundColor: isSelected
|
|
63
|
+
? tokens.colors.primary
|
|
64
|
+
: tokens.colors.primary + "20",
|
|
65
|
+
},
|
|
66
|
+
]}
|
|
67
|
+
>
|
|
68
|
+
<AtomicIcon
|
|
69
|
+
name={style.icon as never}
|
|
70
|
+
size="md"
|
|
71
|
+
color={isSelected ? "onSurface" : "primary"}
|
|
72
|
+
/>
|
|
73
|
+
</View>
|
|
74
|
+
<View style={componentStyles.textContainer}>
|
|
75
|
+
<AtomicText
|
|
76
|
+
type="bodyMedium"
|
|
77
|
+
style={[
|
|
78
|
+
componentStyles.styleName,
|
|
79
|
+
{ color: tokens.colors.textPrimary },
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
{style.name}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
<AtomicText
|
|
85
|
+
type="labelSmall"
|
|
86
|
+
style={{ color: tokens.colors.textSecondary }}
|
|
87
|
+
>
|
|
88
|
+
{style.description}
|
|
89
|
+
</AtomicText>
|
|
90
|
+
</View>
|
|
91
|
+
{isSelected && (
|
|
92
|
+
<AtomicIcon name="Check" size="md" color="primary" />
|
|
93
|
+
)}
|
|
94
|
+
</View>
|
|
95
|
+
</TouchableOpacity>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const componentStyles = StyleSheet.create({
|
|
103
|
+
section: {
|
|
104
|
+
padding: 16,
|
|
105
|
+
marginBottom: 8,
|
|
106
|
+
},
|
|
107
|
+
label: {
|
|
108
|
+
fontWeight: "600",
|
|
109
|
+
marginBottom: 12,
|
|
110
|
+
},
|
|
111
|
+
card: {
|
|
112
|
+
padding: 16,
|
|
113
|
+
borderRadius: 12,
|
|
114
|
+
borderWidth: 2,
|
|
115
|
+
marginBottom: 12,
|
|
116
|
+
},
|
|
117
|
+
cardContent: {
|
|
118
|
+
flexDirection: "row",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
},
|
|
121
|
+
iconContainer: {
|
|
122
|
+
width: 48,
|
|
123
|
+
height: 48,
|
|
124
|
+
borderRadius: 24,
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
justifyContent: "center",
|
|
127
|
+
},
|
|
128
|
+
textContainer: {
|
|
129
|
+
flex: 1,
|
|
130
|
+
marginLeft: 12,
|
|
131
|
+
},
|
|
132
|
+
styleName: {
|
|
133
|
+
fontWeight: "600",
|
|
134
|
+
},
|
|
135
|
+
});
|