some cleanups

This commit is contained in:
Simon Eklundh
2026-06-14 13:48:57 +02:00
parent c024d1ed05
commit bc13317f00
2 changed files with 24 additions and 74 deletions

View File

@@ -33,7 +33,7 @@ interface FavoritesProps {
emptyTitleKey?: string; emptyTitleKey?: string;
emptyTextKey?: string; emptyTextKey?: string;
/** Namespace for the see-all page headers ("favorites" or "kefintweaksWatchlist"). */ /** Namespace for the see-all page headers ("favorites" or "kefintweaksWatchlist"). */
seeAllNamespace?: string; seeAllNamespace?: "kefintweaksWatchlist" | "favorites";
} }
export const Favorites = ({ export const Favorites = ({
@@ -143,71 +143,18 @@ export const Favorites = ({
[fetchFavoritesByType, pageSize], [fetchFavoritesByType, pageSize],
); );
const handleSeeAllSeries = useCallback(() => { // Navigate to the shared see-all screen. `titleKey` is the see-all header
router.push({ // segment in the active namespace (e.g. "seeAllSeries"). The cast is needed
pathname: "/(auth)/(tabs)/(favorites)/see-all", // because the route's custom params aren't part of expo-router's typed Href.
params: { const seeAll = useCallback(
type: "Series", (type: FavoriteTypes, titleKey: string) => {
title: t(`${seeAllNamespace}.seeAllSeries`), router.push({
filter, pathname: "/(auth)/(tabs)/(favorites)/see-all",
}, params: { type, title: t(`${seeAllNamespace}.${titleKey}`), filter },
} as any); } as any);
}, [router, filter, seeAllNamespace]); },
[router, filter, seeAllNamespace],
const handleSeeAllMovies = useCallback(() => { );
router.push({
pathname: "/(auth)/(tabs)/(favorites)/see-all",
params: {
type: "Movie",
title: t(`${seeAllNamespace}.seeAllMovies`),
filter,
},
} as any);
}, [router, filter, seeAllNamespace]);
const handleSeeAllEpisodes = useCallback(() => {
router.push({
pathname: "/(auth)/(tabs)/(favorites)/see-all",
params: {
type: "Episode",
title: t(`${seeAllNamespace}.seeAllEpisodes`),
filter,
},
} as any);
}, [router, filter, seeAllNamespace]);
const handleSeeAllVideos = useCallback(() => {
router.push({
pathname: "/(auth)/(tabs)/(favorites)/see-all",
params: {
type: "Video",
title: t(`${seeAllNamespace}.seeAllVideos`),
filter,
},
} as any);
}, [router, filter, seeAllNamespace]);
const handleSeeAllBoxsets = useCallback(() => {
router.push({
pathname: "/(auth)/(tabs)/(favorites)/see-all",
params: {
type: "BoxSet",
title: t(`${seeAllNamespace}.seeAllBoxsets`),
filter,
},
} as any);
}, [router, filter, seeAllNamespace]);
const handleSeeAllPlaylists = useCallback(() => {
router.push({
pathname: "/(auth)/(tabs)/(favorites)/see-all",
params: {
type: "Playlist",
title: t(`${seeAllNamespace}.seeAllPlaylists`),
filter,
},
} as any);
}, [router, filter, seeAllNamespace]);
return ( return (
<View className='flex flex-co gap-y-4'> <View className='flex flex-co gap-y-4'>
@@ -233,7 +180,7 @@ export const Favorites = ({
title={t("favorites.series")} title={t("favorites.series")}
hideIfEmpty hideIfEmpty
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllSeries} onPressSeeAll={() => seeAll("Series", "seeAllSeries")}
/> />
<InfiniteScrollingCollectionList <InfiniteScrollingCollectionList
queryFn={fetchFavoriteMovies} queryFn={fetchFavoriteMovies}
@@ -242,7 +189,7 @@ export const Favorites = ({
hideIfEmpty hideIfEmpty
orientation='vertical' orientation='vertical'
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllMovies} onPressSeeAll={() => seeAll("Movie", "seeAllMovies")}
/> />
<InfiniteScrollingCollectionList <InfiniteScrollingCollectionList
queryFn={fetchFavoriteEpisodes} queryFn={fetchFavoriteEpisodes}
@@ -250,7 +197,7 @@ export const Favorites = ({
title={t("favorites.episodes")} title={t("favorites.episodes")}
hideIfEmpty hideIfEmpty
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllEpisodes} onPressSeeAll={() => seeAll("Episode", "seeAllEpisodes")}
/> />
<InfiniteScrollingCollectionList <InfiniteScrollingCollectionList
queryFn={fetchFavoriteVideos} queryFn={fetchFavoriteVideos}
@@ -258,7 +205,7 @@ export const Favorites = ({
title={t("favorites.videos")} title={t("favorites.videos")}
hideIfEmpty hideIfEmpty
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllVideos} onPressSeeAll={() => seeAll("Video", "seeAllVideos")}
/> />
<InfiniteScrollingCollectionList <InfiniteScrollingCollectionList
queryFn={fetchFavoriteBoxsets} queryFn={fetchFavoriteBoxsets}
@@ -266,7 +213,7 @@ export const Favorites = ({
title={t("favorites.boxsets")} title={t("favorites.boxsets")}
hideIfEmpty hideIfEmpty
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllBoxsets} onPressSeeAll={() => seeAll("BoxSet", "seeAllBoxsets")}
/> />
<InfiniteScrollingCollectionList <InfiniteScrollingCollectionList
queryFn={fetchFavoritePlaylists} queryFn={fetchFavoritePlaylists}
@@ -274,7 +221,7 @@ export const Favorites = ({
title={t("favorites.playlists")} title={t("favorites.playlists")}
hideIfEmpty hideIfEmpty
pageSize={pageSize} pageSize={pageSize}
onPressSeeAll={handleSeeAllPlaylists} onPressSeeAll={() => seeAll("Playlist", "seeAllPlaylists")}
/> />
</View> </View>
); );

View File

@@ -2,6 +2,7 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import { useCallback, useEffect, useMemo, useRef } from "react"; import { useCallback, useEffect, useMemo, useRef } from "react";
import { toast } from "sonner-native";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
// Shared atom to store watchlist (Likes) status across all components // Shared atom to store watchlist (Likes) status across all components
@@ -92,7 +93,7 @@ export const useWatchlist = (item: BaseItemDto) => {
const currentItem = itemRef.current; const currentItem = itemRef.current;
if (!currentApi || !currentUser?.Id || !currentItem?.Id) { if (!currentApi || !currentUser?.Id || !currentItem?.Id) {
return; throw new Error("Cannot update watchlist: not signed in");
} }
// Watchlist == Jellyfin "Likes" rating: // Watchlist == Jellyfin "Likes" rating:
@@ -120,13 +121,15 @@ export const useWatchlist = (item: BaseItemDto) => {
return { previousIsWatchlisted, previousQueries }; return { previousIsWatchlisted, previousQueries };
}, },
onError: (_err, _nextIsWatchlisted, context) => { onError: (error: Error, _nextIsWatchlisted, context) => {
// Roll back the optimistic Likes flip applied in onMutate.
if (context?.previousQueries) { if (context?.previousQueries) {
for (const [queryKey, data] of context.previousQueries) { for (const [queryKey, data] of context.previousQueries) {
queryClient.setQueryData(queryKey, data); queryClient.setQueryData(queryKey, data);
} }
} }
setIsWatchlisted(context?.previousIsWatchlisted); setIsWatchlisted(context?.previousIsWatchlisted);
toast.error(error.message || "Failed to update watchlist");
}, },
onSettled: () => { onSettled: () => {
queryClient.invalidateQueries({ queryKey: itemQueryKeyPrefix }); queryClient.invalidateQueries({ queryKey: itemQueryKeyPrefix });