mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 11:38:26 +01:00
fix: refactor
This commit is contained in:
@@ -11,7 +11,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
@@ -36,6 +36,10 @@ import ios12 from "@/utils/profiles/ios12";
|
|||||||
import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar";
|
import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar";
|
||||||
import { AudioTrackSelector } from "@/components/AudioTrackSelector";
|
import { AudioTrackSelector } from "@/components/AudioTrackSelector";
|
||||||
import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector";
|
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 page: React.FC = () => {
|
||||||
const local = useLocalSearchParams();
|
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">
|
<View className="flex flex-col">
|
||||||
{item.Type === "Episode" ? (
|
{item.Type === "Episode" ? (
|
||||||
<>
|
<>
|
||||||
@@ -218,7 +222,6 @@ const page: React.FC = () => {
|
|||||||
<Text className="text-center font-bold text-2xl mr-2">
|
<Text className="text-center font-bold text-2xl mr-2">
|
||||||
{item?.Name}
|
{item?.Name}
|
||||||
</Text>
|
</Text>
|
||||||
<PlayedStatus item={item} />
|
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<View className="flex flex-row items-center self-center">
|
<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">
|
<Text className="text-center font-bold text-2xl mr-2">
|
||||||
{item?.Name}
|
{item?.Name}
|
||||||
</Text>
|
</Text>
|
||||||
<PlayedStatus item={item} />
|
|
||||||
</View>
|
</View>
|
||||||
<Text className="text-center opacity-50">
|
<Text className="text-center opacity-50">
|
||||||
{item?.ProductionYear}
|
{item?.ProductionYear}
|
||||||
@@ -253,14 +255,17 @@ const page: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="flex flex-row justify-between items-center w-full my-4">
|
<View className="flex flex-row justify-between items-center w-full my-4">
|
||||||
{playbackUrl && (
|
{playbackUrl ? (
|
||||||
<DownloadItem item={item} playbackUrl={playbackUrl} />
|
<DownloadItem item={item} playbackUrl={playbackUrl} />
|
||||||
|
) : (
|
||||||
|
<View className="h-12 aspect-square flex items-center justify-center"></View>
|
||||||
)}
|
)}
|
||||||
|
<PlayedStatus item={item} />
|
||||||
<Chromecast />
|
<Chromecast />
|
||||||
</View>
|
</View>
|
||||||
<Text>{item.Overview}</Text>
|
<Text>{item.Overview}</Text>
|
||||||
</View>
|
</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">
|
<View className="flex flex-row items-center space-x-2 w-full">
|
||||||
<BitrateSelector
|
<BitrateSelector
|
||||||
onChange={(val) => setMaxBitrate(val)}
|
onChange={(val) => setMaxBitrate(val)}
|
||||||
@@ -277,7 +282,16 @@ const page: React.FC = () => {
|
|||||||
selected={selectedSubtitleStream}
|
selected={selectedSubtitleStream}
|
||||||
/>
|
/>
|
||||||
</View>
|
</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>
|
</View>
|
||||||
<ScrollView horizontal className="flex px-4 mb-4">
|
<ScrollView horizontal className="flex px-4 mb-4">
|
||||||
<View className="flex flex-row space-x-2 ">
|
<View className="flex flex-row space-x-2 ">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ interface ButtonProps extends React.ComponentProps<typeof TouchableOpacity> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
textClassName?: string;
|
textClassName?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: string;
|
children?: string | ReactNode;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
color?: "purple" | "red" | "black";
|
color?: "purple" | "red" | "black";
|
||||||
iconRight?: ReactNode;
|
iconRight?: ReactNode;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
import {
|
import {
|
||||||
CastButton,
|
CastButton,
|
||||||
useCastDevice,
|
useCastDevice,
|
||||||
@@ -30,5 +31,9 @@ export const Chromecast: React.FC<Props> = () => {
|
|||||||
})();
|
})();
|
||||||
}, [client, devices, castDevice, sessionManager, discoveryManager]);
|
}, [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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,23 +7,13 @@ import {
|
|||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import Video, { OnProgressData, VideoRef } from "react-native-video";
|
||||||
import Video, {
|
|
||||||
OnProgressData,
|
|
||||||
SelectedTrack,
|
|
||||||
SelectedTrackType,
|
|
||||||
VideoRef,
|
|
||||||
} from "react-native-video";
|
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useCastDevice, useRemoteMediaClient } from "react-native-google-cast";
|
|
||||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
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 { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
|
||||||
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@@ -35,7 +25,6 @@ import { useRouter, useSegments } from "expo-router";
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
import { Image } from "expo-image";
|
|
||||||
|
|
||||||
export const currentlyPlayingItemAtom = atom<{
|
export const currentlyPlayingItemAtom = atom<{
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
@@ -43,13 +32,10 @@ export const currentlyPlayingItemAtom = atom<{
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
export const CurrentlyPlayingBar: React.FC = () => {
|
export const CurrentlyPlayingBar: React.FC = () => {
|
||||||
const insets = useSafeAreaInsets();
|
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
|
|
||||||
const castDevice = useCastDevice();
|
|
||||||
const client = useRemoteMediaClient();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
|
|
||||||
@@ -57,8 +43,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
|||||||
const [paused, setPaused] = useState(true);
|
const [paused, setPaused] = useState(true);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
const [pip, setPip] = useState(false);
|
|
||||||
|
|
||||||
const aBottom = useSharedValue(0);
|
const aBottom = useSharedValue(0);
|
||||||
const aPadding = useSharedValue(0);
|
const aPadding = useSharedValue(0);
|
||||||
const aHeight = useSharedValue(100);
|
const aHeight = useSharedValue(100);
|
||||||
@@ -239,9 +223,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
|||||||
ignoreSilentSwitch="ignore"
|
ignoreSilentSwitch="ignore"
|
||||||
controls={false}
|
controls={false}
|
||||||
pictureInPicture={true}
|
pictureInPicture={true}
|
||||||
onPictureInPictureStatusChanged={(e) => {
|
|
||||||
setPip(e.isActive);
|
|
||||||
}}
|
|
||||||
poster={
|
poster={
|
||||||
backdropUrl && item?.Type === "Audio"
|
backdropUrl && item?.Type === "Audio"
|
||||||
? backdropUrl
|
? backdropUrl
|
||||||
|
|||||||
@@ -64,12 +64,16 @@ export const DownloadItem: React.FC<DownloadProps> = ({
|
|||||||
}, [process]);
|
}, [process]);
|
||||||
|
|
||||||
if (isLoading) {
|
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) {
|
if (playbackInfo?.MediaSources?.[0].SupportsDirectPlay === false) {
|
||||||
return (
|
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" />
|
<Ionicons name="cloud-download-outline" size={24} color="white" />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -77,21 +81,22 @@ export const DownloadItem: React.FC<DownloadProps> = ({
|
|||||||
|
|
||||||
if (process && process.item.Id !== item.Id!) {
|
if (process && process.item.Id !== item.Id!) {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={() => {}} style={{ opacity: 0.5 }}>
|
<TouchableOpacity onPress={() => {}}>
|
||||||
<Ionicons name="cloud-download-outline" size={24} color="white" />
|
<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>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (process) {
|
||||||
<View>
|
return (
|
||||||
{process ? (
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
onPress={() => {
|
||||||
onPress={() => {
|
router.push("/downloads");
|
||||||
router.push("/downloads");
|
}}
|
||||||
}}
|
>
|
||||||
className="flex flex-row items-center"
|
<View className="rounded h-12 aspect-square flex items-center justify-center">
|
||||||
>
|
|
||||||
{process.progress === 0 ? (
|
{process.progress === 0 ? (
|
||||||
<ActivityIndicator size={"small"} color={"white"} />
|
<ActivityIndicator size={"small"} color={"white"} />
|
||||||
) : (
|
) : (
|
||||||
@@ -118,24 +123,32 @@ export const DownloadItem: React.FC<DownloadProps> = ({
|
|||||||
<Text>{process.speed.toFixed(2)}x</Text>
|
<Text>{process.speed.toFixed(2)}x</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
) : downloaded ? (
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
);
|
||||||
onPress={() => {
|
} else if (downloaded) {
|
||||||
router.push("/downloads");
|
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" />
|
<Ionicons name="cloud-download" size={26} color="#9333ea" />
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
) : (
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
);
|
||||||
onPress={() => {
|
} else {
|
||||||
startRemuxing();
|
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" />
|
<Ionicons name="cloud-download-outline" size={26} color="white" />
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
)}
|
</TouchableOpacity>
|
||||||
</View>
|
);
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
56
components/NewVideoPlayer.tsx
Normal file
56
components/NewVideoPlayer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -30,7 +30,7 @@ type VideoPlayerProps = {
|
|||||||
onChangePlaybackURL: (url: string | null) => void;
|
onChangePlaybackURL: (url: string | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
export const OldVideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
itemId,
|
itemId,
|
||||||
onChangePlaybackURL,
|
onChangePlaybackURL,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -89,7 +89,9 @@ export const ParallaxScrollView: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
{headerImage}
|
{headerImage}
|
||||||
</Animated.View>
|
</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>
|
</Animated.ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
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 { Feather, Ionicons } from "@expo/vector-icons";
|
||||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||||
|
|
||||||
type Props = {
|
interface Props extends React.ComponentProps<typeof Button> {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
chromecastReady: boolean;
|
chromecastReady: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const PlayButton: React.FC<Props> = ({
|
export const PlayButton: React.FC<Props> = ({
|
||||||
item,
|
item,
|
||||||
onPress,
|
onPress,
|
||||||
chromecastReady,
|
chromecastReady,
|
||||||
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -27,6 +25,7 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
<Ionicons name="play-circle" size={24} color="white" />
|
<Ionicons name="play-circle" size={24} color="white" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{runtimeTicksToMinutes(item?.RunTimeTicks)}
|
{runtimeTicksToMinutes(item?.RunTimeTicks)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
|||||||
invalidateQueries();
|
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>
|
||||||
) : (
|
) : (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -61,7 +63,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
|||||||
invalidateQueries();
|
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>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user