@umituz/react-native-ai-generation-content 1.12.37 → 1.12.39
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 +2 -2
- package/src/domains/creations/domain/value-objects/CreationsConfig.ts +1 -13
- package/src/domains/creations/infrastructure/adapters/createRepository.ts +3 -8
- package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +1 -1
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +6 -20
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +1 -1
- package/src/domains/creations/presentation/components/index.ts +0 -1
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +12 -4
- package/src/domains/creations/presentation/utils/filterUtils.ts +2 -2
- package/src/domains/creations/infrastructure/repositories/FirestorePathResolver.ts +0 -26
- package/src/domains/creations/presentation/components/FilterBottomSheet.tsx +0 -171
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.39",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@typescript-eslint/parser": "^7.0.0",
|
|
69
69
|
"@umituz/react-native-animation": "*",
|
|
70
70
|
"@umituz/react-native-design-system": "^2.3.33",
|
|
71
|
-
"@umituz/react-native-firebase": "
|
|
71
|
+
"@umituz/react-native-firebase": "^1.13.20",
|
|
72
72
|
"@umituz/react-native-haptics": "^1.0.2",
|
|
73
73
|
"@umituz/react-native-image": "*",
|
|
74
74
|
"@umituz/react-native-offline": "*",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Creation, CreationDocument } from "../entities/Creation";
|
|
7
|
-
import type { FilterCategory } from "
|
|
7
|
+
import type { FilterCategory } from "@umituz/react-native-design-system";
|
|
8
8
|
|
|
9
9
|
export interface CreationType {
|
|
10
10
|
readonly id: string;
|
|
@@ -25,17 +25,6 @@ export interface CreationsTranslations {
|
|
|
25
25
|
readonly filterTitle: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Path builder function type
|
|
30
|
-
* Allows apps to define custom Firestore path structures
|
|
31
|
-
* @example
|
|
32
|
-
* // Default: users/{userId}/creations
|
|
33
|
-
* pathBuilder: (userId) => ["users", userId, "creations"]
|
|
34
|
-
* // Alternative: creations/{userId}/items
|
|
35
|
-
* pathBuilder: (userId) => ["creations", userId, "items"]
|
|
36
|
-
*/
|
|
37
|
-
export type PathBuilder = (userId: string) => string[];
|
|
38
|
-
|
|
39
28
|
/**
|
|
40
29
|
* Document mapper function type
|
|
41
30
|
* Allows apps to map their specific document structure to Creation
|
|
@@ -49,7 +38,6 @@ export interface CreationsConfig {
|
|
|
49
38
|
readonly translations: CreationsTranslations;
|
|
50
39
|
readonly maxThumbnails?: number;
|
|
51
40
|
readonly gridColumns?: number;
|
|
52
|
-
readonly pathBuilder?: PathBuilder;
|
|
53
41
|
readonly documentMapper?: DocumentMapper;
|
|
54
42
|
}
|
|
55
43
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Architecture:
|
|
6
6
|
* - Factory pattern for repository creation
|
|
7
|
-
* -
|
|
7
|
+
* - Standard path: users/{userId}/{collectionName}
|
|
8
8
|
* - Supports custom document mapping per app
|
|
9
9
|
* - App-agnostic: No Firestore instance needed (BaseRepository handles it)
|
|
10
10
|
*
|
|
@@ -25,16 +25,10 @@ import type { ICreationsRepository } from "../../domain/repositories/ICreationsR
|
|
|
25
25
|
* @returns ICreationsRepository instance
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
|
-
* // Basic usage
|
|
28
|
+
* // Basic usage (path: users/{userId}/photos)
|
|
29
29
|
* const repo = createCreationsRepository("photos");
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
|
-
* // Custom path structure
|
|
33
|
-
* const repo = createCreationsRepository("creations", {
|
|
34
|
-
* pathBuilder: (userId) => ["gallery", userId, "items"],
|
|
35
|
-
* });
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
32
|
* // Custom document mapper
|
|
39
33
|
* const repo = createCreationsRepository("photos", {
|
|
40
34
|
* documentMapper: (id, data) => ({
|
|
@@ -43,6 +37,7 @@ import type { ICreationsRepository } from "../../domain/repositories/ICreationsR
|
|
|
43
37
|
* type: data.category,
|
|
44
38
|
* createdAt: data.timestamp?.toDate() || new Date(),
|
|
45
39
|
* isShared: data.public ?? false,
|
|
40
|
+
* isFavorite: data.favorite ?? false,
|
|
46
41
|
* }),
|
|
47
42
|
* });
|
|
48
43
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getDocs, getDoc, query, orderBy } from "firebase/firestore";
|
|
2
|
+
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
2
3
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
3
4
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
4
|
-
import type { FirestorePathResolver } from "./FirestorePathResolver";
|
|
5
5
|
|
|
6
6
|
declare const __DEV__: boolean;
|
|
7
7
|
|
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
1
|
+
import { BaseRepository, FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
2
2
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
3
3
|
import type { Creation } from "../../domain/entities/Creation";
|
|
4
4
|
import { mapDocumentToCreation } from "../../domain/entities/Creation";
|
|
5
|
-
import type {
|
|
6
|
-
PathBuilder,
|
|
7
|
-
DocumentMapper,
|
|
8
|
-
} from "../../domain/value-objects/CreationsConfig";
|
|
9
|
-
import { FirestorePathResolver } from "./FirestorePathResolver";
|
|
5
|
+
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
10
6
|
import { CreationsFetcher } from "./CreationsFetcher";
|
|
11
7
|
import { CreationsWriter } from "./CreationsWriter";
|
|
12
8
|
|
|
13
9
|
/**
|
|
14
10
|
* Repository options for dynamic configuration
|
|
15
|
-
* Apps can customize
|
|
11
|
+
* Apps can customize document mapping
|
|
16
12
|
*/
|
|
17
13
|
export interface RepositoryOptions {
|
|
18
|
-
readonly pathBuilder?: PathBuilder;
|
|
19
14
|
readonly documentMapper?: DocumentMapper;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
/**
|
|
23
|
-
* Default path builder: users/{userId}/{collectionName}
|
|
24
|
-
*/
|
|
25
|
-
const createDefaultPathBuilder =
|
|
26
|
-
(collectionName: string): PathBuilder =>
|
|
27
|
-
(userId: string) =>
|
|
28
|
-
["users", userId, collectionName];
|
|
29
|
-
|
|
30
17
|
/**
|
|
31
18
|
* Creations Repository Implementation
|
|
32
19
|
* Delegates to specialized classes for different responsibilities
|
|
@@ -36,7 +23,7 @@ const createDefaultPathBuilder =
|
|
|
36
23
|
* - Uses FirestorePathResolver for path resolution
|
|
37
24
|
* - Uses CreationsFetcher for read operations
|
|
38
25
|
* - Uses CreationsWriter for write operations
|
|
39
|
-
* -
|
|
26
|
+
* - Standard path: users/{userId}/{collectionName}
|
|
40
27
|
*/
|
|
41
28
|
export class CreationsRepository
|
|
42
29
|
extends BaseRepository
|
|
@@ -46,15 +33,14 @@ export class CreationsRepository
|
|
|
46
33
|
private readonly writer: CreationsWriter;
|
|
47
34
|
|
|
48
35
|
constructor(
|
|
49
|
-
|
|
36
|
+
collectionName: string,
|
|
50
37
|
options?: RepositoryOptions,
|
|
51
38
|
) {
|
|
52
39
|
super();
|
|
53
40
|
|
|
54
|
-
const pathBuilder = options?.pathBuilder ?? createDefaultPathBuilder(_collectionName);
|
|
55
41
|
const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
56
42
|
|
|
57
|
-
this.pathResolver = new FirestorePathResolver(
|
|
43
|
+
this.pathResolver = new FirestorePathResolver(collectionName, this.getDb());
|
|
58
44
|
this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
|
|
59
45
|
this.writer = new CreationsWriter(this.pathResolver);
|
|
60
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
|
|
2
|
+
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
2
3
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
3
|
-
import type { FirestorePathResolver } from "./FirestorePathResolver";
|
|
4
4
|
|
|
5
5
|
declare const __DEV__: boolean;
|
|
6
6
|
|
|
@@ -11,7 +11,6 @@ export { CreationCard } from "./CreationCard";
|
|
|
11
11
|
export { CreationThumbnail } from "./CreationThumbnail";
|
|
12
12
|
export { CreationImageViewer } from "./CreationImageViewer";
|
|
13
13
|
export { CreationsGrid } from "./CreationsGrid";
|
|
14
|
-
export { FilterBottomSheet, type FilterCategory, type FilterOption } from "./FilterBottomSheet";
|
|
15
14
|
|
|
16
15
|
// Detail Components
|
|
17
16
|
export { DetailHeader } from "./CreationDetail/DetailHeader";
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import React, { useMemo, useCallback, useState } from "react";
|
|
2
2
|
import { View, StyleSheet, type LayoutChangeEvent } from "react-native";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
useAppDesignTokens,
|
|
5
|
+
useAlert,
|
|
6
|
+
AlertType,
|
|
7
|
+
AlertMode,
|
|
8
|
+
useSharing,
|
|
9
|
+
FilterBottomSheet,
|
|
10
|
+
type DesignTokens,
|
|
11
|
+
type BottomSheetModalRef
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
5
13
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
14
|
import { useFocusEffect } from "@react-navigation/native";
|
|
7
15
|
import { useCreations } from "../hooks/useCreations";
|
|
8
16
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
9
17
|
import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
10
|
-
import { GalleryHeader, CreationsGrid,
|
|
18
|
+
import { GalleryHeader, CreationsGrid, CreationImageViewer, GalleryEmptyStates } from "../components";
|
|
11
19
|
import { getTranslatedTypes, getFilterCategoriesFromConfig } from "../utils/filterUtils";
|
|
12
20
|
import type { Creation } from "../../domain/entities/Creation";
|
|
13
21
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
@@ -45,7 +53,7 @@ export function CreationsGalleryScreen({
|
|
|
45
53
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
46
54
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
47
55
|
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
48
|
-
const filterSheetRef = React.useRef<
|
|
56
|
+
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
49
57
|
|
|
50
58
|
const { data: creationsData, isLoading, refetch } = useCreations({ userId, repository });
|
|
51
59
|
const creations = creationsData;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
2
|
-
import { FilterCategory } from "
|
|
1
|
+
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
2
|
+
import type { FilterCategory } from "@umituz/react-native-design-system";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Transforms the creations configuration into filter categories for the UI.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { Firestore } from "firebase/firestore";
|
|
2
|
-
import { collection, doc } from "firebase/firestore";
|
|
3
|
-
import type { PathBuilder } from "../../domain/value-objects/CreationsConfig";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Resolves Firestore paths for creations
|
|
7
|
-
* Single Responsibility: Path resolution
|
|
8
|
-
*/
|
|
9
|
-
export class FirestorePathResolver {
|
|
10
|
-
constructor(
|
|
11
|
-
private readonly pathBuilder: PathBuilder,
|
|
12
|
-
private readonly db: Firestore | null,
|
|
13
|
-
) { }
|
|
14
|
-
|
|
15
|
-
getUserCollection(userId: string) {
|
|
16
|
-
if (!this.db) return null;
|
|
17
|
-
const pathSegments = this.pathBuilder(userId);
|
|
18
|
-
return collection(this.db, pathSegments[0], ...pathSegments.slice(1));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getDocRef(userId: string, creationId: string) {
|
|
22
|
-
if (!this.db) return null;
|
|
23
|
-
const pathSegments = this.pathBuilder(userId);
|
|
24
|
-
return doc(this.db, pathSegments[0], ...pathSegments.slice(1), creationId);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import React, { forwardRef, useCallback, useMemo } from 'react';
|
|
2
|
-
import { View, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
|
|
3
|
-
import { useAppDesignTokens, AtomicText, AtomicIcon, type DesignTokens } from '@umituz/react-native-design-system';
|
|
4
|
-
import { BottomSheetModal } from '@gorhom/bottom-sheet';
|
|
5
|
-
|
|
6
|
-
export interface FilterOption {
|
|
7
|
-
id: string;
|
|
8
|
-
label: string;
|
|
9
|
-
icon?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface FilterCategory {
|
|
13
|
-
id: string;
|
|
14
|
-
title: string;
|
|
15
|
-
multiSelect?: boolean;
|
|
16
|
-
options: FilterOption[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface FilterBottomSheetProps {
|
|
20
|
-
categories: FilterCategory[];
|
|
21
|
-
selectedIds: string[];
|
|
22
|
-
onFilterPress: (id: string, categoryId: string) => void;
|
|
23
|
-
onClearFilters: () => void;
|
|
24
|
-
title: string;
|
|
25
|
-
snapPoints?: string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const FilterBottomSheet = forwardRef<BottomSheetModal, FilterBottomSheetProps>((props, ref) => {
|
|
29
|
-
const { categories, selectedIds, onFilterPress, onClearFilters, title, snapPoints: propSnapPoints } = props;
|
|
30
|
-
const tokens = useAppDesignTokens();
|
|
31
|
-
const styles = useStyles(tokens);
|
|
32
|
-
|
|
33
|
-
const snapPoints = useMemo(() => propSnapPoints || ['50%', '75%'], [propSnapPoints]);
|
|
34
|
-
|
|
35
|
-
const renderOption = useCallback((option: FilterOption, category: FilterCategory) => {
|
|
36
|
-
const isSelected = selectedIds.includes(option.id);
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<TouchableOpacity
|
|
40
|
-
key={option.id}
|
|
41
|
-
style={[styles.option, isSelected && styles.optionSelected]}
|
|
42
|
-
onPress={() => onFilterPress(option.id, category.id)}
|
|
43
|
-
>
|
|
44
|
-
<View style={styles.optionContent}>
|
|
45
|
-
{option.icon && (
|
|
46
|
-
<View style={styles.optionIcon}>
|
|
47
|
-
<AtomicIcon
|
|
48
|
-
name={option.icon}
|
|
49
|
-
size="sm"
|
|
50
|
-
color={isSelected ? "primary" : "onSurface"}
|
|
51
|
-
/>
|
|
52
|
-
</View>
|
|
53
|
-
)}
|
|
54
|
-
<AtomicText
|
|
55
|
-
style={[styles.optionLabel, isSelected && styles.optionLabelSelected]}
|
|
56
|
-
>
|
|
57
|
-
{option.label}
|
|
58
|
-
</AtomicText>
|
|
59
|
-
</View>
|
|
60
|
-
{isSelected && (
|
|
61
|
-
<AtomicIcon name="checkmark" size="sm" color="primary" />
|
|
62
|
-
)}
|
|
63
|
-
</TouchableOpacity>
|
|
64
|
-
);
|
|
65
|
-
}, [onFilterPress, selectedIds, styles]);
|
|
66
|
-
|
|
67
|
-
const backgroundStyle = useMemo(() => ({
|
|
68
|
-
backgroundColor: tokens.colors.surface,
|
|
69
|
-
}), [tokens.colors.surface]);
|
|
70
|
-
|
|
71
|
-
const handleIndicatorStyle = useMemo(() => ({
|
|
72
|
-
backgroundColor: tokens.colors.outline,
|
|
73
|
-
}), [tokens.colors.outline]);
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<BottomSheetModal
|
|
77
|
-
ref={ref}
|
|
78
|
-
snapPoints={snapPoints}
|
|
79
|
-
backgroundStyle={backgroundStyle}
|
|
80
|
-
handleIndicatorStyle={handleIndicatorStyle}
|
|
81
|
-
enablePanDownToClose
|
|
82
|
-
>
|
|
83
|
-
<View style={styles.header}>
|
|
84
|
-
<AtomicText style={styles.headerTitle}>{title}</AtomicText>
|
|
85
|
-
<TouchableOpacity onPress={onClearFilters}>
|
|
86
|
-
<AtomicText style={styles.clearButton}>Clear</AtomicText>
|
|
87
|
-
</TouchableOpacity>
|
|
88
|
-
</View>
|
|
89
|
-
|
|
90
|
-
<ScrollView contentContainerStyle={styles.content}>
|
|
91
|
-
{categories.map(category => (
|
|
92
|
-
<View key={category.id} style={styles.categoryContainer}>
|
|
93
|
-
<AtomicText style={styles.categoryTitle}>{category.title}</AtomicText>
|
|
94
|
-
<View style={styles.optionsContainer}>
|
|
95
|
-
{category.options.map(option => renderOption(option, category))}
|
|
96
|
-
</View>
|
|
97
|
-
</View>
|
|
98
|
-
))}
|
|
99
|
-
</ScrollView>
|
|
100
|
-
</BottomSheetModal>
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
FilterBottomSheet.displayName = 'FilterBottomSheet';
|
|
105
|
-
|
|
106
|
-
const useStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
107
|
-
content: {
|
|
108
|
-
padding: tokens.spacing.md,
|
|
109
|
-
paddingBottom: tokens.spacing.xl,
|
|
110
|
-
},
|
|
111
|
-
header: {
|
|
112
|
-
flexDirection: 'row',
|
|
113
|
-
justifyContent: 'space-between',
|
|
114
|
-
alignItems: 'center',
|
|
115
|
-
paddingHorizontal: tokens.spacing.md,
|
|
116
|
-
paddingBottom: tokens.spacing.sm,
|
|
117
|
-
borderBottomWidth: 1,
|
|
118
|
-
borderBottomColor: tokens.colors.outline,
|
|
119
|
-
},
|
|
120
|
-
headerTitle: {
|
|
121
|
-
fontSize: 20,
|
|
122
|
-
fontWeight: '700',
|
|
123
|
-
color: tokens.colors.textPrimary,
|
|
124
|
-
},
|
|
125
|
-
clearButton: {
|
|
126
|
-
color: tokens.colors.primary,
|
|
127
|
-
fontSize: 14,
|
|
128
|
-
fontWeight: '600',
|
|
129
|
-
},
|
|
130
|
-
categoryContainer: {
|
|
131
|
-
marginTop: tokens.spacing.md,
|
|
132
|
-
},
|
|
133
|
-
categoryTitle: {
|
|
134
|
-
marginBottom: tokens.spacing.xs,
|
|
135
|
-
color: tokens.colors.textSecondary,
|
|
136
|
-
fontSize: 16,
|
|
137
|
-
fontWeight: '600',
|
|
138
|
-
},
|
|
139
|
-
optionsContainer: {
|
|
140
|
-
backgroundColor: tokens.colors.background,
|
|
141
|
-
borderRadius: tokens.borders.radius.md,
|
|
142
|
-
overflow: 'hidden',
|
|
143
|
-
},
|
|
144
|
-
option: {
|
|
145
|
-
flexDirection: 'row',
|
|
146
|
-
alignItems: 'center',
|
|
147
|
-
justifyContent: 'space-between',
|
|
148
|
-
padding: tokens.spacing.md,
|
|
149
|
-
backgroundColor: tokens.colors.background,
|
|
150
|
-
borderBottomWidth: 1,
|
|
151
|
-
borderBottomColor: tokens.colors.surface,
|
|
152
|
-
},
|
|
153
|
-
optionSelected: {
|
|
154
|
-
backgroundColor: tokens.colors.surface,
|
|
155
|
-
},
|
|
156
|
-
optionContent: {
|
|
157
|
-
flexDirection: 'row',
|
|
158
|
-
alignItems: 'center',
|
|
159
|
-
},
|
|
160
|
-
optionIcon: {
|
|
161
|
-
marginRight: tokens.spacing.sm,
|
|
162
|
-
},
|
|
163
|
-
optionLabel: {
|
|
164
|
-
color: tokens.colors.textPrimary,
|
|
165
|
-
fontSize: 14,
|
|
166
|
-
},
|
|
167
|
-
optionLabelSelected: {
|
|
168
|
-
color: tokens.colors.primary,
|
|
169
|
-
fontWeight: 'bold',
|
|
170
|
-
},
|
|
171
|
-
});
|