[fractal/fractal-next] login: Show errors as in-app-notification



commit f46d15e18716563ba83101f16abcc28c7b323301
Author: Julian Sparber <julian sparber net>
Date:   Wed Oct 13 12:28:36 2021 +0200

    login: Show errors as in-app-notification
    
    This also improves how the error message is created and passed from the
    `Session` to the `Login` widget.

 data/resources/ui/login.ui |  5 ---
 src/login.rs               | 95 +++++++++-------------------------------------
 src/matrix_error.rs        | 40 ++++++++++++++++---
 src/session/mod.rs         | 49 +++++++++++++++---------
 4 files changed, 82 insertions(+), 107 deletions(-)
---
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
index 92ed8ae9..7bacd558 100644
--- a/data/resources/ui/login.ui
+++ b/data/resources/ui/login.ui
@@ -64,11 +64,6 @@
                           <object class="GtkBox">
                             <property name="orientation">vertical</property>
                             <property name="spacing">18</property>
-                            <child>
-                              <object class="GtkLabel" id="error_message">
-                                <property name="visible">False</property>
-                              </object>
-                            </child>
                             <child>
                               <object class="GtkBox">
                                 <property name="name">devnotice</property>
diff --git a/src/login.rs b/src/login.rs
index f5a63c3b..30f1edb1 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,13 +1,10 @@
 use crate::Session;
 
 use adw::subclass::prelude::BinImpl;
-use gettextrs::gettext;
 use gtk::subclass::prelude::*;
 use gtk::{self, prelude::*};
 use gtk::{glib, glib::clone, CompositeTemplate};
 use log::debug;
