Merge branch 'master' into wip/general-posters

This commit is contained in:
Fredrik Burmester
2024-09-01 20:11:48 +02:00
12 changed files with 302 additions and 116 deletions

View File

@@ -1,10 +1,12 @@
import { Feather } from "@expo/vector-icons";
import { BlurView } from "expo-blur";
import React, { useEffect } from "react";
import { Platform, View, ViewProps } from "react-native";
import { Platform, TouchableOpacity, ViewProps } from "react-native";
import GoogleCast, {
CastButton,
CastContext,
useCastDevice,
useDevices,
useMediaStatus,
useRemoteMediaClient,
} from "react-native-google-cast";
@@ -25,6 +27,7 @@ export const Chromecast: React.FC<Props> = ({
const devices = useDevices();
const sessionManager = GoogleCast.getSessionManager();
const discoveryManager = GoogleCast.getDiscoveryManager();
const mediaStatus = useMediaStatus();
useEffect(() => {
(async () => {
@@ -38,31 +41,47 @@ export const Chromecast: React.FC<Props> = ({
if (background === "transparent")
return (
<View
<TouchableOpacity
onPress={() => {
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
else CastContext.showCastDialog();
}}
className="rounded-full h-10 w-10 flex items-center justify-center b"
{...props}
>
<CastButton style={{ tintColor: "white", height, width }} />
</View>
<Feather name="cast" size={22} color={"white"} />
</TouchableOpacity>
);
if (Platform.OS === "android")
return (
<View
<TouchableOpacity
onPress={() => {
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
else CastContext.showCastDialog();
}}
className="rounded-full h-10 w-10 flex items-center justify-center bg-neutral-800/80"
{...props}
>
<CastButton style={{ tintColor: "white", height, width }} />
</View>
<Feather name="cast" size={22} color={"white"} />
</TouchableOpacity>
);
return (
<BlurView
intensity={100}
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
<TouchableOpacity
onPress={() => {
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
else CastContext.showCastDialog();
}}
{...props}
>
<CastButton style={{ tintColor: "white", height, width }} />
</BlurView>
<BlurView
intensity={100}
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
{...props}
>
<Feather name="cast" size={22} color={"white"} />
</BlurView>
</TouchableOpacity>
);
};

View File

@@ -9,6 +9,7 @@ import { useEffect, useMemo } from "react";
import { TouchableOpacity, View } from "react-native";
import CastContext, {
PlayServicesState,
useMediaStatus,
useRemoteMediaClient,
} from "react-native-google-cast";
import Animated, {
@@ -22,6 +23,10 @@ import Animated, {
withTiming,
} from "react-native-reanimated";
import { Button } from "./Button";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
import { apiAtom } from "@/providers/JellyfinProvider";
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl";
interface Props extends React.ComponentProps<typeof Button> {
item?: BaseItemDto | null;
@@ -33,11 +38,12 @@ const MIN_PLAYBACK_WIDTH = 15;
export const PlayButton: React.FC<Props> = ({ item, url, ...props }) => {
const { showActionSheetWithOptions } = useActionSheet();
const { setCurrentlyPlayingState } = usePlayback();
const client = useRemoteMediaClient();
const { setCurrentlyPlayingState } = usePlayback();
const mediaStatus = useMediaStatus();
const [colorAtom] = useAtom(itemThemeColorAtom);
const [api] = useAtom(apiAtom);
const memoizedItem = useMemo(() => item, [item?.Id]); // Memoize the item
const memoizedColor = useMemo(() => colorAtom, [colorAtom]); // Memoize the color
@@ -63,24 +69,88 @@ export const PlayButton: React.FC<Props> = ({ item, url, ...props }) => {
cancelButtonIndex,
},
async (selectedIndex: number | undefined) => {
if (!api) return;
const currentTitle = mediaStatus?.mediaInfo?.metadata?.title;
const isOpeningCurrentlyPlayingMedia =
currentTitle && currentTitle === item?.Name;
switch (selectedIndex) {
case 0:
await CastContext.getPlayServicesState().then((state) => {
if (state && state !== PlayServicesState.SUCCESS)
CastContext.showPlayServicesErrorDialog(state);
else {
client.loadMedia({
mediaInfo: {
contentUrl: url,
contentType: "video/mp4",
metadata: {
type: item.Type === "Episode" ? "tvShow" : "movie",
title: item.Name || "",
subtitle: item.Overview || "",
// If we're opening a currently playing item, don't restart the media.
// Instead just open controls.
if (isOpeningCurrentlyPlayingMedia) {
CastContext.showExpandedControls();
return;
}
client
.loadMedia({
mediaInfo: {
contentUrl: url,
contentType: "video/mp4",
metadata:
item.Type === "Episode"
? {
type: "tvShow",
title: item.Name || "",
episodeNumber: item.IndexNumber || 0,
seasonNumber: item.ParentIndexNumber || 0,
seriesTitle: item.SeriesName || "",
images: [
{
url: getParentBackdropImageUrl({
api,
item,
quality: 90,
width: 2000,
})!,
},
],
}
: item.Type === "Movie"
? {
type: "movie",
title: item.Name || "",
subtitle: item.Overview || "",
images: [
{
url: getPrimaryImageUrl({
api,
item,
quality: 90,
width: 2000,
})!,
},
],
}
: {
type: "generic",
title: item.Name || "",
subtitle: item.Overview || "",
images: [
{
url: getPrimaryImageUrl({
api,
item,
quality: 90,
width: 2000,
})!,
},
],
},
},
},
startTime: 0,
});
startTime: 0,
})
.then(() => {
// state is already set when reopening current media, so skip it here.
if (isOpeningCurrentlyPlayingMedia) {
return;
}
CastContext.showExpandedControls();
});
}
});
break;

View File

@@ -23,7 +23,7 @@ export const FilterButton = <T,>({
queryFn,
queryKey,
set,
values,
values, // selected values
title,
renderItemLabel,
searchFilter,

View File

@@ -186,7 +186,7 @@ export const FilterSheet = <T,>({
className=" bg-neutral-800 px-4 py-3 flex flex-row items-center justify-between"
>
<Text>{renderItemLabel(item)}</Text>
{values.includes(item) ? (
{values.some((i) => i === item) ? (
<Ionicons name="radio-button-on" size={24} color="white" />
) : (
<Ionicons name="radio-button-off" size={24} color="white" />