[fractal/fractal-next] room-history: Handle room upgrades



commit b068be5daef17b2d13a5c27bbc8b25172cd3d31a
Author: Julian Sparber <julian sparber net>
Date:   Tue Nov 30 16:30:24 2021 +0100

    room-history: Handle room upgrades
    
    This adds a better widget for creation and tombstone events to the
    room-history and shows buttons to allow users to navigate between
    different version of a room.

 data/resources/resources.gresource.xml             |  2 +
 data/resources/ui/content-state-creation.ui        | 26 ++++++++
 data/resources/ui/content-state-tombstone.ui       | 26 ++++++++
 po/POTFILES.in                                     |  1 +
 src/meson.build                                    |  4 +-
 .../content/room_history/state_row/creation.rs     | 71 ++++++++++++++++++++
 .../{state_row.rs => state_row/mod.rs}             | 78 +++++++++++++++-------
 .../content/room_history/state_row/tombstone.rs    | 55 +++++++++++++++
 8 files changed, 239 insertions(+), 24 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index bfb305e4..8dd8a9e9 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -17,6 +17,8 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-divider-row.ui">ui/content-divider-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-details.ui">ui/content-room-details.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-state-row.ui">ui/content-state-row.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="content-state-tombstone.ui">ui/content-state-tombstone.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="content-state-creation.ui">ui/content-state-creation.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-markdown-popover.ui">ui/content-markdown-popover.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-invite.ui">ui/content-invite.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="event-menu.ui">ui/event-menu.ui</file>
diff --git a/data/resources/ui/content-state-creation.ui b/data/resources/ui/content-state-creation.ui
new file mode 100644
index 00000000..17fc782b
--- /dev/null
+++ b/data/resources/ui/content-state-creation.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContentStateCreation" parent="AdwBin">
+    <property name="focusable">True</property>
+    <property name="valign">center</property>
+    <property name="halign">center</property>
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="description">
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="previous_room_btn">
+            <property name="action-name">session.show-room</property>
+            <property name="label" translatable="yes">Previous room</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/data/resources/ui/content-state-tombstone.ui b/data/resources/ui/content-state-tombstone.ui
new file mode 100644
index 00000000..038522c2
--- /dev/null
+++ b/data/resources/ui/content-state-tombstone.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContentStateTombstone" parent="AdwBin">
+    <property name="focusable">True</property>
+    <property name="valign">center</property>
+    <property name="halign">center</property>
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+            <property name="label" translatable="yes">This room was upgraded.</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="new_room_btn">
+            <property name="label" translatable="yes">Switch to new Room</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index bd618fcd..eabeb42a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -87,6 +87,7 @@ src/session/content/room_history/message_row/mod.rs
 src/session/content/room_history/message_row/text.rs
 src/session/content/room_history/mod.rs
 src/session/content/room_history/state_row.rs
+src/session/content/room_history/state_row/mod.rs
 src/session/media_viewer.rs
 src/session/mod.rs
 src/session/room_creation/mod.rs
