Merge branch 'medium-tracks' into new-import-screen

This commit is contained in:
Elias Projahn 2021-04-08 00:15:19 +02:00
commit 3f5f751992
11 changed files with 327 additions and 539 deletions

View file

@ -7,7 +7,7 @@ use glib::prelude::*;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libadwaita::prelude::*;
use musicus_backend::db::{generate_id, Medium, Track, TrackSet};
use musicus_backend::db::{generate_id, Medium};
use musicus_backend::import::ImportSession;
use std::cell::RefCell;
use std::rc::Rc;
@ -24,7 +24,7 @@ pub struct MediumEditor {
status_page: libadwaita::StatusPage,
disc_status_page: libadwaita::StatusPage,
track_set_list: Rc<List>,
track_sets: RefCell<Vec<TrackSetData>>,
// track_sets: RefCell<Vec<TrackSetData>>,
}
impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
@ -59,7 +59,7 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
status_page,
disc_status_page,
track_set_list: list,
track_sets: RefCell::new(Vec::new()),
// track_sets: RefCell::new(Vec::new()),
});
// Connect signals and callbacks
@ -83,43 +83,43 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
add_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
let length = {
let mut track_sets = this.track_sets.borrow_mut();
track_sets.push(track_set);
track_sets.len()
};
// if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
// let length = {
// let mut track_sets = this.track_sets.borrow_mut();
// track_sets.push(track_set);
// track_sets.len()
// };
this.track_set_list.update(length);
}
// this.track_set_list.update(length);
// }
});
}));
this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| {
let track_set = &this.track_sets.borrow()[index];
// this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| {
// let track_set = &this.track_sets.borrow()[index];
let title = track_set.recording.work.get_title();
let subtitle = track_set.recording.get_performers();
// let title = track_set.recording.work.get_title();
// let subtitle = track_set.recording.get_performers();
let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"));
let edit_button = gtk::Button::new();
edit_button.set_has_frame(false);
edit_button.set_valign(gtk::Align::Center);
edit_button.set_child(Some(&edit_image));
// let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"));
// let edit_button = gtk::Button::new();
// edit_button.set_has_frame(false);
// edit_button.set_valign(gtk::Align::Center);
// edit_button.set_child(Some(&edit_image));
let row = libadwaita::ActionRow::new();
row.set_activatable(true);
row.set_title(Some(&title));
row.set_subtitle(Some(&subtitle));
row.add_suffix(&edit_button);
row.set_activatable_widget(Some(&edit_button));
// let row = libadwaita::ActionRow::new();
// row.set_activatable(true);
// row.set_title(Some(&title));
// row.set_subtitle(Some(&subtitle));
// row.add_suffix(&edit_button);
// row.set_activatable_widget(Some(&edit_button));
edit_button.connect_clicked(clone!(@weak this => move |_| {
// edit_button.connect_clicked(clone!(@weak this => move |_| {
// TODO: Implement editing.
}));
// }));
row.upcast()
}));
// row.upcast()
// }));
try_again_button.connect_clicked(clone!(@weak this => move |_| {
this.widget.set_visible_child_name("content");
@ -136,68 +136,68 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
impl MediumEditor {
/// Save the medium and possibly upload it to the server.
async fn save(&self) -> Result<Medium> {
let name = self.name_entry.get_text().to_string();
// let name = self.name_entry.get_text().to_string();
// Create a new directory in the music library path for the imported medium.
let mut path = self.handle.backend.get_music_library_path().unwrap().clone();
path.push(&name);
std::fs::create_dir(&path)?;
// let mut path = self.handle.backend.get_music_library_path().unwrap().clone();
// path.push(&name);
// std::fs::create_dir(&path)?;
// Convert the track set data to real track sets.
let mut track_sets = Vec::new();
let import_tracks = self.session.tracks();
// let mut track_sets = Vec::new();
// let import_tracks = self.session.tracks();
for track_set_data in &*self.track_sets.borrow() {
let mut tracks = Vec::new();
// for track_set_data in &*self.track_sets.borrow() {
// let mut tracks = Vec::new();
for track_data in &track_set_data.tracks {
// for track_data in &track_set_data.tracks {
// Copy the corresponding audio file to the music library.
let import_track = &import_tracks[track_data.track_source];
// let import_track = &import_tracks[track_data.track_source];
let mut track_path = path.clone();
track_path.push(import_track.path.file_name().unwrap());
// let mut track_path = path.clone();
// track_path.push(import_track.path.file_name().unwrap());
std::fs::copy(&import_track.path, &track_path)?;
// std::fs::copy(&import_track.path, &track_path)?;
// Create the real track.
let track = Track {
work_parts: track_data.work_parts.clone(),
path: track_path.to_str().unwrap().to_owned(),
};
// let track = Track {
// work_parts: track_data.work_parts.clone(),
// path: track_path.to_str().unwrap().to_owned(),
// };
tracks.push(track);
}
// tracks.push(track);
// }
let track_set = TrackSet {
recording: track_set_data.recording.clone(),
tracks,
};
// let track_set = TrackSet {
// recording: track_set_data.recording.clone(),
// tracks,
// };
track_sets.push(track_set);
}
// track_sets.push(track_set);
// }
let medium = Medium {
id: generate_id(),
name: self.name_entry.get_text().to_string(),
discid: Some(self.session.source_id().to_owned()),
tracks: track_sets,
};
// let medium = Medium {
// id: generate_id(),
// name: self.name_entry.get_text().to_string(),
// discid: Some(self.session.source_id().to_owned()),
// tracks: track_sets,
// };
let upload = self.publish_switch.get_active();
if upload {
self.handle.backend.cl().post_medium(&medium).await?;
}
// let upload = self.publish_switch.get_active();
// if upload {
// self.handle.backend.cl().post_medium(&medium).await?;
// }
self.handle.backend
.db()
.update_medium(medium.clone())
.await?;
// self.handle.backend
// .db()
// .update_medium(medium.clone())
// .await?;
self.handle.backend.library_changed();
// self.handle.backend.library_changed();
Ok(medium)
}

View file

@ -5,53 +5,21 @@ use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use libadwaita::prelude::*;
use musicus_backend::PlaylistItem;
use musicus_backend::db::Medium;
use std::rc::Rc;
/// Elements for visually representing the contents of the medium.
enum ListItem {
/// A header shown on top of a track set. The value is the index of the corresponding track set
/// within the medium.
Header(usize),
/// A track. The indices are from the track set and the track.
Track(usize, usize),
/// A separator shown between track sets.
Separator,
}
/// A screen for showing the contents of a medium.
pub struct MediumScreen {
handle: NavigationHandle<()>,
medium: Medium,
widget: widgets::Screen,
list: Rc<List>,
items: Vec<ListItem>,
}
impl Screen<Medium, ()> for MediumScreen {
/// Create a new medium screen for the specified medium and load the
/// contents asynchronously.
fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc<Self> {
let mut items = Vec::new();
let mut first = true;
for (track_set_index, track_set) in medium.tracks.iter().enumerate() {
if !first {
items.push(ListItem::Separator);
} else {
first = false;
}
items.push(ListItem::Header(track_set_index));
for (track_index, _) in track_set.tracks.iter().enumerate() {
items.push(ListItem::Track(track_set_index, track_index));
}
}
let widget = widgets::Screen::new();
widget.set_title(&medium.name);
@ -65,7 +33,6 @@ impl Screen<Medium, ()> for MediumScreen {
medium,
widget,
list,
items,
});
this.widget.set_back_cb(clone!(@weak this => move || {
@ -82,63 +49,35 @@ impl Screen<Medium, ()> for MediumScreen {
}));
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
for track_set in &this.medium.tracks {
let indices = (0..track_set.tracks.len()).collect();
let playlist_item = PlaylistItem {
track_set: track_set.clone(),
indices,
};
this.handle.backend.pl().add_item(playlist_item).unwrap();
for track in &this.medium.tracks {
this.handle.backend.pl().add_item(track.clone()).unwrap();
}
}));
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
match this.items[index] {
ListItem::Header(index) => {
let track_set = &this.medium.tracks[index];
let recording = &track_set.recording;
let track = &this.medium.tracks[index];
let row = libadwaita::ActionRow::new();
row.set_activatable(false);
row.set_selectable(false);
row.set_title(Some(&recording.work.get_title()));
row.set_subtitle(Some(&recording.get_performers()));
row.upcast()
}
ListItem::Track(track_set_index, track_index) => {
let track_set = &this.medium.tracks[track_set_index];
let track = &track_set.tracks[track_index];
let mut parts = Vec::<String>::new();
for part in &track.work_parts {
parts.push(track_set.recording.work.parts[*part].title.clone());
}
let title = if parts.is_empty() {
gettext("Unknown")
} else {
parts.join(", ")
};
let row = libadwaita::ActionRow::new();
row.set_selectable(false);
row.set_activatable(false);
row.set_title(Some(&title));
row.set_margin_start(12);
row.upcast()
}
ListItem::Separator => {
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
separator.upcast()
}
let mut parts = Vec::<String>::new();
for part in &track.work_parts {
parts.push(track.recording.work.parts[*part].title.clone());
}
let title = if parts.is_empty() {
gettext("Unknown")
} else {
parts.join(", ")
};
let row = libadwaita::ActionRow::new();
row.set_selectable(false);
row.set_activatable(false);
row.set_title(Some(&title));
row.set_margin_start(12);
row.upcast()
}));
this.list.update(this.items.len());
this.list.update(this.medium.tracks.len());
this
}

View file

@ -5,21 +5,25 @@ use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libadwaita::prelude::*;
use musicus_backend::PlaylistItem;
use musicus_backend::db::Track;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
/// Elements for visually representing the playlist.
enum ListItem {
/// A header shown on top of a track set. This contains an index
/// referencing the playlist item containing this track set.
Header(usize),
/// A playable track.
Track {
/// Index within the playlist.
index: usize,
/// A playable track. This contains an index to the playlist item, an
/// index to the track and whether it is the currently played one.
Track(usize, usize, bool),
/// Whether this is the first track of the recording.
first: bool,
/// A separator shown between track sets.
/// Whether this is the currently played track.
playing: bool,
},
/// A separator shown between recordings.
Separator,
}
@ -37,10 +41,9 @@ pub struct PlayerScreen {
play_image: gtk::Image,
pause_image: gtk::Image,
list: Rc<List>,
playlist: RefCell<Vec<PlaylistItem>>,
playlist: RefCell<Vec<Track>>,
items: RefCell<Vec<ListItem>>,
seeking: Cell<bool>,
current_item: Cell<usize>,
current_track: Cell<usize>,
}
@ -87,7 +90,6 @@ impl Screen<(), ()> for PlayerScreen {
items: RefCell::new(Vec::new()),
playlist: RefCell::new(Vec::new()),
seeking: Cell::new(false),
current_item: Cell::new(0),
current_track: Cell::new(0),
});
@ -102,28 +104,26 @@ impl Screen<(), ()> for PlayerScreen {
this.show_playlist();
}));
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_item, current_track| {
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_track| {
this.previous_button.set_sensitive(this.handle.backend.pl().has_previous());
this.next_button.set_sensitive(this.handle.backend.pl().has_next());
let item = &this.playlist.borrow()[current_item];
let track = &item.track_set.tracks[current_track];
let track = &this.playlist.borrow()[current_track];
let mut parts = Vec::<String>::new();
for part in &track.work_parts {
parts.push(item.track_set.recording.work.parts[*part].title.clone());
parts.push(track.recording.work.parts[*part].title.clone());
}
let mut title = item.track_set.recording.work.get_title();
let mut title = track.recording.work.get_title();
if !parts.is_empty() {
title = format!("{}: {}", title, parts.join(", "));
}
this.title_label.set_text(&title);
this.subtitle_label.set_text(&item.track_set.recording.get_performers());
this.subtitle_label.set_text(&track.recording.get_performers());
this.position_label.set_text("0:00");
this.current_item.set(current_item);
this.current_track.set(current_track);
this.show_playlist();
@ -205,29 +205,17 @@ impl Screen<(), ()> for PlayerScreen {
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
let widget = match this.items.borrow()[index] {
ListItem::Header(item_index) => {
let playlist_item = &this.playlist.borrow()[item_index];
let recording = &playlist_item.track_set.recording;
let row = libadwaita::ActionRow::new();
row.set_activatable(false);
row.set_selectable(false);
row.set_title(Some(&recording.work.get_title()));
row.set_subtitle(Some(&recording.get_performers()));
row.upcast()
}
ListItem::Track(item_index, track_index, playing) => {
let playlist_item = &this.playlist.borrow()[item_index];
let index = playlist_item.indices[track_index];
let track = &playlist_item.track_set.tracks[index];
ListItem::Track {index, first, playing} => {
let track = &this.playlist.borrow()[index];
let mut parts = Vec::<String>::new();
for part in &track.work_parts {
parts.push(playlist_item.track_set.recording.work.parts[*part].title.clone());
parts.push(track.recording.work.parts[*part].title.clone());
}
let title = if parts.is_empty() {
let title = if first {
track.recording.work.get_title()
} else if parts.is_empty() {
gettext("Unknown")
} else {
parts.join(", ")
@ -238,8 +226,18 @@ impl Screen<(), ()> for PlayerScreen {
row.set_activatable(true);
row.set_title(Some(&title));
if first {
let subtitle = if !parts.is_empty() {
format!("{}\n{}", track.recording.get_performers(), parts.join(", "))
} else {
track.recording.get_performers()
};
row.set_subtitle(Some(&subtitle));
}
row.connect_activated(clone!(@weak this => move |_| {
this.handle.backend.pl().set_track(item_index, track_index).unwrap();
this.handle.backend.pl().set_track(index).unwrap();
}));
let icon = if playing {
@ -272,25 +270,35 @@ impl PlayerScreen {
/// Update the user interface according to the playlist.
fn show_playlist(&self) {
let playlist = self.playlist.borrow();
let current_item = self.current_item.get();
let current_track = self.current_track.get();
let mut first = true;
let mut items = Vec::new();
for (item_index, playlist_item) in playlist.iter().enumerate() {
if !first {
items.push(ListItem::Separator);
let mut last_recording_id = "";
for (index, track) in playlist.iter().enumerate() {
let first_track = if track.recording.id != last_recording_id {
last_recording_id = &track.recording.id;
if !first {
items.push(ListItem::Separator);
} else {
first = false;
}
true
} else {
first = false;
}
false
};
items.push(ListItem::Header(item_index));
let item = ListItem::Track {
index,
first: first_track,
playing: index == current_track,
};
for (index, _) in playlist_item.indices.iter().enumerate() {
let playing = current_item == item_index && current_track == index;
items.push(ListItem::Track(item_index, index, playing));
}
items.push(item);
}
let length = items.len();

View file

@ -6,29 +6,17 @@ use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use libadwaita::prelude::*;
use musicus_backend::PlaylistItem;
use musicus_backend::db::{Recording, TrackSet};
use musicus_backend::db::{Recording, Track};
use std::cell::RefCell;
use std::rc::Rc;
/// Representation of one entry within the track list.
enum ListItem {
/// A track row. This hold an index to the track set and an index to the
/// track within the track set.
Track(usize, usize),
/// A separator intended for use between track sets.
Separator,
}
/// A screen for showing a recording.
pub struct RecordingScreen {
handle: NavigationHandle<()>,
recording: Recording,
widget: widgets::Screen,
list: Rc<List>,
track_sets: RefCell<Vec<TrackSet>>,
items: RefCell<Vec<ListItem>>,
tracks: RefCell<Vec<Track>>,
}
impl Screen<Recording, ()> for RecordingScreen {
@ -48,22 +36,12 @@ impl Screen<Recording, ()> for RecordingScreen {
recording,
widget,
list,
track_sets: RefCell::new(Vec::new()),
items: RefCell::new(Vec::new()),
tracks: RefCell::new(Vec::new()),
});
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
if let Some(player) = this.handle.backend.get_player() {
if let Some(track_set) = this.track_sets.borrow().get(0).cloned() {
let indices = (0..track_set.tracks.len()).collect();
let playlist_item = PlaylistItem {
track_set,
indices,
};
player.add_item(playlist_item).unwrap();
}
for track in &*this.tracks.borrow() {
this.handle.backend.pl().add_item(track.clone()).unwrap();
}
}));
@ -86,47 +64,36 @@ impl Screen<Recording, ()> for RecordingScreen {
}));
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
let widget = match this.items.borrow()[index] {
ListItem::Track(track_set_index, track_index) => {
let track_set = &this.track_sets.borrow()[track_set_index];
let track = &track_set.tracks[track_index];
let track = &this.tracks.borrow()[index];
let mut title_parts = Vec::<String>::new();
for part in &track.work_parts {
title_parts.push(this.recording.work.parts[*part].title.clone());
}
let mut title_parts = Vec::<String>::new();
for part in &track.work_parts {
title_parts.push(this.recording.work.parts[*part].title.clone());
}
let title = if title_parts.is_empty() {
gettext("Unknown")
} else {
title_parts.join(", ")
};
let row = libadwaita::ActionRow::new();
row.set_title(Some(&title));
row.upcast()
}
ListItem::Separator => {
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
separator.upcast()
}
let title = if title_parts.is_empty() {
gettext("Unknown")
} else {
title_parts.join(", ")
};
widget
let row = libadwaita::ActionRow::new();
row.set_title(Some(&title));
row.upcast()
}));
// Load the content asynchronously.
spawn!(@clone this, async move {
let track_sets = this.handle
let tracks = this.handle
.backend
.db()
.get_track_sets(&this.recording.id)
.get_tracks(&this.recording.id)
.await
.unwrap();
this.show_track_sets(track_sets);
this.show_tracks(tracks);
this.widget.ready();
});
@ -135,26 +102,10 @@ impl Screen<Recording, ()> for RecordingScreen {
}
impl RecordingScreen {
/// Update the track sets variable as well as the user interface.
fn show_track_sets(&self, track_sets: Vec<TrackSet>) {
let mut first = true;
let mut items = Vec::new();
for (track_set_index, track_set) in track_sets.iter().enumerate() {
if !first {
items.push(ListItem::Separator);
} else {
first = false;
}
for (track_index, _) in track_set.tracks.iter().enumerate() {
items.push(ListItem::Track(track_set_index, track_index));
}
}
let length = items.len();
self.items.replace(items);
self.track_sets.replace(track_sets);
/// Update the tracks variable as well as the user interface.
fn show_tracks(&self, tracks: Vec<Track>) {
let length = tracks.len();
self.tracks.replace(tracks);
self.list.update(length);
}
}

View file

@ -1,7 +1,7 @@
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use musicus_backend::{Player, PlaylistItem};
use musicus_backend::Player;
use std::cell::RefCell;
use std::rc::Rc;
@ -83,7 +83,7 @@ impl PlayerBar {
self.player.replace(player.clone());
if let Some(player) = player {
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
let playlist = Rc::new(RefCell::new(Vec::new()));
player.add_playlist_cb(clone!(
@strong player,
@ -107,25 +107,24 @@ impl PlayerBar {
@strong self.title_label as title_label,
@strong self.subtitle_label as subtitle_label,
@strong self.position_label as position_label
=> move |current_item, current_track| {
=> move |current_track| {
previous_button.set_sensitive(player.has_previous());
next_button.set_sensitive(player.has_next());
let item = &playlist.borrow()[current_item];
let track = &item.track_set.tracks[current_track];
let track = &playlist.borrow()[current_track];
let mut parts = Vec::<String>::new();
for part in &track.work_parts {
parts.push(item.track_set.recording.work.parts[*part].title.clone());
parts.push(track.recording.work.parts[*part].title.clone());
}
let mut title = item.track_set.recording.work.get_title();
let mut title = track.recording.work.get_title();
if !parts.is_empty() {
title = format!("{}: {}", title, parts.join(", "));
}
title_label.set_text(&title);
subtitle_label.set_text(&item.track_set.recording.get_performers());
subtitle_label.set_text(&track.recording.get_performers());
position_label.set_text("0:00");
}
));