@umituz/react-native-ai-generation-content 1.33.2 → 1.34.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.33.2",
3
+ "version": "1.34.0",
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",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Video Generation Executor
3
- * Generic executor for all video generation features
3
+ * Generic executor for all video generation features (provider-agnostic)
4
4
  */
5
5
 
6
6
  import type {
@@ -41,16 +41,12 @@ export class VideoExecutor
41
41
  return { success: false, error: "AI provider not initialized" };
42
42
  }
43
43
 
44
- options?.onProgress?.(5);
45
-
46
44
  const modelInput = this.buildModelInput(input);
47
45
 
48
46
  if (typeof __DEV__ !== "undefined" && __DEV__) {
49
47
  console.log("[VideoExecutor] Model input prepared");
50
48
  }
51
49
 
52
- options?.onProgress?.(10);
53
-
54
50
  const result = await provider.subscribe(model, modelInput, {
55
51
  timeoutMs: options?.timeoutMs ?? 300000,
56
52
  onQueueUpdate: (status) => {
@@ -101,7 +97,7 @@ export class VideoExecutor
101
97
  }
102
98
  }
103
99
 
104
- private buildModelInput(input: VideoGenerationInput) {
100
+ private buildModelInput(input: VideoGenerationInput): Record<string, unknown> {
105
101
  const { sourceImageBase64, targetImageBase64, prompt } = input;
106
102
 
107
103
  const formatBase64 = (base64: string): string => {
@@ -66,6 +66,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
66
66
  const [currentCreation, setCurrentCreation] = useState<Creation | null>(null);
67
67
  const [showRatingPicker, setShowRatingPicker] = useState(false);
68
68
  const [hasRated, setHasRated] = useState(false);
69
+ const [isGeneratingDismissed, setIsGeneratingDismissed] = useState(false);
69
70
  const prevStepIdRef = useRef<string | undefined>(undefined);
70
71
 
71
72
  const repository = useMemo(() => createCreationsRepository("creations"), []);
@@ -133,10 +134,31 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
133
134
  if (prevStepIdRef.current !== currentStepId) {
134
135
  prevStepIdRef.current = currentStepId;
135
136
  onStepChange(currentStep.id, currentStep.type);
137
+ // Reset dismissed state when entering generating step
138
+ if (currentStep.type === StepType.GENERATING) {
139
+ setIsGeneratingDismissed(false);
140
+ }
136
141
  }
137
142
  }
138
143
  }, [currentStep, currentStepIndex, onStepChange]);
139
144
 
145
+ // Handle dismiss generating - go back but generation continues in background
146
+ const handleDismissGenerating = useCallback(() => {
147
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
148
+ console.log("[GenericWizardFlow] Dismissing generating screen - generation continues in background");
149
+ }
150
+ setIsGeneratingDismissed(true);
151
+ // Show alert that generation continues
152
+ alert.show(
153
+ AlertType.INFO,
154
+ AlertMode.TOAST,
155
+ t("generator.backgroundTitle"),
156
+ t("generator.backgroundMessage")
157
+ );
158
+ // Go back to previous step (or close)
159
+ onBack?.();
160
+ }, [alert, t, onBack]);
161
+
140
162
  const handleBack = useCallback(() => {
141
163
  if (currentStepIndex === 0) {
142
164
  onBack?.();
@@ -209,6 +231,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
209
231
  onShare={handleShare}
210
232
  onRate={handleOpenRatingPicker}
211
233
  onTryAgain={onTryAgain}
234
+ onDismissGenerating={handleDismissGenerating}
212
235
  t={t}
213
236
  renderPreview={renderPreview}
214
237
  renderGenerating={renderGenerating}
@@ -28,6 +28,7 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
28
28
  onShare,
29
29
  onRate,
30
30
  onTryAgain,
31
+ onDismissGenerating,
31
32
  t,
32
33
  renderPreview,
33
34
  renderGenerating,
@@ -61,7 +62,12 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
61
62
  case StepType.GENERATING: {
62
63
  if (renderGenerating) return renderGenerating(generationProgress);
63
64
  return (
64
- <GeneratingScreen progress={generationProgress} scenario={scenario} t={t} />
65
+ <GeneratingScreen
66
+ progress={generationProgress}
67
+ scenario={scenario}
68
+ t={t}
69
+ onDismiss={onDismissGenerating}
70
+ />
65
71
  );
66
72
  }
67
73
 
@@ -18,6 +18,8 @@ export interface WizardStepRendererProps {
18
18
  readonly onShare: () => void;
19
19
  readonly onRate?: () => void;
20
20
  readonly onTryAgain?: () => void;
21
+ /** Called when user dismisses generating screen - generation continues in background */
22
+ readonly onDismissGenerating?: () => void;
21
23
  readonly t: (key: string) => string;
22
24
  readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
23
25
  readonly renderGenerating?: (progress: number) => React.ReactElement | null;
@@ -2,11 +2,12 @@
2
2
  * Generic Generating Screen
3
3
  * Shows indeterminate progress while AI generates content
4
4
  * Uses status messages instead of fake percentages (UX best practice)
5
+ * Supports background generation - user can dismiss and generation continues
5
6
  */
6
7
 
7
8
  import React, { useMemo } from "react";
8
- import { View, StyleSheet, ActivityIndicator } from "react-native";
9
- import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
+ import { View, StyleSheet, ActivityIndicator, TouchableOpacity } from "react-native";
10
+ import { useAppDesignTokens, AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
10
11
  import { useGenerationPhase } from "../hooks/useGenerationPhase";
11
12
  import { IndeterminateProgressBar } from "../components/IndeterminateProgressBar";
12
13
 
@@ -20,17 +21,19 @@ export interface GeneratingScreenProps {
20
21
  readonly title?: string;
21
22
  readonly waitMessage?: string;
22
23
  readonly hint?: string;
24
+ readonly backgroundHint?: string;
23
25
  };
24
26
  };
25
27
  readonly t: (key: string) => string;
26
- readonly onCancel?: () => void;
28
+ /** Called when user dismisses the screen - generation continues in background */
29
+ readonly onDismiss?: () => void;
27
30
  }
28
31
 
29
32
  export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
30
33
  progress: _progress,
31
34
  scenario,
32
35
  t,
33
- onCancel: _onCancel,
36
+ onDismiss,
34
37
  }) => {
35
38
  const tokens = useAppDesignTokens();
36
39
  const phase = useGenerationPhase();
@@ -48,6 +51,7 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
48
51
  title: custom?.title || t("generator.title"),
49
52
  waitMessage: custom?.waitMessage || t("generator.waitMessage"),
50
53
  hint: custom?.hint || t("generator.hint"),
54
+ backgroundHint: custom?.backgroundHint || t("generator.backgroundHint"),
51
55
  };
52
56
  }, [scenario, t]);
