mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-20 02:54:17 +01:00
feat(settings): add Account and Notifications pages, gate download notifications
This commit is contained in:
@@ -243,6 +243,28 @@ export default function IndexLayout() {
|
|||||||
headerLeft: () => <HeaderBackButton />,
|
headerLeft: () => <HeaderBackButton />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='settings/account/page'
|
||||||
|
options={{
|
||||||
|
title: t("home.settings.account.title"),
|
||||||
|
headerShown: !Platform.isTV,
|
||||||
|
headerBlurEffect: "none",
|
||||||
|
headerTransparent: Platform.OS === "ios",
|
||||||
|
headerShadowVisible: false,
|
||||||
|
headerLeft: () => <HeaderBackButton />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='settings/notifications/page'
|
||||||
|
options={{
|
||||||
|
title: t("home.settings.notifications.title"),
|
||||||
|
headerShown: !Platform.isTV,
|
||||||
|
headerBlurEffect: "none",
|
||||||
|
headerTransparent: Platform.OS === "ios",
|
||||||
|
headerShadowVisible: false,
|
||||||
|
headerLeft: () => <HeaderBackButton />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
|
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
|
||||||
<Stack.Screen key={name} name={name} options={options} />
|
<Stack.Screen key={name} name={name} options={options} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
53
app/(auth)/(tabs)/(home)/settings/account/page.tsx
Normal file
53
app/(auth)/(tabs)/(home)/settings/account/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as Application from "expo-application";
|
||||||
|
import { setStringAsync } from "expo-clipboard";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Alert, ScrollView } from "react-native";
|
||||||
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
|
||||||
|
export default function AccountPage() {
|
||||||
|
const [api] = useAtom(apiAtom);
|
||||||
|
const [user] = useAtom(userAtom);
|
||||||
|
const [revealed, setRevealed] = useState(false);
|
||||||
|
const success = useHaptic("success");
|
||||||
|
const version = Application.nativeApplicationVersion ?? "N/A";
|
||||||
|
const token = api?.accessToken ?? "";
|
||||||
|
const masked = token ? `•••• •••• •••• ${token.slice(-4)}` : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={{ padding: 16 }}>
|
||||||
|
<ListGroup title={t("home.settings.user_info.user_info_title")}>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.user_info.user")}
|
||||||
|
value={user?.Name}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.user_info.server")}
|
||||||
|
value={api?.basePath}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.user_info.token")}
|
||||||
|
value={revealed ? token : masked}
|
||||||
|
onPress={() => setRevealed((r) => !r)}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.account.copy_token")}
|
||||||
|
textColor='blue'
|
||||||
|
onPress={async () => {
|
||||||
|
await setStringAsync(token);
|
||||||
|
success();
|
||||||
|
Alert.alert(t("home.settings.account.copied"));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.user_info.app_version")}
|
||||||
|
value={version}
|
||||||
|
/>
|
||||||
|
</ListGroup>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
61
app/(auth)/(tabs)/(home)/settings/notifications/page.tsx
Normal file
61
app/(auth)/(tabs)/(home)/settings/notifications/page.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import * as Notifications from "expo-notifications";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Linking, ScrollView, Switch } from "react-native";
|
||||||
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
|
export default function NotificationsPage() {
|
||||||
|
const { settings, updateSettings } = useSettings();
|
||||||
|
const [granted, setGranted] = useState<boolean | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Notifications.getPermissionsAsync().then((p) => setGranted(p.granted));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const requestPermission = async () => {
|
||||||
|
const p = await Notifications.requestPermissionsAsync();
|
||||||
|
setGranted(p.granted);
|
||||||
|
if (!p.granted) Linking.openSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!settings) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={{ padding: 16 }}>
|
||||||
|
<ListGroup title={t("home.settings.notifications.permission_title")}>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.notifications.system_permission")}
|
||||||
|
value={
|
||||||
|
granted == null
|
||||||
|
? "…"
|
||||||
|
: granted
|
||||||
|
? t("home.settings.notifications.granted")
|
||||||
|
: t("home.settings.notifications.denied")
|
||||||
|
}
|
||||||
|
textColor={granted ? "default" : "blue"}
|
||||||
|
onPress={granted ? undefined : requestPermission}
|
||||||
|
/>
|
||||||
|
</ListGroup>
|
||||||
|
<ListGroup
|
||||||
|
title={t("home.settings.notifications.events_title")}
|
||||||
|
className='mt-4'
|
||||||
|
>
|
||||||
|
<ListItem title={t("home.settings.notifications.master")}>
|
||||||
|
<Switch
|
||||||
|
value={settings.notificationsEnabled}
|
||||||
|
onValueChange={(v) => updateSettings({ notificationsEnabled: v })}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem title={t("home.settings.notifications.downloads")}>
|
||||||
|
<Switch
|
||||||
|
value={settings.notifyDownloads}
|
||||||
|
disabled={!settings.notificationsEnabled}
|
||||||
|
onValueChange={(v) => updateSettings({ notifyDownloads: v })}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ListGroup>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
|||||||
import type * as NotificationsType from "expo-notifications";
|
import type * as NotificationsType from "expo-notifications";
|
||||||
import type { TFunction } from "i18next";
|
import type { TFunction } from "i18next";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
|
import { storage } from "@/utils/mmkv";
|
||||||
|
|
||||||
// Conditionally import expo-notifications only on non-TV platforms
|
// Conditionally import expo-notifications only on non-TV platforms
|
||||||
const Notifications = Platform.isTV
|
const Notifications = Platform.isTV
|
||||||
@@ -67,6 +68,14 @@ export async function sendDownloadNotification(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (Platform.isTV || !Notifications) return;
|
if (Platform.isTV || !Notifications) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = storage.getString("settings");
|
||||||
|
const s = raw ? JSON.parse(raw) : {};
|
||||||
|
if (s.notificationsEnabled === false || s.notifyDownloads === false) return;
|
||||||
|
} catch {
|
||||||
|
// ignore parse errors; fall through to sending (defaults are enabled)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Notifications.scheduleNotificationAsync({
|
await Notifications.scheduleNotificationAsync({
|
||||||
content: {
|
content: {
|
||||||
|
|||||||
Reference in New Issue
Block a user