diff --git a/mobile/src/api.ts b/mobile/src/api.ts index 2c2ffaa..bd0b1c4 100644 --- a/mobile/src/api.ts +++ b/mobile/src/api.ts @@ -173,6 +173,10 @@ export class GoonClient { await this.request(`/scene-favorites/${sceneId}`, { method: 'POST' }); } + async listSceneFavorites(): Promise<{ items: SceneOut[]; total: number }> { + return this.request('/scene-favorites'); + } + async removeSceneFavorite(sceneId: string): Promise { const res = await fetch(`${this.baseUrl}/scene-favorites/${sceneId}`, { method: 'DELETE', diff --git a/mobile/src/screens/FavoritesScreen.tsx b/mobile/src/screens/FavoritesScreen.tsx index 4259c41..8b662b5 100644 --- a/mobile/src/screens/FavoritesScreen.tsx +++ b/mobile/src/screens/FavoritesScreen.tsx @@ -18,17 +18,18 @@ import { } from 'react-native'; import { Image } from 'expo-image'; import { useClient } from '../ClientContext'; +import { SceneTile } from '../components/SceneTile'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { FavoriteMovieOut, FavoriteOut, FavoriteStudioOut } from '../types'; -type Tab = 'performers' | 'studios' | 'movies'; +type Tab = 'scenes' | 'performers' | 'studios' | 'movies'; export function FavoritesScreen() { const client = useClient(); const queryClient = useQueryClient(); const nav = useNavigation>(); - const [tab, setTab] = React.useState('performers'); + const [tab, setTab] = React.useState('scenes'); const performersQuery = useQuery({ queryKey: ['favorites'], @@ -45,6 +46,17 @@ export function FavoritesScreen() { queryFn: () => client.listFavoriteMovies(), }); + const scenesQuery = useQuery({ + queryKey: ['favorites-scenes'], + queryFn: () => client.listSceneFavorites(), + }); + + const removeScene = useMutation({ + mutationFn: (id: string) => client.removeSceneFavorite(id), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites-scenes'] }), + onError: (e) => Alert.alert('Failed', e instanceof Error ? e.message : String(e)), + }); + const removeMovie = useMutation({ mutationFn: (id: string) => client.removeFavoriteMovie(id), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites-movies'] }), @@ -99,37 +111,26 @@ export function FavoritesScreen() { client.markFavoriteStudioSeen(id).catch(() => {}); }; + // performers/studios mają new_total badge; scenes/movies nie (single entity). const activeData = tab === 'performers' ? performersQuery.data : tab === 'studios' ? studiosQuery.data - : null; // movies — nie ma new_total bo single movie nie ma child scenes - const moviesTotal = moviesQuery.data?.total ?? 0; - const isLoading = + : null; + const activeQuery = tab === 'performers' - ? performersQuery.isLoading + ? performersQuery : tab === 'studios' - ? studiosQuery.isLoading - : moviesQuery.isLoading; - const isRefetching = - tab === 'performers' - ? performersQuery.isRefetching - : tab === 'studios' - ? studiosQuery.isRefetching - : moviesQuery.isRefetching; - const error = - tab === 'performers' - ? performersQuery.error - : tab === 'studios' - ? studiosQuery.error - : moviesQuery.error; - const refetch = - tab === 'performers' - ? performersQuery.refetch - : tab === 'studios' - ? studiosQuery.refetch - : moviesQuery.refetch; + ? studiosQuery + : tab === 'movies' + ? moviesQuery + : scenesQuery; + const headerCount = activeData?.total ?? (activeQuery.data as { total?: number } | undefined)?.total ?? 0; + const isLoading = activeQuery.isLoading; + const isRefetching = activeQuery.isRefetching; + const error = activeQuery.error; + const refetch = activeQuery.refetch; return ( @@ -140,13 +141,17 @@ export function FavoritesScreen() { +{activeData.new_total} new ) : ( - - {tab === 'movies' ? moviesTotal : activeData?.total ?? 0} - + {headerCount} )} + setTab('scenes')} + /> } {error instanceof Error && {error.message}} - {tab === 'movies' ? ( + {tab === 'scenes' ? ( + s.id} + numColumns={2} + // patrz ScenesScreen: removeClippedSubviews blankuje miniaturki po scrollu. + removeClippedSubviews={false} + renderItem={({ item }) => ( + + Alert.alert( + 'Remove favorite', + `Remove "${item.title}" from favorites?`, + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Remove', + style: 'destructive', + onPress: () => removeScene.mutate(item.id), + }, + ], + ) + } + /> + )} + columnWrapperStyle={styles.gridRow} + refreshing={isRefetching} + onRefresh={refetch} + ListEmptyComponent={ + !isLoading ? ( + + No scene favorites yet. Tap the heart on a scene to save it here. + + ) : null + } + contentContainerStyle={{ paddingBottom: 24 }} + /> + ) : tab === 'movies' ? ( f.movie_id} @@ -510,6 +554,8 @@ const styles = StyleSheet.create({ hint: { color: theme.mutedDim, fontSize: 11, marginTop: 8, marginBottom: 12 }, + gridRow: { gap: 10, marginBottom: 14 }, + row: { flexDirection: 'row', alignItems: 'center',