@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-creations",
3
- "version": "1.2.15",
3
+ "version": "1.2.16",
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",
@@ -0,0 +1,4 @@
1
+ export * from './DetailHeader';
2
+ export * from './DetailImage';
3
+ export * from './DetailStory';
4
+ export * from './DetailActions';
@@ -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 { CreationThumbnail } from "./CreationThumbnail";
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, FlatList, StyleSheet, RefreshControl, Alert, TouchableOpacity } from "react-native";
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 { CreationCard } from "../components/CreationCard";
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 { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
23
- import { GalleryHeader } from "../components/GalleryHeader";
24
-
25
- import { useAlert } from "@umituz/react-native-alert";
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) || t('creations.category') || 'Category',
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 handleView = useCallback((creation: Creation) => {
94
- setSelectedCreation(creation);
95
- }, []);
96
-
97
- const handleCloseDetail = useCallback(() => {
98
- setSelectedCreation(null);
99
- }, []);
100
-
101
- const handleShare = useCallback(
102
- async (creation: Creation) => {
103
- try {
104
- await share(creation.uri, {
105
- dialogTitle: t(config.translations.title),
106
- mimeType: "image/jpeg",
107
- });
108
- } catch {
109
- // Silent fail
110
- }
111
- },
112
- [share, t, config.translations.title],
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={handleCloseDetail}
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
- <FlatList
227
- data={filtered}
228
- renderItem={renderItem}
229
- keyExtractor={(item) => item.id}
230
- contentContainerStyle={styles.list}
231
- refreshControl={
232
- <RefreshControl
233
- refreshing={isLoading}
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={viewerImages}
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 && { onImageChange: handleImageChange } as any)}
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 as FilterCategory[]}
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
+ });