diff --git a/apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx b/apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx index c0e46d248e04..c01c4980ae92 100644 --- a/apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx +++ b/apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx @@ -35,6 +35,7 @@ export function FontSizeSliderRow(props: { readonly step: number; readonly value: number; readonly onChange: (value: number) => void; + readonly onPreviewChange?: (value: number | null) => void; }) { const icon = useThemeColor("--color-icon"); const iconMuted = String(useThemeColor("--color-icon-muted")); @@ -48,21 +49,30 @@ export function FontSizeSliderRow(props: { const fraction = (value - min) / (max - min); const progress = useSharedValue(clampFraction(fraction)); + const previewValue = useSharedValue(value); const trackWidth = useSharedValue(0); const dragging = useSharedValue(false); useEffect(() => { if (!dragging.value) { progress.value = withTiming(clampFraction(fraction), SNAP_ANIMATION); + previewValue.value = value; } - }, [dragging, fraction, progress]); + }, [dragging, fraction, previewValue, progress, value]); + + const preview = useCallback((next: number | null) => { + latest.current.onPreviewChange?.(next); + }, []); const commit = useCallback((next: number) => { - if (next === latest.current.value) { + const current = latest.current; + if (next === current.value) { + current.onPreviewChange?.(null); return; } Haptics.selectionAsync().catch(() => undefined); - latest.current.onChange(next); + current.onChange(next); + current.onPreviewChange?.(null); }, []); const gesture = useMemo(() => { @@ -96,29 +106,52 @@ export function FontSizeSliderRow(props: { dragging.value = true; const f = fractionAt(event.x); progress.value = f; - runOnJS(commit)(valueAtFraction(f)); + const next = valueAtFraction(f); + if (next !== previewValue.value) { + previewValue.value = next; + runOnJS(preview)(next); + } }) - .onFinalize(() => { + .onFinalize((_event, success) => { if (!dragging.value) { return; } dragging.value = false; - progress.value = withTiming( - fractionOfValue(valueAtFraction(progress.value)), - SNAP_ANIMATION, - ); + if (!success) { + previewValue.value = value; + progress.value = withTiming(fractionOfValue(value), SNAP_ANIMATION); + runOnJS(preview)(null); + return; + } + const next = valueAtFraction(progress.value); + previewValue.value = next; + progress.value = withTiming(fractionOfValue(next), SNAP_ANIMATION); + runOnJS(commit)(next); }); const tap = Gesture.Tap() .enabled(!disabled) .onEnd((event) => { const next = valueAtFraction(fractionAt(event.x)); + previewValue.value = next; progress.value = withTiming(fractionOfValue(next), SNAP_ANIMATION); runOnJS(commit)(next); }); return Gesture.Race(pan, tap); - }, [commit, disabled, dragging, max, min, progress, step, trackWidth]); + }, [ + commit, + disabled, + dragging, + max, + min, + preview, + previewValue, + progress, + step, + trackWidth, + value, + ]); const fillStyle = useAnimatedStyle(() => ({ width: THUMB_SIZE / 2 + progress.value * Math.max(0, trackWidth.value - THUMB_SIZE), diff --git a/apps/mobile/src/features/settings/appearance/sections/CodeAppearanceSection.tsx b/apps/mobile/src/features/settings/appearance/sections/CodeAppearanceSection.tsx index bb4bb4d0a8fe..c0a3b44e690d 100644 --- a/apps/mobile/src/features/settings/appearance/sections/CodeAppearanceSection.tsx +++ b/apps/mobile/src/features/settings/appearance/sections/CodeAppearanceSection.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { CODE_FONT_SIZE_STEP, @@ -17,6 +17,8 @@ import { FontSizeSliderRow } from "../components/FontSizeSliderRow"; export function CodeAppearanceSection() { const { isReady, appearance, setCodeFontSize, setCodeWordBreak } = useAppearancePreferences(); const custom = appearance.isCodeFontSizeCustom; + const [previewFontSize, setPreviewFontSize] = useState(null); + const fontSize = previewFontSize ?? appearance.codeFontSize; const handleToggleCustom = useCallback( (enabled: boolean) => { @@ -27,10 +29,7 @@ export function CodeAppearanceSection() { return ( - + ) : null} (null); + const fontSize = previewFontSize ?? appearance.terminalFontSize; const handleToggleCustom = useCallback( (enabled: boolean) => { @@ -27,7 +29,7 @@ export function TerminalAppearanceSection() { return ( - + ) : null} diff --git a/apps/mobile/src/features/settings/appearance/sections/TextAppearanceSection.tsx b/apps/mobile/src/features/settings/appearance/sections/TextAppearanceSection.tsx index 6617a75720c4..96a5daa58c6e 100644 --- a/apps/mobile/src/features/settings/appearance/sections/TextAppearanceSection.tsx +++ b/apps/mobile/src/features/settings/appearance/sections/TextAppearanceSection.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; + import { BASE_FONT_SIZE_STEP, MAX_BASE_FONT_SIZE, @@ -13,10 +15,12 @@ import { FontSizeSliderRow } from "../components/FontSizeSliderRow"; export function TextAppearanceSection() { const { isReady, appearance, setBaseFontSize } = useAppearancePreferences(); + const [previewFontSize, setPreviewFontSize] = useState(null); + const fontSize = previewFontSize ?? appearance.baseFontSize; return ( - + );