mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
merge develop
This commit is contained in:
@@ -12,6 +12,7 @@ import { HorizontalScroll } from "../common/HorrizontalScroll";
|
||||
import { Text } from "../common/Text";
|
||||
import Poster from "../posters/Poster";
|
||||
import { itemRouter } from "../common/TouchableItemRouter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
item?: BaseItemDto | null;
|
||||
@@ -21,6 +22,7 @@ interface Props extends ViewProps {
|
||||
export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const segments = useSegments();
|
||||
const { t } = useTranslation();
|
||||
const from = segments[2];
|
||||
|
||||
const destinctPeople = useMemo(() => {
|
||||
@@ -40,7 +42,7 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
|
||||
|
||||
return (
|
||||
<View {...props} className="flex flex-col">
|
||||
<Text className="text-lg font-bold mb-2 px-4">Cast & Crew</Text>
|
||||
<Text className="text-lg font-bold mb-2 px-4">{t("item_card.cast_and_crew")}</Text>
|
||||
<HorizontalScroll
|
||||
loading={loading}
|
||||
keyExtractor={(i, idx) => i.Id.toString()}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Poster from "../posters/Poster";
|
||||
import { HorizontalScroll } from "../common/HorrizontalScroll";
|
||||
import { Text } from "../common/Text";
|
||||
import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
item?: BaseItemDto | null;
|
||||
@@ -15,10 +16,11 @@ interface Props extends ViewProps {
|
||||
|
||||
export const CurrentSeries: React.FC<Props> = ({ item, ...props }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View {...props}>
|
||||
<Text className="text-lg font-bold mb-2 px-4">Series</Text>
|
||||
<Text className="text-lg font-bold mb-2 px-4">{t("item_card.series")}</Text>
|
||||
<HorizontalScroll
|
||||
data={[item]}
|
||||
height={247}
|
||||
|
||||
@@ -20,7 +20,9 @@ import { HorizontalScroll } from "@/components/common/HorrizontalScroll";
|
||||
import { Image } from "expo-image";
|
||||
import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||
import { Loader } from "../Loader";
|
||||
import { t } from "i18next";
|
||||
import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie";
|
||||
import {MediaRequestBody} from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
|
||||
|
||||
const JellyseerrSeasonEpisodes: React.FC<{
|
||||
details: TvDetails;
|
||||
@@ -101,8 +103,17 @@ const JellyseerrSeasons: React.FC<{
|
||||
isLoading: boolean;
|
||||
result?: TvResult;
|
||||
details?: TvDetails;
|
||||
hasAdvancedRequest?: boolean,
|
||||
onAdvancedRequest?: (data: MediaRequestBody) => void;
|
||||
refetch: (options?: (RefetchOptions | undefined)) => Promise<QueryObserverResult<TvDetails | MovieDetails | undefined, Error>>;
|
||||
}> = ({ isLoading, result, details, refetch }) => {
|
||||
}> = ({
|
||||
isLoading,
|
||||
result,
|
||||
details,
|
||||
refetch,
|
||||
hasAdvancedRequest,
|
||||
onAdvancedRequest,
|
||||
}) => {
|
||||
if (!details) return null;
|
||||
|
||||
const { jellyseerrApi, requestMedia } = useJellyseerr();
|
||||
@@ -142,7 +153,7 @@ const JellyseerrSeasons: React.FC<{
|
||||
|
||||
const requestAll = useCallback(() => {
|
||||
if (details && jellyseerrApi) {
|
||||
requestMedia(result?.name!!, {
|
||||
const body: MediaRequestBody = {
|
||||
mediaId: details.id,
|
||||
mediaType: MediaType.TV,
|
||||
tvdbId: details.externalIds?.tvdbId,
|
||||
@@ -151,19 +162,25 @@ const JellyseerrSeasons: React.FC<{
|
||||
(s) => s.status === MediaStatus.UNKNOWN && s.seasonNumber !== 0
|
||||
)
|
||||
.map((s) => s.seasonNumber),
|
||||
});
|
||||
}
|
||||
|
||||
if (hasAdvancedRequest) {
|
||||
return onAdvancedRequest?.(body)
|
||||
}
|
||||
|
||||
requestMedia(result?.name!!, body, refetch);
|
||||
}
|
||||
}, [jellyseerrApi, seasons, details]);
|
||||
}, [jellyseerrApi, seasons, details, hasAdvancedRequest, onAdvancedRequest]);
|
||||
|
||||
const promptRequestAll = useCallback(
|
||||
() =>
|
||||
Alert.alert("Confirm", "Are you sure you want to request all seasons?", [
|
||||
Alert.alert(t("jellyseerr.confirm"), t("jellyseerr.are_you_sure_you_want_to_request_all_seasons"), [
|
||||
{
|
||||
text: "Cancel",
|
||||
text: t("jellyseerr.cancel"),
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Yes",
|
||||
text: t("jellyseerr.yes"),
|
||||
onPress: requestAll,
|
||||
},
|
||||
]),
|
||||
@@ -172,24 +189,26 @@ const JellyseerrSeasons: React.FC<{
|
||||
|
||||
const requestSeason = useCallback(async (canRequest: Boolean, seasonNumber: number) => {
|
||||
if (canRequest) {
|
||||
requestMedia(
|
||||
`${result?.name!!}, Season ${seasonNumber}`,
|
||||
{
|
||||
mediaId: details.id,
|
||||
mediaType: MediaType.TV,
|
||||
tvdbId: details.externalIds?.tvdbId,
|
||||
seasons: [seasonNumber],
|
||||
},
|
||||
refetch
|
||||
)
|
||||
const body: MediaRequestBody = {
|
||||
mediaId: details.id,
|
||||
mediaType: MediaType.TV,
|
||||
tvdbId: details.externalIds?.tvdbId,
|
||||
seasons: [seasonNumber],
|
||||
}
|
||||
|
||||
if (hasAdvancedRequest) {
|
||||
return onAdvancedRequest?.(body)
|
||||
}
|
||||
|
||||
requestMedia(`${result?.name!!}, Season ${seasonNumber}`, body, refetch);
|
||||
}
|
||||
}, [requestMedia]);
|
||||
}, [requestMedia, hasAdvancedRequest, onAdvancedRequest]);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<View>
|
||||
<View className="flex flex-row justify-between items-end px-4">
|
||||
<Text className="text-lg font-bold mb-2">Seasons</Text>
|
||||
<Text className="text-lg font-bold mb-2">{t("item_card.seasons")}</Text>
|
||||
{!allSeasonsAvailable && (
|
||||
<RoundButton className="mb-2 pa-2" onPress={promptRequestAll}>
|
||||
<Ionicons name="bag-add" color="white" size={26} />
|
||||
@@ -209,7 +228,7 @@ const JellyseerrSeasons: React.FC<{
|
||||
)}
|
||||
ListHeaderComponent={() => (
|
||||
<View className="flex flex-row justify-between items-end px-4">
|
||||
<Text className="text-lg font-bold mb-2">Seasons</Text>
|
||||
<Text className="text-lg font-bold mb-2">{t("item_card.seasons")}</Text>
|
||||
{!allSeasonsAvailable && (
|
||||
<RoundButton className="mb-2 pa-2" onPress={promptRequestAll}>
|
||||
<Ionicons name="bag-add" color="white" size={26} />
|
||||
@@ -237,8 +256,8 @@ const JellyseerrSeasons: React.FC<{
|
||||
<Tags
|
||||
textClass=""
|
||||
tags={[
|
||||
`Season ${season.seasonNumber}`,
|
||||
`${season.episodeCount} Episodes`,
|
||||
t("jellyseerr.season_number", {season_number: season.seasonNumber}),
|
||||
t("jellyseerr.number_episodes", {episode_number: season.episodeCount}),
|
||||
]}
|
||||
/>
|
||||
{[0].map(() => {
|
||||
|
||||
@@ -12,10 +12,12 @@ import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
||||
import { ItemCardText } from "../ItemCardText";
|
||||
import { TouchableItemRouter } from "../common/TouchableItemRouter";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const NextUp: React.FC<{ seriesId: string }> = ({ seriesId }) => {
|
||||
const [user] = useAtom(userAtom);
|
||||
const [api] = useAtom(apiAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: items } = useQuery({
|
||||
queryKey: ["nextUp", seriesId],
|
||||
@@ -37,14 +39,14 @@ export const NextUp: React.FC<{ seriesId: string }> = ({ seriesId }) => {
|
||||
if (!items?.length)
|
||||
return (
|
||||
<View className="px-4">
|
||||
<Text className="text-lg font-bold mb-2">Next up</Text>
|
||||
<Text className="opacity-50">No items to display</Text>
|
||||
<Text className="text-lg font-bold mb-2">{t("item_card.next_up")}</Text>
|
||||
<Text className="opacity-50">{t("item_card.no_items_to_display")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text className="text-lg font-bold px-4 mb-2">Next up</Text>
|
||||
<Text className="text-lg font-bold px-4 mb-2">{t("item_card.next_up")}</Text>
|
||||
<FlashList
|
||||
contentContainerStyle={{ paddingLeft: 16 }}
|
||||
horizontal
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import * as DropdownMenu from "@/components/DropdownMenu";
|
||||
import { Text } from "../common/Text";
|
||||
import { t } from "i18next";
|
||||
|
||||
type Props = {
|
||||
item: BaseItemDto;
|
||||
@@ -91,7 +92,7 @@ export const SeasonDropdown: React.FC<Props> = ({
|
||||
<DropdownMenu.Trigger>
|
||||
<View className="flex flex-row">
|
||||
<TouchableOpacity className="bg-neutral-900 rounded-2xl border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between">
|
||||
<Text>Season {seasonIndex}</Text>
|
||||
<Text>{t("item_card.season")} {seasonIndex}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</DropdownMenu.Trigger>
|
||||
@@ -104,7 +105,7 @@ export const SeasonDropdown: React.FC<Props> = ({
|
||||
collisionPadding={8}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Label>Seasons</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>{t("item_card.seasons")}</DropdownMenu.Label>
|
||||
{seasons?.sort(sortByIndex).map((season: any) => (
|
||||
<DropdownMenu.Item
|
||||
key={season[keys.title]}
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
SeasonIndexState,
|
||||
} from "@/components/series/SeasonDropdown";
|
||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { PlayedStatus } from "../PlayedStatus";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
item: BaseItemDto;
|
||||
@@ -29,6 +31,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
const [seasonIndexState, setSeasonIndexState] = useAtom(seasonIndexAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const seasonIndex = useMemo(
|
||||
() => seasonIndexState[item.Id ?? ""],
|
||||
@@ -144,17 +147,20 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
}}
|
||||
/>
|
||||
{episodes?.length || 0 > 0 ? (
|
||||
<DownloadItems
|
||||
title="Download Season"
|
||||
className="ml-2"
|
||||
items={episodes || []}
|
||||
MissingDownloadIconComponent={() => (
|
||||
<Ionicons name="download" size={20} color="white" />
|
||||
)}
|
||||
DownloadedIconComponent={() => (
|
||||
<Ionicons name="download" size={20} color="#9333ea" />
|
||||
)}
|
||||
/>
|
||||
<View className="flex flex-row items-center space-x-2">
|
||||
<DownloadItems
|
||||
title={t("item_card.download.download_season")}
|
||||
className="ml-2"
|
||||
items={episodes || []}
|
||||
MissingDownloadIconComponent={() => (
|
||||
<Ionicons name="download" size={20} color="white" />
|
||||
)}
|
||||
DownloadedIconComponent={() => (
|
||||
<Ionicons name="download" size={20} color="#9333ea" />
|
||||
)}
|
||||
/>
|
||||
<PlayedStatus items={episodes || []} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View className="px-4 flex flex-col mt-4">
|
||||
@@ -210,7 +216,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
{(episodes?.length || 0) === 0 ? (
|
||||
<View className="flex flex-col">
|
||||
<Text className="text-neutral-500">
|
||||
No episodes for this season
|
||||
{t("item_card.no_episodes_for_this_season")}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user