@umituz/react-native-ai-generation-content 1.12.43 → 1.13.0
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 +2 -2
- package/src/domains/creations/index.ts +4 -0
- package/src/domains/creations/presentation/components/CreationCard.tsx +6 -4
- package/src/domains/creations/presentation/components/CreationDetail/DetailHeader.tsx +3 -5
- package/src/domains/creations/presentation/components/CreationsGrid.tsx +0 -3
- package/src/domains/creations/presentation/components/GalleryEmptyStates.tsx +70 -5
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +34 -28
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +43 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -102,4 +102,4 @@
|
|
|
102
102
|
"publishConfig": {
|
|
103
103
|
"access": "public"
|
|
104
104
|
}
|
|
105
|
-
}
|
|
105
|
+
}
|
|
@@ -75,6 +75,10 @@ export { CreationCard } from "./presentation/components/CreationCard";
|
|
|
75
75
|
export { CreationsHomeCard } from "./presentation/components/CreationsHomeCard";
|
|
76
76
|
export { FilterChips } from "./presentation/components/FilterChips";
|
|
77
77
|
export { EmptyState } from "./presentation/components/EmptyState";
|
|
78
|
+
export {
|
|
79
|
+
CreationsProvider,
|
|
80
|
+
useCreationsProvider,
|
|
81
|
+
} from "./presentation/components/CreationsProvider";
|
|
78
82
|
|
|
79
83
|
// =============================================================================
|
|
80
84
|
// PRESENTATION LAYER - Screens
|
|
@@ -9,9 +9,10 @@ import { timezoneService } from "@umituz/react-native-timezone";
|
|
|
9
9
|
import type { Creation } from "../../domain/entities/Creation";
|
|
10
10
|
import type { CreationType } from "../../domain/value-objects/CreationsConfig";
|
|
11
11
|
|
|
12
|
+
import { useCreationsProvider } from "./CreationsProvider";
|
|
13
|
+
|
|
12
14
|
interface CreationCardProps {
|
|
13
15
|
readonly creation: Creation;
|
|
14
|
-
readonly types: readonly CreationType[];
|
|
15
16
|
readonly onView?: (creation: Creation) => void;
|
|
16
17
|
readonly onShare: (creation: Creation) => void;
|
|
17
18
|
readonly onDelete: (creation: Creation) => void;
|
|
@@ -21,7 +22,6 @@ interface CreationCardProps {
|
|
|
21
22
|
|
|
22
23
|
export function CreationCard({
|
|
23
24
|
creation,
|
|
24
|
-
types,
|
|
25
25
|
onView,
|
|
26
26
|
onShare,
|
|
27
27
|
onDelete,
|
|
@@ -29,10 +29,12 @@ export function CreationCard({
|
|
|
29
29
|
locale = "en-US",
|
|
30
30
|
}: CreationCardProps) {
|
|
31
31
|
const tokens = useAppDesignTokens();
|
|
32
|
+
const { translatedTypes, t } = useCreationsProvider();
|
|
32
33
|
|
|
33
|
-
const typeConfig =
|
|
34
|
+
const typeConfig = translatedTypes.find((t) => t.id === creation.type);
|
|
34
35
|
const icon = typeConfig?.icon;
|
|
35
|
-
|
|
36
|
+
// Use manual name if available, otherwise use translated label from config
|
|
37
|
+
const label = (creation.metadata?.names as string) || typeConfig?.labelKey || creation.type;
|
|
36
38
|
|
|
37
39
|
const handleView = useCallback(() => onView?.(creation), [creation, onView]);
|
|
38
40
|
const handleShare = useCallback(() => onShare(creation), [creation, onShare]);
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { View, StyleSheet, TouchableOpacity } from 'react-native';
|
|
4
4
|
import { AtomicText, AtomicIcon, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
|
|
5
|
-
import { type EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
5
|
|
|
7
6
|
interface DetailHeaderProps {
|
|
8
7
|
readonly title: string;
|
|
@@ -12,8 +11,7 @@ interface DetailHeaderProps {
|
|
|
12
11
|
|
|
13
12
|
export const DetailHeader: React.FC<DetailHeaderProps> = ({ title, date, onClose }) => {
|
|
14
13
|
const tokens = useAppDesignTokens();
|
|
15
|
-
const
|
|
16
|
-
const styles = useStyles(tokens, insets);
|
|
14
|
+
const styles = useStyles(tokens);
|
|
17
15
|
|
|
18
16
|
return (
|
|
19
17
|
<View style={styles.headerContainer}>
|
|
@@ -34,11 +32,11 @@ export const DetailHeader: React.FC<DetailHeaderProps> = ({ title, date, onClose
|
|
|
34
32
|
);
|
|
35
33
|
};
|
|
36
34
|
|
|
37
|
-
const useStyles = (tokens: DesignTokens
|
|
35
|
+
const useStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
38
36
|
headerContainer: {
|
|
39
37
|
flexDirection: 'row',
|
|
40
38
|
alignItems: 'center',
|
|
41
|
-
paddingTop:
|
|
39
|
+
paddingTop: tokens.spacing.sm,
|
|
42
40
|
paddingBottom: tokens.spacing.md,
|
|
43
41
|
paddingHorizontal: tokens.spacing.md,
|
|
44
42
|
backgroundColor: tokens.colors.background,
|
|
@@ -7,7 +7,6 @@ import { CreationCard } from "./CreationCard";
|
|
|
7
7
|
|
|
8
8
|
interface CreationsGridProps {
|
|
9
9
|
readonly creations: Creation[];
|
|
10
|
-
readonly types: readonly CreationType[];
|
|
11
10
|
readonly isLoading: boolean;
|
|
12
11
|
readonly onRefresh: () => void;
|
|
13
12
|
readonly onView: (creation: Creation) => void;
|
|
@@ -22,7 +21,6 @@ interface CreationsGridProps {
|
|
|
22
21
|
|
|
23
22
|
export const CreationsGrid: React.FC<CreationsGridProps> = ({
|
|
24
23
|
creations,
|
|
25
|
-
types,
|
|
26
24
|
isLoading,
|
|
27
25
|
onRefresh,
|
|
28
26
|
onView,
|
|
@@ -40,7 +38,6 @@ export const CreationsGrid: React.FC<CreationsGridProps> = ({
|
|
|
40
38
|
const renderItem = ({ item }: { item: Creation }) => (
|
|
41
39
|
<CreationCard
|
|
42
40
|
creation={item}
|
|
43
|
-
types={types as CreationType[]}
|
|
44
41
|
onView={() => onView(item)}
|
|
45
42
|
onShare={() => onShare(item)}
|
|
46
43
|
onDelete={() => onDelete(item)}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import type
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { AtomicSkeleton, type DesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { EmptyState } from "./EmptyState";
|
|
10
10
|
import type { Creation } from "../../domain/entities/Creation";
|
|
11
11
|
|
|
@@ -22,6 +22,42 @@ interface GalleryEmptyStatesProps {
|
|
|
22
22
|
onClearFilters: () => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Skeleton card matching CreationCard layout */
|
|
26
|
+
function CreationCardSkeleton({ tokens }: { tokens: DesignTokens }) {
|
|
27
|
+
const styles = createSkeletonStyles(tokens);
|
|
28
|
+
return (
|
|
29
|
+
<View style={styles.card}>
|
|
30
|
+
{/* Thumbnail skeleton */}
|
|
31
|
+
<AtomicSkeleton
|
|
32
|
+
pattern="custom"
|
|
33
|
+
custom={[{ width: 100, height: 100, borderRadius: 0 }]}
|
|
34
|
+
/>
|
|
35
|
+
{/* Content skeleton */}
|
|
36
|
+
<View style={styles.content}>
|
|
37
|
+
<View style={styles.textArea}>
|
|
38
|
+
<AtomicSkeleton
|
|
39
|
+
pattern="custom"
|
|
40
|
+
custom={[
|
|
41
|
+
{ width: 120, height: 18, borderRadius: 4, marginBottom: 8 },
|
|
42
|
+
{ width: 100, height: 14, borderRadius: 4 },
|
|
43
|
+
]}
|
|
44
|
+
/>
|
|
45
|
+
</View>
|
|
46
|
+
{/* Action buttons skeleton */}
|
|
47
|
+
<View style={styles.actions}>
|
|
48
|
+
{[1, 2, 3, 4].map((i) => (
|
|
49
|
+
<AtomicSkeleton
|
|
50
|
+
key={i}
|
|
51
|
+
pattern="custom"
|
|
52
|
+
custom={[{ width: 36, height: 36, borderRadius: 18 }]}
|
|
53
|
+
/>
|
|
54
|
+
))}
|
|
55
|
+
</View>
|
|
56
|
+
</View>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
25
61
|
export function GalleryEmptyStates({
|
|
26
62
|
isLoading,
|
|
27
63
|
creations,
|
|
@@ -36,11 +72,13 @@ export function GalleryEmptyStates({
|
|
|
36
72
|
}: GalleryEmptyStatesProps) {
|
|
37
73
|
const styles = createStyles(tokens);
|
|
38
74
|
|
|
39
|
-
// 1. Loading State
|
|
75
|
+
// 1. Loading State - Show skeleton cards
|
|
40
76
|
if (isLoading && (!creations || creations?.length === 0)) {
|
|
41
77
|
return (
|
|
42
|
-
<View style={styles.
|
|
43
|
-
|
|
78
|
+
<View style={styles.skeletonContainer}>
|
|
79
|
+
{[1, 2, 3].map((i) => (
|
|
80
|
+
<CreationCardSkeleton key={i} tokens={tokens} />
|
|
81
|
+
))}
|
|
44
82
|
</View>
|
|
45
83
|
);
|
|
46
84
|
}
|
|
@@ -84,4 +122,31 @@ const createStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
|
84
122
|
minHeight: 400,
|
|
85
123
|
paddingHorizontal: tokens.spacing.xl,
|
|
86
124
|
},
|
|
125
|
+
skeletonContainer: {
|
|
126
|
+
flex: 1,
|
|
127
|
+
paddingHorizontal: tokens.spacing.md,
|
|
128
|
+
paddingTop: tokens.spacing.md,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const createSkeletonStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
133
|
+
card: {
|
|
134
|
+
flexDirection: 'row',
|
|
135
|
+
backgroundColor: tokens.colors.surface,
|
|
136
|
+
borderRadius: tokens.spacing.md,
|
|
137
|
+
overflow: 'hidden',
|
|
138
|
+
marginBottom: tokens.spacing.md,
|
|
139
|
+
},
|
|
140
|
+
content: {
|
|
141
|
+
flex: 1,
|
|
142
|
+
padding: tokens.spacing.md,
|
|
143
|
+
justifyContent: 'space-between',
|
|
144
|
+
},
|
|
145
|
+
textArea: {
|
|
146
|
+
gap: tokens.spacing.xs,
|
|
147
|
+
},
|
|
148
|
+
actions: {
|
|
149
|
+
flexDirection: 'row',
|
|
150
|
+
gap: tokens.spacing.sm,
|
|
151
|
+
},
|
|
87
152
|
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { useAppDesignTokens, type DesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
|
|
5
4
|
import type { Creation } from '../../domain/entities/Creation';
|
|
6
5
|
import { DetailHeader } from '../components/CreationDetail/DetailHeader';
|
|
7
6
|
import { DetailImage } from '../components/CreationDetail/DetailImage';
|
|
8
7
|
import { DetailStory } from '../components/CreationDetail/DetailStory';
|
|
9
8
|
import { DetailActions } from '../components/CreationDetail/DetailActions';
|
|
10
9
|
|
|
10
|
+
import { useCreationsProvider } from '../components/CreationsProvider';
|
|
11
|
+
|
|
11
12
|
interface CreationDetailScreenProps {
|
|
12
13
|
readonly creation: Creation;
|
|
13
14
|
readonly onClose: () => void;
|
|
@@ -31,47 +32,52 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
31
32
|
t
|
|
32
33
|
}) => {
|
|
33
34
|
const tokens = useAppDesignTokens();
|
|
35
|
+
const { getLocalizedTitle } = useCreationsProvider();
|
|
34
36
|
|
|
35
37
|
// Extract data safely
|
|
36
38
|
const metadata = (creation.metadata || {}) as CreationMetadata;
|
|
37
|
-
|
|
39
|
+
|
|
40
|
+
// Resolve title:
|
|
41
|
+
// 1. Manually set names in metadata
|
|
42
|
+
// 2. Localized title from config types mapping
|
|
43
|
+
// 3. Fallback to raw creation type (formatted)
|
|
44
|
+
const title = metadata.names || getLocalizedTitle(creation.type);
|
|
38
45
|
const story = metadata.story || metadata.description || "";
|
|
39
46
|
const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
|
|
40
47
|
|
|
41
48
|
const styles = useStyles(tokens);
|
|
42
49
|
|
|
43
50
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
<ScreenLayout
|
|
52
|
+
scrollable={true}
|
|
53
|
+
edges={['top', 'bottom']}
|
|
54
|
+
backgroundColor={tokens.colors.background}
|
|
55
|
+
header={
|
|
56
|
+
<DetailHeader
|
|
57
|
+
title={title}
|
|
58
|
+
date={date}
|
|
59
|
+
onClose={onClose}
|
|
60
|
+
/>
|
|
61
|
+
}
|
|
62
|
+
contentContainerStyle={styles.scrollContent}
|
|
63
|
+
>
|
|
64
|
+
<DetailImage uri={creation.uri} />
|
|
56
65
|
|
|
66
|
+
{story ? (
|
|
57
67
|
<DetailStory story={story} />
|
|
68
|
+
) : null}
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</View>
|
|
70
|
+
<DetailActions
|
|
71
|
+
onShare={() => onShare(creation)}
|
|
72
|
+
onDelete={() => onDelete(creation)}
|
|
73
|
+
shareLabel={t("result.shareButton") || "Share"}
|
|
74
|
+
deleteLabel={t("common.delete") || "Delete"}
|
|
75
|
+
/>
|
|
76
|
+
</ScreenLayout>
|
|
67
77
|
);
|
|
68
78
|
};
|
|
69
79
|
|
|
70
80
|
const useStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
71
|
-
container: {
|
|
72
|
-
flex: 1,
|
|
73
|
-
backgroundColor: tokens.colors.background,
|
|
74
|
-
},
|
|
75
81
|
scrollContent: {
|
|
76
82
|
paddingBottom: tokens.spacing.xxl,
|
|
77
83
|
},
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
useSharing,
|
|
11
11
|
FilterBottomSheet,
|
|
12
12
|
type DesignTokens,
|
|
13
|
-
type BottomSheetModalRef
|
|
13
|
+
type BottomSheetModalRef,
|
|
14
|
+
ScreenLayout
|
|
14
15
|
} from "@umituz/react-native-design-system";
|
|
15
16
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
16
17
|
import { useFocusEffect } from "@react-navigation/native";
|
|
@@ -37,7 +38,17 @@ interface CreationsGalleryScreenProps {
|
|
|
37
38
|
readonly showFilter?: boolean;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
import { CreationsProvider } from "../components/CreationsProvider";
|
|
42
|
+
|
|
43
|
+
export function CreationsGalleryScreen(props: CreationsGalleryScreenProps) {
|
|
44
|
+
return (
|
|
45
|
+
<CreationsProvider config={props.config} t={props.t}>
|
|
46
|
+
<CreationsGalleryScreenContent {...props} />
|
|
47
|
+
</CreationsProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CreationsGalleryScreenContent({
|
|
41
52
|
userId,
|
|
42
53
|
repository,
|
|
43
54
|
config,
|
|
@@ -149,40 +160,38 @@ export function CreationsGalleryScreen({
|
|
|
149
160
|
);
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
const handleLayout = (event: LayoutChangeEvent) => {
|
|
153
|
-
// Keep internal logic if needed, currently empty but handles the event correctly
|
|
154
|
-
void event;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
163
|
return (
|
|
158
|
-
<
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
<ScreenLayout
|
|
165
|
+
scrollable={false}
|
|
166
|
+
edges={["top"]}
|
|
167
|
+
backgroundColor={tokens.colors.background}
|
|
168
|
+
header={
|
|
169
|
+
(!creations || creations?.length === 0) && !isLoading ? null : (
|
|
170
|
+
<GalleryHeader
|
|
171
|
+
title={t(config.translations.title) || 'My Creations'}
|
|
172
|
+
count={filtered.length}
|
|
173
|
+
countLabel={t(config.translations.photoCount) || 'photos'}
|
|
174
|
+
isFiltered={isFiltered}
|
|
175
|
+
showFilter={showFilter}
|
|
176
|
+
filterLabel={t(config.translations.filterLabel) || 'Filter'}
|
|
177
|
+
onFilterPress={() => {
|
|
178
|
+
if (__DEV__) {
|
|
179
|
+
// eslint-disable-next-line no-console
|
|
180
|
+
console.log('[CreationsGallery] Filter button pressed');
|
|
181
|
+
// eslint-disable-next-line no-console
|
|
182
|
+
console.log('[CreationsGallery] filterSheetRef.current:', filterSheetRef.current);
|
|
183
|
+
// eslint-disable-next-line no-console
|
|
184
|
+
console.log('[CreationsGallery] allCategories:', allCategories);
|
|
185
|
+
}
|
|
186
|
+
filterSheetRef.current?.present();
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
>
|
|
182
192
|
{/* Main Content Grid - handles empty/loading via ListEmptyComponent */}
|
|
183
193
|
<CreationsGrid
|
|
184
194
|
creations={filtered}
|
|
185
|
-
types={translatedTypes}
|
|
186
195
|
isLoading={isLoading}
|
|
187
196
|
onRefresh={() => void refetch()}
|
|
188
197
|
onView={handleView}
|
|
@@ -190,7 +199,7 @@ export function CreationsGalleryScreen({
|
|
|
190
199
|
onDelete={handleDelete}
|
|
191
200
|
onFavorite={handleFavorite}
|
|
192
201
|
locale={locale}
|
|
193
|
-
contentContainerStyle={{ paddingBottom:
|
|
202
|
+
contentContainerStyle={{ paddingBottom: tokens.spacing.xl }}
|
|
194
203
|
ListEmptyComponent={renderEmptyComponent}
|
|
195
204
|
/>
|
|
196
205
|
|
|
@@ -215,7 +224,7 @@ export function CreationsGalleryScreen({
|
|
|
215
224
|
onClearFilters={clearFilters}
|
|
216
225
|
title={t(config.translations.filterTitle) || t("common.filter")}
|
|
217
226
|
/>
|
|
218
|
-
</
|
|
227
|
+
</ScreenLayout>
|
|
219
228
|
);
|
|
220
229
|
}
|
|
221
230
|
|