@umituz/react-native-ai-creations 1.2.11 → 1.2.12

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.11",
3
+ "version": "1.2.12",
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",
@@ -28,10 +28,11 @@
28
28
  "url": "https://github.com/umituz/react-native-ai-creations"
29
29
  },
30
30
  "dependencies": {
31
+ "@umituz/react-native-alert": "latest",
32
+ "@umituz/react-native-bottom-sheet": "latest",
31
33
  "@umituz/react-native-filter": "latest",
32
34
  "@umituz/react-native-image": "latest",
33
- "@umituz/react-native-bottom-sheet": "latest",
34
- "@umituz/react-native-alert": "latest"
35
+ "expo-linear-gradient": "^15.0.8"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "@tanstack/react-query": ">=5.0.0",
@@ -47,8 +48,8 @@
47
48
  "@tanstack/react-query": "^5.62.16",
48
49
  "@types/react": "^19.0.0",
49
50
  "@umituz/react-native-design-system": "latest",
50
- "@umituz/react-native-sharing": "latest",
51
51
  "@umituz/react-native-firestore": "latest",
52
+ "@umituz/react-native-sharing": "latest",
52
53
  "firebase": "^11.0.0",
53
54
  "react": "19.1.0",
54
55
  "react-native": "0.81.5",
@@ -0,0 +1,74 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
4
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+
6
+ interface DetailActionsProps {
7
+ readonly onShare: () => void;
8
+ readonly onDelete: () => void;
9
+ readonly shareLabel: string;
10
+ readonly deleteLabel: string;
11
+ }
12
+
13
+ export const DetailActions: React.FC<DetailActionsProps> = ({
14
+ onShare,
15
+ onDelete,
16
+ shareLabel,
17
+ deleteLabel
18
+ }) => {
19
+ const tokens = useAppDesignTokens();
20
+ const styles = useStyles(tokens);
21
+
22
+ return (
23
+ <View style={styles.container}>
24
+ <TouchableOpacity
25
+ style={[styles.button, styles.shareButton]}
26
+ onPress={onShare}
27
+ >
28
+ <AtomicIcon name="share-social" size="sm" color="#fff" />
29
+ <AtomicText style={styles.buttonText}>{shareLabel}</AtomicText>
30
+ </TouchableOpacity>
31
+
32
+ <TouchableOpacity
33
+ style={[styles.button, styles.deleteButton]}
34
+ onPress={onDelete}
35
+ >
36
+ <AtomicIcon name="trash" size="sm" color={tokens.colors.error} />
37
+ <AtomicText style={[styles.buttonText, { color: tokens.colors.error }]}>
38
+ {deleteLabel}
39
+ </AtomicText>
40
+ </TouchableOpacity>
41
+ </View>
42
+ );
43
+ };
44
+
45
+ const useStyles = (tokens: any) => StyleSheet.create({
46
+ container: {
47
+ flexDirection: 'row',
48
+ justifyContent: 'center',
49
+ gap: tokens.spacing.md,
50
+ paddingHorizontal: tokens.spacing.lg,
51
+ marginBottom: tokens.spacing.xxl,
52
+ },
53
+ button: {
54
+ flexDirection: 'row',
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ paddingVertical: 12,
58
+ paddingHorizontal: 24,
59
+ borderRadius: 16,
60
+ gap: 8,
61
+ minWidth: 120,
62
+ },
63
+ shareButton: {
64
+ backgroundColor: tokens.colors.primary,
65
+ },
66
+ deleteButton: {
67
+ backgroundColor: tokens.colors.error + '10',
68
+ },
69
+ buttonText: {
70
+ fontWeight: '700',
71
+ color: '#fff',
72
+ fontSize: 15,
73
+ },
74
+ });
@@ -0,0 +1,81 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
4
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
+
7
+ interface DetailHeaderProps {
8
+ readonly title: string;
9
+ readonly date: string;
10
+ readonly onClose: () => void;
11
+ }
12
+
13
+ export const DetailHeader: React.FC<DetailHeaderProps> = ({ title, date, onClose }) => {
14
+ const tokens = useAppDesignTokens();
15
+ const insets = useSafeAreaInsets();
16
+ const styles = useStyles(tokens, insets);
17
+
18
+ return (
19
+ <View style={styles.headerContainer}>
20
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
21
+ <AtomicIcon name="arrow-back" size={24} customColor={tokens.colors.textPrimary} />
22
+ </TouchableOpacity>
23
+
24
+ <View style={styles.titleContainer}>
25
+ <AtomicText style={styles.title} numberOfLines={1}>{title}</AtomicText>
26
+ <View style={styles.dateBadge}>
27
+ <AtomicIcon name="calendar-outline" size={12} customColor={tokens.colors.primary} />
28
+ <AtomicText style={styles.dateText}>{date}</AtomicText>
29
+ </View>
30
+ </View>
31
+
32
+ <View style={styles.placeholder} />
33
+ </View>
34
+ );
35
+ };
36
+
37
+ const useStyles = (tokens: any, insets: any) => StyleSheet.create({
38
+ headerContainer: {
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ paddingTop: insets.top + tokens.spacing.sm,
42
+ paddingBottom: tokens.spacing.md,
43
+ paddingHorizontal: tokens.spacing.md,
44
+ backgroundColor: tokens.colors.backgroundPrimary,
45
+ borderBottomWidth: 1,
46
+ borderBottomColor: tokens.colors.border,
47
+ zIndex: 10,
48
+ },
49
+ closeButton: {
50
+ padding: tokens.spacing.xs,
51
+ marginRight: tokens.spacing.sm,
52
+ },
53
+ titleContainer: {
54
+ flex: 1,
55
+ alignItems: 'center',
56
+ },
57
+ title: {
58
+ fontSize: 18,
59
+ fontWeight: '700',
60
+ color: tokens.colors.textPrimary,
61
+ marginBottom: 4,
62
+ textAlign: 'center',
63
+ },
64
+ dateBadge: {
65
+ flexDirection: 'row',
66
+ alignItems: 'center',
67
+ gap: 4,
68
+ paddingHorizontal: 10,
69
+ paddingVertical: 4,
70
+ borderRadius: 12,
71
+ backgroundColor: tokens.colors.primary + '15',
72
+ },
73
+ dateText: {
74
+ fontSize: 12,
75
+ fontWeight: '600',
76
+ color: tokens.colors.primary,
77
+ },
78
+ placeholder: {
79
+ width: 40,
80
+ },
81
+ });
@@ -0,0 +1,46 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, Image, Dimensions } from 'react-native';
4
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
+
6
+ interface DetailImageProps {
7
+ readonly uri: string;
8
+ }
9
+
10
+ const { width } = Dimensions.get('window');
11
+
12
+ export const DetailImage: React.FC<DetailImageProps> = ({ uri }) => {
13
+ const tokens = useAppDesignTokens();
14
+ const styles = useStyles(tokens);
15
+
16
+ return (
17
+ <View style={styles.container}>
18
+ <View style={styles.frame}>
19
+ <Image source={{ uri }} style={styles.image} resizeMode="cover" />
20
+ </View>
21
+ </View>
22
+ );
23
+ };
24
+
25
+ const useStyles = (tokens: any) => StyleSheet.create({
26
+ container: {
27
+ paddingHorizontal: tokens.spacing.lg,
28
+ marginVertical: tokens.spacing.lg,
29
+ },
30
+ frame: {
31
+ width: width - (tokens.spacing.lg * 2),
32
+ height: width - (tokens.spacing.lg * 2),
33
+ borderRadius: 24,
34
+ overflow: 'hidden',
35
+ backgroundColor: tokens.colors.surface,
36
+ shadowColor: "#000",
37
+ shadowOffset: { width: 0, height: 8 },
38
+ shadowOpacity: 0.2,
39
+ shadowRadius: 16,
40
+ elevation: 8,
41
+ },
42
+ image: {
43
+ width: '100%',
44
+ height: '100%',
45
+ },
46
+ });
@@ -0,0 +1,67 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet } from 'react-native';
4
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import { LinearGradient } from 'expo-linear-gradient';
6
+
7
+ interface DetailStoryProps {
8
+ readonly story: string;
9
+ }
10
+
11
+ export const DetailStory: React.FC<DetailStoryProps> = ({ story }) => {
12
+ const tokens = useAppDesignTokens();
13
+ const styles = useStyles(tokens);
14
+
15
+ if (!story) return null;
16
+
17
+ return (
18
+ <View style={styles.container}>
19
+ <LinearGradient
20
+ colors={[tokens.colors.primary + '15', tokens.colors.primary + '05']}
21
+ style={styles.gradient}
22
+ >
23
+ <AtomicText style={styles.quoteMark}>&quot;</AtomicText>
24
+ <AtomicText style={styles.text}>{story}</AtomicText>
25
+ <View style={styles.quoteEndRow}>
26
+ <AtomicText style={[styles.quoteMark, styles.quoteEnd]}>&quot;</AtomicText>
27
+ </View>
28
+ </LinearGradient>
29
+ </View>
30
+ );
31
+ };
32
+
33
+ const useStyles = (tokens: any) => StyleSheet.create({
34
+ container: {
35
+ paddingHorizontal: tokens.spacing.lg,
36
+ marginBottom: tokens.spacing.lg,
37
+ },
38
+ gradient: {
39
+ padding: tokens.spacing.lg,
40
+ borderRadius: 20,
41
+ borderWidth: 1,
42
+ borderColor: tokens.colors.primary + '20',
43
+ },
44
+ quoteMark: {
45
+ fontSize: 48,
46
+ lineHeight: 48,
47
+ color: tokens.colors.primary,
48
+ opacity: 0.4,
49
+ marginBottom: -16,
50
+ },
51
+ quoteEndRow: {
52
+ alignItems: 'flex-end',
53
+ marginTop: -16,
54
+ },
55
+ quoteEnd: {
56
+ marginBottom: 0,
57
+ },
58
+ text: {
59
+ fontSize: 16,
60
+ lineHeight: 26,
61
+ textAlign: 'center',
62
+ fontStyle: 'italic',
63
+ fontWeight: '500',
64
+ color: tokens.colors.textPrimary,
65
+ paddingBottom: 4,
66
+ },
67
+ });
@@ -8,6 +8,7 @@ interface GalleryHeaderProps {
8
8
  readonly countLabel: string;
9
9
  readonly isFiltered: boolean;
10
10
  readonly onFilterPress: () => void;
11
+ readonly style?: any;
11
12
  }
