-
-
Notifications
You must be signed in to change notification settings - Fork 39
Folders #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Folders #92
Changes from 1 commit
5e2b9a8
6ab9a51
078381e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -334,6 +334,37 @@ pub struct NativeTrackInfo { | |
| pub duration_ms: u32, | ||
| } | ||
|
|
||
| /// A node in the playlist folder hierarchy from Spotify's rootlist | ||
| #[derive(Clone, Debug)] | ||
| pub struct PlaylistFolderNode { | ||
| pub name: Option<String>, | ||
| pub node_type: String, // "folder" or "playlist" | ||
| pub uri: String, | ||
| pub children: Vec<PlaylistFolderNode>, | ||
| } | ||
|
|
||
| /// A folder entry for navigation in the playlist panel | ||
| #[derive(Clone, Debug)] | ||
| pub struct PlaylistFolder { | ||
| pub name: String, | ||
| /// Folder ID this item is visible in (which folder "page" it appears on) | ||
| pub current_id: usize, | ||
| /// Folder ID this item navigates to when selected | ||
| pub target_id: usize, | ||
| } | ||
|
|
||
| /// A flattened item for display in the playlist panel | ||
| #[derive(Clone, Debug)] | ||
| pub enum PlaylistFolderItem { | ||
| Folder(PlaylistFolder), | ||
| Playlist { | ||
| /// Index into app.all_playlists | ||
| index: usize, | ||
| /// Folder ID this playlist is visible in | ||
| current_id: usize, | ||
| }, | ||
| } | ||
|
|
||
| /// Settings screen category tabs | ||
| #[derive(Clone, Copy, PartialEq, Debug, Default)] | ||
| pub enum SettingsCategory { | ||
|
|
@@ -545,6 +576,14 @@ pub struct App { | |
| pub status_message_expires_at: Option<Instant>, | ||
| /// Pending track table selection to apply when new page loads | ||
| pub pending_track_table_selection: Option<PendingTrackSelection>, | ||
| /// Full flat list of all user playlists (all pages combined) | ||
| pub all_playlists: Vec<SimplifiedPlaylist>, | ||
| /// Folder tree from rootlist (None if not fetched or streaming disabled) | ||
| pub playlist_folder_nodes: Option<Vec<PlaylistFolderNode>>, | ||
| /// Flattened folder+playlist items for display navigation | ||
| pub playlist_folder_items: Vec<PlaylistFolderItem>, | ||
| /// Current folder ID being viewed (0 = root) | ||
| pub current_playlist_folder_id: usize, | ||
| /// Reference to the native streaming player for direct control (bypasses event channel) | ||
| #[cfg(feature = "streaming")] | ||
| pub streaming_player: Option<Arc<crate::player::StreamingPlayer>>, | ||
|
|
@@ -682,6 +721,10 @@ impl Default for App { | |
| status_message: None, | ||
| status_message_expires_at: None, | ||
| pending_track_table_selection: None, | ||
| all_playlists: Vec::new(), | ||
| playlist_folder_nodes: None, | ||
| playlist_folder_items: Vec::new(), | ||
| current_playlist_folder_id: 0, | ||
| #[cfg(feature = "streaming")] | ||
| streaming_player: None, | ||
| #[cfg(all(feature = "mpris", target_os = "linux"))] | ||
|
|
@@ -722,6 +765,31 @@ impl App { | |
| self.io_tx = None; | ||
| } | ||
|
|
||
| /// Get the items visible in the current folder level. | ||
| /// Returns a filtered view of playlist_folder_items where current_id matches | ||
| /// the current_playlist_folder_id. | ||
| pub fn get_playlist_display_items(&self) -> Vec<&PlaylistFolderItem> { | ||
| self | ||
| .playlist_folder_items | ||
| .iter() | ||
| .filter(|item| match item { | ||
| PlaylistFolderItem::Folder(f) => f.current_id == self.current_playlist_folder_id, | ||
|
||
| PlaylistFolderItem::Playlist { current_id, .. } => { | ||
| *current_id == self.current_playlist_folder_id | ||
| } | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| /// Get the SimplifiedPlaylist for a PlaylistFolderItem::Playlist variant | ||
| #[allow(dead_code)] | ||
| pub fn get_playlist_for_item(&self, item: &PlaylistFolderItem) -> Option<&SimplifiedPlaylist> { | ||
| match item { | ||
| PlaylistFolderItem::Playlist { index, .. } => self.all_playlists.get(*index), | ||
| PlaylistFolderItem::Folder(_) => None, | ||
| } | ||
| } | ||
|
|
||
| fn apply_seek(&mut self, seek_ms: u32) { | ||
| if let Some(CurrentPlaybackContext { | ||
| item: Some(item), .. | ||
|
|
@@ -1724,16 +1792,18 @@ impl App { | |
| } | ||
|
|
||
| pub fn user_unfollow_playlist(&mut self) { | ||
| if let (Some(playlists), Some(selected_index), Some(user)) = | ||
| (&self.playlists, self.selected_playlist_index, &self.user) | ||
| { | ||
| let selected_playlist = &playlists.items[selected_index]; | ||
| let selected_id = selected_playlist.id.clone(); | ||
| let user_id = user.id.clone(); | ||
| self.dispatch(IoEvent::UserUnfollowPlaylist( | ||
| user_id.into_static(), | ||
| selected_id.into_static(), | ||
| )); | ||
| if let (Some(selected_index), Some(user)) = (self.selected_playlist_index, &self.user) { | ||
| let display_items = self.get_playlist_display_items(); | ||
| if let Some(PlaylistFolderItem::Playlist { index, .. }) = display_items.get(selected_index) { | ||
| if let Some(playlist) = self.all_playlists.get(*index) { | ||
| let selected_id = playlist.id.clone(); | ||
| let user_id = user.id.clone(); | ||
| self.dispatch(IoEvent::UserUnfollowPlaylist( | ||
| user_id.into_static(), | ||
| selected_id.into_static(), | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -358,9 +358,9 @@ fn on_enter(app: &mut App) { | |
| // Get the track ID to play | ||
| let track_playable_id = track_playable_id(track.id.clone()); | ||
|
|
||
| let context_id = match (&app.active_playlist_index, &app.playlists) { | ||
| (Some(active_playlist_index), Some(playlists)) => playlists | ||
| .items | ||
| let context_id = match &app.active_playlist_index { | ||
| Some(active_playlist_index) => app | ||
| .all_playlists | ||
| .get(active_playlist_index.to_owned()) | ||
| .map(|selected_playlist| playlist_context_id_from_ref(&selected_playlist.id)), | ||
| _ => None, | ||
|
Comment on lines
+321
to
326
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PlaylistFolderNode.node_typeis a free-formStringbut the code relies on specific values ("folder" vs "playlist") for control flow. Using a string here makes invalid states representable and adds allocations; consider making this an enum (and parsing into that) to improve type-safety and avoid unexpected fallthroughs.