@umituz/react-native-ai-generation-content 1.14.0 → 1.15.0

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.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -67,7 +67,7 @@
67
67
  "@typescript-eslint/eslint-plugin": "^7.0.0",
68
68
  "@typescript-eslint/parser": "^7.0.0",
69
69
  "@umituz/react-native-animation": "*",
70
- "@umituz/react-native-design-system": "^2.3.33",
70
+ "@umituz/react-native-design-system": "^2.4.1",
71
71
  "@umituz/react-native-firebase": "^1.13.20",
72
72
  "@umituz/react-native-haptics": "^1.0.2",
73
73
  "@umituz/react-native-image": "*",
package/src/index.ts CHANGED
@@ -186,6 +186,7 @@ export {
186
186
  usePendingJobs,
187
187
  useBackgroundGeneration,
188
188
  usePhotoGeneration,
189
+ useGenerationFlow,
189
190
  } from "./presentation/hooks";
190
191
 
191
192
  export type {
@@ -203,6 +204,8 @@ export type {
203
204
  PhotoGenerationConfig,
204
205
  PhotoGenerationState,
205
206
  PhotoGenerationStatus,
207
+ UseGenerationFlowOptions,
208
+ UseGenerationFlowReturn,
206
209
  } from "./presentation/hooks";
207
210
 
208
211
  // =============================================================================
@@ -222,6 +225,7 @@ export {
222
225
  ResultStoryCard,
223
226
  ResultActions,
224
227
  DEFAULT_RESULT_CONFIG,
228
+ PhotoStep,
225
229
  } from "./presentation/components";
226
230
 
227
231
  export type {
@@ -246,8 +250,28 @@ export type {
246
250
  ResultActionsConfig,
247
251
  ResultLayoutConfig,
248
252
  ResultActionButton,
253
+ PhotoStepProps,
249
254
  } from "./presentation/components";
250
255
 
256
+ // =============================================================================
257
+ // PRESENTATION LAYER - Flow Configuration
258
+ // =============================================================================
259
+
260
+ export {
261
+ DEFAULT_SINGLE_PHOTO_FLOW,
262
+ DEFAULT_DUAL_PHOTO_FLOW,
263
+ } from "./presentation/types/flow-config.types";
264
+
265
+ export type {
266
+ PhotoStepConfig,
267
+ TextInputStepConfig,
268
+ PreviewStepConfig,
269
+ GenerationFlowConfig,
270
+ PhotoStepData,
271
+ TextInputStepData,
272
+ GenerationFlowState,
273
+ } from "./presentation/types/flow-config.types";
274
+
251
275
  // =============================================================================
252
276
  // DOMAINS - AI Prompts
253
277
  // =============================================================================
@@ -22,3 +22,4 @@ export type { PendingJobProgressBarProps } from "./PendingJobProgressBar";
22
22
  export type { PendingJobCardActionsProps } from "./PendingJobCardActions";
23
23
 
24
24
  export * from "./result";
25
+ export * from "./photo-step";
@@ -0,0 +1,96 @@
1
+ /**
2
+ * PhotoStep Component
3
+ * Configurable photo upload step using design system components
4
+ *
5
+ * @package @umituz/react-native-ai-generation-content
6
+ */
7
+
8
+ import React, { useMemo } from "react";
9
+ import { View, StyleSheet, type ViewStyle, type StyleProp } from "react-native";
10
+ import {
11
+ StepHeader,
12
+ PhotoUploadCard,
13
+ } from "@umituz/react-native-design-system";
14
+ import type { PhotoStepConfig } from "../../types/flow-config.types";
15
+
16
+ export interface PhotoStepProps {
17
+ /** Step configuration */
18
+ config: PhotoStepConfig;
19
+ /** Current photo URI */
20
+ imageUri: string | null;
21
+ /** Photo preview URL */
22
+ previewUrl?: string;
23
+ /** Whether photo is being validated */
24
+ isValidating?: boolean;
25
+ /** Whether photo is valid */
26
+ isValid?: boolean | null;
27
+ /** Handler for photo selection */
28
+ onPhotoSelect: () => void;
29
+ /** Whether photo selection is disabled */
30
+ disabled?: boolean;
31
+ /** Step title */
32
+ title: string;
33
+ /** Step subtitle */
34
+ subtitle?: string;
35
+ /** Translation strings for photo upload card */
36
+ translations: {
37
+ tapToUpload: string;
38
+ selectPhoto: string;
39
+ change: string;
40
+ analyzing?: string;
41
+ };
42
+ /** Additional content to render below photo card */
43
+ children?: React.ReactNode;
44
+ /** Container style */
45
+ style?: StyleProp<ViewStyle>;
46
+ }
47
+
48
+ export const PhotoStep: React.FC<PhotoStepProps> = ({
49
+ config,
50
+ imageUri,
51
+ previewUrl,
52
+ isValidating = false,
53
+ isValid = null,
54
+ onPhotoSelect,
55
+ disabled = false,
56
+ title,
57
+ subtitle,
58
+ translations,
59
+ children,
60
+ style,
61
+ }) => {
62
+ const styles = useMemo(
63
+ () =>
64
+ StyleSheet.create({
65
+ container: {
66
+ flex: 1,
67
+ },
68
+ }),
69
+ [],
70
+ );
71
+
72
+ return (
73
+ <View style={[styles.container, style]}>
74
+ {/* Step Header */}
75
+ <StepHeader
76
+ title={title}
77
+ subtitle={subtitle}
78
+ config={config.header}
79
+ />
80
+
81
+ {/* Photo Upload Card */}
82
+ <PhotoUploadCard
83
+ imageUri={previewUrl || imageUri}
84
+ onPress={onPhotoSelect}
85
+ isValidating={isValidating}
86
+ isValid={config.enableValidation ? isValid : null}
87
+ disabled={disabled}
88
+ config={config.photoCard}
89
+ translations={translations}
90
+ />
91
+
92
+ {/* Additional content (e.g., name input, tips, etc.) */}
93
+ {children}
94
+ </View>
95
+ );
96
+ };
@@ -0,0 +1,2 @@
1
+ export { PhotoStep } from "./PhotoStep";
2
+ export type { PhotoStepProps } from "./PhotoStep";
@@ -34,3 +34,9 @@ export type {
34
34
  PhotoGenerationState,
35
35
  PhotoGenerationStatus,
36
36
  } from "./photo-generation.types";
37
+
38
+ export { useGenerationFlow } from "./useGenerationFlow";
39
+ export type {
40
+ UseGenerationFlowOptions,
41
+ UseGenerationFlowReturn,
42
+ } from "./useGenerationFlow";
@@ -0,0 +1,315 @@
1
+ /**
2
+ * useGenerationFlow Hook
3
+ * Manages step-by-step generation flow state and navigation
4
+ *
5
+ * @package @umituz/react-native-ai-generation-content
6
+ */
7
+
8
+ import { useState, useCallback, useMemo } from "react";
9
+ import type {
10
+ GenerationFlowConfig,
11
+ GenerationFlowState,
12
+ PhotoStepData,
13
+ PhotoStepConfig,
14
+ } from "../types/flow-config.types";
15
+
16
+ export interface UseGenerationFlowOptions {
17
+ /** Flow configuration */
18
+ config: GenerationFlowConfig;
19
+ /** Callback when flow is complete */
20
+ onComplete?: (state: GenerationFlowState) => void;
21
+ /** Callback when step changes */
22
+ onStepChange?: (stepIndex: number, stepConfig: PhotoStepConfig) => void;
23
+ }
24
+
25
+ export interface UseGenerationFlowReturn {
26
+ /** Current flow state */
27
+ state: GenerationFlowState;
28
+ /** Current step configuration */
29
+ currentStepConfig: PhotoStepConfig | null;
30
+ /** Current step data */
31
+ currentStepData: PhotoStepData | null;
32
+ /** Whether can go to next step */
33
+ canGoNext: boolean;
34
+ /** Whether can go to previous step */
35
+ canGoBack: boolean;
36
+ /** Go to next step */
37
+ goNext: () => void;
38
+ /** Go to previous step */
39
+ goBack: () => void;
40
+ /** Update current step photo */
41
+ updatePhoto: (imageUri: string, previewUrl?: string) => void;
42
+ /** Update current step name */
43
+ updateName: (name: string) => void;
44
+ /** Update current step validation */
45
+ updateValidation: (isValid: boolean) => void;
46
+ /** Update text input */
47
+ updateTextInput: (text: string) => void;
48
+ /** Reset flow */
49
+ reset: () => void;
50
+ /** Complete flow */
51
+ complete: () => void;
52
+ }
53
+
54
+ /**
55
+ * Hook to manage generation flow state
56
+ */
57
+ export const useGenerationFlow = ({
58
+ config,
59
+ onComplete,
60
+ onStepChange,
61
+ }: UseGenerationFlowOptions): UseGenerationFlowReturn => {
62
+ // Initialize state
63
+ const [state, setState] = useState<GenerationFlowState>(() => ({
64
+ currentStepIndex: 0,
65
+ photoSteps: config.photoSteps.map((step) => ({
66
+ id: step.id,
67
+ imageUri: null,
68
+ previewUrl: undefined,
69
+ name: undefined,
70
+ isValid: undefined,
71
+ validationStatus: "pending",
72
+ })),
73
+ textInput: config.textInputStep
74
+ ? {
75
+ id: config.textInputStep.id,
76
+ text: "",
77
+ isValid: false,
78
+ }
79
+ : undefined,
80
+ isComplete: false,
81
+ isProcessing: false,
82
+ }));
83
+
84
+ // Get current step configuration
85
+ const currentStepConfig = useMemo(() => {
86
+ if (state.currentStepIndex >= config.photoSteps.length) {
87
+ return null;
88
+ }
89
+ return config.photoSteps[state.currentStepIndex];
90
+ }, [state.currentStepIndex, config.photoSteps]);
91
+
92
+ // Get current step data
93
+ const currentStepData = useMemo(() => {
94
+ if (state.currentStepIndex >= state.photoSteps.length) {
95
+ return null;
96
+ }
97
+ return state.photoSteps[state.currentStepIndex];
98
+ }, [state.currentStepIndex, state.photoSteps]);
99
+
100
+ // Check if current step is valid
101
+ const isCurrentStepValid = useMemo(() => {
102
+ if (!currentStepData || !currentStepConfig) {
103
+ return false;
104
+ }
105
+
106
+ // Check photo
107
+ if (!currentStepData.imageUri) {
108
+ return false;
109
+ }
110
+
111
+ // Check name if required
112
+ if (currentStepConfig.requireNameInput && !currentStepData.name) {
113
+ return false;
114
+ }
115
+
116
+ // Check validation if enabled
117
+ if (currentStepConfig.enableValidation && !currentStepData.isValid) {
118
+ return false;
119
+ }
120
+
121
+ return true;
122
+ }, [currentStepData, currentStepConfig]);
123
+
124
+ // Can go to next step
125
+ const canGoNext = useMemo(() => {
126
+ return isCurrentStepValid && !state.isProcessing;
127
+ }, [isCurrentStepValid, state.isProcessing]);
128
+
129
+ // Can go back
130
+ const canGoBack = useMemo(() => {
131
+ return (
132
+ state.currentStepIndex > 0 &&
133
+ config.behavior?.allowBack !== false &&
134
+ !state.isProcessing
135
+ );
136
+ }, [state.currentStepIndex, config.behavior?.allowBack, state.isProcessing]);
137
+
138
+ // Go to next step
139
+ const goNext = useCallback(() => {
140
+ if (!canGoNext) return;
141
+
142
+ setState((prev) => {
143
+ const nextIndex = prev.currentStepIndex + 1;
144
+
145
+ // If we've completed all photo steps, mark as complete
146
+ if (nextIndex >= config.photoSteps.length) {
147
+ const newState = {
148
+ ...prev,
149
+ isComplete: true,
150
+ };
151
+ onComplete?.(newState);
152
+ return newState;
153
+ }
154
+
155
+ // Move to next step
156
+ const newState = {
157
+ ...prev,
158
+ currentStepIndex: nextIndex,
159
+ };
160
+
161
+ // Notify step change
162
+ onStepChange?.(nextIndex, config.photoSteps[nextIndex]);
163
+
164
+ return newState;
165
+ });
166
+ }, [canGoNext, config.photoSteps, onComplete, onStepChange]);
167
+
168
+ // Go to previous step
169
+ const goBack = useCallback(() => {
170
+ if (!canGoBack) return;
171
+
172
+ setState((prev) => {
173
+ const prevIndex = prev.currentStepIndex - 1;
174
+ const newState = {
175
+ ...prev,
176
+ currentStepIndex: prevIndex,
177
+ isComplete: false,
178
+ };
179
+
180
+ // Notify step change
181
+ onStepChange?.(prevIndex, config.photoSteps[prevIndex]);
182
+
183
+ return newState;
184
+ });
185
+ }, [canGoBack, config.photoSteps, onStepChange]);
186
+
187
+ // Update photo
188
+ const updatePhoto = useCallback(
189
+ (imageUri: string, previewUrl?: string) => {
190
+ setState((prev) => {
191
+ const newPhotoSteps = [...prev.photoSteps];
192
+ newPhotoSteps[prev.currentStepIndex] = {
193
+ ...newPhotoSteps[prev.currentStepIndex],
194
+ imageUri,
195
+ previewUrl,
196
+ validationStatus: "pending",
197
+ };
198
+
199
+ return {
200
+ ...prev,
201
+ photoSteps: newPhotoSteps,
202
+ };
203
+ });
204
+ },
205
+ [],
206
+ );
207
+
208
+ // Update name
209
+ const updateName = useCallback((name: string) => {
210
+ setState((prev) => {
211
+ const newPhotoSteps = [...prev.photoSteps];
212
+ newPhotoSteps[prev.currentStepIndex] = {
213
+ ...newPhotoSteps[prev.currentStepIndex],
214
+ name,
215
+ };
216
+
217
+ return {
218
+ ...prev,
219
+ photoSteps: newPhotoSteps,
220
+ };
221
+ });
222
+ }, []);
223
+
224
+ // Update validation
225
+ const updateValidation = useCallback((isValid: boolean) => {
226
+ setState((prev) => {
227
+ const newPhotoSteps = [...prev.photoSteps];
228
+ newPhotoSteps[prev.currentStepIndex] = {
229
+ ...newPhotoSteps[prev.currentStepIndex],
230
+ isValid,
231
+ validationStatus: isValid ? "valid" : "invalid",
232
+ };
233
+
234
+ return {
235
+ ...prev,
236
+ photoSteps: newPhotoSteps,
237
+ };
238
+ });
239
+ }, []);
240
+
241
+ // Update text input
242
+ const updateTextInput = useCallback(
243
+ (text: string) => {
244
+ setState((prev) => {
245
+ if (!prev.textInput) return prev;
246
+
247
+ const minLength = config.textInputStep?.minLength ?? 0;
248
+ const maxLength = config.textInputStep?.maxLength ?? Infinity;
249
+ const isValid = text.length >= minLength && text.length <= maxLength;
250
+
251
+ return {
252
+ ...prev,
253
+ textInput: {
254
+ ...prev.textInput,
255
+ text,
256
+ isValid,
257
+ },
258
+ };
259
+ });
260
+ },
261
+ [config.textInputStep],
262
+ );
263
+
264
+ // Reset flow
265
+ const reset = useCallback(() => {
266
+ setState({
267
+ currentStepIndex: 0,
268
+ photoSteps: config.photoSteps.map((step) => ({
269
+ id: step.id,
270
+ imageUri: null,
271
+ previewUrl: undefined,
272
+ name: undefined,
273
+ isValid: undefined,
274
+ validationStatus: "pending",
275
+ })),
276
+ textInput: config.textInputStep
277
+ ? {
278
+ id: config.textInputStep.id,
279
+ text: "",
280
+ isValid: false,
281
+ }
282
+ : undefined,
283
+ isComplete: false,
284
+ isProcessing: false,
285
+ });
286
+ }, [config.photoSteps, config.textInputStep]);
287
+
288
+ // Complete flow
289
+ const complete = useCallback(() => {
290
+ setState((prev) => {
291
+ const newState = {
292
+ ...prev,
293
+ isComplete: true,
294
+ };
295
+ onComplete?.(newState);
296
+ return newState;
297
+ });
298
+ }, [onComplete]);
299
+
300
+ return {
301
+ state,
302
+ currentStepConfig,
303
+ currentStepData,
304
+ canGoNext,
305
+ canGoBack,
306
+ goNext,
307
+ goBack,
308
+ updatePhoto,
309
+ updateName,
310
+ updateValidation,
311
+ updateTextInput,
312
+ reset,
313
+ complete,
314
+ };
315
+ };
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Flow Configuration Types
3
+ * Configuration system for step-by-step generation flows
4
+ *
5
+ * @package @umituz/react-native-ai-generation-content
6
+ */
7
+
8
+ import type { StepHeaderConfig } from "@umituz/react-native-design-system";
9
+ import type { PhotoUploadCardConfig } from "@umituz/react-native-design-system";
10
+
11
+ /**
12
+ * Photo upload step configuration
13
+ */
14
+ export interface PhotoStepConfig {
15
+ /** Whether this step is enabled */
16
+ enabled: boolean;
17
+ /** Step order (1, 2, 3, etc.) */
18
+ order: number;
19
+ /** Step identifier */
20
+ id: string;
21
+ /** Header configuration */
22
+ header?: StepHeaderConfig;
23
+ /** Photo upload card configuration */
24
+ photoCard?: PhotoUploadCardConfig;
25
+ /** Whether name input is required */
26
+ requireNameInput?: boolean;
27
+ /** Whether photo validation is enabled */
28
+ enableValidation?: boolean;
29
+ /** Validation type */
30
+ validationType?: "none" | "face-detection" | "custom";
31
+ /** Whether to show photo tips */
32
+ showPhotoTips?: boolean;
33
+ /** Custom validation function */
34
+ customValidator?: (imageUri: string) => Promise<boolean>;
35
+ }
36
+
37
+ /**
38
+ * Text input step configuration
39
+ */
40
+ export interface TextInputStepConfig {
41
+ /** Whether this step is enabled */
42
+ enabled: boolean;
43
+ /** Step order */
44
+ order: number;
45
+ /** Step identifier */
46
+ id: string;
47
+ /** Header configuration */
48
+ header?: StepHeaderConfig;
49
+ /** Minimum character length */
50
+ minLength?: number;
51
+ /** Maximum character length */
52
+ maxLength?: number;
53
+ /** Placeholder text */
54
+ placeholder?: string;
55
+ /** Whether to show character counter */
56
+ showCharacterCount?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Preview step configuration
61
+ */
62
+ export interface PreviewStepConfig {
63
+ /** Whether this step is enabled */
64
+ enabled: boolean;
65
+ /** Step order */
66
+ order: number;
67
+ /** Step identifier */
68
+ id: string;
69
+ /** Header configuration */
70
+ header?: StepHeaderConfig;
71
+ /** Whether to show edit buttons */
72
+ allowEditing?: boolean;
73
+ /** Preview layout style */
74
+ layout?: "grid" | "list" | "carousel";
75
+ }
76
+
77
+ /**
78
+ * Complete flow configuration
79
+ */
80
+ export interface GenerationFlowConfig {
81
+ /** Photo upload steps (can be 1 or multiple) */
82
+ photoSteps: PhotoStepConfig[];
83
+ /** Text input step */
84
+ textInputStep?: TextInputStepConfig;
85
+ /** Preview step */
86
+ previewStep?: PreviewStepConfig;
87
+ /** Flow behavior */
88
+ behavior?: {
89
+ /** Whether to allow going back */
90
+ allowBack?: boolean;
91
+ /** Whether to show progress indicator */
92
+ showProgress?: boolean;
93
+ /** Whether to auto-advance after photo selection */
94
+ autoAdvance?: boolean;
95
+ /** Whether to require authentication */
96
+ requireAuth?: boolean;
97
+ /** Whether to check feature gate */
98
+ checkFeatureGate?: boolean;
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Default single photo flow configuration
104
+ */
105
+ export const DEFAULT_SINGLE_PHOTO_FLOW: GenerationFlowConfig = {
106
+ photoSteps: [
107
+ {
108
+ enabled: true,
109
+ order: 1,
110
+ id: "photo-1",
111
+ header: {
112
+ showStepIndicator: false,
113
+ titleAlignment: "center",
114
+ titleFontSize: 28,
115
+ subtitleFontSize: 16,
116
+ },
117
+ photoCard: {
118
+ aspectRatio: 1,
119
+ borderRadius: 28,
120
+ showValidationStatus: true,
121
+ allowChange: true,
122
+ borderStyle: "dashed",
123
+ },
124
+ requireNameInput: true,
125
+ enableValidation: false,
126
+ validationType: "none",
127
+ showPhotoTips: true,
128
+ },
129
+ ],
130
+ behavior: {
131
+ allowBack: false,
132
+ showProgress: false,
133
+ autoAdvance: false,
134
+ requireAuth: false,
135
+ checkFeatureGate: true,
136
+ },
137
+ };
138
+
139
+ /**
140
+ * Default dual photo flow configuration
141
+ */
142
+ export const DEFAULT_DUAL_PHOTO_FLOW: GenerationFlowConfig = {
143
+ photoSteps: [
144
+ {
145
+ enabled: true,
146
+ order: 1,
147
+ id: "partner-a",
148
+ header: {
149
+ showStepIndicator: true,
150
+ currentStep: 1,
151
+ totalSteps: 2,
152
+ titleAlignment: "center",
153
+ titleFontSize: 28,
154
+ subtitleFontSize: 16,
155
+ },
156
+ photoCard: {
157
+ aspectRatio: 1,
158
+ borderRadius: 28,
159
+ showValidationStatus: true,
160
+ allowChange: true,
161
+ borderStyle: "dashed",
162
+ },
163
+ requireNameInput: true,
164
+ enableValidation: false,
165
+ validationType: "none",
166
+ showPhotoTips: true,
167
+ },
168
+ {
169
+ enabled: true,
170
+ order: 2,
171
+ id: "partner-b",
172
+ header: {
173
+ showStepIndicator: true,
174
+ currentStep: 2,
175
+ totalSteps: 2,
176
+ titleAlignment: "center",
177
+ titleFontSize: 28,
178
+ subtitleFontSize: 16,
179
+ },
180
+ photoCard: {
181
+ aspectRatio: 1,
182
+ borderRadius: 28,
183
+ showValidationStatus: true,
184
+ allowChange: true,
185
+ borderStyle: "dashed",
186
+ },
187
+ requireNameInput: true,
188
+ enableValidation: false,
189
+ validationType: "none",
190
+ showPhotoTips: false,
191
+ },
192
+ ],
193
+ behavior: {
194
+ allowBack: true,
195
+ showProgress: true,
196
+ autoAdvance: false,
197
+ requireAuth: false,
198
+ checkFeatureGate: true,
199
+ },
200
+ };
201
+
202
+ /**
203
+ * Step data structure
204
+ */
205
+ export interface PhotoStepData {
206
+ /** Step identifier */
207
+ id: string;
208
+ /** Photo URI */
209
+ imageUri: string | null;
210
+ /** Preview URL */
211
+ previewUrl?: string;
212
+ /** Name/label for this photo */
213
+ name?: string;
214
+ /** Whether validation passed */
215
+ isValid?: boolean;
216
+ /** Validation status */
217
+ validationStatus?: "pending" | "validating" | "valid" | "invalid";
218
+ }
219
+
220
+ /**
221
+ * Text input step data
222
+ */
223
+ export interface TextInputStepData {
224
+ /** Step identifier */
225
+ id: string;
226
+ /** Text content */
227
+ text: string;
228
+ /** Whether text is valid */
229
+ isValid?: boolean;
230
+ }
231
+
232
+ /**
233
+ * Complete flow state
234
+ */
235
+ export interface GenerationFlowState {
236
+ /** Current step index */
237
+ currentStepIndex: number;
238
+ /** Photo steps data */
239
+ photoSteps: PhotoStepData[];
240
+ /** Text input data */
241
+ textInput?: TextInputStepData;
242
+ /** Whether flow is complete */
243
+ isComplete: boolean;
244
+ /** Whether currently processing */
245
+ isProcessing: boolean;
246
+ }