@umituz/react-native-ai-creations 1.2.15 → 1.2.16
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
|
@@ -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";
|
|
@@ -1,28 +1,19 @@
|
|
|
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 type { Creation } from "../../domain/entities/Creation";
|
|
13
|
-
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
14
|
-
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
15
7
|
import { useCreations } from "../hooks/useCreations";
|
|
16
8
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
17
9
|
import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
18
|
-
import {
|
|
19
|
-
import { EmptyState } from "../components/EmptyState";
|
|
10
|
+
import { useAlert } from "@umituz/react-native-alert";
|
|
20
11
|
import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-filter";
|
|
21
12
|
import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
import {
|
|
13
|
+
import { GalleryHeader, EmptyState, CreationsGrid } from "../components";
|
|
14
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
15
|
+
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
16
|
+
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
26
17
|
import { CreationDetailScreen } from "./CreationDetailScreen";
|
|
27
18
|
|
|
28
19
|
interface CreationsGalleryScreenProps {
|
|
@@ -51,146 +42,55 @@ export function CreationsGalleryScreen({
|
|
|
51
42
|
const { share } = useSharing();
|
|
52
43
|
const alert = useAlert();
|
|
53
44
|
|
|
54
|
-
// State
|
|
55
45
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
56
46
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
57
47
|
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
58
|
-
|
|
59
48
|
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
60
49
|
|
|
61
|
-
const { data: creations, isLoading, refetch } = useCreations({
|
|
62
|
-
userId,
|
|
63
|
-
repository,
|
|
64
|
-
});
|
|
65
|
-
|
|
50
|
+
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
66
51
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
67
|
-
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } =
|
|
68
|
-
useCreationsFilter({ creations });
|
|
52
|
+
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } = useCreationsFilter({ creations });
|
|
69
53
|
|
|
70
54
|
const allCategories = useMemo(() => {
|
|
71
55
|
const categories: FilterCategory[] = [];
|
|
72
|
-
|
|
73
56
|
if (config.types.length > 0) {
|
|
74
57
|
categories.push({
|
|
75
58
|
id: 'type',
|
|
76
|
-
title: t(config.translations.filterTitle)
|
|
59
|
+
title: t(config.translations.filterTitle),
|
|
77
60
|
multiSelect: false,
|
|
78
|
-
options: config.types.map(type => ({
|
|
79
|
-
id: type.id,
|
|
80
|
-
label: t(type.labelKey),
|
|
81
|
-
icon: type.icon || 'Image',
|
|
82
|
-
}))
|
|
61
|
+
options: config.types.map(type => ({ id: type.id, label: t(type.labelKey), icon: type.icon || 'Image' }))
|
|
83
62
|
});
|
|
84
63
|
}
|
|
85
|
-
|
|
86
|
-
if (config.filterCategories) {
|
|
87
|
-
categories.push(...config.filterCategories);
|
|
88
|
-
}
|
|
89
|
-
|
|
64
|
+
if (config.filterCategories) categories.push(...config.filterCategories);
|
|
90
65
|
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
|
-
);
|
|
66
|
+
}, [config.types, config.filterCategories, t, config.translations.filterTitle]);
|
|
67
|
+
|
|
68
|
+
const handleShare = useCallback(async (creation: Creation) => {
|
|
69
|
+
share(creation.uri, { dialogTitle: t("common.share") });
|
|
70
|
+
}, [share, t]);
|
|
71
|
+
|
|
72
|
+
const handleDelete = useCallback(async (creation: Creation) => {
|
|
73
|
+
alert.show({
|
|
74
|
+
title: t(config.translations.deleteTitle),
|
|
75
|
+
message: t(config.translations.deleteMessage),
|
|
76
|
+
type: 'warning' as any,
|
|
77
|
+
actions: [
|
|
78
|
+
{ id: 'cancel', label: t("common.cancel"), onPress: () => { } },
|
|
79
|
+
{
|
|
80
|
+
id: 'delete', label: t("common.delete"), variant: 'danger' as any, onPress: async () => {
|
|
81
|
+
const success = await deleteMutation.mutateAsync(creation.id);
|
|
82
|
+
if (success) setSelectedCreation(null);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
}, [alert, config, deleteMutation, t]);
|
|
187
88
|
|
|
188
|
-
// If a creation is selected, show detail screen (simulating a stack push)
|
|
189
89
|
if (selectedCreation) {
|
|
190
90
|
return (
|
|
191
91
|
<CreationDetailScreen
|
|
192
92
|
creation={selectedCreation}
|
|
193
|
-
onClose={
|
|
93
|
+
onClose={() => setSelectedCreation(null)}
|
|
194
94
|
onShare={handleShare}
|
|
195
95
|
onDelete={handleDelete}
|
|
196
96
|
t={t}
|
|
@@ -198,6 +98,8 @@ export function CreationsGalleryScreen({
|
|
|
198
98
|
);
|
|
199
99
|
}
|
|
200
100
|
|
|
101
|
+
const styles = useStyles(tokens);
|
|
102
|
+
|
|
201
103
|
if (!isLoading && (!creations || creations.length === 0)) {
|
|
202
104
|
return (
|
|
203
105
|
<View style={styles.container}>
|
|
@@ -222,43 +124,41 @@ export function CreationsGalleryScreen({
|
|
|
222
124
|
onFilterPress={() => filterSheetRef.current?.present()}
|
|
223
125
|
style={{ paddingTop: insets.top }}
|
|
224
126
|
/>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
onRefresh={refetch}
|
|
235
|
-
tintColor={tokens.colors.primary}
|
|
236
|
-
/>
|
|
237
|
-
}
|
|
127
|
+
<CreationsGrid
|
|
128
|
+
creations={filtered}
|
|
129
|
+
types={config.types}
|
|
130
|
+
isLoading={isLoading}
|
|
131
|
+
onRefresh={refetch}
|
|
132
|
+
onView={setSelectedCreation}
|
|
133
|
+
onShare={handleShare}
|
|
134
|
+
onDelete={handleDelete}
|
|
135
|
+
contentContainerStyle={{ paddingBottom: insets.bottom + tokens.spacing.xl }}
|
|
238
136
|
/>
|
|
239
|
-
|
|
240
|
-
{/* Optional: Keep ImageGallery for quick preview if needed, or rely on Detail Screen */}
|
|
241
137
|
<ImageGallery
|
|
242
|
-
images={
|
|
138
|
+
images={filtered.map(c => ({ uri: c.uri }))}
|
|
243
139
|
visible={viewerVisible}
|
|
244
140
|
index={viewerIndex}
|
|
245
141
|
onDismiss={() => setViewerVisible(false)}
|
|
246
142
|
onIndexChange={setViewerIndex}
|
|
247
143
|
{...(enableEditing && { enableEditing } as any)}
|
|
248
|
-
{...(onImageEdit && {
|
|
144
|
+
{...(onImageEdit && {
|
|
145
|
+
onImageChange: async (uri: string) => {
|
|
146
|
+
if (selectedCreation) { await onImageEdit(uri, (selectedCreation as Creation).id); refetch(); }
|
|
147
|
+
}
|
|
148
|
+
} as any)}
|
|
249
149
|
/>
|
|
250
|
-
|
|
251
150
|
<FilterBottomSheet
|
|
252
151
|
ref={filterSheetRef}
|
|
253
|
-
categories={allCategories
|
|
152
|
+
categories={allCategories}
|
|
254
153
|
selectedIds={selectedIds}
|
|
255
|
-
onFilterPress={(id, catId) =>
|
|
256
|
-
const category = allCategories.find(c => c.id === catId);
|
|
257
|
-
toggleFilter(id, category?.multiSelect);
|
|
258
|
-
}}
|
|
154
|
+
onFilterPress={(id, catId) => toggleFilter(id, allCategories.find(c => c.id === catId)?.multiSelect)}
|
|
259
155
|
onClearFilters={clearFilters}
|
|
260
156
|
title={t(config.translations.filterTitle) || t("common.filter")}
|
|
261
157
|
/>
|
|
262
158
|
</View>
|
|
263
159
|
);
|
|
264
160
|
}
|
|
161
|
+
|
|
162
|
+
const useStyles = (tokens: any) => StyleSheet.create({
|
|
163
|
+
container: { flex: 1, backgroundColor: tokens.colors.background },
|
|
164
|
+
});
|