mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-02-05 12:12:23 +00:00
fix: close button modals
This commit is contained in:
@@ -10,6 +10,7 @@ import * as BackgroundTask from "expo-background-task";
|
||||
import * as Device from "expo-device";
|
||||
import { Platform } from "react-native";
|
||||
import { GlobalModal } from "@/components/GlobalModal";
|
||||
|
||||
import i18n from "@/i18n";
|
||||
import { DownloadProvider } from "@/providers/DownloadProvider";
|
||||
import { GlobalModalProvider } from "@/providers/GlobalModalProvider";
|
||||
@@ -443,7 +444,7 @@ function Layout() {
|
||||
}}
|
||||
closeButton
|
||||
/>
|
||||
<GlobalModal />
|
||||
{!Platform.isTV && <GlobalModal />}
|
||||
</ThemeProvider>
|
||||
</IntroSheetProvider>
|
||||
</BottomSheetModalProvider>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
TVFocusGuideView,
|
||||
useTVEventHandler,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
@@ -301,6 +302,63 @@ const _TVOptionRowModal: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
// Cancel button for TV option selectors
|
||||
const TVCancelButton: React.FC<{ onPress: () => void }> = ({ onPress }) => {
|
||||
const { t } = useTranslation();
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const animateTo = (v: number) =>
|
||||
Animated.timing(scale, {
|
||||
toValue: v,
|
||||
duration: 120,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onFocus={() => {
|
||||
setFocused(true);
|
||||
animateTo(1.05);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setFocused(false);
|
||||
animateTo(1);
|
||||
}}
|
||||
>
|
||||
<Animated.View
|
||||
style={{
|
||||
transform: [{ scale }],
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.15)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
name='close'
|
||||
size={20}
|
||||
color={focused ? "#000" : "rgba(255,255,255,0.8)"}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
color: focused ? "#000" : "rgba(255,255,255,0.8)",
|
||||
}}
|
||||
>
|
||||
{t("common.cancel") || "Cancel"}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
// TV Option Selector - Bottom sheet with horizontal scrolling (Apple TV style)
|
||||
const TVOptionSelector = <T,>({
|
||||
visible,
|
||||
@@ -452,6 +510,19 @@ const TVOptionSelector = <T,>({
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
{/* Cancel button */}
|
||||
{isReady && (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 48,
|
||||
paddingTop: 16,
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<TVCancelButton onPress={onClose} />
|
||||
</View>
|
||||
)}
|
||||
</TVFocusGuideView>
|
||||
</BlurView>
|
||||
</Animated.View>
|
||||
@@ -940,6 +1011,15 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
}
|
||||
}, [isModalOpen]);
|
||||
|
||||
// tvOS menu button handler for closing modals
|
||||
// Note: This may not receive events if React Navigation intercepts them first
|
||||
useTVEventHandler((evt) => {
|
||||
if (!evt || !isModalOpen) return;
|
||||
if (evt.eventType === "menu" || evt.eventType === "back") {
|
||||
setOpenModal(null);
|
||||
}
|
||||
});
|
||||
|
||||
// Get available audio tracks
|
||||
const audioTracks = useMemo(() => {
|
||||
const streams = selectedOptions?.mediaSource?.MediaStreams?.filter(
|
||||
|
||||
@@ -212,6 +212,13 @@ const TVOptionSelector = <T,>({
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
{/* Cancel button */}
|
||||
{isReady && (
|
||||
<View style={selectorStyles.cancelButtonContainer}>
|
||||
<TVCancelButton onPress={onClose} label='Cancel' />
|
||||
</View>
|
||||
)}
|
||||
</TVFocusGuideView>
|
||||
</BlurView>
|
||||
</RNAnimated.View>
|
||||
@@ -219,6 +226,61 @@ const TVOptionSelector = <T,>({
|
||||
);
|
||||
};
|
||||
|
||||
// Cancel button for TV option selectors
|
||||
const TVCancelButton: React.FC<{ onPress: () => void; label: string }> = ({
|
||||
onPress,
|
||||
label,
|
||||
}) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new RNAnimated.Value(1)).current;
|
||||
|
||||
const animateTo = (v: number) =>
|
||||
RNAnimated.timing(scale, {
|
||||
toValue: v,
|
||||
duration: 120,
|
||||
easing: RNEasing.out(RNEasing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onFocus={() => {
|
||||
setFocused(true);
|
||||
animateTo(1.05);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setFocused(false);
|
||||
animateTo(1);
|
||||
}}
|
||||
>
|
||||
<RNAnimated.View
|
||||
style={[
|
||||
selectorStyles.cancelButton,
|
||||
{
|
||||
transform: [{ scale }],
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.15)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name='close'
|
||||
size={20}
|
||||
color={focused ? "#000" : "rgba(255,255,255,0.8)"}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
selectorStyles.cancelButtonText,
|
||||
{ color: focused ? "#000" : "rgba(255,255,255,0.8)" },
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</RNAnimated.View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
// Option card for horizontal selector (with forwardRef for programmatic focus)
|
||||
const TVOptionCard = React.forwardRef<
|
||||
View,
|
||||
@@ -663,6 +725,23 @@ const selectorStyles = StyleSheet.create({
|
||||
tabText: {
|
||||
fontSize: 18,
|
||||
},
|
||||
cancelButtonContainer: {
|
||||
paddingHorizontal: 48,
|
||||
paddingTop: 16,
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
cancelButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
gap: 8,
|
||||
},
|
||||
cancelButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
// TV Next Episode Countdown component - horizontal layout with animated progress bar
|
||||
|
||||
@@ -474,6 +474,61 @@ const SubtitleResultCard = React.forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
// Cancel button for TV subtitle sheet
|
||||
const TVCancelButton: React.FC<{ onPress: () => void; label: string }> = ({
|
||||
onPress,
|
||||
label,
|
||||
}) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new RNAnimated.Value(1)).current;
|
||||
|
||||
const animateTo = (v: number) =>
|
||||
RNAnimated.timing(scale, {
|
||||
toValue: v,
|
||||
duration: 120,
|
||||
easing: RNEasing.out(RNEasing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onFocus={() => {
|
||||
setFocused(true);
|
||||
animateTo(1.05);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setFocused(false);
|
||||
animateTo(1);
|
||||
}}
|
||||
>
|
||||
<RNAnimated.View
|
||||
style={[
|
||||
styles.cancelButton,
|
||||
{
|
||||
transform: [{ scale }],
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.15)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name='close'
|
||||
size={20}
|
||||
color={focused ? "#000" : "rgba(255,255,255,0.8)"}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.cancelButtonText,
|
||||
{ color: focused ? "#000" : "rgba(255,255,255,0.8)" },
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</RNAnimated.View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export const TVSubtitleSheet: React.FC<TVSubtitleSheetProps> = ({
|
||||
visible,
|
||||
item,
|
||||
@@ -847,6 +902,16 @@ export const TVSubtitleSheet: React.FC<TVSubtitleSheetProps> = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Cancel button */}
|
||||
{isReady && (
|
||||
<View style={styles.cancelButtonContainer}>
|
||||
<TVCancelButton
|
||||
onPress={onClose}
|
||||
label={t("common.cancel") || "Cancel"}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</TVFocusGuideView>
|
||||
</BlurView>
|
||||
</RNAnimated.View>
|
||||
@@ -1086,4 +1151,21 @@ const styles = StyleSheet.create({
|
||||
color: "rgba(255,255,255,0.4)",
|
||||
fontSize: 12,
|
||||
},
|
||||
cancelButtonContainer: {
|
||||
paddingHorizontal: 48,
|
||||
paddingTop: 20,
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
cancelButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
gap: 8,
|
||||
},
|
||||
cancelButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user