aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/lib
diff options
context:
space:
mode:
authorEvan Simkowitz <esimkowitz@users.noreply.github.com>2025-12-14 16:39:25 -0800
committerGitHub <noreply@github.com>2025-12-15 00:39:25 +0000
commit7f4202afd73105b850498b55ad66922b3505f0e3 (patch)
treea45f9f1b2599f4c9925e36dc51563b06ba6854ac /apps/mobile/lib
parent6db14ac492cd5d9e26d0d986513771f14faa7fd0 (diff)
downloadkarakeep-7f4202afd73105b850498b55ad66922b3505f0e3.tar.zst
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
Diffstat (limited to 'apps/mobile/lib')
-rw-r--r--apps/mobile/lib/providers.tsx5
-rw-r--r--apps/mobile/lib/readerSettings.tsx93
-rw-r--r--apps/mobile/lib/settings.ts8
3 files changed, 105 insertions, 1 deletions
diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx
index 938b8aeb..36ed7e71 100644
--- a/apps/mobile/lib/providers.tsx
+++ b/apps/mobile/lib/providers.tsx
@@ -4,6 +4,7 @@ import { ToastProvider } from "@/components/ui/Toast";
import { TRPCProvider } from "@karakeep/shared-react/providers/trpc-provider";
+import { ReaderSettingsProvider } from "./readerSettings";
import useAppSettings from "./settings";
export function Providers({ children }: { children: React.ReactNode }) {
@@ -20,7 +21,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
return (
<TRPCProvider settings={settings}>
- <ToastProvider>{children}</ToastProvider>
+ <ReaderSettingsProvider>
+ <ToastProvider>{children}</ToastProvider>
+ </ReaderSettingsProvider>
</TRPCProvider>
);
}
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 };
diff --git a/apps/mobile/lib/settings.ts b/apps/mobile/lib/settings.ts
index 40a33976..745c778d 100644
--- a/apps/mobile/lib/settings.ts
+++ b/apps/mobile/lib/settings.ts
@@ -2,6 +2,8 @@ import * as SecureStore from "expo-secure-store";
import { z } from "zod";
import { create } from "zustand";
+import { zReaderFontFamilySchema } from "@karakeep/shared/types/users";
+
const SETTING_NAME = "settings";
const zSettingsSchema = z.object({
@@ -16,6 +18,10 @@ const zSettingsSchema = z.object({
.default("reader"),
showNotes: z.boolean().optional().default(false),
customHeaders: z.record(z.string(), z.string()).optional().default({}),
+ // Reader settings (local device overrides)
+ readerFontSize: z.number().int().min(12).max(24).optional(),
+ readerLineHeight: z.number().min(1.2).max(2.5).optional(),
+ readerFontFamily: zReaderFontFamilySchema.optional(),
});
export type Settings = z.infer<typeof zSettingsSchema>;
@@ -73,3 +79,5 @@ export default function useAppSettings() {
return { ...settings, setSettings, load };
}
+
+export { useSettings };