{
onDismissClick ?
@@ -175,7 +181,8 @@ MetaItem.propTypes = {
onDismissClick: PropTypes.func,
onPlayClick: PropTypes.func,
onClick: PropTypes.func,
- watched: PropTypes.bool
+ watched: PropTypes.bool,
+ nextEpisodeReleaseDate: PropTypes.string
};
module.exports = MetaItem;
diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js
index cb2888a52..85335b986 100644
--- a/src/routes/Board/Board.js
+++ b/src/routes/Board/Board.js
@@ -8,20 +8,35 @@ const { useStreamingServer, useNotifications, withCoreSuspender, getVisibleChild
const { ContinueWatchingItem, EventModal, MainNavBars, MetaItem, MetaRow } = require('stremio/components');
const useBoard = require('./useBoard');
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
+const useMetaDetailsForMetaItem = require('./useMetaDetailsForMetaItem'); // Import the new hook
const styles = require('./styles');
const { default: StreamingServerWarning } = require('./StreamingServerWarning');
const THRESHOLD = 5;
+// Helper component to fetch and pass nextEpisodeReleaseDate for a single item
+// eslint-disable-next-line react/prop-types
+const ContinueWatchingItemWithDetails = ({ item, notifications }) => {
+ const nextEpisodeReleaseDate = useMetaDetailsForMetaItem(item);
+ return (
+
+ );
+};
+
const Board = () => {
const t = useTranslate();
const streamingServer = useStreamingServer();
const continueWatchingPreview = useContinueWatchingPreview();
const [board, loadBoardRows] = useBoard();
- const notifications = useNotifications();
+ const notifications = useNotifications(); // useNotifications returns an object, no need to convert to Map
const profile = useProfile();
const boardCatalogsOffset = continueWatchingPreview.items.length > 0 ? 1 : 0;
const scrollContainerRef = React.useRef();
+
const showStreamingServerWarning = React.useMemo(() => {
return streamingServer.settings !== null && streamingServer.settings.type === 'Err' && (
isNaN(profile.settings.streamingServerWarningDismissed.getTime()) ||
@@ -45,6 +60,7 @@ const Board = () => {
React.useLayoutEffect(() => {
onVisibleRangeChange();
}, [board.catalogs, onVisibleRangeChange]);
+
return (
@@ -55,9 +71,19 @@ const Board = () => {
(
+
+ )),
+ }}
+ // We are passing a React element as itemComponent, so we don't need itemComponent prop here
+ // The items array now contains the rendered components
+ itemComponent={({ item }) => item} // This is a workaround to render the React elements directly
/>
:
null
diff --git a/src/routes/Board/useMetaDetailsForMetaItem.js b/src/routes/Board/useMetaDetailsForMetaItem.js
new file mode 100644
index 000000000..c848052d2
--- /dev/null
+++ b/src/routes/Board/useMetaDetailsForMetaItem.js
@@ -0,0 +1,71 @@
+const React = require('react');
+const { useModelState, useProfile } = require('stremio/common');
+
+const useMetaDetailsForMetaItem = (metaItemPreview) => {
+ const profile = useProfile();
+ // Create a unique model name for each meta item to avoid state conflicts
+ const modelName = React.useMemo(() => {
+ return metaItemPreview && metaItemPreview.id ? `metaDetails_${metaItemPreview.id}` : null;
+ }, [metaItemPreview?.id]);
+
+ const metaDetails = useModelState({
+ model: modelName,
+ action: React.useMemo(() => {
+ if (!metaItemPreview || !metaItemPreview.id || !metaItemPreview.type) {
+ return null;
+ }
+ return {
+ action: 'Load',
+ args: {
+ model: 'MetaDetails',
+ args: {
+ type: metaItemPreview.type,
+ id: metaItemPreview.id
+ }
+ }
+ };
+ }, [metaItemPreview?.id, metaItemPreview?.type])
+ });
+
+ const nextEpisodeReleaseDate = React.useMemo(() => {
+ if (metaDetails && metaDetails.content?.type === 'Ready' && Array.isArray(metaDetails.content.content.videos)) {
+ const now = new Date();
+ now.setHours(0, 0, 0, 0); // Normalize 'now' to start of day
+
+ const upcomingVideos = metaDetails.content.content.videos.filter((video) => {
+ // Check if the video is scheduled and has a release date
+ if (video.scheduled && video.released) {
+ // Parse the release date string (e.g., 'YYYY-MM-DD')
+ const [year, month, day] = video.released.split('-').map(Number);
+ const releaseDate = new Date(year, month - 1, day); // month - 1 because Date months are 0-indexed
+
+ // Only consider episodes that are in the future or today
+ return releaseDate >= now;
+ }
+ return false;
+ }).sort((a, b) => {
+ // Sort by release date to find the soonest upcoming episode
+ const dateA = new Date(a.released);
+ const dateB = new Date(b.released);
+ return dateA.getTime() - dateB.getTime();
+ });
+
+ if (upcomingVideos.length > 0) {
+ const nextVideo = upcomingVideos[0];
+ const [year, month, day] = nextVideo.released.split('-').map(Number);
+ const releaseDate = new Date(year, month - 1, day);
+ // Format the date string using the user's interface language
+ return releaseDate.toLocaleDateString(profile.settings.interfaceLanguage, {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ });
+ }
+ }
+ return null;
+ }, [metaDetails, profile.settings.interfaceLanguage]);
+
+ return nextEpisodeReleaseDate;
+};
+
+module.exports = useMetaDetailsForMetaItem;
diff --git a/src/types/MetaItem.d.ts b/src/types/MetaItem.d.ts
index 56e7db3c7..c127ba7f1 100644
--- a/src/types/MetaItem.d.ts
+++ b/src/types/MetaItem.d.ts
@@ -14,6 +14,7 @@ type MetaItemPreview = {
poster: string | null,
posterShape: PosterShape,
releaseInfo: string | null,
+ nextEpisodeReleaseDate: string | null,
runtime: string | null,
released: Date | null | undefined,
trailerStreams: TrailerStream[],