diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 402f3171..de10e375 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -90,7 +90,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( {item.Type !== "Program" && ( - + )} diff --git a/components/PlayedStatus.tsx b/components/PlayedStatus.tsx index f86e4525..5d738af4 100644 --- a/components/PlayedStatus.tsx +++ b/components/PlayedStatus.tsx @@ -6,16 +6,19 @@ import { View, ViewProps } from "react-native"; import { RoundButton } from "./RoundButton"; interface Props extends ViewProps { - item: BaseItemDto; + items: BaseItemDto[]; + size?: "default" | "large"; } -export const PlayedStatus: React.FC = ({ item, ...props }) => { +export const PlayedStatus: React.FC = ({ items, ...props }) => { const queryClient = useQueryClient(); const invalidateQueries = () => { - queryClient.invalidateQueries({ - queryKey: ["item", item.Id], - }); + items.forEach((item) => { + queryClient.invalidateQueries({ + queryKey: ["item", item.Id], + }); + }) queryClient.invalidateQueries({ queryKey: ["resumeItems"], }); @@ -39,15 +42,20 @@ export const PlayedStatus: React.FC = ({ item, ...props }) => { }); }; - const markAsPlayedStatus = useMarkAsPlayed(item); + const allPlayed = items.every((item) => item.UserData?.Played); + + const markAsPlayedStatus = useMarkAsPlayed(items); return ( markAsPlayedStatus(!item.UserData?.Played)} - size="large" + fillColor={allPlayed ? "primary" : undefined} + icon={allPlayed ? "checkmark" : "checkmark"} + onPress={async () => { + console.log(allPlayed); + await markAsPlayedStatus(!allPlayed) + }} + size={props.size} /> ); diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index 9ca97bee..774b043d 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -57,7 +57,7 @@ export const TouchableItemRouter: React.FC> = ({ const router = useRouter(); const segments = useSegments(); const { showActionSheetWithOptions } = useActionSheet(); - const markAsPlayedStatus = useMarkAsPlayed(item); + const markAsPlayedStatus = useMarkAsPlayed([item]); const from = segments[2]; diff --git a/components/series/SeasonPicker.tsx b/components/series/SeasonPicker.tsx index be625432..6851bbbc 100644 --- a/components/series/SeasonPicker.tsx +++ b/components/series/SeasonPicker.tsx @@ -17,7 +17,9 @@ import { SeasonIndexState, } from "@/components/series/SeasonDropdown"; import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons"; +import { PlayedStatus } from "../PlayedStatus"; import { useTranslation } from "react-i18next"; + type Props = { item: BaseItemDto; initialSeasonIndex?: number; @@ -145,17 +147,20 @@ export const SeasonPicker: React.FC = ({ item, initialSeasonIndex }) => { }} /> {episodes?.length || 0 > 0 ? ( - ( - - )} - DownloadedIconComponent={() => ( - - )} - /> + + ( + + )} + DownloadedIconComponent={() => ( + + )} + /> + + ) : null} diff --git a/components/video-player/controls/NextEpisodeCountDownButton.tsx b/components/video-player/controls/NextEpisodeCountDownButton.tsx index e77c6198..73e3f828 100644 --- a/components/video-player/controls/NextEpisodeCountDownButton.tsx +++ b/components/video-player/controls/NextEpisodeCountDownButton.tsx @@ -60,12 +60,12 @@ const NextEpisodeCountDownButton: React.FC = ({ } }; + const { t } = useTranslation(); + if (!show) { return null; } - const { t } = useTranslation(); - return ( { +export const useMarkAsPlayed = (items: BaseItemDto[]) => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const queryClient = useQueryClient(); @@ -14,7 +14,6 @@ export const useMarkAsPlayed = (item: BaseItemDto) => { const invalidateQueries = () => { const queriesToInvalidate = [ - ["item", item.Id], ["resumeItems"], ["continueWatching"], ["nextUp-all"], @@ -24,6 +23,11 @@ export const useMarkAsPlayed = (item: BaseItemDto) => { ["home"], ]; + items.forEach((item) => { + if(!item.Id) return; + queriesToInvalidate.push(["item", item.Id]); + }); + queriesToInvalidate.forEach((queryKey) => { queryClient.invalidateQueries({ queryKey }); }); @@ -32,40 +36,8 @@ export const useMarkAsPlayed = (item: BaseItemDto) => { const markAsPlayedStatus = async (played: boolean) => { lightHapticFeedback(); - // Optimistic update - queryClient.setQueryData( - ["item", item.Id], - (oldData: BaseItemDto | undefined) => { - if (oldData) { - return { - ...oldData, - UserData: { - ...oldData.UserData, - Played: !played, - }, - }; - } - return oldData; - } - ); - - try { - if (played) { - await markAsPlayed({ - api: api, - item: item, - userId: user?.Id, - }); - } else { - await markAsNotPlayed({ - api: api, - itemId: item?.Id, - userId: user?.Id, - }); - } - invalidateQueries(); - } catch (error) { - // Revert optimistic update on error + items.forEach((item) => { + // Optimistic update queryClient.setQueryData( ["item", item.Id], (oldData: BaseItemDto | undefined) => { @@ -81,8 +53,45 @@ export const useMarkAsPlayed = (item: BaseItemDto) => { return oldData; } ); + }) + + try { + // Process all items + await Promise.all(items.map(item => + played + ? markAsPlayed({ api, item, userId: user?.Id }) + : markAsNotPlayed({ api, itemId: item?.Id, userId: user?.Id }) + )); + + // Bulk invalidate + queryClient.invalidateQueries({ + queryKey: [ + "resumeItems", + "continueWatching", + "nextUp-all", + "nextUp", + "episodes", + "seasons", + "home", + ...items.map(item => ["item", item.Id]) + ].flat() + }); + } catch (error) { + // Revert all optimistic updates on any failure + items.forEach(item => { + queryClient.setQueryData( + ["item", item.Id], + (oldData: BaseItemDto | undefined) => + oldData ? { + ...oldData, + UserData: { ...oldData.UserData, Played: played } + } : oldData + ); + }); console.error("Error updating played status:", error); } + + invalidateQueries(); }; return markAsPlayedStatus;