@umituz/react-native-ai-creations 1.2.15 → 1.2.17
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 +8 -6
- package/src/presentation/components/CreationDetail/index.ts +4 -0
- package/src/presentation/components/CreationsGrid.tsx +65 -0
- package/src/presentation/components/index.ts +12 -4
- package/src/presentation/hooks/useCreations.ts +1 -1
- package/src/presentation/screens/CreationsGalleryScreen.tsx +65 -157
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.17",
|
|
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",
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
"@umituz/react-native-alert": "latest"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
+
"@react-navigation/native": ">=6.0.0",
|
|
34
35
|
"@tanstack/react-query": ">=5.0.0",
|
|
35
36
|
"@umituz/react-native-bottom-sheet": "latest",
|
|
36
37
|
"@umituz/react-native-design-system": "latest",
|
|
37
|
-
"@umituz/react-native-firebase": "latest",
|
|
38
38
|
"@umituz/react-native-filter": "latest",
|
|
39
|
+
"@umituz/react-native-firebase": "latest",
|
|
39
40
|
"@umituz/react-native-image": "latest",
|
|
40
41
|
"@umituz/react-native-sharing": "latest",
|
|
41
42
|
"expo-linear-gradient": ">=14.0.0",
|
|
@@ -45,15 +46,16 @@
|
|
|
45
46
|
"react-native-safe-area-context": ">=5.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@
|
|
49
|
-
"@umituz/react-native-filter": "latest",
|
|
50
|
-
"@umituz/react-native-image": "latest",
|
|
51
|
-
"expo-linear-gradient": "^15.0.8",
|
|
49
|
+
"@react-navigation/native": "^7.1.26",
|
|
52
50
|
"@tanstack/react-query": "^5.62.16",
|
|
53
51
|
"@types/react": "^19.0.0",
|
|
52
|
+
"@umituz/react-native-bottom-sheet": "latest",
|
|
54
53
|
"@umituz/react-native-design-system": "latest",
|
|
54
|
+
"@umituz/react-native-filter": "latest",
|
|
55
55
|
"@umituz/react-native-firebase": "latest",
|
|
56
|
+
"@umituz/react-native-image": "latest",
|
|
56
57
|
"@umituz/react-native-sharing": "latest",
|
|
58
|
+
"expo-linear-gradient": "^15.0.8",
|
|
57
59
|
"firebase": "^11.0.0",
|
|
58
60
|
"react": "19.1.0",
|
|
59
61
|
"react-native": "0.81.5",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FlatList, RefreshControl, StyleSheet } from 'react-native';
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
5
|
+
import type { CreationType } from "../../domain/value-objects/CreationsConfig";
|
|
6
|
+
import { CreationCard } from "./CreationCard";
|
|
7
|
+
|
|
8
|
+
interface CreationsGridProps {
|
|
9
|
+
readonly creations: Creation[];
|
|
10
|
+
readonly types: readonly CreationType[];
|
|
11
|
+
readonly isLoading: boolean;
|
|
12
|
+
readonly onRefresh: () => void;
|
|
13
|
+
readonly onView: (creation: Creation) => void;
|
|
14
|
+
readonly onShare: (creation: Creation) => void;
|
|
15
|
+
readonly onDelete: (creation: Creation) => void;
|
|
16
|
+
readonly contentContainerStyle?: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CreationsGrid: React.FC<CreationsGridProps> = ({
|
|
20
|
+
creations,
|
|
21
|
+
types,
|
|
22
|
+
isLoading,
|
|
23
|
+
onRefresh,
|
|
24
|
+
onView,
|
|
25
|
+
onShare,
|
|
26
|
+
onDelete,
|
|
27
|
+
contentContainerStyle,
|
|
28
|
+
}) => {
|
|
29
|
+
const tokens = useAppDesignTokens();
|
|
30
|
+
const styles = useStyles(tokens);
|
|
31
|
+
|
|
32
|
+
const renderItem = ({ item }: { item: Creation }) => (
|
|
33
|
+
<CreationCard
|
|
34
|
+
creation={item}
|
|
35
|
+
types={types as CreationType[]}
|
|
36
|
+
onView={() => onView(item)}
|
|
37
|
+
onShare={() => onShare(item)}
|
|
38
|
+
onDelete={() => onDelete(item)}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<FlatList
|
|
44
|
+
data={creations}
|
|
45
|
+
renderItem={renderItem}
|
|
46
|
+
keyExtractor={(item) => item.id}
|
|
47
|
+
contentContainerStyle={[styles.list, contentContainerStyle]}
|
|
48
|
+
showsVerticalScrollIndicator={false}
|
|
49
|
+
refreshControl={
|
|
50
|
+
<RefreshControl
|
|
51
|
+
refreshing={isLoading}
|
|
52
|
+
onRefresh={onRefresh}
|
|
53
|
+
tintColor={tokens.colors.primary}
|
|
54
|
+
/>
|
|
55
|
+
}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const useStyles = (tokens: any) => StyleSheet.create({
|
|
61
|
+
list: {
|
|
62
|
+
padding: tokens.spacing.md,
|
|
63
|
+
paddingBottom: 100, // Space for fab or bottom tab
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
* Presentation Components
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export {
|
|
6
|
-
export { CreationCard } from "./CreationCard";
|
|
7
|
-
export { CreationsHomeCard } from "./CreationsHomeCard";
|
|
8
|
-
export { FilterChips } from "./FilterChips";
|
|
5
|
+
export { GalleryHeader } from "./GalleryHeader";
|
|
9
6
|
export { EmptyState } from "./EmptyState";
|
|
7
|
+
export { FilterChips } from "./FilterChips";
|
|
8
|
+
export { CreationsHomeCard } from "./CreationsHomeCard";
|
|
9
|
+
export { CreationCard } from "./CreationCard";
|
|
10
|
+
export { CreationThumbnail } from "./CreationThumbnail";
|
|
11
|
+
export { CreationsGrid } from "./CreationsGrid";
|
|
12
|
+
|
|
13
|
+
// Detail Components
|
|
14
|
+
export { DetailHeader } from "./CreationDetail/DetailHeader";
|
|
15
|
+
export { DetailImage } from "./CreationDetail/DetailImage";
|
|
16
|
+
export { DetailStory } from "./CreationDetail/DetailStory";
|
|
17
|
+
export { DetailActions } from "./CreationDetail/DetailActions";
|
|
@@ -7,7 +7,7 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
7
7
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
8
8
|
|
|
9
9
|
const CACHE_CONFIG = {
|
|
10
|
-
staleTime:
|
|
10
|
+
staleTime: 0, // Always fetch fresh data to show new creations immediately
|
|
11
11
|
gcTime: 30 * 60 * 1000,
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CreationsGalleryScreen
|
|
3
|
-
* Full gallery view with filtering and editing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React, { useMemo, useCallback, useState } from "react";
|
|
7
|
-
import { View,
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
8
3
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
4
|
import { useSharing } from "@umituz/react-native-sharing";
|
|
10
5
|
import { ImageGallery } from "@umituz/react-native-image";
|
|
11
6
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
|
-
import
|
|
13
|
-
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
14
|
-
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
7
|
+
import { useFocusEffect } from "@react-navigation/native";
|
|
15
8
|
import { useCreations } from "../hooks/useCreations";
|
|
16
9
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
17
10
|
import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
18
|
-
import {
|
|
19
|
-
import { EmptyState } from "../components/EmptyState";
|
|
11
|
+
import { useAlert } from "@umituz/react-native-alert";
|
|
20
12
|
import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-filter";
|
|
21
13
|
import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
import {
|
|
14
|
+
import { GalleryHeader, EmptyState, CreationsGrid } from "../components";
|
|
15
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
16
|
+
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
17
|
+
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
26
18
|
import { CreationDetailScreen } from "./CreationDetailScreen";
|
|
27
19
|
|
|
28
20
|
interface CreationsGalleryScreenProps {
|
|
@@ -51,146 +43,62 @@ export function CreationsGalleryScreen({
|
|
|
51
43
|
const { share } = useSharing();
|
|
52
44
|
const alert = useAlert();
|
|
53
45
|
|
|
54
|
-
// State
|
|
55
46
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
56
47
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
57
48
|
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
58
|
-
|
|
59
49
|
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
60
50
|
|
|
61
|
-
const { data: creations, isLoading, refetch } = useCreations({
|
|
62
|
-
userId,
|
|
63
|
-
repository,
|
|
64
|
-
});
|
|
65
|
-
|
|
51
|
+
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
66
52
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
67
|
-
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } =
|
|
68
|
-
|
|
53
|
+
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } = useCreationsFilter({ creations });
|
|
54
|
+
|
|
55
|
+
// Refetch creations when screen comes into focus
|
|
56
|
+
useFocusEffect(
|
|
57
|
+
useCallback(() => {
|
|
58
|
+
refetch();
|
|
59
|
+
}, [refetch])
|
|
60
|
+
);
|
|
69
61
|
|
|
70
62
|
const allCategories = useMemo(() => {
|
|
71
63
|
const categories: FilterCategory[] = [];
|
|
72
|
-
|
|
73
64
|
if (config.types.length > 0) {
|
|
74
65
|
categories.push({
|
|
75
66
|
id: 'type',
|
|
76
|
-
title: t(config.translations.filterTitle)
|
|
67
|
+
title: t(config.translations.filterTitle),
|
|
77
68
|
multiSelect: false,
|
|
78
|
-
options: config.types.map(type => ({
|
|
79
|
-
id: type.id,
|
|
80
|
-
label: t(type.labelKey),
|
|
81
|
-
icon: type.icon || 'Image',
|
|
82
|
-
}))
|
|
69
|
+
options: config.types.map(type => ({ id: type.id, label: t(type.labelKey), icon: type.icon || 'Image' }))
|
|
83
70
|
});
|
|
84
71
|
}
|
|
85
|
-
|
|
86
|
-
if (config.filterCategories) {
|
|
87
|
-
categories.push(...config.filterCategories);
|
|
88
|
-
}
|
|
89
|
-
|
|
72
|
+
if (config.filterCategories) categories.push(...config.filterCategories);
|
|
90
73
|
return categories;
|
|
91
|
-
}, [config.types, config.filterCategories, t]);
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
}, []);
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const handleDelete = useCallback(
|
|
116
|
-
(creation: Creation) => {
|
|
117
|
-
alert.show({
|
|
118
|
-
title: t(config.translations.deleteTitle),
|
|
119
|
-
message: t(config.translations.deleteMessage),
|
|
120
|
-
type: 'warning' as any,
|
|
121
|
-
actions: [
|
|
122
|
-
{
|
|
123
|
-
id: 'cancel',
|
|
124
|
-
label: t("common.cancel"),
|
|
125
|
-
style: 'secondary',
|
|
126
|
-
onPress: () => { }
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: 'delete',
|
|
130
|
-
label: t("common.delete"),
|
|
131
|
-
style: 'destructive',
|
|
132
|
-
variant: 'danger',
|
|
133
|
-
onPress: () => {
|
|
134
|
-
deleteMutation.mutate(creation.id);
|
|
135
|
-
setSelectedCreation(null);
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
]
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
[t, config.translations, deleteMutation, alert],
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
const handleImageChange = useCallback(
|
|
145
|
-
async (uri: string, index: number) => {
|
|
146
|
-
const creation = filtered[index];
|
|
147
|
-
if (creation && onImageEdit) {
|
|
148
|
-
await onImageEdit(uri, creation.id);
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
[filtered, onImageEdit],
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const styles = useMemo(
|
|
155
|
-
() =>
|
|
156
|
-
StyleSheet.create({
|
|
157
|
-
container: {
|
|
158
|
-
flex: 1,
|
|
159
|
-
backgroundColor: tokens.colors.backgroundPrimary,
|
|
160
|
-
},
|
|
161
|
-
list: {
|
|
162
|
-
paddingHorizontal: tokens.spacing.md,
|
|
163
|
-
paddingBottom: insets.bottom + tokens.spacing.xl,
|
|
164
|
-
gap: tokens.spacing.md,
|
|
165
|
-
},
|
|
166
|
-
}),
|
|
167
|
-
[tokens, insets.bottom],
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const viewerImages = useMemo(
|
|
171
|
-
() => filtered.map((creation) => ({ uri: creation.uri })),
|
|
172
|
-
[filtered],
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const renderItem = useCallback(
|
|
176
|
-
({ item }: { item: Creation }) => (
|
|
177
|
-
<CreationCard
|
|
178
|
-
creation={item}
|
|
179
|
-
types={config.types}
|
|
180
|
-
onView={() => handleView(item)}
|
|
181
|
-
onShare={handleShare}
|
|
182
|
-
onDelete={handleDelete}
|
|
183
|
-
/>
|
|
184
|
-
),
|
|
185
|
-
[config.types, handleView, handleShare, handleDelete],
|
|
186
|
-
);
|
|
74
|
+
}, [config.types, config.filterCategories, t, config.translations.filterTitle]);
|
|
75
|
+
|
|
76
|
+
const handleShare = useCallback(async (creation: Creation) => {
|
|
77
|
+
share(creation.uri, { dialogTitle: t("common.share") });
|
|
78
|
+
}, [share, t]);
|
|
79
|
+
|
|
80
|
+
const handleDelete = useCallback(async (creation: Creation) => {
|
|
81
|
+
alert.show({
|
|
82
|
+
title: t(config.translations.deleteTitle),
|
|
83
|
+
message: t(config.translations.deleteMessage),
|
|
84
|
+
type: 'warning' as any,
|
|
85
|
+
actions: [
|
|
86
|
+
{ id: 'cancel', label: t("common.cancel"), onPress: () => { } },
|
|
87
|
+
{
|
|
88
|
+
id: 'delete', label: t("common.delete"), variant: 'danger' as any, onPress: async () => {
|
|
89
|
+
const success = await deleteMutation.mutateAsync(creation.id);
|
|
90
|
+
if (success) setSelectedCreation(null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
}, [alert, config, deleteMutation, t]);
|
|
187
96
|
|
|
188
|
-
// If a creation is selected, show detail screen (simulating a stack push)
|
|
189
97
|
if (selectedCreation) {
|
|
190
98
|
return (
|
|
191
99
|
<CreationDetailScreen
|
|
192
100
|
creation={selectedCreation}
|
|
193
|
-
onClose={
|
|
101
|
+
onClose={() => setSelectedCreation(null)}
|
|
194
102
|
onShare={handleShare}
|
|
195
103
|
onDelete={handleDelete}
|
|
196
104
|
t={t}
|
|
@@ -198,6 +106,8 @@ export function CreationsGalleryScreen({
|
|
|
198
106
|
);
|
|
199
107
|
}
|
|
200
108
|
|
|
109
|
+
const styles = useStyles(tokens);
|
|
110
|
+
|
|
201
111
|
if (!isLoading && (!creations || creations.length === 0)) {
|
|
202
112
|
return (
|
|
203
113
|
<View style={styles.container}>
|
|
@@ -222,43 +132,41 @@ export function CreationsGalleryScreen({
|
|
|
222
132
|
onFilterPress={() => filterSheetRef.current?.present()}
|
|
223
133
|
style={{ paddingTop: insets.top }}
|
|
224
134
|
/>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
onRefresh={refetch}
|
|
235
|
-
tintColor={tokens.colors.primary}
|
|
236
|
-
/>
|
|
237
|
-
}
|
|
135
|
+
<CreationsGrid
|
|
136
|
+
creations={filtered}
|
|
137
|
+
types={config.types}
|
|
138
|
+
isLoading={isLoading}
|
|
139
|
+
onRefresh={refetch}
|
|
140
|
+
onView={setSelectedCreation}
|
|
141
|
+
onShare={handleShare}
|
|
142
|
+
onDelete={handleDelete}
|
|
143
|
+
contentContainerStyle={{ paddingBottom: insets.bottom + tokens.spacing.xl }}
|
|
238
144
|
/>
|
|
239
|
-
|
|
240
|
-
{/* Optional: Keep ImageGallery for quick preview if needed, or rely on Detail Screen */}
|
|
241
145
|
<ImageGallery
|
|
242
|
-
images={
|
|
146
|
+
images={filtered.map(c => ({ uri: c.uri }))}
|
|
243
147
|
visible={viewerVisible}
|
|
244
148
|
index={viewerIndex}
|
|
245
149
|
onDismiss={() => setViewerVisible(false)}
|
|
246
150
|
onIndexChange={setViewerIndex}
|
|
247
151
|
{...(enableEditing && { enableEditing } as any)}
|
|
248
|
-
{...(onImageEdit && {
|
|
152
|
+
{...(onImageEdit && {
|
|
153
|
+
onImageChange: async (uri: string) => {
|
|
154
|
+
if (selectedCreation) { await onImageEdit(uri, (selectedCreation as Creation).id); refetch(); }
|
|
155
|
+
}
|
|
156
|
+
} as any)}
|
|
249
157
|
/>
|
|
250
|
-
|
|
251
158
|
<FilterBottomSheet
|
|
252
159
|
ref={filterSheetRef}
|
|
253
|
-
categories={allCategories
|
|
160
|
+
categories={allCategories}
|
|
254
161
|
selectedIds={selectedIds}
|
|
255
|
-
onFilterPress={(id, catId) =>
|
|
256
|
-
const category = allCategories.find(c => c.id === catId);
|
|
257
|
-
toggleFilter(id, category?.multiSelect);
|
|
258
|
-
}}
|
|
162
|
+
onFilterPress={(id, catId) => toggleFilter(id, allCategories.find(c => c.id === catId)?.multiSelect)}
|
|
259
163
|
onClearFilters={clearFilters}
|
|
260
164
|
title={t(config.translations.filterTitle) || t("common.filter")}
|
|
261
165
|
/>
|
|
262
166
|
</View>
|
|
263
167
|
);
|
|
264
168
|
}
|
|
169
|
+
|
|
170
|
+
const useStyles = (tokens: any) => StyleSheet.create({
|
|
171
|
+
container: { flex: 1, backgroundColor: tokens.colors.background },
|
|
172
|
+
});
|