mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-25 16:26:54 +01:00
fix: move cast button + ask user which device to play on
This commit is contained in:
@@ -3,8 +3,9 @@ import React, { useEffect } from "react";
|
|||||||
import * as NavigationBar from "expo-navigation-bar";
|
import * as NavigationBar from "expo-navigation-bar";
|
||||||
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
|
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import { Platform, TouchableOpacity } from "react-native";
|
import { Platform, TouchableOpacity, View } from "react-native";
|
||||||
import { Feather } from "@expo/vector-icons";
|
import { Feather } from "@expo/vector-icons";
|
||||||
|
import { Chromecast } from "@/components/Chromecast";
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,18 +42,23 @@ export default function TabLayout() {
|
|||||||
router.push("/(auth)/downloads");
|
router.push("/(auth)/downloads");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Feather name="download" color={"white"} size={24} />
|
<Feather name="download" color={"white"} size={22} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
),
|
),
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
|
<View className="flex flex-row items-center space-x-2">
|
||||||
|
<Chromecast />
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{ marginHorizontal: 17 }}
|
style={{ marginRight: 17 }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push("/(auth)/settings");
|
router.push("/(auth)/settings");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Feather name="settings" color={"white"} size={24} />
|
<View className="h-10 aspect-square flex items-center justify-center rounded">
|
||||||
|
<Feather name="settings" color={"white"} size={22} />
|
||||||
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -137,10 +137,11 @@ const page: React.FC = () => {
|
|||||||
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
const client = useRemoteMediaClient();
|
const client = useRemoteMediaClient();
|
||||||
|
|
||||||
const onPressPlay = useCallback(async () => {
|
const onPressPlay = useCallback(
|
||||||
|
async (type: "device" | "cast" = "device") => {
|
||||||
if (!playbackUrl || !item) return;
|
if (!playbackUrl || !item) return;
|
||||||
|
|
||||||
if (chromecastReady && client) {
|
if (type === "cast" && client) {
|
||||||
await CastContext.getPlayServicesState().then((state) => {
|
await CastContext.getPlayServicesState().then((state) => {
|
||||||
if (state && state !== PlayServicesState.SUCCESS)
|
if (state && state !== PlayServicesState.SUCCESS)
|
||||||
CastContext.showPlayServicesErrorDialog(state);
|
CastContext.showPlayServicesErrorDialog(state);
|
||||||
@@ -165,7 +166,9 @@ const page: React.FC = () => {
|
|||||||
playbackUrl,
|
playbackUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [playbackUrl, item]);
|
},
|
||||||
|
[playbackUrl, item],
|
||||||
|
);
|
||||||
|
|
||||||
if (l1)
|
if (l1)
|
||||||
return (
|
return (
|
||||||
@@ -262,7 +265,6 @@ const page: React.FC = () => {
|
|||||||
<View className="h-12 aspect-square flex items-center justify-center"></View>
|
<View className="h-12 aspect-square flex items-center justify-center"></View>
|
||||||
)}
|
)}
|
||||||
<PlayedStatus item={item} />
|
<PlayedStatus item={item} />
|
||||||
<Chromecast />
|
|
||||||
</View>
|
</View>
|
||||||
<Text>{item.Overview}</Text>
|
<Text>{item.Overview}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -287,7 +289,7 @@ const page: React.FC = () => {
|
|||||||
<NextEpisodeButton item={item} type="previous" className="mr-2" />
|
<NextEpisodeButton item={item} type="previous" className="mr-2" />
|
||||||
<PlayButton
|
<PlayButton
|
||||||
item={item}
|
item={item}
|
||||||
chromecastReady={false}
|
chromecastReady={chromecastReady}
|
||||||
onPress={onPressPlay}
|
onPress={onPressPlay}
|
||||||
className="grow"
|
className="grow"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
import * as ScreenOrientation from "expo-screen-orientation";
|
import * as ScreenOrientation from "expo-screen-orientation";
|
||||||
import { StatusBar } from "expo-status-bar";
|
import { StatusBar } from "expo-status-bar";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
||||||
import { CurrentlyPlayingBar } from "@/components/CurrentlyPlayingBar";
|
import { CurrentlyPlayingBar } from "@/components/CurrentlyPlayingBar";
|
||||||
|
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||||
|
|
||||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
@@ -75,6 +75,7 @@ export default function RootLayout() {
|
|||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClientRef.current}>
|
<QueryClientProvider client={queryClientRef.current}>
|
||||||
<JotaiProvider>
|
<JotaiProvider>
|
||||||
|
<ActionSheetProvider>
|
||||||
<JellyfinProvider>
|
<JellyfinProvider>
|
||||||
<StatusBar style="light" backgroundColor="#000" />
|
<StatusBar style="light" backgroundColor="#000" />
|
||||||
<ThemeProvider value={DarkTheme}>
|
<ThemeProvider value={DarkTheme}>
|
||||||
@@ -136,6 +137,7 @@ export default function RootLayout() {
|
|||||||
<CurrentlyPlayingBar />
|
<CurrentlyPlayingBar />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</JellyfinProvider>
|
</JellyfinProvider>
|
||||||
|
</ActionSheetProvider>
|
||||||
</JotaiProvider>
|
</JotaiProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Animated, {
|
|||||||
useScrollViewOffset,
|
useScrollViewOffset,
|
||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { Chromecast } from "./Chromecast";
|
||||||
|
|
||||||
const HEADER_HEIGHT = 400;
|
const HEADER_HEIGHT = 400;
|
||||||
|
|
||||||
@@ -72,6 +73,15 @@ export const ParallaxScrollView: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View
|
||||||
|
className="absolute right-4 z-50 bg-black rounded-full p-0.5"
|
||||||
|
style={{
|
||||||
|
top: inset.top + 17,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Chromecast width={22} height={22} />
|
||||||
|
</View>
|
||||||
|
|
||||||
{logo && (
|
{logo && (
|
||||||
<View className="absolute top-[250px] h-[130px] left-0 w-full z-40 px-4 flex justify-center items-center">
|
<View className="absolute top-[250px] h-[130px] left-0 w-full z-40 px-4 flex justify-center items-center">
|
||||||
{logo}
|
{logo}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { Button } from "./Button";
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||||
|
import { useActionSheet } from "@expo/react-native-action-sheet";
|
||||||
|
import { View } from "react-native";
|
||||||
|
|
||||||
interface Props extends React.ComponentProps<typeof Button> {
|
interface Props extends React.ComponentProps<typeof Button> {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
onPress: () => void;
|
onPress: (type?: "cast" | "device") => void;
|
||||||
chromecastReady: boolean;
|
chromecastReady: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,15 +17,45 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
chromecastReady,
|
chromecastReady,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
const { showActionSheetWithOptions } = useActionSheet();
|
||||||
|
|
||||||
|
const _onPress = () => {
|
||||||
|
if (!chromecastReady) {
|
||||||
|
onPress("device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = ["Chromecast", "Device", "Cancel"];
|
||||||
|
const cancelButtonIndex = 2;
|
||||||
|
|
||||||
|
showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
cancelButtonIndex,
|
||||||
|
},
|
||||||
|
(selectedIndex: number | undefined) => {
|
||||||
|
switch (selectedIndex) {
|
||||||
|
case 0:
|
||||||
|
onPress("cast");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
onPress("device");
|
||||||
|
break;
|
||||||
|
case cancelButtonIndex:
|
||||||
|
console.log("calcel");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onPress={onPress}
|
onPress={_onPress}
|
||||||
iconRight={
|
iconRight={
|
||||||
chromecastReady ? (
|
<View className="flex flex-row items-center space-x-2">
|
||||||
<Feather name="cast" size={20} color="white" />
|
|
||||||
) : (
|
|
||||||
<Ionicons name="play-circle" size={24} color="white" />
|
<Ionicons name="play-circle" size={24} color="white" />
|
||||||
)
|
{chromecastReady && <Feather name="cast" size={22} color="white" />}
|
||||||
|
</View>
|
||||||
}
|
}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user