mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-20 15:54:42 +01:00
wip
This commit is contained in:
@@ -87,14 +87,29 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = Dimensions.addEventListener(
|
||||
const dimensionsSubscription = Dimensions.addEventListener(
|
||||
"change",
|
||||
({ window, screen }) => {
|
||||
setDimensions({ window, screen });
|
||||
}
|
||||
);
|
||||
return () => subscription?.remove();
|
||||
});
|
||||
|
||||
const orientationSubscription =
|
||||
ScreenOrientation.addOrientationChangeListener((event) => {
|
||||
setOrientation(
|
||||
orientationToOrientationLock(event.orientationInfo.orientation)
|
||||
);
|
||||
});
|
||||
|
||||
ScreenOrientation.getOrientationAsync().then((orientation) => {
|
||||
setOrientation(orientationToOrientationLock(orientation));
|
||||
});
|
||||
|
||||
return () => {
|
||||
dimensionsSubscription.remove();
|
||||
orientationSubscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const from = useMemo(() => segments[2], [segments]);
|
||||
|
||||
@@ -165,24 +180,6 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
||||
return () => backHandler.remove();
|
||||
}, [currentlyPlaying, stopPlayback, router]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = ScreenOrientation.addOrientationChangeListener(
|
||||
(event) => {
|
||||
setOrientation(
|
||||
orientationToOrientationLock(event.orientationInfo.orientation)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ScreenOrientation.getOrientationAsync().then((orientation) => {
|
||||
setOrientation(orientationToOrientationLock(orientation));
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isLandscape = useMemo(() => {
|
||||
return orientation === ScreenOrientation.OrientationLock.LANDSCAPE_LEFT ||
|
||||
orientation === ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT
|
||||
|
||||
@@ -169,7 +169,7 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => {
|
||||
if (item?.Type === "Episode") headerHeightRef.current = 400;
|
||||
else if (item?.Type === "Movie") headerHeightRef.current = 500;
|
||||
else headerHeightRef.current = 400;
|
||||
}, [item]);
|
||||
}, [item, orientation]);
|
||||
|
||||
const { data: sessionData } = useQuery({
|
||||
queryKey: ["sessionData", item?.Id],
|
||||
|
||||
@@ -19,6 +19,9 @@ import {
|
||||
} from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { Button } from "../Button";
|
||||
import { Image } from "expo-image";
|
||||
import { useMemo } from "react";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
|
||||
interface Props extends ViewProps {}
|
||||
|
||||
@@ -95,6 +98,10 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
return formatTimeString(timeLeft, true);
|
||||
};
|
||||
|
||||
const base64Image = useMemo(() => {
|
||||
return storage.getString(process.item.Id!);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => router.push(`/(auth)/items/page?id=${process.item.Id}`)}
|
||||
@@ -114,15 +121,29 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
}}
|
||||
></View>
|
||||
)}
|
||||
<View className="p-4 flex flex-col w-full">
|
||||
<View className="flex flex-row items-center justify-between w-full">
|
||||
<View className="shrink">
|
||||
<View className="px-3 py-1.5 flex flex-col w-full">
|
||||
<View className="flex flex-row items-center w-full">
|
||||
{base64Image && (
|
||||
<View className="w-14 aspect-[10/15] rounded-lg overflow-hidden mr-4">
|
||||
<Image
|
||||
source={{
|
||||
uri: `data:image/jpeg;base64,${base64Image}`,
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
resizeMode: "cover",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View className="shrink mb-1">
|
||||
<Text className="text-xs opacity-50">{process.item.Type}</Text>
|
||||
<Text className="font-semibold shrink">{process.item.Name}</Text>
|
||||
<Text className="text-xs opacity-50">
|
||||
{process.item.ProductionYear}
|
||||
</Text>
|
||||
<View className="flex flex-row items-center space-x-2 mt-2 text-purple-600">
|
||||
<View className="flex flex-row items-center space-x-2 mt-1 text-purple-600">
|
||||
{process.progress === 0 ? (
|
||||
<ActivityIndicator size={"small"} color={"white"} />
|
||||
) : (
|
||||
@@ -143,6 +164,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
<TouchableOpacity
|
||||
disabled={cancelJobMutation.isPending}
|
||||
onPress={() => cancelJobMutation.mutate(process.id)}
|
||||
className="ml-auto"
|
||||
>
|
||||
{cancelJobMutation.isPending ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import React, { useCallback, useRef } from "react";
|
||||
import { TouchableOpacity } from "react-native";
|
||||
import React, { useCallback, useMemo, useRef } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import {
|
||||
ActionSheetProvider,
|
||||
useActionSheet,
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
import { useFileOpener } from "@/hooks/useDownloadedFileOpener";
|
||||
import { Text } from "../common/Text";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { Image } from "expo-image";
|
||||
import { ItemCardText } from "../ItemCardText";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
interface EpisodeCardProps {
|
||||
item: BaseItemDto;
|
||||
@@ -25,6 +29,10 @@ export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item }) => {
|
||||
const { openFile } = useFileOpener();
|
||||
const { showActionSheetWithOptions } = useActionSheet();
|
||||
|
||||
const base64Image = useMemo(() => {
|
||||
return storage.getString(item.Id!);
|
||||
}, []);
|
||||
|
||||
const handleOpenFile = useCallback(() => {
|
||||
openFile(item);
|
||||
}, [item, openFile]);
|
||||
@@ -68,10 +76,32 @@ export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item }) => {
|
||||
<TouchableOpacity
|
||||
onPress={handleOpenFile}
|
||||
onLongPress={showActionSheet}
|
||||
className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4"
|
||||
className="flex flex-col"
|
||||
>
|
||||
<Text className="font-bold">{item.Name}</Text>
|
||||
<Text className="text-xs opacity-50">Episode {item.IndexNumber}</Text>
|
||||
{base64Image ? (
|
||||
<View className="w-44 aspect-video rounded-lg overflow-hidden mr-2">
|
||||
<Image
|
||||
source={{
|
||||
uri: `data:image/jpeg;base64,${base64Image}`,
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
resizeMode: "cover",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View className="w-44 aspect-video rounded-lg bg-neutral-900 mr-2 flex items-center justify-center">
|
||||
<Ionicons
|
||||
name="image-outline"
|
||||
size={24}
|
||||
color="gray"
|
||||
className="self-center mt-16"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<ItemCardText item={item} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import {
|
||||
ActionSheetProvider,
|
||||
@@ -12,6 +12,10 @@ import { Text } from "../common/Text";
|
||||
|
||||
import { useFileOpener } from "@/hooks/useDownloadedFileOpener";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { Image } from "expo-image";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { ItemCardText } from "../ItemCardText";
|
||||
|
||||
interface MovieCardProps {
|
||||
item: BaseItemDto;
|
||||
@@ -31,6 +35,10 @@ export const MovieCard: React.FC<MovieCardProps> = ({ item }) => {
|
||||
openFile(item);
|
||||
}, [item, openFile]);
|
||||
|
||||
const base64Image = useMemo(() => {
|
||||
return storage.getString(item.Id!);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Handles deleting the file with haptic feedback.
|
||||
*/
|
||||
@@ -67,18 +75,31 @@ export const MovieCard: React.FC<MovieCardProps> = ({ item }) => {
|
||||
}, [showActionSheetWithOptions, handleDeleteFile]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={handleOpenFile}
|
||||
onLongPress={showActionSheet}
|
||||
className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4"
|
||||
>
|
||||
<Text className="font-bold">{item.Name}</Text>
|
||||
<View className="flex flex-col">
|
||||
<Text className="text-xs opacity-50">{item.ProductionYear}</Text>
|
||||
<Text className="text-xs opacity-50">
|
||||
{runtimeTicksToMinutes(item.RunTimeTicks)}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={handleOpenFile} onLongPress={showActionSheet}>
|
||||
{base64Image ? (
|
||||
<View className="w-28 aspect-[10/15] rounded-lg overflow-hidden mr-2 border border-neutral-900">
|
||||
<Image
|
||||
source={{
|
||||
uri: `data:image/jpeg;base64,${base64Image}`,
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
resizeMode: "cover",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View className="w-28 aspect-[10/15] rounded-lg bg-neutral-900 mr-2 flex items-center justify-center">
|
||||
<Ionicons
|
||||
name="image-outline"
|
||||
size={24}
|
||||
color="gray"
|
||||
className="self-center mt-16"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<ItemCardText item={item} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { View } from "react-native";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { EpisodeCard } from "./EpisodeCard";
|
||||
import { Text } from "../common/Text";
|
||||
import { useMemo } from "react";
|
||||
@@ -22,26 +22,32 @@ export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => {
|
||||
);
|
||||
}, [items]);
|
||||
|
||||
const sortByIndex = (a: BaseItemDto, b: BaseItemDto) => {
|
||||
return a.IndexNumber! > b.IndexNumber! ? 1 : -1;
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<View className="flex flex-row items-center justify-between px-4">
|
||||
<Text className="text-lg font-bold shrink">{items[0].SeriesName}</Text>
|
||||
<View className="bg-purple-600 rounded-full h-6 w-6 flex items-center justify-center">
|
||||
<Text className="text-xs font-bold">{items.length}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text className="opacity-50 mb-2">TV-Series</Text>
|
||||
<Text className="opacity-50 mb-2 px-4">TV-Series</Text>
|
||||
{groupBySeason.map((seasonItems, seasonIndex) => (
|
||||
<View key={seasonIndex}>
|
||||
<Text className="mb-2 font-semibold">
|
||||
<Text className="mb-2 font-semibold px-4">
|
||||
{seasonItems[0].SeasonName}
|
||||
</Text>
|
||||
{seasonItems.map((item, index) => (
|
||||
<View className="mb-2" key={index}>
|
||||
<EpisodeCard item={item} />
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<View className="px-4 flex flex-row">
|
||||
{seasonItems.sort(sortByIndex)?.map((item, index) => (
|
||||
<EpisodeCard item={item} />
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user