aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/mobile/app/dashboard/settings/reader-settings.tsx69
-rw-r--r--apps/mobile/components/reader/ReaderPreview.tsx117
2 files changed, 170 insertions, 16 deletions
diff --git a/apps/mobile/app/dashboard/settings/reader-settings.tsx b/apps/mobile/app/dashboard/settings/reader-settings.tsx
index 6c522557..30ad54b9 100644
--- a/apps/mobile/app/dashboard/settings/reader-settings.tsx
+++ b/apps/mobile/app/dashboard/settings/reader-settings.tsx
@@ -1,7 +1,11 @@
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { Pressable, ScrollView, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { runOnJS, useSharedValue } from "react-native-reanimated";
+import {
+ ReaderPreview,
+ ReaderPreviewRef,
+} from "@/components/reader/ReaderPreview";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
import { Text } from "@/components/ui/Text";
@@ -59,6 +63,40 @@ export default function ReaderSettingsPage() {
const [displayLineHeight, setDisplayLineHeight] =
useState(effectiveLineHeight);
+ // Refs to track latest display values (avoids stale closures in callbacks)
+ const displayFontSizeRef = useRef(displayFontSize);
+ displayFontSizeRef.current = displayFontSize;
+ const displayLineHeightRef = useRef(displayLineHeight);
+ displayLineHeightRef.current = displayLineHeight;
+
+ // Ref for the WebView preview component
+ const previewRef = useRef<ReaderPreviewRef>(null);
+
+ // Functions to update preview styles via IPC (called from worklets via runOnJS)
+ const updatePreviewFontSize = useCallback(
+ (fontSize: number) => {
+ setDisplayFontSize(fontSize);
+ previewRef.current?.updateStyles(
+ effectiveFontFamily,
+ fontSize,
+ displayLineHeightRef.current,
+ );
+ },
+ [effectiveFontFamily],
+ );
+
+ const updatePreviewLineHeight = useCallback(
+ (lineHeight: number) => {
+ setDisplayLineHeight(lineHeight);
+ previewRef.current?.updateStyles(
+ effectiveFontFamily,
+ displayFontSizeRef.current,
+ lineHeight,
+ );
+ },
+ [effectiveFontFamily],
+ );
+
// Sync slider progress and display values with effective settings
useEffect(() => {
fontSizeProgress.value = effectiveFontSize;
@@ -72,6 +110,12 @@ export default function ReaderSettingsPage() {
const handleFontFamilyChange = (fontFamily: ZReaderFontFamily) => {
updateLocal({ fontFamily });
+ // Update preview immediately with new font family
+ previewRef.current?.updateStyles(
+ fontFamily,
+ displayFontSize,
+ displayLineHeight,
+ );
};
const handleFontSizeChange = (value: number) => {
@@ -158,7 +202,7 @@ export default function ReaderSettingsPage() {
renderBubble={() => null}
onValueChange={(value) => {
"worklet";
- runOnJS(setDisplayFontSize)(Math.round(value));
+ runOnJS(updatePreviewFontSize)(Math.round(value));
}}
onSlidingComplete={(value) =>
handleFontSizeChange(Math.round(value))
@@ -191,7 +235,7 @@ export default function ReaderSettingsPage() {
renderBubble={() => null}
onValueChange={(value) => {
"worklet";
- runOnJS(setDisplayLineHeight)(Math.round(value * 10) / 10);
+ runOnJS(updatePreviewLineHeight)(Math.round(value * 10) / 10);
}}
onSlidingComplete={handleLineHeightChange}
/>
@@ -207,19 +251,12 @@ export default function ReaderSettingsPage() {
<Text className="mb-2 px-1 text-sm font-medium text-muted-foreground">
Preview
</Text>
- <View className="w-full rounded-lg bg-card px-4 py-3">
- <Text
- style={{
- fontFamily: MOBILE_FONT_FAMILIES[effectiveFontFamily],
- fontSize: effectiveFontSize,
- lineHeight: effectiveFontSize * effectiveLineHeight,
- }}
- className="text-foreground"
- >
- The quick brown fox jumps over the lazy dog. Pack my box with five
- dozen liquor jugs. How vexingly quick daft zebras jump!
- </Text>
- </View>
+ <ReaderPreview
+ ref={previewRef}
+ initialFontFamily={effectiveFontFamily}
+ initialFontSize={effectiveFontSize}
+ initialLineHeight={effectiveLineHeight}
+ />
</View>
<Divider orientation="horizontal" className="my-2 w-full" />
diff --git a/apps/mobile/components/reader/ReaderPreview.tsx b/apps/mobile/components/reader/ReaderPreview.tsx
new file mode 100644
index 00000000..c091bdbc
--- /dev/null
+++ b/apps/mobile/components/reader/ReaderPreview.tsx
@@ -0,0 +1,117 @@
+import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
+import { View } from "react-native";
+import WebView from "react-native-webview";
+import { WEBVIEW_FONT_FAMILIES } from "@/lib/readerSettings";
+import { useColorScheme } from "@/lib/useColorScheme";
+
+import { ZReaderFontFamily } from "@karakeep/shared/types/users";
+
+const PREVIEW_TEXT =
+ "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. How vexingly quick daft zebras jump!";
+
+export interface ReaderPreviewRef {
+ updateStyles: (
+ fontFamily: ZReaderFontFamily,
+ fontSize: number,
+ lineHeight: number,
+ ) => void;
+}
+
+interface ReaderPreviewProps {
+ initialFontFamily: ZReaderFontFamily;
+ initialFontSize: number;
+ initialLineHeight: number;
+}
+
+export const ReaderPreview = forwardRef<ReaderPreviewRef, ReaderPreviewProps>(
+ ({ initialFontFamily, initialFontSize, initialLineHeight }, ref) => {
+ const webViewRef = useRef<WebView>(null);
+ const { isDarkColorScheme: isDark } = useColorScheme();
+
+ const fontFamily = WEBVIEW_FONT_FAMILIES[initialFontFamily];
+ const textColor = isDark ? "#e5e7eb" : "#374151";
+ const bgColor = isDark ? "#000000" : "#ffffff";
+
+ useImperativeHandle(ref, () => ({
+ updateStyles: (
+ newFontFamily: ZReaderFontFamily,
+ newFontSize: number,
+ newLineHeight: number,
+ ) => {
+ const cssFontFamily = WEBVIEW_FONT_FAMILIES[newFontFamily];
+ webViewRef.current?.injectJavaScript(`
+ window.updateStyles("${cssFontFamily}", ${newFontSize}, ${newLineHeight});
+ true;
+ `);
+ },
+ }));
+
+ // Update colors when theme changes
+ useEffect(() => {
+ webViewRef.current?.injectJavaScript(`
+ document.body.style.color = "${textColor}";
+ document.body.style.background = "${bgColor}";
+ true;
+ `);
+ }, [isDark, textColor, bgColor]);
+
+ const html = `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+ html, body {
+ height: 100%;
+ overflow: hidden;
+ }
+ body {
+ font-family: ${fontFamily};
+ font-size: ${initialFontSize}px;
+ line-height: ${initialLineHeight};
+ color: ${textColor};
+ background: ${bgColor};
+ padding: 16px;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ }
+ </style>
+ <script>
+ window.updateStyles = function(fontFamily, fontSize, lineHeight) {
+ document.body.style.fontFamily = fontFamily;
+ document.body.style.fontSize = fontSize + 'px';
+ document.body.style.lineHeight = lineHeight;
+ };
+ </script>
+ </head>
+ <body>
+ ${PREVIEW_TEXT}
+ </body>
+ </html>
+ `;
+
+ return (
+ <View className="h-32 w-full overflow-hidden rounded-lg">
+ <WebView
+ ref={webViewRef}
+ originWhitelist={["*"]}
+ source={{ html }}
+ style={{
+ flex: 1,
+ backgroundColor: bgColor,
+ }}
+ scrollEnabled={false}
+ showsVerticalScrollIndicator={false}
+ showsHorizontalScrollIndicator={false}
+ />
+ </View>
+ );
+ },
+);
+
+ReaderPreview.displayName = "ReaderPreview";