@umituz/react-native-ai-generation-content 1.12.7 → 1.12.9
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 +5 -7
- package/src/debug-props.ts +10 -0
- package/src/domains/creations/presentation/components/CreationsGalleryEmptyState.tsx +77 -0
- package/src/domains/creations/presentation/components/index.ts +1 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +29 -75
- package/src/domains/feature-background/presentation/components/GenerateButton.tsx +1 -1
- package/src/domains/feature-background/presentation/components/ImagePicker.tsx +2 -2
- package/src/domains/feature-background/presentation/components/ModeSelector.tsx +1 -1
- package/src/domains/feature-background/presentation/components/ResultDisplay.tsx +2 -2
- package/src/presentation/components/result/ResultActions.tsx +3 -3
- package/src/presentation/components/result/ResultHeader.tsx +1 -1
- package/src/presentation/components/result/ResultImageCard.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.9",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"typecheck": "
|
|
20
|
-
"lint": "
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"lint": "eslint src --ext .ts,.tsx --max-warnings 0",
|
|
21
21
|
"lint:fix": "eslint src --ext .ts,.tsx --fix"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@react-navigation/native": ">=6.0.0",
|
|
42
42
|
"@tanstack/react-query": ">=5.0.0",
|
|
43
|
-
"@umituz/react-native-alert": "latest",
|
|
44
43
|
"@umituz/react-native-animation": "latest",
|
|
45
44
|
"@umituz/react-native-bottom-sheet": "latest",
|
|
46
45
|
"@umituz/react-native-design-system": "latest",
|
|
@@ -62,7 +61,6 @@
|
|
|
62
61
|
"@types/react-native": "^0.73.0",
|
|
63
62
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
64
63
|
"@typescript-eslint/parser": "^7.0.0",
|
|
65
|
-
"@umituz/react-native-alert": "latest",
|
|
66
64
|
"@umituz/react-native-animation": "latest",
|
|
67
65
|
"@umituz/react-native-bottom-sheet": "latest",
|
|
68
66
|
"@umituz/react-native-design-system": "latest",
|
|
@@ -75,11 +73,11 @@
|
|
|
75
73
|
"firebase": "^11.1.0",
|
|
76
74
|
"react": "19.1.0",
|
|
77
75
|
"react-native": "0.81.5",
|
|
78
|
-
"react-native-safe-area-context": "^
|
|
76
|
+
"react-native-safe-area-context": "^5.6.2",
|
|
79
77
|
"typescript": "^5.3.0",
|
|
80
78
|
"zustand": "^5.0.2"
|
|
81
79
|
},
|
|
82
80
|
"publishConfig": {
|
|
83
81
|
"access": "public"
|
|
84
82
|
}
|
|
85
|
-
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
import { AtomicTextProps, AtomicIconProps } from "@umituz/react-native-design-system";
|
|
3
|
+
|
|
4
|
+
const textProps: AtomicTextProps = {
|
|
5
|
+
// This helps us see what is required/optional in IDE or tsc output
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const iconProps: AtomicIconProps = {
|
|
9
|
+
name: 'check',
|
|
10
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { View, StyleSheet, ActivityIndicator } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import { EmptyState } from "./EmptyState";
|
|
5
|
+
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
6
|
+
|
|
7
|
+
interface CreationsGalleryEmptyStateProps {
|
|
8
|
+
readonly isLoading: boolean;
|
|
9
|
+
readonly hasCreations: boolean;
|
|
10
|
+
readonly config: CreationsConfig;
|
|
11
|
+
readonly t: (key: string) => string;
|
|
12
|
+
readonly emptyActionLabel?: string;
|
|
13
|
+
readonly onEmptyAction?: () => void;
|
|
14
|
+
readonly clearFilters: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const CreationsGalleryEmptyState: React.FC<CreationsGalleryEmptyStateProps> = ({
|
|
18
|
+
isLoading,
|
|
19
|
+
hasCreations,
|
|
20
|
+
config,
|
|
21
|
+
t,
|
|
22
|
+
emptyActionLabel,
|
|
23
|
+
onEmptyAction,
|
|
24
|
+
clearFilters,
|
|
25
|
+
}) => {
|
|
26
|
+
const tokens = useAppDesignTokens();
|
|
27
|
+
const styles = useStyles(tokens);
|
|
28
|
+
|
|
29
|
+
return useMemo(() => {
|
|
30
|
+
// 1. Loading State
|
|
31
|
+
if (isLoading && !hasCreations) {
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.centerContainer}>
|
|
34
|
+
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. System Empty State (User has NO creations at all)
|
|
40
|
+
// We check 'hasCreations' which represents the full list presence
|
|
41
|
+
if (!hasCreations) {
|
|
42
|
+
return (
|
|
43
|
+
<View style={styles.centerContainer}>
|
|
44
|
+
<EmptyState
|
|
45
|
+
title={t(config.translations.empty)}
|
|
46
|
+
description={t(config.translations.emptyDescription)}
|
|
47
|
+
actionLabel={emptyActionLabel}
|
|
48
|
+
onAction={onEmptyAction}
|
|
49
|
+
/>
|
|
50
|
+
</View>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Filter Empty State (User has creations, but filter returns none)
|
|
55
|
+
// This component is rendered when the list is empty, but hasCreations is true.
|
|
56
|
+
return (
|
|
57
|
+
<View style={styles.centerContainer}>
|
|
58
|
+
<EmptyState
|
|
59
|
+
title={t("common.no_results") || "No results"}
|
|
60
|
+
description={t("common.no_results_description") || "Try changing your filters"}
|
|
61
|
+
actionLabel={t("common.clear_all") || "Clear All"}
|
|
62
|
+
onAction={clearFilters}
|
|
63
|
+
/>
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
}, [isLoading, hasCreations, config, t, emptyActionLabel, onEmptyAction, clearFilters, styles.centerContainer, tokens.colors.primary]);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const useStyles = (tokens: any) => StyleSheet.create({
|
|
70
|
+
centerContainer: {
|
|
71
|
+
flex: 1,
|
|
72
|
+
justifyContent: 'center',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
minHeight: 400,
|
|
75
|
+
paddingHorizontal: tokens.spacing.xl
|
|
76
|
+
},
|
|
77
|
+
});
|
|
@@ -10,6 +10,7 @@ export { CreationCard } from "./CreationCard";
|
|
|
10
10
|
export { CreationThumbnail } from "./CreationThumbnail";
|
|
11
11
|
export { CreationImageViewer } from "./CreationImageViewer";
|
|
12
12
|
export { CreationsGrid } from "./CreationsGrid";
|
|
13
|
+
export { CreationsGalleryEmptyState } from "./CreationsGalleryEmptyState";
|
|
13
14
|
export { FilterBottomSheet, type FilterCategory, type FilterOption } from "./FilterBottomSheet";
|
|
14
15
|
|
|
15
16
|
// Detail Components
|
|
@@ -7,9 +7,9 @@ import { useFocusEffect } from "@react-navigation/native";
|
|
|
7
7
|
import { useCreations } from "../hooks/useCreations";
|
|
8
8
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
9
9
|
import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
10
|
-
import { useAlert } from "@umituz/react-native-
|
|
10
|
+
import { useAlert, AlertMode } from "@umituz/react-native-design-system";
|
|
11
11
|
import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
|
|
12
|
-
import { GalleryHeader,
|
|
12
|
+
import { GalleryHeader, CreationsGrid, FilterBottomSheet, CreationImageViewer, type FilterCategory, CreationsGalleryEmptyState } from "../components";
|
|
13
13
|
import { getTranslatedTypes, getFilterCategoriesFromConfig } from "../utils/filterUtils";
|
|
14
14
|
import type { Creation } from "../../domain/entities/Creation";
|
|
15
15
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
@@ -68,20 +68,22 @@ export function CreationsGalleryScreen({
|
|
|
68
68
|
}, [share, t]);
|
|
69
69
|
|
|
70
70
|
const handleDelete = useCallback(async (creation: Creation) => {
|
|
71
|
-
alert.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
alert.showWarning(
|
|
72
|
+
t(config.translations.deleteTitle),
|
|
73
|
+
t(config.translations.deleteMessage),
|
|
74
|
+
{
|
|
75
|
+
mode: AlertMode.MODAL,
|
|
76
|
+
actions: [
|
|
77
|
+
{ id: 'cancel', label: t("common.cancel"), onPress: () => { } },
|
|
78
|
+
{
|
|
79
|
+
id: 'delete', label: t("common.delete"), style: 'destructive', onPress: async () => {
|
|
80
|
+
const success = await deleteMutation.mutateAsync(creation.id);
|
|
81
|
+
if (success) setSelectedCreation(null);
|
|
82
|
+
}
|
|
81
83
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
);
|
|
85
87
|
}, [alert, config, deleteMutation, t]);
|
|
86
88
|
|
|
87
89
|
// Handle viewing a creation - shows detail screen
|
|
@@ -89,59 +91,8 @@ export function CreationsGalleryScreen({
|
|
|
89
91
|
setSelectedCreation(creation);
|
|
90
92
|
}, []);
|
|
91
93
|
|
|
92
|
-
const styles = useStyles(tokens);
|
|
93
|
-
|
|
94
|
-
// Define empty state content based on state
|
|
95
|
-
const renderEmptyComponent = useMemo(() => {
|
|
96
|
-
// 1. Loading State
|
|
97
|
-
if (isLoading && (!creations || creations?.length === 0)) {
|
|
98
|
-
return (
|
|
99
|
-
<View style={styles.centerContainer}>
|
|
100
|
-
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
101
|
-
</View>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 2. System Empty State (User has NO creations at all)
|
|
106
|
-
// We check 'creations' (the full list)
|
|
107
|
-
if (!creations || creations?.length === 0) {
|
|
108
|
-
return (
|
|
109
|
-
<View style={styles.centerContainer}>
|
|
110
|
-
<EmptyState
|
|
111
|
-
title={t(config.translations.empty)}
|
|
112
|
-
description={t(config.translations.emptyDescription)}
|
|
113
|
-
actionLabel={emptyActionLabel}
|
|
114
|
-
onAction={onEmptyAction}
|
|
115
|
-
/>
|
|
116
|
-
</View>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 3. Filter Empty State (User has creations, but filter returns none)
|
|
121
|
-
// We check 'filtered' (the displayed list)
|
|
122
|
-
return (
|
|
123
|
-
<View style={styles.centerContainer}>
|
|
124
|
-
<EmptyState
|
|
125
|
-
title={t("common.no_results") || "No results"}
|
|
126
|
-
description={t("common.no_results_description") || "Try changing your filters"}
|
|
127
|
-
actionLabel={t("common.clear_all") || "Clear All"}
|
|
128
|
-
onAction={clearFilters}
|
|
129
|
-
/>
|
|
130
|
-
</View>
|
|
131
|
-
);
|
|
132
|
-
}, [isLoading, creations, config, t, emptyActionLabel, onEmptyAction, clearFilters, styles.centerContainer, tokens.colors.primary]);
|
|
133
94
|
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<CreationDetailScreen
|
|
137
|
-
creation={selectedCreation}
|
|
138
|
-
onClose={() => setSelectedCreation(null)}
|
|
139
|
-
onShare={handleShare}
|
|
140
|
-
onDelete={handleDelete}
|
|
141
|
-
t={t}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
95
|
+
const styles = useStyles(tokens);
|
|
145
96
|
|
|
146
97
|
return (
|
|
147
98
|
<View style={styles.container}>
|
|
@@ -179,7 +130,17 @@ export function CreationsGalleryScreen({
|
|
|
179
130
|
onShare={handleShare}
|
|
180
131
|
onDelete={handleDelete}
|
|
181
132
|
contentContainerStyle={{ paddingBottom: insets.bottom + tokens.spacing.xl }}
|
|
182
|
-
ListEmptyComponent={
|
|
133
|
+
ListEmptyComponent={
|
|
134
|
+
<CreationsGalleryEmptyState
|
|
135
|
+
isLoading={isLoading}
|
|
136
|
+
hasCreations={!!creations && creations.length > 0}
|
|
137
|
+
config={config}
|
|
138
|
+
t={t}
|
|
139
|
+
emptyActionLabel={emptyActionLabel}
|
|
140
|
+
onEmptyAction={onEmptyAction}
|
|
141
|
+
clearFilters={clearFilters}
|
|
142
|
+
/>
|
|
143
|
+
}
|
|
183
144
|
/>
|
|
184
145
|
|
|
185
146
|
<CreationImageViewer
|
|
@@ -207,11 +168,4 @@ export function CreationsGalleryScreen({
|
|
|
207
168
|
|
|
208
169
|
const useStyles = (tokens: any) => StyleSheet.create({
|
|
209
170
|
container: { flex: 1, backgroundColor: tokens.colors.background },
|
|
210
|
-
centerContainer: {
|
|
211
|
-
flex: 1,
|
|
212
|
-
justifyContent: 'center',
|
|
213
|
-
alignItems: 'center',
|
|
214
|
-
minHeight: 400,
|
|
215
|
-
paddingHorizontal: tokens.spacing.xl
|
|
216
|
-
},
|
|
217
171
|
});
|
|
@@ -44,7 +44,7 @@ export const ImagePicker: React.FC<ImagePickerProps> = memo(
|
|
|
44
44
|
>
|
|
45
45
|
<AtomicIcon
|
|
46
46
|
name="image-plus"
|
|
47
|
-
size=
|
|
47
|
+
size="md"
|
|
48
48
|
color="onPrimary"
|
|
49
49
|
/>
|
|
50
50
|
</View>
|
|
@@ -65,7 +65,7 @@ export const ImagePicker: React.FC<ImagePickerProps> = memo(
|
|
|
65
65
|
>
|
|
66
66
|
<AtomicIcon
|
|
67
67
|
name="upload"
|
|
68
|
-
size=
|
|
68
|
+
size="lg"
|
|
69
69
|
color="primary"
|
|
70
70
|
/>
|
|
71
71
|
</View>
|
|
@@ -47,7 +47,7 @@ export const ResultDisplay: React.FC<ResultDisplayProps> = memo(
|
|
|
47
47
|
>
|
|
48
48
|
<AtomicIcon
|
|
49
49
|
name="refresh-cw"
|
|
50
|
-
size=
|
|
50
|
+
size="md"
|
|
51
51
|
color="onSurface"
|
|
52
52
|
/>
|
|
53
53
|
<AtomicText
|
|
@@ -67,7 +67,7 @@ export const ResultDisplay: React.FC<ResultDisplayProps> = memo(
|
|
|
67
67
|
>
|
|
68
68
|
<AtomicIcon
|
|
69
69
|
name="download"
|
|
70
|
-
size=
|
|
70
|
+
size="md"
|
|
71
71
|
color="onPrimary"
|
|
72
72
|
/>
|
|
73
73
|
<AtomicText
|
|
@@ -40,7 +40,7 @@ export const ResultActions: React.FC<ResultActionsProps> = ({
|
|
|
40
40
|
<View style={styles.container}>
|
|
41
41
|
{onRetry && (
|
|
42
42
|
<TouchableOpacity style={styles.retryButton} onPress={onRetry}>
|
|
43
|
-
<AtomicIcon name="refresh" size=
|
|
43
|
+
<AtomicIcon name="refresh" size="sm" customColor={tokens.colors.primary} />
|
|
44
44
|
<AtomicText style={styles.retryText}>{translations.retry}</AtomicText>
|
|
45
45
|
</TouchableOpacity>
|
|
46
46
|
)}
|
|
@@ -54,7 +54,7 @@ export const ResultActions: React.FC<ResultActionsProps> = ({
|
|
|
54
54
|
>
|
|
55
55
|
<AtomicIcon
|
|
56
56
|
name={isSharing ? "hourglass" : "share-social"}
|
|
57
|
-
size=
|
|
57
|
+
size="md"
|
|
58
58
|
customColor="#fff"
|
|
59
59
|
/>
|
|
60
60
|
<AtomicText style={styles.shareText}>
|
|
@@ -71,7 +71,7 @@ export const ResultActions: React.FC<ResultActionsProps> = ({
|
|
|
71
71
|
>
|
|
72
72
|
<AtomicIcon
|
|
73
73
|
name={isSaving ? "hourglass" : "download"}
|
|
74
|
-
size=
|
|
74
|
+
size="md"
|
|
75
75
|
customColor={tokens.colors.primary}
|
|
76
76
|
/>
|
|
77
77
|
<AtomicText style={styles.saveText}>{translations.save}</AtomicText>
|
|
@@ -29,7 +29,7 @@ export const ResultHeader: React.FC<ResultHeaderProps> = ({ title, date }) => {
|
|
|
29
29
|
<View style={styles.badge}>
|
|
30
30
|
<AtomicIcon
|
|
31
31
|
name="calendar-outline"
|
|
32
|
-
size=
|
|
32
|
+
size="sm"
|
|
33
33
|
customColor={tokens.colors.primary}
|
|
34
34
|
/>
|
|
35
35
|
<AtomicText style={styles.dateText}>{date}</AtomicText>
|
|
@@ -28,7 +28,7 @@ export const ResultImageCard: React.FC<ResultImageCardProps> = ({
|
|
|
28
28
|
<View style={styles.frame}>
|
|
29
29
|
<Image source={{ uri: imageUrl }} style={styles.image} resizeMode="cover" />
|
|
30
30
|
<View style={styles.badge}>
|
|
31
|
-
<AtomicIcon name="sparkles" size=
|
|
31
|
+
<AtomicIcon name="sparkles" size="xs" customColor="#fff" />
|
|
32
32
|
<AtomicText style={styles.badgeText}>{badgeText}</AtomicText>
|
|
33
33
|
</View>
|
|
34
34
|
</View>
|