From 7f4202afd73105b850498b55ad66922b3505f0e3 Mon Sep 17 00:00:00 2001
From: Evan Simkowitz <esimkowitz@users.noreply.github.com>
Date: Sun, 14 Dec 2025 16:39:25 -0800
Subject: feat: Add unified reader settings with local overrides (#2230)

* Add initial impl

* fix some format inconsistencies, add indicator in user settings when local is out of sync

* Fix sliders in user settings, unify constants and formatting

* address CodeRabbit suggestions

* add mobile implementation

* address coderabbit nitpicks

* fix responsiveness of the reader settings popover

* Move more of the web UI strings to i18n

* update translations for more coverage

* remove duplicate logic/definitions

* fix android font family

* add shared reading setting hook between web and mobile

* unify reader settings context for both web and mobile

* remove unused export

* address coderabbit suggestions

* fix tests
---
 apps/mobile/lib/readerSettings.tsx | 93 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 apps/mobile/lib/readerSettings.tsx

(limited to 'apps/mobile/lib/readerSettings.tsx')

diff --git a/apps/mobile/lib/readerSettings.tsx b/apps/mobile/lib/readerSettings.tsx
new file mode 100644
index 00000000..9a3fc835
--- /dev/null
+++ b/apps/mobile/lib/readerSettings.tsx
@@ -0,0 +1,93 @@
+import { ReactNode, useCallback } from "react";
+import { Platform } from "react-native";
+
+import {
+  ReaderSettingsProvider as BaseReaderSettingsProvider,
+  useReaderSettingsContext,
+} from "@karakeep/shared-react/hooks/reader-settings";
+import { ReaderSettingsPartial } from "@karakeep/shared/types/readers";
+import { ZReaderFontFamily } from "@karakeep/shared/types/users";
+
+import { useSettings } from "./settings";
+
+// Mobile-specific font families for native Text components
+// On Android, use generic font family names: "serif", "sans-serif", "monospace"
+// On iOS, use specific font names like "Georgia" and "Courier"
+// Note: undefined means use the system default font
+export const MOBILE_FONT_FAMILIES: Record<
+  ZReaderFontFamily,
+  string | undefined
+> = Platform.select({
+  android: {
+    serif: "serif",
+    sans: undefined,
+    mono: "monospace",
+  },
+  default: {
+    serif: "Georgia",
+    sans: undefined,
+    mono: "Courier",
+  },
+})!;
+
+// Font families for WebView HTML content (CSS font stacks)
+export const WEBVIEW_FONT_FAMILIES: Record<ZReaderFontFamily, string> = {
+  serif: "Georgia, 'Times New Roman', serif",
+  sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
+  mono: "ui-monospace, Menlo, Monaco, 'Courier New', monospace",
+} as const;
+
+/**
+ * Mobile-specific provider for reader settings.
+ * Wraps the shared provider with mobile storage callbacks.
+ */
+export function ReaderSettingsProvider({ children }: { children: ReactNode }) {
+  // Read from zustand store directly to keep callback stable (empty deps).
+  const getLocalOverrides = useCallback((): ReaderSettingsPartial => {
+    const currentSettings = useSettings.getState().settings.settings;
+    return {
+      fontSize: currentSettings.readerFontSize,
+      lineHeight: currentSettings.readerLineHeight,
+      fontFamily: currentSettings.readerFontFamily,
+    };
+  }, []);
+
+  const saveLocalOverrides = useCallback((overrides: ReaderSettingsPartial) => {
+    const currentSettings = useSettings.getState().settings.settings;
+    // Remove reader settings keys first, then add back only defined ones
+    const {
+      readerFontSize: _fs,
+      readerLineHeight: _lh,
+      readerFontFamily: _ff,
+      ...rest
+    } = currentSettings;
+
+    const newSettings = { ...rest };
+    if (overrides.fontSize !== undefined) {
+      (newSettings as typeof currentSettings).readerFontSize =
+        overrides.fontSize;
+    }
+    if (overrides.lineHeight !== undefined) {
+      (newSettings as typeof currentSettings).readerLineHeight =
+        overrides.lineHeight;
+    }
+    if (overrides.fontFamily !== undefined) {
+      (newSettings as typeof currentSettings).readerFontFamily =
+        overrides.fontFamily;
+    }
+
+    useSettings.getState().setSettings(newSettings);
+  }, []);
+
+  return (
+    <BaseReaderSettingsProvider
+      getLocalOverrides={getLocalOverrides}
+      saveLocalOverrides={saveLocalOverrides}
+    >
+      {children}
+    </BaseReaderSettingsProvider>
+  );
+}
+
+// Re-export the context hook as useReaderSettings for mobile consumers
+export { useReaderSettingsContext as useReaderSettings };
-- 
cgit v1.2.3-70-g09d2