diff --git a/src/meson.build b/src/meson.build
index 229e3a88..34e03aff 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -67,7 +67,9 @@ sources = files(
   'session/content/room_history/message_row/mod.rs',
   'session/content/room_history/message_row/text.rs',
   'session/content/room_history/mod.rs',
-  'session/content/room_history/state_row.rs',
+  'session/content/room_history/state_row/creation.rs',
+  'session/content/room_history/state_row/mod.rs',
+  'session/content/room_history/state_row/tombstone.rs',
   'session/content/mod.rs',
   'session/content/room_details/member_page.rs',
   'session/content/room_details/mod.rs',
diff --git a/src/session/content/room_history/state_row/creation.rs 
b/src/session/content/room_history/state_row/creation.rs
new file mode 100644
index 00000000..99f9467f
--- /dev/null
+++ b/src/session/content/room_history/state_row/creation.rs
@@ -0,0 +1,71 @@
+use adw::{prelude::*, subclass::prelude::*};
+use gettextrs::gettext;
+use gtk::{glib, subclass::prelude::*, CompositeTemplate};
+
+use matrix_sdk::ruma::events::room::create::RoomCreateEventContent;
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/content-state-creation.ui")]
+    pub struct StateCreation {
+        #[template_child]
+        pub previous_room_btn: TemplateChild<gtk::Button>,
+        #[template_child]
+        pub description: TemplateChild<gtk::Label>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for StateCreation {
+        const NAME: &'static str = "ContentStateCreation";
+        type Type = super::StateCreation;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for StateCreation {}
+    impl WidgetImpl for StateCreation {}
+    impl BinImpl for StateCreation {}
+}
+
+glib::wrapper! {
+    pub struct StateCreation(ObjectSubclass<imp::StateCreation>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl StateCreation {
+    pub fn new(event: &RoomCreateEventContent) -> Self {
+        let obj: Self = glib::Object::new(&[]).expect("Failed to create StateCreation");
+        obj.set_event(event);
+        obj
+    }
+
+    fn set_event(&self, event: &RoomCreateEventContent) {
+        let priv_ = imp::StateCreation::from_instance(self);
+        if let Some(predecessor) = &event.predecessor {
+            priv_.previous_room_btn.set_detailed_action_name(&format!(
+                "session.show-room::{}",
+                predecessor.room_id.as_str()
+            ));
+            priv_.previous_room_btn.show();
+            priv_
+                .description
+                .set_label(&gettext("This is the continuation of an upgraded room."));
+        } else {
+            priv_.previous_room_btn.hide();
+            priv_.previous_room_btn.set_action_name(None);
+            priv_
+                .description
+                .set_label(&gettext("The beginning of this room."));
+        }
+    }
+}
diff --git a/src/session/content/room_history/state_row.rs b/src/session/content/room_history/state_row/mod.rs
similarity index 74%
rename from src/session/content/room_history/state_row.rs
rename to src/session/content/room_history/state_row/mod.rs
index 0fbf711d..3c0803a8 100644
--- a/src/session/content/room_history/state_row.rs
+++ b/src/session/content/room_history/state_row/mod.rs
@@ -1,3 +1,9 @@
+mod creation;
+mod tombstone;
+
+use self::creation::StateCreation;
+use self::tombstone::StateTombstone;
+
 use adw::{prelude::*, subclass::prelude::*};
 use gettextrs::gettext;
 use gtk::{glib, subclass::prelude::*, CompositeTemplate};
@@ -52,13 +58,16 @@ impl StateRow {
     }
 
     pub fn update(&self, state: &AnySyncStateEvent) {
-        let _priv_ = imp::StateRow::from_instance(self);
         // We may want to show more state events in the future
         // For a full list of state events see:
         // https://matrix-org.github.io/matrix-rust-sdk/matrix_sdk/events/enum.AnyStateEventContent.html
         let message = match state.content() {
-            AnyStateEventContent::RoomCreate(_event) => gettext("The beginning of this room."),
-            AnyStateEventContent::RoomEncryption(_event) => gettext("This room is now encrypted."),
+            AnyStateEventContent::RoomCreate(event) => {
+                WidgetType::Creation(StateCreation::new(&event))
+            }
+            AnyStateEventContent::RoomEncryption(_event) => {
+                WidgetType::Text(gettext("This room is now encrypted."))
+            }
             AnyStateEventContent::RoomMember(event) => {
                 let display_name = event
                     .displayname
@@ -108,14 +117,19 @@ impl StateRow {
                             _ => None,
                         };
 
-                        message.unwrap_or(gettext!("{} joined this room.", display_name))
+                        WidgetType::Text(
+                            message.unwrap_or(gettext!("{} joined this room.", display_name)),
+                        )
                     }
                     MembershipState::Invite => {
-                        gettext!("{} was invited to this room.", display_name)
+                        WidgetType::Text(gettext!("{} was invited to this room.", display_name))
                     }
                     MembershipState::Knock => {
                         // TODO: Add button to invite the user.
-                        gettext!("{} requested to be invited to this room.", display_name)
+                        WidgetType::Text(gettext!(
+                            "{} requested to be invited to this room.",
+                            display_name
+                        ))
                     }
                     MembershipState::Leave => {
                         let message = match state.prev_content() {
@@ -136,18 +150,20 @@ impl StateRow {
                             _ => None,
                         };
 
-                        message.unwrap_or_else(|| {
+                        WidgetType::Text(message.unwrap_or_else(|| {
                             if state.state_key() == state.sender() {
                                 gettext!("{} left the room.", display_name)
                             } else {
                                 gettext!("{} was kicked out of the room.", display_name)
                             }
-                        })
+                        }))
+                    }
+                    MembershipState::Ban => {
+                        WidgetType::Text(gettext!("{} was banned.", display_name))
                     }
-                    MembershipState::Ban => gettext!("{} was banned.", display_name),
                     _ => {
                         warn!("Unsupported room member event: {:?}", state);
-                        gettext("An unsupported room member event was received.")
+                        WidgetType::Text(gettext("An unsupported room member event was received."))
                     }
                 }
             }
@@ -156,29 +172,45 @@ impl StateRow {
                     s if s.is_empty() => state.state_key().into(),
                     s => s,
                 };
-                gettext!("{} was invited to this room.", display_name)
+                WidgetType::Text(gettext!("{} was invited to this room.", display_name))
             }
             AnyStateEventContent::RoomTombstone(event) => {
-                gettext!("The room was upgraded: {}", event.body)
-                // Todo: add button for new room with action session.show_room::room_id
+                WidgetType::Tombstone(StateTombstone::new(&event))
             }
             _ => {
                 warn!("Unsupported state event: {}", state.event_type());
-                gettext("An unsupported state event was received.")
+                WidgetType::Text(gettext("An unsupported state event was received."))
             }
         };
-        if let Some(Ok(child)) = self.child().map(|w| w.downcast::<gtk::Label>()) {
-            child.set_text(&message);
-        } else {
-            let child = gtk::Label::new(Some(&message));
-            child.set_css_classes(&["event-content", "dim-label"]);
-            child.set_wrap(true);
-            child.set_wrap_mode(gtk::pango::WrapMode::WordChar);
-            self.set_child(Some(&child));
-        };
+
+        match message {
+            WidgetType::Text(message) => {
+                if let Some(Ok(child)) = self.child().map(|w| w.downcast::<gtk::Label>()) {
+                    child.set_text(&message);
+                } else {
+                    self.set_child(Some(&text(message)));
+                };
+            }
+            WidgetType::Creation(widget) => self.set_child(Some(&widget)),
+            WidgetType::Tombstone(widget) => self.set_child(Some(&widget)),
+        }
     }
 }
 
+enum WidgetType {
+    Text(String),
+    Creation(StateCreation),
+    Tombstone(StateTombstone),
+}
+
+fn text(label: String) -> gtk::Label {
+    let child = gtk::Label::new(Some(&label));
+    child.set_css_classes(&["event-content", "dim-label"]);
+    child.set_wrap(true);
+    child.set_wrap_mode(gtk::pango::WrapMode::WordChar);
+    child
+}
+
 impl Default for StateRow {
     fn default() -> Self {
         Self::new()
diff --git a/src/session/content/room_history/state_row/tombstone.rs 
b/src/session/content/room_history/state_row/tombstone.rs
new file mode 100644
index 00000000..9574c0a6
--- /dev/null
+++ b/src/session/content/room_history/state_row/tombstone.rs
@@ -0,0 +1,55 @@
+use adw::{prelude::*, subclass::prelude::*};
+use gtk::{glib, subclass::prelude::*, CompositeTemplate};
+use matrix_sdk::ruma::events::room::tombstone::RoomTombstoneEventContent;
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/content-state-tombstone.ui")]
+    pub struct StateTombstone {
+        #[template_child]
+        pub new_room_btn: TemplateChild<gtk::Button>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for StateTombstone {
+        const NAME: &'static str = "ContentStateTombstone";
+        type Type = super::StateTombstone;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for StateTombstone {}
+    impl WidgetImpl for StateTombstone {}
+    impl BinImpl for StateTombstone {}
+}
+
+glib::wrapper! {
+    pub struct StateTombstone(ObjectSubclass<imp::StateTombstone>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl StateTombstone {
+    pub fn new(event: &RoomTombstoneEventContent) -> Self {
+        let obj: Self = glib::Object::new(&[]).expect("Failed to create StateTombstone");
+        obj.set_event(event);
+        obj
+    }
+
+    fn set_event(&self, event: &RoomTombstoneEventContent) {
+        let priv_ = imp::StateTombstone::from_instance(self);
+        priv_.new_room_btn.set_detailed_action_name(&format!(
+            "session.show-room::{}",
+            event.replacement_room.as_str()
+        ));
+    }
+}


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