[fractal/multi-account: 5/8] Add account switcher




commit a2b2a71103144ef01da62223ae306b533156034e
Author: Alejandro Domínguez <adomu net-c com>
Date:   Mon Jun 14 20:16:07 2021 +0200

    Add account switcher

 data/resources/resources.gresource.xml             |   3 +
 data/resources/style.css                           |  22 +++
 data/resources/ui/add-account-row.ui               |  23 ++++
 data/resources/ui/sidebar-account-switcher.ui      |  11 ++
 data/resources/ui/sidebar.ui                       |  19 ++-
 data/resources/ui/user-entry-row.ui                |  62 +++++++++
 po/POTFILES.in                                     |   6 +
 src/application.rs                                 |   8 ++
 src/components/avatar.rs                           |   2 +-
 src/meson.build                                    |   4 +
 src/session/mod.rs                                 |   7 +-
 .../sidebar/account_switcher/add_account.rs        |  43 ++++++
 src/session/sidebar/account_switcher/item.rs       | 148 +++++++++++++++++++++
 src/session/sidebar/account_switcher/mod.rs        | 124 +++++++++++++++++
 src/session/sidebar/account_switcher/user_entry.rs |  91 +++++++++++++
 src/session/sidebar/mod.rs                         |  12 +-
 src/window.rs                                      |   6 +
 17 files changed, 584 insertions(+), 7 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 58479c3c..ca7c77b3 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/FractalNext/">
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="add-account-row.ui">ui/add-account-row.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="user-entry-row.ui">ui/user-entry-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="shortcuts.ui">ui/shortcuts.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="content.ui">ui/content.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-history.ui">ui/content-room-history.ui</file>
@@ -19,6 +21,7 @@
     <file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-account-switcher.ui">ui/sidebar-account-switcher.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="sidebar-item.ui">ui/sidebar-item.ui</file>
     <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>
diff --git a/data/resources/style.css b/data/resources/style.css
index b291ab32..08e2583f 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -78,6 +78,28 @@ headerbar.flat {
   border: none;
 }
 
