diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx
index de5545d62..1a09e5c78 100644
--- a/app/(auth)/(tabs)/(home)/_layout.tsx
+++ b/app/(auth)/(tabs)/(home)/_layout.tsx
@@ -243,6 +243,28 @@ export default function IndexLayout() {
headerLeft: () => ,
}}
/>
+ ,
+ }}
+ />
+ ,
+ }}
+ />
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
))}
diff --git a/app/(auth)/(tabs)/(home)/settings/account/page.tsx b/app/(auth)/(tabs)/(home)/settings/account/page.tsx
new file mode 100644
index 000000000..2e2a1d314
--- /dev/null
+++ b/app/(auth)/(tabs)/(home)/settings/account/page.tsx
@@ -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 (
+
+
+
+
+ setRevealed((r) => !r)}
+ />
+ {
+ await setStringAsync(token);
+ success();
+ Alert.alert(t("home.settings.account.copied"));
+ }}
+ />
+
+
+
+ );
+}
diff --git a/app/(auth)/(tabs)/(home)/settings/notifications/page.tsx b/app/(auth)/(tabs)/(home)/settings/notifications/page.tsx
new file mode 100644
index 000000000..8ba093d3d
--- /dev/null
+++ b/app/(auth)/(tabs)/(home)/settings/notifications/page.tsx
@@ -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(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 (
+
+
+
+
+
+
+ updateSettings({ notificationsEnabled: v })}
+ />
+
+
+ updateSettings({ notifyDownloads: v })}
+ />
+
+
+
+ );
+}
diff --git a/providers/Downloads/notifications.ts b/providers/Downloads/notifications.ts
index d2d1a7a13..79bb8e1d8 100644
--- a/providers/Downloads/notifications.ts
+++ b/providers/Downloads/notifications.ts
@@ -2,6 +2,7 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import type * as NotificationsType from "expo-notifications";
import type { TFunction } from "i18next";
import { Platform } from "react-native";
+import { storage } from "@/utils/mmkv";
// Conditionally import expo-notifications only on non-TV platforms
const Notifications = Platform.isTV
@@ -67,6 +68,14 @@ export async function sendDownloadNotification(
): Promise {
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 {
await Notifications.scheduleNotificationAsync({
content: {