From 2adec1644be50ef9c7c20c97c8667e5ebb833ef2 Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Tue, 1 Jun 2021 17:14:28 +0200 Subject: [PATCH] Add support for user-defined avatars Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/785 --- data/resources/resources.gresource.xml | 1 + data/resources/ui/components-avatar.ui | 35 ++++ data/resources/ui/content-invite.ui | 7 +- data/resources/ui/content-message-row.ui | 5 +- data/resources/ui/pill.ui | 4 +- data/resources/ui/sidebar-room-row.ui | 5 +- po/POTFILES.in | 2 + src/components/avatar.rs | 155 +++++++++++++++++ src/components/mod.rs | 2 + src/components/pill.rs | 5 +- src/meson.build | 2 + src/session/avatar.rs | 211 +++++++++++++++++++++++ src/session/content/invite.rs | 3 +- src/session/content/message_row.rs | 7 +- src/session/mod.rs | 2 + src/session/room/room.rs | 25 ++- src/session/sidebar/room_row.rs | 8 +- src/session/user.rs | 31 +++- 18 files changed, 476 insertions(+), 34 deletions(-) create mode 100644 data/resources/ui/components-avatar.ui create mode 100644 src/components/avatar.rs create mode 100644 src/session/avatar.rs diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index b38a6083..4e2683c3 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -23,6 +23,7 @@ ui/pill.ui ui/spinner-button.ui ui/in-app-notification.ui + ui/components-avatar.ui style.css icons/scalable/actions/send-symbolic.svg icons/scalable/status/welcome.svg diff --git a/data/resources/ui/components-avatar.ui b/data/resources/ui/components-avatar.ui new file mode 100644 index 00000000..a692dd5a --- /dev/null +++ b/data/resources/ui/components-avatar.ui @@ -0,0 +1,35 @@ + + + + + diff --git a/data/resources/ui/content-invite.ui b/data/resources/ui/content-invite.ui index 0cb680c1..3e59ff07 100644 --- a/data/resources/ui/content-invite.ui +++ b/data/resources/ui/content-invite.ui @@ -46,10 +46,11 @@ Invite - - True + 150 - + + ContentInvite + diff --git a/data/resources/ui/content-message-row.ui b/data/resources/ui/content-message-row.ui index 87a7e300..bba2bfbf 100644 --- a/data/resources/ui/content-message-row.ui +++ b/data/resources/ui/content-message-row.ui @@ -5,11 +5,9 @@ 10 - - True + 36 start - @@ -55,3 +53,4 @@ + diff --git a/data/resources/ui/pill.ui b/data/resources/ui/pill.ui index 8a51ec22..0f57182a 100644 --- a/data/resources/ui/pill.ui +++ b/data/resources/ui/pill.ui @@ -10,10 +10,8 @@ 6 - - True + 24 - diff --git a/data/resources/ui/sidebar-room-row.ui b/data/resources/ui/sidebar-room-row.ui index d5060291..68410ddb 100644 --- a/data/resources/ui/sidebar-room-row.ui +++ b/data/resources/ui/sidebar-room-row.ui @@ -5,10 +5,8 @@ 12 - - True + 24 - @@ -31,3 +29,4 @@ + diff --git a/po/POTFILES.in b/po/POTFILES.in index 2d43d112..28cd433e 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/components-avatar.ui data/resources/ui/content-divider-row.ui data/resources/ui/content-item-row-menu.ui data/resources/ui/content-item.ui @@ -30,6 +31,7 @@ data/resources/ui/window.ui # Rust files src/application.rs +src/components/avatar.rs src/components/context_menu_bin.rs src/components/label_with_widgets.rs src/components/in_app_notification.rs diff --git a/src/components/avatar.rs b/src/components/avatar.rs new file mode 100644 index 00000000..8a4f78af --- /dev/null +++ b/src/components/avatar.rs @@ -0,0 +1,155 @@ +use adw::subclass::prelude::*; +use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate}; + +use crate::session::{Room, User}; + +mod imp { + use super::*; + use glib::subclass::InitializingObject; + use std::cell::RefCell; + + #[derive(Debug, Default, CompositeTemplate)] + #[template(resource = "/org/gnome/FractalNext/components-avatar.ui")] + pub struct Avatar { + /// A `Room` or `User` + pub item: RefCell>, + #[template_child] + pub avatar: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for Avatar { + const NAME: &'static str = "ComponentsAvatar"; + type Type = super::Avatar; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for Avatar { + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_object( + "item", + "Item", + "The Room or User of this Avatar", + glib::Object::static_type(), + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + glib::ParamSpec::new_int( + "size", + "Size", + "The size of the Avatar", + -1, + i32::MAX, + -1, + glib::ParamFlags::READWRITE, + ), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "item" => obj.set_item(value.get().unwrap()), + "size" => self.avatar.set_size(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "item" => obj.item().to_value(), + "size" => self.avatar.size().to_value(), + _ => unimplemented!(), + } + } + + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + obj.connect_map(clone!(@weak obj => move |_| { + obj.request_custom_avatar(); + })); + } + } + + impl WidgetImpl for Avatar {} + + impl BinImpl for Avatar {} +} + +glib::wrapper! { + pub struct Avatar(ObjectSubclass) + @extends gtk::Widget, adw::Bin, @implements gtk::Accessible; +} + +/// A widget displaying an `Avatar` for a `Room` or `User` +impl Avatar { + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create Avatar") + } + + pub fn set_room(&self, room: Option) { + self.set_item(room.map(glib::object::Cast::upcast)); + } + + pub fn room(&self) -> Option { + self.item().and_then(|item| item.downcast().ok()) + } + + pub fn set_user(&self, user: Option) { + self.set_item(user.map(glib::object::Cast::upcast)); + } + + pub fn user(&self) -> Option { + self.item().and_then(|item| item.downcast().ok()) + } + + fn set_item(&self, item: Option) { + let priv_ = imp::Avatar::from_instance(self); + + if *priv_.item.borrow() == item { + return; + } + + priv_.item.replace(item); + + if self.is_mapped() { + self.request_custom_avatar(); + } + + self.notify("item"); + } + + fn item(&self) -> Option { + let priv_ = imp::Avatar::from_instance(self); + priv_.item.borrow().clone() + } + + fn request_custom_avatar(&self) { + let priv_ = imp::Avatar::from_instance(self); + if let Some(item) = &*priv_.item.borrow() { + if let Some(room) = item.downcast_ref::() { + room.avatar().set_needed(true); + } else if let Some(user) = item.downcast_ref::() { + user.avatar().set_needed(true); + } + } + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index bf600ffe..7296e89d 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,9 +1,11 @@ +mod avatar; mod context_menu_bin; mod in_app_notification; mod label_with_widgets; mod pill; mod spinner_button; +pub use self::avatar::Avatar; pub use self::context_menu_bin::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl}; pub use self::in_app_notification::InAppNotification; pub use self::label_with_widgets::LabelWithWidgets; diff --git a/src/components/pill.rs b/src/components/pill.rs index 7ff1668c..be2333f9 100644 --- a/src/components/pill.rs +++ b/src/components/pill.rs @@ -1,3 +1,4 @@ +use crate::components::Avatar; use adw::subclass::prelude::*; use gtk::prelude::*; use gtk::subclass::prelude::*; @@ -20,7 +21,7 @@ mod imp { #[template_child] pub display_name: TemplateChild, #[template_child] - pub avatar: TemplateChild, + pub avatar: TemplateChild, pub bindings: RefCell>, } @@ -124,6 +125,7 @@ impl Pill { priv_.bindings.borrow_mut().push(display_name_binding); } + priv_.avatar.set_user(user.clone()); priv_.user.replace(user); self.notify("user"); @@ -155,6 +157,7 @@ impl Pill { priv_.bindings.borrow_mut().push(display_name_binding); } + priv_.avatar.set_room(room.clone()); priv_.room.replace(room); self.notify("room"); diff --git a/src/meson.build b/src/meson.build index bfd51e20..779b0324 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,6 +20,7 @@ run_command( sources = files( 'application.rs', + 'components/avatar.rs', 'components/context_menu_bin.rs', 'components/label_with_widgets.rs', 'components/mod.rs', @@ -33,6 +34,7 @@ sources = files( 'login.rs', 'secret.rs', 'utils.rs', + 'session/avatar.rs', 'session/event_source_dialog.rs', 'session/user.rs', 'session/mod.rs', diff --git a/src/session/avatar.rs b/src/session/avatar.rs new file mode 100644 index 00000000..0cd91962 --- /dev/null +++ b/src/session/avatar.rs @@ -0,0 +1,211 @@ +use gtk::{gdk, gdk_pixbuf::Pixbuf, gio, glib, glib::clone, prelude::*, subclass::prelude::*}; + +use log::error; +use matrix_sdk::{ + identifiers::MxcUri, + media::{MediaFormat, MediaRequest, MediaType}, +}; + +use crate::utils::do_async; + +use crate::session::Session; + +mod imp { + use super::*; + use once_cell::sync::{Lazy, OnceCell}; + use std::cell::{Cell, RefCell}; + + #[derive(Debug, Default)] + pub struct Avatar { + pub image: RefCell>, + pub needed: Cell, + pub url: RefCell>, + pub session: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for Avatar { + const NAME: &'static str = "Avatar"; + type Type = super::Avatar; + type ParentType = glib::Object; + } + + impl ObjectImpl for Avatar { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_object( + "image", + "Image", + "The user defined image if any", + gdk::Paintable::static_type(), + glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + glib::ParamSpec::new_object( + "needed", + "Needed", + "Whether the user defnied image should be loaded or it's not needed", + gdk::Paintable::static_type(), + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + glib::ParamSpec::new_string( + "url", + "Url", + "The url of the Avatar", + None, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + 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() { + "needed" => obj.set_needed(value.get().unwrap()), + "url" => obj.set_url(value.get::>().unwrap().map(Into::into)), + "session" => self.session.set(value.get().unwrap()).unwrap(), + _ => unimplemented!(), + } + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "image" => obj.image().to_value(), + "needed" => obj.needed().to_value(), + "url" => obj.url().map_or_else( + || { + let none: Option<&str> = None; + none.to_value() + }, + |url| url.as_str().to_value(), + ), + _ => unimplemented!(), + } + } + } +} + +glib::wrapper! { + pub struct Avatar(ObjectSubclass); +} + +/// This an object that holds information about a Users or Rooms `Avatar` +impl Avatar { + pub fn new(session: &Session, url: Option) -> Self { + glib::Object::new(&[ + ("session", session), + ("url", &url.map(|url| url.to_string())), + ]) + .expect("Failed to create Avatar") + } + + fn session(&self) -> &Session { + let priv_ = imp::Avatar::from_instance(self); + priv_.session.get().unwrap() + } + + pub fn image(&self) -> Option { + let priv_ = imp::Avatar::from_instance(self); + priv_.image.borrow().clone() + } + + fn set_image_data(&self, data: Option>) { + let priv_ = imp::Avatar::from_instance(self); + + let image = if let Some(data) = data { + let stream = gio::MemoryInputStream::from_bytes(&glib::Bytes::from(&data)); + Pixbuf::from_stream(&stream, gio::NONE_CANCELLABLE) + .ok() + .and_then(|pixbuf| Some(gdk::Texture::for_pixbuf(&pixbuf).upcast())) + } else { + None + }; + priv_.image.replace(image); + self.notify("image"); + } + + fn load(&self) { + // Don't do anything here if we don't need the avatar + if !self.needed() { + return; + } + + if let Some(url) = self.url() { + let client = self.session().client().clone(); + let request = MediaRequest { + media_type: MediaType::Uri(url), + format: MediaFormat::File, + }; + do_async( + glib::PRIORITY_LOW, + async move { client.get_media_content(&request, true).await }, + clone!(@weak self as obj => move |result| async move { + // FIXME: We should retry if the request failed + match result { + Ok(data) => obj.set_image_data(Some(data)), + Err(error) => error!("Couldn't fetch avatar: {}", error), + }; + }), + ); + } + } + + pub fn set_needed(&self, needed: bool) { + let priv_ = imp::Avatar::from_instance(self); + if self.needed() == needed { + return; + } + + priv_.needed.set(needed); + + if needed { + self.load(); + } + + self.notify("needed"); + } + + pub fn needed(&self) -> bool { + let priv_ = imp::Avatar::from_instance(self); + priv_.needed.get() + } + + pub fn set_url(&self, url: Option) { + let priv_ = imp::Avatar::from_instance(self); + + if priv_.url.borrow().as_ref() == url.as_ref() { + return; + } + + let has_url = url.is_some(); + priv_.url.replace(url); + + if has_url { + self.load(); + } else { + self.set_image_data(None); + } + + self.notify("url"); + } + + pub fn url(&self) -> Option { + let priv_ = imp::Avatar::from_instance(self); + priv_.url.borrow().to_owned() + } +} diff --git a/src/session/content/invite.rs b/src/session/content/invite.rs index 557aff33..2d967d38 100644 --- a/src/session/content/invite.rs +++ b/src/session/content/invite.rs @@ -1,5 +1,5 @@ use crate::{ - components::{LabelWithWidgets, Pill, SpinnerButton}, + components::{Avatar, LabelWithWidgets, Pill, SpinnerButton}, session::{categories::CategoryType, room::Room}, }; use adw::subclass::prelude::*; @@ -40,6 +40,7 @@ mod imp { Pill::static_type(); SpinnerButton::static_type(); LabelWithWidgets::static_type(); + Avatar::static_type(); Self::bind_template(klass); klass.set_accessible_role(gtk::AccessibleRole::Group); diff --git a/src/session/content/message_row.rs b/src/session/content/message_row.rs index 82b41b42..1a4c0c62 100644 --- a/src/session/content/message_row.rs +++ b/src/session/content/message_row.rs @@ -1,3 +1,4 @@ +use crate::components::Avatar; use adw::{prelude::*, subclass::prelude::*}; use gtk::{ gio, glib, glib::clone, glib::signal::SignalHandlerId, prelude::*, subclass::prelude::*, @@ -28,7 +29,7 @@ mod imp { #[template(resource = "/org/gnome/FractalNext/content-message-row.ui")] pub struct MessageRow { #[template_child] - pub avatar: TemplateChild, + pub avatar: TemplateChild, #[template_child] pub header: TemplateChild, #[template_child] @@ -49,6 +50,7 @@ mod imp { type ParentType = adw::Bin; fn class_init(klass: &mut Self::Class) { + Avatar::static_type(); Self::bind_template(klass); } @@ -147,7 +149,8 @@ impl MessageRow { } } - //TODO: bind the user's avatar to the message row + priv_.avatar.set_user(Some(event.sender().clone())); + let display_name_binding = event .sender() .bind_property("display-name", &priv_.display_name.get(), "label") diff --git a/src/session/mod.rs b/src/session/mod.rs index e1906d14..72c25473 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -1,3 +1,4 @@ +mod avatar; mod categories; mod content; mod event_source_dialog; @@ -6,6 +7,7 @@ mod room_list; mod sidebar; mod user; +pub use self::avatar::Avatar; use self::categories::Categories; use self::content::Content; pub use self::room::Room; diff --git a/src/session/room/room.rs b/src/session/room/room.rs index c99b38b5..05d2da4e 100644 --- a/src/session/room/room.rs +++ b/src/session/room/room.rs @@ -1,5 +1,5 @@ use gettextrs::gettext; -use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; +use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; use log::{debug, error, warn}; use matrix_sdk::{ api::r0::sync::sync_events::InvitedRoom, @@ -29,7 +29,7 @@ use crate::event_from_sync_event; use crate::session::{ categories::CategoryType, room::{HighlightFlags, Timeline}, - Session, User, + Avatar, Session, User, }; use crate::utils::do_async; use crate::Error; @@ -47,7 +47,7 @@ mod imp { pub matrix_room: RefCell>, pub session: OnceCell, pub name: RefCell>, - pub avatar: RefCell>, + pub avatar: OnceCell, pub category: Cell, pub timeline: OnceCell, pub room_members: RefCell>, @@ -97,8 +97,8 @@ mod imp { glib::ParamSpec::new_object( "avatar", "Avatar", - "The url of the avatar of this room", - gio::LoadableIcon::static_type(), + "The Avatar of this room", + Avatar::static_type(), glib::ParamFlags::READABLE, ), glib::ParamSpec::new_object( @@ -161,7 +161,7 @@ mod imp { } "room-id" => self .room_id - .set(RoomId::try_from(value.get::().unwrap()).unwrap()) + .set(RoomId::try_from(value.get::<&str>().unwrap()).unwrap()) .unwrap(), _ => unimplemented!(), } @@ -175,7 +175,7 @@ mod imp { "session" => obj.session().to_value(), "inviter" => obj.inviter().to_value(), "display-name" => obj.display_name().to_value(), - "avatar" => self.avatar.borrow().to_value(), + "avatar" => obj.avatar().to_value(), "timeline" => self.timeline.get().unwrap().to_value(), "category" => obj.category().to_value(), "highlight" => obj.highlight().to_value(), @@ -200,6 +200,9 @@ mod imp { obj.set_matrix_room(obj.session().client().get_room(obj.room_id()).unwrap()); self.timeline.set(Timeline::new(obj)).unwrap(); + self.avatar + .set(Avatar::new(obj.session(), obj.matrix_room().avatar_url())) + .unwrap(); } } } @@ -461,6 +464,11 @@ impl Room { ); } + pub fn avatar(&self) -> &Avatar { + let priv_ = imp::Room::from_instance(&self); + priv_.avatar.get().unwrap() + } + pub fn topic(&self) -> Option { self.matrix_room() .topic() @@ -533,6 +541,9 @@ impl Room { AnyRoomEvent::State(AnyStateEvent::RoomMember(ref event)) => { self.update_member_for_member_event(event) } + AnyRoomEvent::State(AnyStateEvent::RoomAvatar(event)) => { + self.avatar().set_url(event.content.url.to_owned()); + } AnyRoomEvent::State(AnyStateEvent::RoomName(_)) => { // FIXME: this doesn't take in account changes in the calculated name self.load_display_name() diff --git a/src/session/sidebar/room_row.rs b/src/session/sidebar/room_row.rs index 6183564c..d592189e 100644 --- a/src/session/sidebar/room_row.rs +++ b/src/session/sidebar/room_row.rs @@ -1,3 +1,4 @@ +use crate::components::Avatar; use adw::subclass::prelude::BinImpl; use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate}; @@ -16,7 +17,7 @@ mod imp { pub bindings: RefCell>, pub signal_handler: RefCell>, #[template_child] - pub avatar: TemplateChild, + pub avatar: TemplateChild, #[template_child] pub display_name: TemplateChild, #[template_child] @@ -30,6 +31,7 @@ mod imp { type ParentType = adw::Bin; fn class_init(klass: &mut Self::Class) { + Avatar::static_type(); Self::bind_template(klass); } @@ -115,8 +117,6 @@ impl RoomRow { } if let Some(ref room) = room { - // TODO: set custom avatar https://gitlab.gnome.org/exalm/libadwaita/-/issues/29 - let display_name_binding = room .bind_property("display-name", &priv_.display_name.get(), "label") .flags(glib::BindingFlags::SYNC_CREATE) @@ -158,7 +158,7 @@ impl RoomRow { notification_count_vislbe_binding, ]); } - + priv_.avatar.set_room(room.clone()); priv_.room.replace(room); self.notify("room"); } diff --git a/src/session/user.rs b/src/session/user.rs index 2cc4af30..82cfedcc 100644 --- a/src/session/user.rs +++ b/src/session/user.rs @@ -1,4 +1,4 @@ -use gtk::{gio, glib, prelude::*, subclass::prelude::*}; +use gtk::{glib, prelude::*, subclass::prelude::*}; use crate::session::Session; use matrix_sdk::{ @@ -7,6 +7,8 @@ use matrix_sdk::{ RoomMember, }; +use crate::session::Avatar; + mod imp { use super::*; use once_cell::sync::{Lazy, OnceCell}; @@ -16,8 +18,8 @@ mod imp { pub struct User { pub user_id: OnceCell, pub display_name: RefCell>, - pub avatar: RefCell>, pub session: OnceCell, + pub avatar: OnceCell, } #[glib::object_subclass] @@ -49,7 +51,7 @@ mod imp { "avatar", "Avatar", "The avatar of this user", - gio::LoadableIcon::static_type(), + Avatar::static_type(), glib::ParamFlags::READABLE, ), glib::ParamSpec::new_object( @@ -86,11 +88,18 @@ mod imp { match pspec.name() { "display-name" => obj.display_name().to_value(), "user-id" => self.user_id.get().to_value(), - "avatar" => self.avatar.borrow().to_value(), "session" => obj.session().to_value(), + "avatar" => obj.avatar().to_value(), _ => unimplemented!(), } } + + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + let avatar = Avatar::new(obj.session(), None); + self.avatar.set(avatar).unwrap(); + } } } @@ -131,11 +140,16 @@ impl User { } } + pub fn avatar(&self) -> &Avatar { + let priv_ = imp::User::from_instance(&self); + priv_.avatar.get().unwrap() + } + /// Update the user based on the the room member state event - //TODO: create the GLoadableIcon and set `avatar` pub fn update_from_room_member(&self, member: &RoomMember) { let changed = { let priv_ = imp::User::from_instance(&self); + let user_id = priv_.user_id.get().unwrap(); if member.user_id().as_str() != user_id { return; @@ -143,6 +157,7 @@ impl User { //let content = event.content; let display_name = member.display_name().map(|name| name.to_owned()); + self.avatar().set_url(member.avatar_url().cloned()); let mut current_display_name = priv_.display_name.borrow_mut(); if *current_display_name != display_name { @@ -159,7 +174,6 @@ impl User { } /// Update the user based on the the room member state event - //TODO: create the GLoadableIcon and set `avatar` pub fn update_from_member_event(&self, event: &StateEvent) { let changed = { let priv_ = imp::User::from_instance(&self); @@ -178,6 +192,8 @@ impl User { .map(|i| i.display_name.to_owned()) }; + self.avatar().set_url(event.content.avatar_url.to_owned()); + let mut current_display_name = priv_.display_name.borrow_mut(); if *current_display_name != display_name { *current_display_name = display_name; @@ -193,7 +209,6 @@ impl User { } /// Update the user based on the the stripped room member state event - //TODO: create the GLoadableIcon and set `avatar` pub fn update_from_stripped_member_event( &self, event: &StrippedStateEvent, @@ -215,6 +230,8 @@ impl User { .map(|i| i.display_name.to_owned()) }; + self.avatar().set_url(event.content.avatar_url.to_owned()); + let mut current_display_name = priv_.display_name.borrow_mut(); if *current_display_name != display_name { *current_display_name = display_name;