@umituz/react-native-ai-generation-content 1.27.24 → 1.27.26
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/generation/wizard/configs/image-to-video.config.ts +5 -2
- package/src/domains/generation/wizard/configs/text-to-image.config.ts +3 -3
- package/src/domains/generation/wizard/configs/text-to-video.config.ts +4 -1
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +13 -11
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +5 -6
- package/src/domains/generation/wizard/infrastructure/utils/index.ts +8 -0
- package/src/domains/generation/wizard/infrastructure/utils/wizard-data-extractors.ts +198 -0
- package/src/domains/generation/wizard/presentation/components/WizardHeader.tsx +96 -0
- package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +67 -18
- package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.types.ts +26 -0
- package/src/domains/generation/wizard/presentation/screens/TextInputScreen.styles.ts +0 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.26",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -12,7 +12,8 @@ export const IMAGE_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
|
12
12
|
{
|
|
13
13
|
id: "photo_1",
|
|
14
14
|
type: "photo_upload",
|
|
15
|
-
|
|
15
|
+
titleKey: "imageToVideo.selectPhoto",
|
|
16
|
+
subtitleKey: "imageToVideo.selectPhotoHint",
|
|
16
17
|
showFaceDetection: false,
|
|
17
18
|
showPhotoTips: true,
|
|
18
19
|
required: true,
|
|
@@ -20,13 +21,15 @@ export const IMAGE_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
|
20
21
|
{
|
|
21
22
|
id: "motion_prompt",
|
|
22
23
|
type: "text_input",
|
|
23
|
-
|
|
24
|
+
titleKey: "imageToVideo.motionPrompt",
|
|
24
25
|
placeholderKey: "imageToVideo.motionPromptPlaceholder",
|
|
26
|
+
required: false,
|
|
25
27
|
maxLength: 200,
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
30
|
id: "duration",
|
|
29
31
|
type: "selection",
|
|
32
|
+
titleKey: "generation.duration.title",
|
|
30
33
|
selectionType: "duration",
|
|
31
34
|
options: [
|
|
32
35
|
{ id: "5s", label: "5 seconds", value: 5 },
|
|
@@ -9,9 +9,9 @@ const promptStep: TextInputStepConfig = {
|
|
|
9
9
|
id: "prompt",
|
|
10
10
|
type: "text_input",
|
|
11
11
|
required: true,
|
|
12
|
-
titleKey: "text2image.
|
|
13
|
-
subtitleKey: "text2image.
|
|
14
|
-
placeholderKey: "text2image.
|
|
12
|
+
titleKey: "text2image.hero.title",
|
|
13
|
+
subtitleKey: "text2image.hero.subtitle",
|
|
14
|
+
placeholderKey: "text2image.prompt.placeholder",
|
|
15
15
|
minLength: 3,
|
|
16
16
|
maxLength: 1000,
|
|
17
17
|
multiline: true,
|
|
@@ -13,7 +13,9 @@ export const TEXT_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
|
13
13
|
id: "prompt",
|
|
14
14
|
type: "text_input",
|
|
15
15
|
required: true,
|
|
16
|
-
|
|
16
|
+
titleKey: "textToVideo.hero.title",
|
|
17
|
+
subtitleKey: "textToVideo.hero.subtitle",
|
|
18
|
+
placeholderKey: "textToVideo.prompt.placeholder",
|
|
17
19
|
minLength: 3,
|
|
18
20
|
maxLength: 500,
|
|
19
21
|
multiline: true,
|
|
@@ -21,6 +23,7 @@ export const TEXT_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
|
21
23
|
{
|
|
22
24
|
id: "duration",
|
|
23
25
|
type: "selection",
|
|
26
|
+
titleKey: "generation.duration.title",
|
|
24
27
|
selectionType: "duration",
|
|
25
28
|
options: [
|
|
26
29
|
{ id: "5s", label: "5 seconds", value: 5 },
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./wizard-strategy.constants";
|
|
17
17
|
import { buildFacePreservationPrompt } from "../../../../prompts/infrastructure/builders/face-preservation-builder";
|
|
18
18
|
import { buildInteractionStylePrompt, type InteractionStyle } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
|
|
19
|
+
import { extractPrompt, extractSelection } from "../utils";
|
|
19
20
|
|
|
20
21
|
declare const __DEV__: boolean;
|
|
21
22
|
|
|
@@ -165,9 +166,8 @@ export async function buildImageInput(
|
|
|
165
166
|
): Promise<ImageGenerationInput | null> {
|
|
166
167
|
const photos = await extractPhotosFromWizardData(wizardData);
|
|
167
168
|
|
|
168
|
-
//
|
|
169
|
-
const
|
|
170
|
-
const prompt = userPrompt?.trim() || scenario.aiPrompt?.trim();
|
|
169
|
+
// Extract prompt using type-safe extractor with fallback
|
|
170
|
+
const prompt = extractPrompt(wizardData, scenario.aiPrompt);
|
|
171
171
|
|
|
172
172
|
if (!prompt) {
|
|
173
173
|
throw new Error("Prompt is required for image generation");
|
|
@@ -178,18 +178,19 @@ export async function buildImageInput(
|
|
|
178
178
|
if (photos.length > 0) {
|
|
179
179
|
const styleEnhancements: string[] = [];
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
// Extract selections using type-safe extractor
|
|
182
|
+
const romanticMoods = extractSelection(wizardData.selection_romantic_mood);
|
|
183
|
+
if (Array.isArray(romanticMoods) && romanticMoods.length > 0) {
|
|
183
184
|
styleEnhancements.push(`Mood: ${romanticMoods.join(", ")}`);
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
const artStyle = wizardData.selection_art_style
|
|
187
|
-
if (artStyle && artStyle !== DEFAULT_STYLE_VALUE) {
|
|
187
|
+
const artStyle = extractSelection(wizardData.selection_art_style);
|
|
188
|
+
if (typeof artStyle === "string" && artStyle !== DEFAULT_STYLE_VALUE) {
|
|
188
189
|
styleEnhancements.push(`Art style: ${artStyle}`);
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
const artist = wizardData.selection_artist_style
|
|
192
|
-
if (artist && artist !== DEFAULT_STYLE_VALUE) {
|
|
192
|
+
const artist = extractSelection(wizardData.selection_artist_style);
|
|
193
|
+
if (typeof artist === "string" && artist !== DEFAULT_STYLE_VALUE) {
|
|
193
194
|
styleEnhancements.push(`Artist style: ${artist}`);
|
|
194
195
|
}
|
|
195
196
|
|
|
@@ -198,8 +199,9 @@ export async function buildImageInput(
|
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
//
|
|
202
|
-
const
|
|
202
|
+
// Extract style using type-safe extractor (for text-to-image)
|
|
203
|
+
const styleValue = extractSelection(wizardData.style);
|
|
204
|
+
const style = typeof styleValue === "string" ? styleValue : undefined;
|
|
203
205
|
|
|
204
206
|
// Get interaction style from scenario (default: romantic for couple apps)
|
|
205
207
|
const interactionStyle = (scenario.interactionStyle as InteractionStyle) ?? "romantic";
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { createCreationsRepository } from "../../../../creations/infrastructure/
|
|
|
10
10
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
11
11
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
12
12
|
import { PHOTO_KEY_PREFIX, VIDEO_FEATURE_PATTERNS } from "./wizard-strategy.constants";
|
|
13
|
+
import { extractPrompt, extractDuration } from "../utils";
|
|
13
14
|
|
|
14
15
|
// ============================================================================
|
|
15
16
|
// Types
|
|
@@ -86,17 +87,15 @@ export async function buildVideoInput(
|
|
|
86
87
|
): Promise<VideoGenerationInput | null> {
|
|
87
88
|
const photos = await extractPhotosFromWizardData(wizardData);
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
const motionPrompt = wizardData.motion_prompt as string | undefined;
|
|
92
|
-
const prompt = userPrompt?.trim() || motionPrompt?.trim() || scenario.aiPrompt?.trim();
|
|
90
|
+
// Extract prompt using type-safe extractor with fallback
|
|
91
|
+
const prompt = extractPrompt(wizardData, scenario.aiPrompt);
|
|
93
92
|
|
|
94
93
|
if (!prompt) {
|
|
95
94
|
throw new Error("Prompt is required for video generation");
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
//
|
|
99
|
-
const duration = wizardData
|
|
97
|
+
// Extract duration using type-safe extractor (default: 5 seconds)
|
|
98
|
+
const duration = extractDuration(wizardData, 5);
|
|
100
99
|
|
|
101
100
|
return {
|
|
102
101
|
sourceImageBase64: photos[0],
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Data Extractors
|
|
3
|
+
* Type-safe utilities for extracting values from wizard data
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Type Guards + Normalizers
|
|
6
|
+
* @see https://www.typescriptlang.org/docs/handbook/2/narrowing.html
|
|
7
|
+
* @see https://betterstack.com/community/guides/scaling-nodejs/typescript-type-guards/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type Guards
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if value is a non-null object
|
|
16
|
+
*/
|
|
17
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if object has a specific property
|
|
23
|
+
* Uses 'in' operator for safe property checking
|
|
24
|
+
*/
|
|
25
|
+
function hasProperty<K extends string>(
|
|
26
|
+
obj: Record<string, unknown>,
|
|
27
|
+
key: K,
|
|
28
|
+
): obj is Record<K, unknown> {
|
|
29
|
+
return key in obj;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// String Extractors
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extracts string from various input formats:
|
|
38
|
+
* - Direct string: "my text"
|
|
39
|
+
* - Object with text field: { text: "my text" }
|
|
40
|
+
* - Object with uri field (fallback): { uri: "my text" }
|
|
41
|
+
*
|
|
42
|
+
* @param value - The value to extract string from
|
|
43
|
+
* @returns The extracted string or undefined
|
|
44
|
+
*/
|
|
45
|
+
export function extractString(value: unknown): string | undefined {
|
|
46
|
+
// Direct string
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Object with text or uri field
|
|
52
|
+
if (isObject(value)) {
|
|
53
|
+
if (hasProperty(value, "text") && typeof value.text === "string") {
|
|
54
|
+
return value.text;
|
|
55
|
+
}
|
|
56
|
+
if (hasProperty(value, "uri") && typeof value.uri === "string") {
|
|
57
|
+
return value.uri;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extracts and trims string, returning undefined if empty
|
|
66
|
+
*/
|
|
67
|
+
export function extractTrimmedString(value: unknown): string | undefined {
|
|
68
|
+
const str = extractString(value);
|
|
69
|
+
const trimmed = str?.trim();
|
|
70
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Number Extractors
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extracts number from various input formats:
|
|
79
|
+
* - Direct number: 5
|
|
80
|
+
* - Object with value field: { value: 5 }
|
|
81
|
+
* - Object with selection field: { selection: 5 }
|
|
82
|
+
*
|
|
83
|
+
* @param value - The value to extract number from
|
|
84
|
+
* @returns The extracted number or undefined
|
|
85
|
+
*/
|
|
86
|
+
export function extractNumber(value: unknown): number | undefined {
|
|
87
|
+
// Direct number
|
|
88
|
+
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Object with value or selection field
|
|
93
|
+
if (isObject(value)) {
|
|
94
|
+
if (hasProperty(value, "value") && typeof value.value === "number") {
|
|
95
|
+
return value.value;
|
|
96
|
+
}
|
|
97
|
+
if (hasProperty(value, "selection") && typeof value.selection === "number") {
|
|
98
|
+
return value.selection;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Selection Extractors
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extracts selection value (string or string array) from wizard data
|
|
111
|
+
*
|
|
112
|
+
* @param value - The value to extract selection from
|
|
113
|
+
* @returns The extracted selection or undefined
|
|
114
|
+
*/
|
|
115
|
+
export function extractSelection(value: unknown): string | string[] | undefined {
|
|
116
|
+
// Direct string
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Direct string array
|
|
122
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
123
|
+
return value as string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Object with selection field
|
|
127
|
+
if (isObject(value)) {
|
|
128
|
+
if (hasProperty(value, "selection")) {
|
|
129
|
+
const selection = value.selection;
|
|
130
|
+
if (typeof selection === "string") {
|
|
131
|
+
return selection;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(selection) && selection.every((v) => typeof v === "string")) {
|
|
134
|
+
return selection as string[];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Prompt Extractor (Specialized)
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extracts prompt from wizard data with fallback chain
|
|
148
|
+
* Checks multiple keys in order: prompt, motion_prompt, text
|
|
149
|
+
*
|
|
150
|
+
* @param wizardData - The wizard data object
|
|
151
|
+
* @param fallback - Optional fallback value (e.g., scenario.aiPrompt)
|
|
152
|
+
* @returns The extracted and trimmed prompt or undefined
|
|
153
|
+
*/
|
|
154
|
+
export function extractPrompt(
|
|
155
|
+
wizardData: Record<string, unknown>,
|
|
156
|
+
fallback?: string,
|
|
157
|
+
): string | undefined {
|
|
158
|
+
// Priority chain for prompt keys
|
|
159
|
+
const promptKeys = ["prompt", "motion_prompt", "text", "userPrompt"];
|
|
160
|
+
|
|
161
|
+
for (const key of promptKeys) {
|
|
162
|
+
if (key in wizardData) {
|
|
163
|
+
const extracted = extractTrimmedString(wizardData[key]);
|
|
164
|
+
if (extracted) {
|
|
165
|
+
return extracted;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Use fallback if provided
|
|
171
|
+
return fallback?.trim() || undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// Duration Extractor (Specialized)
|
|
176
|
+
// ============================================================================
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Extracts duration from wizard data
|
|
180
|
+
* Handles both direct number and object with value field
|
|
181
|
+
*
|
|
182
|
+
* @param wizardData - The wizard data object
|
|
183
|
+
* @param defaultValue - Default duration if not found
|
|
184
|
+
* @returns The extracted duration in seconds
|
|
185
|
+
*/
|
|
186
|
+
export function extractDuration(
|
|
187
|
+
wizardData: Record<string, unknown>,
|
|
188
|
+
defaultValue = 5,
|
|
189
|
+
): number {
|
|
190
|
+
const durationData = wizardData.duration;
|
|
191
|
+
|
|
192
|
+
const extracted = extractNumber(durationData);
|
|
193
|
+
if (extracted !== undefined && extracted > 0) {
|
|
194
|
+
return extracted;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return defaultValue;
|
|
198
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WizardHeader Component
|
|
3
|
+
* Header with back button on left, action button on right
|
|
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
|
+
|
|
14
|
+
export interface WizardHeaderProps {
|
|
15
|
+
readonly onBack: () => void;
|
|
16
|
+
readonly onAction?: () => void;
|
|
17
|
+
readonly backLabel?: string;
|
|
18
|
+
readonly actionLabel?: string;
|
|
19
|
+
readonly isActionDisabled?: boolean;
|
|
20
|
+
readonly showAction?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const WizardHeader: React.FC<WizardHeaderProps> = ({
|
|
24
|
+
onBack,
|
|
25
|
+
onAction,
|
|
26
|
+
backLabel,
|
|
27
|
+
actionLabel,
|
|
28
|
+
isActionDisabled = false,
|
|
29
|
+
showAction = true,
|
|
30
|
+
}) => {
|
|
31
|
+
const tokens = useAppDesignTokens();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={[styles.container, { paddingHorizontal: tokens.spacing.md }]}>
|
|
35
|
+
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
36
|
+
<AtomicIcon name="chevron-left" size="md" color="textPrimary" />
|
|
37
|
+
{backLabel ? (
|
|
38
|
+
<AtomicText type="bodyMedium" color="textPrimary">
|
|
39
|
+
{backLabel}
|
|
40
|
+
</AtomicText>
|
|
41
|
+
) : null}
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
|
|
44
|
+
{showAction && actionLabel ? (
|
|
45
|
+
<TouchableOpacity
|
|
46
|
+
onPress={onAction}
|
|
47
|
+
disabled={isActionDisabled}
|
|
48
|
+
style={[
|
|
49
|
+
styles.actionButton,
|
|
50
|
+
{
|
|
51
|
+
backgroundColor: isActionDisabled
|
|
52
|
+
? tokens.colors.surfaceSecondary
|
|
53
|
+
: tokens.colors.primary,
|
|
54
|
+
borderRadius: tokens.radius.md,
|
|
55
|
+
},
|
|
56
|
+
]}
|
|
57
|
+
>
|
|
58
|
+
<AtomicText
|
|
59
|
+
type="labelLarge"
|
|
60
|
+
style={{
|
|
61
|
+
color: isActionDisabled
|
|
62
|
+
? tokens.colors.textSecondary
|
|
63
|
+
: tokens.colors.textInverse,
|
|
64
|
+
fontWeight: "600",
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{actionLabel}
|
|
68
|
+
</AtomicText>
|
|
69
|
+
</TouchableOpacity>
|
|
70
|
+
) : (
|
|
71
|
+
<View style={styles.placeholder} />
|
|
72
|
+
)}
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const styles = StyleSheet.create({
|
|
78
|
+
container: {
|
|
79
|
+
flexDirection: "row",
|
|
80
|
+
justifyContent: "space-between",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
paddingVertical: 12,
|
|
83
|
+
},
|
|
84
|
+
backButton: {
|
|
85
|
+
flexDirection: "row",
|
|
86
|
+
alignItems: "center",
|
|
87
|
+
gap: 4,
|
|
88
|
+
},
|
|
89
|
+
actionButton: {
|
|
90
|
+
paddingHorizontal: 16,
|
|
91
|
+
paddingVertical: 8,
|
|
92
|
+
},
|
|
93
|
+
placeholder: {
|
|
94
|
+
width: 80,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TextInputScreen
|
|
3
3
|
* Generic text input step for wizard flows
|
|
4
|
+
* Header: Back on left, Continue on right
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import React, { useState, useCallback } from "react";
|
|
7
|
-
import { View, ScrollView, TextInput } from "react-native";
|
|
8
|
+
import { View, ScrollView, TextInput, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
9
|
import {
|
|
9
10
|
AtomicText,
|
|
10
11
|
AtomicButton,
|
|
11
12
|
AtomicIcon,
|
|
12
13
|
useAppDesignTokens,
|
|
13
14
|
} from "@umituz/react-native-design-system";
|
|
14
|
-
import { styles } from "./TextInputScreen.styles";
|
|
15
15
|
import type { TextInputScreenProps } from "./TextInputScreen.types";
|
|
16
16
|
|
|
17
17
|
export type {
|
|
@@ -48,17 +48,33 @@ export const TextInputScreen: React.FC<TextInputScreenProps> = ({
|
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
50
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
51
|
+
{/* Header with Back on left, Continue on right */}
|
|
51
52
|
<View style={[styles.header, { paddingHorizontal: tokens.spacing.md }]}>
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
54
|
+
<AtomicIcon name="chevron-left" size="md" color="textPrimary" />
|
|
55
|
+
</TouchableOpacity>
|
|
56
|
+
|
|
57
|
+
<TouchableOpacity
|
|
58
|
+
onPress={handleContinue}
|
|
59
|
+
disabled={!canContinue}
|
|
60
|
+
style={[
|
|
61
|
+
styles.continueButton,
|
|
62
|
+
{
|
|
63
|
+
backgroundColor: canContinue ? tokens.colors.primary : tokens.colors.surfaceSecondary,
|
|
64
|
+
borderRadius: tokens.radius.md,
|
|
65
|
+
},
|
|
66
|
+
]}
|
|
67
|
+
>
|
|
68
|
+
<AtomicText
|
|
69
|
+
type="labelLarge"
|
|
70
|
+
style={{
|
|
71
|
+
color: canContinue ? tokens.colors.textInverse : tokens.colors.textSecondary,
|
|
72
|
+
fontWeight: "600",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{translations.continueButton}
|
|
76
|
+
</AtomicText>
|
|
77
|
+
</TouchableOpacity>
|
|
62
78
|
</View>
|
|
63
79
|
|
|
64
80
|
<ScrollView
|
|
@@ -120,12 +136,45 @@ export const TextInputScreen: React.FC<TextInputScreenProps> = ({
|
|
|
120
136
|
</View>
|
|
121
137
|
) : null}
|
|
122
138
|
</ScrollView>
|
|
123
|
-
|
|
124
|
-
<View style={[styles.footer, { padding: tokens.spacing.md }]}>
|
|
125
|
-
<AtomicButton variant="primary" size="lg" onPress={handleContinue} disabled={!canContinue} style={styles.continueButton}>
|
|
126
|
-
{translations.continueButton}
|
|
127
|
-
</AtomicButton>
|
|
128
|
-
</View>
|
|
129
139
|
</View>
|
|
130
140
|
);
|
|
131
141
|
};
|
|
142
|
+
|
|
143
|
+
const styles = StyleSheet.create({
|
|
144
|
+
container: {
|
|
145
|
+
flex: 1,
|
|
146
|
+
},
|
|
147
|
+
header: {
|
|
148
|
+
flexDirection: "row",
|
|
149
|
+
justifyContent: "space-between",
|
|
150
|
+
alignItems: "center",
|
|
151
|
+
paddingVertical: 12,
|
|
152
|
+
},
|
|
153
|
+
backButton: {
|
|
154
|
+
flexDirection: "row",
|
|
155
|
+
alignItems: "center",
|
|
156
|
+
gap: 4,
|
|
157
|
+
},
|
|
158
|
+
continueButton: {
|
|
159
|
+
paddingHorizontal: 16,
|
|
160
|
+
paddingVertical: 8,
|
|
161
|
+
},
|
|
162
|
+
scrollView: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
},
|
|
165
|
+
title: {
|
|
166
|
+
marginBottom: 8,
|
|
167
|
+
},
|
|
168
|
+
inputContainer: {
|
|
169
|
+
borderWidth: 1,
|
|
170
|
+
padding: 12,
|
|
171
|
+
},
|
|
172
|
+
textInput: {
|
|
173
|
+
fontSize: 16,
|
|
174
|
+
lineHeight: 22,
|
|
175
|
+
},
|
|
176
|
+
charCount: {
|
|
177
|
+
textAlign: "right",
|
|
178
|
+
marginTop: 8,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextToImageWizardFlow Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
6
|
+
|
|
7
|
+
export interface TextToImageWizardFlowProps {
|
|
8
|
+
readonly model: string;
|
|
9
|
+
readonly userId?: string;
|
|
10
|
+
readonly isAuthenticated: boolean;
|
|
11
|
+
readonly hasPremium: boolean;
|
|
12
|
+
readonly creditBalance: number;
|
|
13
|
+
readonly isCreditsLoaded: boolean;
|
|
14
|
+
readonly onShowAuthModal: (callback: () => void) => void;
|
|
15
|
+
readonly onShowPaywall: () => void;
|
|
16
|
+
readonly onGenerationComplete?: () => void;
|
|
17
|
+
readonly onGenerationError?: (error: string) => void;
|
|
18
|
+
readonly onBack: () => void;
|
|
19
|
+
readonly t: (key: string) => string;
|
|
20
|
+
readonly alertMessages?: AlertMessages;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TextToImageFormState {
|
|
24
|
+
prompt: string;
|
|
25
|
+
selectedStyle: string;
|
|
26
|
+
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TextInputScreen Styles
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { StyleSheet } from "react-native";
|
|
6
|
-
|
|
7
|
-
export const styles = StyleSheet.create({
|
|
8
|
-
container: {
|
|
9
|
-
flex: 1,
|
|
10
|
-
},
|
|
11
|
-
header: {
|
|
12
|
-
flexDirection: "row",
|
|
13
|
-
alignItems: "center",
|
|
14
|
-
paddingVertical: 8,
|
|
15
|
-
},
|
|
16
|
-
backButtonContent: {
|
|
17
|
-
flexDirection: "row",
|
|
18
|
-
alignItems: "center",
|
|
19
|
-
},
|
|
20
|
-
backButtonText: {
|
|
21
|
-
marginLeft: 4,
|
|
22
|
-
},
|
|
23
|
-
scrollView: {
|
|
24
|
-
flex: 1,
|
|
25
|
-
},
|
|
26
|
-
title: {
|
|
27
|
-
marginBottom: 8,
|
|
28
|
-
},
|
|
29
|
-
inputContainer: {
|
|
30
|
-
borderWidth: 1,
|
|
31
|
-
padding: 12,
|
|
32
|
-
},
|
|
33
|
-
textInput: {
|
|
34
|
-
fontSize: 16,
|
|
35
|
-
lineHeight: 24,
|
|
36
|
-
},
|
|
37
|
-
charCount: {
|
|
38
|
-
textAlign: "right",
|
|
39
|
-
marginTop: 4,
|
|
40
|
-
},
|
|
41
|
-
footer: {
|
|
42
|
-
borderTopWidth: 1,
|
|
43
|
-
borderTopColor: "rgba(0,0,0,0.1)",
|
|
44
|
-
},
|
|
45
|
-
continueButton: {
|
|
46
|
-
width: "100%",
|
|
47
|
-
},
|
|
48
|
-
});
|