@umituz/react-native-ai-generation-content 1.28.7 → 1.29.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.28.7",
3
+ "version": "1.29.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",
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Indeterminate Progress Bar Component
3
+ * Pulses left to right to indicate ongoing progress without specific percentage
4
+ * Uses setInterval + state updates (not Animated API) for compatibility
5
+ */
6
+
7
+ import React, { useState, useEffect, useRef } from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+
10
+ export interface IndeterminateProgressBarProps {
11
+ readonly backgroundColor: string;
12
+ readonly fillColor: string;
13
+ }
14
+
15
+ export const IndeterminateProgressBar: React.FC<IndeterminateProgressBarProps> = ({
16
+ backgroundColor,
17
+ fillColor,
18
+ }) => {
19
+ const [position, setPosition] = useState(0);
20
+ const directionRef = useRef(1);
21
+
22
+ useEffect(() => {
23
+ const interval = setInterval(() => {
24
+ setPosition((prev) => {
25
+ const next = prev + directionRef.current * 2;
26
+ if (next >= 70) {
27
+ directionRef.current = -1;
28
+ return 70;
29
+ }
30
+ if (next <= 0) {
31
+ directionRef.current = 1;
32
+ return 0;
33
+ }
34
+ return next;
35
+ });
36
+ }, 30);
37
+
38
+ return () => clearInterval(interval);
39
+ }, []);
40
+
41
+ return (
42
+ <View style={[styles.progressBar, { backgroundColor }]}>
43
+ <View
44
+ style={[
45
+ styles.progressFill,
46
+ {
47
+ backgroundColor: fillColor,
48
+ left: `${position}%`,
49
+ },
50
+ ]}
51
+ />
52
+ </View>
53
+ );
54
+ };
55
+
56
+ const styles = StyleSheet.create({
57
+ progressBar: {
58
+ height: 8,
59
+ borderRadius: 4,
60
+ overflow: "hidden",
61
+ position: "relative",
62
+ },
63
+ progressFill: {
64
+ height: "100%",
65
+ borderRadius: 4,
66
+ position: "absolute",
67
+ width: "30%",
68
+ },
69
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * useGenerationPhase Hook
3
+ * Derives generation phase from elapsed time for UX feedback
4
+ *
5
+ * Best Practice: Since actual API progress is unknown (FAL only returns IN_QUEUE/IN_PROGRESS),
6
+ * we use time-based phases to provide meaningful feedback to users.
7
+ */
8
+
9
+ import { useState, useEffect, useRef } from "react";
10
+
11
+ export type GenerationPhase = "queued" | "processing" | "finalizing";
12
+
13
+ interface UseGenerationPhaseOptions {
14
+ /** Time in ms before transitioning from "queued" to "processing" (default: 5000) */
15
+ readonly queuedDuration?: number;
16
+ }
17
+
18
+ /**
19
+ * Hook to derive generation phase from elapsed time
20
+ * - 0-5s: "queued" (Waiting in queue...)
21
+ * - 5s+: "processing" (Generating your content...)
22
+ *
23
+ * Note: "finalizing" phase is not time-based, it should be set when
24
+ * the API reports completion (not implemented here as it requires status callback)
25
+ */
26
+ export function useGenerationPhase(options?: UseGenerationPhaseOptions): GenerationPhase {
27
+ const { queuedDuration = 5000 } = options ?? {};
28
+
29
+ const [phase, setPhase] = useState<GenerationPhase>("queued");
30
+ const startTimeRef = useRef(Date.now());
31
+
32
+ useEffect(() => {
33
+ const interval = setInterval(() => {
34
+ const elapsed = Date.now() - startTimeRef.current;
35
+
36
+ if (elapsed < queuedDuration) {
37
+ setPhase("queued");
38
+ } else {
39
+ setPhase("processing");
40
+ }
41
+ }, 1000);
42
+
43
+ return () => clearInterval(interval);
44
+ }, [queuedDuration]);
45
+
46
+ return phase;
47
+ }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Generic Generating Screen
3
- * Shows progress while AI generates content
4
- * Uses scenario-specific messages with fallback to generic messages
3
+ * Shows indeterminate progress while AI generates content
4
+ * Uses status messages instead of fake percentages (UX best practice)
5
5
  */
6
6
 
7
7
  import React, { useMemo } from "react";
8
8
  import { View, StyleSheet, ActivityIndicator } from "react-native";
9
9
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
10
+ import { useGenerationPhase } from "../hooks/useGenerationPhase";
11
+ import { IndeterminateProgressBar } from "../components/IndeterminateProgressBar";
10
12
 
11
13
  export interface GeneratingScreenProps {
12
14
  readonly progress: number;
@@ -25,16 +27,17 @@ export interface GeneratingScreenProps {
25
27
  }
26
28
 
27
29
  export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
28
- progress,
30
+ progress: _progress,
29
31
  scenario,
30
32
  t,
31
33
  onCancel: _onCancel,
32
34
  }) => {
33
35
  const tokens = useAppDesignTokens();
36
+ const phase = useGenerationPhase();
34
37
 
35
38
  if (typeof __DEV__ !== "undefined" && __DEV__) {
36
39
  console.log("[GeneratingScreen] Rendering", {
37
- progress,
40
+ phase,
38
41
  scenarioId: scenario?.id,
39
42
  });
40
43
  }
@@ -48,6 +51,19 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
48
51
  };
49
52
  }, [scenario, t]);
