Setup page code

This commit is contained in:
Ward from fusion-voyager-3 2024-02-24 18:41:55 +03:00
parent 356381a83c
commit 5345f8d6b3
9 changed files with 233 additions and 16 deletions

10
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

View File

@ -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"
}
]
}

View File

@ -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)

View File

@ -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::*;

View File

@ -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();

View File

@ -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: &gtk::
}
});
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: &gtk::
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: &gtk::
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: &gtk::
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: &gtk::
}
}));
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);
}

View File

@ -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: &gtk::Stack,
window: &adw::ApplicationWindow,
internet_connected: &Rc<RefCell<bool>>
) {
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<Setup_Steps_Entry> = 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<bool> = 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());
}

View File

@ -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: &gtk::Stack,
window_banner: &adw::Banner,
internet_connected: &Rc<RefCell<bool>>
) {
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());
}