@umituz/react-native-ai-generation-content 1.12.3 → 1.12.5
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 +30 -6
- package/src/domains/content-moderation/domain/entities/moderation.types.ts +84 -0
- package/src/domains/content-moderation/domain/interfaces/content-filter.interface.ts +24 -0
- package/src/domains/content-moderation/index.ts +67 -0
- package/src/domains/content-moderation/infrastructure/rules/default-rules.data.ts +144 -0
- package/src/domains/content-moderation/infrastructure/rules/rules-registry.ts +75 -0
- package/src/domains/content-moderation/infrastructure/services/content-moderation.service.ts +150 -0
- package/src/domains/content-moderation/infrastructure/services/index.ts +8 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/base.moderator.ts +62 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +64 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/index.ts +10 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +144 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +64 -0
- package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +74 -0
- package/src/domains/content-moderation/infrastructure/services/pattern-matcher.service.ts +51 -0
- package/src/domains/content-moderation/presentation/exceptions/content-policy-violation.exception.ts +48 -0
- package/src/domains/creations/application/services/CreationsService.ts +71 -0
- package/src/domains/creations/domain/entities/Creation.ts +51 -0
- package/src/domains/creations/domain/entities/index.ts +6 -0
- package/src/domains/creations/domain/repositories/ICreationsRepository.ts +23 -0
- package/src/domains/creations/domain/repositories/index.ts +5 -0
- package/src/domains/creations/domain/services/ICreationsStorageService.ts +13 -0
- package/src/domains/creations/domain/value-objects/CreationsConfig.ts +76 -0
- package/src/domains/creations/domain/value-objects/index.ts +12 -0
- package/src/domains/creations/index.ts +84 -0
- package/src/domains/creations/infrastructure/adapters/createRepository.ts +54 -0
- package/src/domains/creations/infrastructure/adapters/index.ts +5 -0
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +233 -0
- package/src/domains/creations/infrastructure/repositories/index.ts +8 -0
- package/src/domains/creations/infrastructure/services/CreationsStorageService.ts +48 -0
- package/src/domains/creations/presentation/components/CreationCard.tsx +136 -0
- package/src/domains/creations/presentation/components/CreationDetail/DetailActions.tsx +76 -0
- package/src/domains/creations/presentation/components/CreationDetail/DetailHeader.tsx +81 -0
- package/src/domains/creations/presentation/components/CreationDetail/DetailImage.tsx +41 -0
- package/src/domains/creations/presentation/components/CreationDetail/DetailStory.tsx +67 -0
- package/src/domains/creations/presentation/components/CreationDetail/index.ts +4 -0
- package/src/domains/creations/presentation/components/CreationImageViewer.tsx +43 -0
- package/src/domains/creations/presentation/components/CreationThumbnail.tsx +63 -0
- package/src/domains/creations/presentation/components/CreationsGrid.tsx +75 -0
- package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +176 -0
- package/src/domains/creations/presentation/components/EmptyState.tsx +75 -0
- package/src/domains/creations/presentation/components/FilterBottomSheet.tsx +158 -0
- package/src/domains/creations/presentation/components/FilterChips.tsx +105 -0
- package/src/domains/creations/presentation/components/GalleryHeader.tsx +106 -0
- package/src/domains/creations/presentation/components/index.ts +19 -0
- package/src/domains/creations/presentation/hooks/index.ts +7 -0
- package/src/domains/creations/presentation/hooks/useCreations.ts +33 -0
- package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +70 -0
- package/src/domains/creations/presentation/hooks/useDeleteCreation.ts +51 -0
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +71 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +217 -0
- package/src/domains/creations/presentation/screens/index.ts +5 -0
- package/src/domains/creations/presentation/utils/filterUtils.ts +52 -0
- package/src/domains/creations/types.d.ts +107 -0
- package/src/domains/face-detection/domain/constants/faceDetectionConstants.ts +16 -0
- package/src/domains/face-detection/domain/entities/FaceDetection.ts +19 -0
- package/src/domains/face-detection/index.ts +26 -0
- package/src/domains/face-detection/infrastructure/analyzers/faceAnalyzer.ts +36 -0
- package/src/domains/face-detection/infrastructure/validators/faceValidator.ts +52 -0
- package/src/domains/face-detection/presentation/components/FaceValidationStatus.tsx +111 -0
- package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +58 -0
- package/src/domains/feature-background/domain/entities/background.types.ts +77 -0
- package/src/domains/feature-background/domain/entities/component.types.ts +96 -0
- package/src/domains/feature-background/domain/entities/config.types.ts +41 -0
- package/src/domains/feature-background/domain/entities/index.ts +31 -0
- package/src/domains/feature-background/index.ts +72 -0
- package/src/domains/feature-background/infrastructure/constants/index.ts +5 -0
- package/src/domains/feature-background/infrastructure/constants/prompts.constants.ts +15 -0
- package/src/domains/feature-background/presentation/components/BackgroundFeature.tsx +145 -0
- package/src/domains/feature-background/presentation/components/ComparisonSlider.tsx +199 -0
- package/src/domains/feature-background/presentation/components/ErrorDisplay.tsx +58 -0
- package/src/domains/feature-background/presentation/components/FeatureHeader.tsx +80 -0
- package/src/domains/feature-background/presentation/components/GenerateButton.tsx +86 -0
- package/src/domains/feature-background/presentation/components/ImagePicker.tsx +136 -0
- package/src/domains/feature-background/presentation/components/ModeSelector.tsx +78 -0
- package/src/domains/feature-background/presentation/components/ProcessingModal.tsx +113 -0
- package/src/domains/feature-background/presentation/components/PromptInput.tsx +142 -0
- package/src/domains/feature-background/presentation/components/ResultDisplay.tsx +123 -0
- package/src/domains/feature-background/presentation/components/index.ts +16 -0
- package/src/domains/feature-background/presentation/hooks/index.ts +7 -0
- package/src/domains/feature-background/presentation/hooks/useBackgroundFeature.ts +118 -0
- package/src/index.ts +24 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creations Configuration Value Object
|
|
3
|
+
* Defines the configuration for creations feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Creation, CreationDocument } from "../entities/Creation";
|
|
7
|
+
|
|
8
|
+
export interface CreationType {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly labelKey: string;
|
|
11
|
+
readonly icon: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CreationsTranslations {
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly subtitle: string;
|
|
17
|
+
readonly empty: string;
|
|
18
|
+
readonly emptyDescription: string;
|
|
19
|
+
readonly deleteTitle: string;
|
|
20
|
+
readonly deleteMessage: string;
|
|
21
|
+
readonly photoCount: string;
|
|
22
|
+
readonly filterAll: string;
|
|
23
|
+
readonly filterLabel: string;
|
|
24
|
+
readonly filterTitle: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Path builder function type
|
|
29
|
+
* Allows apps to define custom Firestore path structures
|
|
30
|
+
* @example
|
|
31
|
+
* // Default: users/{userId}/creations
|
|
32
|
+
* pathBuilder: (userId) => ["users", userId, "creations"]
|
|
33
|
+
* // Alternative: creations/{userId}/items
|
|
34
|
+
* pathBuilder: (userId) => ["creations", userId, "items"]
|
|
35
|
+
*/
|
|
36
|
+
export type PathBuilder = (userId: string) => string[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Document mapper function type
|
|
40
|
+
* Allows apps to map their specific document structure to Creation
|
|
41
|
+
*/
|
|
42
|
+
export type DocumentMapper = (id: string, data: CreationDocument) => Creation;
|
|
43
|
+
|
|
44
|
+
import type { FilterCategory } from "@umituz/react-native-bottom-sheet";
|
|
45
|
+
|
|
46
|
+
export interface CreationsConfig {
|
|
47
|
+
readonly collectionName: string;
|
|
48
|
+
readonly types: readonly CreationType[];
|
|
49
|
+
readonly filterCategories?: readonly FilterCategory[];
|
|
50
|
+
readonly translations: CreationsTranslations;
|
|
51
|
+
readonly maxThumbnails?: number;
|
|
52
|
+
readonly gridColumns?: number;
|
|
53
|
+
readonly pathBuilder?: PathBuilder;
|
|
54
|
+
readonly documentMapper?: DocumentMapper;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const DEFAULT_TRANSLATIONS: CreationsTranslations = {
|
|
58
|
+
title: "creations.title",
|
|
59
|
+
subtitle: "creations.subtitle",
|
|
60
|
+
empty: "creations.empty",
|
|
61
|
+
emptyDescription: "creations.emptyDescription",
|
|
62
|
+
deleteTitle: "creations.deleteTitle",
|
|
63
|
+
deleteMessage: "creations.deleteMessage",
|
|
64
|
+
photoCount: "creations.photoCount",
|
|
65
|
+
filterAll: "creations.filterAll",
|
|
66
|
+
filterLabel: "common.filter",
|
|
67
|
+
filterTitle: "common.filter",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const DEFAULT_CONFIG: CreationsConfig = {
|
|
71
|
+
collectionName: "creations",
|
|
72
|
+
types: [],
|
|
73
|
+
translations: DEFAULT_TRANSLATIONS,
|
|
74
|
+
maxThumbnails: 4,
|
|
75
|
+
gridColumns: 2,
|
|
76
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-ai-creations
|
|
3
|
+
*
|
|
4
|
+
* AI-generated creations gallery with filtering, sharing, and management
|
|
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
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* import {
|
|
16
|
+
* CreationsGalleryScreen,
|
|
17
|
+
* CreationsHomeCard,
|
|
18
|
+
* useCreations,
|
|
19
|
+
* createCreationsRepository,
|
|
20
|
+
* } from '@umituz/react-native-ai-creations';
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// DOMAIN LAYER - Entities
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
export type { Creation, CreationDocument } from "./domain/entities";
|
|
28
|
+
export { mapDocumentToCreation } from "./domain/entities";
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// DOMAIN LAYER - Value Objects
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
export type {
|
|
35
|
+
CreationType,
|
|
36
|
+
CreationsTranslations,
|
|
37
|
+
CreationsConfig,
|
|
38
|
+
PathBuilder,
|
|
39
|
+
DocumentMapper,
|
|
40
|
+
} from "./domain/value-objects";
|
|
41
|
+
export { DEFAULT_TRANSLATIONS, DEFAULT_CONFIG } from "./domain/value-objects";
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// DOMAIN LAYER - Repository Interface
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
export type { ICreationsRepository } from "./domain/repositories";
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// INFRASTRUCTURE LAYER
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
CreationsRepository,
|
|
55
|
+
type RepositoryOptions,
|
|
56
|
+
} from "./infrastructure/repositories";
|
|
57
|
+
export { CreationsStorageService } from "./infrastructure/services/CreationsStorageService";
|
|
58
|
+
export { createCreationsRepository } from "./infrastructure/adapters";
|
|
59
|
+
export { CreationsService } from "./application/services/CreationsService";
|
|
60
|
+
export type { ICreationsStorageService } from "./domain/services/ICreationsStorageService";
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// PRESENTATION LAYER - Hooks
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
export { useCreations } from "./presentation/hooks/useCreations";
|
|
67
|
+
export { useDeleteCreation } from "./presentation/hooks/useDeleteCreation";
|
|
68
|
+
export { useCreationsFilter } from "./presentation/hooks/useCreationsFilter";
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// PRESENTATION LAYER - Components
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
export { CreationThumbnail } from "./presentation/components/CreationThumbnail";
|
|
75
|
+
export { CreationCard } from "./presentation/components/CreationCard";
|
|
76
|
+
export { CreationsHomeCard } from "./presentation/components/CreationsHomeCard";
|
|
77
|
+
export { FilterChips } from "./presentation/components/FilterChips";
|
|
78
|
+
export { EmptyState } from "./presentation/components/EmptyState";
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// PRESENTATION LAYER - Screens
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
export { CreationsGalleryScreen } from "./presentation/screens/CreationsGalleryScreen";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Factory
|
|
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.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
CreationsRepository,
|
|
16
|
+
type RepositoryOptions,
|
|
17
|
+
} from "../repositories/CreationsRepository";
|
|
18
|
+
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
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
|
+
*/
|
|
49
|
+
export function createCreationsRepository(
|
|
50
|
+
collectionName: string,
|
|
51
|
+
options?: RepositoryOptions,
|
|
52
|
+
): ICreationsRepository {
|
|
53
|
+
return new CreationsRepository(collectionName, options);
|
|
54
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creations Repository Implementation
|
|
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.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
collection,
|
|
18
|
+
doc,
|
|
19
|
+
getDocs,
|
|
20
|
+
getDoc,
|
|
21
|
+
deleteDoc,
|
|
22
|
+
updateDoc,
|
|
23
|
+
query,
|
|
24
|
+
orderBy,
|
|
25
|
+
setDoc,
|
|
26
|
+
} from "firebase/firestore";
|
|
27
|
+
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
28
|
+
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
29
|
+
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
30
|
+
import { mapDocumentToCreation } from "../../domain/entities/Creation";
|
|
31
|
+
import type {
|
|
32
|
+
PathBuilder,
|
|
33
|
+
DocumentMapper,
|
|
34
|
+
} from "../../domain/value-objects/CreationsConfig";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Repository options for dynamic configuration
|
|
38
|
+
* Apps can customize path structure and document mapping
|
|
39
|
+
*/
|
|
40
|
+
export interface RepositoryOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Custom path builder function
|
|
43
|
+
* @example (userId) => ["users", userId, "photos"]
|
|
44
|
+
* @example (userId) => ["creations", userId, "items"]
|
|
45
|
+
*/
|
|
46
|
+
readonly pathBuilder?: PathBuilder;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Custom document mapper function
|
|
50
|
+
* Maps Firestore documents to Creation entity
|
|
51
|
+
*/
|
|
52
|
+
readonly documentMapper?: DocumentMapper;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default path builder: users/{userId}/{collectionName}
|
|
57
|
+
*/
|
|
58
|
+
const createDefaultPathBuilder =
|
|
59
|
+
(collectionName: string): PathBuilder =>
|
|
60
|
+
(userId: string) =>
|
|
61
|
+
["users", userId, collectionName];
|
|
62
|
+
|
|
63
|
+
export class CreationsRepository
|
|
64
|
+
extends BaseRepository
|
|
65
|
+
implements ICreationsRepository {
|
|
66
|
+
private readonly pathBuilder: PathBuilder;
|
|
67
|
+
private readonly documentMapper: DocumentMapper;
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
private readonly collectionName: string,
|
|
71
|
+
options?: RepositoryOptions,
|
|
72
|
+
) {
|
|
73
|
+
super();
|
|
74
|
+
this.pathBuilder =
|
|
75
|
+
options?.pathBuilder ?? createDefaultPathBuilder(collectionName);
|
|
76
|
+
this.documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getUserCollection(userId: string) {
|
|
80
|
+
const db = this.getDb();
|
|
81
|
+
if (!db) return null;
|
|
82
|
+
const pathSegments = this.pathBuilder(userId);
|
|
83
|
+
return collection(db, pathSegments[0], ...pathSegments.slice(1));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private getDocRef(userId: string, creationId: string) {
|
|
87
|
+
const db = this.getDb();
|
|
88
|
+
if (!db) return null;
|
|
89
|
+
const pathSegments = this.pathBuilder(userId);
|
|
90
|
+
return doc(db, pathSegments[0], ...pathSegments.slice(1), creationId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getAll(userId: string): Promise<Creation[]> {
|
|
94
|
+
if (__DEV__) {
|
|
95
|
+
console.log("[CreationsRepository] getAll()", { userId });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const userCollection = this.getUserCollection(userId);
|
|
99
|
+
if (!userCollection) return [];
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const q = query(userCollection, orderBy("createdAt", "desc"));
|
|
103
|
+
const snapshot = await getDocs(q);
|
|
104
|
+
|
|
105
|
+
if (__DEV__) {
|
|
106
|
+
console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return snapshot.docs.map((docSnap) => {
|
|
110
|
+
const data = docSnap.data() as CreationDocument;
|
|
111
|
+
return this.documentMapper(docSnap.id, data);
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (__DEV__) {
|
|
115
|
+
console.error("[CreationsRepository] getAll() ERROR", error);
|
|
116
|
+
}
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
122
|
+
if (__DEV__) {
|
|
123
|
+
console.log("[CreationsRepository] getById()", { userId, id });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const docRef = this.getDocRef(userId, id);
|
|
127
|
+
if (!docRef) return null;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const docSnap = await getDoc(docRef);
|
|
131
|
+
|
|
132
|
+
if (!docSnap.exists()) {
|
|
133
|
+
if (__DEV__) {
|
|
134
|
+
console.log("[CreationsRepository] Document not found");
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = docSnap.data() as CreationDocument;
|
|
140
|
+
return this.documentMapper(docSnap.id, data);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (__DEV__) {
|
|
143
|
+
console.error("[CreationsRepository] getById() ERROR", error);
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async create(userId: string, creation: Creation): Promise<void> {
|
|
150
|
+
const docRef = this.getDocRef(userId, creation.id);
|
|
151
|
+
if (!docRef) throw new Error("Firestore not initialized");
|
|
152
|
+
|
|
153
|
+
const data: CreationDocument = {
|
|
154
|
+
type: creation.type,
|
|
155
|
+
prompt: creation.prompt,
|
|
156
|
+
uri: creation.uri, // Use uri
|
|
157
|
+
createdAt: creation.createdAt,
|
|
158
|
+
metadata: creation.metadata || {},
|
|
159
|
+
isShared: creation.isShared || false,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
await setDoc(docRef, data);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async update(
|
|
166
|
+
userId: string,
|
|
167
|
+
id: string,
|
|
168
|
+
updates: Partial<Creation>,
|
|
169
|
+
): Promise<boolean> {
|
|
170
|
+
if (__DEV__) {
|
|
171
|
+
console.log("[CreationsRepository] update()", { userId, id, updates });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const docRef = this.getDocRef(userId, id);
|
|
175
|
+
if (!docRef) return false;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const updateData: Record<string, any> = {};
|
|
179
|
+
|
|
180
|
+
if (updates.metadata !== undefined) {
|
|
181
|
+
updateData.metadata = updates.metadata;
|
|
182
|
+
}
|
|
183
|
+
if (updates.isShared !== undefined) {
|
|
184
|
+
updateData.isShared = updates.isShared;
|
|
185
|
+
}
|
|
186
|
+
if (updates.uri !== undefined) {
|
|
187
|
+
updateData.uri = updates.uri;
|
|
188
|
+
}
|
|
189
|
+
if (updates.type !== undefined) {
|
|
190
|
+
updateData.type = updates.type;
|
|
191
|
+
}
|
|
192
|
+
if (updates.prompt !== undefined) {
|
|
193
|
+
updateData.prompt = updates.prompt;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await updateDoc(docRef, updateData);
|
|
197
|
+
return true;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (__DEV__) {
|
|
200
|
+
console.error("[CreationsRepository] update() ERROR", error);
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async delete(userId: string, creationId: string): Promise<boolean> {
|
|
207
|
+
const docRef = this.getDocRef(userId, creationId);
|
|
208
|
+
if (!docRef) return false;
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await deleteDoc(docRef);
|
|
212
|
+
return true;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async updateShared(
|
|
219
|
+
userId: string,
|
|
220
|
+
creationId: string,
|
|
221
|
+
isShared: boolean,
|
|
222
|
+
): Promise<boolean> {
|
|
223
|
+
const docRef = this.getDocRef(userId, creationId);
|
|
224
|
+
if (!docRef) return false;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await updateDoc(docRef, { isShared });
|
|
228
|
+
return true;
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uploadFile,
|
|
3
|
+
uploadBase64Image,
|
|
4
|
+
} from "@umituz/react-native-firebase";
|
|
5
|
+
import type { ICreationsStorageService } from "../../domain/services/ICreationsStorageService";
|
|
6
|
+
|
|
7
|
+
declare const __DEV__: boolean;
|
|
8
|
+
|
|
9
|
+
export class CreationsStorageService implements ICreationsStorageService {
|
|
10
|
+
constructor(private readonly storagePathPrefix: string = "creations") { }
|
|
11
|
+
|
|
12
|
+
private getPath(userId: string, creationId: string): string {
|
|
13
|
+
return `${this.storagePathPrefix}/${userId}/${creationId}.jpg`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async uploadCreationImage(
|
|
17
|
+
userId: string,
|
|
18
|
+
creationId: string,
|
|
19
|
+
imageUri: string,
|
|
20
|
+
mimeType: string = "image/jpeg"
|
|
21
|
+
): Promise<string> {
|
|
22
|
+
const path = this.getPath(userId, creationId);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (imageUri.startsWith("data:")) {
|
|
26
|
+
const result = await uploadBase64Image(imageUri, path, { mimeType });
|
|
27
|
+
return result.downloadUrl;
|
|
28
|
+
}
|
|
29
|
+
const result = await uploadFile(imageUri, path, { mimeType });
|
|
30
|
+
return result.downloadUrl;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (__DEV__) {
|
|
33
|
+
console.error("[CreationsStorageService] upload failed", error);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async deleteCreationImage(
|
|
40
|
+
userId: string,
|
|
41
|
+
creationId: string
|
|
42
|
+
): Promise<boolean> {
|
|
43
|
+
// Delete logic not strictly required for saving loop, but good to have
|
|
44
|
+
// Needs storage reference delete implementation in rn-firebase first
|
|
45
|
+
// For now we skip implementing delete in this iteration as priority is saving
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreationCard Component
|
|
3
|
+
* Displays a creation item with actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo, useCallback } from "react";
|
|
7
|
+
import { View, Image, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
14
|
+
import type { CreationType } from "../../domain/value-objects/CreationsConfig";
|
|
15
|
+
|
|
16
|
+
interface CreationCardProps {
|
|
17
|
+
readonly creation: Creation;
|
|
18
|
+
readonly types: readonly CreationType[];
|
|
19
|
+
readonly onView?: (creation: Creation) => void;
|
|
20
|
+
readonly onShare: (creation: Creation) => void;
|
|
21
|
+
readonly onDelete: (creation: Creation) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function CreationCard({
|
|
25
|
+
creation,
|
|
26
|
+
types,
|
|
27
|
+
onView,
|
|
28
|
+
onShare,
|
|
29
|
+
onDelete,
|
|
30
|
+
}: CreationCardProps) {
|
|
31
|
+
const tokens = useAppDesignTokens();
|
|
32
|
+
|
|
33
|
+
const typeConfig = types.find((t) => t.id === creation.type);
|
|
34
|
+
const icon = typeConfig?.icon || "🎨";
|
|
35
|
+
const label = typeConfig?.labelKey || creation.type;
|
|
36
|
+
|
|
37
|
+
const handleView = useCallback(() => onView?.(creation), [creation, onView]);
|
|
38
|
+
const handleShare = useCallback(() => onShare(creation), [creation, onShare]);
|
|
39
|
+
const handleDelete = useCallback(
|
|
40
|
+
() => onDelete(creation),
|
|
41
|
+
[creation, onDelete],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const formattedDate = useMemo(() => {
|
|
45
|
+
const date =
|
|
46
|
+
creation.createdAt instanceof Date
|
|
47
|
+
? creation.createdAt
|
|
48
|
+
: new Date(creation.createdAt);
|
|
49
|
+
return date.toLocaleDateString(undefined, {
|
|
50
|
+
day: "numeric",
|
|
51
|
+
month: "short",
|
|
52
|
+
hour: "2-digit",
|
|
53
|
+
minute: "2-digit",
|
|
54
|
+
});
|
|
55
|
+
}, [creation.createdAt]);
|
|
56
|
+
|
|
57
|
+
const styles = useMemo(
|
|
58
|
+
() =>
|
|
59
|
+
StyleSheet.create({
|
|
60
|
+
container: {
|
|
61
|
+
flexDirection: "row",
|
|
62
|
+
backgroundColor: tokens.colors.surface,
|
|
63
|
+
borderRadius: tokens.spacing.md,
|
|
64
|
+
overflow: "hidden",
|
|
65
|
+
marginBottom: tokens.spacing.md,
|
|
66
|
+
},
|
|
67
|
+
thumbnail: {
|
|
68
|
+
width: 100,
|
|
69
|
+
height: 100,
|
|
70
|
+
},
|
|
71
|
+
content: {
|
|
72
|
+
flex: 1,
|
|
73
|
+
padding: tokens.spacing.md,
|
|
74
|
+
justifyContent: "space-between",
|
|
75
|
+
},
|
|
76
|
+
typeRow: {
|
|
77
|
+
flexDirection: "row",
|
|
78
|
+
alignItems: "center",
|
|
79
|
+
gap: tokens.spacing.sm,
|
|
80
|
+
},
|
|
81
|
+
icon: {
|
|
82
|
+
fontSize: 20,
|
|
83
|
+
},
|
|
84
|
+
typeText: {
|
|
85
|
+
...tokens.typography.bodyMedium,
|
|
86
|
+
fontWeight: "600",
|
|
87
|
+
color: tokens.colors.textPrimary,
|
|
88
|
+
},
|
|
89
|
+
dateText: {
|
|
90
|
+
...tokens.typography.bodySmall,
|
|
91
|
+
color: tokens.colors.textSecondary,
|
|
92
|
+
},
|
|
93
|
+
actions: {
|
|
94
|
+
flexDirection: "row",
|
|
95
|
+
gap: tokens.spacing.sm,
|
|
96
|
+
},
|
|
97
|
+
actionBtn: {
|
|
98
|
+
width: 36,
|
|
99
|
+
height: 36,
|
|
100
|
+
borderRadius: 18,
|
|
101
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
102
|
+
justifyContent: "center",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
[tokens],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<View style={styles.container}>
|
|
111
|
+
<Image source={{ uri: creation.uri }} style={styles.thumbnail} />
|
|
112
|
+
<View style={styles.content}>
|
|
113
|
+
<View>
|
|
114
|
+
<View style={styles.typeRow}>
|
|
115
|
+
<AtomicText style={styles.icon}>{icon}</AtomicText>
|
|
116
|
+
<AtomicText style={styles.typeText}>{label}</AtomicText>
|
|
117
|
+
</View>
|
|
118
|
+
<AtomicText style={styles.dateText}>{formattedDate}</AtomicText>
|
|
119
|
+
</View>
|
|
120
|
+
<View style={styles.actions}>
|
|
121
|
+
{onView && (
|
|
122
|
+
<TouchableOpacity style={styles.actionBtn} onPress={handleView}>
|
|
123
|
+
<AtomicIcon name="eye" size="sm" color="primary" />
|
|
124
|
+
</TouchableOpacity>
|
|
125
|
+
)}
|
|
126
|
+
<TouchableOpacity style={styles.actionBtn} onPress={handleShare}>
|
|
127
|
+
<AtomicIcon name="share-social" size="sm" color="primary" />
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
<TouchableOpacity style={styles.actionBtn} onPress={handleDelete}>
|
|
130
|
+
<AtomicIcon name="trash" size="sm" color="error" />
|
|
131
|
+
</TouchableOpacity>
|
|
132
|
+
</View>
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
);
|
|
136
|
+
}
|