Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Preserve liked tracks order in UI

Switch the liked-tracks representation to preserve server order.
fetch_liked_tracks now returns Vec<String>. The controller stores
both a membership set (LikedSongs) and an ordered list (LikedOrder).
The Library page builds an index map from LikedOrder and sorts the
displayed liked_tracks, placing unknown IDs at the end.

+23 -8
+1 -2
gpui/src/client.rs
··· 22 22 pub const INSERT_LAST_SHUFFLED: i32 = -7; // play last shuffled 23 23 use crate::state::{ArtistImages, PlaybackStatus, StateUpdate, Track}; 24 24 use anyhow::Result; 25 - use std::collections::HashSet; 26 25 use tokio::sync::mpsc::Sender; 27 26 28 27 const URL: &str = "http://127.0.0.1:6061"; ··· 349 348 350 349 // ── Likes ───────────────────────────────────────────────────────────────────── 351 350 352 - pub async fn fetch_liked_tracks() -> Result<HashSet<String>> { 351 + pub async fn fetch_liked_tracks() -> Result<Vec<String>> { 353 352 let mut c = LibraryServiceClient::connect(URL).await?; 354 353 let resp = c.get_liked_tracks(GetLikedTracksRequest {}).await?; 355 354 Ok(resp.into_inner().tracks.into_iter().map(|t| t.id).collect())
+4 -2
gpui/src/controller.rs
··· 1 1 use crate::now_playing::{MediaCommand, NowPlayingManager}; 2 2 use crate::state::{AppState, PlaybackStatus, StateUpdate}; 3 - use crate::ui::components::LikedSongs; 3 + use crate::ui::components::{LikedOrder, LikedSongs}; 4 4 use gpui::{App, Entity, Global}; 5 5 use std::sync::{ 6 6 atomic::{AtomicU64, Ordering}, ··· 60 60 StateUpdate::Tracks(tracks) => s.tracks = tracks, 61 61 StateUpdate::ArtistImages(images) => s.artist_images = images, 62 62 StateUpdate::LikedTracks(ids) => { 63 - cx.set_global(LikedSongs(ids)); 63 + let set = ids.iter().cloned().collect(); 64 + cx.set_global(LikedSongs(set)); 65 + cx.set_global(LikedOrder(ids)); 64 66 } 65 67 StateUpdate::SearchResults(results) => { 66 68 s.search_results = results;
+1 -1
gpui/src/state.rs
··· 136 136 }, 137 137 Tracks(Vec<Track>), 138 138 ArtistImages(ArtistImages), 139 - LikedTracks(std::collections::HashSet<String>), 139 + LikedTracks(Vec<String>), 140 140 SearchResults(Option<SearchResults>), 141 141 Settings { volume: i32, shuffling: bool, repeat_mode: i32 }, 142 142 }
+4
gpui/src/ui/components/mod.rs
··· 52 52 pub struct LikedSongs(pub std::collections::HashSet<String>); 53 53 impl gpui::Global for LikedSongs {} 54 54 55 + #[derive(Clone, Default)] 56 + pub struct LikedOrder(pub Vec<String>); 57 + impl gpui::Global for LikedOrder {} 58 + 55 59 #[derive(Clone, PartialEq)] 56 60 pub struct SelectedAlbum(pub String); 57 61 impl gpui::Global for SelectedAlbum {}
+13 -3
gpui/src/ui/components/pages/library.rs
··· 11 11 use crate::ui::components::pages::files::{menu_item, FilesView}; 12 12 use crate::ui::components::{ 13 13 AlbumContextMenu, AlbumContextMenuState, BackSection, FileContextMenuState, 14 - HoveredAlbumIdx, LibraryContextMenu, LibraryContextMenuState, LibrarySection, LikedSongs, 15 - SelectedAlbum, SelectedArtist, 14 + HoveredAlbumIdx, LibraryContextMenu, LibraryContextMenuState, LibrarySection, LikedOrder, 15 + LikedSongs, SelectedAlbum, SelectedArtist, 16 16 }; 17 17 use crate::ui::theme::Theme; 18 18 use gpui::prelude::FluentBuilder; ··· 120 120 cx.set_global(AlbumContextMenuState::default()); 121 121 cx.set_global(HoveredAlbumIdx::default()); 122 122 cx.set_global(LikedSongs::default()); 123 + cx.set_global(LikedOrder::default()); 123 124 LibraryPage { 124 125 scroll_handle: UniformListScrollHandle::new(), 125 126 detail_scroll_handle: UniformListScrollHandle::new(), ··· 142 143 143 144 let viewport = window.viewport_size(); 144 145 let liked_songs = cx.global::<LikedSongs>().0.clone(); 146 + let liked_order = cx.global::<LikedOrder>().0.clone(); 145 147 let content_width = f32::from(viewport.width) - 200.0; 146 148 let album_cols = ((content_width / 200.0).floor() as u16).max(2); 147 149 let artist_cols = ((content_width / 160.0).floor() as u16).max(2); ··· 296 298 let artist_detail_image = state.artist_images.get(&selected_artist).cloned(); 297 299 298 300 // (global_idx, path, title, artist, album, duration, id, album_art) 299 - let liked_tracks: Vec<(usize, String, String, String, String, u64, String, Option<String>)> = state 301 + let liked_order_map: std::collections::HashMap<&str, usize> = liked_order 302 + .iter() 303 + .enumerate() 304 + .map(|(i, id)| (id.as_str(), i)) 305 + .collect(); 306 + let mut liked_tracks: Vec<(usize, String, String, String, String, u64, String, Option<String>)> = state 300 307 .tracks 301 308 .iter() 302 309 .enumerate() ··· 314 321 ) 315 322 }) 316 323 .collect(); 324 + liked_tracks.sort_by_key(|(_, _, _, _, _, _, id, _)| { 325 + liked_order_map.get(id.as_str()).copied().unwrap_or(usize::MAX) 326 + }); 317 327 318 328 let current_path = state.current_track().map(|t| t.path.clone()); 319 329 let search_results = state.search_results.clone();