@umituz/react-native-ai-generation-content 1.61.39 → 1.61.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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +16 -213
  3. package/src/domains/creations/infrastructure/repositories/creations-operations.ts +128 -0
  4. package/src/domains/creations/infrastructure/repositories/creations-state-operations.ts +86 -0
  5. package/src/domains/creations/presentation/components/GalleryScreenHeader.tsx +54 -0
  6. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +23 -56
  7. package/src/domains/creations/presentation/utils/filter-buttons.util.ts +75 -0
  8. package/src/domains/generation/wizard/presentation/screens/GeneratingScreen.tsx +36 -43
  9. package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +6 -39
  10. package/src/domains/scenarios/presentation/screens/ScenarioPreviewScreen.tsx +7 -25
  11. package/src/features/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +26 -46
  12. package/src/features/shared/index.ts +6 -0
  13. package/src/features/shared/presentation/components/AutoSkipPreview.tsx +24 -0
  14. package/src/features/shared/presentation/components/index.ts +6 -0
  15. package/src/features/shared/presentation/utils/index.ts +14 -0
  16. package/src/features/shared/presentation/utils/wizard-flow.utils.ts +84 -0
  17. package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +26 -46
  18. package/src/features/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +26 -46
  19. package/src/infrastructure/logging/debug.util.ts +1 -1
  20. package/src/infrastructure/logging/index.ts +1 -1
  21. package/src/infrastructure/validation/advanced-validator.ts +97 -0
  22. package/src/infrastructure/validation/ai-validator.ts +77 -0
  23. package/src/infrastructure/validation/base-validator.ts +149 -0
  24. package/src/infrastructure/validation/entity-validator.ts +64 -0
  25. package/src/infrastructure/validation/input-validator.ts +37 -409
  26. package/src/infrastructure/validation/sanitizer.ts +43 -0
  27. package/src/presentation/components/buttons/ContinueButton.tsx +72 -0
  28. package/src/presentation/components/buttons/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.61.39",
3
+ "version": "1.61.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",
@@ -1,15 +1,12 @@
1
- import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
2
- import { type FirestorePathResolver } from "@umituz/react-native-firebase";
3
- import { submitFeedback } from "@umituz/react-native-subscription";
4
- import type { Creation, CreationDocument } from "../../domain/entities/Creation";
5
-
6
- declare const __DEV__: boolean;
1
+ /**
2
+ * Creations Writer
3
+ * Main class that orchestrates all creation write operations
4
+ */
7
5
 
8
- const UPDATABLE_FIELDS = [
9
- "metadata", "isShared", "uri", "type", "prompt", "status",
10
- "output", "rating", "ratedAt", "isFavorite", "deletedAt",
11
- "requestId", "model",
12
- ] as const;
6
+ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
7
+ import type { Creation } from "../../domain/entities/Creation";
8
+ import * as operations from "./creations-operations";
9
+ import * as stateOperations from "./creations-state-operations";
13
10
 
