From 31f5a552db0ababcac701bf4154657577202674f Mon Sep 17 00:00:00 2001 From: Violet Caulfield Date: Wed, 5 Feb 2025 17:59:14 -0600 Subject: [PATCH] Favorite playlists, item detail changes You can now view your favorite playlists in the Favorites tab You can now see an updated layout of the item detail modal, including big clunky buttons for the queuing options and for viewing the album --- api/queries/favorites.ts | 15 ++-- api/queries/functions/favorites.ts | 32 +++++++ components/Albums/screen.tsx | 15 ++-- components/Favorites/component.tsx | 12 +++ components/Favorites/screens/categories.ts | 1 - components/Global/helpers/icon-button.tsx | 9 +- components/ItemDetail/component.tsx | 87 ++++++++++--------- .../ItemDetail/helpers/TrackOptions.tsx | 78 ++++++++++------- components/Playlists/component.tsx | 38 ++++++++ components/Playlists/screen.tsx | 12 +++ components/types.tsx | 2 + enums/query-keys.ts | 1 + 12 files changed, 207 insertions(+), 95 deletions(-) create mode 100644 components/Playlists/component.tsx create mode 100644 components/Playlists/screen.tsx diff --git a/api/queries/favorites.ts b/api/queries/favorites.ts index 933550d9..34bc52f8 100644 --- a/api/queries/favorites.ts +++ b/api/queries/favorites.ts @@ -1,21 +1,20 @@ import { QueryKeys } from "../../enums/query-keys"; import { useQuery } from "@tanstack/react-query"; -import { fetchFavoriteAlbums, fetchFavoriteArtists, fetchFavoriteTracks, fetchUserData } from "./functions/favorites"; +import { fetchFavoriteAlbums, fetchFavoriteArtists, fetchFavoritePlaylists, fetchFavoriteTracks, fetchUserData } from "./functions/favorites"; export const useFavoriteArtists = () => useQuery({ queryKey: [QueryKeys.FavoriteArtists], - queryFn: () => { - - return fetchFavoriteArtists() - } + queryFn: () => fetchFavoriteArtists() }); export const useFavoriteAlbums = () => useQuery({ queryKey: [QueryKeys.FavoriteAlbums], - queryFn: () => { + queryFn: () => fetchFavoriteAlbums() +}); - return fetchFavoriteAlbums() - } +export const useFavoritePlaylists = () => useQuery({ + queryKey: [QueryKeys.FavoritePlaylists], + queryFn: () => fetchFavoritePlaylists() }); export const useFavoriteTracks = () => useQuery({ diff --git a/api/queries/functions/favorites.ts b/api/queries/functions/favorites.ts index 39afe52b..54eaf516 100644 --- a/api/queries/functions/favorites.ts +++ b/api/queries/functions/favorites.ts @@ -70,6 +70,38 @@ export function fetchFavoriteAlbums(): Promise { }) } +export function fetchFavoritePlaylists(): Promise { + console.debug(`Fetching user's favorite playlists`); + + return new Promise(async (resolver, reject) => { + getItemsApi(Client.api!) + .getItems({ + parentId: Client.library!.playlistLibraryId, + includeItemTypes: [ + BaseItemKind.Playlist + ], + isFavorite: true, + sortBy: [ + ItemSortBy.SortName + ], + sortOrder: [ + SortOrder.Ascending + ] + }) + .then((response) => { + if (response.data.Items) + resolver(response.data.Items) + + else + resolver([]) + }) + .catch((error) => { + console.error(error); + reject(error); + }); + }); +} + export function fetchFavoriteTracks(): Promise { console.debug(`Fetching user's favorite tracks`); diff --git a/components/Albums/screen.tsx b/components/Albums/screen.tsx index 47b452e2..79f74603 100644 --- a/components/Albums/screen.tsx +++ b/components/Albums/screen.tsx @@ -1,17 +1,12 @@ import React from "react" import { StackParamList } from "../types" -import { RouteProp } from "@react-navigation/native" -import { NativeStackNavigationProp } from "@react-navigation/native-stack" +import { NativeStackScreenProps } from "@react-navigation/native-stack" import Albums from "./component" -export default function AlbumsScreen({ - route, - navigation -} : { - route: RouteProp, - navigation: NativeStackNavigationProp -}) : React.JSX.Element { +export default function AlbumsScreen( + props: NativeStackScreenProps +) : React.JSX.Element { return ( - + ) } \ No newline at end of file diff --git a/components/Favorites/component.tsx b/components/Favorites/component.tsx index 417d75fa..5e53526e 100644 --- a/components/Favorites/component.tsx +++ b/components/Favorites/component.tsx @@ -9,6 +9,7 @@ import ArtistsScreen from "../Artists/screen"; import AlbumsScreen from "../Albums/screen"; import TracksScreen from "../Tracks/screen"; import DetailsScreen from "../ItemDetail/screen"; +import PlaylistsScreen from "../Playlists/screen"; const LibraryStack = createNativeStackNavigator(); @@ -82,6 +83,17 @@ export default function Library(): React.JSX.Element { }} /> + + void; name: string; + title?: string | undefined; circular?: boolean | undefined; size?: number; } @@ -13,6 +15,7 @@ interface IconButtonProps { export default function IconButton({ name, onPress, + title, circular, size } : IconButtonProps) : React.JSX.Element { @@ -37,7 +40,11 @@ export default function IconButton({ large name={name} color={"$color"} - /> + /> + + { title && ( + { title } + )} diff --git a/components/ItemDetail/component.tsx b/components/ItemDetail/component.tsx index 883df8fc..6e8ef9cf 100644 --- a/components/ItemDetail/component.tsx +++ b/components/ItemDetail/component.tsx @@ -58,7 +58,7 @@ export default function ItemDetail({ return ( - + - - - { item.Name ?? "Untitled Track" } - + {/* Item Name, Artist, Album, and Favorite Button */} + + + + { item.Name ?? "Untitled Track" } + - { - if (item.ArtistItems) { + { + if (item.ArtistItems) { + + if (isNested) + navigation.getParent()!.goBack(); + + navigation.goBack(); + navigation.push("Artist", { + artist: item.ArtistItems[0] + }); + } + }}> + { item.Artists?.join(", ") ?? "Unknown Artist"} + + + + { item.Album ?? "" } + + - if (isNested) - navigation.getParent()!.goBack(); - - navigation.goBack(); - navigation.push("Artist", { - artist: item.ArtistItems[0] - }); - } - }}> - { item.Artists?.join(", ") ?? "Unknown Artist"} - - - - { item.Album ?? "" } - - - - - + + + + - + - { options ?? } - + { options ?? } diff --git a/components/ItemDetail/helpers/TrackOptions.tsx b/components/ItemDetail/helpers/TrackOptions.tsx index 171230a0..7467065a 100644 --- a/components/ItemDetail/helpers/TrackOptions.tsx +++ b/components/ItemDetail/helpers/TrackOptions.tsx @@ -4,8 +4,10 @@ import Icon from "../../../components/Global/helpers/icon"; import { StackParamList } from "../../../components/types"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import { XStack } from "tamagui"; +import { Spacer, XStack, YStack } from "tamagui"; import { QueuingType } from "../../../enums/queuing-type"; +import { useSafeAreaFrame } from "react-native-safe-area-context"; +import IconButton from "@/components/Global/helpers/icon-button"; export default function TrackOptions({ item, @@ -20,43 +22,53 @@ export default function TrackOptions({ const { data: album, isSuccess } = useItem(item.AlbumId ?? ""); const { useAddToQueue } = usePlayerContext(); + + const { width } = useSafeAreaFrame(); return ( - - { isSuccess && ( - + + + { isSuccess ? ( + { + + if (isNested) + navigation.getParent()!.goBack(); + + navigation.goBack(); + navigation.push("Album", { + album + }); + }} + /> + ) : ( + + )} + + { - - if (isNested) - navigation.getParent()!.goBack(); - - navigation.goBack(); - navigation.push("Album", { - album - }); + useAddToQueue.mutate({ + track: item, + queuingType: QueuingType.PlayingNext + }) }} /> - )} - { - useAddToQueue.mutate({ - track: item, - queuingType: QueuingType.PlayingNext - }) - }} - /> - - { - useAddToQueue.mutate({ - track: item - }) - }} - /> - + { + useAddToQueue.mutate({ + track: item + }) + }} + /> + + ) } \ No newline at end of file diff --git a/components/Playlists/component.tsx b/components/Playlists/component.tsx new file mode 100644 index 00000000..15a1ae6e --- /dev/null +++ b/components/Playlists/component.tsx @@ -0,0 +1,38 @@ +import { useFavoritePlaylists } from "@/api/queries/favorites"; +import { FlatList, RefreshControl } from "react-native-gesture-handler"; +import { useSafeAreaFrame } from "react-native-safe-area-context"; +import { ItemCard } from "../Global/components/item-card"; +import { PlaylistsProps } from "../types"; + +export default function Playlists({ navigation }: PlaylistsProps) : React.JSX.Element { + + const { data: playlists, isPending, refetch } = useFavoritePlaylists(); + + const { width } = useSafeAreaFrame(); + + return ( + + } + renderItem={({ index, item: playlist }) => { + return ( + { + navigation.push("Playlist", { playlist }) + }} + width={width / 2.1} + /> + ) + }} + /> + ) +} \ No newline at end of file diff --git a/components/Playlists/screen.tsx b/components/Playlists/screen.tsx new file mode 100644 index 00000000..3d42de05 --- /dev/null +++ b/components/Playlists/screen.tsx @@ -0,0 +1,12 @@ +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { StackParamList } from "../types"; +import Playlists from "./component"; +import React from "react"; + +export default function PlaylistsScreen( + props: NativeStackScreenProps +) : React.JSX.Element { + return ( + + ) +} \ No newline at end of file diff --git a/components/types.tsx b/components/types.tsx index ad16d86d..f381ec48 100644 --- a/components/types.tsx +++ b/components/types.tsx @@ -62,6 +62,8 @@ export type ArtistsProps = NativeStackScreenProps; export type AlbumsProps = NativeStackScreenProps; +export type PlaylistsProps = NativeStackScreenProps; + export type TracksProps = NativeStackScreenProps; export type GenresProps = NativeStackScreenProps; diff --git a/enums/query-keys.ts b/enums/query-keys.ts index b90fa46c..982cb908 100644 --- a/enums/query-keys.ts +++ b/enums/query-keys.ts @@ -38,4 +38,5 @@ export enum QueryKeys { Item = "Item", Search = "Search", SearchSuggestions = "SearchSuggestions", + FavoritePlaylists = "FavoritePlaylists", } \ No newline at end of file