[fractal/fractal-next] room-history: Show error page on loading errors



commit 762973ce19d9a6f4dfa497bab7dd29a797dd198b
Author: Julian Sparber <julian sparber net>
Date:   Thu Feb 10 12:03:47 2022 +0100

    room-history: Show error page on loading errors
    
    This only shows the error page when the timeline is empty.
    Adds also a enum for the timeline state.

 data/resources/ui/content-room-history.ui | 21 +++++++
 src/session/content/room_history/mod.rs   | 46 +++++++++------
 src/session/room/mod.rs                   |  2 +-
 src/session/room/timeline.rs              | 97 +++++++++++++++++--------------
 4 files changed, 103 insertions(+), 63 deletions(-)
---
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index d257dd09c..abefde088 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -107,6 +107,26 @@
                 </style>
               </object>
             </child>
+            <child>
+              <object class="AdwStatusPage" id="error">
+                <property name="visible">True</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="icon-name">dialog-error-symbolic</property>
+                <property name="title" translatable="yes">Unable to load room</property>
+                <property name="description" translatable="yes">Check your network connection.</property>
+                <property name="child">
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Try Again</property>
+                    <property name="action-name">room-history.try-again</property>
+                    <property name="halign">center</property>
+                    <style>
+                      <class name="pill"/>
+                    </style>
+                  </object>
+                </property>
+              </object>
+            </child>
             <child>
               <object class="GtkOverlay" id="content">
                 <child type="overlay">
@@ -239,3 +259,4 @@
     </child>
   </template>
 </interface>
+
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index 7fdb60749..aaefc2fc1 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -26,7 +26,7 @@ use crate::{
     components::{CustomEntry, Pill, RoomTitle},
     session::{
         content::{MarkdownPopover, RoomDetails},
-        room::{Item, Room, RoomType, Timeline},
+        room::{Item, Room, RoomType, Timeline, TimelineState},
         user::UserExt,
     },
 };
@@ -46,7 +46,7 @@ mod imp {
         pub room: RefCell<Option<Room>>,
         pub category_handler: RefCell<Option<SignalHandlerId>>,
         pub empty_timeline_handler: RefCell<Option<SignalHandlerId>>,
-        pub loading_timeline_handler: RefCell<Option<SignalHandlerId>>,
+        pub state_timeline_handler: RefCell<Option<SignalHandlerId>>,
         pub md_enabled: Cell<bool>,
         pub is_auto_scrolling: Cell<bool>,
         pub sticky: Cell<bool>,
@@ -73,6 +73,8 @@ mod imp {
         #[template_child]
         pub loading: TemplateChild<gtk::Spinner>,
         #[template_child]
+        pub error: TemplateChild<adw::StatusPage>,
+        #[template_child]
         pub stack: TemplateChild<gtk::Stack>,
     }
 
@@ -101,6 +103,10 @@ mod imp {
                 widget.leave();
             });
 
+            klass.install_action("room-history.try-again", None, move |widget, _, _| {
+                widget.try_again();
+            });
+
             klass.install_action("room-history.details", None, move |widget, _, _| {
                 widget.open_room_details("general");
             });
@@ -324,9 +330,9 @@ impl RoomHistory {
             }
         }
 