+/* Account switcher */
+#account-switcher row {
+  border-radius: 10px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+  padding-top: 7px;
+  padding-bottom: 7px;
+}
+
+#account-switcher .user-id {
+  font-size: 12px;
+}
+
+#new-login-icon {
+  /*
+   *  2 * padding + pixel-size = size (of avatar)
+   */
+  padding: 10px;
+  background-color: lightgrey;
+  border-radius: 9999px;
+}
+
 /* Sidebar */
 .sidebar row {
   padding-left: 10px;
diff --git a/data/resources/ui/add-account-row.ui b/data/resources/ui/add-account-row.ui
new file mode 100644
index 00000000..367fe5ca
--- /dev/null
+++ b/data/resources/ui/add-account-row.ui
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="AddAccountRow" parent="AdwBin">
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkImage">
+            <property name="name">new-login-icon</property>
+            <property name="icon-name">list-add-symbolic</property>
+            <property name="pixel-size">20</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="use-underline">true</property>
+            <property name="label">_Add Account</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/resources/ui/sidebar-account-switcher.ui b/data/resources/ui/sidebar-account-switcher.ui
new file mode 100644
index 00000000..74dace8d
--- /dev/null
+++ b/data/resources/ui/sidebar-account-switcher.ui
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="AccountSwitcher" parent="GtkPopover">
+    <property name="name">account-switcher</property>
+    <child>
+      <object class="GtkListView" id="entries">
+        <property name="single-click-activate">true</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/resources/ui/sidebar.ui b/data/resources/ui/sidebar.ui
index c56907c3..06b3c2f9 100644
--- a/data/resources/ui/sidebar.ui
+++ b/data/resources/ui/sidebar.ui
@@ -25,12 +25,16 @@
         <property name="orientation">vertical</property>
         <child>
           <object class="AdwHeaderBar" id="headerbar">
+            <property name="title-widget">
+              <object class="AdwWindowTitle"></object>
+            </property>
             <property name="show-end-title-buttons" bind-source="Sidebar" bind-property="compact" 
bind-flags="sync-create"/>
             <child type="start">
-              <object class="GtkToggleButton" id="search_button">
-                <property name="icon-name">system-search-symbolic</property>
-                <property name="active" bind-source="room_search" bind-property="search-mode-enabled" 
bind-flags="sync-create"/>
-                <property name="action-name">win.toggle-room-search</property>
+              <object class="GtkMenuButton" id="accounts_button">
+                <property name="icon-name">system-users-symbolic</property>
+                <property name="popover">
+                  <object class="AccountSwitcher" id="account_switcher"></object>
+                </property>
               </object>
             </child>
             <child type="end">
@@ -39,6 +43,13 @@
                 <property name="menu-model">primary_menu</property>
               </object>
             </child>
+            <child type="end">
+              <object class="GtkToggleButton" id="search_button">
+                <property name="icon-name">system-search-symbolic</property>
+                <property name="active" bind-source="room_search" bind-property="search-mode-enabled" 
bind-flags="sync-create"/>
+                <property name="action-name">win.toggle-room-search</property>
+              </object>
+            </child>
           </object>
         </child>
         <child>
diff --git a/data/resources/ui/user-entry-row.ui b/data/resources/ui/user-entry-row.ui
new file mode 100644
index 00000000..611fc607
--- /dev/null
+++ b/data/resources/ui/user-entry-row.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="UserEntryRow" parent="AdwBin">
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">10</property>
+        <child>
+          <object class="ComponentsAvatar" id="avatar_component">
+            <property name="size">40</property>
+            <binding name="item">
+              <lookup name="avatar" type="User">
+                <lookup name="user" type="Session">
+                  <lookup name="child" type="GtkStackPage">
+                    <lookup name="session-page">UserEntryRow</lookup>
+                  </lookup>
+                </lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="spacing">5</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="display_name">
+                <property name="xalign">0.0</property>
+                <binding name="label">
+                  <lookup name="display-name" type="User">
+                    <lookup name="user" type="Session">
+                      <lookup name="child" type="GtkStackPage">
+                        <lookup name="session-page">UserEntryRow</lookup>
+                      </lookup>
+                    </lookup>
+                  </lookup>
+                </binding>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="user_id">
+                <property name="xalign">0.0</property>
+                <binding name="label">
+                  <lookup name="user-id" type="User">
+                    <lookup name="user" type="Session">
+                      <lookup name="child" type="GtkStackPage">
+                        <lookup name="session-page">UserEntryRow</lookup>
+                      </lookup>
+                    </lookup>
+                  </lookup>
+                </binding>
+                <style>
+                  <class name="dim-label" />
+                  <class name="user-id" />
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1c55e8f1..222b35dd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,6 +5,7 @@ data/org.gnome.FractalNext.gschema.xml.in
 data/org.gnome.FractalNext.metainfo.xml.in.in
 
 # UI files
+data/resources/ui/add_account.ui
 data/resources/ui/components-avatar.ui
 data/resources/ui/content-divider-row.ui
 data/resources/ui/content-item-row-menu.ui
@@ -29,6 +30,7 @@ data/resources/ui/sidebar-room-row.ui
 data/resources/ui/sidebar.ui
 data/resources/ui/spinner-button.ui
 data/resources/ui/pill.ui
+data/resources/ui/user-entry-row.ui
 data/resources/ui/window.ui
 
 # Rust files
@@ -64,6 +66,10 @@ src/session/room/highlight_flags.rs
 src/session/room/item.rs
 src/session/room/mod.rs
 src/session/room/timeline.rs
+src/session/sidebar/account_switcher/add_account.rs
+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/entry.rs
 src/session/sidebar/mod.rs
diff --git a/src/application.rs b/src/application.rs
index 82ec6f89..0f86a436 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -120,6 +120,14 @@ impl Application {
                 app.show_about_dialog();
             })
         );
+
+        action!(
+            self,
+            "new-login",
+            clone!(@weak self as app => move |_, _| {
+                app.get_main_window().switch_to_login_page();
+            })
+        );
     }
 
     /// Sets up keyboard shortcuts for application and window actions.
