@umituz/react-native-ai-generation-content 1.17.269 → 1.17.271
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 +1 -1
- package/src/domains/creations/domain/repositories/ICreationsRepository.ts +5 -0
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +8 -0
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +19 -0
- package/src/domains/creations/presentation/components/CreationRating.tsx +69 -0
- package/src/domains/creations/presentation/components/index.ts +1 -0
- package/src/domains/creations/presentation/hooks/index.ts +1 -0
- package/src/domains/creations/presentation/hooks/useCreationRating.ts +30 -0
- package/src/domains/creations/presentation/hooks/useCreations.ts +2 -2
- package/src/domains/creations/presentation/hooks/useDeleteCreation.ts +4 -28
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +40 -44
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +13 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.271",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -97,4 +97,12 @@ export class CreationsRepository
|
|
|
97
97
|
): Promise<boolean> {
|
|
98
98
|
return this.writer.updateFavorite(userId, creationId, isFavorite);
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
async rate(
|
|
102
|
+
userId: string,
|
|
103
|
+
creationId: string,
|
|
104
|
+
rating: number,
|
|
105
|
+
): Promise<boolean> {
|
|
106
|
+
return this.writer.rate(userId, creationId, rating);
|
|
107
|
+
}
|
|
100
108
|
}
|
|
@@ -148,4 +148,23 @@ export class CreationsWriter {
|
|
|
148
148
|
return false;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
+
|
|
152
|
+
async rate(
|
|
153
|
+
userId: string,
|
|
154
|
+
creationId: string,
|
|
155
|
+
rating: number,
|
|
156
|
+
): Promise<boolean> {
|
|
157
|
+
const docRef = this.pathResolver.getDocRef(userId, creationId);
|
|
158
|
+
if (!docRef) return false;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
await updateDoc(docRef, {
|
|
162
|
+
rating,
|
|
163
|
+
ratedAt: new Date(),
|
|
164
|
+
});
|
|
165
|
+
return true;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
151
170
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
|
|
5
|
+
export interface CreationRatingProps {
|
|
6
|
+
readonly rating: number;
|
|
7
|
+
readonly max?: number;
|
|
8
|
+
readonly size?: number;
|
|
9
|
+
readonly onRate?: (rating: number) => void;
|
|
10
|
+
readonly readonly?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const CreationRating: React.FC<CreationRatingProps> = ({
|
|
14
|
+
rating,
|
|
15
|
+
max = 5,
|
|
16
|
+
size = 32,
|
|
17
|
+
onRate,
|
|
18
|
+
readonly = false,
|
|
19
|
+
}) => {
|
|
20
|
+
const tokens = useAppDesignTokens();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View style={styles.container}>
|
|
24
|
+
<View style={styles.stars}>
|
|
25
|
+
{Array.from({ length: max }).map((_, i) => {
|
|
26
|
+
const isFilled = i < rating;
|
|
27
|
+
return (
|
|
28
|
+
<TouchableOpacity
|
|
29
|
+
key={i}
|
|
30
|
+
onPress={() => onRate?.(i + 1)}
|
|
31
|
+
activeOpacity={0.7}
|
|
32
|
+
disabled={readonly}
|
|
33
|
+
style={styles.star}
|
|
34
|
+
>
|
|
35
|
+
<AtomicIcon
|
|
36
|
+
name={isFilled ? "star" : "star-outline"}
|
|
37
|
+
customSize={size}
|
|
38
|
+
customColor={isFilled ? tokens.colors.primary : tokens.colors.textTertiary}
|
|
39
|
+
/>
|
|
40
|
+
</TouchableOpacity>
|
|
41
|
+
);
|
|
42
|
+
})}
|
|
43
|
+
</View>
|
|
44
|
+
{!readonly && rating > 0 && (
|
|
45
|
+
<AtomicText variant="bodySmall" color="textSecondary" style={styles.valueText}>
|
|
46
|
+
{rating} / {max}
|
|
47
|
+
</AtomicText>
|
|
48
|
+
)}
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const styles = StyleSheet.create({
|
|
54
|
+
container: {
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
paddingVertical: 12,
|
|
57
|
+
},
|
|
58
|
+
stars: {
|
|
59
|
+
flexDirection: "row",
|
|
60
|
+
gap: 8,
|
|
61
|
+
},
|
|
62
|
+
star: {
|
|
63
|
+
padding: 2,
|
|
64
|
+
},
|
|
65
|
+
valueText: {
|
|
66
|
+
marginTop: 8,
|
|
67
|
+
fontWeight: "600",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -30,6 +30,7 @@ export { GalleryHeader } from "./GalleryHeader";
|
|
|
30
30
|
export { EmptyState } from "./EmptyState";
|
|
31
31
|
export { GalleryEmptyStates } from "./GalleryEmptyStates";
|
|
32
32
|
export { CreationsHomeCard } from "./CreationsHomeCard";
|
|
33
|
+
export { CreationRating } from "./CreationRating";
|
|
33
34
|
export { CreationsGrid } from "./CreationsGrid";
|
|
34
35
|
|
|
35
36
|
// Detail Components
|
|
@@ -15,6 +15,7 @@ export { useFilter, useStatusFilter, useMediaFilter } from "./useFilter";
|
|
|
15
15
|
export type { UseFilterProps, UseFilterReturn } from "./useFilter";
|
|
16
16
|
export { useGalleryFilters } from "./useGalleryFilters";
|
|
17
17
|
export { useCreationPersistence } from "./useCreationPersistence";
|
|
18
|
+
export { useCreationRating } from "./useCreationRating";
|
|
18
19
|
export type {
|
|
19
20
|
UseCreationPersistenceConfig,
|
|
20
21
|
UseCreationPersistenceReturn,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCreationRating Hook
|
|
3
|
+
* Handles rating of creations with optimistic update
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useOptimisticUpdate } from "@umituz/react-native-design-system";
|
|
7
|
+
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
8
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
9
|
+
|
|
10
|
+
interface UseCreationRatingProps {
|
|
11
|
+
readonly userId: string | null;
|
|
12
|
+
readonly repository: ICreationsRepository;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useCreationRating({
|
|
16
|
+
userId,
|
|
17
|
+
repository,
|
|
18
|
+
}: UseCreationRatingProps) {
|
|
19
|
+
return useOptimisticUpdate<boolean, { id: string; rating: number }, Creation[]>({
|
|
20
|
+
mutationFn: async ({ id, rating }) => {
|
|
21
|
+
if (!userId) return false;
|
|
22
|
+
return repository.rate(userId, id, rating);
|
|
23
|
+
},
|
|
24
|
+
queryKey: ["creations", userId ?? ""],
|
|
25
|
+
updateFn: (old, { id, rating }) =>
|
|
26
|
+
old?.map((c) =>
|
|
27
|
+
c.id === id ? { ...c, rating, ratedAt: new Date() } : c
|
|
28
|
+
) ?? [],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Fetches user's creations from repository
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { useAppQuery } from "@umituz/react-native-design-system";
|
|
7
7
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
8
8
|
import type { Creation } from "../../domain/entities/Creation";
|
|
9
9
|
|
|
@@ -23,7 +23,7 @@ export function useCreations({
|
|
|
23
23
|
repository,
|
|
24
24
|
enabled = true,
|
|
25
25
|
}: UseCreationsProps) {
|
|
26
|
-
return
|
|
26
|
+
return useAppQuery<Creation[]>({
|
|
27
27
|
queryKey: ["creations", userId ?? ""],
|
|
28
28
|
queryFn: async () => {
|
|
29
29
|
if (!userId) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles deletion of user creations with optimistic update
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { useOptimisticUpdate } from "@umituz/react-native-design-system";
|
|
7
7
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
8
8
|
import type { Creation } from "../../domain/entities/Creation";
|
|
9
9
|
|
|
@@ -16,36 +16,12 @@ export function useDeleteCreation({
|
|
|
16
16
|
userId,
|
|
17
17
|
repository,
|
|
18
18
|
}: UseDeleteCreationProps) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return useMutation({
|
|
19
|
+
return useOptimisticUpdate<boolean, string, Creation[]>({
|
|
22
20
|
mutationFn: async (creationId: string) => {
|
|
23
21
|
if (!userId) return false;
|
|
24
22
|
return repository.delete(userId, creationId);
|
|
25
23
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await queryClient.cancelQueries({
|
|
30
|
-
queryKey: ["creations", userId],
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const previous = queryClient.getQueryData<Creation[]>([
|
|
34
|
-
"creations",
|
|
35
|
-
userId,
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
queryClient.setQueryData<Creation[]>(
|
|
39
|
-
["creations", userId],
|
|
40
|
-
(old) => old?.filter((c) => c.id !== creationId) ?? [],
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
return { previous };
|
|
44
|
-
},
|
|
45
|
-
onError: (_err, _id, rollback) => {
|
|
46
|
-
if (userId && rollback?.previous) {
|
|
47
|
-
queryClient.setQueryData(["creations", userId], rollback.previous);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
24
|
+
queryKey: ["creations", userId ?? ""],
|
|
25
|
+
updateFn: (old, creationId) => old?.filter((c) => c.id !== creationId) ?? [],
|
|
50
26
|
});
|
|
51
27
|
}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
2
|
+
import { } from 'react-native';
|
|
3
|
+
import { useAppDesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
|
|
5
4
|
import type { Creation } from '../../domain/entities/Creation';
|
|
6
5
|
import type { CreationsConfig } from '../../domain/value-objects/CreationsConfig';
|
|
6
|
+
import type { ICreationsRepository } from '../../domain/repositories/ICreationsRepository';
|
|
7
7
|
import { hasVideoContent, getPreviewUrl } from '../../domain/utils';
|
|
8
|
+
import { useCreationRating } from '../hooks/useCreationRating';
|
|
8
9
|
import { DetailHeader } from '../components/CreationDetail/DetailHeader';
|
|
9
10
|
import { DetailInfo } from '../components/CreationDetail/DetailInfo';
|
|
10
11
|
import { DetailImage } from '../components/CreationDetail/DetailImage';
|
|
11
12
|
import { DetailVideo } from '../components/CreationDetail/DetailVideo';
|
|
12
13
|
import { DetailStory } from '../components/CreationDetail/DetailStory';
|
|
13
14
|
import { DetailActions } from '../components/CreationDetail/DetailActions';
|
|
15
|
+
import { CreationRating } from '../components/CreationRating';
|
|
14
16
|
import { getLocalizedTitle } from '../utils/filterUtils';
|
|
15
17
|
|
|
16
18
|
/** Video creation types */
|
|
17
19
|
const VIDEO_TYPES = ['text-to-video', 'image-to-video'] as const;
|
|
18
20
|
|
|
19
21
|
interface CreationDetailScreenProps {
|
|
22
|
+
readonly userId: string | null;
|
|
23
|
+
readonly repository: ICreationsRepository;
|
|
20
24
|
readonly creation: Creation;
|
|
21
25
|
readonly config: CreationsConfig;
|
|
22
26
|
readonly onClose: () => void;
|
|
@@ -34,6 +38,8 @@ interface CreationMetadata {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
41
|
+
userId,
|
|
42
|
+
repository,
|
|
37
43
|
creation,
|
|
38
44
|
config,
|
|
39
45
|
onClose,
|
|
@@ -43,7 +49,11 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
43
49
|
t
|
|
44
50
|
}) => {
|
|
45
51
|
const tokens = useAppDesignTokens();
|
|
46
|
-
const
|
|
52
|
+
const rateMutation = useCreationRating({ userId, repository });
|
|
53
|
+
|
|
54
|
+
const handleRate = async (rating: number) => {
|
|
55
|
+
await rateMutation.mutateAsync({ id: creation.id, rating });
|
|
56
|
+
};
|
|
47
57
|
|
|
48
58
|
// Extract data safely
|
|
49
59
|
const metadata = (creation.metadata || {}) as CreationMetadata;
|
|
@@ -68,48 +78,34 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
68
78
|
const thumbnailUrl = getPreviewUrl(creation.output) || undefined;
|
|
69
79
|
|
|
70
80
|
return (
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<DetailVideo videoUrl={videoUrl} _thumbnailUrl={thumbnailUrl} />
|
|
82
|
-
) : (
|
|
83
|
-
<DetailImage uri={creation.uri} />
|
|
84
|
-
)}
|
|
81
|
+
<ScreenLayout
|
|
82
|
+
header={<DetailHeader onClose={onClose} />}
|
|
83
|
+
>
|
|
84
|
+
{isVideo ? (
|
|
85
|
+
<DetailVideo videoUrl={videoUrl} _thumbnailUrl={thumbnailUrl} />
|
|
86
|
+
) : (
|
|
87
|
+
<DetailImage uri={creation.uri} />
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
<DetailInfo title={title} date={date} />
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
<CreationRating
|
|
93
|
+
rating={creation.rating || 0}
|
|
94
|
+
onRate={handleRate}
|
|
95
|
+
/>
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
{story ? (
|
|
98
|
+
<DetailStory story={story} />
|
|
99
|
+
) : null}
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</View>
|
|
101
|
+
<DetailActions
|
|
102
|
+
onShare={() => onShare(creation)}
|
|
103
|
+
onDelete={() => onDelete(creation)}
|
|
104
|
+
onViewResult={onViewResult ? () => onViewResult(creation) : undefined}
|
|
105
|
+
shareLabel={t("result.shareButton")}
|
|
106
|
+
deleteLabel={t("common.delete")}
|
|
107
|
+
viewResultLabel={onViewResult ? t("result.viewResult") : undefined}
|
|
108
|
+
/>
|
|
109
|
+
</ScreenLayout>
|
|
102
110
|
);
|
|
103
111
|
};
|
|
104
|
-
|
|
105
|
-
const styles = StyleSheet.create({
|
|
106
|
-
container: {
|
|
107
|
-
flex: 1,
|
|
108
|
-
},
|
|
109
|
-
scrollView: {
|
|
110
|
-
flex: 1,
|
|
111
|
-
},
|
|
112
|
-
scrollContent: {
|
|
113
|
-
paddingTop: 4,
|
|
114
|
-
},
|
|
115
|
-
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useMemo, useCallback } from "react";
|
|
2
2
|
import { View, FlatList, RefreshControl, StyleSheet } from "react-native";
|
|
3
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
3
|
import {
|
|
5
4
|
useAppDesignTokens,
|
|
6
5
|
useAlert,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
AlertMode,
|
|
9
8
|
useSharing,
|
|
10
9
|
FilterSheet,
|
|
10
|
+
ScreenLayout,
|
|
11
11
|
} from "@umituz/react-native-design-system";
|
|
12
12
|
import { useFocusEffect } from "@react-navigation/native";
|
|
13
13
|
import { useCreations } from "../hooks/useCreations";
|
|
@@ -18,7 +18,6 @@ import { MEDIA_FILTER_OPTIONS, STATUS_FILTER_OPTIONS } from "../../domain/types/
|
|
|
18
18
|
import type { Creation } from "../../domain/entities/Creation";
|
|
19
19
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
20
20
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
21
|
-
import { CreationDetailScreen } from "./CreationDetailScreen";
|
|
22
21
|
|
|
23
22
|
interface CreationsGalleryScreenProps {
|
|
24
23
|
readonly userId: string | null;
|
|
@@ -42,13 +41,10 @@ export function CreationsGalleryScreen({
|
|
|
42
41
|
showFilter = config.showFilter ?? true,
|
|
43
42
|
onViewResult,
|
|
44
43
|
}: CreationsGalleryScreenProps) {
|
|
45
|
-
const insets = useSafeAreaInsets();
|
|
46
44
|
const tokens = useAppDesignTokens();
|
|
47
45
|
const { share } = useSharing();
|
|
48
46
|
const alert = useAlert();
|
|
49
47
|
|
|
50
|
-
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
51
|
-
|
|
52
48
|
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
53
49
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
54
50
|
|
|
@@ -70,8 +66,7 @@ export function CreationsGalleryScreen({
|
|
|
70
66
|
actions: [
|
|
71
67
|
{ id: "cancel", label: t("common.cancel"), onPress: () => {} },
|
|
72
68
|
{ id: "delete", label: t("common.delete"), style: "destructive", onPress: async () => {
|
|
73
|
-
|
|
74
|
-
if (success) setSelectedCreation(null);
|
|
69
|
+
await deleteMutation.mutateAsync(c.id);
|
|
75
70
|
}}
|
|
76
71
|
]
|
|
77
72
|
});
|
|
@@ -112,18 +107,18 @@ export function CreationsGalleryScreen({
|
|
|
112
107
|
<CreationCard
|
|
113
108
|
creation={item}
|
|
114
109
|
callbacks={{
|
|
115
|
-
onPress: () =>
|
|
110
|
+
onPress: () => onViewResult?.(item),
|
|
116
111
|
onShare: async () => handleShare(item),
|
|
117
112
|
onDelete: () => handleDelete(item),
|
|
118
113
|
onFavorite: () => handleFavorite(item, !item.isFavorite),
|
|
119
114
|
}}
|
|
120
115
|
/>
|
|
121
|
-
), [handleShare, handleDelete, handleFavorite]);
|
|
116
|
+
), [handleShare, handleDelete, handleFavorite, onViewResult]);
|
|
122
117
|
|
|
123
118
|
const renderHeader = useMemo(() => {
|
|
124
119
|
if ((!creations || creations.length === 0) && !isLoading) return null;
|
|
125
120
|
return (
|
|
126
|
-
<View style={[styles.header, {
|
|
121
|
+
<View style={[styles.header, { backgroundColor: tokens.colors.surface, borderBottomColor: tokens.colors.border }]}>
|
|
127
122
|
<GalleryHeader
|
|
128
123
|
title={t(config.translations.title)}
|
|
129
124
|
count={filters.filtered.length}
|
|
@@ -133,7 +128,7 @@ export function CreationsGalleryScreen({
|
|
|
133
128
|
/>
|
|
134
129
|
</View>
|
|
135
130
|
);
|
|
136
|
-
}, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config,
|
|
131
|
+
}, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config, tokens]);
|
|
137
132
|
|
|
138
133
|
const renderEmpty = useMemo(() => (
|
|
139
134
|
<GalleryEmptyStates
|
|
@@ -150,25 +145,24 @@ export function CreationsGalleryScreen({
|
|
|
150
145
|
/>
|
|
151
146
|
), [isLoading, creations, filters.isFiltered, tokens, t, config, emptyActionLabel, onEmptyAction, filters.clearAllFilters]);
|
|
152
147
|
|
|
153
|
-
if (selectedCreation) {
|
|
154
|
-
return <CreationDetailScreen creation={selectedCreation} config={config} onClose={() => setSelectedCreation(null)} onShare={handleShare} onDelete={handleDelete} onViewResult={onViewResult} t={t} />;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
148
|
return (
|
|
158
|
-
<
|
|
149
|
+
<ScreenLayout>
|
|
159
150
|
<FlatList
|
|
160
151
|
data={filters.filtered}
|
|
161
152
|
renderItem={renderItem}
|
|
162
153
|
keyExtractor={(item) => item.id}
|
|
163
154
|
ListHeaderComponent={renderHeader}
|
|
164
155
|
ListEmptyComponent={renderEmpty}
|
|
165
|
-
contentContainerStyle={[
|
|
156
|
+
contentContainerStyle={[
|
|
157
|
+
styles.listContent,
|
|
158
|
+
(!filters.filtered || filters.filtered.length === 0) && styles.emptyContent
|
|
159
|
+
]}
|
|
166
160
|
showsVerticalScrollIndicator={false}
|
|
167
161
|
refreshControl={<RefreshControl refreshing={isLoading} onRefresh={() => void refetch()} tintColor={tokens.colors.primary} />}
|
|
168
162
|
/>
|
|
169
163
|
<FilterSheet visible={filters.statusFilterVisible} onClose={filters.closeStatusFilter} options={filters.statusFilter.filterOptions} selectedIds={[filters.statusFilter.selectedId]} onFilterPress={filters.statusFilter.selectFilter} onClearFilters={filters.statusFilter.clearFilter} title={t(config.translations.statusFilterTitle ?? "creations.filter.status")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
170
164
|
<FilterSheet visible={filters.mediaFilterVisible} onClose={filters.closeMediaFilter} options={filters.mediaFilter.filterOptions} selectedIds={[filters.mediaFilter.selectedId]} onFilterPress={filters.mediaFilter.selectFilter} onClearFilters={filters.mediaFilter.clearFilter} title={t(config.translations.mediaFilterTitle ?? "creations.filter.media")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
171
|
-
</
|
|
165
|
+
</ScreenLayout>
|
|
172
166
|
);
|
|
173
167
|
}
|
|
174
168
|
|