@umituz/react-native-ai-generation-content 1.26.39 → 1.26.41

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.26.39",
3
+ "version": "1.26.41",
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",
@@ -21,8 +21,8 @@ import type { WizardFeatureConfig, WizardStepConfig } from "../../domain/entitie
21
21
  import { buildFlowStepsFromWizard } from "../../infrastructure/builders/dynamic-step-builder";
22
22
  import { useWizardGeneration, type WizardScenarioData } from "../hooks/useWizardGeneration";
23
23
  import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
24
- import { PhotoStep } from "../../../../../presentation/components/photo-step/PhotoStep";
25
- import { usePhotoUploadState } from "../hooks/usePhotoUploadState";
24
+ import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
25
+ import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
26
26
 
27
27
  export interface GenericWizardFlowProps {
28
28
  readonly featureConfig: WizardFeatureConfig;
@@ -54,7 +54,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
54
54
  onCreditsExhausted,
55
55
  onBack,
56
56
  t,
57
- translations,
57
+ translations: _translations,
58
58
  renderPreview,
59
59
  renderGenerating,
60
60
  renderResult,
@@ -187,44 +187,6 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
187
187
  }
188
188
  }, [currentStep, currentStepIndex, onStepChange]);
189
189
 
190
- // Handle step continue
191
- const handleStepContinue = useCallback(
192
- (stepData: Record<string, unknown>) => {
193
- if (typeof __DEV__ !== "undefined" && __DEV__) {
194
- console.log("[GenericWizardFlow] Step continue", {
195
- stepId: currentStep?.id,
196
- data: stepData,
197
- });
198
- }
199
-
200
- // Store step data in custom data
201
- Object.entries(stepData).forEach(([key, value]) => {
202
- setCustomData(key, value);
203
- });
204
-
205
- // Check if this is the last step before generating
206
- if (currentStepIndex === flowSteps.length - 2) {
207
- // Next step is GENERATING
208
- // Notify parent and provide callback to proceed to generating
209
- // Parent will call proceedToGenerating() after feature gate passes
210
- if (onGenerationStart) {
211
- onGenerationStart(customData, () => {
212
- if (typeof __DEV__ !== "undefined" && __DEV__) {
213
- console.log("[GenericWizardFlow] Proceeding to GENERATING step");
214
- }
215
- nextStep();
216
- });
217
- }
218
- // DON'T call nextStep() here - parent will call it via proceedToGenerating callback
219
- return;
220
- }
221
-
222
- // Move to next step (for all non-generation steps)
223
- nextStep();
224
- },
225
- [currentStep, currentStepIndex, customData, setCustomData, nextStep, flowSteps.length, onGenerationStart],
226
- );
227
-
228
190
  // Handle back
229
191
  const handleBack = useCallback(() => {
230
192
  if (currentStepIndex === 0) {
@@ -234,24 +196,26 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
234
196
  }
235
197
  }, [currentStepIndex, previousStep, onBack]);
236
198
 
237
- // Photo upload state translations (generic, used for all photo upload steps)
238
- const photoUploadTranslations = useMemo(() => ({
239
- fileTooLarge: t("common.errors.file_too_large"),
240
- maxFileSize: t("common.errors.max_file_size"),
241
- error: t("common.error"),
242
- uploadFailed: t("common.errors.upload_failed"),
243
- }), [t]);
244
-
245
- const photoUploadHook = usePhotoUploadState({
246
- translations: photoUploadTranslations,
247
- });
248
-
249
- // Save photo when uploaded
250
- useEffect(() => {
251
- if (photoUploadHook.image && currentStep) {
252
- setCustomData(currentStep.id, photoUploadHook.image);
199
+ // Handle photo continue - saves photo and moves to next step
200
+ const handlePhotoContinue = useCallback((stepId: string, image: UploadedImage) => {
201
+ setCustomData(stepId, image);
202
+
203
+ // Check if this is the last step before generating
204
+ if (currentStepIndex === flowSteps.length - 2) {
205
+ // Next step is GENERATING - call onGenerationStart
206
+ if (onGenerationStart) {
207
+ onGenerationStart({ ...customData, [stepId]: image }, () => {
208
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
209
+ console.log("[GenericWizardFlow] Proceeding to GENERATING step");
210
+ }
211
+ nextStep();
212
+ });
213
+ }
214
+ return;
253
215
  }
254
- }, [photoUploadHook.image, currentStep, setCustomData]);
216
+
217
+ nextStep();
218
+ }, [currentStepIndex, flowSteps.length, customData, setCustomData, nextStep, onGenerationStart]);
255
219
 
