@umituz/react-native-ai-creations 1.0.1 → 1.1.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,12 +1,13 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-creations",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "AI-generated creations gallery with filtering, sharing, and management for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "scripts": {
8
8
  "typecheck": "tsc --noEmit",
9
9
  "lint": "tsc --noEmit",
10
+ "version:patch": "npm version patch -m 'chore: release v%s'",
10
11
  "version:minor": "npm version minor -m 'chore: release v%s'",
11
12
  "version:major": "npm version major -m 'chore: release v%s'"
12
13
  },
@@ -29,6 +30,8 @@
29
30
  "peerDependencies": {
30
31
  "@tanstack/react-query": ">=5.0.0",
31
32
  "@umituz/react-native-design-system": "latest",
33
+ "@umituz/react-native-firestore": "latest",
34
+ "@umituz/react-native-image": "latest",
32
35
  "@umituz/react-native-sharing": "latest",
33
36
  "firebase": ">=11.0.0",
34
37
  "react": ">=18.2.0",
@@ -38,6 +41,7 @@
38
41
  "devDependencies": {
39
42
  "@tanstack/react-query": "^5.62.16",
40
43
  "@types/react": "^19.0.0",
44
+ "@umituz/react-native-image": "^1.1.1",
41
45
  "firebase": "^11.0.0",
42
46
  "react": "^19.0.0",
43
47
  "react-native": "^0.76.0",
@@ -3,6 +3,8 @@
3
3
  * Defines the configuration for creations feature
4
4
  */
5
5
 
6
+ import type { Creation, CreationDocument } from "../entities/Creation";
7
+
6
8
  export interface CreationType {
7
9
  readonly id: string;
8
10
  readonly labelKey: string;
@@ -20,12 +22,31 @@ export interface CreationsTranslations {
20
22
  readonly filterAll: string;
21
23
  }
22
24
 
25
+ /**
26
+ * Path builder function type
27
+ * Allows apps to define custom Firestore path structures
28
+ * @example
29
+ * // Default: users/{userId}/creations
30
+ * pathBuilder: (userId) => ["users", userId, "creations"]
31
+ * // Alternative: creations/{userId}/items
32
+ * pathBuilder: (userId) => ["creations", userId, "items"]
33
+ */
34
+ export type PathBuilder = (userId: string) => string[];
35
+
36
+ /**
37
+ * Document mapper function type
38
+ * Allows apps to map their specific document structure to Creation
39
+ */
40
+ export type DocumentMapper = (id: string, data: CreationDocument) => Creation;
41
+
23
42
  export interface CreationsConfig {
24
43
  readonly collectionName: string;
25
44
  readonly types: readonly CreationType[];
26
45
  readonly translations: CreationsTranslations;
27
46
  readonly maxThumbnails?: number;
28
47
  readonly gridColumns?: number;
48
+ readonly pathBuilder?: PathBuilder;
49
+ readonly documentMapper?: DocumentMapper;
29
50
  }
30
51
 
31
52
  export const DEFAULT_TRANSLATIONS: CreationsTranslations = {
@@ -6,5 +6,7 @@ export type {
6
6
  CreationType,
7
7
  CreationsTranslations,
8
8
  CreationsConfig,
9
+ PathBuilder,
10
+ DocumentMapper,
9
11
  } from "./CreationsConfig";
10
12
  export { DEFAULT_TRANSLATIONS, DEFAULT_CONFIG } from "./CreationsConfig";
package/src/index.ts CHANGED
@@ -3,6 +3,14 @@
3
3
  *
4
4
  * AI-generated creations gallery with filtering, sharing, and management
5
5
  *
6
+ * Architecture:
7
+ * - Extends BaseRepository from @umituz/react-native-firestore
8
+ * - Fully dynamic path structure (configurable per app)
9
+ * - Fully dynamic document mapping (configurable per app)
10
+ * - App-agnostic: Works with any app, no app-specific code
11
+ *
12
+ * This package is designed to be used across hundreds of apps.
13
+ *
6
14
  * Usage:
7
15
  * import {
8
16
  * CreationsGalleryScreen,
@@ -27,6 +35,8 @@ export type {
27
35
  CreationType,
28
36
  CreationsTranslations,
29
37
  CreationsConfig,
38
+ PathBuilder,
39
+ DocumentMapper,
30
40
  } from "./domain/value-objects";
31
41
  export { DEFAULT_TRANSLATIONS, DEFAULT_CONFIG } from "./domain/value-objects";
32
42
 
@@ -40,7 +50,10 @@ export type { ICreationsRepository } from "./domain/repositories";
40
50
  // INFRASTRUCTURE LAYER
41
51
  // =============================================================================
42
52
 
43
- export { CreationsRepository } from "./infrastructure/repositories";
53
+ export {
54
+ CreationsRepository,
55
+ type RepositoryOptions,
56
+ } from "./infrastructure/repositories";
44
57
  export { createCreationsRepository } from "./infrastructure/adapters";
45
58
 
46
59
  // =============================================================================
@@ -1,15 +1,54 @@
1
1
  /**
2
2
  * Repository Factory
3
3
  * Creates repository instance with given configuration
4
+ *
5
+ * Architecture:
6
+ * - Factory pattern for repository creation
7
+ * - Supports dynamic path structure per app
8
+ * - Supports custom document mapping per app
9
+ * - App-agnostic: No Firestore instance needed (BaseRepository handles it)
10
+ *
11
+ * This factory is designed to be used across hundreds of apps.
4
12
  */
5
13
 
6
- import type { Firestore } from "firebase/firestore";
7
- import { CreationsRepository } from "../repositories/CreationsRepository";
14
+ import {
15
+ CreationsRepository,
16
+ type RepositoryOptions,
17
+ } from "../repositories/CreationsRepository";
8
18
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
9
19
 
20
+ /**
21
+ * Creates a new CreationsRepository instance
22
+ *
23
+ * @param collectionName - Firestore collection name
24
+ * @param options - Optional repository configuration
25
+ * @returns ICreationsRepository instance
26
+ *
27
+ * @example
28
+ * // Basic usage with default path (users/{userId}/photos)
29
+ * const repo = createCreationsRepository("photos");
30
+ *
31
+ * @example
32
+ * // Custom path structure
33
+ * const repo = createCreationsRepository("creations", {
34
+ * pathBuilder: (userId) => ["gallery", userId, "items"],
35
+ * });
36
+ *
37
+ * @example
38
+ * // Custom document mapper
39
+ * const repo = createCreationsRepository("photos", {
40
+ * documentMapper: (id, data) => ({
41
+ * id,
42
+ * uri: data.imageUrl,
43
+ * type: data.category,
44
+ * createdAt: data.timestamp?.toDate() || new Date(),
45
+ * isShared: data.public ?? false,
46
+ * }),
47
+ * });
48
+ */
10
49
  export function createCreationsRepository(
11
- db: Firestore | null,
12
50
  collectionName: string,
51
+ options?: RepositoryOptions,
13
52
  ): ICreationsRepository {
14
- return new CreationsRepository(db, collectionName);
53
+ return new CreationsRepository(collectionName, options);
15
54
  }
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * Creations Repository Implementation
3
- * Firestore operations for user creations
3
+ * Extends BaseRepository from @umituz/react-native-firestore
4
+ *
5
+ * Architecture:
6
+ * - Extends BaseRepository for centralized database access
7
+ * - Fully dynamic path structure (configurable per app)
8
+ * - Fully dynamic document mapping (configurable per app)
9
+ * - App-agnostic: Works with any app, no app-specific code
10
+ *
11
+ * This class is designed to be used across hundreds of apps.
4
12
  */
5
13
 
6
14
  declare const __DEV__: boolean;
@@ -13,21 +21,72 @@ import {
13
21
  updateDoc,
14
22
  query,
15
23
  orderBy,
16
- type Firestore,
17
24
  } from "firebase/firestore";
25
+ import { BaseRepository } from "@umituz/react-native-firestore";
18
26
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
19
27
  import type { Creation, CreationDocument } from "../../domain/entities/Creation";
20
28
  import { mapDocumentToCreation } from "../../domain/entities/Creation";
29
+ import type {
30
+ PathBuilder,
31
+ DocumentMapper,
32
+ } from "../../domain/value-objects/CreationsConfig";
33
+
34
+ /**
35
+ * Repository options for dynamic configuration
36
+ * Apps can customize path structure and document mapping
37
+ */
38
+ export interface RepositoryOptions {
39
+ /**
40
+ * Custom path builder function
41
+ * @example (userId) => ["users", userId, "photos"]
42
+ * @example (userId) => ["creations", userId, "items"]
43
+ */
44
+ readonly pathBuilder?: PathBuilder;
45
+
46
+ /**
47
+ * Custom document mapper function
48
+ * Maps Firestore documents to Creation entity
49
+ */
50
+ readonly documentMapper?: DocumentMapper;
51
+ }
52
+
53
+ /**
54
+ * Default path builder: users/{userId}/{collectionName}
55
+ */
56
+ const createDefaultPathBuilder =
57
+ (collectionName: string): PathBuilder =>
58
+ (userId: string) =>
59
+ ["users", userId, collectionName];
60
+
61
+ export class CreationsRepository
62
+ extends BaseRepository
63
+ implements ICreationsRepository
64
+ {
65
+ private readonly pathBuilder: PathBuilder;
66
+ private readonly documentMapper: DocumentMapper;
21
67
 
22
- export class CreationsRepository implements ICreationsRepository {
23
68
  constructor(
24
- private readonly db: Firestore | null,
25
69
  private readonly collectionName: string,
26
- ) {}
70
+ options?: RepositoryOptions,
71
+ ) {
72
+ super();
73
+ this.pathBuilder =
74
+ options?.pathBuilder ?? createDefaultPathBuilder(collectionName);
75
+ this.documentMapper = options?.documentMapper ?? mapDocumentToCreation;
76
+ }
27
77
 
28
78
  private getUserCollection(userId: string) {
29
- if (!this.db) return null;
30
- return collection(this.db, "users", userId, this.collectionName);
79
+ const db = this.getDb();
80
+ if (!db) return null;
81
+ const pathSegments = this.pathBuilder(userId);
82
+ return collection(db, pathSegments[0], ...pathSegments.slice(1));
83
+ }
84
+
85
+ private getDocRef(userId: string, creationId: string) {
86
+ const db = this.getDb();
87
+ if (!db) return null;
88
+ const pathSegments = this.pathBuilder(userId);
89
+ return doc(db, pathSegments[0], ...pathSegments.slice(1), creationId);
31
90
  }
32
91
 
33
92
  async getAll(userId: string): Promise<Creation[]> {
@@ -48,7 +107,7 @@ export class CreationsRepository implements ICreationsRepository {
48
107
 
49
108
  return snapshot.docs.map((docSnap) => {
50
109
  const data = docSnap.data() as CreationDocument;
51
- return mapDocumentToCreation(docSnap.id, data);
110
+ return this.documentMapper(docSnap.id, data);
52
111
  });
53
112
  } catch (error) {
54
113
  if (__DEV__) {
@@ -59,16 +118,10 @@ export class CreationsRepository implements ICreationsRepository {
59
118
  }
60
119
 
61
120
  async delete(userId: string, creationId: string): Promise<boolean> {
62
- if (!this.db) return false;
121
+ const docRef = this.getDocRef(userId, creationId);
122
+ if (!docRef) return false;
63
123
 
64
124
  try {
65
- const docRef = doc(
66
- this.db,
67
- "users",
68
- userId,
69
- this.collectionName,
70
- creationId,
71
- );
72
125
  await deleteDoc(docRef);
73
126
  return true;
74
127
  } catch {
@@ -81,16 +134,10 @@ export class CreationsRepository implements ICreationsRepository {
81
134
  creationId: string,
82
135
  isShared: boolean,
83
136
  ): Promise<boolean> {
84
- if (!this.db) return false;
137
+ const docRef = this.getDocRef(userId, creationId);
138
+ if (!docRef) return false;
85
139
 
86
140
  try {
87
- const docRef = doc(
88
- this.db,
89
- "users",
90
- userId,
91
- this.collectionName,
92
- creationId,
93
- );
94
141
  await updateDoc(docRef, { isShared });
95
142
  return true;
96
143
  } catch {
@@ -2,4 +2,7 @@
2
2
  * Infrastructure Repositories
3
3
  */
4
4
 
5
- export { CreationsRepository } from "./CreationsRepository";
5
+ export {
6
+ CreationsRepository,
7
+ type RepositoryOptions,
8
+ } from "./CreationsRepository";
@@ -16,6 +16,7 @@ import type { CreationType } from "../../domain/value-objects/CreationsConfig";
16
16
  interface CreationCardProps {
17
17
  readonly creation: Creation;
18
18
  readonly types: readonly CreationType[];
19
+ readonly onView?: (creation: Creation) => void;
19
20
  readonly onShare: (creation: Creation) => void;
20
21
  readonly onDelete: (creation: Creation) => void;
21
22
  }
@@ -23,6 +24,7 @@ interface CreationCardProps {
23
24
  export function CreationCard({
24
25
  creation,
25
26
  types,
27
+ onView,
26
28
  onShare,
27
29
  onDelete,
28
30
  }: CreationCardProps) {
@@ -32,6 +34,7 @@ export function CreationCard({
32
34
  const icon = typeConfig?.icon || "🎨";
33
35
  const label = typeConfig?.labelKey || creation.type;
34
36
 
37
+ const handleView = useCallback(() => onView?.(creation), [creation, onView]);
35
38
  const handleShare = useCallback(() => onShare(creation), [creation, onShare]);
36
39
  const handleDelete = useCallback(
37
40
  () => onDelete(creation),
@@ -114,6 +117,11 @@ export function CreationCard({
114
117
  <AtomicText style={styles.dateText}>{formattedDate}</AtomicText>
115
118
  </View>
116
119
  <View style={styles.actions}>
120
+ {onView && (
121
+ <TouchableOpacity style={styles.actionBtn} onPress={handleView}>
122
+ <AtomicIcon name="eye" size="sm" color="primary" />
123
+ </TouchableOpacity>
124
+ )}
117
125
  <TouchableOpacity style={styles.actionBtn} onPress={handleShare}>
118
126
  <AtomicIcon name="share-social" size="sm" color="primary" />
119
127
  </TouchableOpacity>
@@ -3,8 +3,6 @@
3
3
  * Fetches user's creations from repository
4
4
  */
5
5
 
6
- declare const __DEV__: boolean;
7
-
8
6
  import { useQuery } from "@tanstack/react-query";
9
7
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
10
8
 
@@ -24,21 +22,11 @@ export function useCreations({
24
22
  repository,
25
23
  enabled = true,
26
24
  }: UseCreationsProps) {
27
- const result = useQuery({
25
+ return useQuery({
28
26
  queryKey: ["creations", userId ?? ""],
29
27
  queryFn: () => repository.getAll(userId!),
30
28
  enabled: !!userId && enabled,
31
29
  staleTime: CACHE_CONFIG.staleTime,
32
30
  gcTime: CACHE_CONFIG.gcTime,
33
31
  });
34
-
35
- if (__DEV__) {
36
- console.log("[useCreations] State:", {
37
- isLoading: result.isLoading,
38
- count: result.data?.length ?? 0,
39
- enabled: !!userId && enabled,
40
- });
41
- }
42
-
43
- return result;
44
32
  }
@@ -3,12 +3,11 @@
3
3
  * Full gallery view with filtering
4
4
  */
5
5
 
6
- declare const __DEV__: boolean;
7
-
8
- import React, { useMemo, useCallback } from "react";
6
+ import React, { useMemo, useCallback, useState } from "react";
9
7
  import { View, FlatList, StyleSheet, RefreshControl, Alert } from "react-native";
10
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
11
9
  import { useSharing } from "@umituz/react-native-sharing";
10
+ import { ImageGallery } from "@umituz/react-native-image";
12
11
  import { useSafeAreaInsets } from "react-native-safe-area-context";
13
12
  import type { Creation } from "../../domain/entities/Creation";
14
13
  import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
@@ -36,6 +35,8 @@ export function CreationsGalleryScreen({
36
35
  const tokens = useAppDesignTokens();
37
36
  const insets = useSafeAreaInsets();
38
37
  const { share } = useSharing();
38
+ const [viewerVisible, setViewerVisible] = useState(false);
39
+ const [viewerIndex, setViewerIndex] = useState(0);
39
40
 
40
41
  const { data: creations, isLoading, refetch } = useCreations({
41
42
  userId,
@@ -46,13 +47,16 @@ export function CreationsGalleryScreen({
46
47
  const { filtered, selectedType, availableTypes, selectType } =
47
48
  useCreationsFilter({ creations });
48
49
 
49
- if (__DEV__) {
50
- console.log("[CreationsGalleryScreen] Render:", {
51
- count: creations?.length ?? 0,
52
- filtered: filtered.length,
53
- selectedType,
54
- });
55
- }
50
+ const handleView = useCallback(
51
+ (creation: Creation) => {
52
+ const index = filtered.findIndex((c) => c.id === creation.id);
53
+ if (index >= 0) {
54
+ setViewerIndex(index);
55
+ setViewerVisible(true);
56
+ }
57
+ },
58
+ [filtered],
59
+ );
56
60
 
57
61
  const handleShare = useCallback(
58
62
  async (creation: Creation) => {
@@ -102,16 +106,22 @@ export function CreationsGalleryScreen({
102
106
  [tokens, insets.bottom],
103
107
  );
104
108
 
109
+ const viewerImages = useMemo(
110
+ () => filtered.map((creation) => ({ uri: creation.uri })),
111
+ [filtered],
112
+ );
113
+
105
114
  const renderItem = useCallback(
106
115
  ({ item }: { item: Creation }) => (
107
116
  <CreationCard
108
117
  creation={item}
109
118
  types={config.types}
119
+ onView={handleView}
110
120
  onShare={handleShare}
111
121
  onDelete={handleDelete}
112
122
  />
113
123
  ),
114
- [config.types, handleShare, handleDelete],
124
+ [config.types, handleView, handleShare, handleDelete],
115
125
  );
116
126
 
117
127
  if (!isLoading && (!creations || creations.length === 0)) {
@@ -149,6 +159,13 @@ export function CreationsGalleryScreen({
149
159
  />
150
160
  }
151
161
  />
162
+ <ImageGallery
163
+ images={viewerImages}
164
+ visible={viewerVisible}
165
+ index={viewerIndex}
166
+ onDismiss={() => setViewerVisible(false)}
167
+ onIndexChange={setViewerIndex}
168
+ />
152
169
  </View>
153
170
  );
154
171
  }
package/src/types.d.ts CHANGED
@@ -2,6 +2,41 @@
2
2
  * Type declarations for external modules
3
3
  */
4
4
 
5
+ declare module "@umituz/react-native-firestore" {
6
+ import type { Firestore } from "firebase/firestore";
7
+
8
+ export class BaseRepository {
9
+ protected getDb(): Firestore | null;
10
+ protected getDbOrThrow(): Firestore;
11
+ protected isDbInitialized(): boolean;
12
+ protected isQuotaError(error: unknown): boolean;
13
+ protected handleQuotaError(error: unknown): never;
14
+ protected executeWithQuotaHandling<T>(
15
+ operation: () => Promise<T>,
16
+ ): Promise<T>;
17
+ protected trackRead(
18
+ collection: string,
19
+ count: number,
20
+ cached: boolean,
21
+ ): void;
22
+ protected trackWrite(
23
+ collection: string,
24
+ docId: string,
25
+ count: number,
26
+ ): void;
27
+ protected trackDelete(
28
+ collection: string,
29
+ docId: string,
30
+ count: number,
31
+ ): void;
32
+ destroy(): void;
33
+ }
34
+
35
+ export function getFirestore(): Firestore | null;
36
+ export function initializeFirestore(app: unknown): void;
37
+ export function isFirestoreInitialized(): boolean;
38
+ }
39
+
5
40
  declare module "@umituz/react-native-design-system" {
6
41
  import type { FC, ReactNode } from "react";
7
42
  import type { StyleProp, ViewStyle, TextStyle } from "react-native";