fix: refactor

This commit is contained in:
Fredrik Burmester
2024-08-13 20:25:30 +02:00
parent 5289c0519f
commit 855e00a676
10 changed files with 143 additions and 69 deletions

View File

@@ -11,7 +11,7 @@ import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router";
import { useAtom } from "jotai";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
ActivityIndicator,
ScrollView,
@@ -36,6 +36,10 @@ import ios12 from "@/utils/profiles/ios12";
import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar";
import { AudioTrackSelector } from "@/components/AudioTrackSelector";
import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Button } from "@/components/Button";
import { Ionicons } from "@expo/vector-icons";
import { NextEpisodeButton } from "@/components/series/NextEpisodeButton";
const page: React.FC = () => {
const local = useLocalSearchParams();
@@ -201,7 +205,7 @@ const page: React.FC = () => {
</>
}
>
<View className="flex flex-col px-4 mb-4 pt-4">
<View className="flex flex-col px-4 pt-4">
<View className="flex flex-col">
{item.Type === "Episode" ? (
<>
@@ -218,7 +222,6 @@ const page: React.FC = () => {
<Text className="text-center font-bold text-2xl mr-2">
{item?.Name}
</Text>
<PlayedStatus item={item} />
</View>
<View>
<View className="flex flex-row items-center self-center">
@@ -243,7 +246,6 @@ const page: React.FC = () => {
<Text className="text-center font-bold text-2xl mr-2">
{item?.Name}
</Text>
<PlayedStatus item={item} />
</View>
<Text className="text-center opacity-50">
{item?.ProductionYear}
@@ -253,14 +255,17 @@ const page: React.FC = () => {
</View>
<View className="flex flex-row justify-between items-center w-full my-4">
{playbackUrl && (
{playbackUrl ? (
<DownloadItem item={item} playbackUrl={playbackUrl} />
) : (
<View className="h-12 aspect-square flex items-center justify-center"></View>
)}
<PlayedStatus item={item} />
<Chromecast />
</View>
<Text>{item.Overview}</Text>
</View>
<View className="flex flex-col p-4">
<View className="flex flex-col p-4 w-full">
<View className="flex flex-row items-center space-x-2 w-full">
<BitrateSelector
onChange={(val) => setMaxBitrate(val)}
@@ -277,7 +282,16 @@ const page: React.FC = () => {
selected={selectedSubtitleStream}
/>
</View>
<PlayButton item={item} chromecastReady={false} onPress={onPressPlay} />
<View className="flex flex-row items-center justify-between space-x-2 w-full">
<NextEpisodeButton item={item} type="previous" />
<PlayButton
item={item}
chromecastReady={false}
onPress={onPressPlay}
className="grow"
/>
<NextEpisodeButton item={item} />
</View>
</View>
<ScrollView horizontal className="flex px-4 mb-4">
<View className="flex flex-row space-x-2 ">

View File

@@ -7,7 +7,7 @@ interface ButtonProps extends React.ComponentProps<typeof TouchableOpacity> {
className?: string;
textClassName?: string;
disabled?: boolean;
children?: string;
children?: string | ReactNode;
loading?: boolean;
color?: "purple" | "red" | "black";
iconRight?: ReactNode;

View File

@@ -1,5 +1,6 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import React, { useEffect } from "react";
import { View } from "react-native";
import {
CastButton,
useCastDevice,
@@ -30,5 +31,9 @@ export const Chromecast: React.FC<Props> = () => {
})();
}, [client, devices, castDevice, sessionManager, discoveryManager]);
return <CastButton style={{ tintColor: "white", height: 48, width: 48 }} />;
return (
<View className="rounded h-12 aspect-square flex items-center justify-center">
<CastButton style={{ tintColor: "white", height: 48, width: 48 }} />
</View>
);
};

View File

@@ -7,23 +7,13 @@ import {
import { Text } from "./common/Text";
import { Ionicons } from "@expo/vector-icons";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Video, {
OnProgressData,
SelectedTrack,
SelectedTrackType,
VideoRef,
} from "react-native-video";
import Video, { OnProgressData, VideoRef } from "react-native-video";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { atom, useAtom } from "jotai";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCastDevice, useRemoteMediaClient } from "react-native-google-cast";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
import { chromecastProfile } from "@/utils/profiles/chromecast";
import ios12 from "@/utils/profiles/ios12";
import { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
import Animated, {
@@ -35,7 +25,6 @@ import { useRouter, useSegments } from "expo-router";
import { BlurView } from "expo-blur";
import { writeToLog } from "@/utils/log";
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
import { Image } from "expo-image";
export const currentlyPlayingItemAtom = atom<{
item: BaseItemDto;
@@ -43,13 +32,10 @@ export const currentlyPlayingItemAtom = atom<{
} | null>(null);
export const CurrentlyPlayingBar: React.FC = () => {
const insets = useSafeAreaInsets();
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
const castDevice = useCastDevice();
const client = useRemoteMediaClient();
const queryClient = useQueryClient();
const segments = useSegments();
@@ -57,8 +43,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
const [paused, setPaused] = useState(true);
const [progress, setProgress] = useState(0);
const [pip, setPip] = useState(false);
const aBottom = useSharedValue(0);
const aPadding = useSharedValue(0);
const aHeight = useSharedValue(100);
@@ -239,9 +223,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
ignoreSilentSwitch="ignore"
controls={false}
pictureInPicture={true}
onPictureInPictureStatusChanged={(e) => {
setPip(e.isActive);
}}
poster={
backdropUrl && item?.Type === "Audio"
? backdropUrl

View File

@@ -64,12 +64,16 @@ export const DownloadItem: React.FC<DownloadProps> = ({
}, [process]);
if (isLoading) {
return <ActivityIndicator size={"small"} color={"white"} />;
return (
<View className="rounded h-12 aspect-square flex items-center justify-center">
<ActivityIndicator size={"small"} color={"white"} />
</View>
);
}
if (playbackInfo?.MediaSources?.[0].SupportsDirectPlay === false) {
return (
<View style={{ opacity: 0.5 }}>
<View className="rounded h-12 aspect-square flex items-center justify-center opacity-50">
<Ionicons name="cloud-download-outline" size={24} color="white" />
</View>
);
@@ -77,21 +81,22 @@ export const DownloadItem: React.FC<DownloadProps> = ({
if (process && process.item.Id !== item.Id!) {
return (
<TouchableOpacity onPress={() => {}} style={{ opacity: 0.5 }}>
<Ionicons name="cloud-download-outline" size={24} color="white" />
<TouchableOpacity onPress={() => {}}>
<View className="rounded h-12 aspect-square flex items-center justify-center opacity-50">
<Ionicons name="cloud-download-outline" size={24} color="white" />
</View>
</TouchableOpacity>
);
}
return (
<View>
{process ? (
<TouchableOpacity
onPress={() => {
router.push("/downloads");
}}
className="flex flex-row items-center"
>
if (process) {
return (
<TouchableOpacity
onPress={() => {
router.push("/downloads");
}}
>
<View className="rounded h-12 aspect-square flex items-center justify-center">
{process.progress === 0 ? (
<ActivityIndicator size={"small"} color={"white"} />
) : (
@@ -118,24 +123,32 @@ export const DownloadItem: React.FC<DownloadProps> = ({
<Text>{process.speed.toFixed(2)}x</Text>
</View>
) : null}
</TouchableOpacity>
) : downloaded ? (
<TouchableOpacity
onPress={() => {
router.push("/downloads");
}}
>
</View>
</TouchableOpacity>
);
} else if (downloaded) {
return (
<TouchableOpacity
onPress={() => {
router.push("/downloads");
}}
>
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="cloud-download" size={26} color="#9333ea" />
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={() => {
startRemuxing();
}}
>
</View>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity
onPress={() => {
startRemuxing();
}}
>
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="cloud-download-outline" size={26} color="white" />
</TouchableOpacity>
)}
</View>
);
</View>
</TouchableOpacity>
);
}
};

View File

@@ -0,0 +1,56 @@
import { useVideoPlayer, VideoView } from "expo-video";
import { useEffect, useRef, useState } from "react";
import {
PixelRatio,
StyleSheet,
View,
Button,
TouchableOpacity,
} from "react-native";
const videoSource =
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
interface Props {
videoSource: string;
}
export const NewVideoPlayer: React.FC<Props> = ({ videoSource }) => {
const ref = useRef<VideoView | null>(null);
const [isPlaying, setIsPlaying] = useState(true);
const player = useVideoPlayer(videoSource, (player) => {
player.loop = true;
player.play();
});
useEffect(() => {
const subscription = player.addListener("playingChange", (isPlaying) => {
setIsPlaying(isPlaying);
});
return () => {
subscription.remove();
};
}, [player]);
return (
<TouchableOpacity
onPress={() => {
ref.current?.enterFullscreen();
}}
className={`relative h-full bg-neutral-800 rounded-md overflow-hidden
`}
>
<VideoView
ref={ref}
style={{
width: "100%",
height: "100%",
}}
player={player}
allowsFullscreen
allowsPictureInPicture
/>
</TouchableOpacity>
);
};

View File

@@ -30,7 +30,7 @@ type VideoPlayerProps = {
onChangePlaybackURL: (url: string | null) => void;
};
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
export const OldVideoPlayer: React.FC<VideoPlayerProps> = ({
itemId,
onChangePlaybackURL,
}) => {

View File

@@ -89,7 +89,9 @@ export const ParallaxScrollView: React.FC<Props> = ({
>
{headerImage}
</Animated.View>
<View className="flex-1 overflow-hidden bg-black">{children}</View>
<View className="flex-1 overflow-hidden bg-black pb-24">
{children}
</View>
</Animated.ScrollView>
</View>
);

View File

@@ -1,21 +1,19 @@
import { useState } from "react";
import { Button } from "./Button";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { currentlyPlayingItemAtom } from "./CurrentlyPlayingBar";
import { useAtom } from "jotai";
import { Feather, Ionicons } from "@expo/vector-icons";
import { runtimeTicksToMinutes } from "@/utils/time";
type Props = {
interface Props extends React.ComponentProps<typeof Button> {
item: BaseItemDto;
onPress: () => void;
chromecastReady: boolean;
};
}
export const PlayButton: React.FC<Props> = ({
item,
onPress,
chromecastReady,
...props
}) => {
return (
<Button
@@ -27,6 +25,7 @@ export const PlayButton: React.FC<Props> = ({
<Ionicons name="play-circle" size={24} color="white" />
)
}
{...props}
>
{runtimeTicksToMinutes(item?.RunTimeTicks)}
</Button>

View File

@@ -47,7 +47,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
invalidateQueries();
}}
>
<Ionicons name="checkmark-circle" size={26} color="white" />
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle" size={26} color="white" />
</View>
</TouchableOpacity>
) : (
<TouchableOpacity
@@ -61,7 +63,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
invalidateQueries();
}}
>
<Ionicons name="checkmark-circle-outline" size={26} color="white" />
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle-outline" size={26} color="white" />
</View>
</TouchableOpacity>
)}
</View>