@umituz/react-native-ai-generation-content 1.12.4 → 1.12.6

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 (67) hide show
  1. package/package.json +25 -6
  2. package/src/domains/creations/application/services/CreationsService.ts +71 -0
  3. package/src/domains/creations/domain/entities/Creation.ts +51 -0
  4. package/src/domains/creations/domain/entities/index.ts +6 -0
  5. package/src/domains/creations/domain/repositories/ICreationsRepository.ts +23 -0
  6. package/src/domains/creations/domain/repositories/index.ts +5 -0
  7. package/src/domains/creations/domain/services/ICreationsStorageService.ts +13 -0
  8. package/src/domains/creations/domain/value-objects/CreationsConfig.ts +76 -0
  9. package/src/domains/creations/domain/value-objects/index.ts +12 -0
  10. package/src/domains/creations/index.ts +84 -0
  11. package/src/domains/creations/infrastructure/adapters/createRepository.ts +54 -0
  12. package/src/domains/creations/infrastructure/adapters/index.ts +5 -0
  13. package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +233 -0
  14. package/src/domains/creations/infrastructure/repositories/index.ts +8 -0
  15. package/src/domains/creations/infrastructure/services/CreationsStorageService.ts +48 -0
  16. package/src/domains/creations/presentation/components/CreationCard.tsx +136 -0
  17. package/src/domains/creations/presentation/components/CreationDetail/DetailActions.tsx +76 -0
  18. package/src/domains/creations/presentation/components/CreationDetail/DetailHeader.tsx +81 -0
  19. package/src/domains/creations/presentation/components/CreationDetail/DetailImage.tsx +41 -0
  20. package/src/domains/creations/presentation/components/CreationDetail/DetailStory.tsx +67 -0
  21. package/src/domains/creations/presentation/components/CreationDetail/index.ts +4 -0
  22. package/src/domains/creations/presentation/components/CreationImageViewer.tsx +43 -0
  23. package/src/domains/creations/presentation/components/CreationThumbnail.tsx +63 -0
  24. package/src/domains/creations/presentation/components/CreationsGrid.tsx +75 -0
  25. package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +176 -0
  26. package/src/domains/creations/presentation/components/EmptyState.tsx +75 -0
  27. package/src/domains/creations/presentation/components/FilterBottomSheet.tsx +158 -0
  28. package/src/domains/creations/presentation/components/FilterChips.tsx +105 -0
  29. package/src/domains/creations/presentation/components/GalleryHeader.tsx +106 -0
  30. package/src/domains/creations/presentation/components/index.ts +19 -0
  31. package/src/domains/creations/presentation/hooks/index.ts +7 -0
  32. package/src/domains/creations/presentation/hooks/useCreations.ts +33 -0
  33. package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +70 -0
  34. package/src/domains/creations/presentation/hooks/useDeleteCreation.ts +51 -0
  35. package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +71 -0
  36. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +217 -0
  37. package/src/domains/creations/presentation/screens/index.ts +5 -0
  38. package/src/domains/creations/presentation/utils/filterUtils.ts +52 -0
  39. package/src/domains/creations/types.d.ts +107 -0
  40. package/src/domains/face-detection/domain/constants/faceDetectionConstants.ts +16 -0
  41. package/src/domains/face-detection/domain/entities/FaceDetection.ts +19 -0
  42. package/src/domains/face-detection/index.ts +26 -0
  43. package/src/domains/face-detection/infrastructure/analyzers/faceAnalyzer.ts +40 -0
  44. package/src/domains/face-detection/infrastructure/validators/faceValidator.ts +52 -0
  45. package/src/domains/face-detection/presentation/components/FaceValidationStatus.tsx +119 -0
  46. package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +58 -0
  47. package/src/domains/feature-background/domain/entities/background.types.ts +77 -0
  48. package/src/domains/feature-background/domain/entities/component.types.ts +96 -0
  49. package/src/domains/feature-background/domain/entities/config.types.ts +41 -0
  50. package/src/domains/feature-background/domain/entities/index.ts +31 -0
  51. package/src/domains/feature-background/index.ts +72 -0
  52. package/src/domains/feature-background/infrastructure/constants/index.ts +5 -0
  53. package/src/domains/feature-background/infrastructure/constants/prompts.constants.ts +15 -0
  54. package/src/domains/feature-background/presentation/components/BackgroundFeature.tsx +145 -0
  55. package/src/domains/feature-background/presentation/components/ComparisonSlider.tsx +199 -0
  56. package/src/domains/feature-background/presentation/components/ErrorDisplay.tsx +58 -0
  57. package/src/domains/feature-background/presentation/components/FeatureHeader.tsx +80 -0
  58. package/src/domains/feature-background/presentation/components/GenerateButton.tsx +86 -0
  59. package/src/domains/feature-background/presentation/components/ImagePicker.tsx +136 -0
  60. package/src/domains/feature-background/presentation/components/ModeSelector.tsx +78 -0
  61. package/src/domains/feature-background/presentation/components/ProcessingModal.tsx +113 -0
  62. package/src/domains/feature-background/presentation/components/PromptInput.tsx +142 -0
  63. package/src/domains/feature-background/presentation/components/ResultDisplay.tsx +123 -0
  64. package/src/domains/feature-background/presentation/components/index.ts +16 -0
  65. package/src/domains/feature-background/presentation/hooks/index.ts +7 -0
  66. package/src/domains/feature-background/presentation/hooks/useBackgroundFeature.ts +118 -0
  67. package/src/index.ts +18 -0
