2026-06-07 15:57:05 -04:00
|
|
|
import { useMutation, useQuery, useQueries, useQueryClient } from "@tanstack/react-query";
|
2026-05-22 20:26:11 -04:00
|
|
|
import { api } from "@/api/client";
|
|
|
|
|
import type { ProgressListResponse, ProgressEntry, ItemDetail } from "@/api/types";
|
|
|
|
|
import { catalogKeys, progressKeys } from "./keys";
|
|
|
|
|
import { fetchCatalogItemDetail } from "./catalogRead";
|
|
|
|
|
|
|
|
|
|
interface ContinueWatchingOptions {
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useProgressList(libraryId?: number, options?: ContinueWatchingOptions) {
|
|
|
|
|
return useQuery({
|
|
|
|
|
queryKey: progressKeys.list("in_progress", libraryId),
|
|
|
|
|
queryFn: () => {
|
|
|
|
|
const searchParams = new URLSearchParams({
|
|
|
|
|
status: "in_progress",
|
|
|
|
|
limit: "20",
|
|
|
|
|
});
|
|
|
|
|
if (libraryId) {
|
|
|
|
|
searchParams.set("library_id", String(libraryId));
|
|
|
|
|
}
|
|
|
|
|
return api<ProgressListResponse>(`/progress?${searchParams.toString()}`);
|
|
|
|
|
},
|
|
|
|
|
enabled: options?.enabled ?? true,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ContinueWatchingItem {
|
|
|
|
|
progress: ProgressEntry;
|
|
|
|
|
detail: ItemDetail | undefined;
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useContinueWatching(
|
|
|
|
|
libraryId?: number,
|
|
|
|
|
options?: ContinueWatchingOptions,
|
|
|
|
|
): {
|
|
|
|
|
items: ContinueWatchingItem[];
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
} {
|
|
|
|
|
const enabled = options?.enabled ?? true;
|
|
|
|
|
const { data: progressData, isLoading: progressLoading } = useProgressList(libraryId, {
|
|
|
|
|
enabled,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const entries = progressData?.progress ?? [];
|
|
|
|
|
|
|
|
|
|
const detailQueries = useQueries({
|
|
|
|
|
queries: entries.map((entry) => ({
|
|
|
|
|
queryKey: catalogKeys.itemDetail(entry.media_item_id),
|
|
|
|
|
queryFn: () => fetchCatalogItemDetail(entry.media_item_id),
|
|
|
|
|
enabled: enabled && !!entry.media_item_id,
|
|
|
|
|
staleTime: 2 * 60 * 1000,
|
|
|
|
|
})),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const items: ContinueWatchingItem[] = entries.map((entry, i) => ({
|
|
|
|
|
progress: entry,
|
|
|
|
|
detail: detailQueries[i]?.data,
|
|
|
|
|
isLoading: detailQueries[i]?.isLoading ?? true,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
items,
|
|
|
|
|
isLoading: enabled && progressLoading,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-06-07 15:57:05 -04:00
|
|
|
|
|
|
|
|
interface ReportMediaProgressVars {
|
|
|
|
|
contentId: string;
|
|
|
|
|
positionSeconds: number;
|
|
|
|
|
durationSeconds: number;
|
|
|
|
|
forceOverwrite?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useReportMediaProgress() {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
return useMutation({
|
|
|
|
|
mutationFn: ({
|
|
|
|
|
contentId,
|
|
|
|
|
positionSeconds,
|
|
|
|
|
durationSeconds,
|
|
|
|
|
forceOverwrite = true,
|
|
|
|
|
}: ReportMediaProgressVars) =>
|
|
|
|
|
api("/sync/progress", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
media_item_id: contentId,
|
|
|
|
|
position: positionSeconds,
|
|
|
|
|
duration: durationSeconds,
|
|
|
|
|
force_overwrite: forceOverwrite,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
}),
|
2026-06-17 09:51:43 -04:00
|
|
|
onSuccess: (_data, variables) => {
|
|
|
|
|
// Progress genuinely changed → refresh progress-derived surfaces
|
|
|
|
|
// (continue-watching etc.). Scope the catalog invalidation to THIS item's
|
|
|
|
|
// detail rather than all of `catalog`: a progress report fires every ~10s
|
|
|
|
|
// during playback, and invalidating catalogKeys.all refetched every active
|
|
|
|
|
// browse/detail query — including the large audiobook author/narrator
|
|
|
|
|
// group lists — on every tick.
|
2026-06-07 15:57:05 -04:00
|
|
|
queryClient.invalidateQueries({ queryKey: progressKeys.all });
|
2026-06-17 09:51:43 -04:00
|
|
|
queryClient.invalidateQueries({
|
|
|
|
|
queryKey: catalogKeys.itemDetail(variables.contentId),
|
|
|
|
|
});
|
2026-06-07 15:57:05 -04:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|