[fractal/fractal-next] Add loading spinner for session



commit 7101c3f852923266edaf45d5c4901c20958eeb01
Author: Julian Sparber <julian sparber net>
Date:   Mon May 10 19:38:37 2021 +0200

    Add loading spinner for session
    
    We won't show a loading spinner as in-app-notifcation, but we show a big
    loading spinner when a new session is created (via a successful login)
    and when we restore a previously created session.
    
    Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/775
    Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/776

 data/resources/style.css     | 11 +++++++++
 data/resources/ui/session.ui | 55 ++++++++++++++++++++++++++++++++++++--------
 src/login.rs                 | 31 +++++++------------------
 src/session/mod.rs           | 39 +++++++++++++++++++++----------
 src/window.rs                | 23 ++++++++++++------
 5 files changed, 107 insertions(+), 52 deletions(-)
---
diff --git a/data/resources/style.css b/data/resources/style.css
index 1a795019..584ff678 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -8,6 +8,17 @@
   min-width: 250px;
 }
 
+/* Session */
+.session-loading-spinner {
+ min-width: 32px;
+ min-height: 32px;
+}
+
+headerbar.flat {
+  background: none;
+  border: none;
+}
+
 /* Sidebar */
 .sidebar row {
   padding-left: 10px;
diff --git a/data/resources/ui/session.ui b/data/resources/ui/session.ui
index 91826c35..6cd6e86d 100644
--- a/data/resources/ui/session.ui
+++ b/data/resources/ui/session.ui
@@ -1,22 +1,57 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="Session" parent="AdwBin">
-    <child>
-      <object class="AdwLeaflet" id="leaflet">
+    <property name="child">
+      <object class="GtkStack" id="stack">
+        <property name="visible-child">loading</property>
+        <property name="transition-type">crossfade</property>
         <child>
-          <object class="Sidebar" id="sidebar">
-            <property name="compact" bind-source="leaflet" bind-property="folded" bind-flags="sync-create" />
-            <property name="categories" bind-source="Session" bind-property="categories" 
bind-flags="sync-create" />
-            <property name="selected-room" bind-source="Session" bind-property="selected-room" 
bind-flags="sync-create | bidirectional" />
+          <object class="GtkWindowHandle" id="loading">
+            <property name="child">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkHeaderBar">
+                    <property name="show-title-buttons">True</property>
+                    <style>
+                      <class name="flat"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkSpinner">
+                    <property name="spinning">True</property>
+                    <property name="valign">center</property>
+                    <property name="halign">center</property>
+                    <property name="vexpand">True</property>
+                    <style>
+                      <class name="session-loading-spinner"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </property>
           </object>
         </child>
         <child>
-          <object class="Content" id="content">
-            <property name="compact" bind-source="leaflet" bind-property="folded" bind-flags="sync-create" />
-            <property name="room" bind-source="Session" bind-property="selected-room" 
bind-flags="sync-create | bidirectional" />
+          <object class="AdwLeaflet" id="content">
+            <child>
+              <object class="Sidebar">
+                <property name="compact" bind-source="content" bind-property="folded" 
bind-flags="sync-create"/>
+                <property name="categories" bind-source="Session" bind-property="categories" 
bind-flags="sync-create"/>
+                <property name="selected-room" bind-source="Session" bind-property="selected-room" 
bind-flags="sync-create | bidirectional"/>
+              </object>
+            </child>
+            <child>
+              <object class="Content">
+                <property name="compact" bind-source="content" bind-property="folded" 
bind-flags="sync-create"/>
+                <property name="room" bind-source="Session" bind-property="selected-room" 
bind-flags="sync-create | bidirectional"/>
+              </object>
+            </child>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
+
diff --git a/src/login.rs b/src/login.rs
index dcc45626..d8b4e727 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,4 +1,3 @@
-use crate::secret;
 use crate::Session;
 
 use adw;
@@ -122,28 +121,8 @@ impl Login {
         self.freeze();
 
         let session = Session::new();
-        self.setup_session(&session);
-        session.login_with_password(
-            url::Url::parse(homeserver.as_str()).unwrap(),
-            username,
-            password,
-        );
-    }
-
-    pub fn restore_sessions(&self) -> Result<(), secret_service::Error> {
-        let sessions = secret::restore_sessions()?;
-
-        for stored_session in sessions {
-            let session = Session::new();
-            self.setup_session(&session);
-            session.login_with_previous_session(stored_session);
-        }
 
-        Ok(())
-    }
-
-    fn setup_session(&self, session: &Session) {
-        session.connect_ready(clone!(@weak self as obj, @strong session => move |_| {
+        session.connect_prepared(clone!(@weak self as obj, @strong session => move |_| {
             if let Some(error) = session.get_error() {
                 let error_message = &imp::Login::from_instance(&obj).error_message;
                 // TODO: show more specific error
@@ -153,11 +132,17 @@ impl Login {
 
                 obj.unfreeze();
             } else {
-                debug!("A new session is ready");
+                debug!("A new session was prepared");
                 obj.emit_by_name("new-session", &[&session]).unwrap();
                 obj.clean();
             }
         }));
+
+        session.login_with_password(
+            url::Url::parse(homeserver.as_str()).unwrap(),
+            username,
+            password,
+        );
     }
 
     fn clean(&self) {
diff --git a/src/session/mod.rs b/src/session/mod.rs
index c76282c8..34afd376 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -48,11 +48,9 @@ mod imp {
     #[template(resource = "/org/gnome/FractalNext/session.ui")]
     pub struct Session {
         #[template_child]
-        pub leaflet: TemplateChild<adw::Leaflet>,
+        pub stack: TemplateChild<gtk::Stack>,
         #[template_child]
-        pub sidebar: TemplateChild<Sidebar>,
-        #[template_child]
-        pub content: TemplateChild<Content>,
+        pub content: TemplateChild<adw::Leaflet>,
         /// Contains the error if something went wrong
         pub error: RefCell<Option<matrix_sdk::Error>>,
         pub client: OnceCell<Client>,
@@ -60,6 +58,7 @@ mod imp {
         pub categories: Categories,
         pub user: OnceCell<User>,
         pub selected_room: RefCell<Option<Room>>,
+        pub is_ready: OnceCell<bool>,
     }
 
     #[glib::object_subclass]
@@ -73,6 +72,8 @@ mod imp {
         }
 
         fn instance_init(obj: &InitializingObject<Self>) {
+            Sidebar::static_type();
+            Content::static_type();
             obj.init_template();
         }
     }
@@ -127,7 +128,7 @@ mod imp {
 
         fn signals() -> &'static [Signal] {
             static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
-                vec![Signal::builder("ready", &[], <()>::static_type().into()).build()]
+                vec![Signal::builder("prepared", &[], <()>::static_type().into()).build()]
             });
             SIGNALS.as_ref()
         }
@@ -162,11 +163,10 @@ impl Session {
             return;
         }
 
-        let leaflet = priv_.leaflet.get();
         if selected_room.is_some() {
-            leaflet.navigate(adw::NavigationDirection::Forward);
+            priv_.content.navigate(adw::NavigationDirection::Forward);
         } else {
-            leaflet.navigate(adw::NavigationDirection::Back);
+            priv_.content.navigate(adw::NavigationDirection::Back);
         }
 
         priv_.selected_room.replace(selected_room);
@@ -271,7 +271,7 @@ impl Session {
                 priv_.error.replace(Some(error));
             }
         }
-        self.emit_by_name("ready", &[]).unwrap();
+        self.emit_by_name("prepared", &[]).unwrap();
     }
 
     fn sync(&self) {
@@ -308,6 +308,17 @@ impl Session {
         });
     }
 
+    fn mark_ready(&self) {
+        let priv_ = &imp::Session::from_instance(self);
+        priv_.stack.set_visible_child(&*priv_.content);
+        priv_.is_ready.set(true).unwrap();
+    }
+
+    fn is_ready(&self) -> bool {
+        let priv_ = &imp::Session::from_instance(self);
+        priv_.is_ready.get().copied().unwrap_or_default()
+    }
+
     fn set_user(&self, user: User) {
         let priv_ = &imp::Session::from_instance(self);
         priv_.user.set(user).unwrap();
@@ -325,7 +336,11 @@ impl Session {
         receiver.attach(
             None,
             clone!(@weak self as obj => @default-return glib::Continue(false), move |response| {
+                if !obj.is_ready() {
+                        obj.mark_ready();
+                }
                 obj.handle_sync_response(response);
+
                 glib::Continue(true)
             }),
         );
@@ -342,14 +357,14 @@ impl Session {
 
     /// Returns and consumes the `error` that was generated when the session failed to login,
     /// on a successful login this will be `None`.
-    /// Unfortunatly it's not possible to connect the Error direclty to the `ready` signals.
+    /// Unfortunatly it's not possible to connect the Error direclty to the `prepared` signals.
     pub fn get_error(&self) -> Option<matrix_sdk::Error> {
         let priv_ = &imp::Session::from_instance(self);
         priv_.error.take()
     }
 
-    pub fn connect_ready<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
-        self.connect_local("ready", true, move |values| {
+    pub fn connect_prepared<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+        self.connect_local("prepared", true, move |values| {
             let obj = values[0].get::<Self>().unwrap();
 
             f(&obj);
diff --git a/src/window.rs b/src/window.rs
index 3507d09f..1cd0159f 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -1,4 +1,5 @@
 use crate::config::{APP_ID, PROFILE};
+use crate::secret;
 use crate::Application;
 use crate::Login;
 use crate::Session;
@@ -59,14 +60,9 @@ mod imp {
                 obj.add_css_class("devel");
             }
 
-            // load latest window state
             obj.load_window_size();
+            obj.restore_sessions();
 
-            // TODO: tell user that a stored session couldn't be restored
-            let result = self.login.restore_sessions();
-            if let Err(error) = result {
-                warn!("Failed to restore a session: {:?}", error);
-            }
             self.login.connect_new_session(
                 clone!(@weak obj => move |_login, session| obj.add_session(session)),
             );
@@ -99,12 +95,25 @@ impl Window {
             .expect("Failed to create Window")
     }
 
-    pub fn add_session(&self, session: &Session) {
+    fn add_session(&self, session: &Session) {
         let priv_ = &imp::Window::from_instance(self);
         priv_.main_stack.add_child(session);
         priv_.main_stack.set_visible_child(session);
     }
 
+    fn restore_sessions(&self) {
+        match secret::restore_sessions() {
+            Ok(sessions) => {
+                for stored_session in sessions {
+                    let session = Session::new();
+                    session.login_with_previous_session(stored_session);
+                    self.add_session(&session);
+                }
+            }
+            Err(error) => warn!("Failed to restore previous sessions: {:?}", error),
+        }
+    }
+
     pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
         let settings = &imp::Window::from_instance(self).settings;
 


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