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("