@umituz/react-native-ai-generation-content 1.36.2 → 1.37.1

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.
Files changed (22) hide show
  1. package/package.json +2 -2
  2. package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +124 -199
  3. package/src/domains/creations/presentation/components/GalleryResultPreview.tsx +88 -0
  4. package/src/domains/creations/presentation/hooks/useGalleryCallbacks.ts +127 -0
  5. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +37 -123
  6. package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +5 -231
  7. package/src/domains/generation/wizard/presentation/components/WizardContinueButton.tsx +73 -0
  8. package/src/domains/generation/wizard/presentation/components/WizardFlowContent.tsx +181 -0
  9. package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +18 -134
  10. package/src/domains/generation/wizard/presentation/components/step-renderers/renderPhotoUploadStep.tsx +52 -0
  11. package/src/domains/generation/wizard/presentation/components/step-renderers/renderSelectionStep.tsx +59 -0
  12. package/src/domains/generation/wizard/presentation/components/step-renderers/renderTextInputStep.tsx +62 -0
  13. package/src/domains/generation/wizard/presentation/hooks/useWizardFlowHandlers.ts +133 -0
  14. package/src/domains/generation/wizard/presentation/screens/GenericPhotoUploadScreen.tsx +15 -83
  15. package/src/domains/generation/wizard/presentation/screens/SelectionScreen.tsx +55 -134
  16. package/src/domains/result-preview/presentation/components/GenerationErrorScreen.tsx +19 -122
  17. package/src/domains/result-preview/presentation/hooks/useResultActions.ts +16 -131
  18. package/src/domains/scenarios/presentation/components/ScenarioContinueButton.tsx +76 -0
  19. package/src/domains/scenarios/presentation/hooks/useHierarchicalScenarios.ts +70 -0
  20. package/src/domains/scenarios/presentation/screens/HierarchicalScenarioListScreen.tsx +37 -170
  21. package/src/presentation/hooks/generation/moderation-handler.ts +77 -0
  22. package/src/presentation/hooks/generation/orchestrator.ts +33 -125
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.36.2",
3
+ "version": "1.37.1",
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",
@@ -66,7 +66,7 @@
66
66
  "@types/react": "~19.1.10",
67
67
  "@typescript-eslint/eslint-plugin": "^8.0.0",
68
68
  "@typescript-eslint/parser": "^8.0.0",
69
- "@umituz/react-native-design-system": "^2.9.44",
69
+ "@umituz/react-native-design-system": "^2.9.50",
70
70
  "@umituz/react-native-firebase": "^1.13.87",
71
71
  "@umituz/react-native-localization": "*",
72
72
  "@umituz/react-native-subscription": "*",
@@ -5,216 +5,141 @@ import type { Creation, CreationDocument } from "../../domain/entities/Creation"
5
5
 
6
6
  declare const __DEV__: boolean;
7
7
 
8
+ const UPDATABLE_FIELDS = [
9
+ "metadata", "isShared", "uri", "type", "prompt", "status",
10
+ "output", "rating", "ratedAt", "isFavorite", "deletedAt",
11
+ ] as const;
12
+
8
13
  /**
9
14
  * Handles write operations for creations
10
- * Single Responsibility: Write operations
11
15
  */
