diff --git a/src/api/queries/artist/index.ts b/src/api/queries/artist/index.ts index 34d11160..5d2181c6 100644 --- a/src/api/queries/artist/index.ts +++ b/src/api/queries/artist/index.ts @@ -8,6 +8,7 @@ import { } from '@tanstack/react-query' import { isUndefined } from 'lodash' import { fetchArtistAlbums, fetchArtistFeaturedOn, fetchArtists } from './utils/artist' +import fetchArtistTracks from './utils/tracks' import { ApiLimits } from '../query.config' import { RefObject, useCallback, useRef } from 'react' import { useLibrarySortAndFilterContext } from '../../../providers/Library' @@ -36,6 +37,53 @@ export const useArtistFeaturedOn = (artist: BaseItemDto) => { }) } +export const useArtistTracks = ( + artist: BaseItemDto, + isFavorite: boolean, + sortDescending: boolean, + sortBy: ItemSortBy = ItemSortBy.SortName, +) => { + const api = useApi() + const [user] = useJellifyUser() + const [library] = useJellifyLibrary() + + const trackPageParams = useRef>(new Set()) + + const selectTracks = useCallback( + (data: InfiniteData) => + flattenInfiniteQueryPages(data, trackPageParams), + [], + ) + + return useInfiniteQuery({ + queryKey: [ + QueryKeys.ArtistTracks, + library?.musicLibraryId, + artist.Id, + isFavorite, + sortDescending, + sortBy, + ], + queryFn: ({ pageParam }) => + fetchArtistTracks( + api, + user, + library, + artist, + pageParam, + isFavorite ? true : undefined, + sortBy, + sortDescending ? SortOrder.Descending : SortOrder.Ascending, + ), + initialPageParam: 0, + getNextPageParam: (lastPage, allPages, lastPageParam) => { + return lastPage.length === ApiLimits.Library ? lastPageParam + 1 : undefined + }, + select: selectTracks, + enabled: !isUndefined(artist.Id), + }) +} + export const useAlbumArtists: () => [ RefObject>, UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>, diff --git a/src/api/queries/artist/utils/tracks.ts b/src/api/queries/artist/utils/tracks.ts new file mode 100644 index 00000000..9489c474 --- /dev/null +++ b/src/api/queries/artist/utils/tracks.ts @@ -0,0 +1,56 @@ +import { JellifyLibrary } from '../../../../types/JellifyLibrary' +import { Api } from '@jellyfin/sdk' +import { + BaseItemDto, + BaseItemKind, + ItemFields, + ItemSortBy, + SortOrder, +} from '@jellyfin/sdk/lib/generated-client/models' +import { getItemsApi } from '@jellyfin/sdk/lib/utils/api' +import { isUndefined } from 'lodash' +import { ApiLimits } from '../../query.config' +import { JellifyUser } from '../../../../types/JellifyUser' + +export default function fetchArtistTracks( + api: Api | undefined, + user: JellifyUser | undefined, + library: JellifyLibrary | undefined, + artist: BaseItemDto, + pageParam: number, + isFavorite: boolean | undefined, + sortBy: ItemSortBy = ItemSortBy.SortName, + sortOrder: SortOrder = SortOrder.Ascending, +) { + console.debug('Fetching artist tracks', pageParam) + return new Promise((resolve, reject) => { + if (isUndefined(api)) return reject('Client instance not set') + if (isUndefined(library)) return reject('Library instance not set') + if (isUndefined(user)) return reject('User instance not set') + if (isUndefined(artist.Id)) return reject('Artist ID not set') + + getItemsApi(api) + .getItems({ + includeItemTypes: [BaseItemKind.Audio], + parentId: library.musicLibraryId, + enableUserData: true, + userId: user.id, + recursive: true, + artistIds: [artist.Id], + isFavorite: isFavorite, + limit: ApiLimits.Library, + startIndex: pageParam * ApiLimits.Library, + sortBy: [sortBy], + sortOrder: [sortOrder], + fields: [ItemFields.SortName], + }) + .then((response) => { + if (response.data.Items) return resolve(response.data.Items) + else return resolve([]) + }) + .catch((error) => { + console.error(error) + return reject(error) + }) + }) +} diff --git a/src/components/Artist/index.tsx b/src/components/Artist/index.tsx index 024ee076..517f65b0 100644 --- a/src/components/Artist/index.tsx +++ b/src/components/Artist/index.tsx @@ -8,9 +8,13 @@ import ItemRow from '../Global/components/item-row' import ArtistHeader from './header' import { Text } from '../Global/helpers/text' import SimilarArtists from './similar' -import { SafeAreaView } from 'react-native-safe-area-context' +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs' +import ArtistTracks from './tracks' +import { useTheme } from 'tamagui' -export default function ArtistNavigation({ +const Tab = createMaterialTopTabNavigator() + +function ArtistOverview({ navigation, }: { navigation: NativeStackNavigationProp @@ -66,3 +70,26 @@ export default function ArtistNavigation({ /> ) } + +export default function ArtistNavigation({ + navigation, +}: { + navigation: NativeStackNavigationProp +}): React.JSX.Element { + const theme = useTheme() + + return ( + + + {() => } + + {() => } + + ) +} diff --git a/src/components/Artist/tracks.tsx b/src/components/Artist/tracks.tsx new file mode 100644 index 00000000..ca75b0c8 --- /dev/null +++ b/src/components/Artist/tracks.tsx @@ -0,0 +1,87 @@ +import React, { useState } from 'react' +import { useArtistContext } from '../../providers/Artist' +import { useArtistTracks } from '../../api/queries/artist' +import { FlashList } from '@shopify/flash-list' +import ItemRow from '../Global/components/item-row' +import { XStack, Button, Text, YStack, Spinner } from 'tamagui' +import { ItemSortBy, BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' +import Icon from '../Global/components/icon' +import { NativeStackNavigationProp } from '@react-navigation/native-stack' +import { BaseStackParamList } from '@/src/screens/types' + +export default function ArtistTracks({ + navigation, +}: { + navigation: NativeStackNavigationProp +}) { + const { artist } = useArtistContext() + const [isFavorite, setIsFavorite] = useState(false) + const [sortDescending, setSortDescending] = useState(false) + const [sortBy, setSortBy] = useState(ItemSortBy.Name) + + const { data, fetchNextPage, hasNextPage, isFetching } = useArtistTracks( + artist, + isFavorite, + sortDescending, + sortBy, + ) + + const tracks = data ?? [] + + const toggleFavorite = () => setIsFavorite(!isFavorite) + const toggleSortOrder = () => setSortDescending(!sortDescending) + const toggleSortBy = () => { + setSortBy((prev) => (prev === ItemSortBy.Name ? ItemSortBy.PremiereDate : ItemSortBy.Name)) + } + + return ( + + + +