Chore/tv interface scaling (#1591)

Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
lance chant
2026-05-24 12:55:28 +02:00
committed by GitHub
parent 52ad1a06e1
commit 4b577b8111
4 changed files with 50 additions and 10 deletions

View File

@@ -768,7 +768,7 @@ export default function page() {
currentPlayStateInfo() as PlaybackProgressInfo, currentPlayStateInfo() as PlaybackProgressInfo,
); );
} }
if (!Platform.isTV) await activateKeepAwakeAsync(); await activateKeepAwakeAsync();
return; return;
} }
@@ -781,7 +781,7 @@ export default function page() {
currentPlayStateInfo() as PlaybackProgressInfo, currentPlayStateInfo() as PlaybackProgressInfo,
); );
} }
if (!Platform.isTV) await deactivateKeepAwake(); await deactivateKeepAwake();
return; return;
} }

View File

@@ -14,6 +14,7 @@ import {
} from "react"; } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Pressable,
StyleSheet, StyleSheet,
TVFocusGuideView, TVFocusGuideView,
useWindowDimensions, useWindowDimensions,
@@ -278,6 +279,9 @@ export const Controls: FC<Props> = ({
null, null,
); );
// Ref for the invisible focus-stealing overlay (prevents hidden buttons from receiving select events)
const focusOverlayRef = useRef<View>(null);
const audioTracks = useMemo(() => { const audioTracks = useMemo(() => {
return mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") ?? []; return mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") ?? [];
}, [mediaSource]); }, [mediaSource]);
@@ -908,6 +912,19 @@ export const Controls: FC<Props> = ({
setFocusPlayButton(false); setFocusPlayButton(false);
}, [setShowControls]); }, [setShowControls]);
// When controls hide (and no skip/countdown overlay is visible), move focus
// to the invisible overlay so hidden buttons can't receive select events.
useEffect(() => {
if (!showControls && !isSkipOrCountdownVisible) {
// Small delay to let the controls fade-out animation start and
// the focus engine settle before stealing focus
const t = setTimeout(() => {
focusOverlayRef.current?.focus();
}, 100);
return () => clearTimeout(t);
}
}, [showControls, isSkipOrCountdownVisible]);
const handleBack = useCallback(() => { const handleBack = useCallback(() => {
router.back(); router.back();
}, [router]); }, [router]);
@@ -1025,6 +1042,24 @@ export const Controls: FC<Props> = ({
pointerEvents='none' pointerEvents='none'
/> />
{/* Invisible overlay that steals focus when controls are hidden.
Prevents hidden control buttons from receiving select/enter events
from the TV remote. Pressing center button here toggles play/pause. */}
<Pressable
ref={focusOverlayRef}
style={styles.focusStealingOverlay}
pointerEvents={
showControls || isSkipOrCountdownVisible ? "none" : "auto"
}
focusable={!showControls && !isSkipOrCountdownVisible}
hasTVPreferredFocus={!showControls && !isSkipOrCountdownVisible}
onPress={() => {
togglePlay();
setShowControls(true);
setFocusPlayButton(true);
}}
/>
{getTechnicalInfo && ( {getTechnicalInfo && (
<TechnicalInfoOverlay <TechnicalInfoOverlay
showControls={showControls} showControls={showControls}
@@ -1177,6 +1212,7 @@ export const Controls: FC<Props> = ({
<Animated.View <Animated.View
style={[styles.bottomContainer, bottomAnimatedStyle]} style={[styles.bottomContainer, bottomAnimatedStyle]}
pointerEvents={showControls ? "auto" : "none"} pointerEvents={showControls ? "auto" : "none"}
focusable={showControls}
> >
<View <View
style={[ style={[
@@ -1379,6 +1415,10 @@ const styles = StyleSheet.create({
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
backgroundColor: "rgba(0, 0, 0, 0.4)", backgroundColor: "rgba(0, 0, 0, 0.4)",
}, },
focusStealingOverlay: {
...StyleSheet.absoluteFillObject,
zIndex: 1,
},
bottomContainer: { bottomContainer: {
position: "absolute", position: "absolute",
bottom: 0, bottom: 0,

View File

@@ -79,10 +79,10 @@ export const TVAnimation = {
* Applied to poster sizes and gaps. * Applied to poster sizes and gaps.
*/ */
const sizeScaleMultipliers: Record<TVTypographyScale, number> = { const sizeScaleMultipliers: Record<TVTypographyScale, number> = {
[TVTypographyScale.Small]: 0.9, [TVTypographyScale.Small]: 0.63,
[TVTypographyScale.Default]: 1.0, [TVTypographyScale.Default]: 0.7,
[TVTypographyScale.Large]: 1.1, [TVTypographyScale.Large]: 0.77,
[TVTypographyScale.ExtraLarge]: 1.2, [TVTypographyScale.ExtraLarge]: 0.84,
}; };
// ============================================================================= // =============================================================================

View File

@@ -37,10 +37,10 @@ export type TVTypographyKey = keyof typeof TVTypography;
// ============================================================================= // =============================================================================
const scaleMultipliers: Record<TVTypographyScale, number> = { const scaleMultipliers: Record<TVTypographyScale, number> = {
[TVTypographyScale.Small]: 0.85, [TVTypographyScale.Small]: 0.6,
[TVTypographyScale.Default]: 1.0, [TVTypographyScale.Default]: 0.7,
[TVTypographyScale.Large]: 1.2, [TVTypographyScale.Large]: 0.84,
[TVTypographyScale.ExtraLarge]: 1.4, [TVTypographyScale.ExtraLarge]: 0.98,
}; };
// ============================================================================= // =============================================================================