53
57
 
@@ -66,6 +70,17 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
66
70
 
67
71
  return (
68
72
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
73
+ {/* Close button - allows user to dismiss and continue in background */}
74
+ {onDismiss && (
75
+ <TouchableOpacity
76
+ style={styles.closeButton}
77
+ onPress={onDismiss}
78
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
79
+ >
80
+ <AtomicIcon name="close" size="md" customColor={tokens.colors.textSecondary} />
81
+ </TouchableOpacity>
82
+ )}
83
+
69
84
  <View style={styles.content}>
70
85
  <ActivityIndicator size="large" color={tokens.colors.primary} />
71
86
 
@@ -93,6 +108,15 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
93
108
  <AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
94
109
  {messages.hint}
95
110
  </AtomicText>
111
+
112
+ {/* Background hint - tap to dismiss */}
113
+ {onDismiss && (
114
+ <TouchableOpacity style={styles.backgroundHintButton} onPress={onDismiss}>
115
+ <AtomicText type="bodySmall" style={[styles.backgroundHint, { color: tokens.colors.primary }]}>
116
+ {messages.backgroundHint}
117
+ </AtomicText>
118
+ </TouchableOpacity>
119
+ )}
96
120
  </View>
97
121
  </View>
98
122
  );
@@ -104,6 +128,13 @@ const styles = StyleSheet.create({
104
128
  justifyContent: "center",
105
129
  alignItems: "center",
106
130
  },
131
+ closeButton: {
132
+ position: "absolute",
133
+ top: 16,
134
+ right: 16,
135
+ zIndex: 10,
136
+ padding: 8,
137
+ },
107
138
  content: {
108
139
  width: "80%",
109
140
  maxWidth: 400,
@@ -125,4 +156,13 @@ const styles = StyleSheet.create({
125
156
  textAlign: "center",
126
157
  marginTop: 8,
127
158
  },
159
+ backgroundHintButton: {
160
+ marginTop: 24,
161
+ paddingVertical: 12,
162
+ paddingHorizontal: 16,
163
+ },
164
+ backgroundHint: {
165
+ textAlign: "center",
166
+ fontWeight: "600",
167
+ },
128
168
  });
@@ -5,7 +5,7 @@
5
5
  * Note: No Modal wrapper - shows fullscreen progress when processing (FutureUS pattern)
6
6
  */
7
7
 
8
- import React, { useCallback } from "react";
8
+ import React, { useCallback, useState, useEffect } from "react";
9
9
  import { View, ScrollView, StyleSheet } from "react-native";
10
10
  import {
11
11
  useAppDesignTokens,
@@ -31,6 +31,19 @@ export const DualImageFeatureLayout: React.FC<DualImageFeatureLayoutProps> = ({
31
31
  const { width: screenWidth, horizontalPadding } = useResponsive();
32
32
  const imageSize = screenWidth - horizontalPadding * 2;
33
33
 
34
+ // Background generation: user can dismiss progress but generation continues
35
+ const [isProgressDismissed, setIsProgressDismissed] = useState(false);
36
+
37
+ useEffect(() => {
38
+ if (feature.isProcessing) {
39
+ setIsProgressDismissed(false);
40
+ }
41
+ }, [feature.isProcessing]);
42
+
43
+ const handleDismissProgress = useCallback(() => {
44
+ setIsProgressDismissed(true);
45
+ }, []);
46
+
34
47
  const handleProcess = useCallback(() => {
35
48
  void feature.process();
36
49
  }, [feature]);
@@ -48,7 +61,8 @@ export const DualImageFeatureLayout: React.FC<DualImageFeatureLayoutProps> = ({
48
61
  }, [feature]);
49
62
 
50
63
  // Processing view - fullscreen (FutureUS pattern, no Modal)
51
- if (feature.isProcessing) {
64
+ // Show only if processing AND not dismissed
65
+ if (feature.isProcessing && !isProgressDismissed) {
52
66
  return (
53
67
  <View
54
68
  style={[
@@ -63,6 +77,7 @@ export const DualImageFeatureLayout: React.FC<DualImageFeatureLayoutProps> = ({
63
77
  message={modalTranslations.message}
64
78
  hint={modalTranslations.hint}
65
79
  backgroundHint={modalTranslations.backgroundHint}
80
+ onClose={handleDismissProgress}
66
81
  backgroundColor={tokens.colors.surface}
67
82
  textColor={tokens.colors.textPrimary}
68
83
  progressColor={tokens.colors.primary}
@@ -5,7 +5,7 @@
5
5
  * Note: No Modal wrapper - shows fullscreen progress when processing (FutureUS pattern)
6
6
  */
7
7
 
8
- import React, { useCallback } from "react";
8
+ import React, { useCallback, useState, useEffect } from "react";
9
9
  import { View, ScrollView, StyleSheet } from "react-native";
10
10
  import {
11
11
  useAppDesignTokens,
@@ -27,6 +27,19 @@ export const DualImageVideoFeatureLayout: React.FC<DualImageVideoFeatureLayoutPr
27
27
  }) => {
28
28
  const tokens = useAppDesignTokens();
29
29
 
30
+ // Background generation: user can dismiss progress but generation continues
31
+ const [isProgressDismissed, setIsProgressDismissed] = useState(false);
32
+
33
+ useEffect(() => {
34
+ if (feature.isProcessing) {
35
+ setIsProgressDismissed(false);
36
+ }
37
+ }, [feature.isProcessing]);
38
+
39
+ const handleDismissProgress = useCallback(() => {
40
+ setIsProgressDismissed(true);
41
+ }, []);
42
+
30
43
  const handleProcess = useCallback(() => {
31
44
  void feature.process();
32
45
  }, [feature]);
@@ -44,7 +57,8 @@ export const DualImageVideoFeatureLayout: React.FC<DualImageVideoFeatureLayoutPr
44
57
  }, [feature]);
45
58
 
46
59
  // Processing view - fullscreen (FutureUS pattern, no Modal)
47
- if (feature.isProcessing) {
60
+ // Show only if processing AND not dismissed
61
+ if (feature.isProcessing && !isProgressDismissed) {
48
62
  return (
49
63
  <View
50
64
  style={[
@@ -59,6 +73,7 @@ export const DualImageVideoFeatureLayout: React.FC<DualImageVideoFeatureLayoutPr
59
73
  message={modalTranslations.message}
60
74
  hint={modalTranslations.hint}
61
75
  backgroundHint={modalTranslations.backgroundHint}
76
+ onClose={handleDismissProgress}
62
77
  backgroundColor={tokens.colors.surface}
63
78
  textColor={tokens.colors.textPrimary}
64
79
  progressColor={tokens.colors.primary}
@@ -5,7 +5,7 @@
5
5
  * Note: No Modal wrapper - shows fullscreen progress when processing (FutureUS pattern)
6
6
  */
7
7
 
8
- import React, { useCallback } from "react";
8
+ import React, { useCallback, useState, useEffect } from "react";
9
9
  import { View, ScrollView, StyleSheet } from "react-native";
10
10
  import {
11
11
  useAppDesignTokens,
@@ -32,6 +32,20 @@ export const SingleImageFeatureLayout: React.FC<SingleImageFeatureLayoutProps> =
32
32
  const { width: screenWidth, horizontalPadding } = useResponsive();
33
33
  const imageSize = screenWidth - horizontalPadding * 2;
34
34
 
35
+ // Background generation: user can dismiss progress but generation continues
36
+ const [isProgressDismissed, setIsProgressDismissed] = useState(false);
37
+
38
+ // Reset dismissed state when processing starts
39
+ useEffect(() => {
40
+ if (feature.isProcessing) {
41
+ setIsProgressDismissed(false);
42
+ }
43
+ }, [feature.isProcessing]);
44
+
45
+ const handleDismissProgress = useCallback(() => {
46
+ setIsProgressDismissed(true);
47
+ }, []);
48
+
35
49
  const handleProcess = useCallback(() => {
36
50
  void feature.process();
37
51
  }, [feature]);
@@ -45,7 +59,8 @@ export const SingleImageFeatureLayout: React.FC<SingleImageFeatureLayoutProps> =
45
59
  }, [feature]);
46
60
 
47
61
  // Processing view - fullscreen (FutureUS pattern, no Modal)
48
- if (feature.isProcessing) {
62
+ // Show only if processing AND not dismissed (user can dismiss and generation continues)
63
+ if (feature.isProcessing && !isProgressDismissed) {
49
64
  return (
50
65
  <View
51
66
  style={[
@@ -60,6 +75,7 @@ export const SingleImageFeatureLayout: React.FC<SingleImageFeatureLayoutProps> =
60
75
  message={modalTranslations.message}
61
76
  hint={modalTranslations.hint}
62
77
  backgroundHint={modalTranslations.backgroundHint}
78
+ onClose={handleDismissProgress}
63
79
  backgroundColor={tokens.colors.surface}
64
80
  textColor={tokens.colors.textPrimary}
65
81
  progressColor={tokens.colors.primary}
@@ -6,7 +6,7 @@
6
6
  * Note: No Modal wrapper - shows fullscreen progress when processing (FutureUS pattern)
7
7
  */
8
8
 
9
- import React, { useCallback } from "react";
9
+ import React, { useCallback, useState, useEffect } from "react";
10
10
  import { View, ScrollView, StyleSheet } from "react-native";
11
11
  import {
12
12
  useAppDesignTokens,
@@ -32,6 +32,19 @@ export const SingleImageWithPromptFeatureLayout: React.FC<SingleImageWithPromptF
32
32
  const { width: screenWidth, horizontalPadding } = useResponsive();
33
33
  const imageSize = screenWidth - horizontalPadding * 2;
34
34
 
35
+ // Background generation: user can dismiss progress but generation continues
36
+ const [isProgressDismissed, setIsProgressDismissed] = useState(false);
37
+
38
+ useEffect(() => {
39
+ if (feature.isProcessing) {
40
+ setIsProgressDismissed(false);
41
+ }
42
+ }, [feature.isProcessing]);
43
+
44
+ const handleDismissProgress = useCallback(() => {
45
+ setIsProgressDismissed(true);
46
+ }, []);
47
+
35
48
  const handleProcess = useCallback(() => {
36
49
  void feature.process();
37
50
  }, [feature]);
@@ -52,7 +65,8 @@ export const SingleImageWithPromptFeatureLayout: React.FC<SingleImageWithPromptF
52
65
  );
53
66
 
54
67
  // Processing view - fullscreen (FutureUS pattern, no Modal)
55
- if (feature.isProcessing) {
68
+ // Show only if processing AND not dismissed
69
+ if (feature.isProcessing && !isProgressDismissed) {
56
70
  return (
57
71
  <View
58
72
  style={[
@@ -67,6 +81,7 @@ export const SingleImageWithPromptFeatureLayout: React.FC<SingleImageWithPromptF
67
81
  message={modalTranslations.message}
68
82
  hint={modalTranslations.hint}
69
83
  backgroundHint={modalTranslations.backgroundHint}
84
+ onClose={handleDismissProgress}
70
85
  backgroundColor={tokens.colors.surface}
71
86
  textColor={tokens.colors.textPrimary}
72
87
  progressColor={tokens.colors.primary}