From ab472bab6eb5302a27f62bb58ae16854d59f918d Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Tue, 30 Sep 2025 15:23:15 +0200 Subject: [PATCH] fix: modal for android + dropdown for ios --- components/GlobalModal.tsx | 3 +- components/PlatformDropdown.tsx | 64 +++++++++----- components/settings/AppLanguageSelector.tsx | 1 - components/settings/AudioToggles.tsx | 1 - components/settings/OtherSettings.tsx | 3 - components/settings/SubtitleToggles.tsx | 2 - .../controls/ScaleFactorSelector.tsx | 83 +++++++++---------- .../controls/VideoScalingModeSelector.tsx | 83 +++++++++---------- .../controls/dropdown/DropdownView.tsx | 6 +- .../controls/useControlsTimeout.ts | 2 +- 10 files changed, 129 insertions(+), 119 deletions(-) diff --git a/components/GlobalModal.tsx b/components/GlobalModal.tsx index db80675d..d97f2f6d 100644 --- a/components/GlobalModal.tsx +++ b/components/GlobalModal.tsx @@ -2,7 +2,6 @@ import { BottomSheetBackdrop, type BottomSheetBackdropProps, BottomSheetModal, - BottomSheetView, } from "@gorhom/bottom-sheet"; import { useCallback } from "react"; import { useGlobalModal } from "@/providers/GlobalModalProvider"; @@ -66,7 +65,7 @@ export const GlobalModal = () => { enablePanDownToClose={modalOptions.enablePanDownToClose} enableDismissOnClose > - {modalState.content} + {modalState.content} ); }; diff --git a/components/PlatformDropdown.tsx b/components/PlatformDropdown.tsx index 61461146..52ea5fd9 100644 --- a/components/PlatformDropdown.tsx +++ b/components/PlatformDropdown.tsx @@ -1,5 +1,6 @@ import { Button, ContextMenu, Host, Picker } from "@expo/ui/swift-ui"; import { Ionicons } from "@expo/vector-icons"; +import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; import React, { useEffect } from "react"; import { Platform, StyleSheet, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -156,7 +157,7 @@ const BottomSheetContent: React.FC<{ })); return ( - ( ))} - + ); }; @@ -228,25 +229,48 @@ const PlatformDropdownComponent = ({ const items = []; - // Add Picker for radio options if present + // Add Picker for radio options ONLY if there's a group title + // Otherwise render as individual buttons if (radioOptions.length > 0) { - items.push( - opt.label)} - variant='menu' - selectedIndex={radioOptions.findIndex( - (opt) => opt.selected, - )} - onOptionSelected={(event: any) => { - const index = event.nativeEvent.index; - const selectedOption = radioOptions[index]; - selectedOption?.onPress(); - onOptionSelect?.(selectedOption?.value); - }} - />, - ); + if (group.title) { + // Use Picker for grouped options + items.push( + opt.label)} + variant='menu' + selectedIndex={radioOptions.findIndex( + (opt) => opt.selected, + )} + onOptionSelected={(event: any) => { + const index = event.nativeEvent.index; + const selectedOption = radioOptions[index]; + selectedOption?.onPress(); + onOptionSelect?.(selectedOption?.value); + }} + />, + ); + } else { + // Render radio options as direct buttons + radioOptions.forEach((option, optionIndex) => { + items.push( + , + ); + }); + } } // Add Buttons for toggle options diff --git a/components/settings/AppLanguageSelector.tsx b/components/settings/AppLanguageSelector.tsx index c384a897..ac52896c 100644 --- a/components/settings/AppLanguageSelector.tsx +++ b/components/settings/AppLanguageSelector.tsx @@ -35,7 +35,6 @@ export const AppLanguageSelector: React.FC = () => { return [ { - title: t("home.settings.languages.title"), options, }, ]; diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx index aa6d2bd6..1a1c1457 100644 --- a/components/settings/AudioToggles.tsx +++ b/components/settings/AudioToggles.tsx @@ -49,7 +49,6 @@ export const AudioToggles: React.FC = ({ ...props }) => { return [ { - title: t("home.settings.audio.language"), options, }, ]; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 5e73d99f..61b95aeb 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -92,7 +92,6 @@ export const OtherSettings: React.FC = () => { const orientationOptions = useMemo( () => [ { - title: t("home.settings.other.orientation"), options: orientations.map((orientation) => ({ type: "radio" as const, label: t(ScreenOrientationEnum[orientation]), @@ -109,7 +108,6 @@ export const OtherSettings: React.FC = () => { const bitrateOptions = useMemo( () => [ { - title: t("home.settings.other.default_quality"), options: BITRATES.map((bitrate) => ({ type: "radio" as const, label: bitrate.key, @@ -125,7 +123,6 @@ export const OtherSettings: React.FC = () => { const autoPlayEpisodeOptions = useMemo( () => [ { - title: t("home.settings.other.max_auto_play_episode_count"), options: AUTOPLAY_EPISODES_COUNT(t).map((item) => ({ type: "radio" as const, label: item.key, diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx index 749d81be..53fb2a99 100644 --- a/components/settings/SubtitleToggles.tsx +++ b/components/settings/SubtitleToggles.tsx @@ -65,7 +65,6 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { return [ { - title: t("home.settings.subtitles.language"), options, }, ]; @@ -82,7 +81,6 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { return [ { - title: t("home.settings.subtitles.subtitle_mode"), options, }, ]; diff --git a/components/video-player/controls/ScaleFactorSelector.tsx b/components/video-player/controls/ScaleFactorSelector.tsx index b6ed1853..0e5f2b10 100644 --- a/components/video-player/controls/ScaleFactorSelector.tsx +++ b/components/video-player/controls/ScaleFactorSelector.tsx @@ -1,8 +1,10 @@ import { Ionicons } from "@expo/vector-icons"; -import React, { useState } from "react"; -import { Platform, TouchableOpacity } from "react-native"; -import { Text } from "@/components/common/Text"; -import { FilterSheet } from "@/components/filters/FilterSheet"; +import React, { useMemo } from "react"; +import { Platform, View } from "react-native"; +import { + type OptionGroup, + PlatformDropdown, +} from "@/components/PlatformDropdown"; import { useHaptic } from "@/hooks/useHaptic"; export type ScaleFactor = @@ -94,56 +96,51 @@ export const ScaleFactorSelector: React.FC = ({ disabled = false, }) => { const lightHapticFeedback = useHaptic("light"); - const [open, setOpen] = useState(false); - - // Hide on TV platforms - if (Platform.isTV) return null; const handleScaleSelect = (scale: ScaleFactor) => { onScaleChange(scale); lightHapticFeedback(); }; - const currentOption = SCALE_FACTOR_OPTIONS.find( - (option) => option.id === currentScale, - ); + const optionGroups = useMemo(() => { + return [ + { + options: SCALE_FACTOR_OPTIONS.map((option) => ({ + type: "radio" as const, + label: option.label, + value: option.id, + selected: option.id === currentScale, + onPress: () => handleScaleSelect(option.id), + disabled, + })), + }, + ]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentScale, disabled]); - return ( - <> - ( + setOpen(true)} > - + + ), + [disabled], + ); - { - const option = item as ScaleFactorOption; - return ( - option.label.toLowerCase().includes(query.toLowerCase()) || - option.description.toLowerCase().includes(query.toLowerCase()) - ); - }} - renderItemLabel={(item) => { - const option = item as ScaleFactorOption; - return {option.label}; - }} - set={(vals) => { - const chosen = vals[0] as ScaleFactorOption | undefined; - if (chosen) { - handleScaleSelect(chosen.id); - } - }} - /> - + // Hide on TV platforms + if (Platform.isTV) return null; + + return ( + ); }; diff --git a/components/video-player/controls/VideoScalingModeSelector.tsx b/components/video-player/controls/VideoScalingModeSelector.tsx index 00866666..2608f04d 100644 --- a/components/video-player/controls/VideoScalingModeSelector.tsx +++ b/components/video-player/controls/VideoScalingModeSelector.tsx @@ -1,8 +1,10 @@ import { Ionicons } from "@expo/vector-icons"; -import React, { useState } from "react"; -import { Platform, TouchableOpacity } from "react-native"; -import { Text } from "@/components/common/Text"; -import { FilterSheet } from "@/components/filters/FilterSheet"; +import React, { useMemo } from "react"; +import { Platform, View } from "react-native"; +import { + type OptionGroup, + PlatformDropdown, +} from "@/components/PlatformDropdown"; import { useHaptic } from "@/hooks/useHaptic"; export type AspectRatio = "default" | "16:9" | "4:3" | "1:1" | "21:9"; @@ -53,56 +55,51 @@ export const AspectRatioSelector: React.FC = ({ disabled = false, }) => { const lightHapticFeedback = useHaptic("light"); - const [open, setOpen] = useState(false); - - // Hide on TV platforms - if (Platform.isTV) return null; const handleRatioSelect = (ratio: AspectRatio) => { onRatioChange(ratio); lightHapticFeedback(); }; - const currentOption = ASPECT_RATIO_OPTIONS.find( - (option) => option.id === currentRatio, - ); + const optionGroups = useMemo(() => { + return [ + { + options: ASPECT_RATIO_OPTIONS.map((option) => ({ + type: "radio" as const, + label: option.label, + value: option.id, + selected: option.id === currentRatio, + onPress: () => handleRatioSelect(option.id), + disabled, + })), + }, + ]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentRatio, disabled]); - return ( - <> - ( + setOpen(true)} > - + + ), + [disabled], + ); - { - const option = item as AspectRatioOption; - return ( - option.label.toLowerCase().includes(query.toLowerCase()) || - option.description.toLowerCase().includes(query.toLowerCase()) - ); - }} - renderItemLabel={(item) => { - const option = item as AspectRatioOption; - return {option.label}; - }} - set={(vals) => { - const chosen = vals[0] as AspectRatioOption | undefined; - if (chosen) { - handleRatioSelect(chosen.id); - } - }} - /> - + // Hide on TV platforms + if (Platform.isTV) return null; + + return ( + ); }; diff --git a/components/video-player/controls/dropdown/DropdownView.tsx b/components/video-player/controls/dropdown/DropdownView.tsx index 56dfc23f..e1332e43 100644 --- a/components/video-player/controls/dropdown/DropdownView.tsx +++ b/components/video-player/controls/dropdown/DropdownView.tsx @@ -1,7 +1,7 @@ import { Ionicons } from "@expo/vector-icons"; import { useLocalSearchParams, useRouter } from "expo-router"; import { useCallback, useMemo, useRef } from "react"; -import { Platform, TouchableOpacity } from "react-native"; +import { Platform, View } from "react-native"; import { BITRATES } from "@/components/BitrateSelector"; import { type OptionGroup, @@ -133,9 +133,9 @@ const DropdownView = () => { // Memoize the trigger to prevent re-renders const trigger = useMemo( () => ( - + - + ), [], ); diff --git a/components/video-player/controls/useControlsTimeout.ts b/components/video-player/controls/useControlsTimeout.ts index d1e95b8c..80d41af2 100644 --- a/components/video-player/controls/useControlsTimeout.ts +++ b/components/video-player/controls/useControlsTimeout.ts @@ -13,7 +13,7 @@ export const useControlsTimeout = ({ isSliding, episodeView, onHideControls, - timeout = 4000, + timeout = 10000, }: UseControlsTimeoutProps) => { const controlsTimeoutRef = useRef | null>(null);