@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-creations",
3
- "version": "1.2.15",
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
- "@umituz/react-native-bottom-sheet": "latest",
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,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";
@@ -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: 5 * 60 * 1000,
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, 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";
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 { CreationCard } from "../components/CreationCard";
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 { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
23
- import { GalleryHeader } from "../components/GalleryHeader";
24
-
25
- import { useAlert } from "@umituz/react-native-alert";
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
- useCreationsFilter({ creations });
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) || t('creations.category') || 'Category',
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 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
- );
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={handleCloseDetail}
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
- <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
- }
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={viewerImages}
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 && { onImageChange: handleImageChange } as any)}
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 as FilterCategory[]}
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
+ });