feat: KSPlayer as an option for iOS + other improvements (#1266)

This commit is contained in:
Fredrik Burmester
2026-01-03 13:05:50 +01:00
committed by GitHub
parent d1795c9df8
commit 74d86b5d12
191 changed files with 88479 additions and 2316 deletions

View File

@@ -58,7 +58,7 @@ export const HorizontalScroll = <T,>(
if (!data || loading) {
return (
<View className='px-4 mb-2'>
<View className='px-4'>
<View className='bg-neutral-950 h-24 w-full rounded-md mb-2' />
<View className='bg-neutral-950 h-10 w-full rounded-md mb-1' />
</View>

View File

@@ -0,0 +1,42 @@
import { TouchableOpacity, View } from "react-native";
import { Colors } from "@/constants/Colors";
import { Text } from "./Text";
type Props = {
title: string;
actionLabel?: string;
actionDisabled?: boolean;
onPressAction?: () => void;
};
export const SectionHeader: React.FC<Props> = ({
title,
actionLabel,
actionDisabled = false,
onPressAction,
}) => {
const shouldShowAction = Boolean(actionLabel) && Boolean(onPressAction);
return (
<View className='px-4 flex flex-row items-center justify-between mb-2'>
<Text className='text-lg font-bold text-neutral-100'>{title}</Text>
{shouldShowAction && (
<TouchableOpacity
onPress={onPressAction}
disabled={actionDisabled}
accessibilityRole='button'
accessibilityLabel={actionLabel}
className='py-1 pl-3'
>
<Text
style={{
color: actionDisabled ? "rgba(255,255,255,0.4)" : Colors.primary,
}}
>
{actionLabel}
</Text>
</TouchableOpacity>
)}
</View>
);
};

View File

@@ -16,6 +16,10 @@ export const itemRouter = (item: BaseItemDto, from: string) => {
return `/(auth)/(tabs)/${from}/livetv`;
}
if ("CollectionType" in item && item.CollectionType === "music") {
return `/(auth)/(tabs)/(libraries)/music/${item.Id}`;
}
if (item.Type === "Series") {
return `/(auth)/(tabs)/${from}/series/${item.Id}`;
}
@@ -50,6 +54,13 @@ export const getItemNavigation = (item: BaseItemDto, _from: string) => {
};
}
if ("CollectionType" in item && item.CollectionType === "music") {
return {
pathname: "/music/[libraryId]" as const,
params: { libraryId: item.Id! },
};
}
if (item.Type === "Series") {
return {
pathname: "/series/[id]" as const,
@@ -99,6 +110,25 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
const from = (segments as string[])[2] || "(home)";
const handlePress = useCallback(() => {
// For offline mode, we still need to use query params
if (isOffline) {
const url = `${itemRouter(item, from)}&offline=true`;
router.push(url as any);
return;
}
// Force music libraries to navigate via the explicit string route.
// This avoids losing the dynamic [libraryId] param when going through a nested navigator.
if ("CollectionType" in item && item.CollectionType === "music") {
router.push(itemRouter(item, from) as any);
return;
}
const navigation = getItemNavigation(item, from);
router.push(navigation as any);
}, [from, isOffline, item, router]);
const showActionSheet = useCallback(() => {
if (
!(
@@ -108,13 +138,14 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
)
)
return;
const options = [
const options: string[] = [
"Mark as Played",
"Mark as Not Played",
isFavorite ? "Unmark as Favorite" : "Mark as Favorite",
"Cancel",
];
const cancelButtonIndex = 3;
const cancelButtonIndex = options.length - 1;
showActionSheetWithOptions(
{
@@ -131,28 +162,24 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
}
},
);
}, [showActionSheetWithOptions, isFavorite, markAsPlayedStatus]);
}, [
showActionSheetWithOptions,
isFavorite,
markAsPlayedStatus,
toggleFavorite,
]);
if (
from === "(home)" ||
from === "(search)" ||
from === "(libraries)" ||
from === "(favorites)"
from === "(favorites)" ||
from === "(watchlists)"
)
return (
<TouchableOpacity
onLongPress={showActionSheet}
onPress={() => {
if (isOffline) {
// For offline mode, we still need to use query params
const url = `${itemRouter(item, from)}&offline=true`;
router.push(url as any);
return;
}
const navigation = getItemNavigation(item, from);
router.push(navigation as any);
}}
onPress={handlePress}
{...props}
>
{children}