50
53
 
54
+ const statusMessage = useMemo(() => {
55
+ switch (phase) {
56
+ case "queued":
57
+ return t("generator.status.queued") || "Waiting in queue...";
58
+ case "processing":
59
+ return t("generator.status.processing") || "Generating your content...";
60
+ case "finalizing":
61
+ return t("generator.status.finalizing") || "Almost done...";
62
+ default:
63
+ return messages.waitMessage;
64
+ }
65
+ }, [phase, t, messages.waitMessage]);
66
+
51
67
  return (
52
68
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
53
69
  <View style={styles.content}>
@@ -58,35 +74,22 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
58
74
  </AtomicText>
59
75
 
60
76
  <AtomicText type="bodyMedium" style={[styles.message, { color: tokens.colors.textSecondary }]}>
61
- {messages.waitMessage}
77
+ {statusMessage}
62
78
  </AtomicText>
63
79
 
64
- {/* Progress Bar */}
65
80
  <View style={styles.progressContainer}>
66
- <View style={[styles.progressBar, { backgroundColor: tokens.colors.surfaceVariant }]}>
67
- <View
68
- style={[
69
- styles.progressFill,
70
- {
71
- backgroundColor: tokens.colors.primary,
72
- width: `${Math.min(100, Math.max(0, progress))}%`,
73
- },
74
- ]}
75
- />
76
- </View>
77
- <AtomicText type="bodySmall" style={[styles.progressText, { color: tokens.colors.textSecondary }]}>
78
- {Math.round(progress)}%
79
- </AtomicText>
81
+ <IndeterminateProgressBar
82
+ backgroundColor={tokens.colors.surfaceVariant}
83
+ fillColor={tokens.colors.primary}
84
+ />
80
85
  </View>
81
86
 
82
- {/* Scenario Info */}
83
87
  {scenario && (
84
88
  <AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
85
89
  {scenario.title || scenario.id}
86
90
  </AtomicText>
87
91
  )}
88
92
 
89
- {/* Hint */}
90
93
  <AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
91
94
  {messages.hint}
92
95
  </AtomicText>
@@ -117,19 +120,6 @@ const styles = StyleSheet.create({
117
120
  progressContainer: {
118
121
  width: "100%",
119
122
  marginTop: 24,
120
- gap: 8,
121
- },
122
- progressBar: {
123
- height: 8,
124
- borderRadius: 4,
125
- overflow: "hidden",
126
- },
127
- progressFill: {
128
- height: "100%",
129
- borderRadius: 4,
130
- },
131
- progressText: {
132
- textAlign: "center",
133
123
  },
134
124
  hint: {
135
125
  textAlign: "center",