mirror of
https://github.com/Jellify-Music/App.git
synced 2026-02-21 19:28:59 -06:00
add playlist support on home screen
refactor album stuff so that it works for playlists since they're like the same
This commit is contained in:
@@ -4,19 +4,3 @@ import { QueryKeys } from "../../enums/query-keys";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { ItemSortBy } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
export const useAlbumTracks = (albumId: string, api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.AlbumTracks, albumId, api],
|
||||
queryFn: ({ queryKey }) => {
|
||||
return getItemsApi(queryKey[2] as Api).getItems({
|
||||
parentId: albumId,
|
||||
sortBy: [
|
||||
ItemSortBy.ParentIndexNumber,
|
||||
ItemSortBy.IndexNumber,
|
||||
ItemSortBy.SortName
|
||||
]
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data.Items ? response.data.Items! : [];
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -20,7 +20,35 @@ export function fetchUserPlaylists(api: Api, userId: string, playlistLibraryId:
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.Items)
|
||||
resolve(response.data.Items)
|
||||
resolve(response.data.Items.filter(playlist => playlist.Path?.includes("/config/data/playlists")))
|
||||
else
|
||||
resolve([]);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPublicPlaylists(api: Api, playlistLibraryId: string): Promise<BaseItemDto[]> {
|
||||
console.debug("Fetching public playlists");
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
.getItems({
|
||||
parentId: playlistLibraryId,
|
||||
sortBy: [
|
||||
ItemSortBy.IsFolder,
|
||||
ItemSortBy.SortName
|
||||
],
|
||||
sortOrder: [
|
||||
SortOrder.Ascending
|
||||
]
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.Items)
|
||||
resolve(response.data.Items.filter(playlist => !playlist.Path?.includes("/config/data/playlists")))
|
||||
else
|
||||
resolve([]);
|
||||
})
|
||||
|
||||
@@ -12,4 +12,5 @@ export const useUserPlaylists = (api: Api, userId: string, playlistLibraryId: st
|
||||
|
||||
return fetchUserPlaylists(api, userId, playlistLibraryId);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
26
api/queries/tracks.ts
Normal file
26
api/queries/tracks.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { QueryKeys } from "@/enums/query-keys";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { ItemSortBy } from "@jellyfin/sdk/lib/generated-client/models/item-sort-by";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api/items-api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
export const useItemTracks = (itemId: string, api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, itemId, api],
|
||||
queryFn: ({ queryKey }) => {
|
||||
|
||||
const itemId : string = queryKey[1] as string;
|
||||
const api : Api = queryKey[2] as Api;
|
||||
|
||||
return getItemsApi(api).getItems({
|
||||
parentId: itemId,
|
||||
sortBy: [
|
||||
ItemSortBy.ParentIndexNumber,
|
||||
ItemSortBy.IndexNumber,
|
||||
ItemSortBy.SortName
|
||||
]
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data.Items ? response.data.Items! : [];
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -8,10 +8,10 @@ import { BaseItemDto, ImageType } from "@jellyfin/sdk/lib/generated-client/model
|
||||
import { queryConfig } from "../../api/queries/query.config";
|
||||
import { H4, H5, Text } from "../Global/helpers/text";
|
||||
import { FlatList } from "react-native";
|
||||
import { useAlbumTracks } from "../../api/queries/album";
|
||||
import { usePlayerContext } from "../../player/provider";
|
||||
import RunTimeTicks from "../Global/helpers/runtimeticks";
|
||||
import Track from "../Global/components/track";
|
||||
import { useItemTracks } from "@/api/queries/tracks";
|
||||
|
||||
interface AlbumProps {
|
||||
album: BaseItemDto,
|
||||
@@ -22,9 +22,9 @@ export default function Album(props: AlbumProps): React.JSX.Element {
|
||||
|
||||
const { apiClient, sessionId } = useApiClientContext();
|
||||
|
||||
const { resetQueue, addToQueue, play, nowPlaying } = usePlayerContext();
|
||||
const { nowPlaying } = usePlayerContext();
|
||||
|
||||
const { data: tracks, isLoading } = useAlbumTracks(props.album.Id!, apiClient!);
|
||||
const { data: tracks, isLoading } = useItemTracks(props.album.Id!, apiClient!);
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
|
||||
@@ -29,7 +29,9 @@ export default function Artist(props: ArtistProps): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<SafeAreaView edges={["top", "right", "left"]}>
|
||||
<ScrollView alignContent="center">
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
alignContent="center">
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
.getItemImageUrlById(
|
||||
|
||||
@@ -27,23 +27,21 @@ export default function Track({
|
||||
showArtwork?: boolean | undefined
|
||||
}) : React.JSX.Element {
|
||||
|
||||
const { apiClient, sessionId } = useApiClientContext();
|
||||
const { nowPlaying, playNewQueue } = usePlayerContext();
|
||||
|
||||
const { nowPlaying, resetQueue, addToQueue, play } = usePlayerContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
let isPlaying = nowPlaying?.ItemId === track.Id
|
||||
const isPlaying = nowPlaying?.ItemId === track.Id
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Separator />
|
||||
<XStack
|
||||
flex={1}
|
||||
onPress={async () => {
|
||||
await resetQueue(false)
|
||||
await addToQueue(tracklist.map((track) => mapDtoToTrack(apiClient!, sessionId, track)));
|
||||
play(index);
|
||||
onPress={() => {
|
||||
playNewQueue.mutate({
|
||||
track,
|
||||
index,
|
||||
tracklist
|
||||
});
|
||||
}}
|
||||
paddingVertical={"$2"}
|
||||
paddingHorizontal={"$1"}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { useUserPlaylists } from "@/api/queries/playlist";
|
||||
import { Card } from "@/components/Global/helpers/card";
|
||||
import { H2 } from "@/components/Global/helpers/text";
|
||||
import { useApiClientContext } from "@/components/jellyfin-api-provider";
|
||||
import { ProvidedHomeProps } from "@/components/types";
|
||||
import React from "react";
|
||||
import { FlatList } from "react-native";
|
||||
import { ScrollView, View } from "tamagui";
|
||||
import { View } from "tamagui";
|
||||
|
||||
export default function Playlists() : React.JSX.Element {
|
||||
export default function Playlists({ navigation }: ProvidedHomeProps) : React.JSX.Element {
|
||||
|
||||
const { apiClient, user, library } = useApiClientContext();
|
||||
|
||||
@@ -23,7 +24,9 @@ export default function Playlists() : React.JSX.Element {
|
||||
itemId={playlist.Id!}
|
||||
caption={playlist.Name ?? "Untitled Playlist"}
|
||||
onPress={() => {
|
||||
|
||||
navigation.navigate('Playlist', {
|
||||
playlist
|
||||
})
|
||||
}} />
|
||||
)
|
||||
}} />
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { BaseItemDto, ImageType } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { StackParamList } from "../types";
|
||||
import { ScrollView } from "tamagui";
|
||||
import { ScrollView, XStack, YStack } from "tamagui";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import { usePlayerContext } from "@/player/provider";
|
||||
import { useItemTracks } from "@/api/queries/tracks";
|
||||
import RunTimeTicks from "../Global/helpers/runtimeticks";
|
||||
import { H4, H5, Text } from "../Global/helpers/text";
|
||||
import Track from "../Global/components/track";
|
||||
import { FlatList } from "react-native";
|
||||
import { queryConfig } from "@/api/queries/query.config";
|
||||
import { getImageApi } from "@jellyfin/sdk/lib/utils/api/image-api";
|
||||
import CachedImage from "@georstat/react-native-image-cache/lib/typescript/CachedImage";
|
||||
|
||||
interface PlaylistProps {
|
||||
playlist: BaseItemDto;
|
||||
@@ -9,9 +19,58 @@ interface PlaylistProps {
|
||||
}
|
||||
|
||||
export default function Playlist(props: PlaylistProps): React.JSX.Element {
|
||||
|
||||
const { apiClient, sessionId } = useApiClientContext();
|
||||
|
||||
const { playNewQueue, nowPlaying } = usePlayerContext();
|
||||
|
||||
const { data: tracks, isLoading } = useItemTracks(props.playlist.Id!, apiClient!);
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
|
||||
</ScrollView>
|
||||
<YStack alignItems="center">
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
.getItemImageUrlById(
|
||||
props.playlist.Id!,
|
||||
ImageType.Primary,
|
||||
{ ...queryConfig.images})}
|
||||
imageStyle={{
|
||||
position: "relative",
|
||||
width: 300,
|
||||
height: 300,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
|
||||
<H4>{ props.playlist.Name ?? "Untitled Playlist" }</H4>
|
||||
<H5>{ props.playlist.ProductionYear?.toString() ?? "" }</H5>
|
||||
</YStack>
|
||||
<FlatList
|
||||
data={tracks}
|
||||
extraData={nowPlaying}
|
||||
numColumns={1}
|
||||
renderItem={({ item: track, index }) => {
|
||||
|
||||
return (
|
||||
<Track
|
||||
track={track}
|
||||
tracklist={tracks!}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
|
||||
}}/>
|
||||
|
||||
<XStack justifyContent="flex-end">
|
||||
<Text
|
||||
color={"$gray10"}
|
||||
style={{ display: "block"}}
|
||||
>
|
||||
Total Runtime:
|
||||
</Text>
|
||||
<RunTimeTicks>{ props.playlist.RunTimeTicks }</RunTimeTicks>
|
||||
</XStack>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
@@ -28,4 +28,5 @@ export enum QueryKeys {
|
||||
PlaybackStateChange = "PlaybackStateChange",
|
||||
Player = "Player",
|
||||
UserPlaylists = "UserPlaylists",
|
||||
ItemTracks = "ItemTracks",
|
||||
}
|
||||
Reference in New Issue
Block a user