mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-25 16:26:54 +01:00
feat(tv): add configurable inactivity timeout with auto-logout
This commit is contained in:
@@ -24,6 +24,7 @@ import { APP_LANGUAGES } from "@/i18n";
|
||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||
import {
|
||||
AudioTranscodeMode,
|
||||
InactivityTimeout,
|
||||
type MpvCacheMode,
|
||||
TVTypographyScale,
|
||||
useSettings,
|
||||
@@ -300,6 +301,57 @@ export default function SettingsTV() {
|
||||
[t, currentLanguage],
|
||||
);
|
||||
|
||||
// Inactivity timeout options (TV security feature)
|
||||
const currentInactivityTimeout =
|
||||
settings.inactivityTimeout ?? InactivityTimeout.Disabled;
|
||||
|
||||
const inactivityTimeoutOptions: TVOptionItem<InactivityTimeout>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.disabled"),
|
||||
value: InactivityTimeout.Disabled,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.Disabled,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.1_minute"),
|
||||
value: InactivityTimeout.OneMinute,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.OneMinute,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.5_minutes"),
|
||||
value: InactivityTimeout.FiveMinutes,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.FiveMinutes,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.15_minutes"),
|
||||
value: InactivityTimeout.FifteenMinutes,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.FifteenMinutes,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.30_minutes"),
|
||||
value: InactivityTimeout.ThirtyMinutes,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.ThirtyMinutes,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.1_hour"),
|
||||
value: InactivityTimeout.OneHour,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.OneHour,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.4_hours"),
|
||||
value: InactivityTimeout.FourHours,
|
||||
selected: currentInactivityTimeout === InactivityTimeout.FourHours,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.security.inactivity_timeout.24_hours"),
|
||||
value: InactivityTimeout.TwentyFourHours,
|
||||
selected:
|
||||
currentInactivityTimeout === InactivityTimeout.TwentyFourHours,
|
||||
},
|
||||
],
|
||||
[t, currentInactivityTimeout],
|
||||
);
|
||||
|
||||
// Get display labels for option buttons
|
||||
const audioTranscodeLabel = useMemo(() => {
|
||||
const option = audioTranscodeModeOptions.find((o) => o.selected);
|
||||
@@ -337,6 +389,13 @@ export default function SettingsTV() {
|
||||
return option?.label || t("home.settings.languages.system");
|
||||
}, [currentLanguage, t]);
|
||||
|
||||
const inactivityTimeoutLabel = useMemo(() => {
|
||||
const option = inactivityTimeoutOptions.find((o) => o.selected);
|
||||
return (
|
||||
option?.label || t("home.settings.security.inactivity_timeout.disabled")
|
||||
);
|
||||
}, [inactivityTimeoutOptions, t]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
@@ -371,6 +430,21 @@ export default function SettingsTV() {
|
||||
isFirst
|
||||
/>
|
||||
|
||||
{/* Security Section */}
|
||||
<TVSectionHeader title={t("home.settings.security.title")} />
|
||||
<TVSettingsOptionButton
|
||||
label={t("home.settings.security.inactivity_timeout.title")}
|
||||
value={inactivityTimeoutLabel}
|
||||
onPress={() =>
|
||||
showOptions({
|
||||
title: t("home.settings.security.inactivity_timeout.title"),
|
||||
options: inactivityTimeoutOptions,
|
||||
onSelect: (value) =>
|
||||
updateSettings({ inactivityTimeout: value }),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Audio Section */}
|
||||
<TVSectionHeader title={t("home.settings.audio.audio_title")} />
|
||||
<TVSettingsOptionButton
|
||||
|
||||
273
app/_layout.tsx
273
app/_layout.tsx
@@ -14,6 +14,7 @@ import { enableTVMenuKeyInterception } from "@/hooks/useTVBackHandler";
|
||||
import i18n from "@/i18n";
|
||||
import { DownloadProvider } from "@/providers/DownloadProvider";
|
||||
import { GlobalModalProvider } from "@/providers/GlobalModalProvider";
|
||||
import { InactivityProvider } from "@/providers/InactivityProvider";
|
||||
import { IntroSheetProvider } from "@/providers/IntroSheetProvider";
|
||||
import {
|
||||
apiAtom,
|
||||
@@ -385,143 +386,145 @@ function Layout() {
|
||||
}}
|
||||
>
|
||||
<JellyfinProvider>
|
||||
<ServerUrlProvider>
|
||||
<NetworkStatusProvider>
|
||||
<PlaySettingsProvider>
|
||||
<LogProvider>
|
||||
<WebSocketProvider>
|
||||
<DownloadProvider>
|
||||
<MusicPlayerProvider>
|
||||
<GlobalModalProvider>
|
||||
<BottomSheetModalProvider>
|
||||
<IntroSheetProvider>
|
||||
<ThemeProvider value={DarkTheme}>
|
||||
<SystemBars style='light' hidden={false} />
|
||||
<Stack initialRouteName='(auth)/(tabs)'>
|
||||
<Stack.Screen
|
||||
name='(auth)/(tabs)'
|
||||
options={{
|
||||
headerShown: false,
|
||||
title: "",
|
||||
header: () => null,
|
||||
<InactivityProvider>
|
||||
<ServerUrlProvider>
|
||||
<NetworkStatusProvider>
|
||||
<PlaySettingsProvider>
|
||||
<LogProvider>
|
||||
<WebSocketProvider>
|
||||
<DownloadProvider>
|
||||
<MusicPlayerProvider>
|
||||
<GlobalModalProvider>
|
||||
<BottomSheetModalProvider>
|
||||
<IntroSheetProvider>
|
||||
<ThemeProvider value={DarkTheme}>
|
||||
<SystemBars style='light' hidden={false} />
|
||||
<Stack initialRouteName='(auth)/(tabs)'>
|
||||
<Stack.Screen
|
||||
name='(auth)/(tabs)'
|
||||
options={{
|
||||
headerShown: false,
|
||||
title: "",
|
||||
header: () => null,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/player'
|
||||
options={{
|
||||
headerShown: false,
|
||||
title: "",
|
||||
header: () => null,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/now-playing'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "modal",
|
||||
gestureEnabled: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='login'
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: "",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name='+not-found' />
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-option-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-subtitle-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-request-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-season-select-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-series-season-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='tv-account-action-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='tv-account-select-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-user-switch-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Toaster
|
||||
duration={4000}
|
||||
toastOptions={{
|
||||
style: {
|
||||
backgroundColor: "#262626",
|
||||
borderColor: "#363639",
|
||||
borderWidth: 1,
|
||||
},
|
||||
titleStyle: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
closeButton
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/player'
|
||||
options={{
|
||||
headerShown: false,
|
||||
title: "",
|
||||
header: () => null,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/now-playing'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "modal",
|
||||
gestureEnabled: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='login'
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: "",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name='+not-found' />
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-option-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-subtitle-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-request-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-season-select-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-series-season-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='tv-account-action-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='tv-account-select-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='(auth)/tv-user-switch-modal'
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "transparentModal",
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Toaster
|
||||
duration={4000}
|
||||
toastOptions={{
|
||||
style: {
|
||||
backgroundColor: "#262626",
|
||||
borderColor: "#363639",
|
||||
borderWidth: 1,
|
||||
},
|
||||
titleStyle: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
closeButton
|
||||
/>
|
||||
{!Platform.isTV && <GlobalModal />}
|
||||
</ThemeProvider>
|
||||
</IntroSheetProvider>
|
||||
</BottomSheetModalProvider>
|
||||
</GlobalModalProvider>
|
||||
</MusicPlayerProvider>
|
||||
</DownloadProvider>
|
||||
</WebSocketProvider>
|
||||
</LogProvider>
|
||||
</PlaySettingsProvider>
|
||||
</NetworkStatusProvider>
|
||||
</ServerUrlProvider>
|
||||
{!Platform.isTV && <GlobalModal />}
|
||||
</ThemeProvider>
|
||||
</IntroSheetProvider>
|
||||
</BottomSheetModalProvider>
|
||||
</GlobalModalProvider>
|
||||
</MusicPlayerProvider>
|
||||
</DownloadProvider>
|
||||
</WebSocketProvider>
|
||||
</LogProvider>
|
||||
</PlaySettingsProvider>
|
||||
</NetworkStatusProvider>
|
||||
</ServerUrlProvider>
|
||||
</InactivityProvider>
|
||||
</JellyfinProvider>
|
||||
</PersistQueryClientProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user