From 5345f8d6b3bcc6384d1a49a0599eeee32372eb9c Mon Sep 17 00:00:00 2001 From: Ward from fusion-voyager-3 Date: Sat, 24 Feb 2024 18:41:55 +0300 Subject: [PATCH] Setup page code --- Cargo.lock | 10 +- Cargo.toml | 2 + data/config/setup_steps.json | 20 +++ src/build_ui.rs | 1 - src/main.rs | 2 - src/save_window_size/mod.rs | 6 +- src/welcome_content_page/mod.rs | 20 ++- .../setup_steps_page/mod.rs | 157 ++++++++++++++++++ src/welcome_content_page/welcome_page/mod.rs | 31 +++- 9 files changed, 233 insertions(+), 16 deletions(-) create mode 100644 data/config/setup_steps.json create mode 100644 src/welcome_content_page/setup_steps_page/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 04221fd..065d3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,6 +854,8 @@ dependencies = [ "os_pipe", "regex", "rust-i18n", + "serde", + "serde_json", "single-instance", "user", "users", @@ -1051,18 +1053,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4573f78..f9bd46e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,5 @@ user = "0.1.1" users = "0.11.0" rust-i18n = "3.0.1" single-instance = "0.3.3" +serde_json = "1.0.114" +serde = { version = "1.0.197", features = ["derive"] } diff --git a/data/config/setup_steps.json b/data/config/setup_steps.json new file mode 100644 index 0000000..7305e17 --- /dev/null +++ b/data/config/setup_steps.json @@ -0,0 +1,20 @@ +{ + "setup_steps": [ + { + "id": 0, + "title": "update-pikaos-title", + "subtitle": "update-pikaos-subtitle", + "icon": "pika-system-software-update", + "button": "update-pikaos-button-label", + "command": "echo update" + }, + { + "id": 1, + "title": "install-media-codec-title", + "subtitle": "install-media-codec-subtitle", + "icon": "pika-media-tape", + "button": "install-media-codec-button-label", + "command": "echo codec" + } + ] +} \ No newline at end of file diff --git a/src/build_ui.rs b/src/build_ui.rs index e27a5f4..63517a6 100644 --- a/src/build_ui.rs +++ b/src/build_ui.rs @@ -1,7 +1,6 @@ // GTK crates use adw::prelude::*; use adw::*; -use glib::*; use gtk::Orientation; /// Use all gtk4 libraries (gtk4 -> gtk because cargo) /// Use all libadwaita libraries (libadwaita -> adw because cargo) diff --git a/src/main.rs b/src/main.rs index 78c1663..18dcbb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,6 @@ use gdk::Display; /// Use all libadwaita libraries (libadwaita -> adw because cargo) use gtk::*; use single_instance::SingleInstance; -use glib::*; -use glib::prelude::*; use std::boxed::Box; use users::*; diff --git a/src/save_window_size/mod.rs b/src/save_window_size/mod.rs index d125a86..844b555 100644 --- a/src/save_window_size/mod.rs +++ b/src/save_window_size/mod.rs @@ -1,10 +1,6 @@ -use adw::ffi::AdwApplicationWindow; -use adw::{gio, glib}; +use adw::{gio,}; use adw::prelude::SettingsExt; -use adw::subclass::window; use gtk::prelude::GtkWindowExt; -use glib::*; -use glib::prelude::*; pub fn save_window_size(window: &adw::ApplicationWindow, glib_settings: &gio::Settings) { let size = window.default_size(); diff --git a/src/welcome_content_page/mod.rs b/src/welcome_content_page/mod.rs index 1e31228..1dd13ce 100644 --- a/src/welcome_content_page/mod.rs +++ b/src/welcome_content_page/mod.rs @@ -9,7 +9,10 @@ use std::rc::Rc; // stack crates mod welcome_page; +mod setup_steps_page; + use welcome_page::welcome_page; +use setup_steps_page::setup_steps_page; use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION}; @@ -47,6 +50,10 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: } }); + let window_banner = adw::Banner::builder() + .revealed(false) + .build(); + let window_title_bar = gtk::HeaderBar::builder().show_title_buttons(true).build(); let credits_button = gtk::Button::builder() @@ -65,6 +72,12 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: content_box.append(&window_title_bar); + let welcome_content_page_stack_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .orientation(gtk::Orientation::Vertical) + .build(); + let welcome_content_page_stack = gtk::Stack::builder() .vexpand(true) .hexpand(true) @@ -79,7 +92,7 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: let welcome_content_page_split_view = adw::OverlaySplitView::builder() .vexpand(true) .hexpand(true) - .content(&welcome_content_page_stack) + .content(&welcome_content_page_stack_box) .sidebar(&welcome_content_page_stack_sidebar) .max_sidebar_width(300.0) .min_sidebar_width(300.0) @@ -110,6 +123,8 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: window.add_breakpoint(welcome_content_page_split_view_breakpoint); + welcome_content_page_stack_box.append(&window_banner); + welcome_content_page_stack_box.append(&welcome_content_page_stack); window_title_bar.pack_end(&credits_button); window_title_bar.pack_start(&sidebar_toggle_button); window_title_bar.pack_start(&startup_switch); @@ -136,5 +151,6 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: } })); - welcome_page(&welcome_content_page_stack, &internet_connected); + welcome_page(&welcome_content_page_stack, &window_banner, &internet_connected); + setup_steps_page(&welcome_content_page_stack, &window, &internet_connected); } diff --git a/src/welcome_content_page/setup_steps_page/mod.rs b/src/welcome_content_page/setup_steps_page/mod.rs new file mode 100644 index 0000000..1c6dbf6 --- /dev/null +++ b/src/welcome_content_page/setup_steps_page/mod.rs @@ -0,0 +1,157 @@ +// GTK crates +use duct::cmd; +use std::path::Path; +use std::fs; +use serde::Deserialize; +use std::{thread, time}; +use std::rc::Rc; +use std::cell::RefCell; +use adw::prelude::*; +use adw::*; +use glib::*; +use std::fs::File; + +#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)] +struct Setup_Steps_Entry { + id: i32, + title: String, + subtitle: String, + icon: String, + button: String, + command: String +} + +pub fn setup_steps_page( + setup_steps_content_page_stack: >k::Stack, + window: &adw::ApplicationWindow, + internet_connected: &Rc> +) { + let internet_connected_status = internet_connected.clone(); + + let (internet_loop_sender, internet_loop_receiver) = async_channel::unbounded(); + let internet_loop_sender = internet_loop_sender.clone(); + // The long running operation runs now in a separate thread + gio::spawn_blocking(move || loop { + thread::sleep(time::Duration::from_secs(1)); + internet_loop_sender + .send_blocking(true) + .expect("The channel needs to be open."); + }); + + let setup_steps_page_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .build(); + + let setup_steps_page_listbox = gtk::ListBox::builder() + .margin_top(20) + .margin_bottom(20) + .margin_start(20) + .margin_end(20) + .vexpand(true) + .hexpand(true) + .build(); + setup_steps_page_listbox.add_css_class("boxed-list"); + + let setup_steps_page_scroll = gtk::ScrolledWindow::builder() + // that puts items vertically + .hexpand(true) + .vexpand(true) + .child(&setup_steps_page_box) + .propagate_natural_width(true) + .propagate_natural_height(true) + .build(); + + let internet_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + internet_loop_context.spawn_local( + clone!(@strong internet_connected_status, @weak setup_steps_page_box => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + setup_steps_page_box.set_sensitive(true); + } else { + setup_steps_page_box.set_sensitive(false); + } + } + }), + ); + + let mut json_array: Vec = Vec::new(); + let json_path = "/home/ward/builds/pkg-pika-welcome/data/config/setup_steps.json"; + let json_data = fs::read_to_string(json_path).expect("Unable to read json"); + let json_data: serde_json::Value = serde_json::from_str(&json_data).expect("JSON format invalid"); + if let serde_json::Value::Array(setup_steps) = &json_data["setup_steps"] { + for setup_steps_entry in setup_steps { + let setup_steps_entry_struct: Setup_Steps_Entry = serde_json::from_value(setup_steps_entry.clone()).unwrap(); + json_array.push(setup_steps_entry_struct); + } + } + + for setup_steps_entry in json_array { + let (entry_command_status_loop_sender, entry_command_status_loop_receiver) = async_channel::unbounded(); + let entry_command_status_loop_sender: async_channel::Sender = entry_command_status_loop_sender.clone(); + + let entry_title = setup_steps_entry.title; + let entry_subtitle = setup_steps_entry.subtitle; + let entry_icon = setup_steps_entry.icon; + let entry_button = setup_steps_entry.button; + let entry_command = setup_steps_entry.command; + let entry_row = adw::ActionRow::builder() + .title(t!(&entry_title)) + .subtitle(t!(&entry_subtitle)) + .vexpand(true) + .hexpand(true) + .build(); + let entry_row_icon = gtk::Image::builder() + .icon_name(entry_icon) + .pixel_size(80) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + let entry_row_button = gtk::Button::builder() + .label(t!(&entry_button)) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + entry_row.add_prefix(&entry_row_icon); + entry_row.add_suffix(&entry_row_button); + + entry_row_button.connect_clicked(clone!(@strong entry_command, @weak window => move |_| { + gio::spawn_blocking(clone!(@strong entry_command_status_loop_sender, @strong entry_command => move || { + if Path::new("/tmp/pika-welcome-exec.sh").exists() { + fs::remove_file("/tmp/pika-welcome-exec.sh").expect("Bad permissions on /tmp/pika-installer-gtk4-target-manual.txt"); + } + fs::write("/tmp/pika-welcome-exec.sh", "#! /bin/bash\nset -e\n".to_owned() + &entry_command).expect("Unable to write file"); + let _ = cmd!("chmod", "+x", "/tmp/pika-welcome-exec.sh").read(); + let command = cmd!("/tmp/pika-welcome-exec.sh").run(); + if command.is_err() { + entry_command_status_loop_sender.send_blocking(false).expect("The channel needs to be open."); + } else { + entry_command_status_loop_sender.send_blocking(true).expect("The channel needs to be open."); + } + })); + })); + + let cmd_err_dialog = adw::MessageDialog::builder() + .body(t!("cmd_err_dialog_body")) + .heading(t!("cmd_err_dialog_heading")) + .transient_for(window) + .build(); + cmd_err_dialog.add_response("cmd_err_dialog_ok", &t!("cmd_err_dialog_ok_label").to_string()); + + let entry_command_status_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + entry_command_status_loop_context.spawn_local(clone!(@weak cmd_err_dialog, @strong entry_command_status_loop_receiver => async move { + while let Ok(state) = entry_command_status_loop_receiver.recv().await { + if state == false { + cmd_err_dialog.present(); + } + } + })); + setup_steps_page_listbox.append(&entry_row) + } + + setup_steps_page_box.append(&setup_steps_page_listbox); + + setup_steps_content_page_stack.add_titled(&setup_steps_page_scroll, Some("setup_steps_page"), &t!("setup_steps_page_title").to_string()); +} \ No newline at end of file diff --git a/src/welcome_content_page/welcome_page/mod.rs b/src/welcome_content_page/welcome_page/mod.rs index 8bdfb59..1e28701 100644 --- a/src/welcome_content_page/welcome_page/mod.rs +++ b/src/welcome_content_page/welcome_page/mod.rs @@ -1,17 +1,29 @@ // GTK crates +use std::{thread, time}; use std::rc::Rc; use std::cell::RefCell; use adw::prelude::*; use adw::*; -use adw::gio::ffi::GReallocFunc; use glib::*; -use gtk::Orientation; use crate::config::DISTRO_ICON; pub fn welcome_page( welcome_content_page_stack: >k::Stack, + window_banner: &adw::Banner, internet_connected: &Rc> ) { + let internet_connected_status = internet_connected.clone(); + + let (internet_loop_sender, internet_loop_receiver) = async_channel::unbounded(); + let internet_loop_sender = internet_loop_sender.clone(); + // The long running operation runs now in a separate thread + gio::spawn_blocking(move || loop { + thread::sleep(time::Duration::from_secs(1)); + internet_loop_sender + .send_blocking(true) + .expect("The channel needs to be open."); + }); + let welcome_page_text = adw::StatusPage::builder() .icon_name(DISTRO_ICON) .title(t!("welcome_page_text_title")) @@ -29,5 +41,20 @@ pub fn welcome_page( .propagate_natural_height(true) .build(); + let internet_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + internet_loop_context.spawn_local( + clone!(@strong internet_connected_status, @weak window_banner => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + window_banner.set_revealed(false); + } else { + window_banner.set_revealed(true); + window_banner.set_title(&t!("window_banner_no_internet")); + } + } + }), + ); + welcome_content_page_stack.add_titled(&welcome_page_scroll, Some("welcome_page"), &t!("welcome_page_title").to_string()); } \ No newline at end of file