-use std::fmt;
-use std::time::Duration;
 use url::{ParseError, Url};
 
 mod imp {
@@ -37,8 +34,6 @@ mod imp {
         #[template_child]
         pub password_entry: TemplateChild<gtk::PasswordEntry>,
         #[template_child]
-        pub error_message: TemplateChild<gtk::Label>,
-        #[template_child]
         pub back_to_session_button: TemplateChild<gtk::Button>,
     }
 
@@ -199,22 +194,27 @@ impl Login {
     }
 
     fn set_handler_for_prepared_session(&self, session: &Session) {
-        session.connect_prepared(clone!(@weak self as login => move |session| {
-            if let Some(error) = session.get_error() {
-                let error_message = &imp::Login::from_instance(&login).error_message;
-                error_message.set_text(&error.to_string());
-                error_message.show();
-                debug!("Failed to create a new session: {:?}", error);
-
-                login.unfreeze();
-            } else {
-                debug!("A new session was prepared");
-                login.emit_by_name("new-session", &[&session]).unwrap();
-                login.clean();
+        session.connect_prepared(clone!(@weak self as login => move |session, error| {
+            match error {
+                Some(e) => {
+                    login.parent_window().append_error(&e);
+                    login.unfreeze();
+                },
+                None => {
+                    debug!("A new session was prepared");
+                    login.emit_by_name("new-session", &[&session]).unwrap();
+                    login.clean();
+                }
             }
             login.drop_session_reference();
         }));
     }
+
+    fn parent_window(&self) -> crate::Window {
+        self.root()
+            .and_then(|root| root.downcast().ok())
+            .expect("Login needs to have a parent window")
+    }
 }
 
 impl Default for Login {
@@ -230,64 +230,3 @@ fn build_homeserver_url(server: &str) -> Result<Url, ParseError> {
         Url::parse(&format!("https://{}";, server))
     }
 }
-
-#[derive(Debug)]
-pub enum LoginError {
-    ServerNotFound,
-    Forbidden,
-    UserDeactivated,
-    LimitExceeded(Option<Duration>),
-    Unknown(String),
-}
-
-impl fmt::Display for LoginError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let error_msg = match &self {
-            LoginError::ServerNotFound => gettext("⚠️ Homeserver not found."),
-            LoginError::Forbidden => gettext("⚠️ Invalid credentials."),
-            LoginError::UserDeactivated => gettext("⚠️ The user is deactivated."),
-            LoginError::LimitExceeded(retry_ms) => {
-                if let Some(ms) = retry_ms {
-                    gettext!("⚠️ Exceeded rate limit, retry in {} seconds.", ms.as_secs())
-                } else {
-                    gettext("⚠️ Exceeded rate limit, try again later.")
-                }
-            }
-            LoginError::Unknown(info) => {
-                debug!("Unknown error occurred during login: {}", info);
-                gettext("⚠️ Login Failed! Unknown error.")
-            }
-        };
-        f.write_str(&error_msg)
-    }
-}
-
-impl From<matrix_sdk::Error> for LoginError {
-    /// Transform a matrix_sdk error into a LoginError based on the login with password
-    /// Logging in can result in the following errors:
-    /// M_FORBIDDEN: The provided authentication data was incorrect.
-    /// M_USER_DEACTIVATED: The user has been deactivated.
-    /// M_LIMIT_EXCEEDED: This request was rate-limited.
-    /// M_UNKNOWN: An unknown error occurred
-    /// or the home server was not found/unavailable (a Reqwest error)
-    fn from(error: matrix_sdk::Error) -> Self {
-        use matrix_sdk::ruma::api::client::error::ErrorKind::{
-            Forbidden, LimitExceeded, UserDeactivated,
-        };
-        use matrix_sdk::ruma::api::error::{FromHttpResponseError, ServerError};
-        use matrix_sdk::Error::Http;
-        use matrix_sdk::HttpError::{ClientApi, Reqwest};
-        match error {
-            Http(Reqwest(_)) => LoginError::ServerNotFound,
-            Http(ClientApi(FromHttpResponseError::Http(ServerError::Known(server_err)))) => {
-                match server_err.kind {
-                    Forbidden => LoginError::Forbidden,
-                    UserDeactivated => LoginError::UserDeactivated,
-                    LimitExceeded { retry_after_ms } => LoginError::LimitExceeded(retry_after_ms),
-                    e => LoginError::Unknown(e.to_string()),
-                }
-            }
-            e => LoginError::Unknown(e.to_string()),
-        }
-    }
-}
diff --git a/src/matrix_error.rs b/src/matrix_error.rs
index 53f1faed..31747752 100644
--- a/src/matrix_error.rs
+++ b/src/matrix_error.rs
@@ -1,6 +1,9 @@
 use matrix_sdk::{
-    ruma::api::error::{FromHttpResponseError, ServerError},
-    HttpError,
+    ruma::api::{
+        client::error::ErrorKind::{Forbidden, LimitExceeded, UserDeactivated},
+        error::{FromHttpResponseError, ServerError},
+    },
+    Error, HttpError,
 };
 
 use gettextrs::gettext;
@@ -14,13 +17,38 @@ impl UserFacingMatrixError for HttpError {
         match self {
             HttpError::Reqwest(_) => {
                 // TODO: Add more information based on the error
-                gettext("Couldn't connect to the server.")
+                gettext("Unable to connect to the homeserver.")
             }
             HttpError::ClientApi(FromHttpResponseError::Http(ServerError::Known(error))) => {
-                // TODO: The server may not give us pretty enough error message. We should add our own error 
message.
-                error.message
+                match error.kind {
+                    Forbidden => gettext("The provided username or password is invalid."),
+                    UserDeactivated => gettext("The user is deactivated."),
+                    LimitExceeded { retry_after_ms } => {
+                        if let Some(ms) = retry_after_ms {
+                            gettext(format!(
+                                "You exceeded the homeservers rate limit, retry in {} seconds.",
+                                ms.as_secs()
+                            ))
+                        } else {
+                            gettext("You exceeded the homeservers rate limit, try again later.")
+                        }
+                    }
+                    _ => {
+                        // TODO: The server may not give us pretty enough error message. We should add our 
own error message.
+                        error.message
+                    }
+                }
             }
-            _ => gettext("An Unknown error occurred."),
+            _ => gettext("An unknown connection error occurred."),
+        }
+    }
+}
+
+impl UserFacingMatrixError for Error {
+    fn to_user_facing(self) -> String {
+        match self {
+            Error::Http(http_error) => http_error.to_user_facing(),
+            _ => gettext("An unknown error occurred."),
         }
     }
 }
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 8c0e2029..833a9166 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -24,7 +24,7 @@ use crate::Error;
 use crate::Window;
 use crate::RUNTIME;
 
-use crate::login::LoginError;
+use crate::matrix_error::UserFacingMatrixError;
 use crate::session::content::ContentType;
 use adw::subclass::prelude::BinImpl;
 use futures::StreamExt;
@@ -74,8 +74,6 @@ mod imp {
         pub content: TemplateChild<adw::Leaflet>,
         #[template_child]
         pub sidebar: TemplateChild<Sidebar>,
-        /// Contains the error if something went wrong
-        pub error: RefCell<Option<matrix_sdk::Error>>,
         pub client: RefCell<Option<Client>>,
         pub room_list: OnceCell<RoomList>,
         pub user: OnceCell<User>,
@@ -211,7 +209,12 @@ mod imp {
         fn signals() -> &'static [Signal] {
             static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                 vec![
-                    Signal::builder("prepared", &[], <()>::static_type().into()).build(),
+                    Signal::builder(
+                        "prepared",
+                        &[Option::<Error>::static_type().into()],
+                        <()>::static_type().into(),
+                    )
+                    .build(),
                     Signal::builder("logged-out", &[], <()>::static_type().into()).build(),
                 ]
             });
@@ -372,7 +375,7 @@ impl Session {
         store_session: bool,
     ) {
         let priv_ = imp::Session::from_instance(self);
-        match result {
+        let error = match result {
             Ok((client, session)) => {
                 priv_.client.replace(Some(client.clone()));
                 let user = User::new(self, &session.user_id);
@@ -406,12 +409,25 @@ impl Session {
 
                 self.room_list().load();
                 self.sync();
+
+                None
             }
             Err(error) => {
-                priv_.error.replace(Some(error));
+                error!("Failed to prepare the session: {}", error);
+
+                let error_string = error.to_user_facing();
+
+                Some(Error::new(move |_| {
+                    let error_label = gtk::LabelBuilder::new()
+                        .label(&error_string)
+                        .wrap(true)
+                        .build();
+                    Some(error_label.upcast())
+                }))
             }
-        }
-        self.emit_by_name("prepared", &[]).unwrap();
+        };
+
+        self.emit_by_name("prepared", &[&error]).unwrap();
     }
 
     fn sync(&self) {
@@ -501,19 +517,16 @@ impl Session {
         sender
     }
 
-    /// Returns and consumes the `error` that was generated when the session failed to login,
-    /// on a successful login this will be `None`.
-    /// Unfortunately it's not possible to connect the Error directly to the `prepared` signals.
-    pub fn get_error(&self) -> Option<LoginError> {
-        let priv_ = &imp::Session::from_instance(self);
-        priv_.error.take().map(LoginError::from)
-    }
-
-    pub fn connect_prepared<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+    /// Connects the prepared signals to the function f given in input
+    pub fn connect_prepared<F: Fn(&Self, Option<Error>) + 'static>(
+        &self,
+        f: F,
+    ) -> glib::SignalHandlerId {
         self.connect_local("prepared", true, move |values| {
             let obj = values[0].get::<Self>().unwrap();
+            let err = values[1].get::<Option<Error>>().unwrap();
 
-            f(&obj);
+            f(&obj, err);
 
             None
         })


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