[fractal/fractal-next] Add login requests to the sidebar and remove `ContentType`



commit 3514fcdbcab4edad49d102f3d47b18a7d248be2b
Author: Julian Sparber <julian sparber net>
Date:   Wed Nov 10 14:34:33 2021 +0100

    Add login requests to the sidebar and remove `ContentType`
    
    The `ContentType` could be removed because the needed information is
    already contained in the `selected-item`. This also addes `EntryType` to
    differentiat between different `Entry`s even tought we currently have
    only "Explore". This also cleans up how the selected-item is passed
    between `Content` and `Sidebar`
    
    This also replaces the `ToDeviceHandler` with `VerificationList` that's
    used to track verifications.

 data/resources/resources.gresource.xml            |   1 +
 data/resources/ui/content.ui                      |   2 -
 data/resources/ui/session.ui                      |   7 +-
 data/resources/ui/sidebar-verification-row.ui     |  39 ++++++
 po/POTFILES.in                                    |   7 +-
 src/meson.build                                   |   7 +-
 src/session/content/content_type.rs               |  27 ----
 src/session/content/explore/public_room.rs        |   2 +-
 src/session/content/mod.rs                        | 111 +++++++----------
 src/session/mod.rs                                | 145 +++++++---------------
 src/session/room/room_type.rs                     |  10 +-
 src/session/room_creation/mod.rs                  |   2 +-
 src/session/sidebar/category.rs                   |  75 ++++++-----
 src/session/sidebar/category_type.rs              |  58 +++++++++
 src/session/sidebar/entry.rs                      |  21 ++--
 src/session/sidebar/entry_type.rs                 |  23 ++++
 src/session/sidebar/item_list.rs                  | 103 ++++++++++-----
 src/session/sidebar/mod.rs                        |  96 +++++---------
 src/session/sidebar/row.rs                        |  19 ++-
 src/session/sidebar/selection.rs                  |  83 -------------
 src/session/sidebar/verification_row.rs           | 104 ++++++++++++++++
 src/session/verification/identity_verification.rs |  13 ++
 src/session/verification/mod.rs                   |   4 +-
 src/session/verification/to_device_handler.rs     |  95 --------------
 src/session/verification/verification_list.rs     | 123 ++++++++++++++++++
 25 files changed, 648 insertions(+), 529 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index cea9d767..c613e5f8 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -27,6 +27,7 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-category-row.ui">ui/sidebar-category-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-entry-row.ui">ui/sidebar-entry-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-room-row.ui">ui/sidebar-room-row.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-verification-row.ui">ui/sidebar-verification-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="window.ui">ui/window.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="context-menu-bin.ui">ui/context-menu-bin.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="pill.ui">ui/pill.ui</file>
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index 748f4a21..9a56762c 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -41,13 +41,11 @@
         <child>
           <object class="ContentRoomHistory" id="room_history">
             <property name="compact" bind-source="Content" bind-property="compact" bind-flags="sync-create"/>
-            <property name="room" bind-source="Content" bind-property="room" bind-flags="sync-create"/>
           </object>
         </child>
         <child>
           <object class="ContentInvite" id="invite">
             <property name="compact" bind-source="Content" bind-property="compact" bind-flags="sync-create"/>
-            <property name="room" bind-source="Content" bind-property="room" bind-flags="sync-create"/>
           </object>
         </child>
         <child>
diff --git a/data/resources/ui/session.ui b/data/resources/ui/session.ui
index f20a4630..f8dee1d9 100644
--- a/data/resources/ui/session.ui
+++ b/data/resources/ui/session.ui
@@ -41,9 +41,7 @@
               <object class="Sidebar" id="sidebar">
                 <property name="compact" bind-source="content" bind-property="folded" 
bind-flags="sync-create"/>
                 <property name="user" bind-source="Session" bind-property="user" bind-flags="sync-create"/>
-                <property name="room-list" bind-source="Session" bind-property="room-list" 
bind-flags="sync-create"/>
-                <property name="selected-room" bind-source="Session" bind-property="selected-room" 
bind-flags="sync-create | bidirectional"/>
-                <property name="selected-type" bind-source="Session" bind-property="selected-content-type" 
bind-flags="sync-create | bidirectional"/>
+                <property name="item-list" bind-source="Session" bind-property="item-list" 
bind-flags="sync-create"/>
               </object>
             </child>
             <child>
@@ -57,8 +55,7 @@
             <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"/>
-                <property name="content-type" bind-source="Session" bind-property="selected-content-type" 
bind-flags="sync-create | bidirectional"/>
+                <property name="item" bind-source="sidebar" bind-property="selected-item" 
bind-flags="sync-create | bidirectional"/>
                 <property name="session">Session</property>
               </object>
             </child>
diff --git a/data/resources/ui/sidebar-verification-row.ui b/data/resources/ui/sidebar-verification-row.ui
new file mode 100644
index 00000000..545b0179
--- /dev/null
+++ b/data/resources/ui/sidebar-verification-row.ui
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="SidebarVerificationRow" parent="AdwBin">
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkImage">
+            <property name="icon-name">devices-symbolic</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="display_name">
+            <property name="ellipsize">end</property>
+            <binding name="label">
+              <lookup name="display-name" type="IdentityVerification">
+                <lookup name="identity-verification">SidebarVerificationRow</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkLabel">
+            <property name="hexpand">True</property>
+            <property name="halign">end</property>
+            <property name="valign">center</property>
+            <property name="yalign">1.0</property>
+            <property name="label">•</property>
+            <style>
+              <class name="notification_count"/>
+              <class name="highlight"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1e9de487..c1579fb3 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -36,6 +36,7 @@ data/resources/ui/sidebar-category-row.ui
 data/resources/ui/sidebar-entry-row.ui
 data/resources/ui/sidebar-item.ui
 data/resources/ui/sidebar-room-row.ui
+data/resources/ui/sidebar-verification-row.ui
 data/resources/ui/sidebar.ui
 data/resources/ui/spinner-button.ui
 data/resources/ui/pill.ui
@@ -96,7 +97,11 @@ src/session/sidebar/account_switcher/item.rs
 src/session/sidebar/account_switcher/mod.rs
 src/session/sidebar/account_switcher/user_entry.rs
 src/session/sidebar/category_row.rs
+src/session/sidebar/category_type.rs
 src/session/sidebar/entry.rs
+src/session/sidebar/entry_row.rs,
+src/session/sidebar/entry_type.rs,
+src/session/sidebar/verification_row.rs,
 src/session/sidebar/mod.rs
 src/session/sidebar/room_row.rs
 src/session/sidebar/row.rs
@@ -105,7 +110,7 @@ src/session/verification/emoji.rs
 src/session/verification/identity_verification.rs
 src/session/verification/mod.rs
 src/session/verification/session_verification.rs
-src/session/verification/to_device_handler.rs
+src/session/verification/verification_list.rs
 src/session/user.rs
 src/utils.rs
 src/window.rs
