Compare commits

...

2 Commits

Author SHA1 Message Date
Fredrik Burmester
d1b15a9dde feat(settings): add toggle to disable auto-play next episode
Add new setting to completely disable the auto-play next episode feature.
When disabled, the countdown button is hidden and the max episode count
setting appears greyed out.
2026-01-11 10:16:10 +01:00
Fredrik Burmester
12847894df feat(watchlists): add see all navigation for promoted watchlists
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
2026-01-11 00:19:31 +01:00
5 changed files with 53 additions and 17 deletions

View File

@@ -4,8 +4,10 @@ import type {
} from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi, getSystemApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "expo-router";
import { useAtomValue } from "jotai";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { ScrollView, View, type ViewProps } from "react-native";
import { SectionHeader } from "@/components/common/SectionHeader";
import { Text } from "@/components/common/Text";
@@ -33,6 +35,8 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
const api = useAtomValue(apiAtom);
const user = useAtomValue(userAtom);
const { settings } = useSettings();
const router = useRouter();
const { t } = useTranslation();
const { data: items, isLoading } = useQuery({
queryKey: [
@@ -84,11 +88,23 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
return items?.map((_, index) => index * ITEM_WIDTH) ?? [];
}, [items]);
const handleSeeAll = () => {
router.push({
pathname: "/(auth)/(tabs)/(watchlists)/[watchlistId]",
params: { watchlistId: watchlist.id.toString() },
} as any);
};
if (!isLoading && (!items || items.length === 0)) return null;
return (
<View {...props}>
<SectionHeader title={watchlist.name} />
<SectionHeader
title={watchlist.name}
actionLabel={t("common.seeAll", { defaultValue: "See all" })}
actionDisabled={isLoading}
onPressAction={handleSeeAll}
/>
{isLoading ? (
<View className='flex flex-row gap-2 px-4'>
{[1, 2, 3].map((i) => (

View File

@@ -211,7 +211,23 @@ export const PlaybackControlsSettings: React.FC = () => {
/>
</ListItem>
<ListItem title={t("home.settings.other.max_auto_play_episode_count")}>
<ListItem
title={t("home.settings.other.auto_play_next_episode")}
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
>
<Switch
value={settings.autoPlayNextEpisode}
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
onValueChange={(autoPlayNextEpisode) =>
updateSettings({ autoPlayNextEpisode })
}
/>
</ListItem>
<ListItem
title={t("home.settings.other.max_auto_play_episode_count")}
disabled={!settings.autoPlayNextEpisode}
>
<PlatformDropdown
groups={autoPlayEpisodeOptions}
trigger={

View File

@@ -148,21 +148,22 @@ export const BottomControls: FC<BottomControlsProps> = ({
onPress={skipCredit}
buttonText='Skip Credits'
/>
{(settings.maxAutoPlayEpisodeCount.value === -1 ||
settings.autoPlayEpisodeCount <
settings.maxAutoPlayEpisodeCount.value) && (
<NextEpisodeCountDownButton
show={
!nextItem
? false
: // Show during credits if no content after, OR near end of video
(showSkipCreditButton && !hasContentAfterCredits) ||
remainingTime < 10000
}
onFinish={handleNextEpisodeAutoPlay}
onPress={handleNextEpisodeManual}
/>
)}
{settings.autoPlayNextEpisode !== false &&
(settings.maxAutoPlayEpisodeCount.value === -1 ||
settings.autoPlayEpisodeCount <
settings.maxAutoPlayEpisodeCount.value) && (
<NextEpisodeCountDownButton
show={
!nextItem
? false
: // Show during credits if no content after, OR near end of video
(showSkipCreditButton && !hasContentAfterCredits) ||
remainingTime < 10000
}
onFinish={handleNextEpisodeAutoPlay}
onPress={handleNextEpisodeManual}
/>
)}
</View>
</View>
<View

View File

@@ -278,6 +278,7 @@
"disable_haptic_feedback": "Disable Haptic Feedback",
"default_quality": "Default Quality",
"default_playback_speed": "Default Playback Speed",
"auto_play_next_episode": "Auto-play Next Episode",
"max_auto_play_episode_count": "Max Auto Play Episode Count",
"disabled": "Disabled"
},

View File

@@ -180,6 +180,7 @@ export type Settings = {
enableH265ForChromecast: boolean;
maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount;
autoPlayEpisodeCount: number;
autoPlayNextEpisode: boolean;
// Playback speed settings
defaultPlaybackSpeed: number;
playbackSpeedPerMedia: Record<string, number>;
@@ -264,6 +265,7 @@ export const defaultValues: Settings = {
enableH265ForChromecast: false,
maxAutoPlayEpisodeCount: { key: "3", value: 3 },
autoPlayEpisodeCount: 0,
autoPlayNextEpisode: true,
// Playback speed defaults
defaultPlaybackSpeed: 1.0,
playbackSpeedPerMedia: {},