Skip to content

Commit

Permalink
feat: 197 move items in a playlist
Browse files Browse the repository at this point in the history
add track actions to move items up and down in the current playlist

Closes: 197
  • Loading branch information
cobbinma committed Jul 26, 2023
1 parent 335ea4b commit 33b61fe
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 2 deletions.
52 changes: 52 additions & 0 deletions spotify_player/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,23 @@ impl Client {
state.player.write().queue = Some(queue);
}
}
ClientRequest::ReorderPlaylistItems {
playlist_id,
insert_before,
range_start,
range_length,
snapshot_id,
} => {
self.reorder_playlist_items(
state,
playlist_id,
insert_before,
range_start,
range_length,
snapshot_id.as_deref(),
)
.await?;
}
};

tracing::info!(
Expand Down Expand Up @@ -866,6 +883,41 @@ impl Client {
Ok(())
}

/// reorder items in a playlist
pub async fn reorder_playlist_items(
&self,
state: &SharedState,
playlist_id: PlaylistId<'_>,
insert_before: usize,
range_start: usize,
range_length: Option<usize>,
snapshot_id: Option<&str>,
) -> Result<()> {
self.spotify
.playlist_reorder_items(
playlist_id.clone(),
Some(range_start as i32),
Some(insert_before as i32),
range_length.map(|range_length| range_length as u32),
snapshot_id,
)
.await?;

// After making a reorder request, update the playlist in-memory data stored inside the app caches.
if let Some(Context::Playlist { tracks, .. }) = state
.data
.write()
.caches
.context
.get_mut(&playlist_id.uri())
{
let track = tracks.remove(range_start);
tracks.insert(insert_before, track);
}

Ok(())
}

/// adds a Spotify item to current user's library.
/// Before adding new item, the function checks if that item already exists in the library
/// to avoid adding a duplicated item.
Expand Down
4 changes: 3 additions & 1 deletion spotify_player/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub enum Command {
ReverseTrackOrder,
}

#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TrackAction {
GoToArtist,
GoToAlbum,
Expand All @@ -78,6 +78,8 @@ pub enum TrackAction {
AddToQueue,
AddToPlaylist,
DeleteFromCurrentPlaylist,
MoveUpInCurrentPlaylist,
MoveDownInCurrentPlaylist,
AddToLikedTracks,
DeleteFromLikedTracks,
CopyTrackLink,
Expand Down
7 changes: 7 additions & 0 deletions spotify_player/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ pub enum ClientRequest {
AddTrackToQueue(TrackId<'static>),
AddTrackToPlaylist(PlaylistId<'static>, TrackId<'static>),
DeleteTrackFromPlaylist(PlaylistId<'static>, TrackId<'static>),
ReorderPlaylistItems {
playlist_id: PlaylistId<'static>,
insert_before: usize,
range_start: usize,
range_length: Option<usize>,
snapshot_id: Option<String>,
},
AddToLibrary(Item),
DeleteFromLibrary(ItemId),
ConnectDevice(Option<String>),
Expand Down
60 changes: 60 additions & 0 deletions spotify_player/src/event/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,66 @@ fn handle_nth_action(
}
ui.popup = None;
}
TrackAction::MoveUpInCurrentPlaylist => {
if let PageState::Context {
id: Some(ContextId::Playlist(playlist_id)),
state: Some(ContextPageUIState::Playlist { track_table }),
..
} = ui.current_page()
{
if let (Some(_), Some(selected)) = (
state
.data
.read()
.user_data
.playlists
.iter()
.find(|playlist| &playlist.id == playlist_id),
track_table.selected(),
) {
let insert_before = selected - 1;
client_pub.send(ClientRequest::ReorderPlaylistItems {
playlist_id: playlist_id.clone_static(),
insert_before,
range_start: selected,
range_length: None,
snapshot_id: None,
})?;
ui.current_page_mut().select(insert_before);
};
}
ui.popup = None;
}
TrackAction::MoveDownInCurrentPlaylist => {
if let PageState::Context {
id: Some(ContextId::Playlist(playlist_id)),
state: Some(ContextPageUIState::Playlist { track_table }),
..
} = ui.current_page()
{
if let (Some(_), Some(selected)) = (
state
.data
.read()
.user_data
.playlists
.iter()
.find(|playlist| &playlist.id == playlist_id),
track_table.selected(),
) {
let insert_before = selected + 1;
client_pub.send(ClientRequest::ReorderPlaylistItems {
playlist_id: playlist_id.clone_static(),
insert_before,
range_start: selected,
range_length: None,
snapshot_id: None,
})?;
ui.current_page_mut().select(insert_before);
};
}
ui.popup = None;
}
},
ActionListItem::Album(album, actions) => match actions[n] {
AlbumAction::GoToArtist => {
Expand Down
104 changes: 103 additions & 1 deletion spotify_player/src/event/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,23 @@ pub fn handle_command_for_track_table_window(
}
Command::ShowActionsOnSelectedItem => {
let mut actions = command::construct_track_actions(tracks[id], data);
if let ContextId::Playlist(_) = context_id {
if let ContextId::Playlist(playlist_id) = context_id {
actions.push(TrackAction::DeleteFromCurrentPlaylist);

if let (Some(Context::Playlist { tracks, playlist }), Some(user_id)) = (
state.data.read().caches.context.get(&playlist_id.uri()),
state
.data
.read()
.user_data
.user
.as_ref()
.map(|user| &user.id),
) {
actions.append(&mut track_actions_for_playlist_owner(
tracks, id, user_id, playlist,
));
}
}
ui.popup = Some(PopupState::ActionList(
ActionListItem::Track(tracks[id].clone(), actions),
Expand Down Expand Up @@ -375,3 +390,90 @@ pub fn handle_command_for_playlist_list_window(
}
Ok(true)
}

fn track_actions_for_playlist_owner(
tracks: &[Track],
track_index: usize,
user_id: &UserId<'_>,
playlist: &Playlist,
) -> Vec<TrackAction> {
if &playlist.owner.1 != user_id {
return vec![];
};

let mut actions = vec![];
if track_index > 0 {
actions.push(TrackAction::MoveUpInCurrentPlaylist);
}
if track_index + 1 < tracks.len() {
actions.push(TrackAction::MoveDownInCurrentPlaylist);
}

actions
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_track_actions_for_playlist_owner() {
fn create_dummy_track(track_id: &str) -> Track {
Track {
id: TrackId::from_id(track_id.to_owned()).unwrap(),
name: "".to_string(),
artists: vec![],
album: None,
duration: chrono::Duration::minutes(1),
added_at: 0,
}
}

let user_id = "jhgDSLJahsgd";
let tracks = vec![
create_dummy_track("37BTh5g05cxBIRYMbw8g2T"),
create_dummy_track("4cOdK2wGLETKBW3PvgPWqT"),
create_dummy_track("6tASfEUyB7lE2r6DLzURji"),
];

let playlist = Playlist {
id: PlaylistId::from_id(user_id.to_owned()).unwrap(),
collaborative: false,
name: "".to_string(),
owner: (
user_id.to_string(),
UserId::from_id(user_id.to_owned()).unwrap(),
),
};

// test when the track index is neither first or last
let actions = track_actions_for_playlist_owner(
&tracks,
1,
&UserId::from_id(user_id).unwrap(),
&playlist,
);
assert!(actions.contains(&TrackAction::MoveUpInCurrentPlaylist));
assert!(actions.contains(&TrackAction::MoveDownInCurrentPlaylist));

// test when the track index is first
let actions = track_actions_for_playlist_owner(
&tracks,
0,
&UserId::from_id(user_id).unwrap(),
&playlist,
);
assert!(!actions.contains(&TrackAction::MoveUpInCurrentPlaylist));
assert!(actions.contains(&TrackAction::MoveDownInCurrentPlaylist));

// test when the track index is last
let actions = track_actions_for_playlist_owner(
&tracks,
2,
&UserId::from_id(user_id).unwrap(),
&playlist,
);
assert!(actions.contains(&TrackAction::MoveUpInCurrentPlaylist));
assert!(!actions.contains(&TrackAction::MoveDownInCurrentPlaylist));
}
}

0 comments on commit 33b61fe

Please sign in to comment.