diff --git a/src/meson.build b/src/meson.build
index 6a9f04cb..bd5b9bad 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -80,17 +80,22 @@ sources = files(
   'session/sidebar/mod.rs',
   'session/sidebar/row.rs',
   'session/sidebar/room_row.rs',
+  'session/sidebar/entry.rs',
+  'session/sidebar/entry_row.rs',
+  'session/sidebar/entry_type.rs',
+  'session/sidebar/verification_row.rs',
   'session/sidebar/selection.rs',
   'session/sidebar/account_switcher/add_account.rs',
   'session/sidebar/account_switcher/avatar_with_selection.rs',
   'session/sidebar/account_switcher/item.rs',
   'session/sidebar/account_switcher/mod.rs',
   'session/sidebar/account_switcher/user_entry.rs',
+  'session/sidebar/category_type.rs',
   'session/verification/mod.rs',
   'session/verification/emoji.rs',
   'session/verification/identity_verification.rs',
   'session/verification/session_verification.rs',
-  'session/verification/to_device_handler.rs',
+  'session/verification/verification_list.rs',
 )
 
 custom_target(
diff --git a/src/session/content/explore/public_room.rs b/src/session/content/explore/public_room.rs
index ec1aa5d1..23edc1e4 100644
--- a/src/session/content/explore/public_room.rs
+++ b/src/session/content/explore/public_room.rs
@@ -205,7 +205,7 @@ impl PublicRoom {
     pub fn join_or_view(&self) {
         let session = self.session();
         if let Some(room) = self.room() {
-            session.set_selected_room(Some(room.clone()));
+            session.select_room(Some(room.clone()));
         } else if let Some(matrix_public_room) = self.matrix_public_room() {
             session
                 .room_list()
diff --git a/src/session/content/mod.rs b/src/session/content/mod.rs
index 12bf3fb5..1b0ecbd2 100644
--- a/src/session/content/mod.rs
+++ b/src/session/content/mod.rs
@@ -1,4 +1,3 @@
-mod content_type;
 mod divider_row;
 mod explore;
 mod invite;
@@ -9,7 +8,6 @@ mod room_details;
 mod room_history;
 mod state_row;
 
-pub use self::content_type::ContentType;
 use self::divider_row::DividerRow;
 use self::explore::Explore;
 use self::invite::Invite;
@@ -18,6 +16,9 @@ use self::markdown_popover::MarkdownPopover;
 use self::room_details::RoomDetails;
 use self::room_history::RoomHistory;
 use self::state_row::StateRow;
+use crate::session::sidebar::{Entry, EntryType};
+
+use crate::session::verification::IdentityVerification;
 
 use adw::subclass::prelude::*;
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
@@ -37,8 +38,7 @@ mod imp {
     pub struct Content {
         pub compact: Cell<bool>,
         pub session: RefCell<Option<WeakRef<Session>>>,
-        pub room: RefCell<Option<Room>>,
-        pub content_type: Cell<ContentType>,
+        pub item: RefCell<Option<glib::Object>>,
         pub error_list: RefCell<Option<gio::ListStore>>,
         pub category_handler: RefCell<Option<SignalHandlerId>>,
         #[template_child]
@@ -67,7 +67,7 @@ mod imp {
             klass.set_accessible_role(gtk::AccessibleRole::Group);
 
             klass.install_action("content.go-back", None, move |widget, _, _| {
-                widget.set_content_type(ContentType::None);
+                widget.set_item(None);
             });
         }
 
@@ -95,10 +95,10 @@ mod imp {
                         glib::ParamFlags::READWRITE,
                     ),
                     glib::ParamSpec::new_object(
-                        "room",
-                        "Room",
-                        "The room currently shown",
-                        Room::static_type(),
+                        "item",
+                        "Item",
+                        "The item currently shown",
+                        glib::Object::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                     glib::ParamSpec::new_object(
@@ -108,14 +108,6 @@ mod imp {
                         gio::ListStore::static_type(),
                         glib::ParamFlags::READWRITE,
                     ),
-                    glib::ParamSpec::new_enum(
-                        "content-type",
-                        "Content Type",
-                        "The type of content currently displayed",
-                        ContentType::static_type(),
-                        ContentType::default() as i32,
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                    ),
                 ]
             });
 
@@ -135,14 +127,10 @@ mod imp {
                     self.compact.set(compact);
                 }
                 "session" => obj.set_session(value.get().unwrap()),
-                "room" => {
-                    let room = value.get().unwrap();
-                    obj.set_room(room);
-                }
+                "item" => obj.set_item(value.get().unwrap()),
                 "error-list" => {
                     self.error_list.replace(value.get().unwrap());
                 }
-                "content-type" => obj.set_content_type(value.get().unwrap()),
                 _ => unimplemented!(),
             }
         }
@@ -151,9 +139,8 @@ mod imp {
             match pspec.name() {
                 "compact" => self.compact.get().to_value(),
                 "session" => obj.session().to_value(),
-                "room" => obj.room().to_value(),
+                "item" => obj.item().to_value(),
                 "error-list" => self.error_list.borrow().to_value(),
-                "content-type" => obj.content_type().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -195,78 +182,76 @@ impl Content {
         self.notify("session");
     }
 
-    pub fn content_type(&self) -> ContentType {
-        let priv_ = imp::Content::from_instance(self);
-        priv_.content_type.get()
-    }
-
-    pub fn set_content_type(&self, content_type: ContentType) {
-        let priv_ = imp::Content::from_instance(self);
-
-        if self.content_type() == content_type {
-            return;
-        }
-
-        priv_.content_type.set(content_type);
-        self.set_visible_child();
-
-        self.notify("content-type");
-    }
-
-    pub fn set_room(&self, room: Option<Room>) {
+    pub fn set_item(&self, item: Option<glib::Object>) {
         let priv_ = imp::Content::from_instance(self);
 
-        if self.room() == room {
+        if self.item() == item {
             return;
         }
 
         if let Some(category_handler) = priv_.category_handler.take() {
-            if let Some(room) = self.room() {
-                room.disconnect(category_handler);
+            if let Some(item) = self.item() {
+                item.disconnect(category_handler);
             }
         }
 
-        if let Some(ref room) = room {
-            let handler_id = room.connect_notify_local(
-                Some("category"),
-                clone!(@weak self as obj => move |_, _| {
-                        obj.set_visible_child();
-                }),
-            );
+        if let Some(ref room) = item {
+            if room.is::<Room>() {
+                let handler_id = room.connect_notify_local(
+                    Some("category"),
+                    clone!(@weak self as obj => move |_, _| {
+                            obj.set_visible_child();
+                    }),
+                );
 
-            priv_.category_handler.replace(Some(handler_id));
+                priv_.category_handler.replace(Some(handler_id));
+            }
         }
 
-        priv_.room.replace(room);
+        priv_.item.replace(item);
         self.set_visible_child();
-        self.notify("room");
+        self.notify("item");
     }
 
-    pub fn room(&self) -> Option<Room> {
+    pub fn item(&self) -> Option<glib::Object> {
         let priv_ = imp::Content::from_instance(self);
-        priv_.room.borrow().clone()
+        priv_.item.borrow().clone()
     }
 
     fn set_visible_child(&self) {
         let priv_ = imp::Content::from_instance(self);
 
-        match self.content_type() {
-            ContentType::None => {
+        match self.item() {
+            None => {
                 priv_.stack.set_visible_child(&*priv_.empty_page);
             }
-            ContentType::Room => {
-                if let Some(room) = &*priv_.room.borrow() {
+            Some(o) if o.is::<Room>() => {
+                if let Some(room) = priv_
+                    .item
+                    .borrow()
+                    .as_ref()
+                    .and_then(|item| item.downcast_ref::<Room>())
+                {
                     if room.category() == RoomType::Invited {
+                        priv_.invite.set_room(Some(room.clone()));
                         priv_.stack.set_visible_child(&*priv_.invite);
                     } else {
+                        priv_.room_history.set_room(Some(room.clone()));
                         priv_.stack.set_visible_child(&*priv_.room_history);
                     }
                 }
             }
-            ContentType::Explore => {
+            Some(o)
+                if o.is::<Entry>()
+                    && o.downcast_ref::<Entry>().unwrap().type_() == EntryType::Explore =>
+            {
                 priv_.explore.init();
                 priv_.stack.set_visible_child(&*priv_.explore);
             }
+            Some(o) if o.is::<IdentityVerification>() => {
+                todo!("Incoming verifications arn't implemented yet");
+            }
+            _ => {}
         }
     }
 }
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 250b5837..eae03535 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -7,7 +7,7 @@ mod room_creation;
 mod room_list;
 mod sidebar;
 mod user;
-mod verification;
+pub mod verification;
 
 use self::account_settings::AccountSettings;
 pub use self::avatar::Avatar;
@@ -17,7 +17,8 @@ pub use self::room_creation::RoomCreation;
 use self::room_list::RoomList;
 use self::sidebar::Sidebar;
 pub use self::user::{User, UserExt};
-pub use self::verification::{IdentityVerification, SessionVerification, ToDeviceHandler};
+use self::verification::{IdentityVerification, SessionVerification, VerificationList};
+use crate::session::sidebar::ItemList;
 
 use crate::secret;
 use crate::secret::StoredSession;
@@ -26,7 +27,6 @@ use crate::Window;
 use crate::{spawn, spawn_tokio};
 
 use crate::matrix_error::UserFacingError;
-use crate::session::content::ContentType;
 use adw::subclass::prelude::BinImpl;
 use futures::StreamExt;
 use gettextrs::gettext;
@@ -76,16 +76,13 @@ mod imp {
         #[template_child]
         pub sidebar: TemplateChild<Sidebar>,
         pub client: RefCell<Option<Client>>,
-        pub room_list: OnceCell<RoomList>,
+        pub item_list: OnceCell<ItemList>,
         pub user: OnceCell<User>,
-        pub selected_room: RefCell<Option<Room>>,
-        pub selected_content_type: Cell<ContentType>,
         pub is_ready: Cell<bool>,
         pub logout_on_dispose: Cell<bool>,
         pub info: OnceCell<StoredSession>,
         pub source_id: RefCell<Option<SourceId>>,
         pub sync_tokio_handle: RefCell<Option<JoinHandle<()>>>,
-        pub to_device_handler: OnceCell<ToDeviceHandler>,
     }
 
     #[glib::object_subclass]
@@ -98,7 +95,7 @@ mod imp {
             Self::bind_template(klass);
 
             klass.install_action("session.close-room", None, move |session, _, _| {
-                session.set_selected_room(None);
+                session.select_room(None);
             });
 
             klass.install_action("session.logout", None, move |session, _, _| {
@@ -156,27 +153,12 @@ mod imp {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
                 vec![
                     glib::ParamSpec::new_object(
-                        "room-list",
-                        "Room List",
-                        "The list of rooms",
-                        RoomList::static_type(),
+                        "item-list",
+                        "Item List",
+                        "The list of items in the sidebar",
+                        ItemList::static_type(),
                         glib::ParamFlags::READABLE,
                     ),
-                    glib::ParamSpec::new_object(
-                        "selected-room",
-                        "Selected Room",
-                        "The selected room in this session",
-                        Room::static_type(),
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                    ),
-                    glib::ParamSpec::new_enum(
-                        "selected-content-type",
-                        "Selected Content Type",
-                        "The current content type selected",
-                        ContentType::static_type(),
-                        ContentType::default() as i32,
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                    ),
                     glib::ParamSpec::new_object(
                         "user",
                         "User",
@@ -190,29 +172,10 @@ mod imp {
             PROPERTIES.as_ref()
         }
 
-        fn set_property(
-            &self,
-            obj: &Self::Type,
-            _id: usize,
-            value: &glib::Value,
-            pspec: &glib::ParamSpec,
-        ) {
-            match pspec.name() {
-                "selected-room" => {
-                    let selected_room = value.get().unwrap();
-                    obj.set_selected_room(selected_room);
-                }
-                "selected-content-type" => obj.set_selected_content_type(value.get().unwrap()),
-                _ => unimplemented!(),
-            }
-        }
-
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
-                "room-list" => obj.room_list().to_value(),
-                "selected-room" => obj.selected_room().to_value(),
+                "item-list" => obj.item_list().to_value(),
                 "user" => obj.user().to_value(),
-                "selected-content-type" => obj.selected_content_type().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -233,6 +196,23 @@ mod imp {
             SIGNALS.as_ref()
         }
 
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            self.sidebar.connect_notify_local(
+                Some("selected-item"),
+                clone!(@weak obj => move |_, _| {
+                    let priv_ = imp::Session::from_instance(&obj);
+
+                    if priv_.sidebar.selected_item().is_none() {
+                        priv_.content.navigate(adw::NavigationDirection::Back);
+                    } else {
+                        priv_.content.navigate(adw::NavigationDirection::Forward);
+                    }
+                }),
+            );
+        }
+
         fn dispose(&self, obj: &Self::Type) {
             if let Some(source_id) = self.source_id.take() {
                 let _ = glib::Source::remove(source_id);
@@ -261,44 +241,11 @@ impl Session {
         glib::Object::new(&[]).expect("Failed to create Session")
     }
 
-    pub fn selected_content_type(&self) -> ContentType {
-        let priv_ = imp::Session::from_instance(self);
-        priv_.selected_content_type.get()
-    }
-
-    pub fn set_selected_content_type(&self, selected_type: ContentType) {
-        let priv_ = imp::Session::from_instance(self);
-
-        if self.selected_content_type() == selected_type {
-            return;
-        }
-
-        if selected_type == ContentType::None {
-            priv_.content.navigate(adw::NavigationDirection::Back);
-        } else {
-            priv_.content.navigate(adw::NavigationDirection::Forward);
-        }
-
-        priv_.selected_content_type.set(selected_type);
-
-        self.notify("selected-content-type");
-    }
-
-    pub fn selected_room(&self) -> Option<Room> {
-        let priv_ = imp::Session::from_instance(self);
-        priv_.selected_room.borrow().clone()
-    }
-
-    pub fn set_selected_room(&self, selected_room: Option<Room>) {
+    pub fn select_room(&self, room: Option<Room>) {
         let priv_ = imp::Session::from_instance(self);
-
-        if self.selected_room() == selected_room {
-            return;
-        }
-
-        priv_.selected_room.replace(selected_room);
-
-        self.notify("selected-room");
+        priv_
+            .sidebar
+            .set_selected_item(room.map(|item| item.upcast()));
     }
 
     pub fn login_with_password(&self, homeserver: Url, username: String, password: String) {
@@ -402,11 +349,6 @@ impl Session {
                 priv_.user.set(user.clone()).unwrap();
                 self.notify("user");
 
-                priv_
-                    .to_device_handler
-                    .set(ToDeviceHandler::new(self))
-                    .unwrap();
-
                 let handle = spawn_tokio!(async move {
                     let display_name = client.display_name().await?;
                     let avatar_url = client.avatar_url().await?;
@@ -552,11 +494,7 @@ impl Session {
 
                 let verification = IdentityVerification::new(obj.user().unwrap());
                 let session = SessionVerification::new(&verification, &obj);
-                priv_
-                    .to_device_handler
-                    .get()
-                    .unwrap()
-                    .add_request(verification);
+                obj.verification_list().add(verification);
                 priv_
                     .stack
                     .add_named(&session, Some("session-verification"));
@@ -576,8 +514,18 @@ impl Session {
     }
 
     pub fn room_list(&self) -> &RoomList {
+        self.item_list().room_list()
+    }
+
+    pub fn verification_list(&self) -> &VerificationList {
+        self.item_list().verification_list()
+    }
+
+    pub fn item_list(&self) -> &ItemList {
         let priv_ = &imp::Session::from_instance(self);
-        priv_.room_list.get_or_init(|| RoomList::new(self))
+        priv_
+            .item_list
+            .get_or_init(|| ItemList::new(&RoomList::new(self), &VerificationList::new(self)))
     }
 
     pub fn user(&self) -> Option<&User> {
@@ -655,18 +603,13 @@ impl Session {
     }
 
     fn handle_sync_response(&self, response: Result<SyncResponse, matrix_sdk::Error>) {
-        let priv_ = imp::Session::from_instance(self);
-
         match response {
             Ok(response) => {
                 if !self.is_ready() {
                     self.mark_ready();
                 }
                 self.room_list().handle_response_rooms(response.rooms);
-                priv_
-                    .to_device_handler
-                    .get()
-                    .unwrap()
+                self.verification_list()
                     .handle_response_to_device(response.to_device);
             }
             Err(error) => {
diff --git a/src/session/room/room_type.rs b/src/session/room/room_type.rs
index ca80a07d..1b7d88d3 100644
--- a/src/session/room/room_type.rs
+++ b/src/session/room/room_type.rs
@@ -1,4 +1,4 @@
-use gettextrs::gettext;
+use crate::session::sidebar::CategoryType;
 use gtk::glib;
 
 // TODO: do we also want the category `People` and a custom category support?
@@ -21,12 +21,6 @@ impl Default for RoomType {
 
 impl ToString for RoomType {
     fn to_string(&self) -> String {
-        match self {
-            RoomType::Invited => gettext("Invited"),
-            RoomType::Favorite => gettext("Favorite"),
-            RoomType::Normal => gettext("Rooms"),
-            RoomType::LowPriority => gettext("Low Priority"),
-            RoomType::Left => gettext("Historical"),
-        }
+        CategoryType::from(self).to_string()
     }
 }
diff --git a/src/session/room_creation/mod.rs b/src/session/room_creation/mod.rs
index 0b142209..c8e806d6 100644
--- a/src/session/room_creation/mod.rs
+++ b/src/session/room_creation/mod.rs
@@ -242,7 +242,7 @@ impl RoomCreation {
                         Ok(response) => {
                             if let Some(session) = obj.session() {
                                 let room = session.room_list().get_wait(response.room_id).await;
-                                session.set_selected_room(room);
+                                session.select_room(room);
                             }
                             obj.close();
                         },
diff --git a/src/session/sidebar/category.rs b/src/session/sidebar/category.rs
index 44b6dd8d..41ad6334 100644
--- a/src/session/sidebar/category.rs
+++ b/src/session/sidebar/category.rs
@@ -1,9 +1,7 @@
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use crate::session::{
-    room::{Room, RoomType},
-    room_list::RoomList,
-};
+use crate::session::sidebar::CategoryType;
+use crate::session::{room::Room, room_list::RoomList};
 
 mod imp {
     use once_cell::unsync::OnceCell;
@@ -14,7 +12,7 @@ mod imp {
     #[derive(Debug, Default)]
     pub struct Category {
         pub model: OnceCell<gio::ListModel>,
-        pub type_: Cell<RoomType>,
+        pub type_: Cell<CategoryType>,
     }
 
     #[glib::object_subclass]
@@ -34,8 +32,8 @@ mod imp {
                         "type",
                         "Type",
                         "The type of this category",
-                        RoomType::static_type(),
-                        RoomType::default() as i32,
+                        CategoryType::static_type(),
+                        CategoryType::default() as i32,
                         glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                     ),
                     glib::ParamSpec::new_string(
@@ -90,19 +88,19 @@ mod imp {
 
     impl ListModelImpl for Category {
         fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
-            Room::static_type()
+            glib::Object::static_type()
         }
         fn n_items(&self, _list_model: &Self::Type) -> u32 {
-            self.model.get().map(|l| l.n_items()).unwrap_or(0)
+            self.model.get().unwrap().n_items()
         }
         fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
-            self.model.get().and_then(|l| l.item(position))
+            self.model.get().unwrap().item(position)
         }
     }
 }
 
 glib::wrapper! {
-    /// A list of Rooms in the same category implementing ListModel.
+    /// A list of Items in the same category implementing ListModel.
     ///
     /// This struct is used in ItemList for the sidebar.
     pub struct Category(ObjectSubclass<imp::Category>)
@@ -110,11 +108,11 @@ glib::wrapper! {
 }
 
 impl Category {
-    pub fn new(type_: RoomType, model: &RoomList) -> Self {
+    pub fn new(type_: CategoryType, model: &impl IsA<gio::ListModel>) -> Self {
         glib::Object::new(&[("type", &type_), ("model", model)]).expect("Failed to create Category")
     }
 
-    pub fn type_(&self) -> RoomType {
+    pub fn type_(&self) -> CategoryType {
         let priv_ = imp::Category::from_instance(self);
         priv_.type_.get()
     }
@@ -123,26 +121,35 @@ impl Category {
         let priv_ = imp::Category::from_instance(self);
         let type_ = self.type_();
 
-        let filter = gtk::CustomFilter::new(move |o| {
-            o.downcast_ref::<Room>()
-                .filter(|r| r.category() == type_)
-                .is_some()
-        });
-        let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter));
-
-        let sorter = gtk::CustomSorter::new(|a, b| {
-            let a = a.downcast_ref::<Room>().unwrap();
-            let b = b.downcast_ref::<Room>().unwrap();
-            b.latest_change().cmp(&a.latest_change()).into()
-        });
-        let sort_model = gtk::SortListModel::new(Some(&filter_model), Some(&sorter));
-
-        sort_model.connect_items_changed(
-            clone!(@weak self as obj => move |_, pos, added, removed| {
-                obj.items_changed(pos, added, removed);
-            }),
-        );
-
-        priv_.model.set(sort_model.upcast()).unwrap();
+        // Special case room lists so that they are sorted and in the right category
+        if model.is::<RoomList>() {
+            let filter = gtk::CustomFilter::new(move |o| {
+                o.downcast_ref::<Room>()
+                    .filter(|r| CategoryType::from(r.category()) == type_)
+                    .is_some()
+            });
+            let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter));
+
+            let sorter = gtk::CustomSorter::new(|a, b| {
+                let a = a.downcast_ref::<Room>().unwrap();
+                let b = b.downcast_ref::<Room>().unwrap();
+                b.latest_change().cmp(&a.latest_change()).into()
+            });
+            let sort_model = gtk::SortListModel::new(Some(&filter_model), Some(&sorter));
+
+            sort_model.connect_items_changed(
+                clone!(@weak self as obj => move |_, pos, removed, added| {
+                    obj.items_changed(pos, removed, added);
+                }),
+            );
+            priv_.model.set(sort_model.upcast()).unwrap();
+        } else {
+            model.connect_items_changed(
+                clone!(@weak self as obj => move |_, pos, removed, added| {
+                    obj.items_changed(pos, removed, added);
+                }),
+            );
+            priv_.model.set(model).unwrap();
+        }
     }
 }
diff --git a/src/session/sidebar/category_type.rs b/src/session/sidebar/category_type.rs
new file mode 100644
index 00000000..3a027eba
--- /dev/null
+++ b/src/session/sidebar/category_type.rs
@@ -0,0 +1,58 @@
+use crate::session::room::RoomType;
+use gettextrs::gettext;
+use gtk::glib;
+
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[repr(u32)]
+#[genum(type_name = "CategoryType")]
+pub enum CategoryType {
+    VerificationRequest = 0,
+    Invited = 1,
+    Favorite = 2,
+    Normal = 3,
+    LowPriority = 4,
+    Left = 5,
+}
+
+impl Default for CategoryType {
+    fn default() -> Self {
+        CategoryType::Normal
+    }
+}
+
+impl ToString for CategoryType {
+    fn to_string(&self) -> String {
+        match self {
+            CategoryType::VerificationRequest => gettext("Login Requests"),
+            CategoryType::Invited => gettext("Invited"),
+            CategoryType::Favorite => gettext("Favorite"),
+            CategoryType::Normal => gettext("Rooms"),
+            CategoryType::LowPriority => gettext("Low Priority"),
+            CategoryType::Left => gettext("Historical"),
+        }
+    }
+}
+
+impl From<RoomType> for CategoryType {
+    fn from(room_type: RoomType) -> Self {
+        match room_type {
+            RoomType::Invited => Self::Invited,
+            RoomType::Favorite => Self::Favorite,
+            RoomType::Normal => Self::Normal,
+            RoomType::LowPriority => Self::LowPriority,
+            RoomType::Left => Self::Left,
+        }
+    }
+}
+
+impl From<&RoomType> for CategoryType {
+    fn from(room_type: &RoomType) -> Self {
+        match room_type {
+            RoomType::Invited => Self::Invited,
+            RoomType::Favorite => Self::Favorite,
+            RoomType::Normal => Self::Normal,
+            RoomType::LowPriority => Self::LowPriority,
+            RoomType::Left => Self::Left,
+        }
+    }
+}
diff --git a/src/session/sidebar/entry.rs b/src/session/sidebar/entry.rs
index d98f4527..4a0572f6 100644
--- a/src/session/sidebar/entry.rs
+++ b/src/session/sidebar/entry.rs
@@ -1,6 +1,6 @@
 use gtk::{glib, prelude::*, subclass::prelude::*};
 
-use crate::session::content::ContentType;
+use crate::session::sidebar::EntryType;
 
 mod imp {
     use std::cell::{Cell, RefCell};
@@ -9,8 +9,7 @@ mod imp {
 
     #[derive(Debug, Default)]
     pub struct Entry {
-        pub type_: Cell<ContentType>,
-        pub display_name: RefCell<Option<String>>,
+        pub type_: Cell<EntryType>,
         pub icon_name: RefCell<Option<String>>,
     }
 
@@ -30,8 +29,8 @@ mod imp {
                         "type",
                         "Type",
                         "The type of this category",
-                        ContentType::static_type(),
-                        ContentType::default() as i32,
+                        EntryType::static_type(),
+                        EntryType::default() as i32,
                         glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                     ),
                     glib::ParamSpec::new_string(
@@ -39,7 +38,7 @@ mod imp {
                         "Display Name",
                         "The display name of this Entry",
                         None,
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                        glib::ParamFlags::READABLE,
                     ),
                     glib::ParamSpec::new_string(
                         "icon-name",
@@ -65,9 +64,6 @@ mod imp {
                 "type" => {
                     self.type_.set(value.get().unwrap());
                 }
-                "display-name" => {
-                    let _ = self.display_name.replace(value.get().unwrap());
-                }
                 "icon-name" => {
                     let _ = self.icon_name.replace(value.get().unwrap());
                 }
@@ -95,19 +91,18 @@ glib::wrapper! {
 }
 
 impl Entry {
-    pub fn new(type_: ContentType) -> Self {
+    pub fn new(type_: EntryType) -> Self {
         glib::Object::new(&[("type", &type_)]).expect("Failed to create Entry")
     }
 
-    pub fn type_(&self) -> ContentType {
+    pub fn type_(&self) -> EntryType {
         let priv_ = imp::Entry::from_instance(self);
         priv_.type_.get()
     }
 
     pub fn icon_name(&self) -> Option<&str> {
         match self.type_() {
-            ContentType::Explore => Some("explore-symbolic"),
-            _ => None,
+            EntryType::Explore => Some("explore-symbolic"),
         }
     }
 }
diff --git a/src/session/sidebar/entry_type.rs b/src/session/sidebar/entry_type.rs
new file mode 100644
index 00000000..fcbdfd17
--- /dev/null
+++ b/src/session/sidebar/entry_type.rs
@@ -0,0 +1,23 @@
+use gettextrs::gettext;
+use gtk::glib;
+
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[repr(u32)]
+#[genum(type_name = "EntryType")]
+pub enum EntryType {
+    Explore = 0,
+}
+
+impl Default for EntryType {
+    fn default() -> Self {
+        EntryType::Explore
+    }
+}
+
+impl ToString for EntryType {
+    fn to_string(&self) -> String {
+        match self {
+            EntryType::Explore => gettext("Explore"),
+        }
+    }
+}
diff --git a/src/session/sidebar/item_list.rs b/src/session/sidebar/item_list.rs
index 8042d3a8..7fa61cfd 100644
--- a/src/session/sidebar/item_list.rs
+++ b/src/session/sidebar/item_list.rs
@@ -1,10 +1,11 @@
 use gtk::{gio, glib, prelude::*, subclass::prelude::*};
 
 use crate::session::{
-    content::ContentType,
-    room::RoomType,
     room_list::RoomList,
+    sidebar::CategoryType,
+    sidebar::EntryType,
     sidebar::{Category, Entry},
+    verification::VerificationList,
 };
 
 mod imp {
@@ -15,7 +16,9 @@ mod imp {
 
     #[derive(Debug, Default)]
     pub struct ItemList {
-        pub list: OnceCell<[glib::Object; 6]>,
+        pub list: OnceCell<[glib::Object; 7]>,
+        pub room_list: OnceCell<RoomList>,
+        pub verification_list: OnceCell<VerificationList>,
     }
 
     #[glib::object_subclass]
@@ -29,13 +32,22 @@ mod imp {
     impl ObjectImpl for ItemList {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-                vec![glib::ParamSpec::new_object(
-                    "room-list",
-                    "Room list",
-                    "Data model for the categories",
-                    RoomList::static_type(),
-                    glib::ParamFlags::WRITABLE | glib::ParamFlags::CONSTRUCT_ONLY,
-                )]
+                vec![
+                    glib::ParamSpec::new_object(
+                        "room-list",
+                        "Room list",
+                        "The list of rooms",
+                        RoomList::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpec::new_object(
+                        "verification-list",
+                        "Verification list",
+                        "The list of verification requests",
+                        VerificationList::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                ]
             });
 
             PROPERTIES.as_ref()
@@ -49,13 +61,39 @@ mod imp {
             pspec: &glib::ParamSpec,
         ) {
             match pspec.name() {
-                "room-list" => {
-                    let x = value.get().unwrap();
-                    obj.set_room_list(&x)
-                }
+                "room-list" => obj.set_room_list(value.get().unwrap()),
+                "verification-list" => obj.set_verification_list(value.get().unwrap()),
                 _ => unimplemented!(),
             }
         }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "room-list" => obj.room_list().to_value(),
+                "verification-list" => obj.verification_list().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            let room_list = obj.room_list();
+            let verification_list = obj.verification_list();
+
+            let list = [
+                Entry::new(EntryType::Explore).upcast::<glib::Object>(),
+                Category::new(CategoryType::VerificationRequest, verification_list)
+                    .upcast::<glib::Object>(),
+                Category::new(CategoryType::Invited, room_list).upcast::<glib::Object>(),
+                Category::new(CategoryType::Favorite, room_list).upcast::<glib::Object>(),
+                Category::new(CategoryType::Normal, room_list).upcast::<glib::Object>(),
+                Category::new(CategoryType::LowPriority, room_list).upcast::<glib::Object>(),
+                Category::new(CategoryType::Left, room_list).upcast::<glib::Object>(),
+            ];
+
+            self.list.set(list).unwrap();
+        }
     }
 
     impl ListModelImpl for ItemList {
@@ -85,24 +123,31 @@ glib::wrapper! {
 }
 
 impl ItemList {
-    pub fn new(room_list: &RoomList) -> Self {
-        glib::Object::new(&[("room-list", room_list)]).expect("Failed to create ItemList")
+    pub fn new(room_list: &RoomList, verification_list: &VerificationList) -> Self {
+        glib::Object::new(&[
+            ("room-list", room_list),
+            ("verification-list", verification_list),
+        ])
+        .expect("Failed to create ItemList")
     }
 
-    fn set_room_list(&self, room_list: &RoomList) {
+    fn set_room_list(&self, room_list: RoomList) {
         let priv_ = imp::ItemList::from_instance(self);
+        priv_.room_list.set(room_list).unwrap();
+    }
+
+    fn set_verification_list(&self, verification_list: VerificationList) {
+        let priv_ = imp::ItemList::from_instance(self);
+        priv_.verification_list.set(verification_list).unwrap();
+    }
 
-        let list = [
-            Entry::new(ContentType::Explore).upcast::<glib::Object>(),
-            Category::new(RoomType::Invited, room_list).upcast::<glib::Object>(),
-            Category::new(RoomType::Favorite, room_list).upcast::<glib::Object>(),
-            Category::new(RoomType::Normal, room_list).upcast::<glib::Object>(),
-            Category::new(RoomType::LowPriority, room_list).upcast::<glib::Object>(),
-            Category::new(RoomType::Left, room_list).upcast::<glib::Object>(),
-        ];
-        let len = list.len() as u32;
-
-        priv_.list.set(list).unwrap();
-        self.items_changed(0, 0, len);
+    pub fn room_list(&self) -> &RoomList {
+        let priv_ = imp::ItemList::from_instance(self);
+        priv_.room_list.get().unwrap()
+    }
+
+    pub fn verification_list(&self) -> &VerificationList {
+        let priv_ = imp::ItemList::from_instance(self);
+        priv_.verification_list.get().unwrap()
     }
 }
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index 45f6d773..606f33fc 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -1,29 +1,34 @@
 mod account_switcher;
 mod category;
 mod category_row;
+mod category_type;
 mod entry;
 mod entry_row;
+mod entry_type;
 mod item_list;
 mod room_row;
 mod row;
 mod selection;
+mod verification_row;
 
 pub use self::category::Category;
 use self::category_row::CategoryRow;
+pub use self::category_type::CategoryType;
 pub use self::entry::Entry;
 use self::entry_row::EntryRow;
+pub use self::entry_type::EntryType;
 pub use self::item_list::ItemList;
 use self::room_row::RoomRow;
 use self::row::Row;
 use self::selection::Selection;
+use self::verification_row::VerificationRow;
 
 use adw::subclass::prelude::BinImpl;
 use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate, SelectionModel};
 
 use crate::components::Avatar;
-use crate::session::content::ContentType;
 use crate::session::room::Room;
-use crate::session::RoomList;
+use crate::session::verification::IdentityVerification;
 use crate::session::Session;
 use crate::session::User;
 use account_switcher::AccountSwitcher;
@@ -38,8 +43,7 @@ mod imp {
     #[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
     pub struct Sidebar {
         pub compact: Cell<bool>,
-        pub selected_room: RefCell<Option<Room>>,
-        pub selected_type: Cell<ContentType>,
+        pub selected_item: RefCell<Option<glib::Object>>,
         #[template_child]
         pub headerbar: TemplateChild<adw::HeaderBar>,
         #[template_child]
@@ -90,25 +94,17 @@ mod imp {
                         glib::ParamFlags::READWRITE,
                     ),
                     glib::ParamSpec::new_object(
-                        "room-list",
-                        "Room List",
-                        "The list of rooms",
-                        RoomList::static_type(),
-                        glib::ParamFlags::WRITABLE,
-                    ),
-                    glib::ParamSpec::new_object(
-                        "selected-room",
-                        "Selected Room",
-                        "The selected room in this sidebar",
-                        Room::static_type(),
+                        "item-list",
+                        "Item List",
+                        "The list of items in the sidebar",
+                        ItemList::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
-                    glib::ParamSpec::new_enum(
-                        "selected-type",
-                        "Selected",
-                        "The type of item that is selected",
-                        ContentType::static_type(),
-                        ContentType::default() as i32,
+                    glib::ParamSpec::new_object(
+                        "selected-item",
+                        "Selected Item",
+                        "The selected item in this sidebar",
+                        glib::Object::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                 ]
@@ -132,15 +128,13 @@ mod imp {
                 "user" => {
                     obj.set_user(value.get().unwrap());
                 }
-                "room-list" => {
-                    let room_list = value.get().unwrap();
-                    obj.set_room_list(room_list);
+                "item-list" => {
+                    obj.set_item_list(value.get().unwrap());
                 }
-                "selected-room" => {
-                    let selected_room = value.get().unwrap();
-                    obj.set_selected_room(selected_room);
+                "selected-item" => {
+                    let selected_item = value.get().unwrap();
+                    obj.set_selected_item(selected_item);
                 }
-                "selected-type" => obj.set_selected_type(value.get().unwrap()),
                 _ => unimplemented!(),
             }
         }
@@ -149,8 +143,7 @@ mod imp {
             match pspec.name() {
                 "compact" => self.compact.get().to_value(),
                 "user" => obj.user().to_value(),
-                "selected-room" => obj.selected_room().to_value(),
-                "selected-type" => obj.selected_type().to_value(),
+                "selected-item" => obj.selected_item().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -174,6 +167,7 @@ mod imp {
                     Some(o) if o.is::<Category>() => row.set_expanded(!row.is_expanded()),
                     Some(o) if o.is::<Room>() => model.set_selected(pos),
                     Some(o) if o.is::<Entry>() => model.set_selected(pos),
+                    Some(o) if o.is::<IdentityVerification>() => model.set_selected(pos),
                     _ => {}
                 }
             });
@@ -194,26 +188,9 @@ impl Sidebar {
         glib::Object::new(&[]).expect("Failed to create Sidebar")
     }
 
-    pub fn selected_type(&self) -> ContentType {
+    pub fn selected_item(&self) -> Option<glib::Object> {
         let priv_ = imp::Sidebar::from_instance(self);
-        priv_.selected_type.get()
-    }
-
-    fn set_selected_type(&self, selected_type: ContentType) {
-        let priv_ = imp::Sidebar::from_instance(self);
-
-        if self.selected_type() == selected_type {
-            return;
-        }
-
-        priv_.selected_type.set(selected_type);
-
-        self.notify("selected-type");
-    }
-
-    pub fn selected_room(&self) -> Option<Room> {
-        let priv_ = imp::Sidebar::from_instance(self);
-        priv_.selected_room.borrow().clone()
+        priv_.selected_item.borrow().clone()
     }
 
     pub fn room_search_bar(&self) -> gtk::SearchBar {
@@ -221,10 +198,10 @@ impl Sidebar {
         priv_.room_search.clone()
     }
 
-    pub fn set_room_list(&self, room_list: Option<RoomList>) {
+    pub fn set_item_list(&self, item_list: Option<ItemList>) {
         let priv_ = imp::Sidebar::from_instance(self);
-        let room_list = match room_list {
-            Some(room_list) => room_list,
+        let item_list = match item_list {
+            Some(item_list) => item_list,
             None => {
                 priv_.listview.set_model(gtk::NONE_SELECTION_MODEL);
                 return;
@@ -232,7 +209,6 @@ impl Sidebar {
         };
 
         // TODO: hide empty categories
-        let item_list = ItemList::new(&room_list);
         let tree_model = gtk::TreeListModel::new(&item_list, false, true, |item| {
             item.clone().downcast::<gio::ListModel>().ok()
         });
@@ -262,26 +238,22 @@ impl Sidebar {
             .build();
 
         let selection = Selection::new(Some(&filter_model));
-        self.bind_property("selected-room", &selection, "selected-item")
-            .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
-            .build();
-
-        self.bind_property("selected-type", &selection, "selected-type")
+        self.bind_property("selected-item", &selection, "selected-item")
             .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
             .build();
 
         priv_.listview.set_model(Some(&selection));
     }
 
-    fn set_selected_room(&self, selected_room: Option<Room>) {
+    pub fn set_selected_item(&self, selected_item: Option<glib::Object>) {
         let priv_ = imp::Sidebar::from_instance(self);
 
-        if self.selected_room() == selected_room {
+        if self.selected_item() == selected_item {
             return;
         }
 
-        priv_.selected_room.replace(selected_room);
-        self.notify("selected-room");
+        priv_.selected_item.replace(selected_item);
+        self.notify("selected-item");
     }
 
     pub fn user(&self) -> Option<User> {
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
index 2bf7f87f..d993d91e 100644
--- a/src/session/sidebar/row.rs
+++ b/src/session/sidebar/row.rs
@@ -3,7 +3,8 @@ use gtk::{glib, subclass::prelude::*};
 
 use crate::session::{
     room::Room,
-    sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow},
+    sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, VerificationRow},
+    verification::IdentityVerification,
 };
 
 mod imp {
@@ -166,6 +167,22 @@ impl Row {
                 if let Some(list_item) = self.parent() {
                     list_item.set_css_classes(&["entry"]);
                 }
+            } else if let Some(verification) = item.downcast_ref::<IdentityVerification>() {
+                let child = if let Some(Ok(child)) =
+                    self.child().map(|w| w.downcast::<VerificationRow>())
+                {
+                    child
+                } else {
+                    let child = VerificationRow::new();
+                    self.set_child(Some(&child));
+                    child
+                };
+
+                child.set_identity_verification(Some(verification.clone()));
+
+                if let Some(list_item) = self.parent() {
+                    list_item.set_css_classes(&["room"]);
+                }
             } else {
                 panic!("Wrong row item: {:?}", item);
             }
diff --git a/src/session/sidebar/selection.rs b/src/session/sidebar/selection.rs
index 544b6bf2..a0fc77b5 100644
--- a/src/session/sidebar/selection.rs
+++ b/src/session/sidebar/selection.rs
@@ -1,7 +1,5 @@
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use crate::session::{content::ContentType, room::Room, sidebar::Entry};
-
 mod imp {
     use super::*;
     use once_cell::sync::Lazy;
@@ -57,14 +55,6 @@ mod imp {
                         glib::Object::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
-                    glib::ParamSpec::new_enum(
-                        "selected-type",
-                        "Selected Type",
-                        "The currently selected content type",
-                        ContentType::static_type(),
-                        ContentType::default() as i32,
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                    ),
                 ]
             });
 
@@ -88,7 +78,6 @@ mod imp {
                     obj.set_selected(selected);
                 }
                 "selected-item" => obj.set_selected_item(value.get().unwrap()),
-                "selected-type" => obj.set_selected_type(value.get().unwrap()),
                 _ => unimplemented!(),
             }
         }
@@ -98,7 +87,6 @@ mod imp {
                 "model" => obj.model().to_value(),
                 "selected" => obj.selected().to_value(),
                 "selected-item" => obj.selected_item().to_value(),
-                "selected-type" => obj.selected_type().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -169,74 +157,6 @@ impl Selection {
         priv_.selected_item.borrow().clone()
     }
 
-    pub fn selected_type(&self) -> ContentType {
-        if let Some(item) = self.selected_item() {
-            if item.is::<Room>() {
-                return ContentType::Room;
-            } else if let Ok(entry) = item.downcast::<Entry>() {
-                return entry.type_();
-            }
-        }
-
-        ContentType::None
-    }
-
-    pub fn set_selected_type(&self, selected_type: ContentType) {
-        let priv_ = imp::Selection::from_instance(self);
-
-        if self.selected_type() == selected_type {
-            return;
-        }
-
-        match selected_type {
-            ContentType::None => self.set_selected_item(None),
-            ContentType::Room => {
-                if self
-                    .selected_item()
-                    .and_then(|item| item.downcast::<Room>().ok())
-                    .is_none()
-                {
-                    if let Some(model) = &*priv_.model.borrow() {
-                        for i in 0..model.n_items() {
-                            if let Some(room) = model
-                                .item(i)
-                                .and_then(|item| item.downcast::<gtk::TreeListRow>().ok())
-                                .and_then(|i| i.item())
-                                .and_then(|o| o.downcast::<Room>().ok())
-                            {
-                                self.set_selected_item(Some(room.upcast()));
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-            ContentType::Explore => {
-                if !self
-                    .selected_item()
-                    .and_then(|item| item.downcast::<Entry>().ok())
-                    .map_or(false, |entry| entry.type_() == selected_type)
-                {
-                    if let Some(model) = &*priv_.model.borrow() {
-                        for i in 0..model.n_items() {
-                            if let Some(entry) = model
-                                .item(i)
-                                .and_then(|item| item.downcast::<gtk::TreeListRow>().ok())
-                                .and_then(|i| i.item())
-                                .and_then(|o| o.downcast::<Entry>().ok())
-                            {
-                                if entry.type_() == selected_type {
-                                    self.set_selected_item(Some(entry.upcast()));
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        };
-    }
-
     pub fn set_model<P: IsA<gio::ListModel>>(&self, model: Option<&P>) {
         let priv_ = imp::Selection::from_instance(self);
 
@@ -279,7 +199,6 @@ impl Selection {
             }
             if self.selected_item().is_some() {
                 priv_.selected_item.replace(None);
-                self.notify("selected-type");
                 self.notify("selected-item");
             }
 
@@ -328,7 +247,6 @@ impl Selection {
 
         self.notify("selected");
         self.notify("selected-item");
-        self.notify("selected-type");
     }
 
     fn set_selected_item(&self, item: Option<glib::Object>) {
@@ -376,7 +294,6 @@ impl Selection {
         }
 
         self.notify("selected-item");
-        self.notify("selected-type");
     }
 
     fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) {
diff --git a/src/session/sidebar/verification_row.rs b/src/session/sidebar/verification_row.rs
new file mode 100644
index 00000000..fdc30c3d
--- /dev/null
+++ b/src/session/sidebar/verification_row.rs
@@ -0,0 +1,104 @@
+use adw::subclass::prelude::BinImpl;
+use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use crate::session::verification::IdentityVerification;
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+    use once_cell::sync::Lazy;
+    use std::cell::RefCell;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/sidebar-verification-row.ui")]
+    pub struct VerificationRow {
+        pub verification: RefCell<Option<IdentityVerification>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for VerificationRow {
+        const NAME: &'static str = "SidebarVerificationRow";
+        type Type = super::VerificationRow;
+        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 VerificationRow {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_object(
+                    "identity-verification",
+                    "Identity Verification",
+                    "The identity verification of this row",
+                    IdentityVerification::static_type(),
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "identity-verification" => obj.set_identity_verification(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "identity-verification" => obj.identity_verification().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl WidgetImpl for VerificationRow {}
+    impl BinImpl for VerificationRow {}
+}
+
+glib::wrapper! {
+    pub struct VerificationRow(ObjectSubclass<imp::VerificationRow>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl VerificationRow {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create VerificationRow")
+    }
+
+    pub fn identity_verification(&self) -> Option<IdentityVerification> {
+        let priv_ = imp::VerificationRow::from_instance(self);
+        priv_.verification.borrow().clone()
+    }
+
+    pub fn set_identity_verification(&self, verification: Option<IdentityVerification>) {
+        let priv_ = imp::VerificationRow::from_instance(self);
+
+        if self.identity_verification() == verification {
+            return;
+        }
+
+        priv_.verification.replace(verification);
+        self.notify("identity-verification");
+    }
+}
+
+impl Default for VerificationRow {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/src/session/verification/identity_verification.rs 
b/src/session/verification/identity_verification.rs
index a2abc07e..53ccdb5d 100644
--- a/src/session/verification/identity_verification.rs
+++ b/src/session/verification/identity_verification.rs
@@ -130,6 +130,13 @@ mod imp {
                         Mode::default() as i32,
                         glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
+                    glib::ParamSpec::new_string(
+                        "display-name",
+                        "Display name",
+                        "The display name of this verificaiton request",
+                        None,
+                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
                 ]
             });
 
@@ -153,6 +160,7 @@ mod imp {
             match pspec.name() {
                 "user" => obj.user().to_value(),
                 "mode" => obj.mode().to_value(),
+                "display-name" => obj.display_name().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -283,6 +291,11 @@ impl IdentityVerification {
         priv_.request.replace(request);
     }
 
+    pub fn display_name(&self) -> String {
+        // TODO: give this request a name based on the device
+        "Request".to_string()
+    }
+
     /// Get the QrCode for this verification request
     ///
     /// This is only set once the request reached the `State::Ready`
diff --git a/src/session/verification/mod.rs b/src/session/verification/mod.rs
index ba2e3074..9fc05594 100644
--- a/src/session/verification/mod.rs
+++ b/src/session/verification/mod.rs
@@ -1,9 +1,9 @@
 mod emoji;
 mod identity_verification;
 mod session_verification;
-mod to_device_handler;
+mod verification_list;
 
 pub use self::emoji::Emoji;
 pub use self::identity_verification::{IdentityVerification, Mode as VerificationMode};
 pub use self::session_verification::SessionVerification;
-pub use self::to_device_handler::ToDeviceHandler;
+pub use self::verification_list::VerificationList;
diff --git a/src/session/verification/verification_list.rs b/src/session/verification/verification_list.rs
new file mode 100644
index 00000000..f3d73421
--- /dev/null
+++ b/src/session/verification/verification_list.rs
@@ -0,0 +1,123 @@
+use gtk::{gio, glib, prelude::*, subclass::prelude::*};
+use matrix_sdk::ruma::{api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent};
+
+use crate::session::{verification::IdentityVerification, Session};
+
+mod imp {
+    use glib::object::WeakRef;
+    use once_cell::{sync::Lazy, unsync::OnceCell};
+    use std::cell::RefCell;
+
+    use super::*;
+
+    #[derive(Debug, Default)]
+    pub struct VerificationList {
+        pub list: RefCell<Vec<IdentityVerification>>,
+        pub session: OnceCell<WeakRef<Session>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for VerificationList {
+        const NAME: &'static str = "VerificationList";
+        type Type = super::VerificationList;
+        type ParentType = glib::Object;
+        type Interfaces = (gio::ListModel,);
+    }
+
+    impl ObjectImpl for VerificationList {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_object(
+                    "session",
+                    "Session",
+                    "The session",
+                    Session::static_type(),
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            _obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "session" => self
+                    .session
+                    .set(value.get::<Session>().unwrap().downgrade())
+                    .unwrap(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "session" => obj.session().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl ListModelImpl for VerificationList {
+        fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
+            IdentityVerification::static_type()
+        }
+        fn n_items(&self, _list_model: &Self::Type) -> u32 {
+            self.list.borrow().len() as u32
+        }
+        fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
+            self.list
+                .borrow()
+                .get(position as usize)
+                .map(glib::object::Cast::upcast_ref::<glib::Object>)
+                .cloned()
+        }
+    }
+}
+
+glib::wrapper! {
+    pub struct VerificationList(ObjectSubclass<imp::VerificationList>)
+        @implements gio::ListModel;
+}
+
+impl VerificationList {
+    pub fn new(session: &Session) -> Self {
+        glib::Object::new(&[("session", session)]).expect("Failed to create VerificationList")
+    }
+
+    pub fn session(&self) -> Session {
+        let priv_ = imp::VerificationList::from_instance(self);
+        priv_.session.get().unwrap().upgrade().unwrap()
+    }
+
+    pub fn handle_response_to_device(&self, to_device: ToDevice) {
+        let priv_ = imp::VerificationList::from_instance(self);
+
+        for event in &to_device.events {
+            if let Ok(AnyToDeviceEvent::KeyVerificationRequest(_event)) = event.deserialize() {
+                //TODO: implement handling of incomming requests
+            }
+        }
+
+        for verification in &*priv_.list.borrow() {
+            verification.handle_response_to_device(to_device.clone());
+        }
+    }
+
+    /// Add a new `IdentityVerification` request
+    pub fn add(&self, request: IdentityVerification) {
+        let priv_ = imp::VerificationList::from_instance(self);
+        let length = {
+            let mut list = priv_.list.borrow_mut();
+            let length = list.len();
+            list.push(request);
+            length as u32
+        };
+        self.items_changed(length, 0, 1)
+    }
+}


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