mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Add empty page offering download
This commit is contained in:
parent
bf1ffef05a
commit
424c4c57a8
10 changed files with 295 additions and 9 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="@PATH_ID@">
|
<gresource prefix="@PATH_ID@">
|
||||||
|
<file preprocess="xml-stripblanks">icons/scalable/actions/library-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/actions/music-note-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/actions/music-note-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/actions/playlist-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/actions/playlist-symbolic.svg</file>
|
||||||
<file compressed="true">style.css</file>
|
<file compressed="true">style.css</file>
|
||||||
|
|
|
||||||
2
data/res/icons/scalable/actions/library-symbolic.svg
Normal file
2
data/res/icons/scalable/actions/library-symbolic.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 1.5 2 h 2 c 0.277344 0 0.5 0.222656 0.5 0.5 v 12 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -2 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -12 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 5.5 4 h 1 c 0.277344 0 0.5 0.222656 0.5 0.5 v 10 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -1 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -10 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 8.5 3 h 1 c 0.277344 0 0.5 0.222656 0.5 0.5 v 11 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -1 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -11 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 10.707031 1.460938 l 0.964844 -0.261719 c 0.265625 -0.070313 0.539063 0.089843 0.613281 0.355469 l 3.363282 12.558593 c 0.070312 0.265625 -0.085938 0.539063 -0.351563 0.609375 l -0.96875 0.261719 c -0.265625 0.070313 -0.539063 -0.089844 -0.613281 -0.355469 l -3.363282 -12.554687 c -0.070312 -0.269531 0.085938 -0.542969 0.355469 -0.613281 z m 0 0"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
72
data/ui/empty_page.blp
Normal file
72
data/ui/empty_page.blp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusEmptyPage: Adw.NavigationPage {
|
||||||
|
title: _("New Library");
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar header_bar {
|
||||||
|
[end]
|
||||||
|
MenuButton {
|
||||||
|
icon-name: "open-menu-symbolic";
|
||||||
|
menu-model: primary_menu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.StatusPage {
|
||||||
|
icon-name: "library-symbolic";
|
||||||
|
title: _("New Library");
|
||||||
|
description: _("You can import your recordings by selecting \"Import music\" in the main menu. Musicus also comes with a small pre-made library of recordings. You can download it using the button below.");
|
||||||
|
|
||||||
|
child: Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
|
||||||
|
Gtk.Button download_button {
|
||||||
|
halign: center;
|
||||||
|
label: _("Download music");
|
||||||
|
clicked => $download_library() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"suggested-action",
|
||||||
|
"pill",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.Clamp {
|
||||||
|
Gtk.ListBox process_list {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
visible: false;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list-separate",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu primary_menu {
|
||||||
|
item {
|
||||||
|
label: _("_Import music");
|
||||||
|
action: "win.import";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("_Library manager");
|
||||||
|
action: "win.library";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("_Preferences");
|
||||||
|
action: "win.preferences";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("_About Musicus");
|
||||||
|
action: "app.about";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using Adw 1;
|
||||||
template $MusicusPreferencesDialog: Adw.PreferencesDialog {
|
template $MusicusPreferencesDialog: Adw.PreferencesDialog {
|
||||||
Adw.PreferencesPage {
|
Adw.PreferencesPage {
|
||||||
title: _("Playback");
|
title: _("Playback");
|
||||||
|
icon-name: "media-playback-start-symbolic";
|
||||||
|
|
||||||
Adw.PreferencesGroup {
|
Adw.PreferencesGroup {
|
||||||
title: _("Default program");
|
title: _("Default program");
|
||||||
|
|
@ -65,6 +66,7 @@ template $MusicusPreferencesDialog: Adw.PreferencesDialog {
|
||||||
|
|
||||||
Adw.PreferencesPage {
|
Adw.PreferencesPage {
|
||||||
title: _("Library");
|
title: _("Library");
|
||||||
|
icon-name: "library-symbolic";
|
||||||
|
|
||||||
Adw.PreferencesGroup {
|
Adw.PreferencesGroup {
|
||||||
title: _("Library download");
|
title: _("Library download");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $MusicusWelcomePage : Adw.NavigationPage {
|
template $MusicusWelcomePage: Adw.NavigationPage {
|
||||||
title: _("Welcome to Musicus");
|
title: _("Welcome to Musicus");
|
||||||
tag: "welcome";
|
tag: "welcome";
|
||||||
|
|
||||||
|
|
@ -15,12 +15,17 @@ template $MusicusWelcomePage : Adw.NavigationPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Adw.StatusPage status_page {
|
Adw.StatusPage {
|
||||||
icon-name: "music-note-symbolic";
|
icon-name: "music-note-symbolic";
|
||||||
title: _("Welcome to Musicus");
|
title: _("Welcome to Musicus");
|
||||||
description: _("Get started by choosing where you want to store your music library. Are you using Musicus for the first time? If so, create a new empty folder for your library. If you wish, Musicus will automatically download some music for you.");
|
description: _("Get started by choosing where you want to store your music library. Are you using Musicus for the first time? If so, create a new empty folder for your library. If you wish, Musicus will automatically download some music for you.");
|
||||||
|
|
||||||
child: Gtk.Button {
|
child: Gtk.Button {
|
||||||
styles ["suggested-action", "pill"]
|
styles [
|
||||||
|
"suggested-action",
|
||||||
|
"pill",
|
||||||
|
]
|
||||||
|
|
||||||
halign: center;
|
halign: center;
|
||||||
label: _("Choose library folder");
|
label: _("Choose library folder");
|
||||||
clicked => $choose_library_folder() swapped;
|
clicked => $choose_library_folder() swapped;
|
||||||
|
|
@ -34,6 +39,7 @@ menu primary_menu {
|
||||||
label: _("_Preferences");
|
label: _("_Preferences");
|
||||||
action: "win.preferences";
|
action: "win.preferences";
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
label: _("_About Musicus");
|
label: _("_About Musicus");
|
||||||
action: "app.about";
|
action: "app.about";
|
||||||
|
|
|
||||||
173
src/empty_page.rs
Normal file
173
src/empty_page.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
|
use adw::{
|
||||||
|
prelude::*,
|
||||||
|
subclass::{navigation_page::NavigationPageImpl, prelude::*},
|
||||||
|
};
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use glib::clone;
|
||||||
|
use gtk::{gio, glib, glib::subclass::Signal};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config, library::Library, process::Process, process_manager::ProcessManager,
|
||||||
|
process_row::ProcessRow,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "data/ui/empty_page.blp")]
|
||||||
|
pub struct EmptyPage {
|
||||||
|
pub library: OnceCell<Library>,
|
||||||
|
pub process_manager: OnceCell<ProcessManager>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub download_button: TemplateChild<gtk::Button>,
|
||||||
|
#[template_child]
|
||||||
|
pub process_list: TemplateChild<gtk::ListBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for EmptyPage {
|
||||||
|
const NAME: &'static str = "MusicusEmptyPage";
|
||||||
|
type Type = super::EmptyPage;
|
||||||
|
type ParentType = adw::NavigationPage;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for EmptyPage {
|
||||||
|
fn signals() -> &'static [Signal] {
|
||||||
|
static SIGNALS: Lazy<Vec<Signal>> =
|
||||||
|
Lazy::new(|| vec![Signal::builder("ready").build()]);
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for EmptyPage {}
|
||||||
|
impl NavigationPageImpl for EmptyPage {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct EmptyPage(ObjectSubclass<imp::EmptyPage>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl EmptyPage {
|
||||||
|
pub fn new(library: &Library, process_manager: &ProcessManager) -> Self {
|
||||||
|
let obj: Self = glib::Object::new();
|
||||||
|
|
||||||
|
for process in process_manager.processes() {
|
||||||
|
obj.add_process(&process);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp().library.set(library.to_owned()).unwrap();
|
||||||
|
obj.imp()
|
||||||
|
.process_manager
|
||||||
|
.set(process_manager.to_owned())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_ready<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("ready", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
f(&obj);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
async fn download_library(&self) {
|
||||||
|
let dialog = adw::AlertDialog::builder()
|
||||||
|
.heading(&gettext("Disclaimer"))
|
||||||
|
.body(&gettext("You are about to download a library of audio files. These are from recordings that are in the public domain under EU law and are hosted on a server within the EU. Please ensure that you comply with the copyright laws of you country."))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
dialog.add_response("continue", &gettext("Continue"));
|
||||||
|
dialog.set_response_appearance("continue", adw::ResponseAppearance::Suggested);
|
||||||
|
dialog.add_response("cancel", &gettext("Cancel"));
|
||||||
|
dialog.set_default_response(Some("cancel"));
|
||||||
|
dialog.set_close_response("cancel");
|
||||||
|
|
||||||
|
let obj = self.to_owned();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
if dialog.choose_future(&obj).await == "continue" {
|
||||||
|
obj.imp().download_button.set_visible(false);
|
||||||
|
|
||||||
|
let settings = gio::Settings::new(config::APP_ID);
|
||||||
|
let url = if settings.boolean("use-custom-library-url") {
|
||||||
|
settings.string("custom-library-url").to_string()
|
||||||
|
} else {
|
||||||
|
config::LIBRARY_URL.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
match obj.imp().library.get().unwrap().import_url(&url) {
|
||||||
|
Ok(receiver) => {
|
||||||
|
let process = Process::new(&gettext("Downloading music library"), receiver);
|
||||||
|
|
||||||
|
process.connect_finished_notify(clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |process| {
|
||||||
|
if process.finished() {
|
||||||
|
if process.error().is_some() {
|
||||||
|
obj.imp().download_button.set_visible(true);
|
||||||
|
} else {
|
||||||
|
obj.emit_by_name::<()>("ready", &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.process_manager
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.add_process(&process);
|
||||||
|
|
||||||
|
obj.add_process(&process);
|
||||||
|
}
|
||||||
|
Err(err) => log::error!("Failed to download library: {err:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_process(&self, process: &Process) {
|
||||||
|
let row = ProcessRow::new(process);
|
||||||
|
|
||||||
|
row.connect_remove(clone!(
|
||||||
|
#[weak(rename_to = obj)]
|
||||||
|
self,
|
||||||
|
move |row| {
|
||||||
|
obj.imp()
|
||||||
|
.process_manager
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.remove_process(&row.process());
|
||||||
|
|
||||||
|
obj.imp().process_list.remove(row);
|
||||||
|
|
||||||
|
if obj.imp().process_list.first_child().is_none() {
|
||||||
|
obj.imp().process_list.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
self.imp().process_list.append(&row);
|
||||||
|
self.imp().process_list.set_visible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -77,6 +77,16 @@ impl Library {
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this library is empty. The library is considered empty, if
|
||||||
|
/// there are no tracks.
|
||||||
|
pub fn is_empty(&self) -> Result<bool> {
|
||||||
|
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
|
||||||
|
Ok(tracks::table
|
||||||
|
.first::<tables::Track>(connection)
|
||||||
|
.optional()?
|
||||||
|
.is_none())
|
||||||
|
}
|
||||||
|
|
||||||
/// Import from a library archive.
|
/// Import from a library archive.
|
||||||
pub fn import_archive(
|
pub fn import_archive(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod editor;
|
mod editor;
|
||||||
|
mod empty_page;
|
||||||
mod library;
|
mod library;
|
||||||
mod library_manager;
|
mod library_manager;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@ mod imp {
|
||||||
|
|
||||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
#[template(file = "data/ui/welcome_page.blp")]
|
#[template(file = "data/ui/welcome_page.blp")]
|
||||||
pub struct WelcomePage {
|
pub struct WelcomePage {}
|
||||||
#[template_child]
|
|
||||||
pub status_page: TemplateChild<adw::StatusPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for WelcomePage {
|
impl ObjectSubclass for WelcomePage {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use gtk::{gio, glib, glib::clone};
|
||||||
use crate::{
|
use crate::{
|
||||||
config,
|
config,
|
||||||
editor::tracks::TracksEditor,
|
editor::tracks::TracksEditor,
|
||||||
|
empty_page::EmptyPage,
|
||||||
library::{Library, LibraryQuery},
|
library::{Library, LibraryQuery},
|
||||||
library_manager::LibraryManager,
|
library_manager::LibraryManager,
|
||||||
player::Player,
|
player::Player,
|
||||||
|
|
@ -259,8 +260,29 @@ impl Window {
|
||||||
));
|
));
|
||||||
|
|
||||||
self.imp().player.set_library(&library);
|
self.imp().player.set_library(&library);
|
||||||
|
|
||||||
|
let is_empty = library.is_empty()?;
|
||||||
self.imp().library.replace(Some(library));
|
self.imp().library.replace(Some(library));
|
||||||
|
|
||||||
|
if is_empty {
|
||||||
|
let navigation = self.imp().navigation_view.get();
|
||||||
|
let empty_page = EmptyPage::new(
|
||||||
|
self.imp().library.borrow().as_ref().unwrap(),
|
||||||
|
&self.imp().process_manager,
|
||||||
|
);
|
||||||
|
|
||||||
|
empty_page.connect_ready(clone!(
|
||||||
|
#[weak(rename_to = obj)]
|
||||||
|
self,
|
||||||
|
move |_| {
|
||||||
|
obj.reset_view();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
navigation.replace(&[empty_page.into()]);
|
||||||
|
} else {
|
||||||
self.reset_view();
|
self.reset_view();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue