@umituz/react-native-ai-generation-content 1.61.39 → 1.61.40
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/infrastructure/repositories/CreationsWriter.ts +16 -213
- package/src/domains/creations/infrastructure/repositories/creations-operations.ts +128 -0
- package/src/domains/creations/infrastructure/repositories/creations-state-operations.ts +86 -0
- package/src/domains/creations/presentation/components/GalleryScreenHeader.tsx +54 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +23 -56
- package/src/domains/creations/presentation/utils/filter-buttons.util.ts +75 -0
- package/src/domains/generation/wizard/presentation/screens/GeneratingScreen.tsx +36 -43
- package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +6 -39
- package/src/domains/scenarios/presentation/screens/ScenarioPreviewScreen.tsx +7 -25
- package/src/features/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +26 -46
- package/src/features/shared/index.ts +6 -0
- package/src/features/shared/presentation/components/AutoSkipPreview.tsx +24 -0
- package/src/features/shared/presentation/components/index.ts +6 -0
- package/src/features/shared/presentation/utils/index.ts +14 -0
- package/src/features/shared/presentation/utils/wizard-flow.utils.ts +84 -0
- package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +26 -46
- package/src/features/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +26 -46
- package/src/infrastructure/logging/debug.util.ts +1 -1
- package/src/infrastructure/logging/index.ts +1 -1
- package/src/infrastructure/validation/advanced-validator.ts +97 -0
- package/src/infrastructure/validation/ai-validator.ts +77 -0
- package/src/infrastructure/validation/base-validator.ts +149 -0
- package/src/infrastructure/validation/entity-validator.ts +64 -0
- package/src/infrastructure/validation/input-validator.ts +37 -409
- package/src/infrastructure/validation/sanitizer.ts +43 -0
- package/src/presentation/components/buttons/ContinueButton.tsx +72 -0
- package/src/presentation/components/buttons/index.ts +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter Buttons Utility
|
|
3
|
+
* Creates filter buttons for the gallery screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface FilterButton {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly icon: string;
|
|
10
|
+
readonly isActive: boolean;
|
|
11
|
+
readonly onPress: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FilterButtonsConfig {
|
|
15
|
+
readonly showStatusFilter: boolean;
|
|
16
|
+
readonly showMediaFilter: boolean;
|
|
17
|
+
readonly statusFilterActive: boolean;
|
|
18
|
+
readonly mediaFilterActive: boolean;
|
|
19
|
+
readonly statusFilterLabel: string;
|
|
20
|
+
readonly mediaFilterLabel: string;
|
|
21
|
+
readonly onStatusFilterPress: () => void;
|
|
22
|
+
readonly onMediaFilterPress: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates filter buttons array for gallery header
|
|
27
|
+
*/
|
|
28
|
+
export function createFilterButtons(config: FilterButtonsConfig): FilterButton[] {
|
|
29
|
+
const buttons: FilterButton[] = [];
|
|
30
|
+
|
|
31
|
+
if (config.showStatusFilter) {
|
|
32
|
+
buttons.push({
|
|
33
|
+
id: "status",
|
|
34
|
+
label: config.statusFilterLabel,
|
|
35
|
+
icon: "list-outline",
|
|
36
|
+
isActive: config.statusFilterActive,
|
|
37
|
+
onPress: config.onStatusFilterPress,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (config.showMediaFilter) {
|
|
42
|
+
buttons.push({
|
|
43
|
+
id: "media",
|
|
44
|
+
label: config.mediaFilterLabel,
|
|
45
|
+
icon: "grid-outline",
|
|
46
|
+
isActive: config.mediaFilterActive,
|
|
47
|
+
onPress: config.onMediaFilterPress,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return buttons;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates item title for creation card
|
|
56
|
+
*/
|
|
57
|
+
export function createItemTitle(
|
|
58
|
+
item: { type: string; metadata?: Record<string, unknown> },
|
|
59
|
+
config: {
|
|
60
|
+
readonly types?: readonly { id: string; labelKey?: string }[];
|
|
61
|
+
readonly getCreationTitle?: (params: { type: string; metadata?: Record<string, unknown> }) => string;
|
|
62
|
+
},
|
|
63
|
+
t: (key: string) => string
|
|
64
|
+
): string {
|
|
65
|
+
if (config.getCreationTitle) {
|
|
66
|
+
return config.getCreationTitle({ type: item.type, metadata: item.metadata });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const typeConfig = config.types?.find((tc) => tc.id === item.type);
|
|
70
|
+
if (typeConfig?.labelKey) {
|
|
71
|
+
return t(typeConfig.labelKey);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return item.type.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
75
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React, { useMemo } from "react";
|
|
9
9
|
import { View, StyleSheet, ActivityIndicator, TouchableOpacity } from "react-native";
|
|
10
|
-
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
10
|
+
import { useAppDesignTokens, AtomicText, ScreenLayout } from "@umituz/react-native-design-system";
|
|
11
11
|
import { useGenerationPhase } from "../hooks/useGenerationPhase";
|
|
12
12
|
import { IndeterminateProgressBar } from "../components/IndeterminateProgressBar";
|
|
13
13
|
|
|
@@ -25,7 +25,6 @@ export interface GeneratingScreenProps {
|
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
27
|
readonly t: (key: string) => string;
|
|
28
|
-
/** Called when user dismisses the screen - generation continues in background */
|
|
29
28
|
readonly onDismiss?: () => void;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -38,13 +37,6 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
|
|
|
38
37
|
const tokens = useAppDesignTokens();
|
|
39
38
|
const phase = useGenerationPhase();
|
|
40
39
|
|
|
41
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
42
|
-
console.log("[GeneratingScreen] Rendering", {
|
|
43
|
-
phase,
|
|
44
|
-
scenarioId: scenario?.id,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
const messages = useMemo(() => {
|
|
49
41
|
const custom = scenario?.generatingMessages;
|
|
50
42
|
return {
|
|
@@ -69,48 +61,49 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
|
|
|
69
61
|
}, [phase, t, messages.waitMessage]);
|
|
70
62
|
|
|
71
63
|
return (
|
|
72
|
-
<
|
|
73
|
-
<View style={styles.
|
|
74
|
-
<
|
|
64
|
+
<ScreenLayout backgroundColor={tokens.colors.backgroundPrimary}>
|
|
65
|
+
<View style={styles.container}>
|
|
66
|
+
<View style={styles.content}>
|
|
67
|
+
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
<AtomicText type="headlineMedium" style={styles.title}>
|
|
70
|
+
{messages.title}
|
|
71
|
+
</AtomicText>
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
<AtomicText type="bodyMedium" style={[styles.message, { color: tokens.colors.textSecondary }]}>
|
|
74
|
+
{statusMessage}
|
|
75
|
+
</AtomicText>
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
<View style={styles.progressContainer}>
|
|
78
|
+
<IndeterminateProgressBar
|
|
79
|
+
backgroundColor={tokens.colors.surfaceVariant}
|
|
80
|
+
fillColor={tokens.colors.primary}
|
|
81
|
+
/>
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
{scenario && (
|
|
85
|
+
<AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
86
|
+
{scenario.title || scenario.id}
|
|
87
|
+
</AtomicText>
|
|
88
|
+
)}
|
|
90
89
|
|
|
91
|
-
{scenario && (
|
|
92
90
|
<AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
93
|
-
{
|
|
91
|
+
{messages.hint}
|
|
94
92
|
</AtomicText>
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
<AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
98
|
-
{messages.hint}
|
|
99
|
-
</AtomicText>
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
{onDismiss && (
|
|
95
|
+
<TouchableOpacity
|
|
96
|
+
style={[styles.backgroundHintButton, { borderColor: tokens.colors.primary }]}
|
|
97
|
+
onPress={onDismiss}
|
|
98
|
+
>
|
|
99
|
+
<AtomicText type="bodyLarge" style={[styles.backgroundHint, { color: tokens.colors.primary }]}>
|
|
100
|
+
{messages.backgroundHint}
|
|
101
|
+
</AtomicText>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
)}
|
|
104
|
+
</View>
|
|
112
105
|
</View>
|
|
113
|
-
</
|
|
106
|
+
</ScreenLayout>
|
|
114
107
|
);
|
|
115
108
|
};
|
|
116
109
|
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React, { useState, useCallback, useMemo } from "react";
|
|
8
|
-
import { View, TextInput,
|
|
8
|
+
import { View, TextInput, StyleSheet } from "react-native";
|
|
9
9
|
import {
|
|
10
10
|
AtomicText,
|
|
11
11
|
AtomicButton,
|
|
12
|
-
AtomicIcon,
|
|
13
12
|
useAppDesignTokens,
|
|
14
13
|
ScreenLayout,
|
|
15
14
|
NavigationHeader,
|
|
16
15
|
type DesignTokens,
|
|
17
16
|
} from "@umituz/react-native-design-system";
|
|
17
|
+
import { ContinueButton } from "#presentation/components/buttons";
|
|
18
18
|
import type { TextInputScreenProps } from "./TextInputScreen.types";
|
|
19
19
|
|
|
20
20
|
export type {
|
|
@@ -57,33 +57,11 @@ export const TextInputScreen: React.FC<TextInputScreenProps> = ({
|
|
|
57
57
|
title=""
|
|
58
58
|
onBackPress={onBack}
|
|
59
59
|
rightElement={
|
|
60
|
-
<
|
|
60
|
+
<ContinueButton
|
|
61
|
+
label={translations.continueButton}
|
|
62
|
+
canContinue={canContinue}
|
|
61
63
|
onPress={handleContinue}
|
|
62
|
-
|
|
63
|
-
disabled={!canContinue}
|
|
64
|
-
style={[
|
|
65
|
-
styles.continueButton,
|
|
66
|
-
{
|
|
67
|
-
backgroundColor: canContinue ? tokens.colors.primary : tokens.colors.surfaceVariant,
|
|
68
|
-
opacity: canContinue ? 1 : 0.5,
|
|
69
|
-
},
|
|
70
|
-
]}
|
|
71
|
-
>
|
|
72
|
-
<AtomicText
|
|
73
|
-
type="bodyMedium"
|
|
74
|
-
style={[
|
|
75
|
-
styles.continueText,
|
|
76
|
-
{ color: canContinue ? tokens.colors.onPrimary : tokens.colors.textSecondary },
|
|
77
|
-
]}
|
|
78
|
-
>
|
|
79
|
-
{translations.continueButton}
|
|
80
|
-
</AtomicText>
|
|
81
|
-
<AtomicIcon
|
|
82
|
-
name="arrow-forward"
|
|
83
|
-
size="sm"
|
|
84
|
-
color={canContinue ? "onPrimary" : "textSecondary"}
|
|
85
|
-
/>
|
|
86
|
-
</TouchableOpacity>
|
|
64
|
+
/>
|
|
87
65
|
}
|
|
88
66
|
/>
|
|
89
67
|
<ScreenLayout
|
|
@@ -179,15 +157,4 @@ const createStyles = (tokens: DesignTokens) =>
|
|
|
179
157
|
exampleButton: {
|
|
180
158
|
marginBottom: tokens.spacing.xs,
|
|
181
159
|
},
|
|
182
|
-
continueButton: {
|
|
183
|
-
flexDirection: "row",
|
|
184
|
-
alignItems: "center",
|
|
185
|
-
paddingHorizontal: tokens.spacing.md,
|
|
186
|
-
paddingVertical: tokens.spacing.xs,
|
|
187
|
-
borderRadius: tokens.borders.radius.full,
|
|
188
|
-
},
|
|
189
|
-
continueText: {
|
|
190
|
-
fontWeight: "800",
|
|
191
|
-
marginRight: 4,
|
|
192
|
-
},
|
|
193
160
|
});
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from "react";
|
|
7
|
-
import { View, StyleSheet
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
10
11
|
useAppDesignTokens,
|
|
11
12
|
ScreenLayout,
|
|
12
13
|
type DesignTokens,
|
|
13
14
|
HeroSection,
|
|
14
|
-
AtomicIcon,
|
|
15
15
|
NavigationHeader,
|
|
16
16
|
} from "@umituz/react-native-design-system";
|
|
17
|
+
import { ContinueButton } from "#presentation/components/buttons";
|
|
17
18
|
import type { ScenarioData } from "../../domain/scenario.types";
|
|
18
19
|
|
|
19
20
|
export interface ScenarioPreviewTranslations {
|
|
@@ -45,30 +46,11 @@ export const ScenarioPreviewScreen: React.FC<ScenarioPreviewScreenProps> = ({
|
|
|
45
46
|
title=""
|
|
46
47
|
onBackPress={onBack}
|
|
47
48
|
rightElement={
|
|
48
|
-
<
|
|
49
|
+
<ContinueButton
|
|
50
|
+
label={translations.continueButton}
|
|
51
|
+
canContinue={true}
|
|
49
52
|
onPress={onContinue}
|
|
50
|
-
|
|
51
|
-
style={{
|
|
52
|
-
flexDirection: "row",
|
|
53
|
-
alignItems: "center",
|
|
54
|
-
backgroundColor: tokens.colors.primary,
|
|
55
|
-
paddingHorizontal: tokens.spacing.md,
|
|
56
|
-
paddingVertical: tokens.spacing.xs,
|
|
57
|
-
borderRadius: tokens.borders.radius.full,
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
<AtomicText
|
|
61
|
-
type="bodyMedium"
|
|
62
|
-
style={{
|
|
63
|
-
fontWeight: "800",
|
|
64
|
-
color: tokens.colors.onPrimary,
|
|
65
|
-
marginRight: 4,
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
{translations.continueButton}
|
|
69
|
-
</AtomicText>
|
|
70
|
-
<AtomicIcon name="arrow-forward" size="sm" color="onPrimary" />
|
|
71
|
-
</TouchableOpacity>
|
|
53
|
+
/>
|
|
72
54
|
}
|
|
73
55
|
/>
|
|
74
56
|
<ScreenLayout
|
|
@@ -3,26 +3,19 @@
|
|
|
3
3
|
* Step-based wizard flow for image-to-video generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useMemo
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { GenericWizardFlow } from "../../../../domains/generation/wizard/presentation/components";
|
|
10
10
|
import { IMAGE_TO_VIDEO_WIZARD_CONFIG } from "../../../../domains/generation/wizard/configs";
|
|
11
11
|
import { useAIFeatureGate } from "../../../../domains/access-control";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
createDefaultAlerts,
|
|
14
|
+
createScenarioData,
|
|
15
|
+
useWizardFlowHandlers,
|
|
16
|
+
AutoSkipPreview,
|
|
17
|
+
} from "../../../shared";
|
|
13
18
|
import type { BaseWizardFlowProps } from "../../../../domains/generation/wizard/presentation/components/WizardFlow.types";
|
|
14
|
-
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
15
|
-
|
|
16
|
-
const AutoSkipPreview: React.FC<{ onContinue: () => void }> = ({ onContinue }) => {
|
|
17
|
-
const hasContinued = useRef(false);
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!hasContinued.current) {
|
|
20
|
-
hasContinued.current = true;
|
|
21
|
-
onContinue();
|
|
22
|
-
}
|
|
23
|
-
}, [onContinue]);
|
|
24
|
-
return null;
|
|
25
|
-
};
|
|
26
19
|
|
|
27
20
|
export type ImageToVideoWizardFlowProps = BaseWizardFlowProps;
|
|
28
21
|
|
|
@@ -41,46 +34,33 @@ export const ImageToVideoWizardFlow: React.FC<ImageToVideoWizardFlowProps> = (pr
|
|
|
41
34
|
|
|
42
35
|
const tokens = useAppDesignTokens();
|
|
43
36
|
|
|
44
|
-
// Centralized access control - handles offline, auth, credits, paywall
|
|
45
37
|
const { requireFeature } = useAIFeatureGate({
|
|
46
38
|
creditCost,
|
|
47
39
|
onNetworkError,
|
|
48
40
|
});
|
|
49
41
|
|
|
50
|
-
const scenario
|
|
51
|
-
() =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
networkError: t("common.errors.network"),
|
|
64
|
-
policyViolation: t("common.errors.policy"),
|
|
65
|
-
saveFailed: t("common.errors.saveFailed"),
|
|
66
|
-
creditFailed: t("common.errors.creditFailed"),
|
|
67
|
-
unknown: t("common.errors.unknown"),
|
|
68
|
-
}),
|
|
69
|
-
[t],
|
|
42
|
+
const scenario = useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
createScenarioData(
|
|
45
|
+
{
|
|
46
|
+
id: "image-to-video",
|
|
47
|
+
outputType: "video",
|
|
48
|
+
inputType: "single",
|
|
49
|
+
model,
|
|
50
|
+
titleKey: "image2video.title",
|
|
51
|
+
},
|
|
52
|
+
t
|
|
53
|
+
),
|
|
54
|
+
[model, t]
|
|
70
55
|
);
|
|
71
56
|
|
|
72
|
-
const
|
|
73
|
-
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
74
|
-
// Use centralized access control - checks offline, auth, credits
|
|
75
|
-
requireFeature(proceed);
|
|
76
|
-
},
|
|
77
|
-
[requireFeature],
|
|
78
|
-
);
|
|
57
|
+
const defaultAlerts = useMemo(() => createDefaultAlerts(t), [t]);
|
|
79
58
|
|
|
80
|
-
const handleGenerationComplete =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
59
|
+
const { handleGenerationStart, handleGenerationComplete } = useWizardFlowHandlers({
|
|
60
|
+
requireFeature,
|
|
61
|
+
onGenerationComplete,
|
|
62
|
+
onBack,
|
|
63
|
+
});
|
|
84
64
|
|
|
85
65
|
return (
|
|
86
66
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoSkipPreview Component
|
|
3
|
+
* Automatically continues to the next step on mount
|
|
4
|
+
* Used in wizard flows to skip the preview step
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from "react";
|
|
8
|
+
|
|
9
|
+
export interface AutoSkipPreviewProps {
|
|
10
|
+
readonly onContinue: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const AutoSkipPreview: React.FC<AutoSkipPreviewProps> = ({ onContinue }) => {
|
|
14
|
+
const hasContinued = useRef(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!hasContinued.current) {
|
|
18
|
+
hasContinued.current = true;
|
|
19
|
+
onContinue();
|
|
20
|
+
}
|
|
21
|
+
}, [onContinue]);
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Flow Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createDefaultAlerts,
|
|
7
|
+
createScenarioData,
|
|
8
|
+
useWizardFlowHandlers,
|
|
9
|
+
type WizardFlowConfig,
|
|
10
|
+
type UseWizardFlowHandlersOptions,
|
|
11
|
+
} from "./wizard-flow.utils";
|
|
12
|
+
|
|
13
|
+
export { AutoSkipPreview } from "../components/AutoSkipPreview";
|
|
14
|
+
export type { AutoSkipPreviewProps } from "../components/AutoSkipPreview";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Flow Utilities
|
|
3
|
+
* Common utilities for wizard flow screens
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import type { WizardScenarioData } from "../../../../domains/generation/wizard/presentation/hooks/useWizardGeneration";
|
|
8
|
+
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates default alert messages for wizard flows
|
|
12
|
+
*/
|
|
13
|
+
export function createDefaultAlerts(t: (key: string) => string): AlertMessages {
|
|
14
|
+
return {
|
|
15
|
+
networkError: t("common.errors.network"),
|
|
16
|
+
policyViolation: t("common.errors.policy"),
|
|
17
|
+
saveFailed: t("common.errors.saveFailed"),
|
|
18
|
+
creditFailed: t("common.errors.creditFailed"),
|
|
19
|
+
unknown: t("common.errors.unknown"),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for wizard flow utilities
|
|
25
|
+
*/
|
|
26
|
+
export interface WizardFlowConfig {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly outputType: "image" | "video";
|
|
29
|
+
readonly inputType: "text" | "single" | "dual";
|
|
30
|
+
readonly model: string;
|
|
31
|
+
readonly titleKey: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates wizard scenario data from config
|
|
36
|
+
*/
|
|
37
|
+
export function createScenarioData(
|
|
38
|
+
config: WizardFlowConfig,
|
|
39
|
+
t: (key: string) => string
|
|
40
|
+
): WizardScenarioData {
|
|
41
|
+
return {
|
|
42
|
+
id: config.id,
|
|
43
|
+
outputType: config.outputType,
|
|
44
|
+
inputType: config.inputType,
|
|
45
|
+
model: config.model,
|
|
46
|
+
title: t(config.titleKey),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hook for wizard flow handlers
|
|
52
|
+
*/
|
|
53
|
+
export interface UseWizardFlowHandlersOptions {
|
|
54
|
+
readonly requireFeature: (proceed: () => void) => void;
|
|
55
|
+
readonly onGenerationComplete?: () => void;
|
|
56
|
+
readonly onBack: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useWizardFlowHandlers({
|
|
60
|
+
requireFeature,
|
|
61
|
+
onGenerationComplete,
|
|
62
|
+
onBack,
|
|
63
|
+
}: UseWizardFlowHandlersOptions) {
|
|
64
|
+
const handleGenerationStart = useCallback(
|
|
65
|
+
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
66
|
+
requireFeature(proceed);
|
|
67
|
+
},
|
|
68
|
+
[requireFeature]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const handleGenerationComplete = useCallback(() => {
|
|
72
|
+
onGenerationComplete?.();
|
|
73
|
+
onBack();
|
|
74
|
+
}, [onGenerationComplete, onBack]);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
handleGenerationStart,
|
|
78
|
+
handleGenerationComplete,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Wizard flow type definitions
|
|
84
|
+
*/
|
|
@@ -3,26 +3,19 @@
|
|
|
3
3
|
* Step-based wizard flow for text-to-image generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useMemo
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { GenericWizardFlow } from "../../../../domains/generation/wizard/presentation/components";
|
|
10
10
|
import { TEXT_TO_IMAGE_WIZARD_CONFIG } from "../../../../domains/generation/wizard/configs";
|
|
11
11
|
import { useAIFeatureGate } from "../../../../domains/access-control";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
createDefaultAlerts,
|
|
14
|
+
createScenarioData,
|
|
15
|
+
useWizardFlowHandlers,
|
|
16
|
+
AutoSkipPreview,
|
|
17
|
+
} from "../../../shared";
|
|
13
18
|
import type { BaseWizardFlowProps } from "../../../../domains/generation/wizard/presentation/components/WizardFlow.types";
|
|
14
|
-
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
15
|
-
|
|
16
|
-
const AutoSkipPreview: React.FC<{ onContinue: () => void }> = ({ onContinue }) => {
|
|
17
|
-
const hasContinued = useRef(false);
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!hasContinued.current) {
|
|
20
|
-
hasContinued.current = true;
|
|
21
|
-
onContinue();
|
|
22
|
-
}
|
|
23
|
-
}, [onContinue]);
|
|
24
|
-
return null;
|
|
25
|
-
};
|
|
26
19
|
|
|
27
20
|
export type TextToImageWizardFlowProps = BaseWizardFlowProps;
|
|
28
21
|
|
|
@@ -41,46 +34,33 @@ export const TextToImageWizardFlow: React.FC<TextToImageWizardFlowProps> = (prop
|
|
|
41
34
|
|
|
42
35
|
const tokens = useAppDesignTokens();
|
|
43
36
|
|
|
44
|
-
// Centralized access control - handles offline, auth, credits, paywall
|
|
45
37
|
const { requireFeature } = useAIFeatureGate({
|
|
46
38
|
creditCost,
|
|
47
39
|
onNetworkError,
|
|
48
40
|
});
|
|
49
41
|
|
|
50
|
-
const scenario
|
|
51
|
-
() =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
networkError: t("common.errors.network"),
|
|
64
|
-
policyViolation: t("common.errors.policy"),
|
|
65
|
-
saveFailed: t("common.errors.saveFailed"),
|
|
66
|
-
creditFailed: t("common.errors.creditFailed"),
|
|
67
|
-
unknown: t("common.errors.unknown"),
|
|
68
|
-
}),
|
|
69
|
-
[t],
|
|
42
|
+
const scenario = useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
createScenarioData(
|
|
45
|
+
{
|
|
46
|
+
id: "text-to-image",
|
|
47
|
+
outputType: "image",
|
|
48
|
+
inputType: "text",
|
|
49
|
+
model,
|
|
50
|
+
titleKey: "text2image.title",
|
|
51
|
+
},
|
|
52
|
+
t
|
|
53
|
+
),
|
|
54
|
+
[model, t]
|
|
70
55
|
);
|
|
71
56
|
|
|
72
|
-
const
|
|
73
|
-
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
74
|
-
// Use centralized access control - checks offline, auth, credits
|
|
75
|
-
requireFeature(proceed);
|
|
76
|
-
},
|
|
77
|
-
[requireFeature],
|
|
78
|
-
);
|
|
57
|
+
const defaultAlerts = useMemo(() => createDefaultAlerts(t), [t]);
|
|
79
58
|
|
|
80
|
-
const handleGenerationComplete =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
59
|
+
const { handleGenerationStart, handleGenerationComplete } = useWizardFlowHandlers({
|
|
60
|
+
requireFeature,
|
|
61
|
+
onGenerationComplete,
|
|
62
|
+
onBack,
|
|
63
|
+
});
|
|
84
64
|
|
|
85
65
|
return (
|
|
86
66
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|