@@ -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,8 @@
1
+ /**
2
+ * Infrastructure Repositories
3
+ */
4
+
5
+ export {
6
+ CreationsRepository,
7
+ type RepositoryOptions,
8
+ } from "./CreationsRepository";
@@ -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
+ }
@@ -0,0 +1,76 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
4
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+
6
+ interface DetailActionsProps {
7
+ readonly onShare: () => void;
8
+ readonly onDelete: () => void;
9
+ readonly shareLabel: string;
10
+ readonly deleteLabel: string;
11
+ }
12
+
13
+ export const DetailActions: React.FC<DetailActionsProps> = ({
14
+ onShare,
15
+ onDelete,
16
+ shareLabel,
17
+ deleteLabel
18
+ }) => {
19
+ const tokens = useAppDesignTokens();
20
+ const styles = useStyles(tokens);
21
+
22
+ return (
23
+ <View style={styles.container}>
24
+ <TouchableOpacity
25
+ style={[styles.button, styles.shareButton]}
26
+ onPress={onShare}
27
+ activeOpacity={0.7}
28
+ >
29
+ <AtomicIcon name="share-social-outline" size="sm" color="onPrimary" />
30
+ <AtomicText style={styles.buttonText}>{shareLabel}</AtomicText>
31
+ </TouchableOpacity>
32
+
33
+ <TouchableOpacity
34
+ style={[styles.button, styles.deleteButton]}
35
+ onPress={onDelete}
36
+ activeOpacity={0.7}
37
+ >
38
+ <AtomicIcon name="trash-outline" size="sm" color="error" />
39
+ <AtomicText style={[styles.buttonText, { color: tokens.colors.error }]}>
40
+ {deleteLabel}
41
+ </AtomicText>
42
+ </TouchableOpacity>
43
+ </View>
44
+ );
45
+ };
46
+
47
+ const useStyles = (tokens: any) => StyleSheet.create({
48
+ container: {
49
+ flexDirection: 'row',
50
+ justifyContent: 'center',
51
+ gap: tokens.spacing.md,
52
+ paddingHorizontal: tokens.spacing.lg,
53
+ marginBottom: tokens.spacing.xxl,
54
+ },
55
+ button: {
56
+ flexDirection: 'row',
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ paddingVertical: 12,
60
+ paddingHorizontal: 24,
61
+ borderRadius: 16,
62
+ gap: 8,
63
+ minWidth: 120,
64
+ },
65
+ shareButton: {
66
+ backgroundColor: tokens.colors.primary,
67
+ },
68
+ deleteButton: {
69
+ backgroundColor: tokens.colors.error + '10',
70
+ },
71
+ buttonText: {
72
+ fontWeight: '700',
73
+ color: '#fff',
74
+ fontSize: 15,
75
+ },
76
+ });
@@ -0,0 +1,81 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
4
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
+
7
+ interface DetailHeaderProps {
8
+ readonly title: string;
9
+ readonly date: string;
10
+ readonly onClose: () => void;
11
+ }
12
+
13
+ export const DetailHeader: React.FC<DetailHeaderProps> = ({ title, date, onClose }) => {
14
+ const tokens = useAppDesignTokens();
15
+ const insets = useSafeAreaInsets();
16
+ const styles = useStyles(tokens, insets);
17
+
18
+ return (
19
+ <View style={styles.headerContainer}>
20
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
21
+ <AtomicIcon name="arrow-back" size={24} customColor={tokens.colors.textPrimary} />
22
+ </TouchableOpacity>
23
+
24
+ <View style={styles.titleContainer}>
25
+ <AtomicText style={styles.title} numberOfLines={1}>{title}</AtomicText>
26
+ <View style={styles.dateBadge}>
27
+ <AtomicIcon name="calendar-outline" size={12} customColor={tokens.colors.primary} />
28
+ <AtomicText style={styles.dateText}>{date}</AtomicText>
29
+ </View>
30
+ </View>
31
+
32
+ <View style={styles.placeholder} />
33
+ </View>
34
+ );
35
+ };
36
+
37
+ const useStyles = (tokens: any, insets: any) => StyleSheet.create({
38
+ headerContainer: {
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ paddingTop: insets.top + tokens.spacing.sm,
42
+ paddingBottom: tokens.spacing.md,
43
+ paddingHorizontal: tokens.spacing.md,
44
+ backgroundColor: tokens.colors.backgroundPrimary,
45
+ borderBottomWidth: 1,
46
+ borderBottomColor: tokens.colors.border,
47
+ zIndex: 10,
48
+ },
49
+ closeButton: {
50
+ padding: tokens.spacing.xs,
51
+ marginRight: tokens.spacing.sm,
52
+ },
53
+ titleContainer: {
54
+ flex: 1,
55
+ alignItems: 'center',
56
+ },
57
+ title: {
58
+ fontSize: 18,
59
+ fontWeight: '700',
60
+ color: tokens.colors.textPrimary,
61
+ marginBottom: 4,
62
+ textAlign: 'center',
63
+ },
64
+ dateBadge: {
65
+ flexDirection: 'row',
66
+ alignItems: 'center',
67
+ gap: 4,
68
+ paddingHorizontal: 10,
69
+ paddingVertical: 4,
70
+ borderRadius: 12,
71
+ backgroundColor: tokens.colors.primary + '15',
72
+ },
73
+ dateText: {
74
+ fontSize: 12,
75
+ fontWeight: '600',
76
+ color: tokens.colors.primary,
77
+ },
78
+ placeholder: {
79
+ width: 40,
80
+ },
81
+ });
@@ -0,0 +1,41 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, Image, Dimensions } from 'react-native';
4
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
+
6
+ interface DetailImageProps {
7
+ readonly uri: string;
8
+ }
9
+
10
+ const { width } = Dimensions.get('window');
11
+
12
+ export const DetailImage: React.FC<DetailImageProps> = ({ uri }) => {
13
+ const tokens = useAppDesignTokens();
14
+ const styles = useStyles(tokens);
15
+
16
+ return (
17
+ <View style={styles.container}>
18
+ <View style={styles.frame}>
19
+ <Image source={{ uri }} style={styles.image} resizeMode="cover" />
20
+ </View>
21
+ </View>
22
+ );
23
+ };
24
+
25
+ const useStyles = (tokens: any) => StyleSheet.create({
26
+ container: {
27
+ paddingHorizontal: tokens.spacing.lg,
28
+ marginVertical: tokens.spacing.lg,
29
+ },
30
+ frame: {
31
+ width: width - (tokens.spacing.lg * 2),
32
+ height: width - (tokens.spacing.lg * 2),
33
+ borderRadius: 24,
34
+ overflow: 'hidden',
35
+ backgroundColor: tokens.colors.surface,
36
+ },
37
+ image: {
38
+ width: '100%',
39
+ height: '100%',
40
+ },
41
+ });
@@ -0,0 +1,67 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet } from 'react-native';
4
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import { LinearGradient } from 'expo-linear-gradient';
6
+
7
+ interface DetailStoryProps {
8
+ readonly story: string;
9
+ }
10
+
11
+ export const DetailStory: React.FC<DetailStoryProps> = ({ story }) => {
12
+ const tokens = useAppDesignTokens();
13
+ const styles = useStyles(tokens);
14
+
15
+ if (!story) return null;
16
+
17
+ return (
18
+ <View style={styles.container}>
19
+ <LinearGradient
20
+ colors={[tokens.colors.primary + '15', tokens.colors.primary + '05']}
21
+ style={styles.gradient}
22
+ >
23
+ <AtomicText style={styles.quoteMark}>&quot;</AtomicText>
24
+ <AtomicText style={styles.text}>{story}</AtomicText>
25
+ <View style={styles.quoteEndRow}>
26
+ <AtomicText style={[styles.quoteMark, styles.quoteEnd]}>&quot;</AtomicText>
27
+ </View>
28
+ </LinearGradient>
29
+ </View>
30
+ );
31
+ };
32
+
33
+ const useStyles = (tokens: any) => StyleSheet.create({
34
+ container: {
35
+ paddingHorizontal: tokens.spacing.lg,
36
+ marginBottom: tokens.spacing.lg,
37
+ },
38
+ gradient: {
39
+ padding: tokens.spacing.lg,
40
+ borderRadius: 20,
41
+ borderWidth: 1,
42
+ borderColor: tokens.colors.primary + '20',
43
+ },
44
+ quoteMark: {
45
+ fontSize: 48,
46
+ lineHeight: 48,
47
+ color: tokens.colors.primary,
48
+ opacity: 0.4,
49
+ marginBottom: -16,
50
+ },
51
+ quoteEndRow: {
52
+ alignItems: 'flex-end',
53
+ marginTop: -16,
54
+ },
55
+ quoteEnd: {
56
+ marginBottom: 0,
57
+ },
58
+ text: {
59
+ fontSize: 16,
60
+ lineHeight: 26,
61
+ textAlign: 'center',
62
+ fontStyle: 'italic',
63
+ fontWeight: '500',
64
+ color: tokens.colors.textPrimary,
65
+ paddingBottom: 4,
66
+ },
67
+ });
@@ -0,0 +1,4 @@
1
+ export * from './DetailHeader';
2
+ export * from './DetailImage';
3
+ export * from './DetailStory';
4
+ export * from './DetailActions';