-        if let Some(loading_timeline_handler) = priv_.loading_timeline_handler.take() {
-            if let Some(room) = self.room() {
-                room.timeline().disconnect(loading_timeline_handler);
+        if let Some(room) = self.room() {
+            if let Some(state_timeline_handler) = priv_.state_timeline_handler.take() {
+                room.timeline().disconnect(state_timeline_handler);
             }
         }
 
@@ -343,24 +349,20 @@ impl RoomHistory {
             let handler_id = room.timeline().connect_notify_local(
                 Some("empty"),
                 clone!(@weak self as obj => move |_, _| {
-                        obj.set_empty_timeline();
+                        obj.update_view();
                 }),
             );
 
             priv_.empty_timeline_handler.replace(Some(handler_id));
 
             let handler_id = room.timeline().connect_notify_local(
-                Some("loading"),
-                clone!(@weak self as obj => move |timeline, _| {
-                    // We need to make sure that we loaded enough events to fill the `ScrolledWindow`
-                    if !timeline.loading() {
-                        let adj = obj.imp().listview.vadjustment().unwrap();
-                        obj.load_more_messages(&adj);
-                    }
+                Some("state"),
+                clone!(@weak self as obj => move |_, _| {
+                        obj.update_view();
                 }),
             );
 
-            priv_.loading_timeline_handler.replace(Some(handler_id));
+            priv_.state_timeline_handler.replace(Some(handler_id));
 
             room.load_members();
         }
@@ -374,8 +376,8 @@ impl RoomHistory {
         priv_.room.replace(room);
         let adj = priv_.listview.vadjustment().unwrap();
         self.load_more_messages(&adj);
+        self.update_view();
         self.update_room_state();
-        self.set_empty_timeline();
         self.notify("room");
         self.notify("empty");
     }
@@ -519,12 +521,16 @@ impl RoomHistory {
         }
     }
 
-    fn set_empty_timeline(&self) {
+    fn update_view(&self) {
         let priv_ = self.imp();
 
         if let Some(room) = &*priv_.room.borrow() {
             if room.timeline().is_empty() {
-                priv_.stack.set_visible_child(&*priv_.loading);
+                if room.timeline().state() == TimelineState::Error {
+                    priv_.stack.set_visible_child(&*priv_.error);
+                } else {
+                    priv_.stack.set_visible_child(&*priv_.loading);
+                }
             } else {
                 priv_.stack.set_visible_child(&*priv_.content);
             }
@@ -577,6 +583,12 @@ impl RoomHistory {
             .scrolled_window
             .emit_by_name::<bool>("scroll-child", &[&gtk::ScrollType::End, &false]);
     }
+
+    fn try_again(&self) {
+        if let Some(room) = self.room() {
+            room.timeline().load_previous_events();
+        }
+    }
 }
 
 impl Default for RoomHistory {
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 6e447364f..ff201db9f 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -54,7 +54,7 @@ pub use self::{
     reaction_group::ReactionGroup,
     reaction_list::ReactionList,
     room_type::RoomType,
-    timeline::Timeline,
+    timeline::{Timeline, TimelineState},
 };
 use crate::{
     components::{LabelWithWidgets, Pill},
diff --git a/src/session/room/timeline.rs b/src/session/room/timeline.rs
index 197051b14..899e59e99 100644
--- a/src/session/room/timeline.rs
+++ b/src/session/room/timeline.rs
@@ -23,6 +23,23 @@ use crate::{
     spawn, spawn_tokio,
 };
 
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
+#[repr(u32)]
+#[enum_type(name = "TimelineState")]
+pub enum TimelineState {
+    Initial,
+    Loading,
+    Ready,
+    Error,
+    Complete,
+}
+
+impl Default for TimelineState {
+    fn default() -> Self {
+        TimelineState::Initial
+    }
+}
+
 mod imp {
     use std::{
         cell::{Cell, RefCell},
@@ -48,9 +65,8 @@ mod imp {
         pub pending_events: RefCell<HashMap<String, Box<EventId>>>,
         /// A Hashset of `EventId`s that where just redacted.
         pub redacted_events: RefCell<HashSet<Box<EventId>>>,
-        pub loading: Cell<bool>,
-        pub complete: Cell<bool>,
         pub oldest_event: RefCell<Option<Box<EventId>>>,
+        pub state: Cell<TimelineState>,
         /// The most recent verification request event
         pub verification: RefCell<Option<IdentityVerification>>,
     }
@@ -74,13 +90,6 @@ mod imp {
                         Room::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                     ),
-                    glib::ParamSpecBoolean::new(
-                        "loading",
-                        "Loading",
-                        "Whether a response is loaded or not",
-                        false,
-                        glib::ParamFlags::READABLE,
-                    ),
                     glib::ParamSpecBoolean::new(
                         "empty",
                         "Empty",
@@ -88,11 +97,12 @@ mod imp {
                         false,
                         glib::ParamFlags::READABLE,
                     ),
-                    glib::ParamSpecBoolean::new(
-                        "complete",
-                        "Complete",
-                        "Whether the full timeline is loaded",
-                        false,
+                    glib::ParamSpecEnum::new(
+                        "state",
+                        "State",
+                        "The state the timeline is in",
+                        TimelineState::static_type(),
+                        TimelineState::default() as i32,
                         glib::ParamFlags::READABLE,
                     ),
                     glib::ParamSpecObject::new(
@@ -127,9 +137,8 @@ mod imp {
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "room" => obj.room().to_value(),
-                "loading" => obj.loading().to_value(),
                 "empty" => obj.is_empty().to_value(),
-                "complete" => obj.is_complete().to_value(),
+                "state" => obj.state().to_value(),
                 "verification" => obj.verification().to_value(),
                 _ => unimplemented!(),
             }
@@ -680,41 +689,27 @@ impl Timeline {
         self.imp().room.get().unwrap().upgrade().unwrap()
     }
 
-    fn set_loading(&self, loading: bool) {
+    fn set_state(&self, state: TimelineState) {
         let priv_ = self.imp();
 
-        if loading == priv_.loading.get() {
+        if state == self.state() {
             return;
         }
 
-        priv_.loading.set(loading);
+        priv_.state.set(state);
 
-        self.notify("loading");
+        self.notify("state");
     }
 
-    fn set_complete(&self, complete: bool) {
-        let priv_ = self.imp();
-
-        if complete == priv_.complete.get() {
-            return;
-        }
-
-        priv_.complete.set(complete);
-        self.notify("complete");
-    }
-
-    // Whether the timeline is fully loaded
-    pub fn is_complete(&self) -> bool {
-        self.imp().complete.get()
-    }
-
-    pub fn loading(&self) -> bool {
-        self.imp().loading.get()
+    // The state of the timeline
+    pub fn state(&self) -> TimelineState {
+        self.imp().state.get()
     }
 
     pub fn is_empty(&self) -> bool {
         let priv_ = self.imp();
-        priv_.list.borrow().is_empty() || (priv_.list.borrow().len() == 1 && self.loading())
+        priv_.list.borrow().is_empty()
+            || (priv_.list.borrow().len() == 1 && self.state() == TimelineState::Loading)
     }
 
     fn oldest_event(&self) -> Option<Box<EventId>> {
@@ -734,11 +729,14 @@ impl Timeline {
     }
 
     pub fn load_previous_events(&self) {
-        if self.loading() || self.is_complete() {
+        if matches!(
+            self.state(),
+            TimelineState::Loading | TimelineState::Complete
+        ) {
             return;
         }
 
-        self.set_loading(true);
+        self.set_state(TimelineState::Loading);
         self.add_loading_spinner();
 
         let matrix_room = self.room().matrix_room();
@@ -770,15 +768,24 @@ impl Timeline {
                                            .into_iter()
                                            .map(|event| Event::new(event, &obj.room())).collect()
                             };
-                            obj.set_complete(events.iter().any(|event| matches!(event.matrix_event(), 
Some(AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_))))));
-                            obj.prepend(events)
+
+                            if events.iter().any(|event| matches!(event.matrix_event(), 
Some(AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_))))) {
+                                obj.set_state(TimelineState::Complete);
+                            } else {
+                                obj.set_state(TimelineState::Ready);
+                            }
+
+                            obj.prepend(events);
                        },
                        Ok(None) => {
                            error!("The start event wasn't found in the timeline for room {}.", 
obj.room().room_id());
+                           obj.set_state(TimelineState::Error);
                        },
-                       Err(error) => error!("Couldn't load previous events for room {}: {}", error, 
obj.room().room_id()),
+                       Err(error) => {
+                           error!("Couldn't load previous events for room {}: {}", error, 
obj.room().room_id());
+                           obj.set_state(TimelineState::Error);
+                       }
                }
-               obj.set_loading(false);
             })
         );
     }


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