mirror of
https://github.com/danbulant/despot
synced 2026-05-19 03:58:32 +00:00
functional playlist list
This commit is contained in:
parent
2963da1634
commit
811ac8eebf
6 changed files with 127 additions and 10 deletions
|
|
@ -6,7 +6,7 @@ use futures_util::lock::Mutex;
|
|||
use librespot_core::Session;
|
||||
use librespot_oauth::OAuthToken;
|
||||
use reqwest::StatusCode;
|
||||
use rspotify::model::PrivateUser;
|
||||
use rspotify::model::{Page, PrivateUser, SimplifiedPlaylist, UserId};
|
||||
use rspotify::prelude::*;
|
||||
use rspotify::{AuthCodeSpotify, ClientError, ClientResult, Config, Token};
|
||||
use rspotify::http::HttpError;
|
||||
|
|
@ -108,6 +108,10 @@ impl SpotifyContext {
|
|||
self.api_with_retry(|api| api.current_user()).await.ok_or(())
|
||||
}
|
||||
|
||||
pub async fn current_user_playlists(&self, limit: Option<u32>, offset: Option<u32>) -> Result<Page<SimplifiedPlaylist>, ()> {
|
||||
self.api_with_retry(|api| api.current_user_playlists_manual(limit, offset)).await.ok_or(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn librespot_token_to_rspotify(token: &OAuthToken) -> Token {
|
||||
|
|
|
|||
12
src/main.rs
12
src/main.rs
|
|
@ -4,9 +4,10 @@ use api::SpotifyContext;
|
|||
use auth::get_token;
|
||||
use clap::Parser;
|
||||
use cli::Args;
|
||||
use cushy::{window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
|
||||
use cushy::{value::Dynamic, widget::MakeWidget, window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
|
||||
use librespot_core::{authentication::Credentials, Session, SessionConfig};
|
||||
use librespot_playback::{audio_backend, config::{AudioFormat, PlayerConfig}, mixer::NoOpVolume, player::Player};
|
||||
use widgets::{library::playlist::playlists_widget, ActivePage};
|
||||
|
||||
mod vibrancy;
|
||||
mod theme;
|
||||
|
|
@ -64,9 +65,14 @@ fn main() -> cushy::Result {
|
|||
dbg!(&user);
|
||||
let userid = user.id;
|
||||
|
||||
format!("Hello, {}!", user.display_name.unwrap())
|
||||
let playlists = context.current_user_playlists(None, None).await.unwrap();
|
||||
|
||||
let selected_page = Dynamic::new(ActivePage::default());
|
||||
|
||||
playlists_widget(playlists.items, selected_page)
|
||||
.make_window()
|
||||
.open(&mut app).unwrap();
|
||||
.open(&mut app)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
drop(guard);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use cushy::{kludgine::{AnyTexture, LazyTexture}, value::{CallbackHandle, Destination, Dynamic, Source, Value}, widgets::Image};
|
||||
use cushy::{kludgine::{AnyTexture, LazyTexture}, value::{CallbackDisconnected, CallbackHandle, Destination, Dynamic, Source, Value}, widgets::Image};
|
||||
use futures_util::lock::Mutex;
|
||||
use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions};
|
||||
use image::imageops::FilterType;
|
||||
|
|
@ -10,11 +10,17 @@ use tokio::task::JoinHandle;
|
|||
|
||||
use crate::rt::tokio_runtime;
|
||||
|
||||
|
||||
trait ImageExt {
|
||||
pub trait ImageExt {
|
||||
fn new_empty() -> Self;
|
||||
|
||||
fn load_url(&mut self, url: Dynamic<Option<String>>) -> CallbackHandle;
|
||||
|
||||
fn with_url(mut self, url: Dynamic<Option<String>>) -> Self
|
||||
where Self: Sized
|
||||
{
|
||||
self.load_url(url).persist();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageExt for Image {
|
||||
|
|
@ -22,6 +28,8 @@ impl ImageExt for Image {
|
|||
Image::new(Dynamic::new(get_empty_texture()))
|
||||
}
|
||||
|
||||
/// Makes the image connected to a URL
|
||||
/// Calling this multiple times on a single image may cause memory leaks
|
||||
fn load_url(&mut self, url: Dynamic<Option<String>>) -> CallbackHandle {
|
||||
let client = ClientBuilder::new(Client::new())
|
||||
.with(Cache(HttpCache {
|
||||
|
|
@ -41,12 +49,15 @@ impl ImageExt for Image {
|
|||
Value::Dynamic(dynamic) => dynamic,
|
||||
_ => unreachable!()
|
||||
};
|
||||
let texture = texture.clone();
|
||||
|
||||
let prev_request_join = Arc::new(Mutex::new(None::<JoinHandle<()>>));
|
||||
url.for_each({
|
||||
url.for_each_try({
|
||||
let texture = texture.clone();
|
||||
move |url| {
|
||||
let texture_count = texture.instances();
|
||||
if texture_count <= 1 {
|
||||
return Err(CallbackDisconnected);
|
||||
}
|
||||
let guard = tokio_runtime().enter();
|
||||
let url = url.clone();
|
||||
let prev_request_join = prev_request_join.clone();
|
||||
|
|
@ -74,6 +85,7 @@ impl ImageExt for Image {
|
|||
}
|
||||
});
|
||||
drop(guard);
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
3
src/widgets/library/mod.rs
Normal file
3
src/widgets/library/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
use rspotify::model::SimplifiedPlaylist;
|
||||
|
||||
pub mod playlist;
|
||||
78
src/widgets/library/playlist.rs
Normal file
78
src/widgets/library/playlist.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use cushy::{styles::components::WidgetBackground, value::{Destination, Dynamic, IntoDynamic, IntoValue, Source, Value}, widget::{MakeWidget, WidgetList}, widgets::{grid::Orientation, Image, Stack}};
|
||||
use rspotify::model::SimplifiedPlaylist;
|
||||
use cushy::kludgine::Color;
|
||||
|
||||
use crate::widgets::{image::ImageExt, ActivePage, SelectedPage};
|
||||
|
||||
fn playlist_entry(playlist: impl IntoValue<SimplifiedPlaylist>, selected_page: SelectedPage) -> impl MakeWidget {
|
||||
let playlist: Value<SimplifiedPlaylist> = playlist.into_value();
|
||||
let id = playlist.map(|p| p.id.clone());
|
||||
let background = selected_page.map_each(move |page| {
|
||||
match page {
|
||||
ActivePage::Playlist(p) if p.id == id => {
|
||||
Color(0xFFFFFF10)
|
||||
}
|
||||
_ => Color::CLEAR_WHITE
|
||||
}
|
||||
});
|
||||
Image::new_empty()
|
||||
.with_url(
|
||||
playlist
|
||||
.map_each(|playlist| playlist.images.first().map(|image| image.url.clone()))
|
||||
.into_dynamic()
|
||||
)
|
||||
|
||||
.and(
|
||||
playlist
|
||||
.map_each(|p| p.name.clone())
|
||||
.align_left()
|
||||
.expand()
|
||||
)
|
||||
.into_columns()
|
||||
.into_button()
|
||||
.on_click(move |_| {
|
||||
selected_page.set(ActivePage::Playlist(playlist.get()));
|
||||
})
|
||||
.with(&WidgetBackground, background)
|
||||
}
|
||||
|
||||
pub fn playlists_widget(playlists: impl IntoValue<Vec<SimplifiedPlaylist>>, selected_page: SelectedPage) -> impl MakeWidget {
|
||||
let playlists: Value<Vec<SimplifiedPlaylist>> = playlists.into_value();
|
||||
Stack::new(
|
||||
Orientation::Row,
|
||||
playlists
|
||||
.map_each(move |t| {
|
||||
let mut list = t.clone().into_iter().map(|playlist| playlist_entry(playlist, selected_page.clone())).collect::<WidgetList>();
|
||||
list.insert(0, liked_songs_entry(selected_page.clone()));
|
||||
list
|
||||
})
|
||||
)
|
||||
.vertical_scroll()
|
||||
.expand()
|
||||
}
|
||||
|
||||
pub fn liked_songs_entry(selected_page: SelectedPage) -> impl MakeWidget {
|
||||
let background = selected_page.map_each(move |page| {
|
||||
match page {
|
||||
ActivePage::LikedSongs => {
|
||||
Color(0xFFFFFF10)
|
||||
}
|
||||
_ => Color::CLEAR_WHITE
|
||||
}
|
||||
});
|
||||
Image::new_empty()
|
||||
.with_url(
|
||||
Dynamic::new(Some("https://misc.scdn.co/liked-songs/liked-songs-300.png".to_string()))
|
||||
)
|
||||
.and(
|
||||
"Liked Songs"
|
||||
.align_left()
|
||||
.expand()
|
||||
)
|
||||
.into_columns()
|
||||
.into_button()
|
||||
.on_click(move |_| {
|
||||
selected_page.set(ActivePage::LikedSongs);
|
||||
})
|
||||
.with(&WidgetBackground, background)
|
||||
}
|
||||
|
|
@ -1 +1,15 @@
|
|||
pub mod image;
|
||||
use cushy::value::Dynamic;
|
||||
use rspotify::model::{SimplifiedAlbum, SimplifiedPlaylist};
|
||||
|
||||
pub mod image;
|
||||
pub mod library;
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub enum ActivePage {
|
||||
#[default]
|
||||
LikedSongs,
|
||||
Playlist(SimplifiedPlaylist),
|
||||
Album(SimplifiedAlbum)
|
||||
}
|
||||
|
||||
type SelectedPage = Dynamic<ActivePage>;
|
||||
Loading…
Reference in a new issue