12
16
  export class CreationsWriter {
13
- constructor(private readonly pathResolver: FirestorePathResolver) { }
14
-
15
- async create(userId: string, creation: Creation): Promise<void> {
16
- if (typeof __DEV__ !== "undefined" && __DEV__) {
17
-
18
- console.log("[CreationsWriter] create() start", { userId, creationId: creation.id });
19
- }
20
-
21
- const docRef = this.pathResolver.getDocRef(userId, creation.id);
22
- if (!docRef) throw new Error("Firestore not initialized");
23
-
24
- const data: CreationDocument = {
25
- type: creation.type,
26
- uri: creation.uri,
27
- createdAt: creation.createdAt,
28
- metadata: creation.metadata || {},
29
- isShared: creation.isShared || false,
30
- isFavorite: creation.isFavorite || false,
31
- ...(creation.status !== undefined && { status: creation.status }),
32
- ...(creation.output !== undefined && { output: creation.output }),
33
- ...(creation.prompt !== undefined && { prompt: creation.prompt }),
34
- };
35
-
36
- try {
37
- await setDoc(docRef, data);
38
- if (typeof __DEV__ !== "undefined" && __DEV__) {
39
-
40
- console.log("[CreationsWriter] create() success", { creationId: creation.id });
41
- }
42
- } catch (error) {
43
- if (typeof __DEV__ !== "undefined" && __DEV__) {
44
-
45
- console.error("[CreationsWriter] create() error", error);
46
- }
47
- throw error;
48
- }
49
- }
17
+ constructor(private readonly pathResolver: FirestorePathResolver) {}
50
18
 
51
- async update(
52
- userId: string,
53
- id: string,
54
- updates: Partial<Creation>,
55
- ): Promise<boolean> {
56
- if (__DEV__) {
57
-
58
- console.log("[CreationsRepository] update()", { userId, id, updates });
59
- }
60
-
61
- const docRef = this.pathResolver.getDocRef(userId, id);
62
- if (!docRef) return false;
63
-
64
- try {
65
- const updateData: Record<string, unknown> = {};
66
-
67
- if (updates.metadata !== undefined) {
68
- updateData.metadata = updates.metadata;
69
- }
70
- if (updates.isShared !== undefined) {
71
- updateData.isShared = updates.isShared;
72
- }
73
- if (updates.uri !== undefined) {
74
- updateData.uri = updates.uri;
75
- }
76
- if (updates.type !== undefined) {
77
- updateData.type = updates.type;
78
- }
79
- if (updates.prompt !== undefined) {
80
- updateData.prompt = updates.prompt;
81
- }
82
- if (updates.status !== undefined) {
83
- updateData.status = updates.status;
84
- }
85
- if (updates.output !== undefined) {
86
- updateData.output = updates.output;
87
- }
88
- if (updates.rating !== undefined) {
89
- updateData.rating = updates.rating;
90
- }
91
- if (updates.ratedAt !== undefined) {
92
- updateData.ratedAt = updates.ratedAt;
93
- }
94
- if (updates.isFavorite !== undefined) {
95
- updateData.isFavorite = updates.isFavorite;
96
- }
97
- if (updates.deletedAt !== undefined) {
98
- updateData.deletedAt = updates.deletedAt;
99
- }
100
-
101
- await updateDoc(docRef, updateData);
102
- return true;
103
- } catch (error) {
104
- if (__DEV__) {
105
-
106
- console.error("[CreationsRepository] update() ERROR", error);
107
- }
108
- return false;
109
- }
19
+ async create(userId: string, creation: Creation): Promise<void> {
20
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
21
+ console.log("[CreationsWriter] create()", { userId, creationId: creation.id });
110
22
  }
111
23
 
112
- async delete(userId: string, creationId: string): Promise<boolean> {
113
- const docRef = this.pathResolver.getDocRef(userId, creationId);
114
- if (!docRef) return false;
115
-
116
- try {
117
- // Soft delete: set deletedAt timestamp instead of hard delete
118
- await updateDoc(docRef, { deletedAt: new Date() });
119
- return true;
120
- } catch {
121
- return false;
122
- }
24
+ const docRef = this.pathResolver.getDocRef(userId, creation.id);
25
+ if (!docRef) throw new Error("Firestore not initialized");
26
+
27
+ const data: CreationDocument = {
28
+ type: creation.type,
29
+ uri: creation.uri,
30
+ createdAt: creation.createdAt,
31
+ metadata: creation.metadata || {},
32
+ isShared: creation.isShared || false,
33
+ isFavorite: creation.isFavorite || false,
34
+ ...(creation.status !== undefined && { status: creation.status }),
35
+ ...(creation.output !== undefined && { output: creation.output }),
36
+ ...(creation.prompt !== undefined && { prompt: creation.prompt }),
37
+ };
38
+
39
+ try {
40
+ await setDoc(docRef, data);
41
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CreationsWriter] create() success");
42
+ } catch (error) {
43
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[CreationsWriter] create() error", error);
44
+ throw error;
123
45
  }
124
-
125
- async hardDelete(userId: string, creationId: string): Promise<boolean> {
126
- const docRef = this.pathResolver.getDocRef(userId, creationId);
127
- if (!docRef) return false;
128
-
129
- try {
130
- await deleteDoc(docRef);
131
- return true;
132
- } catch {
133
- return false;
134
- }
46
+ }
47
+
48
+ async update(userId: string, id: string, updates: Partial<Creation>): Promise<boolean> {
49
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CreationsWriter] update()", { userId, id });
50
+
51
+ const docRef = this.pathResolver.getDocRef(userId, id);
52
+ if (!docRef) return false;
53
+
54
+ try {
55
+ const updateData: Record<string, unknown> = {};
56
+ for (const field of UPDATABLE_FIELDS) {
57
+ if (updates[field] !== undefined) updateData[field] = updates[field];
58
+ }
59
+ await updateDoc(docRef, updateData);
60
+ return true;
61
+ } catch (error) {
62
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[CreationsWriter] update() error", error);
63
+ return false;
135
64
  }
136
-
137
- async restore(userId: string, creationId: string): Promise<boolean> {
138
- const docRef = this.pathResolver.getDocRef(userId, creationId);
139
- if (!docRef) return false;
140
-
141
- try {
142
- // Remove deletedAt to restore the creation
143
- await updateDoc(docRef, { deletedAt: null });
144
- return true;
145
- } catch {
146
- return false;
147
- }
65
+ }
66
+
67
+ async delete(userId: string, creationId: string): Promise<boolean> {
68
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
69
+ if (!docRef) return false;
70
+ try {
71
+ await updateDoc(docRef, { deletedAt: new Date() });
72
+ return true;
73
+ } catch {
74
+ return false;
148
75
  }
149
-
150
- async updateShared(
151
- userId: string,
152
- creationId: string,
153
- isShared: boolean,
154
- ): Promise<boolean> {
155
- const docRef = this.pathResolver.getDocRef(userId, creationId);
156
- if (!docRef) return false;
157
-
158
- try {
159
- await updateDoc(docRef, { isShared });
160
- return true;
161
- } catch {
162
- return false;
163
- }
76
+ }
77
+
78
+ async hardDelete(userId: string, creationId: string): Promise<boolean> {
79
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
80
+ if (!docRef) return false;
81
+ try {
82
+ await deleteDoc(docRef);
83
+ return true;
84
+ } catch {
85
+ return false;
164
86
  }
165
-
166
- async updateFavorite(
167
- userId: string,
168
- creationId: string,
169
- isFavorite: boolean,
170
- ): Promise<boolean> {
171
- const docRef = this.pathResolver.getDocRef(userId, creationId);
172
- if (!docRef) return false;
173
-
174
- try {
175
- await updateDoc(docRef, { isFavorite });
176
- return true;
177
- } catch {
178
- return false;
179
- }
87
+ }
88
+
89
+ async restore(userId: string, creationId: string): Promise<boolean> {
90
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
91
+ if (!docRef) return false;
92
+ try {
93
+ await updateDoc(docRef, { deletedAt: null });
94
+ return true;
95
+ } catch {
96
+ return false;
180
97
  }
181
-
182
- async rate(
183
- userId: string,
184
- creationId: string,
185
- rating: number,
186
- description?: string,
187
- ): Promise<boolean> {
188
- const docRef = this.pathResolver.getDocRef(userId, creationId);
189
- if (!docRef) return false;
190
-
191
- try {
192
- // Update creation document with rating
193
- await updateDoc(docRef, {
194
- rating,
195
- ratedAt: new Date(),
196
- });
197
-
198
- // If description exists or we want to log every rating as feedback
199
- // we submit to feedback collection
200
- if (description || rating) {
201
- await submitFeedback({
202
- userId,
203
- userEmail: null, // We don't have email here, but standard is userId
204
- type: "creation_rating",
205
- title: `Creation Rating: ${rating} Stars`,
206
- description: description || `User rated creation ${rating} stars`,
207
- rating,
208
- status: "pending",
209
- });
210
- }
211
-
212
- return true;
213
- } catch (error) {
214
- if (__DEV__) {
215
- console.error("[CreationsWriter] rate() error", error);
216
- }
217
- return false;
218
- }
98
+ }
99
+
100
+ async updateShared(userId: string, creationId: string, isShared: boolean): Promise<boolean> {
101
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
102
+ if (!docRef) return false;
103
+ try {
104
+ await updateDoc(docRef, { isShared });
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ async updateFavorite(userId: string, creationId: string, isFavorite: boolean): Promise<boolean> {
112
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
113
+ if (!docRef) return false;
114
+ try {
115
+ await updateDoc(docRef, { isFavorite });
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ async rate(userId: string, creationId: string, rating: number, description?: string): Promise<boolean> {
123
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
124
+ if (!docRef) return false;
125
+
126
+ try {
127
+ await updateDoc(docRef, { rating, ratedAt: new Date() });
128
+ if (description || rating) {
129
+ await submitFeedback({
130
+ userId,
131
+ userEmail: null,
132
+ type: "creation_rating",
133
+ title: `Creation Rating: ${rating} Stars`,
134
+ description: description || `User rated creation ${rating} stars`,
135
+ rating,
136
+ status: "pending",
137
+ });
138
+ }
139
+ return true;
140
+ } catch (error) {
141
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[CreationsWriter] rate() error", error);
142
+ return false;
219
143
  }
144
+ }
220
145
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Gallery Result Preview Component
3
+ * Displays result preview when a creation is selected in gallery
4
+ */
5
+
6
+ import React from "react";
7
+ import { useAlert, AlertType, AlertMode } from "@umituz/react-native-design-system";
8
+ import { ResultPreviewScreen } from "../../../result-preview/presentation/components/ResultPreviewScreen";
9
+ import { StarRatingPicker } from "../../../result-preview/presentation/components/StarRatingPicker";
10
+ import { useResultActions } from "../../../result-preview/presentation/hooks/useResultActions";
11
+ import type { Creation } from "../../domain/entities/Creation";
12
+ import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
13
+
14
+ interface GalleryResultPreviewProps {
15
+ readonly selectedCreation: Creation;
16
+ readonly imageUrl?: string;
17
+ readonly videoUrl?: string;
18
+ readonly showRatingPicker: boolean;
19
+ readonly config: CreationsConfig;
20
+ readonly t: (key: string) => string;
21
+ readonly onBack: () => void;
22
+ readonly onTryAgain: () => void;
23
+ readonly onRate: () => void;
24
+ readonly onSubmitRating: (rating: number, description: string) => void;
25
+ readonly onCloseRating: () => void;
26
+ }
27
+
28
+ export function GalleryResultPreview({
29
+ selectedCreation,
30
+ imageUrl,
31
+ videoUrl,
32
+ showRatingPicker,
33
+ config,
34
+ t,
35
+ onBack,
36
+ onTryAgain,
37
+ onRate,
38
+ onSubmitRating,
39
+ onCloseRating,
40
+ }: GalleryResultPreviewProps) {
41
+ const alert = useAlert();
42
+
43
+ const { isSaving, isSharing, handleDownload, handleShare } = useResultActions({
44
+ imageUrl,
45
+ videoUrl,
46
+ onSaveSuccess: () => alert.show(AlertType.SUCCESS, AlertMode.TOAST, t("result.saveSuccess"), t("result.saveSuccessMessage")),
47
+ onSaveError: () => alert.show(AlertType.ERROR, AlertMode.TOAST, t("common.error"), t("result.saveError")),
48
+ });
49
+
50
+ const hasRating = selectedCreation.rating !== undefined && selectedCreation.rating !== null;
51
+
52
+ return (
53
+ <>
54
+ <ResultPreviewScreen
55
+ imageUrl={videoUrl ? undefined : imageUrl}
56
+ videoUrl={videoUrl}
57
+ isSaving={isSaving}
58
+ isSharing={isSharing}
59
+ onDownload={handleDownload}
60
+ onShare={handleShare}
61
+ onTryAgain={onTryAgain}
62
+ onNavigateBack={onBack}
63
+ onRate={onRate}
64
+ hideLabel
65
+ iconOnly
66
+ showTryAgain
67
+ showRating={!hasRating}
68
+ translations={{
69
+ title: t(config.translations.title),
70
+ saveButton: t("result.saveButton"),
71
+ saving: t("result.saving"),
72
+ shareButton: t("result.shareButton"),
73
+ sharing: t("result.sharing"),
74
+ tryAnother: t("result.tryAnother"),
75
+ }}
76
+ />
77
+ <StarRatingPicker
78
+ visible={showRatingPicker}
79
+ onClose={onCloseRating}
80
+ onRate={onSubmitRating}
81
+ title={t("result.rateTitle")}
82
+ submitLabel={t("common.submit")}
83
+ cancelLabel={t("common.cancel")}
84
+ descriptionPlaceholder={t("result.feedbackPlaceholder")}
85
+ />
86
+ </>
87
+ );
88
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Gallery Callbacks Hook
3
+ * Extracts callback handlers from CreationsGalleryScreen
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { useAlert, AlertType, AlertMode, useSharing } from "@umituz/react-native-design-system";
8
+ import type { Creation } from "../../domain/entities/Creation";
9
+ import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
10
+ import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
11
+
12
+ export interface UseGalleryCallbacksProps {
13
+ readonly userId: string | null;
14
+ readonly repository: ICreationsRepository;
15
+ readonly config: CreationsConfig;
16
+ readonly t: (key: string) => string;
17
+ readonly deleteMutation: { mutateAsync: (id: string) => Promise<boolean> };
18
+ readonly refetch: () => Promise<void>;
19
+ readonly setSelectedCreation: (creation: Creation | null) => void;
20
+ readonly setShowRatingPicker: (show: boolean) => void;
21
+ readonly selectedCreation: Creation | null;
22
+ }
23
+
24
+ export function useGalleryCallbacks(props: UseGalleryCallbacksProps) {
25
+ const {
26
+ userId,
27
+ repository,
28
+ config,
29
+ t,
30
+ deleteMutation,
31
+ refetch,
32
+ setSelectedCreation,
33
+ setShowRatingPicker,
34
+ selectedCreation,
35
+ } = props;
36
+
37
+ const { share } = useSharing();
38
+ const alert = useAlert();
39
+
40
+ const handleShareCard = useCallback(
41
+ (c: Creation) => {
42
+ void share(c.uri, { dialogTitle: t("common.share") });
43
+ },
44
+ [share, t],
45
+ );
46
+
47
+ const handleDelete = useCallback(
48
+ (c: Creation) => {
49
+ alert.show(
50
+ AlertType.WARNING,
51
+ AlertMode.MODAL,
52
+ t(config.translations.deleteTitle),
53
+ t(config.translations.deleteMessage),
54
+ {
55
+ actions: [
56
+ { id: "cancel", label: t("common.cancel"), onPress: () => {} },
57
+ {
58
+ id: "delete",
59
+ label: t("common.delete"),
60
+ style: "destructive",
61
+ onPress: async () => {
62
+ await deleteMutation.mutateAsync(c.id);
63
+ },
64
+ },
65
+ ],
66
+ },
67
+ );
68
+ },
69
+ [alert, config, deleteMutation, t],
70
+ );
71
+
72
+ const handleFavorite = useCallback(
73
+ (c: Creation, isFavorite: boolean) => {
74
+ void (async () => {
75
+ if (!userId) return;
76
+ const success = await repository.updateFavorite(userId, c.id, isFavorite);
77
+ if (success) void refetch();
78
+ })();
79
+ },
80
+ [userId, repository, refetch],
81
+ );
82
+
83
+ const handleCardPress = useCallback(
84
+ (item: Creation) => {
85
+ setSelectedCreation(item);
86
+ },
87
+ [setSelectedCreation],
88
+ );
89
+
90
+ const handleBack = useCallback(() => {
91
+ setSelectedCreation(null);
92
+ }, [setSelectedCreation]);
93
+
94
+ const handleTryAgain = useCallback(() => {
95
+ setSelectedCreation(null);
96
+ }, [setSelectedCreation]);
97
+
98
+ const handleOpenRatingPicker = useCallback(() => {
99
+ setShowRatingPicker(true);
100
+ }, [setShowRatingPicker]);
101
+
102
+ const handleSubmitRating = useCallback(
103
+ (rating: number, description: string) => {
104
+ if (!userId || !selectedCreation) return;
105
+ void (async () => {
106
+ const success = await repository.rate(userId, selectedCreation.id, rating, description);
107
+ if (success) {
108
+ setSelectedCreation({ ...selectedCreation, rating, ratedAt: new Date() });
109
+ alert.show(AlertType.SUCCESS, AlertMode.TOAST, t("result.rateSuccessTitle"), t("result.rateSuccessMessage"));
110
+ void refetch();
111
+ }
112
+ })();
113
+ },
114
+ [userId, selectedCreation, repository, alert, t, refetch, setSelectedCreation],
115
+ );
116
+
117
+ return {
118
+ handleShareCard,
119
+ handleDelete,
120
+ handleFavorite,
121
+ handleCardPress,
122
+ handleBack,
123
+ handleTryAgain,
124
+ handleOpenRatingPicker,
125
+ handleSubmitRating,
126
+ };
127
+ }