@@ -65,7 +65,7 @@ fn get_metadata(playable: Option<Playable>) -> Metadata {
|
||||
Variant(Box::new(
|
||||
playable
|
||||
.and_then(|p| p.track())
|
||||
.map(|t| t.album)
|
||||
.map(|t| t.album.unwrap_or_default())
|
||||
.unwrap_or_default(),
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ use rspotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack};
|
||||
use rspotify::model::user::PrivateUser;
|
||||
use rspotify::senum::SearchType;
|
||||
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Map};
|
||||
|
||||
use failure::Error;
|
||||
|
||||
@@ -59,6 +59,7 @@ use crate::events::{Event, EventManager};
|
||||
use crate::playable::Playable;
|
||||
use crate::queue;
|
||||
use crate::track::Track;
|
||||
use rspotify::model::recommend::Recommendations;
|
||||
use rspotify::model::show::{FullEpisode, FullShow, Show, SimplifiedEpisode};
|
||||
|
||||
pub const VOLUME_PERCENT: u16 = ((u16::max_value() as f64) * 1.0 / 100.0) as u16;
|
||||
@@ -686,6 +687,24 @@ impl Spotify {
|
||||
self.api_with_retry(|api| api.get_an_episode(episode_id.to_string(), None))
|
||||
}
|
||||
|
||||
pub fn recommentations(
|
||||
&self,
|
||||
seed_artists: Option<Vec<String>>,
|
||||
seed_genres: Option<Vec<String>>,
|
||||
seed_tracks: Option<Vec<String>>,
|
||||
) -> Option<Recommendations> {
|
||||
self.api_with_retry(|api| {
|
||||
api.recommendations(
|
||||
seed_artists.clone(),
|
||||
seed_genres.clone(),
|
||||
seed_tracks.clone(),
|
||||
100,
|
||||
None,
|
||||
&Map::new(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
&self,
|
||||
searchtype: SearchType,
|
||||
|
||||
72
src/track.rs
72
src/track.rs
@@ -1,5 +1,5 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use rspotify::model::album::FullAlbum;
|
||||
@@ -10,7 +10,8 @@ use crate::artist::Artist;
|
||||
use crate::library::Library;
|
||||
use crate::playable::Playable;
|
||||
use crate::queue::Queue;
|
||||
use crate::traits::{ListItem, ViewExt};
|
||||
use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use crate::ui::listview::ListView;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Track {
|
||||
@@ -22,7 +23,7 @@ pub struct Track {
|
||||
pub duration: u32,
|
||||
pub artists: Vec<String>,
|
||||
pub artist_ids: Vec<String>,
|
||||
pub album: String,
|
||||
pub album: Option<String>,
|
||||
pub album_id: Option<String>,
|
||||
pub album_artists: Vec<String>,
|
||||
pub cover_url: Option<String>,
|
||||
@@ -58,7 +59,7 @@ impl Track {
|
||||
duration: track.duration_ms,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: album.name.clone(),
|
||||
album: Some(album.name.clone()),
|
||||
album_id: Some(album.id.clone()),
|
||||
album_artists,
|
||||
cover_url: album.images.get(0).map(|img| img.url.clone()),
|
||||
@@ -74,6 +75,39 @@ impl Track {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SimplifiedTrack> for Track {
|
||||
fn from(track: &SimplifiedTrack) -> Self {
|
||||
let artists = track
|
||||
.artists
|
||||
.iter()
|
||||
.map(|ref artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let artist_ids = track
|
||||
.artists
|
||||
.iter()
|
||||
.filter(|a| a.id.is_some())
|
||||
.map(|ref artist| artist.id.clone().unwrap())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Self {
|
||||
id: track.id.clone(),
|
||||
uri: track.uri.clone(),
|
||||
title: track.name.clone(),
|
||||
track_number: track.track_number,
|
||||
disc_number: track.disc_number,
|
||||
duration: track.duration_ms,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: None,
|
||||
album_id: None,
|
||||
album_artists: Vec::new(),
|
||||
cover_url: None,
|
||||
url: track.uri.clone(),
|
||||
added_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FullTrack> for Track {
|
||||
fn from(track: &FullTrack) -> Self {
|
||||
let artists = track
|
||||
@@ -103,7 +137,7 @@ impl From<&FullTrack> for Track {
|
||||
duration: track.duration_ms,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: track.album.name.clone(),
|
||||
album: Some(track.album.name.clone()),
|
||||
album_id: track.album.id.clone(),
|
||||
album_artists,
|
||||
cover_url: track.album.images.get(0).map(|img| img.url.clone()),
|
||||
@@ -155,7 +189,7 @@ impl ListItem for Track {
|
||||
|
||||
fn display_center(&self, library: Arc<Library>) -> String {
|
||||
if library.cfg.values().album_column.unwrap_or(true) {
|
||||
self.album.to_string()
|
||||
self.album.clone().unwrap_or_default()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
@@ -207,6 +241,32 @@ impl ListItem for Track {
|
||||
None
|
||||
}
|
||||
|
||||
fn open_recommentations(
|
||||
&self,
|
||||
queue: Arc<Queue>,
|
||||
library: Arc<Library>,
|
||||
) -> Option<Box<dyn ViewExt>> {
|
||||
let spotify = queue.get_spotify();
|
||||
|
||||
let recommendations: Option<Vec<Track>> = if let Some(id) = &self.id {
|
||||
spotify
|
||||
.recommentations(None, None, Some(vec![id.clone()]))
|
||||
.map(|r| r.tracks)
|
||||
.map(|tracks| tracks.iter().map(Track::from).collect())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
recommendations.map(|tracks| {
|
||||
ListView::new(
|
||||
Arc::new(RwLock::new(tracks)),
|
||||
queue.clone(),
|
||||
library.clone(),
|
||||
)
|
||||
.as_boxed_view_ext()
|
||||
})
|
||||
}
|
||||
|
||||
fn share_url(&self) -> Option<String> {
|
||||
self.id
|
||||
.clone()
|
||||
|
||||
@@ -26,6 +26,13 @@ pub trait ListItem: Sync + Send + 'static {
|
||||
fn save(&mut self, library: Arc<Library>);
|
||||
fn unsave(&mut self, library: Arc<Library>);
|
||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>>;
|
||||
fn open_recommentations(
|
||||
&self,
|
||||
_queue: Arc<Queue>,
|
||||
_library: Arc<Library>,
|
||||
) -> Option<Box<dyn ViewExt>> {
|
||||
None
|
||||
}
|
||||
fn share_url(&self) -> Option<String>;
|
||||
|
||||
fn album(&self, _queue: Arc<Queue>) -> Option<Album> {
|
||||
|
||||
@@ -24,6 +24,7 @@ enum ContextMenuAction {
|
||||
ShowItem(Box<dyn ListItem>),
|
||||
ShareUrl(String),
|
||||
AddToPlaylist(Box<Track>),
|
||||
ShowRecommentations(Box<dyn ListItem>),
|
||||
}
|
||||
|
||||
impl ContextMenu {
|
||||
@@ -62,8 +63,12 @@ impl ContextMenu {
|
||||
if let Some(t) = item.track() {
|
||||
content.add_item(
|
||||
"Add to playlist",
|
||||
ContextMenuAction::AddToPlaylist(Box::new(t)),
|
||||
)
|
||||
ContextMenuAction::AddToPlaylist(Box::new(t.clone())),
|
||||
);
|
||||
content.add_item(
|
||||
"Similar tracks",
|
||||
ContextMenuAction::ShowRecommentations(Box::new(t)),
|
||||
);
|
||||
}
|
||||
|
||||
// open detail view of artist/album
|
||||
@@ -88,6 +93,11 @@ impl ContextMenu {
|
||||
let dialog = Self::add_track_dialog(library, *track.clone());
|
||||
s.add_layer(dialog);
|
||||
}
|
||||
ContextMenuAction::ShowRecommentations(item) => {
|
||||
if let Some(view) = item.open_recommentations(queue, library) {
|
||||
s.call_on_name("main", move |v: &mut Layout| v.push_view(view));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user