-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
369 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import {Text} from "@/components/common/Text"; | ||
import {useDownload} from "@/providers/DownloadProvider"; | ||
import {router, useLocalSearchParams, useNavigation} from "expo-router"; | ||
import React, {useEffect, useMemo, useState} from "react"; | ||
import {ScrollView, View} from "react-native"; | ||
import {EpisodeCard} from "@/components/downloads/EpisodeCard"; | ||
import {BaseItemDto} from "@jellyfin/sdk/lib/generated-client/models"; | ||
import {SeasonDropdown, SeasonIndexState} from "@/components/series/SeasonDropdown"; | ||
import {storage} from "@/utils/mmkv"; | ||
|
||
export default function page() { | ||
const navigation = useNavigation(); | ||
const local = useLocalSearchParams(); | ||
const {seriesId, episodeSeasonIndex} = local as { | ||
seriesId: string, | ||
episodeSeasonIndex: number | string | undefined | ||
}; | ||
|
||
const [seasonIndexState, setSeasonIndexState] = useState<SeasonIndexState>({}); | ||
const {downloadedFiles} = useDownload(); | ||
|
||
const series = useMemo(() => { | ||
try { | ||
return downloadedFiles | ||
?.filter((f) => f.item.SeriesId == seriesId) | ||
?.sort((a, b) => a?.item.ParentIndexNumber! - b.item.ParentIndexNumber!) | ||
|| []; | ||
} catch { | ||
return []; | ||
} | ||
}, [downloadedFiles]); | ||
|
||
const seasonIndex = seasonIndexState[series?.[0]?.item?.ParentId ?? ""] || episodeSeasonIndex || ""; | ||
|
||
const groupBySeason = useMemo<BaseItemDto[]>(() => { | ||
const seasons: Record<string, BaseItemDto[]> = {}; | ||
|
||
series?.forEach((episode) => { | ||
if (!seasons[episode.item.ParentIndexNumber!]) { | ||
seasons[episode.item.ParentIndexNumber!] = []; | ||
} | ||
|
||
seasons[episode.item.ParentIndexNumber!].push(episode.item); | ||
}); | ||
return seasons[seasonIndex] | ||
?.sort((a, b) => a.IndexNumber! - b.IndexNumber!) | ||
?? [] | ||
}, [series, seasonIndex]); | ||
|
||
const initialSeasonIndex = useMemo(() => | ||
Object.values(groupBySeason)?.[0]?.ParentIndexNumber ?? series?.[0]?.item?.ParentIndexNumber, | ||
[groupBySeason] | ||
); | ||
|
||
useEffect(() => { | ||
if (series.length > 0) { | ||
navigation.setOptions({ | ||
title: series[0].item.SeriesName, | ||
}); | ||
} | ||
else { | ||
storage.delete(seriesId); | ||
router.back(); | ||
} | ||
}, [series]); | ||
|
||
return ( | ||
<> | ||
{series.length > 0 && <View className="my-4 flex flex-row items-center justify-start"> | ||
<SeasonDropdown | ||
item={series[0].item} | ||
seasons={series.map(s => s.item)} | ||
state={seasonIndexState} | ||
initialSeasonIndex={initialSeasonIndex!} | ||
onSelect={(season) => { | ||
setSeasonIndexState((prev) => ({ | ||
...prev, | ||
[series[0].item.ParentId ?? ""]: season.ParentIndexNumber, | ||
})); | ||
}}/> | ||
<View className="bg-purple-600 rounded-full h-6 w-6 flex items-center justify-center"> | ||
<Text className="text-xs font-bold">{groupBySeason.length}</Text> | ||
</View> | ||
</View>} | ||
<ScrollView key={seasonIndex}> | ||
{groupBySeason.map((episode) => ( | ||
<View className="px-4 flex flex-col my-4"> | ||
<EpisodeCard item={episode}/> | ||
</View> | ||
))} | ||
</ScrollView> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,51 @@ | ||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; | ||
import { ScrollView, View } from "react-native"; | ||
import { EpisodeCard } from "./EpisodeCard"; | ||
import {TouchableOpacity, View} from "react-native"; | ||
import { Text } from "../common/Text"; | ||
import { useMemo } from "react"; | ||
import { SeasonPicker } from "../series/SeasonPicker"; | ||
import React, {useMemo} from "react"; | ||
import {storage} from "@/utils/mmkv"; | ||
import {Image} from "expo-image"; | ||
import {Ionicons} from "@expo/vector-icons"; | ||
import {router} from "expo-router"; | ||
|
||
export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => { | ||
const groupBySeason = useMemo(() => { | ||
const seasons: Record<string, BaseItemDto[]> = {}; | ||
|
||
items.forEach((item) => { | ||
if (!seasons[item.SeasonName!]) { | ||
seasons[item.SeasonName!] = []; | ||
} | ||
|
||
seasons[item.SeasonName!].push(item); | ||
}); | ||
|
||
return Object.values(seasons).sort( | ||
(a, b) => a[0].IndexNumber! - b[0].IndexNumber! | ||
); | ||
}, [items]); | ||
|
||
const sortByIndex = (a: BaseItemDto, b: BaseItemDto) => { | ||
return a.IndexNumber! > b.IndexNumber! ? 1 : -1; | ||
}; | ||
export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({items}) => { | ||
const base64Image = useMemo(() => { | ||
return storage.getString(items[0].SeriesId!); | ||
}, []); | ||
|
||
return ( | ||
<View> | ||
<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> | ||
<TouchableOpacity onPress={() => router.push(`/downloads/${items[0].SeriesId}`)}> | ||
{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 | ||
className="bg-purple-600 rounded-full h-6 w-6 flex items-center justify-center absolute bottom-1 right-1"> | ||
<Text className="text-xs font-bold">{items.length}</Text> | ||
</View> | ||
</View> | ||
</View> | ||
|
||
<Text className="opacity-50 mb-2 px-4">TV-Series</Text> | ||
{groupBySeason.map((seasonItems, seasonIndex) => ( | ||
<View key={seasonIndex}> | ||
<Text className="mb-2 font-semibold px-4"> | ||
{seasonItems[0].SeasonName} | ||
</Text> | ||
<ScrollView horizontal showsHorizontalScrollIndicator={false}> | ||
<View className="px-4 flex flex-row"> | ||
{seasonItems.sort(sortByIndex)?.map((item, index) => ( | ||
<EpisodeCard item={item} key={index} /> | ||
))} | ||
</View> | ||
</ScrollView> | ||
) : ( | ||
<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> | ||
))} | ||
</View> | ||
)} | ||
|
||
<View className="w-28 mt-2 flex flex-col"> | ||
<Text numberOfLines={2} className="">{items[0].SeriesName}</Text> | ||
<Text className="text-xs opacity-50">{items[0].ProductionYear}</Text> | ||
</View> | ||
</TouchableOpacity> | ||
); | ||
}; |
Oops, something went wrong.