Skip to content
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

Add play button to album and playlist view #662

Merged
merged 12 commits into from
Jun 7, 2023
44 changes: 35 additions & 9 deletions src/app/components/details/album_header.blp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ template $AlbumHeaderWidget : Box {
margin-end: 6;
margin-bottom: 6;

Overlay {
Overlay album_overlay {
overflow: hidden;
halign: center;
margin-top: 18;
margin-bottom: 6;
margin-start: 6;

Image album_art {
width-request: 160;
Expand Down Expand Up @@ -44,6 +47,7 @@ template $AlbumHeaderWidget : Box {
valign: center;
orientation: vertical;
spacing: 6;
margin-start: 18;

Label album_label {
xalign: 0;
Expand Down Expand Up @@ -92,18 +96,40 @@ template $AlbumHeaderWidget : Box {
}
}

Button like_button {
receives-default: true;
halign: center;
Box button_box {
orientation: horizontal;
valign: center;
tooltip-text: "Add to Library";

styles [
"circular",
"like__button",
]
margin-end: 6;
spacing: 8;

Button play_button {
receives-default: true;
halign: center;
valign: center;
tooltip-text: "Play";
icon-name: "media-playback-start-symbolic";

styles [
"circular",
"play__button",
]
}

Button like_button {
receives-default: true;
halign: center;
valign: center;
tooltip-text: "Add to Library";

styles [
"circular",
"like__button",
]
}
}


styles [
"album__header",
]
Expand Down
15 changes: 0 additions & 15 deletions src/app/components/details/album_header.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,6 @@
background-image: image(alpha(currentColor, 0.08));
}

.album__header .like__button {
margin-right: 6px;
}

.album__header .vertical {
margin-left: 18px;
}

.album__header .card {
margin-top: 18px;
margin-left: 6px;
margin-bottom: 6px;
}


clamp.details__clamp {
background-color: @view_bg_color;
box-shadow: inset 0px -1px 0px @borders;
Expand Down
40 changes: 40 additions & 0 deletions src/app/components/details/album_header.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::app::components::display_add_css_provider;
use gettextrs::gettext;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
Expand All @@ -10,15 +11,24 @@ mod imp {
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/dev/alextren/Spot/components/album_header.ui")]
pub struct AlbumHeaderWidget {
#[template_child]
pub album_overlay: TemplateChild<gtk::Overlay>,

#[template_child]
pub album_label: TemplateChild<gtk::Label>,

#[template_child]
pub album_art: TemplateChild<gtk::Image>,

#[template_child]
pub button_box: TemplateChild<gtk::Box>,

#[template_child]
pub like_button: TemplateChild<gtk::Button>,

#[template_child]
pub play_button: TemplateChild<gtk::Button>,

#[template_child]
pub info_button: TemplateChild<gtk::Button>,

Expand Down Expand Up @@ -65,6 +75,13 @@ impl AlbumHeaderWidget {
glib::Object::new()
}

pub fn connect_play<F>(&self, f: F)
where
F: Fn() + 'static,
{
self.imp().play_button.connect_clicked(move |_| f());
}

pub fn connect_liked<F>(&self, f: F)
where
F: Fn() + 'static,
Expand Down Expand Up @@ -97,6 +114,25 @@ impl AlbumHeaderWidget {
});
}

pub fn set_playing(&self, is_playing: bool) {
let playback_icon = if is_playing {
"media-playback-pause-symbolic"
} else {
"media-playback-start-symbolic"
};

let translated_tooltip = if is_playing {
gettext("Pause")
} else {
gettext("Play")
};
let tooltip_text = Some(translated_tooltip.as_str());
let playback_control = imp::AlbumHeaderWidget::from_obj(self);
Zaedus marked this conversation as resolved.
Show resolved Hide resolved

playback_control.play_button.set_icon_name(playback_icon);
playback_control.play_button.set_tooltip_text(tooltip_text);
}

pub fn set_artwork(&self, art: &gdk_pixbuf::Pixbuf) {
self.imp().album_art.set_from_pixbuf(Some(art));
}
Expand All @@ -117,5 +153,9 @@ impl AlbumHeaderWidget {
widget.album_label.set_justify(gtk::Justification::Center);
widget.artist_button.set_halign(gtk::Align::Center);
widget.year_label.set_halign(gtk::Align::Center);
widget.button_box.set_halign(gtk::Align::Center);
widget.album_overlay.set_margin_start(0);
widget.button_box.set_margin_end(0);
widget.album_info.set_margin_start(0);
}
}
34 changes: 33 additions & 1 deletion src/app/components/details/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ use super::release_details::ReleaseDetailsWindow;
use super::DetailsModel;

use crate::app::components::{
Component, EventListener, HeaderBarComponent, HeaderBarWidget, Playlist, ScrollingHeaderWidget,
playlist::PlaylistModel, Component, EventListener, HeaderBarComponent, HeaderBarWidget,
Playlist, ScrollingHeaderWidget,
};
use crate::app::dispatch::Worker;
use crate::app::loader::ImageLoader;
use crate::app::state::PlaybackEvent;
use crate::app::{AppEvent, BrowserEvent};

mod imp {
Expand Down Expand Up @@ -123,6 +125,14 @@ impl AlbumDetailsWidget {
self.imp().header_mobile.connect_liked(f);
}

fn connect_play<F>(&self, f: F)
where
F: Fn() + Clone + 'static,
{
self.imp().header_widget.connect_play(f.clone());
self.imp().header_mobile.connect_play(f);
}

fn connect_info<F>(&self, f: F)
where
F: Fn() + Clone + 'static,
Expand All @@ -136,6 +146,11 @@ impl AlbumDetailsWidget {
self.imp().header_mobile.set_liked(is_liked);
}

fn set_playing(&self, is_playing: bool) {
self.imp().header_widget.set_playing(is_playing);
self.imp().header_mobile.set_playing(is_playing);
}

fn set_album_and_artist_and_year(&self, album: &str, artist: &str, year: Option<u32>) {
self.imp()
.header_widget
Expand Down Expand Up @@ -193,6 +208,8 @@ impl Details {

widget.connect_liked(clone!(@weak model => move || model.toggle_save_album()));

widget.connect_play(clone!(@weak model => move || model.toggle_play_album()));

widget.connect_header();

widget.connect_bottom_edge(clone!(@weak model => move || {
Expand Down Expand Up @@ -228,6 +245,14 @@ impl Details {
}
}

fn update_playing(&self, is_playing: bool) {
if !self.model.playlist_is_playing() {
self.widget.set_playing(false);
return;
}
self.widget.set_playing(is_playing);
}

fn update_details(&mut self) {
if let Some(album) = self.model.get_album_info() {
let details = &album.release_details;
Expand Down Expand Up @@ -290,13 +315,20 @@ impl EventListener for Details {
if id == &self.model.id =>
{
self.update_details();
self.update_playing(true);
}
AppEvent::BrowserEvent(BrowserEvent::AlbumSaved(id))
| AppEvent::BrowserEvent(BrowserEvent::AlbumUnsaved(id))
if id == &self.model.id =>
{
self.update_liked();
}
AppEvent::PlaybackEvent(PlaybackEvent::PlaybackPaused) => {
self.update_playing(false);
}
AppEvent::PlaybackEvent(PlaybackEvent::PlaybackResumed) => {
self.update_playing(true);
}
_ => {}
}
self.broadcast_event(event);
Expand Down
44 changes: 44 additions & 0 deletions src/app/components/details/details_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,27 @@ impl DetailsModel {
}
}

pub fn toggle_play_album(&self) {
if let Some(album) = self.get_album_description() {
if !self.playlist_is_playing() {
if self.state().playback.is_shuffled() {
self.dispatcher
.dispatch(AppAction::PlaybackAction(PlaybackAction::ToggleShuffle));
}
let id_of_first_song = album.songs.songs[0].id.as_str();
self.play_song_at(0, id_of_first_song);
return;
}
if self.state().playback.is_playing() {
self.dispatcher
.dispatch(AppAction::PlaybackAction(PlaybackAction::Pause));
} else {
self.dispatcher
.dispatch(AppAction::PlaybackAction(PlaybackAction::Play));
}
}
}

pub fn load_more(&self) -> Option<()> {
let last_batch = self.song_list_model().last_batch()?;
let query = BatchQuery {
Expand Down Expand Up @@ -176,6 +197,29 @@ impl PlaylistModel for DetailsModel {
self.state().playback.current_song_id()
}

fn playlist_song_ids(&self) -> Option<Vec<String>> {
if let Some(album) = self.get_album_description() {
let playlist_ids = album
.songs
.songs
.iter()
.map(|song| song.id.clone())
.collect::<Vec<_>>();
return Some(playlist_ids);
}
None
}

fn playlist_is_playing(&self) -> bool {
let current_song_id = self.state().playback.current_song_id();
Zaedus marked this conversation as resolved.
Show resolved Hide resolved
if current_song_id.is_none() || self.playlist_song_ids().is_none() {
return false;
}
self.playlist_song_ids()
.unwrap()
.contains(&current_song_id.unwrap())
}

fn play_song_at(&self, pos: usize, id: &str) {
let source = SongsSource::Album(self.id.clone());
let batch = self.song_list_model().song_batch_for(pos);
Expand Down
8 changes: 8 additions & 0 deletions src/app/components/playlist/playlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ pub trait PlaylistModel {

fn current_song_id(&self) -> Option<String>;

fn playlist_song_ids(&self) -> Option<Vec<String>> {
Zaedus marked this conversation as resolved.
Show resolved Hide resolved
None
}

fn playlist_is_playing(&self) -> bool {
false
}

fn play_song_at(&self, pos: usize, id: &str);

fn autoscroll_to_playing(&self) -> bool {
Expand Down
23 changes: 23 additions & 0 deletions src/app/components/playlist_details/playlist_details_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,29 @@ impl PlaylistModel for PlaylistDetailsModel {
self.app_model.get_state().playback.current_song_id()
}

fn playlist_song_ids(&self) -> Option<Vec<String>> {
if let Some(playlist) = self.get_playlist_info() {
let playlist_ids = playlist
.songs
.songs
.iter()
.map(|song| song.id.clone())
.collect::<Vec<_>>();
return Some(playlist_ids);
}
None
}

fn playlist_is_playing(&self) -> bool {
let current_song_id = self.app_model.get_state().playback.current_song_id();
if current_song_id.is_none() || self.playlist_song_ids().is_none() {
return false;
}
self.playlist_song_ids()
.unwrap()
.contains(&current_song_id.unwrap())
}

fn play_song_at(&self, pos: usize, id: &str) {
let source = SongsSource::Playlist(self.id.clone());
let batch = self.song_list_model().song_batch_for(pos);
Expand Down