fix: move cast button + ask user which device to play on

This commit is contained in:
Fredrik Burmester
2024-08-14 10:09:59 +02:00
parent ede390e74b
commit bd8bf8349f
5 changed files with 159 additions and 107 deletions

View File

@@ -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: () => (
<TouchableOpacity <View className="flex flex-row items-center space-x-2">
style={{ marginHorizontal: 17 }} <Chromecast />
onPress={() => { <TouchableOpacity
router.push("/(auth)/settings"); style={{ marginRight: 17 }}
}} onPress={() => {
> router.push("/(auth)/settings");
<Feather name="settings" color={"white"} size={24} /> }}
</TouchableOpacity> >
<View className="h-10 aspect-square flex items-center justify-center rounded">
<Feather name="settings" color={"white"} size={22} />
</View>
</TouchableOpacity>
</View>
), ),
}} }}
/> />

View File

@@ -137,35 +137,38 @@ 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(
if (!playbackUrl || !item) return; async (type: "device" | "cast" = "device") => {
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);
else { else {
client.loadMedia({ client.loadMedia({
mediaInfo: { mediaInfo: {
contentUrl: playbackUrl, contentUrl: playbackUrl,
contentType: "video/mp4", contentType: "video/mp4",
metadata: { metadata: {
type: item.Type === "Episode" ? "tvShow" : "movie", type: item.Type === "Episode" ? "tvShow" : "movie",
title: item.Name || "", title: item.Name || "",
subtitle: item.Overview || "", subtitle: item.Overview || "",
},
}, },
}, startTime: 0,
startTime: 0, });
}); }
} });
}); } else {
} else { setCp({
setCp({ item,
item, 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"
/> />

View File

@@ -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,67 +75,69 @@ export default function RootLayout() {
return ( return (
<QueryClientProvider client={queryClientRef.current}> <QueryClientProvider client={queryClientRef.current}>
<JotaiProvider> <JotaiProvider>
<JellyfinProvider> <ActionSheetProvider>
<StatusBar style="light" backgroundColor="#000" /> <JellyfinProvider>
<ThemeProvider value={DarkTheme}> <StatusBar style="light" backgroundColor="#000" />
<Stack> <ThemeProvider value={DarkTheme}>
<Stack.Screen <Stack>
name="(auth)/(tabs)" <Stack.Screen
options={{ name="(auth)/(tabs)"
headerShown: false, options={{
title: "Home", headerShown: false,
}} title: "Home",
/> }}
<Stack.Screen />
name="(auth)/settings" <Stack.Screen
options={{ name="(auth)/settings"
headerShown: true, options={{
title: "Settings", headerShown: true,
headerStyle: { backgroundColor: "black" }, title: "Settings",
headerShadowVisible: false, headerStyle: { backgroundColor: "black" },
}} headerShadowVisible: false,
/> }}
<Stack.Screen />
name="(auth)/downloads" <Stack.Screen
options={{ name="(auth)/downloads"
headerShown: true, options={{
title: "Downloads", headerShown: true,
headerStyle: { backgroundColor: "black" }, title: "Downloads",
headerShadowVisible: false, headerStyle: { backgroundColor: "black" },
}} headerShadowVisible: false,
/> }}
<Stack.Screen />
name="(auth)/items/[id]/page" <Stack.Screen
options={{ name="(auth)/items/[id]/page"
title: "", options={{
headerShown: false, title: "",
}} headerShown: false,
/> }}
<Stack.Screen />
name="(auth)/collections/[collection]/page" <Stack.Screen
options={{ name="(auth)/collections/[collection]/page"
title: "", options={{
headerShown: true, title: "",
headerStyle: { backgroundColor: "black" }, headerShown: true,
headerShadowVisible: false, headerStyle: { backgroundColor: "black" },
}} headerShadowVisible: false,
/> }}
<Stack.Screen />
name="(auth)/series/[id]/page" <Stack.Screen
options={{ name="(auth)/series/[id]/page"
title: "", options={{
headerShown: false, title: "",
}} headerShown: false,
/> }}
<Stack.Screen />
name="login" <Stack.Screen
options={{ headerShown: false, title: "Login" }} name="login"
/> options={{ headerShown: false, title: "Login" }}
<Stack.Screen name="+not-found" /> />
</Stack> <Stack.Screen name="+not-found" />
<CurrentlyPlayingBar /> </Stack>
</ThemeProvider> <CurrentlyPlayingBar />
</JellyfinProvider> </ThemeProvider>
</JellyfinProvider>
</ActionSheetProvider>
</JotaiProvider> </JotaiProvider>
</QueryClientProvider> </QueryClientProvider>
); );

View File

@@ -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}

View File

@@ -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}
> >