14
11
  /**
15
12
  * Handles write operations for creations
@@ -18,228 +15,34 @@ export class CreationsWriter {
18
15
  constructor(private readonly pathResolver: FirestorePathResolver) {}
19
16
 
20
17
  async create(userId: string, creation: Creation): Promise<void> {
21
- if (typeof __DEV__ !== "undefined" && __DEV__) {
22
- console.log("[CreationsWriter] create()", { userId, creationId: creation.id });
23
- }
24
-
25
- const docRef = this.pathResolver.getDocRef(userId, creation.id);
26
- if (!docRef) throw new Error("Firestore not initialized");
27
-
28
- const data: CreationDocument = {
29
- type: creation.type,
30
- uri: creation.uri,
31
- createdAt: creation.createdAt,
32
- metadata: creation.metadata || {},
33
- isShared: creation.isShared || false,
34
- isFavorite: creation.isFavorite || false,
35
- ...(creation.status !== undefined && { status: creation.status }),
36
- ...(creation.output !== undefined && { output: creation.output }),
37
- ...(creation.prompt !== undefined && { prompt: creation.prompt }),
38
- ...(creation.requestId !== undefined && { requestId: creation.requestId }),
39
- ...(creation.model !== undefined && { model: creation.model }),
40
- };
41
-
42
- try {
43
- await setDoc(docRef, data);
44
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CreationsWriter] create() success");
45
- } catch (error) {
46
- const errorMessage = error instanceof Error ? error.message : String(error);
47
- if (typeof __DEV__ !== "undefined" && __DEV__) {
48
- console.error("[CreationsWriter] create() error", {
49
- userId,
50
- creationId: creation.id,
51
- error: errorMessage,
52
- });
53
- }
54
- throw new Error(`Failed to create creation ${creation.id}: ${errorMessage}`);
55
- }
18
+ return operations.createCreation(this.pathResolver, userId, creation);
56
19
  }
57
20
 
58
21
  async update(userId: string, id: string, updates: Partial<Creation>): Promise<boolean> {
59
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CreationsWriter] update()", { userId, id });
60
-
61
- const docRef = this.pathResolver.getDocRef(userId, id);
62
- if (!docRef) {
63
- if (typeof __DEV__ !== "undefined" && __DEV__) {
64
- console.error("[CreationsWriter] update() - Firestore not initialized", { userId, id });
65
- }
66
- return false;
67
- }
68
-
69
- try {
70
- const updateData: Record<string, unknown> = {};
71
- for (const field of UPDATABLE_FIELDS) {
72
- if (updates[field] !== undefined) updateData[field] = updates[field];
73
- }
74
- await updateDoc(docRef, updateData);
75
- return true;
76
- } catch (error) {
77
- const errorMessage = error instanceof Error ? error.message : String(error);
78
- if (typeof __DEV__ !== "undefined" && __DEV__) {
79
- console.error("[CreationsWriter] update() error", {
80
- userId,
81
- creationId: id,
82
- error: errorMessage,
83
- });
84
- }
85
- return false;
86
- }
22
+ return operations.updateCreation(this.pathResolver, userId, id, updates);
87
23
  }
88
24
 
89
25
  async delete(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: new Date() });
94
- return true;
95
- } catch (error) {
96
- const errorMessage = error instanceof Error ? error.message : String(error);
97
- if (typeof __DEV__ !== "undefined" && __DEV__) {
98
- console.error("[CreationsWriter] delete() error", {
99
- userId,
100
- creationId,
101
- error: errorMessage,
102
- });
103
- }
104
- return false;
105
- }
26
+ return operations.deleteCreation(this.pathResolver, userId, creationId);
106
27
  }
107
28
 
108
29
  async hardDelete(userId: string, creationId: string): Promise<boolean> {
109
- const docRef = this.pathResolver.getDocRef(userId, creationId);
110
- if (!docRef) return false;
111
- try {
112
- await deleteDoc(docRef);
113
- return true;
114
- } catch (error) {
115
- const errorMessage = error instanceof Error ? error.message : String(error);
116
- if (typeof __DEV__ !== "undefined" && __DEV__) {
117
- console.error("[CreationsWriter] hardDelete() error", {
118
- userId,
119
- creationId,
120
- error: errorMessage,
121
- });
122
- }
123
- return false;
124
- }
30
+ return operations.hardDeleteCreation(this.pathResolver, userId, creationId);
125
31
  }
126
32
 
127
33
  async restore(userId: string, creationId: string): Promise<boolean> {
128
- const docRef = this.pathResolver.getDocRef(userId, creationId);
129
- if (!docRef) return false;
130
- try {
131
- await updateDoc(docRef, { deletedAt: null });
132
- return true;
133
- } catch (error) {
134
- const errorMessage = error instanceof Error ? error.message : String(error);
135
- if (typeof __DEV__ !== "undefined" && __DEV__) {
136
- console.error("[CreationsWriter] restore() error", {
137
- userId,
138
- creationId,
139
- error: errorMessage,
140
- });
141
- }
142
- return false;
143
- }
34
+ return operations.restoreCreation(this.pathResolver, userId, creationId);
144
35
  }
145
36
 
146
37
  async updateShared(userId: string, creationId: string, isShared: boolean): Promise<boolean> {
147
- const docRef = this.pathResolver.getDocRef(userId, creationId);
148
- if (!docRef) return false;
149
- try {
150
- await updateDoc(docRef, { isShared });
151
- return true;
152
- } catch (error) {
153
- const errorMessage = error instanceof Error ? error.message : String(error);
154
- if (typeof __DEV__ !== "undefined" && __DEV__) {
155
- console.error("[CreationsWriter] updateShared() error", {
156
- userId,
157
- creationId,
158
- isShared,
159
- error: errorMessage,
160
- });
161
- }
162
- return false;
163
- }
38
+ return stateOperations.updateCreationShared(this.pathResolver, userId, creationId, isShared);
164
39
  }
165
40
 
166
41
  async updateFavorite(userId: string, creationId: string, isFavorite: boolean): Promise<boolean> {
167
- if (typeof __DEV__ !== "undefined" && __DEV__) {
168
- console.log("[CreationsWriter] updateFavorite()", { userId, creationId, isFavorite });
169
- }
170
- const docRef = this.pathResolver.getDocRef(userId, creationId);
171
- if (!docRef) {
172
- if (typeof __DEV__ !== "undefined" && __DEV__) {
173
- console.warn("[CreationsWriter] updateFavorite() - Firestore not initialized", {
174
- userId,
175
- creationId,
176
- });
177
- }
178
- return false;
179
- }
180
- try {
181
- await updateDoc(docRef, { isFavorite });
182
- if (typeof __DEV__ !== "undefined" && __DEV__) {
183
- console.log("[CreationsWriter] updateFavorite() success");
184
- }
185
- return true;
186
- } catch (error) {
187
- const errorMessage = error instanceof Error ? error.message : String(error);
188
- if (typeof __DEV__ !== "undefined" && __DEV__) {
189
- console.error("[CreationsWriter] updateFavorite() error", {
190
- userId,
191
- creationId,
192
- isFavorite,
193
- error: errorMessage,
194
- });
195
- }
196
- return false;
197
- }
42
+ return stateOperations.updateCreationFavorite(this.pathResolver, userId, creationId, isFavorite);
198
43
  }
199
44
 
200
45
  async rate(userId: string, creationId: string, rating: number, description?: string): Promise<boolean> {
201
- const docRef = this.pathResolver.getDocRef(userId, creationId);
202
- if (!docRef) return false;
203
-
204
- try {
205
- await updateDoc(docRef, { rating, ratedAt: new Date() });
206
- if (description || rating) {
207
- try {
208
- await submitFeedback({
209
- userId,
210
- userEmail: null,
211
- type: "creation_rating",
212
- title: `Creation Rating: ${rating} Stars`,
213
- description: description || `User rated creation ${rating} stars`,
214
- rating,
215
- status: "pending",
216
- });
217
- } catch (feedbackError) {
218
- // Log but don't fail - the rating was saved successfully
219
- const feedbackErrorMessage = feedbackError instanceof Error
220
- ? feedbackError.message
221
- : String(feedbackError);
222
- if (typeof __DEV__ !== "undefined" && __DEV__) {
223
- console.warn("[CreationsWriter] rate() - feedback submission failed", {
224
- userId,
225
- creationId,
226
- error: feedbackErrorMessage,
227
- });
228
- }
229
- }
230
- }
231
- return true;
232
- } catch (error) {
233
- const errorMessage = error instanceof Error ? error.message : String(error);
234
- if (typeof __DEV__ !== "undefined" && __DEV__) {
235
- console.error("[CreationsWriter] rate() error", {
236
- userId,
237
- creationId,
238
- rating,
239
- error: errorMessage,
240
- });
241
- }
242
- return false;
243
- }
46
+ return stateOperations.rateCreation(this.pathResolver, userId, creationId, rating, description);
244
47
  }
245
48
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Creation CRUD Operations
3
+ * Core create, read, update, delete operations for creations
4
+ */
5
+
6
+ import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
7
+ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
8
+ import type { Creation, CreationDocument } from "../../domain/entities/Creation";
9
+
10
+ const UPDATABLE_FIELDS = [
11
+ "metadata", "isShared", "uri", "type", "prompt", "status",
12
+ "output", "rating", "ratedAt", "isFavorite", "deletedAt",
13
+ "requestId", "model",
14
+ ] as const;
15
+
16
+ /**
17
+ * Creates a new creation document
18
+ */
19
+ export async function createCreation(
20
+ pathResolver: FirestorePathResolver,
21
+ userId: string,
22
+ creation: Creation
23
+ ): Promise<void> {
24
+ const docRef = 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
+ ...(creation.requestId !== undefined && { requestId: creation.requestId }),
38
+ ...(creation.model !== undefined && { model: creation.model }),
39
+ };
40
+
41
+ try {
42
+ await setDoc(docRef, data);
43
+ } catch (error) {
44
+ const errorMessage = error instanceof Error ? error.message : String(error);
45
+ throw new Error(`Failed to create creation ${creation.id}: ${errorMessage}`);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Updates a creation document
51
+ */
52
+ export async function updateCreation(
53
+ pathResolver: FirestorePathResolver,
54
+ userId: string,
55
+ id: string,
56
+ updates: Partial<Creation>
57
+ ): Promise<boolean> {
58
+ const docRef = pathResolver.getDocRef(userId, id);
59
+ if (!docRef) return false;
60
+
61
+ try {
62
+ const updateData: Record<string, unknown> = {};
63
+ for (const field of UPDATABLE_FIELDS) {
64
+ if (updates[field] !== undefined) updateData[field] = updates[field];
65
+ }
66
+ await updateDoc(docRef, updateData);
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Soft deletes a creation (marks as deleted)
75
+ */
76
+ export async function deleteCreation(
77
+ pathResolver: FirestorePathResolver,
78
+ userId: string,
79
+ creationId: string
80
+ ): Promise<boolean> {
81
+ const docRef = pathResolver.getDocRef(userId, creationId);
82
+ if (!docRef) return false;
83
+
84
+ try {
85
+ await updateDoc(docRef, { deletedAt: new Date() });
86
+ return true;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Hard deletes a creation (removes from database)
94
+ */
95
+ export async function hardDeleteCreation(
96
+ pathResolver: FirestorePathResolver,
97
+ userId: string,
98
+ creationId: string
99
+ ): Promise<boolean> {
100
+ const docRef = pathResolver.getDocRef(userId, creationId);
101
+ if (!docRef) return false;
102
+
103
+ try {
104
+ await deleteDoc(docRef);
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Restores a soft-deleted creation
113
+ */
114
+ export async function restoreCreation(
115
+ pathResolver: FirestorePathResolver,
116
+ userId: string,
117
+ creationId: string
118
+ ): Promise<boolean> {
119
+ const docRef = pathResolver.getDocRef(userId, creationId);
120
+ if (!docRef) return false;
121
+
122
+ try {
123
+ await updateDoc(docRef, { deletedAt: null });
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Creation State Operations
3
+ * State-specific operations like sharing, favoriting, and rating
4
+ */
5
+
6
+ import { updateDoc } from "firebase/firestore";
7
+ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
8
+ import { submitFeedback } from "@umituz/react-native-subscription";
9
+
10
+ /**
11
+ * Updates the shared status of a creation
12
+ */
13
+ export async function updateCreationShared(
14
+ pathResolver: FirestorePathResolver,
15
+ userId: string,
16
+ creationId: string,
17
+ isShared: boolean
18
+ ): Promise<boolean> {
19
+ const docRef = pathResolver.getDocRef(userId, creationId);
20
+ if (!docRef) return false;
21
+
22
+ try {
23
+ await updateDoc(docRef, { isShared });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Updates the favorite status of a creation
32
+ */
33
+ export async function updateCreationFavorite(
34
+ pathResolver: FirestorePathResolver,
35
+ userId: string,
36
+ creationId: string,
37
+ isFavorite: boolean
38
+ ): Promise<boolean> {
39
+ const docRef = pathResolver.getDocRef(userId, creationId);
40
+ if (!docRef) return false;
41
+
42
+ try {
43
+ await updateDoc(docRef, { isFavorite });
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Rates a creation and optionally submits feedback
52
+ */
53
+ export async function rateCreation(
54
+ pathResolver: FirestorePathResolver,
55
+ userId: string,
56
+ creationId: string,
57
+ rating: number,
58
+ description?: string
59
+ ): Promise<boolean> {
60
+ const docRef = pathResolver.getDocRef(userId, creationId);
61
+ if (!docRef) return false;
62
+
63
+ try {
64
+ await updateDoc(docRef, { rating, ratedAt: new Date() });
65
+
66
+ // Submit feedback if description or rating is provided
67
+ if (description || rating) {
68
+ try {
69
+ await submitFeedback({
70
+ userId,
71
+ userEmail: null,
72
+ type: "creation_rating",
73
+ title: `Creation Rating: ${rating} Stars`,
74
+ description: description || `User rated creation ${rating} stars`,
75
+ rating,
76
+ status: "pending",
77
+ });
78
+ } catch {
79
+ // Feedback submission failed but rating was saved - continue
80
+ }
81
+ }
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * GalleryScreenHeader Component
3
+ * Header component for the gallery screen
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens, AtomicIcon, AtomicText, type DesignTokens } from "@umituz/react-native-design-system";
9
+
10
+ export interface GalleryScreenHeaderProps {
11
+ readonly title: string;
12
+ readonly onBack: () => void;
13
+ }
14
+
15
+ export const GalleryScreenHeader: React.FC<GalleryScreenHeaderProps> = ({ title, onBack }) => {
16
+ const tokens = useAppDesignTokens();
17
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
18
+
19
+ return (
20
+ <View style={styles.screenHeader}>
21
+ <TouchableOpacity onPress={onBack} style={styles.backButton}>
22
+ <AtomicIcon
23
+ name="chevron-left"
24
+ customSize={28}
25
+ customColor={tokens.colors.textPrimary}
26
+ />
27
+ </TouchableOpacity>
28
+ <AtomicText
29
+ type="titleLarge"
30
+ style={{ color: tokens.colors.textPrimary }}
31
+ >
32
+ {title}
33
+ </AtomicText>
34
+ <View style={styles.placeholder} />
35
+ </View>
36
+ );
37
+ };
38
+
39
+ const createStyles = (tokens: DesignTokens) =>
40
+ StyleSheet.create({
41
+ screenHeader: {
42
+ flexDirection: "row",
43
+ alignItems: "center",
44
+ paddingHorizontal: tokens.spacing.md,
45
+ paddingVertical: tokens.spacing.sm,
46
+ gap: tokens.spacing.md,
47
+ },
48
+ backButton: {
49
+ padding: tokens.spacing.xs,
50
+ },
51
+ placeholder: {
52
+ width: 36,
53
+ },
54
+ });
@@ -1,12 +1,10 @@
1
1
  import React, { useState, useMemo, useCallback, useEffect, useRef } from "react";
2
- import { View, FlatList, RefreshControl, TouchableOpacity } from "react-native";
2
+ import { View, FlatList, RefreshControl } from "react-native";
3
3
  import {
4
4
  useAppDesignTokens,
5
5
  FilterSheet,
6
6
  ScreenLayout,
7
7
  useAppFocusEffect,
8
- AtomicIcon,
9
- AtomicText,
10
8
  } from "@umituz/react-native-design-system";
11
9
  import { useCreations } from "../hooks/useCreations";
12
10
  import { useDeleteCreation } from "../hooks/useDeleteCreation";
@@ -15,8 +13,10 @@ import { useGalleryFilters } from "../hooks/useGalleryFilters";
15
13
  import { useGalleryCallbacks } from "../hooks/useGalleryCallbacks";
16
14
  import { GalleryHeader, CreationCard, GalleryEmptyStates } from "../components";
17
15
  import { GalleryResultPreview } from "../components/GalleryResultPreview";
16
+ import { GalleryScreenHeader } from "../components/GalleryScreenHeader";
18
17
  import { MEDIA_FILTER_OPTIONS, STATUS_FILTER_OPTIONS } from "../../domain/types/creation-filter";
19
18
  import { getPreviewUrl } from "../../domain/utils";
19
+ import { createFilterButtons, createItemTitle } from "../utils/filter-buttons.util";
20
20
  import type { Creation } from "../../domain/entities/Creation";
21
21
  import type { CreationsGalleryScreenProps } from "./creations-gallery.types";
22
22
  import { creationsGalleryStyles as styles } from "./creations-gallery.styles";
@@ -44,7 +44,6 @@ export function CreationsGalleryScreen({
44
44
  const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
45
45
  const deleteMutation = useDeleteCreation({ userId, repository });
46
46
 
47
- // Poll FAL queue for "processing" creations (enables true background generation)
48
47
  useProcessingJobsPoller({
49
48
  userId,
50
49
  creations: creations ?? [],
@@ -84,38 +83,24 @@ export function CreationsGalleryScreen({
84
83
 
85
84
  useAppFocusEffect(useCallback(() => { void refetch(); }, [refetch]));
86
85
 
87
- const filterButtons = useMemo(() => {
88
- const buttons = [];
89
- if (showStatusFilter) {
90
- buttons.push({
91
- id: "status",
92
- label: t(config.translations.statusFilterTitle ?? "creations.filter.status"),
93
- icon: "list-outline",
94
- isActive: filters.statusFilter.hasActiveFilter,
95
- onPress: filters.openStatusFilter,
96
- });
97
- }
98
- if (showMediaFilter) {
99
- buttons.push({
100
- id: "media",
101
- label: t(config.translations.mediaFilterTitle ?? "creations.filter.media"),
102
- icon: "grid-outline",
103
- isActive: filters.mediaFilter.hasActiveFilter,
104
- onPress: filters.openMediaFilter,
105
- });
106
- }
107
- return buttons;
108
- }, [showStatusFilter, showMediaFilter, filters, t, config.translations]);
86
+ const filterButtons = useMemo(() =>
87
+ createFilterButtons({
88
+ showStatusFilter,
89
+ showMediaFilter,
90
+ statusFilterActive: filters.statusFilter.hasActiveFilter,
91
+ mediaFilterActive: filters.mediaFilter.hasActiveFilter,
92
+ statusFilterLabel: t(config.translations.statusFilterTitle ?? "creations.filter.status"),
93
+ mediaFilterLabel: t(config.translations.mediaFilterTitle ?? "creations.filter.media"),
94
+ onStatusFilterPress: filters.openStatusFilter,
95
+ onMediaFilterPress: filters.openMediaFilter,
96
+ }),
97
+ [showStatusFilter, showMediaFilter, filters, t, config.translations]
98
+ );
109
99
 
110
- const getItemTitle = useCallback((item: Creation): string => {
111
- // Use custom title function if provided
112
- if (getCreationTitle) {
113
- return getCreationTitle({ type: item.type, metadata: item.metadata });
114
- }
115
- // Default: use type config label
116
- const typeConfig = config.types?.find((tc) => tc.id === item.type);
117
- return typeConfig?.labelKey ? t(typeConfig.labelKey) : item.type.split("_").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
118
- }, [config.types, t, getCreationTitle]);
100
+ const getItemTitle = useCallback((item: Creation): string =>
101
+ createItemTitle(item, { types: config.types, getCreationTitle }, t),
102
+ [config.types, t, getCreationTitle]
103
+ );
119
104
 
120
105
  const renderItem = useCallback(({ item }: { item: Creation }) => (
121
106
  <CreationCard
@@ -143,7 +128,7 @@ export function CreationsGalleryScreen({
143
128
  />
144
129
  </View>
145
130
  );
146
- }, [creations, isLoading, filters.filtered.length, filters.processingCount, showFilter, filterButtons, t, config, tokens]);
131
+ }, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config, tokens]);
147
132
 
148
133
  const renderEmpty = useMemo(() => (
149
134
  <GalleryEmptyStates
@@ -167,26 +152,8 @@ export function CreationsGalleryScreen({
167
152
 
168
153
  const screenHeader = useMemo(() => {
169
154
  if (!onBack) return undefined;
170
-
171
- return (
172
- <View style={styles.screenHeader}>
173
- <TouchableOpacity onPress={onBack} style={styles.backButton}>
174
- <AtomicIcon
175
- name="chevron-left"
176
- customSize={28}
177
- customColor={tokens.colors.textPrimary}
178
- />
179
- </TouchableOpacity>
180
- <AtomicText
181
- type="titleLarge"
182
- style={{ color: tokens.colors.textPrimary }}
183
- >
184
- {t(config.translations.title)}
185
- </AtomicText>
186
- <View style={styles.placeholder} />
187
- </View>
188
- );
189
- }, [onBack, tokens, t, config]);
155
+ return <GalleryScreenHeader title={t(config.translations.title)} onBack={onBack} />;
156
+ }, [onBack, t, config.translations.title]);
190
157
 
191
158
  if (showPreview) {
192
159
  return (