256
220
  // Render current step
257
221
  const renderCurrentStep = useCallback(() => {
@@ -290,37 +254,31 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
290
254
  const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
291
255
  const title = t(titleKey);
292
256
 
293
- // Subtitle is optional
294
- const subtitle = wizardConfig?.subtitleKey ? t(wizardConfig.subtitleKey) : undefined;
257
+ // Subtitle from config
258
+ const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
259
+ const subtitle = t(subtitleKey);
295
260
 
296
261
  // Get existing photo for this step from customData
297
- const existingPhoto = customData[step.id];
298
- const imageUri = existingPhoto && typeof existingPhoto === "object" && "uri" in existingPhoto
299
- ? (existingPhoto.uri as string)
300
- : photoUploadHook.image?.uri || null;
262
+ const existingPhoto = customData[step.id] as UploadedImage | undefined;
301
263
 
302
264
  return (
303
- <PhotoStep
304
- config={{
305
- enabled: true,
306
- order: currentStepIndex,
307
- id: step.id,
308
- header: {},
309
- photoCard: {},
310
- enableValidation: false,
311
- }}
312
- imageUri={imageUri}
313
- isValidating={false}
314
- isValid={null}
315
- onPhotoSelect={photoUploadHook.handlePickImage}
316
- disabled={false}
317
- title={title}
318
- subtitle={subtitle}
265
+ <GenericPhotoUploadScreen
319
266
  translations={{
267
+ title,
268
+ subtitle,
269
+ continue: t("common.continue"),
320
270
  tapToUpload: t("photoUpload.tapToUpload"),
321
271
  selectPhoto: t("photoUpload.selectPhoto"),
322
272
  change: t("common.change"),
273
+ fileTooLarge: t("common.errors.file_too_large"),
274
+ maxFileSize: t("common.errors.max_file_size"),
275
+ error: t("common.error"),
276
+ uploadFailed: t("common.errors.upload_failed"),
323
277
  }}
278
+ t={t}
279
+ onBack={handleBack}
280
+ onContinue={(image) => handlePhotoContinue(step.id, image)}
281
+ existingImage={existingPhoto}
324
282
  />
325
283
  );
326
284
  }
@@ -334,16 +292,16 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
334
292
  }
335
293
  }, [
336
294
  currentStep,
295
+ customData,
337
296
  generationProgress,
338
297
  generationResult,
339
298
  nextStep,
340
299
  renderPreview,
341
300
  renderGenerating,
342
301
  renderResult,
343
- handleStepContinue,
302
+ handlePhotoContinue,
344
303
  handleBack,
345
304
  t,
346
- translations,
347
305
  ]);
348
306
 
