From 076573e673b7c8d2534032d79f44247f50377e9d Mon Sep 17 00:00:00 2001 From: Alex Kim Date: Tue, 2 Jun 2026 20:00:16 +1000 Subject: [PATCH] Refactor to remove tertiary check for safe areas --- components/chapters/ChapterList.tsx | 9 ++------- .../video-player/controls/BottomControls.tsx | 14 +++++--------- .../video-player/controls/CenterControls.tsx | 8 ++++---- .../video-player/controls/EpisodeList.tsx | 15 +++++---------- .../video-player/controls/HeaderControls.tsx | 13 +++++-------- .../controls/TechnicalInfoOverlay.tsx | 14 ++++---------- hooks/useControlsSafeAreaInsets.ts | 18 ++++++++++++++++++ 7 files changed, 43 insertions(+), 48 deletions(-) create mode 100644 hooks/useControlsSafeAreaInsets.ts diff --git a/components/chapters/ChapterList.tsx b/components/chapters/ChapterList.tsx index 98d058524..e44332095 100644 --- a/components/chapters/ChapterList.tsx +++ b/components/chapters/ChapterList.tsx @@ -9,10 +9,9 @@ import type { ChapterInfo } from "@jellyfin/sdk/lib/generated-client/models"; import { memo, useEffect, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { FlatList, Modal, Pressable, StyleSheet, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import { Colors } from "@/constants/Colors"; -import { useSettings } from "@/utils/atoms/settings"; +import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets"; import { type ChapterEntry, chapterStartsMs, @@ -31,7 +30,6 @@ interface ChapterListProps { } const ROW_HEIGHT = 48; -const ZERO_INSETS = { top: 0, right: 0, bottom: 0, left: 0 }; function ChapterListComponent({ visible, @@ -41,10 +39,7 @@ function ChapterListComponent({ onClose, }: ChapterListProps) { const { t } = useTranslation(); - const { settings } = useSettings(); - const insets = useSafeAreaInsets(); - const safeArea = - (settings?.safeAreaInControlsEnabled ?? true) ? insets : ZERO_INSETS; + const safeArea = useControlsSafeAreaInsets(); const listRef = useRef>(null); const entries = useMemo(() => sortedChapters(chapters), [chapters]); diff --git a/components/video-player/controls/BottomControls.tsx b/components/video-player/controls/BottomControls.tsx index f47c73457..81c77ab8c 100644 --- a/components/video-player/controls/BottomControls.tsx +++ b/components/video-player/controls/BottomControls.tsx @@ -8,10 +8,10 @@ import { useTranslation } from "react-i18next"; import { Pressable, View } from "react-native"; import { Slider } from "react-native-awesome-slider"; import { type SharedValue } from "react-native-reanimated"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { ChapterList } from "@/components/chapters/ChapterList"; import { ChapterTicks } from "@/components/chapters/ChapterTicks"; import { Text } from "@/components/common/Text"; +import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets"; import { useSettings } from "@/utils/atoms/settings"; import { chapterMarkers, chapterNameAt } from "@/utils/chapters"; import NextEpisodeCountDownButton from "./NextEpisodeCountDownButton"; @@ -111,7 +111,7 @@ export const BottomControls: FC = ({ }) => { const { settings } = useSettings(); const { t } = useTranslation(); - const insets = useSafeAreaInsets(); + const insets = useControlsSafeAreaInsets(); const [chapterListVisible, setChapterListVisible] = useState(false); // Only expose chapter UI when there are at least two real markers. @@ -142,13 +142,9 @@ export const BottomControls: FC = ({ style={[ { position: "absolute", - right: - (settings?.safeAreaInControlsEnabled ?? true) ? insets.right : 0, - left: (settings?.safeAreaInControlsEnabled ?? true) ? insets.left : 0, - bottom: - (settings?.safeAreaInControlsEnabled ?? true) - ? Math.max(insets.bottom - 17, 0) - : 0, + right: insets.right, + left: insets.left, + bottom: Math.max(insets.bottom - 17, 0), }, ]} className={"flex flex-col px-2"} diff --git a/components/video-player/controls/CenterControls.tsx b/components/video-player/controls/CenterControls.tsx index c668f3d6e..955fb8e57 100644 --- a/components/video-player/controls/CenterControls.tsx +++ b/components/video-player/controls/CenterControls.tsx @@ -1,9 +1,9 @@ import { Ionicons } from "@expo/vector-icons"; import type { FC } from "react"; import { Platform, TouchableOpacity, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import { Loader } from "@/components/Loader"; +import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets"; import { useSettings } from "@/utils/atoms/settings"; import AudioSlider from "./AudioSlider"; import BrightnessSlider from "./BrightnessSlider"; @@ -42,15 +42,15 @@ export const CenterControls: FC = ({ goToNextChapter, }) => { const { settings } = useSettings(); - const insets = useSafeAreaInsets(); + const insets = useControlsSafeAreaInsets(); return ( = ({ item, close, goToItem }) => { scrollViewRef.current?.scrollToIndex(index, 100); }; const isOffline = useOfflineMode(); - const { settings } = useSettings(); - const insets = useSafeAreaInsets(); + const insets = useControlsSafeAreaInsets(); // Set the initial season index useEffect(() => { @@ -182,12 +180,9 @@ export const EpisodeList: React.FC = ({ item, close, goToItem }) => { backgroundColor: "black", height: "100%", width: "100%", - paddingTop: - (settings?.safeAreaInControlsEnabled ?? true) ? insets.top : 0, - paddingLeft: - (settings?.safeAreaInControlsEnabled ?? true) ? insets.left : 0, - paddingRight: - (settings?.safeAreaInControlsEnabled ?? true) ? insets.right : 0, + paddingTop: insets.top, + paddingLeft: insets.left, + paddingRight: insets.right, }} > = ({ showTechnicalInfo = false, onToggleTechnicalInfo, }) => { - const { settings } = useSettings(); const router = useRouter(); - const insets = useSafeAreaInsets(); + const insets = useControlsSafeAreaInsets(); const lightHapticFeedback = useHaptic("light"); const { orientation, lockOrientation } = useOrientation(); const [isTogglingOrientation, setIsTogglingOrientation] = useState(false); @@ -99,10 +97,9 @@ export const HeaderControls: FC = ({ style={[ { position: "absolute", - top: (settings?.safeAreaInControlsEnabled ?? true) ? insets.top : 0, - left: (settings?.safeAreaInControlsEnabled ?? true) ? insets.left : 0, - right: - (settings?.safeAreaInControlsEnabled ?? true) ? insets.right : 0, + top: insets.top, + left: insets.left, + right: insets.right, padding: HEADER_LAYOUT.CONTAINER_PADDING, }, ]} diff --git a/components/video-player/controls/TechnicalInfoOverlay.tsx b/components/video-player/controls/TechnicalInfoOverlay.tsx index 20ec1fc47..fe98a16a9 100644 --- a/components/video-player/controls/TechnicalInfoOverlay.tsx +++ b/components/video-player/controls/TechnicalInfoOverlay.tsx @@ -16,8 +16,8 @@ import Animated, { } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useScaledTVTypography } from "@/constants/TVTypography"; +import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets"; import type { TechnicalInfo } from "@/modules/mpv-player"; -import { useSettings } from "@/utils/atoms/settings"; import { HEADER_LAYOUT } from "./constants"; type PlayMethod = "DirectPlay" | "DirectStream" | "Transcode"; @@ -184,8 +184,8 @@ export const TechnicalInfoOverlay: FC = memo( currentAudioIndex, }) => { const typography = useScaledTVTypography(); - const { settings } = useSettings(); const insets = useSafeAreaInsets(); + const safeInsets = useControlsSafeAreaInsets(); const [info, setInfo] = useState(null); const opacity = useSharedValue(0); @@ -268,14 +268,8 @@ export const TechnicalInfoOverlay: FC = memo( left: Math.max(insets.left, 48) + 20, } : { - top: - (settings?.safeAreaInControlsEnabled ?? true) - ? insets.top + HEADER_LAYOUT.CONTAINER_PADDING + 4 - : HEADER_LAYOUT.CONTAINER_PADDING + 4, - left: - (settings?.safeAreaInControlsEnabled ?? true) - ? insets.left + HEADER_LAYOUT.CONTAINER_PADDING + 20 - : HEADER_LAYOUT.CONTAINER_PADDING + 20, + top: safeInsets.top + HEADER_LAYOUT.CONTAINER_PADDING + 4, + left: safeInsets.left + HEADER_LAYOUT.CONTAINER_PADDING + 20, }; const textStyle = Platform.isTV diff --git a/hooks/useControlsSafeAreaInsets.ts b/hooks/useControlsSafeAreaInsets.ts new file mode 100644 index 000000000..4fa4968e6 --- /dev/null +++ b/hooks/useControlsSafeAreaInsets.ts @@ -0,0 +1,18 @@ +import { + type EdgeInsets, + useSafeAreaInsets, +} from "react-native-safe-area-context"; +import { useSettings } from "@/utils/atoms/settings"; + +const ZERO_INSETS: EdgeInsets = { top: 0, right: 0, bottom: 0, left: 0 }; + +/** + * Returns safe-area insets to apply to in-player controls, honoring the + * `safeAreaInControlsEnabled` user setting. When the setting is disabled, + * returns zero insets so controls can sit flush against the screen edges. + */ +export const useControlsSafeAreaInsets = (): EdgeInsets => { + const { settings } = useSettings(); + const insets = useSafeAreaInsets(); + return settings.safeAreaInControlsEnabled ? insets : ZERO_INSETS; +};