diff --git a/src/components/avatar.rs b/src/components/avatar.rs
index e3cbc25b..4745dd82 100644
--- a/src/components/avatar.rs
+++ b/src/components/avatar.rs
@@ -6,6 +6,7 @@ use crate::session::Avatar as AvatarItem;
 mod imp {
     use super::*;
     use glib::subclass::InitializingObject;
+    use once_cell::sync::Lazy;
     use std::cell::RefCell;
 
     #[derive(Debug, Default, CompositeTemplate)]
@@ -34,7 +35,6 @@ mod imp {
 
     impl ObjectImpl for Avatar {
         fn properties() -> &'static [glib::ParamSpec] {
-            use once_cell::sync::Lazy;
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
                 vec![
                     glib::ParamSpec::new_object(
diff --git a/src/meson.build b/src/meson.build
index c0d57ba0..b72383c2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -69,6 +69,10 @@ sources = files(
   'session/sidebar/row.rs',
   'session/sidebar/room_row.rs',
   'session/sidebar/selection.rs',
+  'session/sidebar/account_switcher/add_account.rs',
+  'session/sidebar/account_switcher/item.rs',
+  'session/sidebar/account_switcher/mod.rs',
+  'session/sidebar/account_switcher/user_entry.rs',
 )
 
 custom_target(
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 3084f7cd..436fa2f6 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -25,7 +25,7 @@ use crate::session::content::ContentType;
 use adw::subclass::prelude::BinImpl;
 use gtk::subclass::prelude::*;
 use gtk::{self, prelude::*};
-use gtk::{gio, glib, glib::clone, glib::SyncSender, CompositeTemplate};
+use gtk::{gio, glib, glib::clone, glib::SyncSender, CompositeTemplate, SelectionModel};
 use gtk_macros::send;
 use log::error;
 use matrix_sdk::ruma::{
@@ -449,6 +449,11 @@ impl Session {
     fn handle_sync_response(&self, response: SyncResponse) {
         self.room_list().handle_response_rooms(response.rooms);
     }
+
+    pub fn set_logged_in_users(&self, sessions_stack_pages: &SelectionModel) {
+        let priv_ = &imp::Session::from_instance(self);
+        priv_.sidebar.set_logged_in_users(sessions_stack_pages);
+    }
 }
 
 impl Default for Session {
diff --git a/src/session/sidebar/account_switcher/add_account.rs 
b/src/session/sidebar/account_switcher/add_account.rs
new file mode 100644
index 00000000..abf9ccff
--- /dev/null
+++ b/src/session/sidebar/account_switcher/add_account.rs
@@ -0,0 +1,43 @@
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/add-account-row.ui")]
+    pub struct AddAccountRow;
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for AddAccountRow {
+        const NAME: &'static str = "AddAccountRow";
+        type Type = super::AddAccountRow;
+        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 AddAccountRow {}
+    impl WidgetImpl for AddAccountRow {}
+    impl BinImpl for AddAccountRow {}
+}
+
+glib::wrapper! {
+    pub struct AddAccountRow(ObjectSubclass<imp::AddAccountRow>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl AddAccountRow {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create AddAccountRow")
+    }
+}
diff --git a/src/session/sidebar/account_switcher/item.rs b/src/session/sidebar/account_switcher/item.rs
new file mode 100644
index 00000000..332fa3e8
--- /dev/null
+++ b/src/session/sidebar/account_switcher/item.rs
@@ -0,0 +1,148 @@
+use super::add_account::AddAccountRow;
+use super::user_entry::UserEntryRow;
+use gtk::{gio::ListStore, glib, prelude::*, subclass::prelude::*};
+use std::convert::TryFrom;
+
+mod imp {
+    use super::*;
+    use once_cell::sync::Lazy;
+    use std::cell::Cell;
+
+    #[derive(Debug, Default)]
+    pub struct ExtraItemObj(pub Cell<super::ExtraItem>);
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for ExtraItemObj {
+        const NAME: &'static str = "ExtraItemObj";
+        type Type = super::ExtraItemObj;
+        type ParentType = glib::Object;
+    }
+
+    impl ObjectImpl for ExtraItemObj {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_enum(
+                    "inner",
+                    "Inner",
+                    "Inner value of ExtraItem",
+                    super::ExtraItem::static_type(),
+                    0,
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "inner" => obj.get().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn set_property(
+            &self,
+            _obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "inner" => self.0.set(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+    }
+}
+
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[repr(u32)]
+#[genum(type_name = "ExtraItem")]
+pub enum ExtraItem {
+    Separator = 0,
+    AddAccount = 1,
+}
+
+impl ExtraItem {
+    const VALUES: [Self; 2] = [Self::Separator, Self::AddAccount];
+}
+
+impl Default for ExtraItem {
+    fn default() -> Self {
+        Self::Separator
+    }
+}
+
+glib::wrapper! {
+    pub struct ExtraItemObj(ObjectSubclass<imp::ExtraItemObj>);
+}
+
+impl From<&ExtraItem> for ExtraItemObj {
+    fn from(item: &ExtraItem) -> Self {
+        glib::Object::new(&[("inner", item)]).expect("Failed to create ExtraItem")
+    }
+}
+
+impl ExtraItemObj {
+    pub fn list_store() -> ListStore {
+        ExtraItem::VALUES.iter().map(ExtraItemObj::from).fold(
+            ListStore::new(ExtraItemObj::static_type()),
+            |list_items, item| {
+                list_items.append(&item);
+                list_items
+            },
+        )
+    }
+
+    pub fn get(&self) -> ExtraItem {
+        let priv_ = imp::ExtraItemObj::from_instance(self);
+
+        priv_.0.get()
+    }
+
+    pub fn is_separator(&self) -> bool {
+        self.get() == ExtraItem::Separator
+    }
+
+    pub fn is_add_account(&self) -> bool {
+        self.get() == ExtraItem::AddAccount
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum Item {
+    User(gtk::StackPage),
+    Separator,
+    AddAccount,
+}
+
+impl From<ExtraItem> for Item {
+    fn from(extra_item: ExtraItem) -> Self {
+        match extra_item {
+            ExtraItem::Separator => Self::Separator,
+            ExtraItem::AddAccount => Self::AddAccount,
+        }
+    }
+}
+
+impl TryFrom<glib::Object> for Item {
+    type Error = glib::Object;
+
+    fn try_from(object: glib::Object) -> Result<Self, Self::Error> {
+        object
+            .downcast::<gtk::StackPage>()
+            .map(Self::User)
+            .or_else(|object| object.downcast::<ExtraItemObj>().map(|it| it.get().into()))
+    }
+}
+
+impl Item {
+    pub fn build_widget(&self) -> gtk::Widget {
+        match self {
+            Self::User(ref session_page) => UserEntryRow::new(session_page).upcast(),
+            Self::Separator => gtk::Separator::new(gtk::Orientation::Vertical).upcast(),
+            Self::AddAccount => AddAccountRow::new().upcast(),
+        }
+    }
+}
diff --git a/src/session/sidebar/account_switcher/mod.rs b/src/session/sidebar/account_switcher/mod.rs
new file mode 100644
index 00000000..e1e5db0b
--- /dev/null
+++ b/src/session/sidebar/account_switcher/mod.rs
@@ -0,0 +1,124 @@
+use gtk::{
+    gio::{self, ListModel, ListStore},
+    glib,
+    prelude::*,
+    subclass::prelude::*,
+    CompositeTemplate, SelectionModel,
+};
+use std::convert::TryFrom;
+
+use super::account_switcher::item::{ExtraItemObj, Item as AccountSwitcherItem};
+
+pub mod add_account;
+pub mod item;
+pub mod user_entry;
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/sidebar-account-switcher.ui")]
+    pub struct AccountSwitcher {
+        #[template_child]
+        pub entries: TemplateChild<gtk::ListView>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for AccountSwitcher {
+        const NAME: &'static str = "AccountSwitcher";
+        type Type = super::AccountSwitcher;
+        type ParentType = gtk::Popover;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for AccountSwitcher {
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            self.entries.connect_activate(|list_view, index| {
+                list_view
+                    .model()
+                    .and_then(|model| model.item(index))
+                    .map(AccountSwitcherItem::try_from)
+                    .and_then(Result::ok)
+                    .map(|item| match item {
+                        AccountSwitcherItem::User(session_page) => {
+                            let session_widget = session_page.child();
+                            session_widget
+                                .parent()
+                                .unwrap()
+                                .downcast::<gtk::Stack>()
+                                .unwrap()
+                                .set_visible_child(&session_widget);
+                        }
+                        AccountSwitcherItem::AddAccount => {
+                            list_view.activate_action("app.new-login", None);
+                        }
+                        _ => {}
+                    });
+            });
+
+            // There is no permanent stuff to take care of,
+            // so only bind and unbind are connected.
+            let ref factory = gtk::SignalListItemFactory::new();
+            factory.connect_bind(|_, list_item| {
+                list_item.set_selectable(false);
+                let child = list_item
+                    .item()
+                    .map(AccountSwitcherItem::try_from)
+                    .and_then(Result::ok)
+                    .as_ref()
+                    .map(|item| {
+                        match item {
+                            AccountSwitcherItem::Separator => {
+                                list_item.set_activatable(false);
+                            }
+                            _ => {}
+                        }
+
+                        item
+                    })
+                    .map(AccountSwitcherItem::build_widget);
+
+                list_item.set_child(child.as_ref());
+            });
+
+            factory.connect_unbind(|_, list_item| {
+                list_item.set_child(gtk::NONE_WIDGET);
+            });
+
+            self.entries.set_factory(Some(factory));
+        }
+    }
+
+    impl WidgetImpl for AccountSwitcher {}
+    impl PopoverImpl for AccountSwitcher {}
+}
+
+glib::wrapper! {
+    pub struct AccountSwitcher(ObjectSubclass<imp::AccountSwitcher>)
+        @extends gtk::Widget, gtk::Popover, @implements gtk::Accessible, gio::ListModel;
+}
+
+impl AccountSwitcher {
+    pub fn set_logged_in_users(&self, sessions_stack_pages: &SelectionModel) {
+        let entries = imp::AccountSwitcher::from_instance(self).entries.get();
+
+        let ref end_items = ExtraItemObj::list_store();
+        let ref items_split = ListStore::new(ListModel::static_type());
+        items_split.append(sessions_stack_pages);
+        items_split.append(end_items);
+        let ref items = gtk::FlattenListModel::new(Some(items_split));
+        let ref selectable_items = gtk::NoSelection::new(Some(items));
+
+        entries.set_model(Some(selectable_items));
+    }
+}
diff --git a/src/session/sidebar/account_switcher/user_entry.rs 
b/src/session/sidebar/account_switcher/user_entry.rs
new file mode 100644
index 00000000..6edeaf14
--- /dev/null
+++ b/src/session/sidebar/account_switcher/user_entry.rs
@@ -0,0 +1,91 @@
+use crate::components::Avatar;
+use adw::subclass::prelude::BinImpl;
+use gtk::{self, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+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/user-entry-row.ui")]
+    pub struct UserEntryRow {
+        #[template_child]
+        pub avatar_component: TemplateChild<Avatar>,
+        #[template_child]
+        pub display_name: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub user_id: TemplateChild<gtk::Label>,
+        pub session_page: RefCell<Option<gtk::StackPage>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for UserEntryRow {
+        const NAME: &'static str = "UserEntryRow";
+        type Type = super::UserEntryRow;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            Avatar::static_type();
+            Self::bind_template(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for UserEntryRow {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_object(
+                    "session-page",
+                    "Session StackPage",
+                    "The stack page of the session that this entry represents",
+                    gtk::StackPage::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() {
+                "session-page" => {
+                    let session_page = value.get().unwrap();
+                    self.session_page.replace(Some(session_page));
+                }
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "session-page" => self.session_page.borrow().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl WidgetImpl for UserEntryRow {}
+    impl BinImpl for UserEntryRow {}
+}
+
+glib::wrapper! {
+    pub struct UserEntryRow(ObjectSubclass<imp::UserEntryRow>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl UserEntryRow {
+    pub fn new(session_page: &gtk::StackPage) -> Self {
+        glib::Object::new(&[("session-page", session_page)]).expect("Failed to create UserEntryRow")
+    }
+}
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index 56cbef1e..c68e7320 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -1,3 +1,4 @@
+mod account_switcher;
 mod category;
 mod category_row;
 mod entry;
@@ -17,8 +18,9 @@ use self::row::Row;
 use self::selection::Selection;
 
 use adw::subclass::prelude::BinImpl;
-use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate, SelectionModel};
 
+use account_switcher::AccountSwitcher;
 use crate::session::content::ContentType;
 use crate::session::room::Room;
 use crate::session::RoomList;
@@ -38,6 +40,8 @@ mod imp {
         #[template_child]
         pub headerbar: TemplateChild<adw::HeaderBar>,
         #[template_child]
+        pub account_switcher: TemplateChild<AccountSwitcher>,
+        #[template_child]
         pub listview: TemplateChild<gtk::ListView>,
         #[template_child]
         pub room_search_entry: TemplateChild<gtk::SearchEntry>,
@@ -263,6 +267,12 @@ impl Sidebar {
         priv_.selected_room.replace(selected_room);
         self.notify("selected-room");
     }
+
+    pub fn set_logged_in_users(&self, sessions_stack_pages: &SelectionModel) {
+        imp::Sidebar::from_instance(self)
+            .account_switcher
+            .set_logged_in_users(sessions_stack_pages);
+    }
 }
 
 impl Default for Sidebar {
diff --git a/src/window.rs b/src/window.rs
index b15df917..be2c512d 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -99,6 +99,7 @@ impl Window {
 
     fn add_session(&self, session: &Session) {
         let priv_ = &imp::Window::from_instance(self);
+        session.set_logged_in_users(&priv_.sessions.pages());
         priv_.sessions.add_child(session);
         priv_.sessions.set_visible_child(session);
         self.install_session_actions(session);
@@ -179,4 +180,9 @@ impl Window {
         let priv_ = imp::Window::from_instance(self);
         priv_.main_stack.set_visible_child(&priv_.sessions.get());
     }
+
+    pub fn switch_to_login_page(&self) {
+        let priv_ = imp::Window::from_instance(self);
+        priv_.main_stack.set_visible_child(&priv_.login.get());
+    }
 }


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