12
13
 
13
14
  export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
@@ -15,13 +16,14 @@ export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
15
16
  count,
16
17
  countLabel,
17
18
  isFiltered,
18
- onFilterPress
19
+ onFilterPress,
20
+ style,
19
21
  }) => {
20
22
  const tokens = useAppDesignTokens();
21
23
  const styles = useStyles(tokens);
22
24
 
23
25
  return (
24
- <View style={styles.headerArea}>
26
+ <View style={[styles.headerArea, style]}>
25
27
  <View>
26
28
  <AtomicText style={styles.title}>{title}</AtomicText>
27
29
  <AtomicText style={styles.subtitle}>
@@ -0,0 +1,71 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, ScrollView } from 'react-native';
4
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import type { Creation } from '../../domain/entities/Creation';
6
+ import { DetailHeader } from '../components/CreationDetail/DetailHeader';
7
+ import { DetailImage } from '../components/CreationDetail/DetailImage';
8
+ import { DetailStory } from '../components/CreationDetail/DetailStory';
9
+ import { DetailActions } from '../components/CreationDetail/DetailActions';
10
+
11
+ interface CreationDetailScreenProps {
12
+ readonly creation: Creation;
13
+ readonly onClose: () => void;
14
+ readonly onShare: (creation: Creation) => void;
15
+ readonly onDelete: (creation: Creation) => void;
16
+ readonly t: (key: string) => string;
17
+ }
18
+
19
+ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
20
+ creation,
21
+ onClose,
22
+ onShare,
23
+ onDelete,
24
+ t
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+
28
+ // Extract data
29
+ const metadata = (creation as any).metadata || {};
30
+ const title = metadata.names || creation.type;
31
+ const story = metadata.story || metadata.description || "";
32
+ const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
33
+
34
+ const styles = useStyles(tokens);
35
+
36
+ return (
37
+ <View style={styles.container}>
38
+ <DetailHeader
39
+ title={title}
40
+ date={date}
41
+ onClose={onClose}
42
+ />
43
+
44
+ <ScrollView
45
+ contentContainerStyle={styles.scrollContent}
46
+ showsVerticalScrollIndicator={false}
47
+ >
48
+ <DetailImage uri={creation.uri} />
49
+
50
+ <DetailStory story={story} />
51
+
52
+ <DetailActions
53
+ onShare={() => onShare(creation)}
54
+ onDelete={() => onDelete(creation)}
55
+ shareLabel={t("result.shareButton") || "Share"}
56
+ deleteLabel={t("common.delete") || "Delete"}
57
+ />
58
+ </ScrollView>
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const useStyles = (tokens: any) => StyleSheet.create({
64
+ container: {
65
+ flex: 1,
66
+ backgroundColor: tokens.colors.backgroundPrimary,
67
+ },
68
+ scrollContent: {
69
+ paddingBottom: tokens.spacing.xxl,
70
+ },
71
+ });
@@ -23,6 +23,7 @@ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
23
23
  import { GalleryHeader } from "../components/GalleryHeader";
24
24
 
25
25
  import { useAlert } from "@umituz/react-native-alert";
26
+ import { CreationDetailScreen } from "./CreationDetailScreen";
26
27
 
27
28
  interface CreationsGalleryScreenProps {
28
29
  readonly userId: string | null;
@@ -49,8 +50,12 @@ export function CreationsGalleryScreen({
49
50
  const insets = useSafeAreaInsets();
50
51
  const { share } = useSharing();
51
52
  const alert = useAlert();
53
+
54
+ // State
52
55
  const [viewerVisible, setViewerVisible] = useState(false);
53
56
  const [viewerIndex, setViewerIndex] = useState(0);
57
+ const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
58
+
54
59
  const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
55
60
 
56
61
  const { data: creations, isLoading, refetch } = useCreations({
@@ -65,7 +70,6 @@ export function CreationsGalleryScreen({
65
70
  const allCategories = useMemo(() => {
66
71
  const categories: FilterCategory[] = [];
67
72
 
68
- // Add dynamic types category if types exist
69
73
  if (config.types.length > 0) {
70
74
  categories.push({
71
75
  id: 'type',
@@ -86,16 +90,13 @@ export function CreationsGalleryScreen({
86
90
  return categories;
87
91
  }, [config.types, config.filterCategories, t]);
88
92
 
89
- const handleView = useCallback(
90
- (creation: Creation) => {
91
- const index = filtered.findIndex((c) => c.id === creation.id);
92
- if (index >= 0) {
93
- setViewerIndex(index);
94
- setViewerVisible(true);
95
- }
96
- },
97
- [filtered],
98
- );
93
+ const handleView = useCallback((creation: Creation) => {
94
+ setSelectedCreation(creation);
95
+ }, []);
96
+
97
+ const handleCloseDetail = useCallback(() => {
98
+ setSelectedCreation(null);
99
+ }, []);
99
100
 
100
101
  const handleShare = useCallback(
101
102
  async (creation: Creation) => {
@@ -129,7 +130,10 @@ export function CreationsGalleryScreen({
129
130
  label: t("common.delete"),
130
131
  style: 'destructive',
131
132
  variant: 'danger',
132
- onPress: () => deleteMutation.mutate(creation.id),
133
+ onPress: () => {
134
+ deleteMutation.mutate(creation.id);
135
+ setSelectedCreation(null);
136
+ },
133
137
  },
134
138
  ]
135
139
  });
@@ -173,7 +177,7 @@ export function CreationsGalleryScreen({
173
177
  <CreationCard
174
178
  creation={item}
175
179
  types={config.types}
176
- onView={handleView}
180
+ onView={() => handleView(item)}
177
181
  onShare={handleShare}
178
182
  onDelete={handleDelete}
179
183
  />
@@ -181,6 +185,19 @@ export function CreationsGalleryScreen({
181
185
  [config.types, handleView, handleShare, handleDelete],
182
186
  );
183
187
 
188
+ // If a creation is selected, show detail screen (simulating a stack push)
189
+ if (selectedCreation) {
190
+ return (
191
+ <CreationDetailScreen
192
+ creation={selectedCreation}
193
+ onClose={handleCloseDetail}
194
+ onShare={handleShare}
195
+ onDelete={handleDelete}
196
+ t={t}
197
+ />
198
+ );
199
+ }
200
+
184
201
  if (!isLoading && (!creations || creations.length === 0)) {
185
202
  return (
186
203
  <View style={styles.container}>
@@ -202,6 +219,7 @@ export function CreationsGalleryScreen({
202
219
  countLabel={t(config.translations.photoCount) || 'photos'}
203
220
  isFiltered={isFiltered}
204
221
  onFilterPress={() => filterSheetRef.current?.present()}
222
+ style={{ paddingTop: insets.top }}
205
223
  />
206
224
 
207
225
  <FlatList
@@ -217,6 +235,8 @@ export function CreationsGalleryScreen({
217
235
  />
218
236
  }
219
237
  />
238
+
239
+ {/* Optional: Keep ImageGallery for quick preview if needed, or rely on Detail Screen */}
220
240
  <ImageGallery
221
241
  images={viewerImages}
222
242
  visible={viewerVisible}