mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 14:53:24 +00:00
feat: watch history
This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
import React from "react";
|
||||
import { View } from "tamagui";
|
||||
|
||||
import { ItemListSection, watching } from "~/components/item/ItemListSection";
|
||||
import { ItemListSection } from "~/components/item/ItemListSection";
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { useBookmarkStore } from "~/stores/settings";
|
||||
import { useBookmarkStore, useWatchHistoryStore } from "~/stores/settings";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { bookmarks } = useBookmarkStore();
|
||||
const { watchHistory } = useWatchHistoryStore();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }} flex={1}>
|
||||
<ScreenLayout>
|
||||
<ItemListSection title="Bookmarks" items={bookmarks} />
|
||||
<ItemListSection title="Continue Watching" items={watching} />
|
||||
<ItemListSection
|
||||
title="Continue Watching"
|
||||
items={watchHistory.map((x) => x.item)}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
</View>
|
||||
);
|
||||
|
@@ -5,41 +5,6 @@ import { ScrollView, Text, View } from "tamagui";
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
import Item from "~/components/item/item";
|
||||
|
||||
export const watching: ItemData[] = [
|
||||
{
|
||||
id: "219651",
|
||||
title: "Welcome to Samdal-ri",
|
||||
posterUrl:
|
||||
"https://www.themoviedb.org/t/p/w500/98IvA2i0PsTY8CThoHByCKOEAjz.jpg",
|
||||
type: "tv",
|
||||
year: 2023,
|
||||
},
|
||||
{
|
||||
id: "194797",
|
||||
title: "Doona!",
|
||||
posterUrl:
|
||||
"https://www.themoviedb.org/t/p/w500/bQhiOkU3lCu5pwCqPdNVG5GBLlj.jpg",
|
||||
type: "tv",
|
||||
year: 2023,
|
||||
},
|
||||
{
|
||||
id: "113268",
|
||||
title: "The Uncanny Counter",
|
||||
posterUrl:
|
||||
"https://www.themoviedb.org/t/p/w500/tKU34QiJUfVipcuhAs5S3TdCpAF.jpg",
|
||||
type: "tv",
|
||||
year: 2020,
|
||||
},
|
||||
{
|
||||
id: "203508",
|
||||
title: "Earth Arcade",
|
||||
posterUrl:
|
||||
"https://www.themoviedb.org/t/p/w500/vBJ0uF0WlFcjr9obZZqE6GSsKoL.jpg",
|
||||
type: "tv",
|
||||
year: 2022,
|
||||
},
|
||||
];
|
||||
|
||||
const padding = 20;
|
||||
const screenWidth = Dimensions.get("window").width;
|
||||
const itemWidth = screenWidth / 2.3 - padding;
|
||||
|
@@ -25,10 +25,17 @@ import { useBrightness } from "~/hooks/player/useBrightness";
|
||||
import { usePlaybackSpeed } from "~/hooks/player/usePlaybackSpeed";
|
||||
import { usePlayer } from "~/hooks/player/usePlayer";
|
||||
import { useVolume } from "~/hooks/player/useVolume";
|
||||
import { convertMetaToScrapeMedia, getNextEpisode } from "~/lib/meta";
|
||||
import {
|
||||
convertMetaToItemData,
|
||||
convertMetaToScrapeMedia,
|
||||
getNextEpisode,
|
||||
} from "~/lib/meta";
|
||||
import { useAudioTrackStore } from "~/stores/audio";
|
||||
import { usePlayerStore } from "~/stores/player/store";
|
||||
import { usePlayerSettingsStore } from "~/stores/settings";
|
||||
import {
|
||||
usePlayerSettingsStore,
|
||||
useWatchHistoryStore,
|
||||
} from "~/stores/settings";
|
||||
import { CaptionRenderer } from "./CaptionRenderer";
|
||||
import { ControlsOverlay } from "./ControlsOverlay";
|
||||
|
||||
@@ -68,6 +75,7 @@ export const VideoPlayer = () => {
|
||||
const setMeta = usePlayerStore((state) => state.setMeta);
|
||||
|
||||
const { gestureControls, autoPlay } = usePlayerSettingsStore();
|
||||
const { updateWatchHistory, removeFromWatchHistory } = useWatchHistoryStore();
|
||||
|
||||
const updateResizeMode = (newMode: ResizeMode) => {
|
||||
setResizeMode(newMode);
|
||||
@@ -195,6 +203,15 @@ export const VideoPlayer = () => {
|
||||
}, 60000);
|
||||
|
||||
return () => {
|
||||
if (meta) {
|
||||
const item = convertMetaToItemData(meta);
|
||||
const scrapeMedia = convertMetaToScrapeMedia(meta);
|
||||
updateWatchHistory(
|
||||
item,
|
||||
scrapeMedia,
|
||||
videoRef?.props.positionMillis ?? 0,
|
||||
);
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
void synchronizePlayback();
|
||||
};
|
||||
@@ -202,11 +219,14 @@ export const VideoPlayer = () => {
|
||||
asset,
|
||||
dismissFullscreenPlayer,
|
||||
hasStartedPlaying,
|
||||
meta,
|
||||
router,
|
||||
selectedAudioTrack,
|
||||
setVideoSrc,
|
||||
stream,
|
||||
synchronizePlayback,
|
||||
updateWatchHistory,
|
||||
videoRef?.props.positionMillis,
|
||||
]);
|
||||
|
||||
const onVideoLoadStart = () => {
|
||||
@@ -218,11 +238,24 @@ export const VideoPlayer = () => {
|
||||
setHasStartedPlaying(true);
|
||||
if (videoRef) {
|
||||
void videoRef.setRateAsync(currentSpeed, true);
|
||||
if (meta) {
|
||||
const item = convertMetaToItemData(meta);
|
||||
const scrapeMedia = convertMetaToScrapeMedia(meta);
|
||||
updateWatchHistory(
|
||||
item,
|
||||
scrapeMedia,
|
||||
videoRef.props.positionMillis ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPlaybackStatusUpdate = async (status: AVPlaybackStatus) => {
|
||||
setStatus(status);
|
||||
if (meta && status.isLoaded && status.didJustFinish) {
|
||||
const item = convertMetaToItemData(meta);
|
||||
removeFromWatchHistory(item);
|
||||
}
|
||||
if (
|
||||
status.isLoaded &&
|
||||
status.didJustFinish &&
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
||||
import { fetchMediaDetails, fetchSeasonDetails } from "@movie-web/tmdb";
|
||||
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
import type { PlayerMeta } from "~/stores/player/slices/video";
|
||||
|
||||
export const convertMetaToScrapeMedia = (meta: PlayerMeta): ScrapeMedia => {
|
||||
@@ -27,6 +28,28 @@ export const convertMetaToScrapeMedia = (meta: PlayerMeta): ScrapeMedia => {
|
||||
throw new Error("Invalid meta type");
|
||||
};
|
||||
|
||||
export const convertMetaToItemData = (meta: PlayerMeta): ItemData => {
|
||||
if (meta.type === "movie") {
|
||||
return {
|
||||
id: meta.tmdbId,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
type: meta.type,
|
||||
posterUrl: meta.poster ?? "",
|
||||
};
|
||||
}
|
||||
if (meta.type === "show") {
|
||||
return {
|
||||
id: meta.tmdbId,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
type: "tv",
|
||||
posterUrl: meta.poster ?? "",
|
||||
};
|
||||
}
|
||||
throw new Error("Invalid media type");
|
||||
};
|
||||
|
||||
export const getNextEpisode = async (
|
||||
meta: PlayerMeta,
|
||||
): Promise<PlayerMeta | undefined> => {
|
||||
|
@@ -4,10 +4,11 @@ import { MMKV } from "react-native-mmkv";
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
||||
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
import type { DownloadItem } from "~/hooks/DownloadManagerContext";
|
||||
import type { ThemeStoreOption } from "~/stores/theme";
|
||||
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
||||
|
||||
const storage = new MMKV();
|
||||
|
||||
@@ -141,7 +142,11 @@ interface WatchHistoryItem {
|
||||
interface WatchHistoryStoreState {
|
||||
watchHistory: WatchHistoryItem[];
|
||||
setWatchHistory: (watchHistory: WatchHistoryItem[]) => void;
|
||||
addToWatchHistory: (item: ItemData, media: ScrapeMedia) => void;
|
||||
updateWatchHistory: (
|
||||
item: ItemData,
|
||||
media: ScrapeMedia,
|
||||
positionMillis: number,
|
||||
) => void;
|
||||
removeFromWatchHistory: (item: ItemData) => void;
|
||||
}
|
||||
|
||||
@@ -154,7 +159,11 @@ export const useWatchHistoryStore = create<
|
||||
watchHistory: [],
|
||||
setWatchHistory: (watchHistory: WatchHistoryItem[]) =>
|
||||
set({ watchHistory }),
|
||||
addToWatchHistory: (item: ItemData, media: ScrapeMedia) =>
|
||||
updateWatchHistory: (
|
||||
item: ItemData,
|
||||
media: ScrapeMedia,
|
||||
positionMillis: number,
|
||||
) =>
|
||||
set((state) => ({
|
||||
watchHistory: [
|
||||
...state.watchHistory.filter(
|
||||
@@ -163,7 +172,7 @@ export const useWatchHistoryStore = create<
|
||||
{
|
||||
item,
|
||||
media,
|
||||
positionMillis: 0,
|
||||
positionMillis,
|
||||
},
|
||||
],
|
||||
})),
|
||||
@@ -172,8 +181,8 @@ export const useWatchHistoryStore = create<
|
||||
watchHistory: state.watchHistory.filter(
|
||||
(historyItem) => historyItem.item.id !== item.id,
|
||||
),
|
||||
})),
|
||||
}),
|
||||
)}),
|
||||
{
|
||||
name: "watch-history",
|
||||
storage: createJSONStorage(() => zustandStorage),
|
||||
|
Reference in New Issue
Block a user