[fractal/fractal-next] verification: Add InfoBar for user verifications



commit 1d8de86b553525cdefc058350fb50c9230b1658b
Author: Julian Sparber <julian sparber net>
Date:   Fri Jan 7 19:01:44 2022 +0100

    verification: Add InfoBar for user verifications

 data/resources/resources.gresource.xml             |   1 +
 data/resources/ui/content-room-history.ui          |  12 ++
 data/resources/ui/content-verification-info-bar.ui |  43 +++++
 po/POTFILES.in                                     |   1 +
 src/meson.build                                    |   1 +
 src/session/content/room_history/mod.rs            |   6 +-
 .../content/room_history/verification_info_bar.rs  | 177 +++++++++++++++++++++
 src/session/mod.rs                                 |   5 +
 8 files changed, 245 insertions(+), 1 deletion(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index a1600b22..ffb2867d 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -26,6 +26,7 @@
     <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="content-verification-info-bar.ui">ui/content-verification-info-bar.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="event-menu.ui">ui/event-menu.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="event-source-dialog.ui">ui/event-source-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index dd5454f2..473ead9c 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -82,6 +82,17 @@
             </property>
           </object>
         </child>
+        <child>
+          <object class="ContentVerificationInfoBar" id="verification_info_bar">
+            <binding name="request">
+              <lookup name="verification" type="Timeline">
+                <lookup name="timeline" type="Room">
+                  <lookup name="room">ContentRoomHistory</lookup>
+                </lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
         <child>
           <object class="GtkStack" id="stack">
             <property name="transition-type">crossfade</property>
@@ -225,3 +236,4 @@
     </child>
   </template>
 </interface>
+
diff --git a/data/resources/ui/content-verification-info-bar.ui 
b/data/resources/ui/content-verification-info-bar.ui
new file mode 100644
index 00000000..79bd3a67
--- /dev/null
+++ b/data/resources/ui/content-verification-info-bar.ui
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContentVerificationInfoBar" parent="AdwBin">
+    <style>
+      <class name="info"/>
+    </style>
+    <child>
+      <object class="GtkRevealer" id="revealer">
+        <property name="child">
+          <object class="GtkBox">
+            <child>
+              <object class="GtkLabel" id="label">
+                <property name="margin-start">12</property>
+                <property name="hexpand">True</property>
+                <property name="halign">start</property>
+                <property name="wrap">True</property>
+                <property name="wrap-mode">word-char</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="button">
+                <property name="action-name">verification.accept</property>
+                <property name="valign">center</property>
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+              </object>
+            </child>
+            <child type="end">
+              <object class="GtkButton">
+                <property name="margin-end">12</property>
+                <property name="label" translatable="yes">Decline</property>
+                <property name="action-name">verification.decline</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </property>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a4c16ada..5d48afc1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -43,6 +43,7 @@ src/session/content/room_history/message_row/media.rs
 src/session/content/room_history/message_row/mod.rs
 src/session/content/room_history/state_row/creation.rs
 src/session/content/room_history/state_row/mod.rs
+src/session/content/room_history/verification_info_bar.rs
 src/session/content/verification/identity_verification_widget.rs
 src/session/content/verification/session_verification.rs
 src/session/media_viewer.rs
diff --git a/src/meson.build b/src/meson.build
index 794f42c6..97cafe3b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -71,6 +71,7 @@ sources = files(
   '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/room_history/verification_info_bar.rs',
   'session/content/mod.rs',
   'session/content/room_details/invite_subpage/invitee.rs',
   'session/content/room_details/invite_subpage/mod.rs',
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index c38f481f..97ed176e 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -2,10 +2,12 @@ mod divider_row;
 mod item_row;
 mod message_row;
 mod state_row;
+mod verification_info_bar;
 
 use self::divider_row::DividerRow;
 use self::item_row::ItemRow;
 use self::state_row::StateRow;
+use self::verification_info_bar::VerificationInfoBar;
 
 use adw::subclass::prelude::*;
 use gtk::{
@@ -20,7 +22,7 @@ use sourceview::prelude::*;
 
 use crate::components::{CustomEntry, Pill, RoomTitle};
 use crate::session::content::{MarkdownPopover, RoomDetails};
-use crate::session::room::{Item, Room, RoomType};
+use crate::session::room::{Item, Room, RoomType, Timeline};
 use crate::session::user::UserExt;
 
 mod imp {
@@ -76,6 +78,8 @@ mod imp {
             CustomEntry::static_type();
             ItemRow::static_type();
             MarkdownPopover::static_type();
+            VerificationInfoBar::static_type();
+            Timeline::static_type();
             Self::bind_template(klass);
             klass.set_accessible_role(gtk::AccessibleRole::Group);
             klass.install_action(
diff --git a/src/session/content/room_history/verification_info_bar.rs 
b/src/session/content/room_history/verification_info_bar.rs
new file mode 100644
index 00000000..212ddd36
--- /dev/null
+++ b/src/session/content/room_history/verification_info_bar.rs
@@ -0,0 +1,177 @@
+use adw::subclass::prelude::*;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use crate::session::user::UserExt;
+use crate::session::verification::{IdentityVerification, VerificationMode};
+use gettextrs::gettext;
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+    use glib::SignalHandlerId;
+    use std::cell::RefCell;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/content-verification-info-bar.ui")]
+    pub struct VerificationInfoBar {
+        #[template_child]
+        pub revealer: TemplateChild<gtk::Revealer>,
+        #[template_child]
+        pub label: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub button: TemplateChild<gtk::Button>,
+        pub request: RefCell<Option<IdentityVerification>>,
+        pub mode_handler: RefCell<Option<SignalHandlerId>>,
+        pub user_handler: RefCell<Option<SignalHandlerId>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for VerificationInfoBar {
+        const NAME: &'static str = "ContentVerificationInfoBar";
+        type Type = super::VerificationInfoBar;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            klass.set_css_name("infobar");
+            Self::bind_template(klass);
+
+            klass.set_accessible_role(gtk::AccessibleRole::Group);
+
+            klass.install_action("verification.accept", None, move |widget, _, _| {
+                let request = widget.request().unwrap();
+                request.accept();
+                request.session().select_item(Some(request.upcast()));
+            });
+
+            klass.install_action("verification.decline", None, move |widget, _, _| {
+                widget.request().unwrap().cancel();
+            });
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for VerificationInfoBar {
+        fn properties() -> &'static [glib::ParamSpec] {
+            use once_cell::sync::Lazy;
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_object(
+                    "request",
+                    "Request",
+                    "The verification request this InfoBar is showing",
+                    IdentityVerification::static_type(),
+                    glib::ParamFlags::READWRITE,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "request" => obj.set_request(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "request" => obj.request().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+    impl WidgetImpl for VerificationInfoBar {}
+    impl BinImpl for VerificationInfoBar {}
+}
+
+glib::wrapper! {
+    pub struct VerificationInfoBar(ObjectSubclass<imp::VerificationInfoBar>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl VerificationInfoBar {
+    pub fn new(label: String) -> Self {
+        glib::Object::new(&[("label", &label)]).expect("Failed to create VerificationInfoBar")
+    }
+
+    pub fn request(&self) -> Option<IdentityVerification> {
+        let priv_ = imp::VerificationInfoBar::from_instance(self);
+        priv_.request.borrow().clone()
+    }
+
+    pub fn set_request(&self, request: Option<IdentityVerification>) {
+        let priv_ = imp::VerificationInfoBar::from_instance(self);
+
+        if let Some(old_request) = &*priv_.request.borrow() {
+            if Some(old_request) == request.as_ref() {
+                return;
+            }
+
+            if let Some(handler) = priv_.mode_handler.take() {
+                old_request.disconnect(handler);
+            }
+
+            if let Some(handler) = priv_.user_handler.take() {
+                old_request.user().disconnect(handler);
+            }
+        }
+
+        if let Some(ref request) = request {
+            let handler = request.connect_notify_local(
+                Some("mode"),
+                clone!(@weak self as obj => move |_, _| {
+                    obj.update_view();
+                }),
+            );
+
+            priv_.mode_handler.replace(Some(handler));
+
+            let handler = request.user().connect_notify_local(
+                Some("display-name"),
+                clone!(@weak self as obj => move |_, _| {
+                    obj.update_view();
+                }),
+            );
+
+            priv_.user_handler.replace(Some(handler));
+        }
+
+        priv_.request.replace(request);
+
+        self.update_view();
+        self.notify("request");
+    }
+
+    pub fn update_view(&self) {
+        let priv_ = imp::VerificationInfoBar::from_instance(self);
+        let visible = if let Some(request) = self.request() {
+            if request.is_finished() {
+                false
+            } else if matches!(request.mode(), VerificationMode::Requested) {
+                // Translators: The value is the display name of the user who wants to be verified
+                priv_.label.set_markup(&gettext!(
+                    "<b>{}</b> wants to be verified",
+                    request.user().display_name()
+                ));
+                priv_.button.set_label(&gettext("Verify"));
+                true
+            } else {
+                priv_.label.set_label(&gettext("Verification in progess"));
+                priv_.button.set_label(&gettext("Continue"));
+                true
+            }
+        } else {
+            false
+        };
+
+        priv_.revealer.set_reveal_child(visible);
+    }
+}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 78abb300..9c848b8e 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -270,6 +270,11 @@ impl Session {
             .set_selected_item(room.map(|item| item.upcast()));
     }
 
+    pub fn select_item(&self, item: Option<glib::Object>) {
+        let priv_ = imp::Session::from_instance(self);
+        priv_.sidebar.set_selected_item(item);
+    }
+
     pub fn select_room_by_id(&self, room_id: RoomId) {
         if let Some(room) = self.room_list().get(&room_id) {
             self.select_room(Some(room));


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