349
307
  return (
@@ -23,6 +23,7 @@ export interface PhotoUploadTranslations {
23
23
  export interface UsePhotoUploadStateProps {
24
24
  readonly config?: PhotoUploadConfig;
25
25
  readonly translations: PhotoUploadTranslations;
26
+ readonly initialImage?: UploadedImage;
26
27
  }
27
28
 
28
29
  export interface UsePhotoUploadStateReturn {
@@ -36,8 +37,9 @@ const DEFAULT_MAX_FILE_SIZE_MB = 10;
36
37
  export const usePhotoUploadState = ({
37
38
  config,
38
39
  translations,
40
+ initialImage,
39
41
  }: UsePhotoUploadStateProps): UsePhotoUploadStateReturn => {
40
- const [image, setImage] = useState<UploadedImage | null>(null);
42
+ const [image, setImage] = useState<UploadedImage | null>(initialImage || null);
41
43
 
42
44
  const maxFileSizeMB = config?.maxFileSizeMB ?? DEFAULT_MAX_FILE_SIZE_MB;
43
45
 
@@ -0,0 +1,229 @@
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
+ InfoGrid,
17
+ type DesignTokens,
18
+ type InfoGridItem,
19
+ } from "@umituz/react-native-design-system";
20
+ import { PhotoUploadCard } from "../../../../../presentation/components";
21
+ import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
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 showPhotoTips?: boolean;
41
+ readonly maxFileSizeMB?: number;
42
+ }
43
+
44
+ export interface PhotoUploadScreenProps {
45
+ readonly translations: PhotoUploadScreenTranslations;
46
+ readonly t: (key: string) => string;
47
+ readonly config?: PhotoUploadScreenConfig;
48
+ readonly onBack: () => void;
49
+ readonly onContinue: (image: UploadedImage) => void;
50
+ readonly existingImage?: UploadedImage | null;
51
+ }
52
+
53
+ const DEFAULT_CONFIG: PhotoUploadScreenConfig = {
54
+ showPhotoTips: true,
55
+ maxFileSizeMB: 10,
56
+ };
57
+
58
+ export const GenericPhotoUploadScreen: React.FC<PhotoUploadScreenProps> = ({
59
+ translations,
60
+ t,
61
+ config = DEFAULT_CONFIG,
62
+ onBack,
63
+ onContinue,
64
+ existingImage,
65
+ }) => {
66
+ const tokens = useAppDesignTokens();
67
+
68
+ const { image, handlePickImage, canContinue } = usePhotoUploadState({
69
+ config: { maxFileSizeMB: config.maxFileSizeMB },
70
+ translations: {
71
+ fileTooLarge: translations.fileTooLarge,
72
+ maxFileSize: translations.maxFileSize,
73
+ error: translations.error,
74
+ uploadFailed: translations.uploadFailed,
75
+ },
76
+ initialImage: existingImage || undefined,
77
+ });
78
+
79
+ const handleContinuePress = () => {
80
+ if (!canContinue || !image) return;
81
+ onContinue(image);
82
+ };
83
+
84
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
85
+ const showPhotoTips = config.showPhotoTips ?? true;
86
+
87
+ // Build photo tips items from translations
88
+ const photoTipsItems: InfoGridItem[] = useMemo(() => {
89
+ const tipKeys = [
90
+ { key: "photoUpload.tips.clearFace", icon: "Smile" },
91
+ { key: "photoUpload.tips.goodLighting", icon: "Sun" },
92
+ { key: "photoUpload.tips.recentPhoto", icon: "Clock" },
93
+ { key: "photoUpload.tips.noFilters", icon: "Image" },
94
+ ];
95
+ return tipKeys.map(({ key, icon }) => ({
96
+ text: t(key),
97
+ icon,
98
+ }));
99
+ }, [t]);
100
+
101
+ return (
102
+ <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
103
+ <NavigationHeader
104
+ title={translations.title}
105
+ onBackPress={onBack}
106
+ rightElement={
107
+ <TouchableOpacity
108
+ onPress={handleContinuePress}
109
+ activeOpacity={0.7}
110
+ disabled={!canContinue || !image}
111
+ style={[
112
+ styles.continueButton,
113
+ {
114
+ backgroundColor: canContinue && image ? tokens.colors.primary : tokens.colors.surfaceVariant,
115
+ opacity: canContinue && image ? 1 : 0.5,
116
+ },
117
+ ]}
118
+ >
119
+ <AtomicText
120
+ type="bodyMedium"
121
+ style={[
122
+ styles.continueText,
123
+ { color: canContinue && image ? tokens.colors.onPrimary : tokens.colors.textSecondary },
124
+ ]}
125
+ >
126
+ {translations.continue}
127
+ </AtomicText>
128
+ <AtomicIcon
129
+ name="ChevronRight"
130
+ size="sm"
131
+ color={canContinue && image ? "onPrimary" : "textSecondary"}
132
+ />
133
+ </TouchableOpacity>
134
+ }
135
+ />
136
+ <ScreenLayout
137
+ edges={["left", "right"]}
138
+ backgroundColor="transparent"
139
+ scrollable={true}
140
+ keyboardAvoiding={true}
141
+ contentContainerStyle={styles.scrollContent}
142
+ hideScrollIndicator={true}
143
+ >
144
+ <AtomicText style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
145
+ {translations.subtitle}
146
+ </AtomicText>
147
+
148
+ {/* Photo Tips - InfoGrid version */}
149
+ {showPhotoTips && (
150
+ <View style={styles.tipsContainer}>
151
+ <InfoGrid
152
+ items={photoTipsItems}
153
+ columns={2}
154
+ title={t("photoUpload.tips.title")}
155
+ headerIcon="Lightbulb"
156
+ />
157
+ </View>
158
+ )}
159
+
160
+ <PhotoUploadCard
161
+ imageUri={image?.previewUrl || image?.uri || null}
162
+ onPress={handlePickImage}
163
+ isValidating={false}
164
+ isValid={null}
165
+ translations={{
166
+ tapToUpload: translations.tapToUpload,
167
+ selectPhoto: translations.selectPhoto,
168
+ change: translations.change,
169
+ analyzing: translations.analyzing,
170
+ }}
171
+ />
172
+
173
+ {translations.aiDisclosure && (
174
+ <View style={styles.disclosureContainer}>
175
+ <AtomicText
176
+ type="labelSmall"
177
+ style={[styles.disclosureText, { color: tokens.colors.textSecondary }]}
178
+ >
179
+ {translations.aiDisclosure}
180
+ </AtomicText>
181
+ </View>
182
+ )}
183
+ </ScreenLayout>
184
+ </View>
185
+ );
186
+ };
187
+
188
+ const createStyles = (tokens: DesignTokens) =>
189
+ StyleSheet.create({
190
+ container: {
191
+ flex: 1,
192
+ },
193
+ scrollContent: {
194
+ paddingBottom: 40,
195
+ },
196
+ subtitle: {
197
+ fontSize: 16,
198
+ textAlign: "center",
199
+ marginHorizontal: 24,
200
+ marginBottom: 24,
201
+ },
202
+ tipsContainer: {
203
+ marginHorizontal: 24,
204
+ marginBottom: 20,
205
+ },
206
+ continueButton: {
207
+ flexDirection: "row",
208
+ alignItems: "center",
209
+ paddingHorizontal: tokens.spacing.md,
210
+ paddingVertical: tokens.spacing.xs,
211
+ borderRadius: tokens.borders.radius.full,
212
+ },
213
+ continueText: {
214
+ fontWeight: "800",
215
+ marginRight: 4,
216
+ },
217
+ disclosureContainer: {
218
+ marginTop: 24,
219
+ marginHorizontal: 24,
220
+ padding: 16,
221
+ borderRadius: 12,
222
+ backgroundColor: tokens.colors.surfaceVariant + "40",
223
+ },
224
+ disclosureText: {
225
+ textAlign: "center",
226
+ lineHeight: 18,
227
+ opacity: 0.8,
228
+ },
229
+ });
@@ -1 +1,7 @@
1
1
  export { GeneratingScreen } from "./GeneratingScreen";
2
+ export { GenericPhotoUploadScreen } from "./GenericPhotoUploadScreen";
3
+ export type {
4
+ PhotoUploadScreenTranslations,
5
+ PhotoUploadScreenConfig,
6
+ PhotoUploadScreenProps,
7
+ } from "./GenericPhotoUploadScreen";