[fractal/fractal-next] toast: Simplify the API



commit d5b4b3f3f215e63375fdc75867b94b08c5bde06f
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Wed Feb 16 13:22:21 2022 +0100

    toast: Simplify the API

 src/components/in_app_notification.rs              |   2 +-
 src/components/toast.rs                            | 137 ++++++++++++++++++---
 src/login.rs                                       |  20 +--
 .../content/verification/session_verification.rs   |  10 +-
 src/session/mod.rs                                 |  33 +----
 src/session/room/event_actions.rs                  |  22 +---
 src/session/room/mod.rs                            | 114 ++++++++---------
 src/session/room_list.rs                           |   8 +-
 src/session/verification/identity_verification.rs  |  10 +-
 src/window.rs                                      |  16 +--
 10 files changed, 192 insertions(+), 180 deletions(-)
---
diff --git a/src/components/in_app_notification.rs b/src/components/in_app_notification.rs
index 64dbf7edb..5a8f06f31 100644
--- a/src/components/in_app_notification.rs
+++ b/src/components/in_app_notification.rs
@@ -155,7 +155,7 @@ impl InAppNotification {
             .as_ref()
             .and_then(|error_list| error_list.item(0))
             .and_then(|obj| obj.downcast::<Toast>().ok())
-            .and_then(|error| error.widget())
+            .map(|error| error.widget())
         {
             if let Some(current_widget) = priv_.current_widget.take() {
                 priv_.box_.remove(&current_widget);
diff --git a/src/components/toast.rs b/src/components/toast.rs
index 8a9f52d38..df52cbd42 100644
--- a/src/components/toast.rs
+++ b/src/components/toast.rs
@@ -1,15 +1,18 @@
-use gtk::{glib, subclass::prelude::*};
+use gtk::{glib, prelude::*, subclass::prelude::*};
 
-type WidgetBuilderFn = Box<dyn Fn(&super::Toast) -> Option<gtk::Widget> + 'static>;
+use crate::components::LabelWithWidgets;
 
 mod imp {
     use std::cell::RefCell;
 
+    use once_cell::sync::Lazy;
+
     use super::*;
 
     #[derive(Default)]
     pub struct Toast {
-        pub widget_builder: RefCell<Option<WidgetBuilderFn>>,
+        pub title: RefCell<Option<String>>,
+        pub widgets: RefCell<Vec<gtk::Widget>>,
     }
 
     #[glib::object_subclass]
@@ -19,7 +22,41 @@ mod imp {
         type ParentType = glib::Object;
     }
 
-    impl ObjectImpl for Toast {}
+    impl ObjectImpl for Toast {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpecString::new(
+                    "title",
+                    "Title",
+                    "The title of the toast",
+                    None,
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "title" => obj.set_title(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "title" => obj.title().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
 }
 
 glib::wrapper! {
@@ -28,22 +65,88 @@ glib::wrapper! {
 }
 
 impl Toast {
-    pub fn new<F: Fn(&Self) -> Option<gtk::Widget> + 'static>(f: F) -> Self {
-        let obj: Self = glib::Object::new(&[]).expect("Failed to create Toast");
-        obj.set_widget_builder(f);
-        obj
+    pub fn new(title: &str) -> Self {
+        glib::Object::new(&[("title", &title)]).expect("Failed to create Toast")
+    }
+
+    pub fn builder() -> ToastBuilder {
+        ToastBuilder::new()
+    }
+
+    pub fn title(&self) -> Option<String> {
+        self.imp().title.borrow().clone()
+    }
+
+    pub fn set_title(&self, title: Option<&str>) {
+        let priv_ = self.imp();
+        if priv_.title.borrow().as_deref() == title {
+            return;
+        }
+
+        priv_.title.replace(title.map(ToOwned::to_owned));
+        self.notify("title");
+    }
+
+    pub fn widgets(&self) -> Vec<gtk::Widget> {
+        self.imp().widgets.borrow().clone()
+    }
+
+    pub fn set_widgets(&self, widgets: &[&impl IsA<gtk::Widget>]) {
+        self.imp()
+            .widgets
+            .replace(widgets.iter().map(|w| w.upcast_ref().clone()).collect());
+    }
+
+    pub fn widget(&self) -> gtk::Widget {
+        if self.widgets().is_empty() {
+            gtk::Label::builder()
+                .wrap(true)
+                .label(&self.title().unwrap_or_default())
+                .build()
+                .upcast()
+        } else {
+            LabelWithWidgets::new(&self.title().unwrap_or_default(), self.widgets()).upcast()
+        }
+    }
+}
+
+impl From<Toast> for adw::Toast {
+    fn from(toast: Toast) -> Self {
+        if toast.widgets().is_empty() {
+            adw::Toast::new(&toast.title().unwrap_or_default())
+        } else {
+            // When AdwToast supports custom titles.
+            todo!()
+        }
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct ToastBuilder {
+    title: Option<String>,
+    widgets: Option<Vec<gtk::Widget>>,
+}
+
+impl ToastBuilder {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn title(mut self, title: &str) -> Self {
+        self.title = Some(title.to_owned());
+        self
     }
 
-    /// Set a function that builds the widget used to display this error in the
-    /// UI
-    pub fn set_widget_builder<F: Fn(&Self) -> Option<gtk::Widget> + 'static>(&self, f: F) {
-        self.imp().widget_builder.replace(Some(Box::new(f)));
+    pub fn widgets(mut self, widgets: &[&impl IsA<gtk::Widget>]) -> Self {
+        self.widgets = Some(widgets.iter().map(|w| w.upcast_ref().clone()).collect());
+        self
     }
 
-    /// Produces a widget via the function set in `Self::set_widget_builder()`
-    pub fn widget(&self) -> Option<gtk::Widget> {
-        let widget_builder = self.imp().widget_builder.borrow();
-        let widget_builder = widget_builder.as_ref()?;
-        widget_builder(self)
+    pub fn build(&self) -> Toast {
+        let toast = Toast::new(self.title.as_ref().unwrap());
+        if let Some(widgets) = &self.widgets {
+            toast.set_widgets(widgets.iter().collect::<Vec<_>>().as_slice());
+        }
+        toast
     }
 }
diff --git a/src/login.rs b/src/login.rs
index 13692fe8d..5285dfac7 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -311,15 +311,7 @@ impl Login {
                     }
                     Err(error) => {
                         warn!("Failed to discover homeserver: {}", error);
-                        let error_string = error.to_user_facing();
-
-                        obj.parent_window().append_error(&Toast::new(move |_| {
-                            let error_label = gtk::Label::builder()
-                                .label(&error_string)
-                                .wrap(true)
-                                .build();
-                            Some(error_label.upcast())
-                        }));
+                        obj.parent_window().append_error(&Toast::new(&error.to_user_facing()));
                     }
                 };
                 obj.unfreeze();
@@ -353,15 +345,7 @@ impl Login {
                     }
                     Err(error) => {
                         warn!("Failed to check homeserver: {}", error);
-                        let error_string = error.to_user_facing();
-
-                        obj.parent_window().append_error(&Toast::new(move |_| {
-                            let error_label = gtk::Label::builder()
-                                .label(&error_string)
-                                .wrap(true)
-                                .build();
-                            Some(error_label.upcast())
-                        }));
+                        obj.parent_window().append_error(&Toast::new(&error.to_user_facing()));
                     }
                 };
                 obj.unfreeze();
diff --git a/src/session/content/verification/session_verification.rs 
b/src/session/content/verification/session_verification.rs
index 4bf642c49..b9de6140e 100644
--- a/src/session/content/verification/session_verification.rs
+++ b/src/session/content/verification/session_verification.rs
@@ -315,16 +315,8 @@ impl SessionVerification {
             };
 
             if let Some(error_message) = error_message {
-                let error = Toast::new(move |_| {
-                    let error_label = gtk::Label::builder()
-                        .label(&error_message)
-                        .wrap(true)
-                        .build();
-                    Some(error_label.upcast())
-                });
-
                 if let Some(window) = obj.parent_window() {
-                    window.append_error(&error);
+                    window.append_error(&Toast::new(&error_message));
                 }
             } else {
                 // TODO tell user that the a crypto identity was created
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 653e9cec8..9c39bf706 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -422,18 +422,10 @@ impl Session {
                         Ok(()) => None,
                         Err(error) => {
                             warn!("Couldn't store session: {:?}", error);
-                            let error_string = error.to_user_facing();
-                            Some(Toast::new(move |_| {
-                                let error_label = gtk::Label::builder()
-                                    .label(
-                                        &(gettext("Unable to store session")
-                                            + ": "
-                                            + &error_string),
-                                    )
-                                    .wrap(true)
-                                    .build();
-                                Some(error_label.upcast())
-                            }))
+                            Some(Toast::new(&gettext!(
+                                "Unable to store session: {}",
+                                &error.to_user_facing()
+                            )))
                         }
                     }
                 } else {
@@ -453,15 +445,7 @@ impl Session {
 
                 priv_.logout_on_dispose.set(false);
 
-                let error_string = error.to_user_facing();
-
-                Some(Toast::new(move |_| {
-                    let error_label = gtk::Label::builder()
-                        .label(&error_string)
-                        .wrap(true)
-                        .build();
-                    Some(error_label.upcast())
-                }))
+                Some(Toast::new(&error.to_user_facing()))
             }
         };
 
@@ -715,13 +699,8 @@ impl Session {
             Ok(_) => self.cleanup_session(),
             Err(error) => {
                 error!("Couldn’t logout the session {}", error);
-                let error = Toast::new(move |_| {
-                    let label = gtk::Label::new(Some(&gettext("Failed to logout the session.")));
-                    Some(label.upcast())
-                });
-
                 if let Some(window) = self.parent_window() {
-                    window.append_error(&error);
+                    window.append_error(&Toast::new(&gettext("Failed to logout the session.")));
                 }
             }
         }
diff --git a/src/session/room/event_actions.rs b/src/session/room/event_actions.rs
index bfcba25da..8072008fb 100644
--- a/src/session/room/event_actions.rs
+++ b/src/session/room/event_actions.rs
@@ -195,16 +195,7 @@ where
                     Ok(res) => res,
                     Err(err) => {
                         error!("Could not get file: {}", err);
-
-                        let error_message = err.to_user_facing();
-                        let error = Toast::new(move |_| {
-                            let error_label = gtk::Label::builder()
-                                .label(&error_message)
-                                .wrap(true)
-                                .build();
-                            Some(error_label.upcast())
-                        });
-                        window.append_error(&error);
+                        window.append_error(&Toast::new(&err.to_user_facing()));
 
                         return;
                     }
@@ -253,16 +244,7 @@ where
                     Ok(res) => res,
                     Err(err) => {
                         error!("Could not get file: {}", err);
-
-                        let error_message = err.to_user_facing();
-                        let error = Toast::new(move |_| {
-                            let error_label = gtk::Label::builder()
-                                .label(&error_message)
-                                .wrap(true)
-                                .build();
-                            Some(error_label.upcast())
-                        });
-                        window.append_error(&error);
+                        window.append_error(&Toast::new(&err.to_user_facing()));
 
                         return;
                     }
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 0996f0677..62c8f668c 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -56,7 +56,7 @@ pub use self::{
     timeline::{Timeline, TimelineState},
 };
 use crate::{
-    components::{LabelWithWidgets, Pill, Toast},
+    components::{Pill, Toast},
     prelude::*,
     session::{
         avatar::update_room_avatar_from_file, room::member_list::MemberList, Avatar, Session, User,
@@ -406,18 +406,13 @@ impl Room {
                     }
                     Err(error) => {
                             error!("Couldn’t forget the room: {}", error);
-                            let error = Toast::new(
-                                    clone!(@weak obj => @default-return None, move |_| {
-                                            let error_message = gettext(
-                                                "Failed to forget <widget>."
-                                            );
-                                            let room_pill = Pill::new();
-                                            room_pill.set_room(Some(obj));
-                                            let label = LabelWithWidgets::new(&error_message, 
vec![room_pill]);
-
-                                            Some(label.upcast())
-                                    }),
-                            );
+
+                            let room_pill = Pill::new();
+                            room_pill.set_room(Some(obj.clone()));
+                            let error = Toast::builder()
+                                .title(&gettext("Failed to forget <widget>."))
+                                .widgets(&[&room_pill])
+                                .build();
 
                             if let Some(window) = obj.session().parent_window() {
                                 window.append_error(&error);
@@ -557,20 +552,17 @@ impl Room {
                         Ok(_) => {},
                         Err(error) => {
                                 error!("Couldn’t set the room category: {}", error);
-                                let error = Toast::new(
-                                        clone!(@weak obj => @default-return None, move |_| {
-                                                let error_message = gettext!(
-                                                    "Failed to move <widget> from {} to {}.",
-                                                    previous_category.to_string(),
-                                                    category.to_string()
-                                                );
-                                                let room_pill = Pill::new();
-                                                room_pill.set_room(Some(obj));
-                                                let label = LabelWithWidgets::new(&error_message, 
vec![room_pill]);
-
-                                                Some(label.upcast())
-                                        }),
-                                );
+
+                                let room_pill = Pill::new();
+                                room_pill.set_room(Some(obj.clone()));
+                                let error = Toast::builder()
+                                    .title(&gettext!(
+                                        "Failed to move <widget> from {} to {}.",
+                                        previous_category.to_string(),
+                                        category.to_string()
+                                    ))
+                                    .widgets(&[&room_pill])
+                                    .build();
 
                                 if let Some(window) = obj.session().parent_window() {
                                     window.append_error(&error);
@@ -1064,13 +1056,15 @@ impl Room {
                 Ok(result) => Ok(result),
                 Err(error) => {
                     error!("Accepting invitation failed: {}", error);
-                    let error = Toast::new(clone!(@strong self as room => move |_| {
-                            let error_message = gettext("Failed to accept invitation for <widget>. Try again 
later.");
-                            let room_pill = Pill::new();
-                            room_pill.set_room(Some(room.clone()));
-                            let error_label = LabelWithWidgets::new(&error_message, vec![room_pill]);
-                            Some(error_label.upcast())
-                    }));
+
+                    let room_pill = Pill::new();
+                    room_pill.set_room(Some(self.clone()));
+                    let error = Toast::builder()
+                        .title(&gettext(
+                            "Failed to accept invitation for <widget>. Try again later.",
+                        ))
+                        .widgets(&[&room_pill])
+                        .build();
 
                     if let Some(window) = self.session().parent_window() {
                         window.append_error(&error);
@@ -1094,13 +1088,15 @@ impl Room {
                 Ok(result) => Ok(result),
                 Err(error) => {
                     error!("Rejecting invitation failed: {}", error);
-                    let error = Toast::new(clone!(@strong self as room => move |_| {
-                            let error_message = gettext("Failed to reject invitation for <widget>. Try again 
later.");
-                            let room_pill = Pill::new();
-                            room_pill.set_room(Some(room.clone()));
-                            let error_label = LabelWithWidgets::new(&error_message, vec![room_pill]);
-                            Some(error_label.upcast())
-                    }));
+
+                    let room_pill = Pill::new();
+                    room_pill.set_room(Some(self.clone()));
+                    let error = Toast::builder()
+                        .title(&gettext(
+                            "Failed to reject invitation for <widget>. Try again later.",
+                        ))
+                        .widgets(&[&room_pill])
+                        .build();
 
                     if let Some(window) = self.session().parent_window() {
                         window.append_error(&error);
@@ -1241,25 +1237,23 @@ impl Room {
             if !failed_invites.is_empty() {
                 let no_failed = failed_invites.len();
                 let first_failed = failed_invites.first().unwrap();
-                let error = Toast::new(
-                    clone!(@strong self as room, @strong first_failed => move |_| {
-                            // TODO: should we show all the failed users?
-                            let error_message = if no_failed == 1 {
-                                gettext("Failed to invite <widget> to <widget>. Try again later.")
-                            } else if no_failed == 2 {
-                                gettext("Failed to invite <widget> and some other user to <widget>. Try 
again later.")
-                            } else {
-                               gettext("Failed to invite <widget> and some other users to <widget>. Try 
again later.")
-                            };
-
-                            let user_pill = Pill::new();
-                            user_pill.set_user(Some(first_failed.clone()));
-                            let room_pill = Pill::new();
-                            room_pill.set_room(Some(room.clone()));
-                            let error_label = LabelWithWidgets::new(&error_message, vec![user_pill, 
room_pill]);
-                            Some(error_label.upcast())
-                    }),
-                );
+
+                // TODO: should we show all the failed users?
+                let error_message = if no_failed == 1 {
+                    gettext("Failed to invite <widget> to <widget>. Try again later.")
+                } else if no_failed == 2 {
+                    gettext("Failed to invite <widget> and some other user to <widget>. Try again later.")
+                } else {
+                    gettext("Failed to invite <widget> and some other users to <widget>. Try again later.")
+                };
+                let user_pill = Pill::new();
+                user_pill.set_user(Some(first_failed.clone()));
+                let room_pill = Pill::new();
+                room_pill.set_room(Some(self.clone()));
+                let error = Toast::builder()
+                    .title(&error_message)
+                    .widgets(&[&user_pill, &room_pill])
+                    .build();
 
                 if let Some(window) = self.session().parent_window() {
                     window.append_error(&error);
diff --git a/src/session/room_list.rs b/src/session/room_list.rs
index b4c0c09cb..548bdbaad 100644
--- a/src/session/room_list.rs
+++ b/src/session/room_list.rs
@@ -323,13 +323,7 @@ impl RoomList {
                         obj.pending_rooms_remove(&identifier);
                         error!("Joining room {} failed: {}", identifier, error);
                         let error = Toast::new(
-                            clone!(@strong obj => move |_| {
-                                    let error_message = gettext!(
-                                        "Failed to join room {}. Try again later.", identifier
-                                    );
-                                    let error_label = 
gtk::Label::builder().label(&error_message).wrap(true).build();
-                                    Some(error_label.upcast())
-                            }),
+                            &gettext!("Failed to join room {}. Try again later.", identifier)
                         );
 
                         if let Some(window) = obj.session().parent_window() {
diff --git a/src/session/verification/identity_verification.rs 
b/src/session/verification/identity_verification.rs
index 9a3ea6dad..59e6e4fff 100644
--- a/src/session/verification/identity_verification.rs
+++ b/src/session/verification/identity_verification.rs
@@ -654,16 +654,8 @@ impl IdentityVerification {
             gettext("An unknown error occurred during the verification process.")
         });
 
-        let error = Toast::new(move |_| {
-            let error_label = gtk::Label::builder()
-                .label(&error_message)
-                .wrap(true)
-                .build();
-            Some(error_label.upcast())
-        });
-
         if let Some(window) = self.session().parent_window() {
-            window.append_error(&error);
+            window.append_error(&Toast::new(&error_message));
         }
     }
 
diff --git a/src/window.rs b/src/window.rs
index ad4cf1f27..f92c9a54f 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -180,18 +180,10 @@ impl Window {
             }
             Err(error) => {
                 warn!("Failed to restore previous sessions: {:?}", error);
-                let error_string = error.to_user_facing();
-                self.append_error(&Toast::new(move |_| {
-                    let error_label = gtk::Label::builder()
-                        .label(
-                            &(gettext("Unable to restore previous sessions")
-                                + ": "
-                                + &error_string),
-                        )
-                        .wrap(true)
-                        .build();
-                    Some(error_label.upcast())
-                }));
+                self.append_error(&Toast::new(&gettext!(
+                    "Unable to restore previous sessions: {}",
+                    &error.to_user_facing()
+                )));
             }
         }
     }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]