From 70db195511d4ce00d895d9fa0fd01a4d2d96e1b5 Mon Sep 17 00:00:00 2001
From: Decidetto <5384177+Decidetto@users.noreply.github.com>
Date: Sun, 6 Sep 2026 12:17:20 +0200
Subject: [PATCH] feat(mobile): expose reading settings from Me
---
.../app-expo/src/navigation/RootNavigator.tsx | 3 +
.../app-expo/src/screens/ProfileScreen.tsx | 6 ++
.../screens/reader/ReaderSettingsPanel.tsx | 97 ++++++++++++++-----
.../settings/ReadingSettingsScreen.tsx | 38 ++++++++
.../reading-settings-navigation.test.ts | 21 ++++
5 files changed, 140 insertions(+), 25 deletions(-)
create mode 100644 packages/app-expo/src/screens/settings/ReadingSettingsScreen.tsx
create mode 100644 packages/app-expo/src/screens/settings/reading-settings-navigation.test.ts
diff --git a/packages/app-expo/src/navigation/RootNavigator.tsx b/packages/app-expo/src/navigation/RootNavigator.tsx
index d720f534b..dcae57c35 100644
--- a/packages/app-expo/src/navigation/RootNavigator.tsx
+++ b/packages/app-expo/src/navigation/RootNavigator.tsx
@@ -14,6 +14,7 @@ import AppearanceSettingsScreen from "@/screens/settings/AppearanceSettingsScree
import FeedbackDetailScreen from "@/screens/settings/FeedbackDetailScreen";
import FeedbackScreen from "@/screens/settings/FeedbackScreen";
import FontSettingsScreen from "@/screens/settings/FontSettingsScreen";
+import ReadingSettingsScreen from "@/screens/settings/ReadingSettingsScreen";
import SyncSettingsScreen from "@/screens/settings/SyncSettingsScreen";
import TTSSettingsScreen from "@/screens/settings/TTSSettingsScreen";
import TranslationSettingsScreen from "@/screens/settings/TranslationSettingsScreen";
@@ -37,6 +38,7 @@ export type RootStackParamList = {
Skills: undefined;
VectorModelSettings: undefined;
AppearanceSettings: undefined;
+ ReadingSettings: undefined;
AISettings: undefined;
TTSSettings: undefined;
TranslationSettings: undefined;
@@ -102,6 +104,7 @@ export function RootNavigator() {
options={{ animation: "slide_from_right" }}
/>
+
diff --git a/packages/app-expo/src/screens/ProfileScreen.tsx b/packages/app-expo/src/screens/ProfileScreen.tsx
index bc19ebce0..9a8ee813e 100644
--- a/packages/app-expo/src/screens/ProfileScreen.tsx
+++ b/packages/app-expo/src/screens/ProfileScreen.tsx
@@ -73,6 +73,7 @@ type ProfileMenuIcon = (props: {
type ProfileMenuRoute = Extract<
keyof RootStackParamList,
| "AppearanceSettings"
+ | "ReadingSettings"
| "FontSettings"
| "SyncSettings"
| "AISettings"
@@ -429,6 +430,11 @@ export function ProfileScreen() {
label: t("settings.general", "通用"),
route: "AppearanceSettings" as const,
},
+ {
+ icon: BookOpenIcon,
+ label: t("settings.reading_title", "Reading"),
+ route: "ReadingSettings" as const,
+ },
{
icon: TypeIcon,
label: t("fonts.title", "字体"),
diff --git a/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx b/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
index f3ae11e95..67204cb35 100644
--- a/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
+++ b/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
@@ -5,24 +5,62 @@ import { XIcon } from "@/components/ui/Icon";
import { useResponsiveLayout } from "@/hooks/use-responsive-layout";
import { useColors } from "@/styles/theme";
import type { ReadSettings } from "@readany/core/types";
-import { ActivityIndicator, Modal, Platform, Pressable, ScrollView, Text, TouchableOpacity, View } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { type ReactNode, useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
+import {
+ ActivityIndicator,
+ Modal,
+ Platform,
+ Pressable,
+ ScrollView,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
import { makeStyles } from "./reader-styles";
import { useFontStore } from "@readany/core/stores";
import { useRubyStore, type RubyMode } from "@readany/core/stores/ruby-store";
-import { useCallback, useState } from "react";
interface Props {
visible: boolean;
readSettings: ReadSettings;
bookId?: string;
+ embedded?: boolean;
onClose: () => void;
onUpdateSetting: (key: K, value: ReadSettings[K]) => void;
onRubyModeChange?: (mode: RubyMode) => void;
}
-export function ReaderSettingsPanel({ visible, readSettings, bookId, onClose, onUpdateSetting, onRubyModeChange }: Props) {
+function ReaderSettingsContainer({
+ children,
+ embedded,
+ visible,
+ onClose,
+}: {
+ children: ReactNode;
+ embedded: boolean;
+ visible: boolean;
+ onClose: () => void;
+}) {
+ if (embedded) return {children};
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function ReaderSettingsPanel({
+ visible,
+ readSettings,
+ bookId,
+ embedded = false,
+ onClose,
+ onUpdateSetting,
+ onRubyModeChange,
+}: Props) {
const colors = useColors();
const s = makeStyles(colors);
const insets = useSafeAreaInsets();
@@ -47,28 +85,37 @@ export function ReaderSettingsPanel({ visible, readSettings, bookId, onClose, on
} = readSettings;
return (
-
-
+
+ {!embedded && }
-
- {t("reader.settings", "阅读设置")}
-
-
-
-
+ {!embedded && (
+
+ {t("reader.settings", "阅读设置")}
+
+
+
+
+ )}
{/* Font Size */}
@@ -351,7 +398,7 @@ export function ReaderSettingsPanel({ visible, readSettings, bookId, onClose, on
)}
-
+
);
}
diff --git a/packages/app-expo/src/screens/settings/ReadingSettingsScreen.tsx b/packages/app-expo/src/screens/settings/ReadingSettingsScreen.tsx
new file mode 100644
index 000000000..de493b196
--- /dev/null
+++ b/packages/app-expo/src/screens/settings/ReadingSettingsScreen.tsx
@@ -0,0 +1,38 @@
+import { ReaderSettingsPanel } from "@/screens/reader/ReaderSettingsPanel";
+import { useSettingsStore } from "@/stores";
+import { useColors } from "@/styles/theme";
+import type { ReadSettings } from "@readany/core/types";
+import { useCallback } from "react";
+import { useTranslation } from "react-i18next";
+import { SafeAreaView } from "react-native-safe-area-context";
+import { SettingsHeader } from "./SettingsHeader";
+
+export default function ReadingSettingsScreen() {
+ const { t } = useTranslation();
+ const colors = useColors();
+ const readSettings = useSettingsStore((state) => state.readSettings);
+ const updateReadSettings = useSettingsStore((state) => state.updateReadSettings);
+
+ const updateSetting = useCallback(
+ (key: K, value: ReadSettings[K]) => {
+ updateReadSettings({ [key]: value } as Partial);
+ },
+ [updateReadSettings],
+ );
+
+ return (
+
+
+ undefined}
+ onUpdateSetting={updateSetting}
+ />
+
+ );
+}
diff --git a/packages/app-expo/src/screens/settings/reading-settings-navigation.test.ts b/packages/app-expo/src/screens/settings/reading-settings-navigation.test.ts
new file mode 100644
index 000000000..d02bd8c77
--- /dev/null
+++ b/packages/app-expo/src/screens/settings/reading-settings-navigation.test.ts
@@ -0,0 +1,21 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { describe, expect, it } from "vitest";
+
+const root = resolve(import.meta.dirname, "../../../../..");
+const read = (path: string) => readFileSync(resolve(root, path), "utf8");
+
+describe("Reading settings navigation", () => {
+ it("exposes the shared reader settings from Me", () => {
+ const profile = read("packages/app-expo/src/screens/ProfileScreen.tsx");
+ const navigator = read("packages/app-expo/src/navigation/RootNavigator.tsx");
+ const screen = read("packages/app-expo/src/screens/settings/ReadingSettingsScreen.tsx");
+
+ expect(profile).toContain('route: "ReadingSettings"');
+ expect(navigator).toContain("ReadingSettings: undefined");
+ expect(navigator).toContain('name="ReadingSettings"');
+ expect(screen).toContain("