mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-25 20:37:24 +02: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"?>
|
||||
<gresources>
|
||||
<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/playlist-symbolic.svg</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 {
|
||||
Adw.PreferencesPage {
|
||||
title: _("Playback");
|
||||
icon-name: "media-playback-start-symbolic";
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
title: _("Default program");
|
||||
|
|
@ -65,6 +66,7 @@ template $MusicusPreferencesDialog: Adw.PreferencesDialog {
|
|||
|
||||
Adw.PreferencesPage {
|
||||
title: _("Library");
|
||||
icon-name: "library-symbolic";
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
title: _("Library download");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusWelcomePage : Adw.NavigationPage {
|
||||
template $MusicusWelcomePage: Adw.NavigationPage {
|
||||
title: _("Welcome to Musicus");
|
||||
tag: "welcome";
|
||||
|
||||
|
|
@ -15,12 +15,17 @@ template $MusicusWelcomePage : Adw.NavigationPage {
|
|||
}
|
||||
}
|
||||
|
||||
Adw.StatusPage status_page {
|
||||
Adw.StatusPage {
|
||||
icon-name: "music-note-symbolic";
|
||||
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.");
|
||||
|
||||
child: Gtk.Button {
|
||||
styles ["suggested-action", "pill"]
|
||||
styles [
|
||||
"suggested-action",
|
||||
"pill",
|
||||
]
|
||||
|
||||
halign: center;
|
||||
label: _("Choose library folder");
|
||||
clicked => $choose_library_folder() swapped;
|
||||
|
|
@ -34,8 +39,9 @@ menu primary_menu {
|
|||
label: _("_Preferences");
|
||||
action: "win.preferences";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_About Musicus");
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn import_archive(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod application;
|
|||
mod config;
|
||||
mod db;
|
||||
mod editor;
|
||||
mod empty_page;
|
||||
mod library;
|
||||
mod library_manager;
|
||||
mod player;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ mod imp {
|
|||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||
#[template(file = "data/ui/welcome_page.blp")]
|
||||
pub struct WelcomePage {
|
||||
#[template_child]
|
||||
pub status_page: TemplateChild<adw::StatusPage>,
|
||||
}
|
||||
pub struct WelcomePage {}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for WelcomePage {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use gtk::{gio, glib, glib::clone};
|
|||
use crate::{
|
||||
config,
|
||||
editor::tracks::TracksEditor,
|
||||
empty_page::EmptyPage,
|
||||
library::{Library, LibraryQuery},
|
||||
library_manager::LibraryManager,
|
||||
player::Player,
|
||||
|
|
@ -259,8 +260,29 @@ impl Window {
|
|||
));
|
||||
|
||||
self.imp().player.set_library(&library);
|
||||
|
||||
let is_empty = library.is_empty()?;
|
||||
self.imp().library.replace(Some(library));
|
||||
self.reset_view();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue