Add support for user-defined avatars
Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/785
This commit is contained in:
parent
a4d7f87bac
commit
2adec1644b
18 changed files with 476 additions and 34 deletions
|
@ -23,6 +23,7 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks" alias="pill.ui">ui/pill.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="spinner-button.ui">ui/spinner-button.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="in-app-notification.ui">ui/in-app-notification.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="components-avatar.ui">ui/components-avatar.ui</file>
|
||||
<file compressed="true">style.css</file>
|
||||
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
|
||||
|
|
35
data/resources/ui/components-avatar.ui
Normal file
35
data/resources/ui/components-avatar.ui
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="ComponentsAvatar" parent="AdwBin">
|
||||
<property name="child">
|
||||
<object class="AdwAvatar" id="avatar">
|
||||
<property name="show-initials">True</property>
|
||||
<binding name="text">
|
||||
<lookup name="display-name" type="Room">
|
||||
<lookup name="item">ComponentsAvatar</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<binding name="text">
|
||||
<lookup name="display-name" type="User">
|
||||
<lookup name="item">ComponentsAvatar</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<binding name="custom-image">
|
||||
<lookup name="image" type="Avatar">
|
||||
<lookup name="avatar" type="Room">
|
||||
<lookup name="item">ComponentsAvatar</lookup>
|
||||
</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<binding name="custom-image">
|
||||
<lookup name="image" type="Avatar">
|
||||
<lookup name="avatar" type="User">
|
||||
<lookup name="item">ComponentsAvatar</lookup>
|
||||
</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
@ -46,10 +46,11 @@
|
|||
<property name="label" translatable="yes">Invite</property>
|
||||
</accessibility>
|
||||
<child>
|
||||
<object class="AdwAvatar">
|
||||
<property name="show-initials">True</property>
|
||||
<object class="ComponentsAvatar">
|
||||
<property name="size">150</property>
|
||||
<property name="text" bind-source="display_name" bind-property="label" bind-flags="sync-create"/>
|
||||
<binding name="item">
|
||||
<lookup name="room">ContentInvite</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
<object class="GtkBox">
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="AdwAvatar" id="avatar">
|
||||
<property name="show-initials">True</property>
|
||||
<object class="ComponentsAvatar" id="avatar">
|
||||
<property name="size">36</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="text" bind-source="display_name" bind-property="label" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -55,3 +53,4 @@
|
|||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -10,10 +10,8 @@
|
|||
<object class="GtkBox">
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="AdwAvatar" id="avatar">
|
||||
<property name="show-initials">True</property>
|
||||
<object class="ComponentsAvatar" id="avatar">
|
||||
<property name="size">24</property>
|
||||
<property name="text" bind-source="display_name" bind-property="label" bind-flags="sync-create" />
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
<object class="GtkBox">
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="AdwAvatar" id="avatar">
|
||||
<property name="show-initials">True</property>
|
||||
<object class="ComponentsAvatar" id="avatar">
|
||||
<property name="size">24</property>
|
||||
<property name="text" bind-source="display_name" bind-property="label" bind-flags="sync-create" />
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -31,3 +29,4 @@
|
|||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
155
src/components/avatar.rs
Normal file
155
src/components/avatar.rs
Normal file
|
@ -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<Option<glib::Object>>,
|
||||
#[template_child]
|
||||
pub avatar: TemplateChild<adw::Avatar>,
|
||||
}
|
||||
|
||||
#[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<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
"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<imp::Avatar>)
|
||||
@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<Room>) {
|
||||
self.set_item(room.map(glib::object::Cast::upcast));
|
||||
}
|
||||
|
||||
pub fn room(&self) -> Option<Room> {
|
||||
self.item().and_then(|item| item.downcast().ok())
|
||||
}
|
||||
|
||||
pub fn set_user(&self, user: Option<User>) {
|
||||
self.set_item(user.map(glib::object::Cast::upcast));
|
||||
}
|
||||
|
||||
pub fn user(&self) -> Option<User> {
|
||||
self.item().and_then(|item| item.downcast().ok())
|
||||
}
|
||||
|
||||
fn set_item(&self, item: Option<glib::Object>) {
|
||||
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<glib::Object> {
|
||||
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>() {
|
||||
room.avatar().set_needed(true);
|
||||
} else if let Some(user) = item.downcast_ref::<User>() {
|
||||
user.avatar().set_needed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<gtk::Label>,
|
||||
#[template_child]
|
||||
pub avatar: TemplateChild<adw::Avatar>,
|
||||
pub avatar: TemplateChild<Avatar>,
|
||||
pub bindings: RefCell<Vec<glib::Binding>>,
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
|
|
@ -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',
|
||||
|
|
211
src/session/avatar.rs
Normal file
211
src/session/avatar.rs
Normal file
|
@ -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<Option<gdk::Paintable>>,
|
||||
pub needed: Cell<bool>,
|
||||
pub url: RefCell<Option<MxcUri>>,
|
||||
pub session: OnceCell<Session>,
|
||||
}
|
||||
|
||||
#[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<Vec<glib::ParamSpec>> = 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::<Option<&str>>().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<imp::Avatar>);
|
||||
}
|
||||
|
||||
/// This an object that holds information about a Users or Rooms `Avatar`
|
||||
impl Avatar {
|
||||
pub fn new(session: &Session, url: Option<MxcUri>) -> 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<gdk::Paintable> {
|
||||
let priv_ = imp::Avatar::from_instance(self);
|
||||
priv_.image.borrow().clone()
|
||||
}
|
||||
|
||||
fn set_image_data(&self, data: Option<Vec<u8>>) {
|
||||
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<MxcUri>) {
|
||||
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<MxcUri> {
|
||||
let priv_ = imp::Avatar::from_instance(self);
|
||||
priv_.url.borrow().to_owned()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<adw::Avatar>,
|
||||
pub avatar: TemplateChild<Avatar>,
|
||||
#[template_child]
|
||||
pub header: TemplateChild<gtk::Box>,
|
||||
#[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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Option<MatrixRoom>>,
|
||||
pub session: OnceCell<Session>,
|
||||
pub name: RefCell<Option<String>>,
|
||||
pub avatar: RefCell<Option<gio::LoadableIcon>>,
|
||||
pub avatar: OnceCell<Avatar>,
|
||||
pub category: Cell<CategoryType>,
|
||||
pub timeline: OnceCell<Timeline>,
|
||||
pub room_members: RefCell<HashMap<UserId, User>>,
|
||||
|
@ -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::<String>().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<String> {
|
||||
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()
|
||||
|
|
|
@ -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<Vec<glib::Binding>>,
|
||||
pub signal_handler: RefCell<Option<SignalHandlerId>>,
|
||||
#[template_child]
|
||||
pub avatar: TemplateChild<adw::Avatar>,
|
||||
pub avatar: TemplateChild<Avatar>,
|
||||
#[template_child]
|
||||
pub display_name: TemplateChild<gtk::Label>,
|
||||
#[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");
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
pub display_name: RefCell<Option<String>>,
|
||||
pub avatar: RefCell<Option<gio::LoadableIcon>>,
|
||||
pub session: OnceCell<Session>,
|
||||
pub avatar: OnceCell<Avatar>,
|
||||
}
|
||||
|
||||
#[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<MemberEventContent>) {
|
||||
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<MemberEventContent>,
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue