[fractal/fractal-next] content: Show empty page and loading spinner



commit f9d656f154986f20909eb4fe63931926d8a80c51
Author: Julian Sparber <julian sparber net>
Date:   Sun Jul 11 15:08:14 2021 +0200

    content: Show empty page and loading spinner

 .../resources/icons/scalable/status/empty-page.svg |  6 ++
 data/resources/resources.gresource.xml             |  1 +
 data/resources/ui/content-room-history.ui          | 58 ++++++++++++-------
 data/resources/ui/content.ui                       | 32 +++++++++++
 src/session/content/content.rs                     |  4 +-
 src/session/content/room_history.rs                | 65 +++++++++++++++++++---
 src/session/room/timeline.rs                       | 33 ++++++++---
 7 files changed, 160 insertions(+), 39 deletions(-)
---
diff --git a/data/resources/icons/scalable/status/empty-page.svg 
b/data/resources/icons/scalable/status/empty-page.svg
new file mode 100644
index 00000000..17e38b34
--- /dev/null
+++ b/data/resources/icons/scalable/status/empty-page.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="140" height="140" viewBox="0 0 37.042 37.042">
+    <g color="#000" fill="#bebebe">
+        <path d="M31.75 5.292v14.717c0 1.364-.138 2.933-1.158 4.3-1.019 1.366-2.896 2.218-5.457 
2.15H14.387l-2.729 2.645h12.154l7.938 7.938v-7.938h2.646a2.64 2.64 0 0 0 2.646-2.646V7.938a2.64 2.64 0 0 
0-2.646-2.646z" style="marker:none" overflow="visible" fill-opacity=".365"/>
+        <path d="M2.646 0A2.64 2.64 0 0 0 0 2.646v18.52a2.64 2.64 0 0 0 2.646 
2.646h2.646v7.938l7.937-7.938h13.23a2.64 2.64 0 0 0 2.645-2.645V2.646A2.64 2.64 0 0 0 26.458 0z" 
style="marker:none" overflow="visible"/>
+    </g>
+</svg>
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 2bde98bb..2a6d9825 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -32,6 +32,7 @@
     <file compressed="true">style.css</file>
     <file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/status/empty-page.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/explore-symbolic.svg</file>
   </gresource>
 </gresources>
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index 7c5939cd..a9cf96e7 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -71,33 +71,49 @@
           </object>
         </child>
         <child>
-          <object class="GtkScrolledWindow" id="scrolled_window">
-            <property name="vexpand">True</property>
-            <property name="hscrollbar-policy">never</property>
-            <style>
-              <class name="room-history"/>
-            </style>
-            <property name="child">
-              <object class="AdwClampScrollable">
+          <object class="GtkStack" id="stack">
+            <property name="transition-type">crossfade</property>
+            <child>
+              <object class="GtkSpinner" id="loading">
+                <property name="spinning">True</property>
+                <property name="valign">center</property>
+                <property name="halign">center</property>
                 <property name="vexpand">True</property>
-                <property name="hexpand">True</property>
+                <style>
+                  <class name="session-loading-spinner"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolled_window">
+                <property name="vexpand">True</property>
+                <property name="hscrollbar-policy">never</property>
+                <style>
+                  <class name="room-history"/>
+                </style>
                 <property name="child">
-                  <object class="GtkListView" id="listview">
-                    <style>
-                      <class name="navigation-sidebar"/>
-                    </style>
-                    <property name="factory">
-                      <object class="GtkBuilderListItemFactory">
-                        <property name="resource">/org/gnome/FractalNext/content-item.ui</property>
+                  <object class="AdwClampScrollable">
+                    <property name="vexpand">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="child">
+                      <object class="GtkListView" id="listview">
+                        <style>
+                          <class name="navigation-sidebar"/>
+                        </style>
+                        <property name="factory">
+                          <object class="GtkBuilderListItemFactory">
+                            <property name="resource">/org/gnome/FractalNext/content-item.ui</property>
+                          </object>
+                        </property>
+                        <accessibility>
+                          <property name="label" translatable="yes">Room History</property>
+                        </accessibility>
                       </object>
                     </property>
-                    <accessibility>
-                      <property name="label" translatable="yes">Room History</property>
-                    </accessibility>
                   </object>
                 </property>
               </object>
-            </property>
+            </child>
           </object>
         </child>
         <child>
@@ -131,7 +147,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkScrolledWindow" id="scrolled_window">
+                  <object class="GtkScrolledWindow">
                     <property name="vexpand">True</property>
                     <property name="hexpand">True</property>
                     <property name="vscrollbar-policy">external</property>
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index 0560327a..560c8669 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -14,6 +14,38 @@
         <child>
           <object class="GtkStack" id="stack">
             <property name="transition-type">crossfade</property>
+            <child>
+              <object class="GtkBox" id="empty_page">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="AdwHeaderBar" id="headerbar">
+                    <property name="show-start-title-buttons" bind-source="Content" bind-property="compact" 
bind-flags="sync-create"/>
+                    <child type="start">
+                      <object class="GtkRevealer">
+                        <property name="transition-type">crossfade</property>
+                        <property name="reveal-child" bind-source="Content" bind-property="compact" 
bind-flags="sync-create"/>
+                        <property name="child">
+                          <object class="GtkButton" id="back">
+                            <property name="icon-name">go-previous-symbolic</property>
+                            <property name="action-name">content.go-back</property>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwStatusPage">
+                    <property name="visible">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="icon-name">empty-page</property>
+                    <property name="title" translatable="yes">No Room Selected</property>
+                    <property name="description" translatable="yes">Join a room to start chatting.</property>
+                  </object>
+                </child>
+              </object>
+            </child>
             <child>
               <object class="ContentRoomHistory" id="room_history">
                 <property name="compact" bind-source="Content" bind-property="compact" 
bind-flags="sync-create"/>
diff --git a/src/session/content/content.rs b/src/session/content/content.rs
index 74b0eeeb..f2380d8a 100644
--- a/src/session/content/content.rs
+++ b/src/session/content/content.rs
@@ -29,6 +29,8 @@ mod imp {
         pub invite: TemplateChild<Invite>,
         #[template_child]
         pub explore: TemplateChild<Explore>,
+        #[template_child]
+        pub empty_page: TemplateChild<gtk::Box>,
     }
 
     #[glib::object_subclass]
@@ -215,7 +217,7 @@ impl Content {
 
         match self.content_type() {
             ContentType::None => {
-                //TODO: display an empty state
+                priv_.stack.set_visible_child(&*priv_.empty_page);
             }
             ContentType::Room => {
                 if let Some(room) = &*priv_.room.borrow() {
diff --git a/src/session/content/room_history.rs b/src/session/content/room_history.rs
index cb06ea94..498c4118 100644
--- a/src/session/content/room_history.rs
+++ b/src/session/content/room_history.rs
@@ -19,6 +19,7 @@ mod imp {
         pub compact: Cell<bool>,
         pub room: RefCell<Option<Room>>,
         pub category_handler: RefCell<Option<SignalHandlerId>>,
+        pub empty_timeline_handler: RefCell<Option<SignalHandlerId>>,
         pub md_enabled: Cell<bool>,
         #[template_child]
         pub headerbar: TemplateChild<adw::HeaderBar>,
@@ -34,6 +35,10 @@ mod imp {
         pub message_entry: TemplateChild<sourceview::View>,
         #[template_child]
         pub markdown_button: TemplateChild<gtk::MenuButton>,
+        #[template_child]
+        pub loading: TemplateChild<gtk::Spinner>,
+        #[template_child]
+        pub stack: TemplateChild<gtk::Stack>,
     }
 
     #[glib::object_subclass]
@@ -83,6 +88,13 @@ mod imp {
                         Room::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
+                    glib::ParamSpec::new_boolean(
+                        "empty",
+                        "Empty",
+                        "Wheter there is currently a room shown",
+                        false,
+                        glib::ParamFlags::READABLE,
+                    ),
                     glib::ParamSpec::new_boolean(
                         "markdown-enabled",
                         "Markdown enabled",
@@ -129,23 +141,17 @@ mod imp {
             match pspec.name() {
                 "compact" => self.compact.get().to_value(),
                 "room" => obj.room().to_value(),
+                "empty" => obj.room().is_none().to_value(),
                 "markdown-enabled" => self.md_enabled.get().to_value(),
                 _ => unimplemented!(),
             }
         }
 
         fn constructed(&self, obj: &Self::Type) {
-            let adj = self.scrolled_window.vadjustment().unwrap();
-            // TODO: make sure that we have enough messages to fill at least to scroll pages, if the room 
history is long enough
+            let adj = self.listview.vadjustment().unwrap();
 
             adj.connect_value_changed(clone!(@weak obj => move |adj| {
-                // Load more message when the user gets close to the end of the known room history
-                // Use the page size twice to detect if the user gets close the end
-                if adj.value() < adj.page_size() * 2.0 {
-                    if let Some(room) = obj.room() {
-                        room.load_previous_events();
-                        }
-                }
+                obj.load_more_messages(adj);
             }));
 
             let key_events = gtk::EventControllerKey::new();
@@ -218,6 +224,12 @@ impl RoomHistory {
             }
         }
 
+        if let Some(empty_timeline_handler) = priv_.empty_timeline_handler.take() {
+            if let Some(room) = self.room() {
+                room.timeline().disconnect(empty_timeline_handler);
+            }
+        }
+
         if let Some(ref room) = room {
             let handler_id = room.connect_notify_local(
                 Some("category"),
@@ -227,6 +239,15 @@ impl RoomHistory {
             );
 
             priv_.category_handler.replace(Some(handler_id));
+
+            let handler_id = room.timeline().connect_notify_local(
+                Some("empty"),
+                clone!(@weak self as obj => move |_, _| {
+                        obj.set_empty_timeline();
+                }),
+            );
+
+            priv_.empty_timeline_handler.replace(Some(handler_id));
         }
 
         // TODO: use gtk::MultiSelection to allow selection
@@ -236,8 +257,12 @@ impl RoomHistory {
 
         priv_.listview.set_model(model.as_ref());
         priv_.room.replace(room);
+        let adj = priv_.listview.vadjustment().unwrap();
+        self.load_more_messages(&adj);
         self.update_room_state();
+        self.set_empty_timeline();
         self.notify("room");
+        self.notify("empty");
     }
 
     pub fn room(&self) -> Option<Room> {
@@ -279,4 +304,26 @@ impl RoomHistory {
             }
         }
     }
+
+    fn set_empty_timeline(&self) {
+        let priv_ = imp::RoomHistory::from_instance(self);
+
+        if let Some(room) = &*priv_.room.borrow() {
+            if room.timeline().empty() {
+                priv_.stack.set_visible_child(&*priv_.loading);
+            } else {
+                priv_.stack.set_visible_child(&*priv_.scrolled_window);
+            }
+        }
+    }
+
+    fn load_more_messages(&self, adj: &gtk::Adjustment) {
+        // Load more message when the user gets close to the end of the known room history
+        // Use the page size twice to detect if the user gets close the end
+        if adj.value() < adj.page_size() * 2.0 || adj.upper() <= adj.page_size() * 2.0 {
+            if let Some(room) = self.room() {
+                room.load_previous_events();
+            }
+        }
+    }
 }
diff --git a/src/session/room/timeline.rs b/src/session/room/timeline.rs
index 2ceb569e..4acc7c4e 100644
--- a/src/session/room/timeline.rs
+++ b/src/session/room/timeline.rs
@@ -39,13 +39,22 @@ mod imp {
     impl ObjectImpl for Timeline {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-                vec![glib::ParamSpec::new_object(
-                    "room",
-                    "Room",
-                    "The Room containing this timeline",
-                    Room::static_type(),
-                    glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
-                )]
+                vec![
+                    glib::ParamSpec::new_object(
+                        "room",
+                        "Room",
+                        "The Room containing this timeline",
+                        Room::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpec::new_boolean(
+                        "empty",
+                        "Empty",
+                        "Whether the timeline is empty or not",
+                        false,
+                        glib::ParamFlags::READABLE,
+                    ),
+                ]
             });
 
             PROPERTIES.as_ref()
@@ -67,9 +76,10 @@ mod imp {
             }
         }
 
-        fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "room" => self.room.get().unwrap().to_value(),
+                "empty" => obj.empty().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -220,6 +230,8 @@ impl Timeline {
             }
         }
 
+        self.notify("empty");
+
         self.upcast_ref::<gio::ListModel>()
             .items_changed(position, removed, added);
     }
@@ -390,4 +402,9 @@ impl Timeline {
         let priv_ = imp::Timeline::from_instance(self);
         priv_.room.get().unwrap()
     }
+
+    pub fn empty(&self) -> bool {
+        let priv_ = imp::Timeline::from_instance(self);
+        priv_.list.borrow().is_empty()
+    }
 }


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