diff options
| author | Evan Simkowitz <esimkowitz@users.noreply.github.com> | 2026-01-18 05:32:38 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-18 13:32:38 +0000 |
| commit | 1b98014d6cb0e3eb824d58ccbd35f39864e6ec88 (patch) | |
| tree | 694403cfd7cfe1aa91a6f05af28fc65846a2b85e /apps/mobile | |
| parent | 789188b522c0570b092f588209c4f0d270b703e3 (diff) | |
| download | karakeep-1b98014d6cb0e3eb824d58ccbd35f39864e6ec88.tar.zst | |
fix(mobile): Reader settings preview on mobile matches reader view formatting (#2365)
* fix: Reader settings preview on mobile matches reader view formatting
* address comments
Diffstat (limited to 'apps/mobile')
| -rw-r--r-- | apps/mobile/app/dashboard/settings/reader-settings.tsx | 69 | ||||
| -rw-r--r-- | apps/mobile/components/reader/ReaderPreview.tsx | 117 |
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"; |
