// Sceny dla wybranego studia (filter studio_slugs=). Mirror PerformerScenesScreen. // Studios używają slug filter (nie id) — backend ScenesListParams ma `studio_slugs`, // nie `studio_ids`. Dlatego nav param dla StudioScenes wymaga slug — przekazujemy go // z FavoritesScreen (mamy w FavoriteStudioOut.slug). import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React from 'react'; import { ActivityIndicator, Alert, FlatList, Pressable, StyleSheet, Text, View, } from 'react-native'; import { FavoriteSceneRow } from '../components/FavoriteSceneRow'; import { SceneTile } from '../components/SceneTile'; import { useClient } from '../ClientContext'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { SceneOut } from '../types'; export function StudioScenesScreen() { const client = useClient(); const queryClient = useQueryClient(); const navigation = useNavigation>(); const route = useRoute>(); // `id` w nav params dla StudioScenes to studio.slug (nie UUID), żeby pasowało do // backend ScenesListParams.studio_slugs. Trzymamy tę nazwę dla spójności z // PerformerScenes — ale w handlerach favorite/blacklist używamy studioId (UUID) // który dodajemy jako kolejny param. const { id: slug, name, seenSince, studioId } = route.params; const favoritesQuery = useQuery({ queryKey: ['favorites-studios'], queryFn: () => client.listFavoriteStudios(), staleTime: 30_000, }); const isFavorite = !!favoritesQuery.data?.items.find((f) => f.studio_id === studioId); const addMutation = useMutation({ mutationFn: () => client.addFavoriteStudio(studioId), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites-studios'] }), }); const removeMutation = useMutation({ mutationFn: () => client.removeFavoriteStudio(studioId), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites-studios'] }), }); const blacklistMutation = useMutation({ mutationFn: () => client.addBlacklist('studio', studioId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['scenes'] }); queryClient.invalidateQueries({ queryKey: ['studio-scenes', slug] }); navigation.goBack(); }, }); const onHide = () => { Alert.alert( 'Hide studio', `Hide all scenes from ${name}? You can undo this from Settings → Blacklist.`, [ { text: 'Cancel', style: 'cancel' }, { text: 'Hide', style: 'destructive', onPress: () => blacklistMutation.mutate() }, ], ); }; // Mark seen przy wejściu (jeśli już ulubiony) — zerujemy badge nowych scen React.useEffect(() => { if (isFavorite) { client.markFavoriteStudioSeen(studioId).then(() => { queryClient.invalidateQueries({ queryKey: ['favorites-studios'] }); }).catch(() => {}); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFavorite, studioId]); React.useLayoutEffect(() => { navigation.setOptions({ title: name, headerRight: () => ( 🚫 (isFavorite ? removeMutation.mutate() : addMutation.mutate())} hitSlop={12} disabled={addMutation.isPending || removeMutation.isPending} > {isFavorite ? '★' : '☆'} ), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [navigation, name, isFavorite, addMutation.isPending, removeMutation.isPending]); const { data, isLoading, error, refetch, isRefetching } = useQuery({ queryKey: ['studio-scenes', slug], queryFn: () => client.listScenes({ studio_slugs: [slug], sort: 'release_date', per_page: 200, has_playback: true, }), }); const sortedItems = React.useMemo(() => { const items = data?.items ?? []; if (!seenSince) return items; const newOnes: SceneOut[] = []; const rest: SceneOut[] = []; for (const s of items) { if (s.created_at && s.created_at > seenSince) { newOnes.push(s); } else { rest.push(s); } } return [...newOnes, ...rest]; }, [data?.items, seenSince]); return ( {isLoading && } {error instanceof Error && {error.message}} s.id} numColumns={2} renderItem={({ item }) => ( )} columnWrapperStyle={styles.gridRow} refreshing={isRefetching} onRefresh={refetch} ListHeaderComponent={ {data ? `${data.total} ${data.total === 1 ? 'scene' : 'scenes'}` : ' '} } ListEmptyComponent={!isLoading ? no scenes : null} contentContainerStyle={{ paddingBottom: 24 }} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.bg, paddingHorizontal: 12, paddingTop: 8 }, gridRow: { gap: 10, marginBottom: 14 }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, paddingHorizontal: 4, }, subtitle: { color: theme.muted }, muted: { color: theme.muted, textAlign: 'center', marginTop: 24 }, error: { color: theme.bad, padding: 12 }, });