@umituz/react-native-ai-generation-content 1.26.7 → 1.26.9
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/application/generation-strategy.factory.ts +1 -3
- package/src/domains/generation/infrastructure/executors/executor-factory.ts +1 -1
- package/src/domains/generation/infrastructure/flow/step-builder.ts +2 -11
- package/src/domains/generation/infrastructure/flow/useFlow.ts +1 -1
- package/src/domains/generation/infrastructure/flow/useFlowStore.ts +1 -1
- package/src/domains/generation/presentation/useAIGeneration.hook.ts +9 -3
- package/src/domains/generation/wizard/domain/entities/wizard-config.types.ts +1 -1
- package/src/domains/generation/wizard/index.ts +0 -29
- package/src/domains/generation/wizard/infrastructure/builders/dynamic-step-builder.ts +2 -3
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.factory.ts +13 -15
- package/src/domains/generation/wizard/presentation/hooks/usePhotoUploadState.ts +2 -2
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +2 -2
- package/src/domains/scenarios/configs/wizard-configs.ts +1 -1
- package/src/index.ts +1 -1
- package/src/infrastructure/wrappers/synchronous-generation.wrapper.ts +3 -2
- package/src/presentation/hooks/generation/index.ts +0 -1
- package/src/presentation/hooks/generation/orchestrator.ts +8 -3
- package/src/presentation/hooks/generation/useAIGenerateState.ts +3 -0
- package/src/presentation/hooks/index.ts +0 -1
- package/src/presentation/layouts/types/layout-props.ts +30 -4
- package/src/domains/generation/wizard/infrastructure/renderers/step-renderer.tsx +0 -107
- package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +0 -297
- package/src/domains/generation/wizard/presentation/screens/GeneratingScreen.tsx +0 -123
- package/src/domains/generation/wizard/presentation/screens/GenericPhotoUploadScreen.tsx +0 -222
- package/src/domains/generation/wizard/presentation/steps/PhotoUploadStep.tsx +0 -66
- package/src/domains/generation/wizard/presentation/steps/SelectionStep.tsx +0 -244
- package/src/domains/generation/wizard/presentation/steps/TextInputStep.tsx +0 -199
- package/src/presentation/hooks/generation/useAIFeatureGeneration.ts +0 -180
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Wizard Flow Component
|
|
3
|
-
* ONE wizard to rule them all!
|
|
4
|
-
*
|
|
5
|
-
* Works for:
|
|
6
|
-
* - Couple features (romantic-kiss, ai-hug, etc.)
|
|
7
|
-
* - Face swap
|
|
8
|
-
* - Image-to-video
|
|
9
|
-
* - Text-to-video
|
|
10
|
-
* - ANY future feature!
|
|
11
|
-
*
|
|
12
|
-
* NO feature-specific code here - everything driven by configuration!
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React, { useMemo, useCallback, useEffect, useRef } from "react";
|
|
16
|
-
import { View, StyleSheet } from "react-native";
|
|
17
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
18
|
-
import { useFlow } from "../../../../infrastructure/flow/useFlow";
|
|
19
|
-
import { StepType } from "../../../../domain/entities/flow-config.types";
|
|
20
|
-
import type { StepDefinition } from "../../../../domain/entities/flow-config.types";
|
|
21
|
-
import { renderStep } from "../../infrastructure/renderers/step-renderer";
|
|
22
|
-
import type { WizardFeatureConfig } from "../../domain/entities/wizard-config.types";
|
|
23
|
-
import { buildFlowStepsFromWizard } from "../../infrastructure/builders/dynamic-step-builder";
|
|
24
|
-
import { useWizardGeneration, type WizardScenarioData } from "../hooks/useWizardGeneration";
|
|
25
|
-
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
26
|
-
|
|
27
|
-
export interface GenericWizardFlowProps {
|
|
28
|
-
readonly featureConfig: WizardFeatureConfig;
|
|
29
|
-
readonly scenario?: WizardScenarioData;
|
|
30
|
-
readonly userId?: string;
|
|
31
|
-
readonly alertMessages?: AlertMessages;
|
|
32
|
-
readonly onStepChange?: (stepId: string, stepType: StepType | string) => void;
|
|
33
|
-
readonly onGenerationStart?: (data: Record<string, unknown>, proceedToGenerating: () => void) => void;
|
|
34
|
-
readonly onGenerationComplete?: (result: unknown) => void;
|
|
35
|
-
readonly onGenerationError?: (error: string) => void;
|
|
36
|
-
readonly onCreditsExhausted?: () => void;
|
|
37
|
-
readonly onBack?: () => void;
|
|
38
|
-
readonly t: (key: string) => string;
|
|
39
|
-
readonly translations?: Record<string, string>;
|
|
40
|
-
readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
|
|
41
|
-
readonly renderGenerating?: (progress: number) => React.ReactElement | null;
|
|
42
|
-
readonly renderResult?: (result: unknown) => React.ReactElement | null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
46
|
-
featureConfig,
|
|
47
|
-
scenario,
|
|
48
|
-
userId,
|
|
49
|
-
alertMessages,
|
|
50
|
-
onStepChange,
|
|
51
|
-
onGenerationStart,
|
|
52
|
-
onGenerationComplete,
|
|
53
|
-
onGenerationError,
|
|
54
|
-
onCreditsExhausted,
|
|
55
|
-
onBack,
|
|
56
|
-
t,
|
|
57
|
-
translations,
|
|
58
|
-
renderPreview,
|
|
59
|
-
renderGenerating,
|
|
60
|
-
renderResult,
|
|
61
|
-
}) => {
|
|
62
|
-
const tokens = useAppDesignTokens();
|
|
63
|
-
|
|
64
|
-
// Build flow steps from wizard config
|
|
65
|
-
const flowSteps = useMemo<StepDefinition[]>(() => {
|
|
66
|
-
return buildFlowStepsFromWizard(featureConfig, {
|
|
67
|
-
includePreview: !!renderPreview,
|
|
68
|
-
includeGenerating: !!renderGenerating,
|
|
69
|
-
});
|
|
70
|
-
}, [featureConfig, renderPreview, renderGenerating]);
|
|
71
|
-
|
|
72
|
-
// Initialize flow and destructure to prevent infinite loops
|
|
73
|
-
const flow = useFlow({
|
|
74
|
-
steps: flowSteps,
|
|
75
|
-
initialStepIndex: 0,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Destructure flow to get stable references for useCallback dependencies
|
|
79
|
-
const {
|
|
80
|
-
currentStep,
|
|
81
|
-
currentStepIndex,
|
|
82
|
-
customData,
|
|
83
|
-
generationProgress,
|
|
84
|
-
generationResult,
|
|
85
|
-
nextStep,
|
|
86
|
-
previousStep,
|
|
87
|
-
setCustomData,
|
|
88
|
-
updateProgress,
|
|
89
|
-
} = flow;
|
|
90
|
-
|
|
91
|
-
// Handle progress change - memoized to prevent infinite loops
|
|
92
|
-
const handleProgressChange = useCallback(
|
|
93
|
-
(progress: number) => {
|
|
94
|
-
updateProgress(progress);
|
|
95
|
-
},
|
|
96
|
-
[updateProgress],
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Ensure scenario has required fields - use feature config as fallback
|
|
100
|
-
const validatedScenario = useMemo(() => {
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.log("[GenericWizardFlow] Validating scenario", {
|
|
103
|
-
hasScenario: !!scenario,
|
|
104
|
-
scenarioId: scenario?.id,
|
|
105
|
-
hasAiPrompt: scenario?.aiPrompt !== undefined,
|
|
106
|
-
hasModel: !!scenario?.model,
|
|
107
|
-
scenarioModel: scenario?.model,
|
|
108
|
-
outputType: scenario?.outputType,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (scenario && scenario.id && scenario.aiPrompt !== undefined) {
|
|
113
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
114
|
-
console.log("[GenericWizardFlow] Scenario validation passed", {
|
|
115
|
-
scenarioId: scenario.id,
|
|
116
|
-
model: scenario.model,
|
|
117
|
-
outputType: scenario.outputType,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
return scenario;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Fallback to feature config
|
|
124
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
125
|
-
console.warn("[GenericWizardFlow] Scenario missing required fields, using fallback", {
|
|
126
|
-
hasScenario: !!scenario,
|
|
127
|
-
scenarioId: scenario?.id,
|
|
128
|
-
featureConfigId: featureConfig.id,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
id: featureConfig.id,
|
|
134
|
-
aiPrompt: "",
|
|
135
|
-
outputType: "image" as const, // Default to image for safety
|
|
136
|
-
title: featureConfig.id,
|
|
137
|
-
};
|
|
138
|
-
}, [scenario, featureConfig.id]);
|
|
139
|
-
|
|
140
|
-
// Generation hook - handles AI generation automatically
|
|
141
|
-
const generationHook = useWizardGeneration({
|
|
142
|
-
scenario: validatedScenario,
|
|
143
|
-
wizardData: customData,
|
|
144
|
-
userId,
|
|
145
|
-
isGeneratingStep: currentStep?.type === StepType.GENERATING,
|
|
146
|
-
alertMessages,
|
|
147
|
-
onSuccess: onGenerationComplete,
|
|
148
|
-
onError: onGenerationError,
|
|
149
|
-
onProgressChange: handleProgressChange,
|
|
150
|
-
onCreditsExhausted,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Track previous step ID to prevent infinite loops
|
|
154
|
-
const prevStepIdRef = useRef<string | undefined>(undefined);
|
|
155
|
-
|
|
156
|
-
// DEBUG logging
|
|
157
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
158
|
-
console.log("[GenericWizardFlow] Render", {
|
|
159
|
-
featureId: featureConfig.id,
|
|
160
|
-
currentStepId: currentStep?.id,
|
|
161
|
-
currentStepType: currentStep?.type,
|
|
162
|
-
stepIndex: currentStepIndex,
|
|
163
|
-
totalSteps: flowSteps.length,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Notify parent when step changes
|
|
168
|
-
// Only call onStepChange when step ID actually changes (not on every object reference change)
|
|
169
|
-
useEffect(() => {
|
|
170
|
-
if (currentStep && onStepChange) {
|
|
171
|
-
const currentStepId = currentStep.id;
|
|
172
|
-
// Only notify if step ID changed
|
|
173
|
-
if (prevStepIdRef.current !== currentStepId) {
|
|
174
|
-
prevStepIdRef.current = currentStepId;
|
|
175
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
176
|
-
console.log("[GenericWizardFlow] Step changed", {
|
|
177
|
-
stepId: currentStep.id,
|
|
178
|
-
stepType: currentStep.type,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
onStepChange(currentStep.id, currentStep.type);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}, [currentStep, currentStepIndex, onStepChange]);
|
|
185
|
-
|
|
186
|
-
// Handle step continue
|
|
187
|
-
const handleStepContinue = useCallback(
|
|
188
|
-
(stepData: Record<string, unknown>) => {
|
|
189
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
190
|
-
console.log("[GenericWizardFlow] Step continue", {
|
|
191
|
-
stepId: currentStep?.id,
|
|
192
|
-
data: stepData,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Store step data in custom data
|
|
197
|
-
Object.entries(stepData).forEach(([key, value]) => {
|
|
198
|
-
setCustomData(key, value);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Check if this is the last step before generating
|
|
202
|
-
if (currentStepIndex === flowSteps.length - 2) {
|
|
203
|
-
// Next step is GENERATING
|
|
204
|
-
// Notify parent and provide callback to proceed to generating
|
|
205
|
-
// Parent will call proceedToGenerating() after feature gate passes
|
|
206
|
-
if (onGenerationStart) {
|
|
207
|
-
onGenerationStart(customData, () => {
|
|
208
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
209
|
-
console.log("[GenericWizardFlow] Proceeding to GENERATING step");
|
|
210
|
-
}
|
|
211
|
-
nextStep();
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
// DON'T call nextStep() here - parent will call it via proceedToGenerating callback
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Move to next step (for all non-generation steps)
|
|
219
|
-
nextStep();
|
|
220
|
-
},
|
|
221
|
-
[currentStep, currentStepIndex, customData, setCustomData, nextStep, flowSteps.length, onGenerationStart],
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Handle back
|
|
225
|
-
const handleBack = useCallback(() => {
|
|
226
|
-
if (currentStepIndex === 0) {
|
|
227
|
-
onBack?.();
|
|
228
|
-
} else {
|
|
229
|
-
previousStep();
|
|
230
|
-
}
|
|
231
|
-
}, [currentStepIndex, previousStep, onBack]);
|
|
232
|
-
|
|
233
|
-
// Render current step
|
|
234
|
-
const renderCurrentStep = useCallback(() => {
|
|
235
|
-
const step = currentStep;
|
|
236
|
-
if (!step) {
|
|
237
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
238
|
-
console.warn("[GenericWizardFlow] No current step!");
|
|
239
|
-
}
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
244
|
-
console.log("[GenericWizardFlow] Rendering step", {
|
|
245
|
-
stepId: step.id,
|
|
246
|
-
stepType: step.type,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Special steps with custom renderers
|
|
251
|
-
switch (step.type) {
|
|
252
|
-
case StepType.SCENARIO_PREVIEW:
|
|
253
|
-
// Preview continues to next step automatically
|
|
254
|
-
return renderPreview?.(nextStep) || null;
|
|
255
|
-
|
|
256
|
-
case StepType.GENERATING:
|
|
257
|
-
return renderGenerating?.(generationProgress) || null;
|
|
258
|
-
|
|
259
|
-
case StepType.RESULT_PREVIEW:
|
|
260
|
-
return renderResult?.(generationResult) || null;
|
|
261
|
-
|
|
262
|
-
default:
|
|
263
|
-
// Use generic step renderer
|
|
264
|
-
return renderStep({
|
|
265
|
-
step,
|
|
266
|
-
onContinue: handleStepContinue,
|
|
267
|
-
onBack: handleBack,
|
|
268
|
-
t,
|
|
269
|
-
translations,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
}, [
|
|
273
|
-
currentStep,
|
|
274
|
-
generationProgress,
|
|
275
|
-
generationResult,
|
|
276
|
-
nextStep,
|
|
277
|
-
renderPreview,
|
|
278
|
-
renderGenerating,
|
|
279
|
-
renderResult,
|
|
280
|
-
handleStepContinue,
|
|
281
|
-
handleBack,
|
|
282
|
-
t,
|
|
283
|
-
translations,
|
|
284
|
-
]);
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
288
|
-
{renderCurrentStep()}
|
|
289
|
-
</View>
|
|
290
|
-
);
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
const styles = StyleSheet.create({
|
|
294
|
-
container: {
|
|
295
|
-
flex: 1,
|
|
296
|
-
},
|
|
297
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Generating Screen
|
|
3
|
-
* Shows progress while AI generates content
|
|
4
|
-
* Used by ALL features - NO feature-specific code!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from "react";
|
|
8
|
-
import { View, StyleSheet, ActivityIndicator } from "react-native";
|
|
9
|
-
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
10
|
-
|
|
11
|
-
export interface GeneratingScreenProps {
|
|
12
|
-
readonly progress: number;
|
|
13
|
-
readonly scenario?: {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly title?: string;
|
|
16
|
-
};
|
|
17
|
-
readonly t: (key: string) => string;
|
|
18
|
-
readonly onCancel?: () => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
|
|
22
|
-
progress,
|
|
23
|
-
scenario,
|
|
24
|
-
t,
|
|
25
|
-
onCancel,
|
|
26
|
-
}) => {
|
|
27
|
-
const tokens = useAppDesignTokens();
|
|
28
|
-
|
|
29
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
|
-
console.log("[GeneratingScreen] Rendering", {
|
|
31
|
-
progress,
|
|
32
|
-
scenarioId: scenario?.id,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
38
|
-
<View style={styles.content}>
|
|
39
|
-
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
40
|
-
|
|
41
|
-
<AtomicText type="heading2" style={styles.title}>
|
|
42
|
-
{t("generator.title")}
|
|
43
|
-
</AtomicText>
|
|
44
|
-
|
|
45
|
-
<AtomicText type="body" style={[styles.message, { color: tokens.colors.textSecondary }]}>
|
|
46
|
-
{t("generator.waitMessage")}
|
|
47
|
-
</AtomicText>
|
|
48
|
-
|
|
49
|
-
{/* Progress Bar */}
|
|
50
|
-
<View style={styles.progressContainer}>
|
|
51
|
-
<View style={[styles.progressBar, { backgroundColor: tokens.colors.surfaceVariant }]}>
|
|
52
|
-
<View
|
|
53
|
-
style={[
|
|
54
|
-
styles.progressFill,
|
|
55
|
-
{
|
|
56
|
-
backgroundColor: tokens.colors.primary,
|
|
57
|
-
width: `${Math.min(100, Math.max(0, progress))}%`,
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
/>
|
|
61
|
-
</View>
|
|
62
|
-
<AtomicText type="caption" style={[styles.progressText, { color: tokens.colors.textSecondary }]}>
|
|
63
|
-
{Math.round(progress)}%
|
|
64
|
-
</AtomicText>
|
|
65
|
-
</View>
|
|
66
|
-
|
|
67
|
-
{/* Scenario Info */}
|
|
68
|
-
{scenario && (
|
|
69
|
-
<AtomicText type="caption" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
70
|
-
{scenario.title || scenario.id}
|
|
71
|
-
</AtomicText>
|
|
72
|
-
)}
|
|
73
|
-
|
|
74
|
-
{/* Hint */}
|
|
75
|
-
<AtomicText type="caption" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
76
|
-
{t("generator.hint")}
|
|
77
|
-
</AtomicText>
|
|
78
|
-
</View>
|
|
79
|
-
</View>
|
|
80
|
-
);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const styles = StyleSheet.create({
|
|
84
|
-
container: {
|
|
85
|
-
flex: 1,
|
|
86
|
-
justifyContent: "center",
|
|
87
|
-
alignItems: "center",
|
|
88
|
-
},
|
|
89
|
-
content: {
|
|
90
|
-
width: "80%",
|
|
91
|
-
maxWidth: 400,
|
|
92
|
-
alignItems: "center",
|
|
93
|
-
gap: 16,
|
|
94
|
-
},
|
|
95
|
-
title: {
|
|
96
|
-
textAlign: "center",
|
|
97
|
-
marginTop: 24,
|
|
98
|
-
},
|
|
99
|
-
message: {
|
|
100
|
-
textAlign: "center",
|
|
101
|
-
},
|
|
102
|
-
progressContainer: {
|
|
103
|
-
width: "100%",
|
|
104
|
-
marginTop: 24,
|
|
105
|
-
gap: 8,
|
|
106
|
-
},
|
|
107
|
-
progressBar: {
|
|
108
|
-
height: 8,
|
|
109
|
-
borderRadius: 4,
|
|
110
|
-
overflow: "hidden",
|
|
111
|
-
},
|
|
112
|
-
progressFill: {
|
|
113
|
-
height: "100%",
|
|
114
|
-
borderRadius: 4,
|
|
115
|
-
},
|
|
116
|
-
progressText: {
|
|
117
|
-
textAlign: "center",
|
|
118
|
-
},
|
|
119
|
-
hint: {
|
|
120
|
-
textAlign: "center",
|
|
121
|
-
marginTop: 8,
|
|
122
|
-
},
|
|
123
|
-
});
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Photo Upload Screen
|
|
3
|
-
* Used by wizard domain for ANY photo upload step
|
|
4
|
-
* NO feature-specific concepts (no partner, couple, etc.)
|
|
5
|
-
* Works for: couple features, face-swap, image-to-video, ANY photo upload need
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useMemo } from "react";
|
|
9
|
-
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
10
|
-
import {
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
ScreenLayout,
|
|
13
|
-
AtomicText,
|
|
14
|
-
AtomicIcon,
|
|
15
|
-
NavigationHeader,
|
|
16
|
-
type DesignTokens,
|
|
17
|
-
} from "@umituz/react-native-design-system";
|
|
18
|
-
import { PhotoUploadCard } from "../../../../presentation/components";
|
|
19
|
-
import { FaceDetectionToggle } from "../../../../domains/face-detection";
|
|
20
|
-
import { PhotoTips } from "../../../../features/partner-upload/presentation/components/PhotoTips";
|
|
21
|
-
import type { UploadedImage } from "../../../../features/partner-upload/domain/types";
|
|
22
|
-
import { usePhotoUploadState } from "../hooks/usePhotoUploadState";
|
|
23
|
-
|
|
24
|
-
export interface PhotoUploadScreenTranslations {
|
|
25
|
-
readonly title: string;
|
|
26
|
-
readonly subtitle: string;
|
|
27
|
-
readonly continue: string;
|
|
28
|
-
readonly tapToUpload: string;
|
|
29
|
-
readonly selectPhoto: string;
|
|
30
|
-
readonly change: string;
|
|
31
|
-
readonly analyzing: string;
|
|
32
|
-
readonly fileTooLarge: string;
|
|
33
|
-
readonly maxFileSize: string;
|
|
34
|
-
readonly error: string;
|
|
35
|
-
readonly uploadFailed: string;
|
|
36
|
-
readonly aiDisclosure?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface PhotoUploadScreenConfig {
|
|
40
|
-
readonly showFaceDetection?: boolean;
|
|
41
|
-
readonly showPhotoTips?: boolean;
|
|
42
|
-
readonly maxFileSizeMB?: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface PhotoUploadScreenProps {
|
|
46
|
-
readonly translations: PhotoUploadScreenTranslations;
|
|
47
|
-
readonly t: (key: string) => string;
|
|
48
|
-
readonly config?: PhotoUploadScreenConfig;
|
|
49
|
-
readonly faceDetectionEnabled?: boolean;
|
|
50
|
-
readonly onFaceDetectionToggle?: (enabled: boolean) => void;
|
|
51
|
-
readonly onBack: () => void;
|
|
52
|
-
readonly onContinue: (image: UploadedImage) => void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const DEFAULT_CONFIG: PhotoUploadScreenConfig = {
|
|
56
|
-
showFaceDetection: false,
|
|
57
|
-
showPhotoTips: true,
|
|
58
|
-
maxFileSizeMB: 10,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const GenericPhotoUploadScreen: React.FC<PhotoUploadScreenProps> = ({
|
|
62
|
-
translations,
|
|
63
|
-
t,
|
|
64
|
-
config = DEFAULT_CONFIG,
|
|
65
|
-
faceDetectionEnabled = false,
|
|
66
|
-
onFaceDetectionToggle,
|
|
67
|
-
onBack,
|
|
68
|
-
onContinue,
|
|
69
|
-
}) => {
|
|
70
|
-
const tokens = useAppDesignTokens();
|
|
71
|
-
|
|
72
|
-
const { image, handlePickImage, canContinue } = usePhotoUploadState({
|
|
73
|
-
config: { maxFileSizeMB: config.maxFileSizeMB },
|
|
74
|
-
translations: {
|
|
75
|
-
fileTooLarge: translations.fileTooLarge,
|
|
76
|
-
maxFileSize: translations.maxFileSize,
|
|
77
|
-
error: translations.error,
|
|
78
|
-
uploadFailed: translations.uploadFailed,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const handleContinuePress = () => {
|
|
83
|
-
if (!canContinue || !image) return;
|
|
84
|
-
onContinue(image);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
88
|
-
const showFaceDetection = config.showFaceDetection ?? false;
|
|
89
|
-
const showPhotoTips = config.showPhotoTips ?? true;
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
93
|
-
<NavigationHeader
|
|
94
|
-
title={translations.title}
|
|
95
|
-
onBackPress={onBack}
|
|
96
|
-
rightElement={
|
|
97
|
-
<TouchableOpacity
|
|
98
|
-
onPress={handleContinuePress}
|
|
99
|
-
activeOpacity={0.7}
|
|
100
|
-
disabled={!canContinue || !image}
|
|
101
|
-
style={[
|
|
102
|
-
styles.continueButton,
|
|
103
|
-
{
|
|
104
|
-
backgroundColor: canContinue && image ? tokens.colors.primary : tokens.colors.surfaceVariant,
|
|
105
|
-
opacity: canContinue && image ? 1 : 0.5,
|
|
106
|
-
},
|
|
107
|
-
]}
|
|
108
|
-
>
|
|
109
|
-
<AtomicText
|
|
110
|
-
type="bodyMedium"
|
|
111
|
-
style={[
|
|
112
|
-
styles.continueText,
|
|
113
|
-
{ color: canContinue && image ? tokens.colors.onPrimary : tokens.colors.textSecondary },
|
|
114
|
-
]}
|
|
115
|
-
>
|
|
116
|
-
{translations.continue}
|
|
117
|
-
</AtomicText>
|
|
118
|
-
<AtomicIcon
|
|
119
|
-
name="arrow-forward"
|
|
120
|
-
size="sm"
|
|
121
|
-
color={canContinue && image ? "onPrimary" : "textSecondary"}
|
|
122
|
-
/>
|
|
123
|
-
</TouchableOpacity>
|
|
124
|
-
}
|
|
125
|
-
/>
|
|
126
|
-
<ScreenLayout
|
|
127
|
-
edges={["left", "right"]}
|
|
128
|
-
backgroundColor="transparent"
|
|
129
|
-
scrollable={true}
|
|
130
|
-
keyboardAvoiding={true}
|
|
131
|
-
contentContainerStyle={styles.scrollContent}
|
|
132
|
-
hideScrollIndicator={true}
|
|
133
|
-
>
|
|
134
|
-
<AtomicText style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
|
|
135
|
-
{translations.subtitle}
|
|
136
|
-
</AtomicText>
|
|
137
|
-
|
|
138
|
-
{/* Photo Tips - InfoGrid version */}
|
|
139
|
-
{showPhotoTips && (
|
|
140
|
-
<PhotoTips
|
|
141
|
-
t={t}
|
|
142
|
-
titleKey="photoUpload.tips.title"
|
|
143
|
-
headerIcon="bulb"
|
|
144
|
-
style={{ marginHorizontal: 24, marginBottom: 20 }}
|
|
145
|
-
/>
|
|
146
|
-
)}
|
|
147
|
-
|
|
148
|
-
{showFaceDetection && onFaceDetectionToggle && (
|
|
149
|
-
<FaceDetectionToggle
|
|
150
|
-
isEnabled={faceDetectionEnabled}
|
|
151
|
-
onToggle={onFaceDetectionToggle}
|
|
152
|
-
label={t("photoUpload.faceDetection")}
|
|
153
|
-
hidden={true}
|
|
154
|
-
/>
|
|
155
|
-
)}
|
|
156
|
-
|
|
157
|
-
<PhotoUploadCard
|
|
158
|
-
imageUri={image?.previewUrl || null}
|
|
159
|
-
onPress={handlePickImage}
|
|
160
|
-
isValidating={false}
|
|
161
|
-
isValid={null}
|
|
162
|
-
translations={{
|
|
163
|
-
tapToUpload: translations.tapToUpload,
|
|
164
|
-
selectPhoto: translations.selectPhoto,
|
|
165
|
-
change: translations.change,
|
|
166
|
-
analyzing: translations.analyzing,
|
|
167
|
-
}}
|
|
168
|
-
/>
|
|
169
|
-
|
|
170
|
-
{translations.aiDisclosure && (
|
|
171
|
-
<View style={styles.disclosureContainer}>
|
|
172
|
-
<AtomicText
|
|
173
|
-
type="labelSmall"
|
|
174
|
-
style={[styles.disclosureText, { color: tokens.colors.textSecondary }]}
|
|
175
|
-
>
|
|
176
|
-
{translations.aiDisclosure}
|
|
177
|
-
</AtomicText>
|
|
178
|
-
</View>
|
|
179
|
-
)}
|
|
180
|
-
</ScreenLayout>
|
|
181
|
-
</View>
|
|
182
|
-
);
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const createStyles = (tokens: DesignTokens) =>
|
|
186
|
-
StyleSheet.create({
|
|
187
|
-
container: {
|
|
188
|
-
flex: 1,
|
|
189
|
-
},
|
|
190
|
-
scrollContent: {
|
|
191
|
-
paddingBottom: 40,
|
|
192
|
-
},
|
|
193
|
-
subtitle: {
|
|
194
|
-
fontSize: 16,
|
|
195
|
-
textAlign: "center",
|
|
196
|
-
marginHorizontal: 24,
|
|
197
|
-
marginBottom: 24,
|
|
198
|
-
},
|
|
199
|
-
continueButton: {
|
|
200
|
-
flexDirection: "row",
|
|
201
|
-
alignItems: "center",
|
|
202
|
-
paddingHorizontal: tokens.spacing.md,
|
|
203
|
-
paddingVertical: tokens.spacing.xs,
|
|
204
|
-
borderRadius: tokens.borders.radius.full,
|
|
205
|
-
},
|
|
206
|
-
continueText: {
|
|
207
|
-
fontWeight: "800",
|
|
208
|
-
marginRight: 4,
|
|
209
|
-
},
|
|
210
|
-
disclosureContainer: {
|
|
211
|
-
marginTop: 24,
|
|
212
|
-
marginHorizontal: 24,
|
|
213
|
-
padding: 16,
|
|
214
|
-
borderRadius: 12,
|
|
215
|
-
backgroundColor: tokens.colors.surfaceVariant + "40",
|
|
216
|
-
},
|
|
217
|
-
disclosureText: {
|
|
218
|
-
textAlign: "center",
|
|
219
|
-
lineHeight: 18,
|
|
220
|
-
opacity: 0.8,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Photo Upload Step
|
|
3
|
-
* Used by ALL features that need photo uploads
|
|
4
|
-
* (couple, face-swap, image-to-video, etc.)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from "react";
|
|
8
|
-
import type { PhotoUploadStepConfig } from "../../domain/entities/wizard-config.types";
|
|
9
|
-
|
|
10
|
-
// Use wizard domain's generic photo upload screen - NO feature-specific references!
|
|
11
|
-
import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
|
|
12
|
-
import type { UploadedImage } from "../../../../features/partner-upload/domain/types";
|
|
13
|
-
|
|
14
|
-
export interface PhotoUploadStepProps {
|
|
15
|
-
readonly config: PhotoUploadStepConfig;
|
|
16
|
-
readonly onContinue: (image: UploadedImage) => void;
|
|
17
|
-
readonly onBack: () => void;
|
|
18
|
-
readonly t: (key: string) => string;
|
|
19
|
-
readonly translations?: Record<string, string>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const PhotoUploadStep: React.FC<PhotoUploadStepProps> = ({
|
|
23
|
-
config,
|
|
24
|
-
onContinue,
|
|
25
|
-
onBack,
|
|
26
|
-
t,
|
|
27
|
-
translations,
|
|
28
|
-
}) => {
|
|
29
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
|
-
console.log("[PhotoUploadStep] Rendering", {
|
|
31
|
-
stepId: config.id,
|
|
32
|
-
label: config.label,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<GenericPhotoUploadScreen
|
|
38
|
-
translations={{
|
|
39
|
-
title: config.titleKey ? t(config.titleKey) : config.label || "Upload Photo",
|
|
40
|
-
subtitle: config.subtitleKey ? t(config.subtitleKey) : t("photoUpload.subtitle"),
|
|
41
|
-
continue: t("common.continue"),
|
|
42
|
-
tapToUpload: t("photoUpload.tapToUpload"),
|
|
43
|
-
selectPhoto: t("photoUpload.selectPhoto"),
|
|
44
|
-
change: t("common.change"),
|
|
45
|
-
analyzing: t("photoUpload.analyzing"),
|
|
46
|
-
fileTooLarge: t("common.errors.file_too_large"),
|
|
47
|
-
maxFileSize: t("common.errors.max_file_size"),
|
|
48
|
-
error: t("common.error"),
|
|
49
|
-
uploadFailed: t("common.errors.upload_failed"),
|
|
50
|
-
}}
|
|
51
|
-
t={t}
|
|
52
|
-
config={{
|
|
53
|
-
showFaceDetection: config.showFaceDetection ?? false,
|
|
54
|
-
showPhotoTips: config.showPhotoTips ?? true,
|
|
55
|
-
maxFileSizeMB: config.maxFileSizeMB ?? 10,
|
|
56
|
-
}}
|
|
57
|
-
onBack={onBack}
|
|
58
|
-
onContinue={(image) => {
|
|
59
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
60
|
-
console.log("[PhotoUploadStep] Photo uploaded", { stepId: config.id });
|
|
61
|
-
}
|
|
62
|
-
onContinue(image);
|
|
63
|
-
}}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
};
|