Merge pull request #123 from anultravioletaurora/9-again-implement-playlist-crud

9 again implement playlist crud
This commit is contained in:
Violet Caulfield
2025-02-13 15:10:20 -06:00
committed by GitHub
12 changed files with 112 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
import { BaseItemDto, MediaType } from "@jellyfin/sdk/lib/generated-client/models";
import Client from "../../../api/client";
import { getPlaylistsApi } from "@jellyfin/sdk/lib/utils/api";
import { getLibraryApi, getPlaylistsApi } from "@jellyfin/sdk/lib/utils/api";
export async function addToPlaylist(track: BaseItemDto, playlist: BaseItemDto) {
@@ -40,7 +40,7 @@ export async function reorderPlaylist(playlistId: string, itemId: string, to: nu
}
export async function createPlaylist(name: string) {
console.debug("Creating new playlist");
console.debug("Creating new playlist...");
return getPlaylistsApi(Client.api!)
.createPlaylist({
@@ -52,6 +52,15 @@ export async function createPlaylist(name: string) {
});
}
export async function deletePlaylist(playlistId: string) {
console.debug("Deleting playlist...");
return getLibraryApi(Client.api!)
.deleteItem({
itemId: playlistId
})
}
/**
* Updates a Jellyfin playlist with the provided options.
*

View File

@@ -20,7 +20,7 @@ export function fetchUserPlaylists(
fields: [
"Path"
],
sortBy: defaultSorting.concat(sortBy),
sortBy: sortBy.concat(defaultSorting),
sortOrder: [
SortOrder.Ascending
]

View File

@@ -18,7 +18,7 @@ interface LabelProps {
export function Label(props: LabelProps): React.JSX.Element {
return (
<TamaguiLabel htmlFor={props.htmlFor} justifyContent="flex-end">{ props.children }</TamaguiLabel>
<TamaguiLabel fontWeight={600} htmlFor={props.htmlFor} justifyContent="flex-end">{ props.children }</TamaguiLabel>
)
}

View File

@@ -1,13 +1,11 @@
import { StackParamList } from "../types";
import { ScrollView, RefreshControl } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { YStack, XStack, Separator } from "tamagui";
import Playlists from "./helpers/playlists";
import RecentArtists from "./helpers/recent-artists";
import RecentlyPlayed from "./helpers/recently-played";
import { useHomeContext } from "./provider";
import { H3 } from "../Global/helpers/text";
import Avatar from "../Global/components/avatar";
import Client from "../../api/client";
import { usePlayerContext } from "../../player/provider";
import { useEffect } from "react";
@@ -30,7 +28,6 @@ export function ProvidedHome({
])
return (
<SafeAreaView edges={["top", "right", "left"]}>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
refreshControl={
@@ -57,6 +54,5 @@ export function ProvidedHome({
<Playlists navigation={navigation}/>
</YStack>
</ScrollView>
</SafeAreaView>
);
}

View File

@@ -26,10 +26,10 @@ export default function Home(): React.JSX.Element {
name="Home"
component={ProvidedHome}
options={{
headerLargeTitle: true,
headerLargeTitleStyle: {
fontFamily: 'Aileron-Bold'
}
// headerLargeTitle: true,
// headerLargeTitleStyle: {
// fontFamily: 'Aileron-Bold'
// }
}}
/>

View File

@@ -35,9 +35,9 @@ export default function AddPlaylist({
navigation.goBack();
// Refresh user playlists component on home screen
// Refresh user playlists component in library
queryClient.invalidateQueries({
queryKey: [QueryKeys.UserPlaylists]
queryKey: [QueryKeys.FavoritePlaylists]
});
},
onError: () => {

View File

@@ -0,0 +1,57 @@
import { View, XStack } from "tamagui";
import { DeletePlaylistProps } from "../../../components/types";
import Button from "../../../components/Global/helpers/button";
import { Text } from "../../../components/Global/helpers/text";
import { useMutation } from "@tanstack/react-query";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { deletePlaylist } from "../../../api/mutations/functions/playlists";
import { trigger } from "react-native-haptic-feedback";
import { queryClient } from "../../../constants/query-client";
import { QueryKeys } from "../../../enums/query-keys";
import * as Burnt from "burnt";
export default function DeletePlaylist(
{
navigation,
route
}: DeletePlaylistProps) : React.JSX.Element {
const useDeletePlaylist = useMutation({
mutationFn: (playlist: BaseItemDto) => deletePlaylist(playlist.Id!),
onSuccess: (data, playlist) => {
trigger("notificationSuccess");
navigation.goBack();
navigation.goBack();
Burnt.alert({
title: `Playlist deleted`,
message: `Deleted ${playlist.Name ?? "Untitled Playlist"}`,
duration: 1,
preset: 'done'
});
// Refresh user playlists component in library
queryClient.invalidateQueries({
queryKey: [QueryKeys.FavoritePlaylists]
});
},
onError: () => {
trigger("notificationError");
}
})
return (
<View marginHorizontal={"$2"}>
<Text bold textAlign="center">{`Delete playlist ${route.params.playlist.Name ?? "Untitled Playlist"}?`}</Text>
<XStack justifyContent="space-evenly">
<Button onPress={() => navigation.goBack()}>Cancel</Button>
<Button danger onPress={() => useDeletePlaylist.mutate(route.params.playlist)}>Delete</Button>
</XStack>
</View>
)
}

View File

@@ -11,6 +11,7 @@ import TracksScreen from "../Tracks/screen";
import DetailsScreen from "../ItemDetail/screen";
import PlaylistsScreen from "../Playlists/screen";
import AddPlaylist from "./components/add-playlist";
import DeletePlaylist from "./components/delete-playlist";
const Stack = createNativeStackNavigator<StackParamList>();
@@ -111,6 +112,14 @@ export default function LibraryStack(): React.JSX.Element {
title: "Add Playlist",
}}
/>
<Stack.Screen
name="DeletePlaylist"
component={DeletePlaylist}
options={{
title: "Delete Playlist"
}}
/>
</Stack.Group>
</Stack.Navigator>

View File

@@ -73,7 +73,7 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers<Para
color={theme.borderColor.val}
name="skip-next"
onPress={() => useSkip.mutate(undefined)}
/>
/>
</XStack>
</XStack>
)

View File

@@ -1,7 +1,7 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { StackParamList } from "../types";
import { getTokens, Separator, XStack, YStack } from "tamagui";
import { getToken, Separator, Spacer, XStack, YStack } from "tamagui";
import { useItemTracks } from "../../api/queries/tracks";
import { RunTimeTicks } from "../Global/helpers/time-codes";
import { H4, H5, Text } from "../Global/helpers/text";
@@ -45,14 +45,26 @@ export default function Playlist({
navigation.setOptions({
headerRight: () => {
return (
<Icon
color={editing
? getTokens().color.telemagenta.val
: getTokens().color.white.val
}
name={editing ? 'check' : 'pencil'}
onPress={() => setEditing(!editing)}
/>
<XStack justifyContent="space-between">
{ editing && (
<Icon
color={getToken("$color.danger")}
name="delete-sweep-outline" // otherwise use "delete-circle"
onPress={() => navigation.navigate("DeletePlaylist", { playlist })}
/>
)}
<Spacer />
<Icon
color={getToken("$color.amethyst")}
name={editing ? 'content-save-outline' : 'pencil'}
onPress={() => setEditing(!editing)}
/>
</XStack>
)
}
});

View File

@@ -26,6 +26,9 @@ export type StackParamList = {
Tracks: undefined;
Genres: undefined;
Playlists: undefined;
DeletePlaylist: {
playlist: BaseItemDto
}
Search: undefined;
@@ -84,6 +87,7 @@ export type ArtistsProps = NativeStackScreenProps<StackParamList, "Artists">;
export type AlbumsProps = NativeStackScreenProps<StackParamList, "Albums">;
export type FavoritePlaylistsProps = NativeStackScreenProps<StackParamList, "Playlists">;
export type DeletePlaylistProps = NativeStackScreenProps<StackParamList, "DeletePlaylist">;
export type FavoriteTracksProps = NativeStackScreenProps<StackParamList, "Tracks">;

View File

@@ -5,6 +5,7 @@ import { headingFont, bodyFont } from './fonts.config'
const tokens = createTokens({
...TamaguiTokens,
color: {
danger: "#ff0000",
purpleDark: "#0C0622",
purple: "#100538",
purpleGray: "#66617B",