refactor(casting): extract CastPlayerHeader and CastPlayerTitle

This commit is contained in:
Uruk
2026-05-22 00:57:55 +02:00
parent 8c9506c7b5
commit 02df2477d8
3 changed files with 182 additions and 106 deletions

View File

@@ -0,0 +1,94 @@
/**
* Casting Player Header
* Fixed top bar: dismiss button, connection indicator, settings button.
*/
import { Ionicons } from "@expo/vector-icons";
import type { TFunction } from "i18next";
import { Pressable, View } from "react-native";
import { Text } from "@/components/common/Text";
interface CastPlayerHeaderProps {
/** Top safe-area inset, used to offset the fixed header. */
insetTop: number;
/** Streamyfin protocol accent color. */
protocolColor: string;
/** Friendly name of the connected cast device, or null. */
currentDevice: string | null;
/** Translation function. */
t: TFunction;
/** Dismiss the casting player modal. */
onDismiss: () => void;
/** Open the device sheet (connection indicator press). */
onPressConnectionIndicator: () => void;
/** Open the settings menu. */
onPressSettings: () => void;
}
export function CastPlayerHeader({
insetTop,
protocolColor,
currentDevice,
t,
onDismiss,
onPressConnectionIndicator,
onPressSettings,
}: CastPlayerHeaderProps) {
return (
<View
style={{
position: "absolute",
top: insetTop + 8,
left: 20,
right: 20,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
zIndex: 100,
}}
>
<Pressable onPress={onDismiss} style={{ padding: 8, marginLeft: -8 }}>
<Ionicons name='chevron-down' size={32} color='white' />
</Pressable>
{/* Connection indicator */}
<Pressable
onPress={onPressConnectionIndicator}
style={{
flexDirection: "row",
alignItems: "center",
gap: 6,
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: "#1a1a1a",
borderRadius: 16,
}}
>
<View
style={{
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: protocolColor,
}}
/>
<Text
style={{
color: protocolColor,
fontSize: 14,
fontWeight: "500",
}}
>
{currentDevice || t("casting_player.unknown_device")}
</Text>
</Pressable>
<Pressable
onPress={onPressSettings}
style={{ padding: 8, marginRight: -8 }}
>
<Ionicons name='settings-outline' size={24} color='white' />
</Pressable>
</View>
);
}

View File

@@ -0,0 +1,72 @@
/**
* Casting Player Title Area
* Fixed title bar: item title and optional grey episode/season info.
*/
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import type { TFunction } from "i18next";
import { View } from "react-native";
import { Text } from "@/components/common/Text";
import { truncateTitle } from "@/utils/casting/helpers";
interface CastPlayerTitleProps {
/** Top safe-area inset, used to offset the fixed title area. */
insetTop: number;
/** The currently playing item. */
currentItem: BaseItemDto;
/** Translation function. */
t: TFunction;
}
export function CastPlayerTitle({
insetTop,
currentItem,
t,
}: CastPlayerTitleProps) {
return (
<View
style={{
position: "absolute",
top: insetTop + 50,
left: 0,
right: 0,
zIndex: 95,
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.8)",
paddingVertical: 16,
paddingHorizontal: 20,
}}
>
{/* Title */}
<Text
style={{
color: "white",
fontSize: 20,
fontWeight: "700",
textAlign: "center",
marginBottom: 6,
}}
>
{truncateTitle(currentItem.Name || t("casting_player.unknown"), 50)}
</Text>
{/* Grey episode/season info */}
{currentItem.Type === "Episode" &&
currentItem.ParentIndexNumber !== undefined &&
currentItem.IndexNumber !== undefined && (
<Text
style={{
color: "#999",
fontSize: 15,
textAlign: "center",
}}
>
{t("casting_player.season_episode_format", {
season: currentItem.ParentIndexNumber,
episode: currentItem.IndexNumber,
})}
</Text>
)}
</View>
);
}