@umituz/react-native-ai-creations 1.2.2 → 1.2.4
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-creations",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|
|
@@ -34,18 +34,24 @@
|
|
|
34
34
|
"@umituz/react-native-image": "latest",
|
|
35
35
|
"@umituz/react-native-sharing": "latest",
|
|
36
36
|
"firebase": ">=11.0.0",
|
|
37
|
-
"react": ">=
|
|
38
|
-
"react-native": ">=0.
|
|
39
|
-
"react-native-safe-area-context": ">=
|
|
37
|
+
"react": ">=19.1.0",
|
|
38
|
+
"react-native": ">=0.81.5",
|
|
39
|
+
"react-native-safe-area-context": ">=5.0.0",
|
|
40
|
+
"@umituz/react-native-filter": "^1.4.2",
|
|
41
|
+
"@umituz/react-native-bottom-sheet": "latest",
|
|
42
|
+
"@umituz/react-native-design-system-atoms": "latest"
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@tanstack/react-query": "^5.62.16",
|
|
43
46
|
"@types/react": "^19.0.0",
|
|
44
47
|
"@umituz/react-native-image": "^1.1.1",
|
|
45
48
|
"firebase": "^11.0.0",
|
|
46
|
-
"react": "
|
|
47
|
-
"react-native": "
|
|
48
|
-
"react-native-safe-area-context": "^
|
|
49
|
+
"react": "19.1.0",
|
|
50
|
+
"react-native": "0.81.5",
|
|
51
|
+
"react-native-safe-area-context": "^5.6.0",
|
|
52
|
+
"@umituz/react-native-filter": "^1.4.2",
|
|
53
|
+
"@umituz/react-native-bottom-sheet": "latest",
|
|
54
|
+
"@umituz/react-native-design-system-atoms": "latest",
|
|
49
55
|
"typescript": "^5.3.3"
|
|
50
56
|
},
|
|
51
57
|
"publishConfig": {
|
|
@@ -39,9 +39,12 @@ export type PathBuilder = (userId: string) => string[];
|
|
|
39
39
|
*/
|
|
40
40
|
export type DocumentMapper = (id: string, data: CreationDocument) => Creation;
|
|
41
41
|
|
|
42
|
+
import type { FilterCategory } from "@umituz/react-native-filter";
|
|
43
|
+
|
|
42
44
|
export interface CreationsConfig {
|
|
43
45
|
readonly collectionName: string;
|
|
44
46
|
readonly types: readonly CreationType[];
|
|
47
|
+
readonly filterCategories?: readonly FilterCategory[];
|
|
45
48
|
readonly translations: CreationsTranslations;
|
|
46
49
|
readonly maxThumbnails?: number;
|
|
47
50
|
readonly gridColumns?: number;
|
|
@@ -14,6 +14,7 @@ interface FilterChipsProps {
|
|
|
14
14
|
readonly selectedType: string;
|
|
15
15
|
readonly allLabel: string;
|
|
16
16
|
readonly onSelect: (type: string) => void;
|
|
17
|
+
readonly style?: any;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function FilterChips({
|
|
@@ -22,6 +23,7 @@ export function FilterChips({
|
|
|
22
23
|
selectedType,
|
|
23
24
|
allLabel,
|
|
24
25
|
onSelect,
|
|
26
|
+
style,
|
|
25
27
|
}: FilterChipsProps) {
|
|
26
28
|
const tokens = useAppDesignTokens();
|
|
27
29
|
|
|
@@ -59,7 +61,7 @@ export function FilterChips({
|
|
|
59
61
|
const visibleTypes = types.filter((t) => availableTypes.includes(t.id));
|
|
60
62
|
|
|
61
63
|
return (
|
|
62
|
-
<View style={styles.container}>
|
|
64
|
+
<View style={[styles.container, style]}>
|
|
63
65
|
<ScrollView
|
|
64
66
|
horizontal
|
|
65
67
|
showsHorizontalScrollIndicator={false}
|
|
@@ -12,35 +12,42 @@ interface UseCreationsFilterProps {
|
|
|
12
12
|
readonly creations: Creation[] | undefined;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const [selectedType, setSelectedType] = useState<string>(ALL_FILTER);
|
|
15
|
+
import { FilterUtils } from "@umituz/react-native-filter";
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
interface UseCreationsFilterProps {
|
|
18
|
+
readonly creations: Creation[] | undefined;
|
|
19
|
+
readonly defaultFilterId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useCreationsFilter({
|
|
23
|
+
creations,
|
|
24
|
+
defaultFilterId = "all"
|
|
25
|
+
}: UseCreationsFilterProps) {
|
|
26
|
+
const [selectedIds, setSelectedIds] = useState<string[]>([defaultFilterId]);
|
|
23
27
|
|
|
24
28
|
const filtered = useMemo(() => {
|
|
25
29
|
if (!creations) return [];
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (selectedIds.includes(defaultFilterId)) return creations;
|
|
31
|
+
|
|
32
|
+
return creations.filter((c) =>
|
|
33
|
+
selectedIds.includes(c.type) ||
|
|
34
|
+
selectedIds.some(id => (c as any).metadata?.tags?.includes(id))
|
|
35
|
+
);
|
|
36
|
+
}, [creations, selectedIds, defaultFilterId]);
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
}, []);
|
|
38
|
+
const toggleFilter = useCallback((filterId: string, multiSelect: boolean = false) => {
|
|
39
|
+
setSelectedIds(prev => FilterUtils.toggleFilter(prev, filterId, multiSelect, defaultFilterId));
|
|
40
|
+
}, [defaultFilterId]);
|
|
33
41
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
}, []);
|
|
42
|
+
const clearFilters = useCallback(() => {
|
|
43
|
+
setSelectedIds([defaultFilterId]);
|
|
44
|
+
}, [defaultFilterId]);
|
|
37
45
|
|
|
38
46
|
return {
|
|
39
47
|
filtered,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
isFiltered: selectedType !== ALL_FILTER,
|
|
48
|
+
selectedIds,
|
|
49
|
+
toggleFilter,
|
|
50
|
+
clearFilters,
|
|
51
|
+
isFiltered: !selectedIds.includes(defaultFilterId),
|
|
45
52
|
};
|
|
46
53
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo, useCallback, useState } from "react";
|
|
7
|
-
import { View, FlatList, StyleSheet, RefreshControl, Alert } from "react-native";
|
|
7
|
+
import { View, FlatList, StyleSheet, RefreshControl, Alert, TouchableOpacity } from "react-native";
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { useSharing } from "@umituz/react-native-sharing";
|
|
10
10
|
import { ImageGallery } from "@umituz/react-native-image";
|
|
@@ -16,8 +16,10 @@ import { useCreations } from "../hooks/useCreations";
|
|
|
16
16
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
17
17
|
import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
18
18
|
import { CreationCard } from "../components/CreationCard";
|
|
19
|
-
import { FilterChips } from "../components/FilterChips";
|
|
20
19
|
import { EmptyState } from "../components/EmptyState";
|
|
20
|
+
import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-filter";
|
|
21
|
+
import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
|
|
22
|
+
import { AtomicIcon, AtomicText, AtomicButton } from "@umituz/react-native-design-system-atoms";
|
|
21
23
|
|
|
22
24
|
interface CreationsGalleryScreenProps {
|
|
23
25
|
readonly userId: string | null;
|
|
@@ -41,6 +43,7 @@ export function CreationsGalleryScreen({
|
|
|
41
43
|
const { share } = useSharing();
|
|
42
44
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
43
45
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
46
|
+
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
44
47
|
|
|
45
48
|
const { data: creations, isLoading, refetch } = useCreations({
|
|
46
49
|
userId,
|
|
@@ -48,9 +51,33 @@ export function CreationsGalleryScreen({
|
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
51
|
-
const { filtered,
|
|
54
|
+
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } =
|
|
52
55
|
useCreationsFilter({ creations });
|
|
53
56
|
|
|
57
|
+
const allCategories = useMemo(() => {
|
|
58
|
+
const categories: FilterCategory[] = [];
|
|
59
|
+
|
|
60
|
+
// Add dynamic types category if types exist
|
|
61
|
+
if (config.types.length > 0) {
|
|
62
|
+
categories.push({
|
|
63
|
+
id: 'type', // This ID matches logic in useFilter? No, filter logic is flat ID based. 'type' is category ID.
|
|
64
|
+
title: t('creations.category') || 'Category',
|
|
65
|
+
multiSelect: false,
|
|
66
|
+
options: config.types.map(t => ({
|
|
67
|
+
id: t.id,
|
|
68
|
+
label: t.labelKey, // In a real app we'd translate this, but for now passing key or mocked
|
|
69
|
+
icon: 'Image', // Default icon since types have emojis which might not work with AtomicIcon
|
|
70
|
+
}))
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (config.filterCategories) {
|
|
75
|
+
categories.push(...config.filterCategories);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return categories;
|
|
79
|
+
}, [config.types, config.filterCategories, t]);
|
|
80
|
+
|
|
54
81
|
const handleView = useCallback(
|
|
55
82
|
(creation: Creation) => {
|
|
56
83
|
const index = filtered.findIndex((c) => c.id === creation.id);
|
|
@@ -111,8 +138,40 @@ export function CreationsGalleryScreen({
|
|
|
111
138
|
flex: 1,
|
|
112
139
|
backgroundColor: tokens.colors.backgroundPrimary,
|
|
113
140
|
},
|
|
141
|
+
headerArea: {
|
|
142
|
+
flexDirection: "row",
|
|
143
|
+
alignItems: "center",
|
|
144
|
+
justifyContent: 'space-between',
|
|
145
|
+
paddingHorizontal: tokens.spacing.md,
|
|
146
|
+
paddingVertical: tokens.spacing.sm,
|
|
147
|
+
marginBottom: tokens.spacing.sm,
|
|
148
|
+
},
|
|
149
|
+
filterButton: {
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
gap: tokens.spacing.xs,
|
|
153
|
+
paddingVertical: tokens.spacing.xs,
|
|
154
|
+
paddingHorizontal: tokens.spacing.md,
|
|
155
|
+
borderRadius: (tokens as any).borders?.radius?.full || 999,
|
|
156
|
+
backgroundColor: tokens.colors.surfaceVariant,
|
|
157
|
+
borderWidth: 1,
|
|
158
|
+
borderColor: 'transparent',
|
|
159
|
+
},
|
|
160
|
+
filterButtonActive: {
|
|
161
|
+
backgroundColor: tokens.colors.primary + "15",
|
|
162
|
+
borderColor: tokens.colors.primary + "30",
|
|
163
|
+
},
|
|
164
|
+
badge: {
|
|
165
|
+
width: 8,
|
|
166
|
+
height: 8,
|
|
167
|
+
borderRadius: 4,
|
|
168
|
+
backgroundColor: tokens.colors.primary,
|
|
169
|
+
position: 'absolute',
|
|
170
|
+
top: 6,
|
|
171
|
+
right: 6,
|
|
172
|
+
},
|
|
114
173
|
list: {
|
|
115
|
-
|
|
174
|
+
paddingHorizontal: tokens.spacing.md,
|
|
116
175
|
paddingBottom: insets.bottom + tokens.spacing.xl,
|
|
117
176
|
gap: tokens.spacing.md,
|
|
118
177
|
},
|
|
@@ -151,15 +210,22 @@ export function CreationsGalleryScreen({
|
|
|
151
210
|
|
|
152
211
|
return (
|
|
153
212
|
<View style={styles.container}>
|
|
154
|
-
{
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
213
|
+
<View style={styles.headerArea}>
|
|
214
|
+
<View>
|
|
215
|
+
<AtomicText type="headlineSmall">{t(config.translations.title) || 'My Creations'}</AtomicText>
|
|
216
|
+
<AtomicText type="bodySmall" style={{ opacity: 0.6 }}>{filtered.length} {t(config.translations.photoCount) || 'photos'}</AtomicText>
|
|
217
|
+
</View>
|
|
218
|
+
<TouchableOpacity
|
|
219
|
+
onPress={() => filterSheetRef.current?.present()}
|
|
220
|
+
style={[styles.filterButton, isFiltered && styles.filterButtonActive]}
|
|
221
|
+
>
|
|
222
|
+
<AtomicIcon name="ListFilter" size="sm" color={isFiltered ? "primary" : "secondary"} />
|
|
223
|
+
<AtomicText type="labelMedium" color={isFiltered ? "primary" : "secondary"}>Filter</AtomicText>
|
|
224
|
+
{isFiltered && (
|
|
225
|
+
<View style={styles.badge} />
|
|
226
|
+
)}
|
|
227
|
+
</TouchableOpacity>
|
|
228
|
+
</View>
|
|
163
229
|
<FlatList
|
|
164
230
|
data={filtered}
|
|
165
231
|
renderItem={renderItem}
|
|
@@ -182,6 +248,18 @@ export function CreationsGalleryScreen({
|
|
|
182
248
|
enableEditing={enableEditing}
|
|
183
249
|
onImageChange={handleImageChange}
|
|
184
250
|
/>
|
|
251
|
+
|
|
252
|
+
<FilterBottomSheet
|
|
253
|
+
ref={filterSheetRef}
|
|
254
|
+
categories={allCategories as FilterCategory[]}
|
|
255
|
+
selectedIds={selectedIds}
|
|
256
|
+
onFilterPress={(id, catId) => {
|
|
257
|
+
const category = allCategories.find(c => c.id === catId);
|
|
258
|
+
toggleFilter(id, category?.multiSelect);
|
|
259
|
+
}}
|
|
260
|
+
onClearFilters={clearFilters}
|
|
261
|
+
title={t("common.filter")}
|
|
262
|
+
/>
|
|
185
263
|
</View>
|
|
186
264
|
);
|
|
187
265
|
}
|