feat: show toast after upload is finished

This commit is contained in:
uku 2025-05-13 00:01:05 +02:00
parent 3b75134572
commit 1d93482209
Signed by: uku
SSH key fingerprint: SHA256:4P0aN6M8ajKukNi6aPOaX0LacanGYtlfjmN+m/sHY/o
4 changed files with 336 additions and 59 deletions

View file

@ -6,7 +6,7 @@ use std::{borrow::Cow, path::PathBuf, time::Duration};
use color_eyre::eyre::{OptionExt, Result, bail};
use gobject::GtkZiplineFolder;
use relm::{Dialog, DialogInput};
use relm::{Dialog, DialogInput, Toast, ToastInput};
use relm4::{
Component, ComponentController, Controller, RelmApp, Sender,
adw::{self, prelude::*},
@ -51,7 +51,7 @@ enum Message {
enum ProgressMessage {
SetTotal(usize),
Progress(usize),
Finish,
Finish(String),
Error(String),
}
@ -69,6 +69,7 @@ struct Tyrolienne {
video_path: Option<PathBuf>,
folder: Option<ZiplineFolder>,
dialog: Controller<Dialog>,
toast: Controller<Toast>,
}
impl Tyrolienne {
@ -100,63 +101,68 @@ impl AsyncComponent for Tyrolienne {
set_title: Some("Tyrolienne"),
set_default_width: 500,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 0,
adw::HeaderBar {},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 32,
set_margin_top: 32,
set_margin_bottom: 32,
set_margin_start: 32,
set_margin_end: 32,
gtk::ListBox {
set_selection_mode: gtk::SelectionMode::None,
set_css_classes: &["boxed-list"],
adw::ActionRow {
set_activatable: true,
set_css_classes: &["property"],
set_title: "Video file",
#[watch]
set_subtitle: &model.display_video_path(),
connect_activated => Message::OpenFilePicker,
},
adw::ComboRow {
set_title: "Folder",
set_model: Some(&folder_store),
set_expression: Some(&folder_expression),
connect_activated[sender] => move |r| {
if let Some(item) = r
.selected_item()
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
{
sender.input(Message::SetFolder(item.as_folder()));
}
},
}
},
#[local_ref]
toast_overlay -> adw::ToastOverlay {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 10,
set_spacing: 32,
set_margin_top: 32,
set_margin_bottom: 32,
set_margin_start: 32,
set_margin_end: 32,
gtk::ProgressBar {
#[watch]
set_fraction: model.progress as f64 / model.total as f64,
gtk::ListBox {
set_selection_mode: gtk::SelectionMode::None,
set_css_classes: &["boxed-list"],
adw::ActionRow {
set_activatable: true,
set_css_classes: &["property"],
set_title: "Video file",
#[watch]
set_subtitle: &model.display_video_path(),
connect_activated => Message::OpenFilePicker,
},
adw::ComboRow {
set_title: "Folder",
set_model: Some(&folder_store),
set_expression: Some(&folder_expression),
connect_activated[sender] => move |r| {
if let Some(item) = r
.selected_item()
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
{
sender.input(Message::SetFolder(item.as_folder()));
}
},
}
},
gtk::Button {
#[watch]
set_label: if model.locked { "Uploading..." } else { "Send" },
#[watch]
set_sensitive: !model.locked && model.video_path.is_some(),
connect_clicked => Message::StartTheProcess,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 10,
gtk::ProgressBar {
#[watch]
set_fraction: model.progress as f64 / model.total as f64,
},
gtk::Button {
#[watch]
set_label: if model.locked { "Uploading..." } else { "Send" },
#[watch]
set_sensitive: !model.locked && model.video_path.is_some(),
connect_clicked => Message::StartTheProcess,
}
}
}
}
@ -169,6 +175,8 @@ impl AsyncComponent for Tyrolienne {
root: Self::Root,
sender: relm4::AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> {
let toast_overlay = adw::ToastOverlay::new();
let model = Tyrolienne {
config,
locked: false,
@ -177,7 +185,10 @@ impl AsyncComponent for Tyrolienne {
video_path: None,
folder: None,
dialog: Dialog::builder()
.launch(root.clone())
.launch(toast_overlay.clone())
.forward(sender.input_sender(), |_| Message::Nothing),
toast: Toast::builder()
.launch(toast_overlay.clone())
.forward(sender.input_sender(), |_| Message::Nothing),
};
@ -242,10 +253,7 @@ impl AsyncComponent for Tyrolienne {
shutdown
.register(async move {
match the_process(info, &out).await {
Ok(url) => {
tracing::info!("{url}");
out.emit(ProgressMessage::Finish);
}
Ok(url) => out.emit(ProgressMessage::Finish(url)),
Err(e) => out.emit(ProgressMessage::Error(e.to_string())),
}
})
@ -265,12 +273,14 @@ impl AsyncComponent for Tyrolienne {
match message {
ProgressMessage::SetTotal(total) => self.total = total,
ProgressMessage::Progress(prog) => self.progress += prog,
ProgressMessage::Finish | ProgressMessage::Error(_) => {
ProgressMessage::Finish(_) | ProgressMessage::Error(_) => {
self.locked = false;
self.progress = 0;
self.total = 1;
if let ProgressMessage::Error(e) = message {
if let ProgressMessage::Finish(url) = message {
self.toast.emit(ToastInput::Show(url));
} else if let ProgressMessage::Error(e) = message {
self.dialog.emit(DialogInput::Show {
heading: "An error occurred".into(),
body: e.to_string(),