Initial version of new import screen

This commit is contained in:
Elias Projahn 2021-03-23 09:53:16 +01:00
parent e293972c0d
commit 606ee563e9
8 changed files with 361 additions and 2 deletions

View file

@ -167,6 +167,22 @@ impl Database {
Ok(medium)
}
/// Get mediums that have a specific source ID.
pub fn get_mediums_by_source_id(&self, source_id: &str) -> Result<Vec<Medium>> {
let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table
.filter(mediums::discid.nullable().eq(source_id))
.load::<MediumRow>(&self.connection)?;
for row in rows {
let medium = self.get_medium_data(row)?;
mediums.push(medium);
}
Ok(mediums)
}
/// Get mediums on which this person is performing.
pub fn get_mediums_for_person(&self, person_id: &str) -> Result<Vec<Medium>> {
let mut mediums: Vec<Medium> = Vec::new();

View file

@ -29,6 +29,7 @@ pub enum Action {
RecordingExists(String, Sender<Result<bool>>),
UpdateMedium(Medium, Sender<Result<()>>),
GetMedium(String, Sender<Result<Option<Medium>>>),
GetMediumsBySourceId(String, Sender<Result<Vec<Medium>>>),
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
DeleteMedium(String, Sender<Result<()>>),
@ -132,6 +133,9 @@ impl DbThread {
GetMedium(id, sender) => {
sender.send(db.get_medium(&id)).unwrap();
}
GetMediumsBySourceId(id, sender) => {
sender.send(db.get_mediums_by_source_id(&id)).unwrap();
}
GetMediumsForPerson(id, sender) => {
sender.send(db.get_mediums_for_person(&id)).unwrap();
}
@ -346,6 +350,13 @@ impl DbThread {
receiver.await?
}
/// Get all mediums with the specified source ID.
pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetMediumsBySourceId(id.to_owned(), sender))?;
receiver.await?
}
/// Get all mediums on which a person performs.
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
let (sender, receiver) = oneshot::channel();

View file

@ -11,6 +11,7 @@ futures-channel = "0.3.5"
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
gstreamer = "0.16.4"
gtk-macros = "0.2.0"
log = "0.4.14"
musicus_backend = { version = "0.1.0", path = "../backend" }
once_cell = "1.5.2"
rand = "0.7.3"

View file

@ -2,9 +2,11 @@
<gresources>
<gresource prefix="/de/johrpan/musicus">
<file preprocess="xml-stripblanks">ui/editor.ui</file>
<file preprocess="xml-stripblanks">ui/import_screen.ui</file>
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
<file preprocess="xml-stripblanks">ui/main_screen.ui</file>
<file preprocess="xml-stripblanks">ui/medium_editor.ui</file>
<file preprocess="xml-stripblanks">ui/medium_preview.ui</file>
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
<file preprocess="xml-stripblanks">ui/player_screen.ui</file>

View file

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<object class="GtkBox" id="widget">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar">
<property name="show-start-title-buttons">false</property>
<property name="show-end-title-buttons">false</property>
<property name="title-widget">
<object class="AdwWindowTitle" id="window_title">
<property name="title" translatable="yes">Import music</property>
</object>
</property>
<child>
<object class="GtkButton" id="back_button">
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">True</property>
<child>
<object class="AdwClamp">
<child>
<object class="GtkBox">
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-bottom">6</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="margin-top">12</property>
<property name="margin-bottom">6</property>
<property name="label" translatable="yes">Matching metadata</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<child>
<object class="GtkStack" id="matching_stack">
<property name="transition-type">crossfade</property>
<property name="vhomogeneous">false</property>
<property name="interpolate-size">true</property>
<child>
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="child">
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Loading…</property>
<child>
<object class="GtkSpinner">
<property name="spinning">True</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">error</property>
<property name="child">
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow" id="error_row">
<property name="title" translatable="yes">Error while searching for matching metadata</property>
<property name="activatable">True</property>
<property name="activatable-widget">try_again_button</property>
<child>
<object class="GtkButton" id="try_again_button">
<property name="icon-name">view-refresh-symbolic</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">empty</property>
<property name="child">
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">No matching metadata found</property>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">content</property>
<property name="child">
<object class="GtkListBox" id="matching_list">
<property name="selection-mode">none</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="margin-top">12</property>
<property name="margin-bottom">6</property>
<property name="label" translatable="yes">Manually add metadata</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Select existing medium</property>
<property name="activatable">True</property>
<property name="activatable-widget">select_button</property>
<child>
<object class="GtkButton" id="select_button">
<property name="label" translatable="yes">Select</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Add a new medium</property>
<property name="activatable">True</property>
<property name="activatable-widget">add_button</property>
<child>
<object class="GtkButton" id="add_button">
<property name="label" translatable="yes">Add</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View file

@ -0,0 +1,147 @@
use super::medium_editor::MediumEditor;
use super::medium_preview::MediumPreview;
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libadwaita::prelude::*;
use log::debug;
use musicus_backend::db::Medium;
use musicus_backend::import::ImportSession;
use std::rc::Rc;
use std::sync::Arc;
/// A dialog for selecting metadata when importing music.
pub struct ImportScreen {
handle: NavigationHandle<()>,
session: Arc<ImportSession>,
widget: gtk::Box,
matching_stack: gtk::Stack,
error_row: libadwaita::ActionRow,
matching_list: gtk::ListBox,
}
impl ImportScreen {
/// Find matching mediums on the server.
fn load_matches(self: &Rc<Self>) {
self.matching_stack.set_visible_child_name("loading");
let this = self;
spawn!(@clone this, async move {
match this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await {
Ok(mediums) => {
if !mediums.is_empty() {
this.show_matches(mediums);
this.matching_stack.set_visible_child_name("content");
} else {
this.matching_stack.set_visible_child_name("empty");
}
}
Err(err) => {
this.error_row.set_subtitle(Some(&err.to_string()));
this.matching_stack.set_visible_child_name("error");
}
}
});
}
/// Populate the list of matches
fn show_matches(self: &Rc<Self>, mediums: Vec<Medium>) {
if let Some(mut child) = self.matching_list.get_first_child() {
loop {
let next_child = child.get_next_sibling();
self.matching_list.remove(&child);
match next_child {
Some(next_child) => child = next_child,
None => break,
}
}
}
let this = self;
for medium in mediums {
let row = libadwaita::ActionRowBuilder::new()
.title(&medium.name)
.subtitle(&format!("{} Recordings", medium.tracks.len()))
.activatable(true)
.build();
row.connect_activated(clone!(@weak this => move |_| {
debug!("Medium selected: {}", medium.name);
}));
this.matching_list.append(&row);
}
}
/// Select a medium from somewhere and present a preview.
fn select_medium(self: &Rc<Self>, medium: Medium) {
let this = self;
spawn!(@clone this, async move {
push!(this.handle, MediumPreview, (this.session.clone(), medium)).await;
});
}
}
impl Screen<Arc<ImportSession>, ()> for ImportScreen {
/// Create a new import screen.
fn new(session: Arc<ImportSession>, handle: NavigationHandle<()>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_screen.ui");
get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Stack, matching_stack);
get_widget!(builder, gtk::Button, try_again_button);
get_widget!(builder, libadwaita::ActionRow, error_row);
get_widget!(builder, gtk::ListBox, matching_list);
get_widget!(builder, gtk::Button, select_button);
get_widget!(builder, gtk::Button, add_button);
let this = Rc::new(Self {
handle,
session,
widget,
matching_stack,
error_row,
matching_list,
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
try_again_button.connect_clicked(clone!(@weak this => move |_| {
this.load_matches();
}));
select_button.connect_clicked(clone!(@weak this => move |_| {
debug!("TODO: Show medium selector.");
}));
add_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
push!(this.handle, MediumEditor, Arc::clone(&this.session)).await;
});
}));
// Initialize the view
this.load_matches();
this
}
}
impl Widget for ImportScreen {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
}

View file

@ -1,4 +1,6 @@
mod import_screen;
mod medium_editor;
mod medium_preview;
mod source_selector;
mod track_editor;
mod track_selector;

View file

@ -1,3 +1,4 @@
use super::import_screen::ImportScreen;
use super::medium_editor::MediumEditor;
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
@ -65,8 +66,10 @@ impl Screen<(), ()> for SourceSelector {
spawn!(@clone this, async move {
match ImportSession::folder(PathBuf::from(path)).await {
Ok(session) => {
push!(this.handle, MediumEditor, session).await;
this.handle.pop(Some(()));
// push!(this.handle, MediumEditor, session).await;
// this.handle.pop(Some(()));
push!(this.handle, ImportScreen, session).await;
}
Err(err) => {
this.status_page.set_description(Some(&err.to_string()));