feat: add Android typography fix for Capacitor apps and integrate into ClientNonUIFeatures
All checks were successful
Build / increment-version (push) Successful in 6s
Build / build-android (push) Successful in 4m30s
Build / create-release (push) Successful in 14s

This commit is contained in:
2026-07-09 03:23:22 +10:00
parent 5c18e38314
commit e27ec9d7ab
3 changed files with 54 additions and 0 deletions

View File

@@ -35,9 +35,11 @@ import {
isTauri, isTauri,
isElectron, isElectron,
isCapacitorNative, isCapacitorNative,
applyAndroidTypographyFix,
sendNotification, sendNotification,
setupNotificationTapListener, setupNotificationTapListener,
} from '../../utils/tauri'; } from '../../utils/tauri';
import '../../styles/android-typography.css';
import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api'; import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api';
import { import {
startBackgroundSync, startBackgroundSync,
@@ -84,6 +86,14 @@ function EmojiStyleFeature() {
return null; return null;
} }
function AndroidTypographyFeature() {
useEffect(() => {
applyAndroidTypographyFix();
}, []);
return null;
}
function PageZoomFeature() { function PageZoomFeature() {
const [pageZoom] = useSetting(settingsAtom, 'pageZoom'); const [pageZoom] = useSetting(settingsAtom, 'pageZoom');
@@ -684,6 +694,7 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
return ( return (
<> <>
<EmojiStyleFeature /> <EmojiStyleFeature />
<AndroidTypographyFeature />
<PageZoomFeature /> <PageZoomFeature />
<FaviconUpdater /> <FaviconUpdater />
<InviteNotifications /> <InviteNotifications />

View File

@@ -0,0 +1,8 @@
/*
* Android WebView renders Inter Variable with broken word/letter spacing.
* Applied when html.android-capacitor is set (see applyAndroidTypographyFix).
*/
html.android-capacitor body {
letter-spacing: normal;
word-spacing: normal;
}

View File

@@ -248,6 +248,41 @@ export const isAndroid = (): boolean => {
return ua.includes('android'); return ua.includes('android');
}; };
/** Returns true when running as a Capacitor app on Android. */
export const isCapacitorAndroid = (): boolean => {
if (typeof window === 'undefined') return false;
const cap = (window as { Capacitor?: { isNativePlatform?: () => boolean; getPlatform?: () => string } }).Capacitor;
return Boolean(cap?.isNativePlatform?.() && cap?.getPlatform?.() === 'android');
};
/**
* Android WebView mishandles Inter Variable — swap to static Inter / system fonts.
*/
export const applyAndroidTypographyFix = (): void => {
if (!isCapacitorAndroid()) return;
const root = document.documentElement;
root.classList.add('android-capacitor');
void (async () => {
try {
await import('@fontsource/inter/400.css');
await import('@fontsource/inter/500.css');
await import('@fontsource/inter/600.css');
root.style.setProperty(
'--font-secondary',
`'Inter', system-ui, Roboto, 'Noto Sans', var(--font-emoji), sans-serif`
);
} catch (err) {
console.warn('[applyAndroidTypographyFix] Falling back to system font:', err);
root.style.setProperty(
'--font-secondary',
`system-ui, Roboto, 'Noto Sans', var(--font-emoji), sans-serif`
);
}
})();
};
/** /**
* Apply safe area insets for mobile devices * Apply safe area insets for mobile devices
* On Android, CSS env() may not work, so we apply fallback padding * On Android, CSS env() may not work, so we apply fallback padding