From 939fd2512d5a480f55fa61b6d14983439febf8af Mon Sep 17 00:00:00 2001 From: lance chant <13349722+lancechant@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:38:34 +0200 Subject: [PATCH 1/2] fix: max episodes count (#1554) Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com> --- .../settings/plugins/streamystats/page.tsx | 2 +- components/settings/OtherSettings.tsx | 5 +- .../settings/PlaybackControlsSettings.tsx | 5 +- utils/atoms/settings.ts | 130 ++++++++++-------- 4 files changed, 85 insertions(+), 57 deletions(-) diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx index 1c4dcd199..8f0a2c931 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -114,7 +114,7 @@ export default function StreamystatsPage() { }; const handleRefreshFromServer = useCallback(async () => { - const newPluginSettings = await refreshStreamyfinPluginSettings(true); + const newPluginSettings = await refreshStreamyfinPluginSettings(); // Update local state with new values const newUrl = newPluginSettings?.streamyStatsServerUrl?.value || ""; setUrl(newUrl); diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 7abf10fbd..bd339c543 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -196,7 +196,10 @@ export const OtherSettings: React.FC = () => { } /> - + { { + if (typeof value !== "object" || value === null) { + const defaultVal = defaultValues[settingsKey]; + if ( + typeof defaultVal === "object" && + defaultVal !== null && + "key" in defaultVal && + "value" in defaultVal + ) { + // defaultBitrate needs a lookup because its keys are human-readable + // (e.g. "8 Mb/s") that can't be derived from the raw value (e.g. 8000000). + // Other { key, value } settings like maxAutoPlayEpisodeCount work with + // the fallback because their keys are just String(value) (e.g. "5"). + if (settingsKey === "defaultBitrate") { + const match = BITRATES.find( + (b) => b.key === value || b.value === value, + ); + if (match) return match; + } + // maxAutoPlayEpisodeCount: 0 is invalid (breaks autoplay), clamp to -1 + // -1 key must match the translated dropdown label so the UI shows "Disabled" + if ( + settingsKey === "maxAutoPlayEpisodeCount" && + (value === 0 || value === -1) + ) { + return { key: t("home.settings.other.disabled"), value: -1 }; + } + return { key: String(value), value }; + } + } + return value; +}; + export type HomeSectionLatestResolver = { parentId?: string; limit?: number; @@ -427,61 +468,37 @@ export const useSettings = () => { [_setPluginSettings], ); - const refreshStreamyfinPluginSettings = useCallback( - async (forceOverride = false) => { - if (!api) { - return; + const refreshStreamyfinPluginSettings = useCallback(async () => { + if (!api) { + return; + } + const newPluginSettings = await api.getStreamyfinPluginConfig().then( + ({ data }) => { + writeInfoLog("Got plugin settings", data?.settings); + return data?.settings; + }, + (_err) => undefined, + ); + setPluginSettings(newPluginSettings); + + // Locked/unlocked values are handled by the settings memo, which + // applies locked values at runtime without overwriting user storage. + // We only handle auto-enabling Streamystats here. + if (newPluginSettings && _settings) { + const streamyStatsUrl = newPluginSettings.streamyStatsServerUrl; + if (streamyStatsUrl?.value && _settings.searchEngine !== "Streamystats") { + const newSettings = { + ...defaultValues, + ..._settings, + searchEngine: "Streamystats", + } as Settings; + setSettings(newSettings); + saveSettings(newSettings); } - const newPluginSettings = await api.getStreamyfinPluginConfig().then( - ({ data }) => { - writeInfoLog("Got plugin settings", data?.settings); - return data?.settings; - }, - (_err) => undefined, - ); - setPluginSettings(newPluginSettings); + } - // Apply plugin values to settings - if (newPluginSettings && _settings) { - const updates: Partial = {}; - for (const [key, setting] of Object.entries(newPluginSettings)) { - if (setting && !setting.locked && setting.value !== undefined) { - const settingsKey = key as keyof Settings; - const effectiveValue = getEffectiveSettingValue( - _settings, - settingsKey, - ); - // Apply if forceOverride is true, or if neither persisted settings - // nor app defaults provide a meaningful value. - if (forceOverride || !hasMeaningfulSettingValue(effectiveValue)) { - (updates as any)[settingsKey] = setting.value; - } - } - } - - // Auto-enable Streamystats if server URL is provided - const streamyStatsUrl = newPluginSettings.streamyStatsServerUrl; - if ( - streamyStatsUrl?.value && - _settings.searchEngine !== "Streamystats" - ) { - updates.searchEngine = "Streamystats"; - } - if (Object.keys(updates).length > 0) { - const newSettings = { - ...defaultValues, - ..._settings, - ...updates, - } as Settings; - setSettings(newSettings); - saveSettings(newSettings); - } - } - - return newPluginSettings; - }, - [api, _settings], - ); + return newPluginSettings; + }, [api, _settings]); const updateSettings = (update: Partial) => { if (!_settings) { @@ -512,8 +529,13 @@ export const useSettings = () => { Partial >((acc, [key, setting]) => { if (setting) { - const { value, locked } = setting; + let { value } = setting; + const { locked } = setting; const settingsKey = key as keyof Settings; + + // Normalize object-typed settings from plugin (plain primitive → { key, value }) + value = normalizePluginValue(settingsKey, value); + const effectiveValue = getEffectiveSettingValue(_settings, settingsKey); (acc as any)[settingsKey] = locked From 338fb9713b28fe6d1a2aaf1d53e7440d720c9361 Mon Sep 17 00:00:00 2001 From: lance chant <13349722+lancechant@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:38:54 +0200 Subject: [PATCH 2/2] fix: qr code scanning not working ios (#1619) Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com> --- app/(auth)/(tabs)/(home)/settings.tsx | 24 +++++++++++++----------- components/login/TVAddServerForm.tsx | 4 ++-- utils/pairingService.ts | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index db223b2bf..69b980d3e 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -59,17 +59,19 @@ function SettingsMobile() { - - - - router.push("/(auth)/(tabs)/(home)/companion-login") - } - title={t("pairing.pair_with_phone")} - textColor='blue' - /> - - + {Platform.OS !== "ios" && ( + + + + router.push("/(auth)/(tabs)/(home)/companion-login") + } + title={t("pairing.pair_with_phone")} + textColor='blue' + /> + + + )} diff --git a/components/login/TVAddServerForm.tsx b/components/login/TVAddServerForm.tsx index 8d0168b23..cf38fa54c 100644 --- a/components/login/TVAddServerForm.tsx +++ b/components/login/TVAddServerForm.tsx @@ -1,6 +1,6 @@ import { t } from "i18next"; import React, { useCallback, useState } from "react"; -import { ScrollView, View } from "react-native"; +import { Platform, ScrollView, View } from "react-native"; import { Button } from "@/components/Button"; import { Text } from "@/components/common/Text"; import { useScaledTVTypography } from "@/constants/TVTypography"; @@ -107,7 +107,7 @@ export const TVAddServerForm: React.FC = ({ {/* Pair with Phone */} - {onStartPairing && ( + {Platform.OS !== "ios" && onStartPairing && (