From cd1911fce89be5856ffc9e27d33de9c9e7773ad9 Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Mon, 6 Nov 2023 19:47:31 +0100 Subject: [PATCH] room-history: Add popover to show senders of a reaction The popover will show for a reaction the list of people that have sent it and when they did. * Add a `ReactionPopover` object for the popover * Add a `ReactionSenderRow` object for senders as rows of the popover * Add a `MemberReactionSender` object to represent a room member that sent a reaction --- po/POTFILES.in | 1 + .../content/room_history/message_row/mod.rs | 3 +- .../room_history/message_row/reaction.rs | 117 --------- .../reaction/member_reaction_sender.rs | 155 ++++++++++++ .../room_history/message_row/reaction/mod.rs | 238 ++++++++++++++++++ .../{reaction.ui => reaction/mod.ui} | 15 ++ .../message_row/reaction/reaction_popover.rs | 108 ++++++++ .../message_row/reaction/reaction_popover.ui | 42 ++++ .../reaction/reaction_sender_row.rs | 105 ++++++++ .../reaction/reaction_sender_row.ui | 62 +++++ .../room_history/message_row/reaction_list.rs | 18 +- src/ui-resources.gresource.xml | 5 +- 12 files changed, 744 insertions(+), 125 deletions(-) delete mode 100644 src/session/view/content/room_history/message_row/reaction.rs create mode 100644 src/session/view/content/room_history/message_row/reaction/member_reaction_sender.rs create mode 100644 src/session/view/content/room_history/message_row/reaction/mod.rs rename src/session/view/content/room_history/message_row/{reaction.ui => reaction/mod.ui} (63%) create mode 100644 src/session/view/content/room_history/message_row/reaction/reaction_popover.rs create mode 100644 src/session/view/content/room_history/message_row/reaction/reaction_popover.ui create mode 100644 src/session/view/content/room_history/message_row/reaction/reaction_sender_row.rs create mode 100644 src/session/view/content/room_history/message_row/reaction/reaction_sender_row.ui diff --git a/po/POTFILES.in b/po/POTFILES.in index 9982d5a4..4e55ac46 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -85,6 +85,7 @@ src/session/view/content/room_history/message_row/file.ui src/session/view/content/room_history/message_row/location.rs src/session/view/content/room_history/message_row/media.rs src/session/view/content/room_history/message_row/mod.ui +src/session/view/content/room_history/message_row/reaction/member_reaction_sender.rs src/session/view/content/room_history/message_toolbar/attachment_dialog.ui src/session/view/content/room_history/message_toolbar/mod.rs src/session/view/content/room_history/message_toolbar/mod.ui diff --git a/src/session/view/content/room_history/message_row/mod.rs b/src/session/view/content/room_history/message_row/mod.rs index 65eb9b83..a255e7da 100644 --- a/src/session/view/content/room_history/message_row/mod.rs +++ b/src/session/view/content/room_history/message_row/mod.rs @@ -214,7 +214,8 @@ impl MessageRow { ))); self.update_content(&event); - imp.reactions.set_reaction_list(event.reactions()); + imp.reactions + .set_reaction_list(&event.room().get_or_create_members(), event.reactions()); imp.read_receipts .set_list(&event.room(), event.read_receipts()); imp.event.replace(Some(event)); diff --git a/src/session/view/content/room_history/message_row/reaction.rs b/src/session/view/content/room_history/message_row/reaction.rs deleted file mode 100644 index a04ccb8a..00000000 --- a/src/session/view/content/room_history/message_row/reaction.rs +++ /dev/null @@ -1,117 +0,0 @@ -use adw::subclass::prelude::*; -use gtk::{glib, prelude::*, CompositeTemplate}; - -use crate::{session::model::ReactionGroup, utils::EMOJI_REGEX}; - -mod imp { - use glib::subclass::InitializingObject; - use once_cell::{sync::Lazy, unsync::OnceCell}; - - use super::*; - - #[derive(Debug, Default, CompositeTemplate)] - #[template( - resource = "/org/gnome/Fractal/ui/session/view/content/room_history/message_row/reaction.ui" - )] - pub struct MessageReaction { - /// The reaction group to display. - pub group: OnceCell, - #[template_child] - pub button: TemplateChild, - #[template_child] - pub reaction_key: TemplateChild, - #[template_child] - pub reaction_count: TemplateChild, - } - - #[glib::object_subclass] - impl ObjectSubclass for MessageReaction { - const NAME: &'static str = "ContentMessageReaction"; - type Type = super::MessageReaction; - type ParentType = gtk::FlowBoxChild; - - fn class_init(klass: &mut Self::Class) { - Self::bind_template(klass); - } - - fn instance_init(obj: &InitializingObject) { - obj.init_template(); - } - } - - impl ObjectImpl for MessageReaction { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("group") - .construct_only() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "group" => { - self.obj().set_group(value.get().unwrap()); - } - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "group" => self.obj().group().to_value(), - _ => unimplemented!(), - } - } - } - - impl WidgetImpl for MessageReaction {} - - impl FlowBoxChildImpl for MessageReaction {} -} - -glib::wrapper! { - /// A widget displaying the reactions of a message. - pub struct MessageReaction(ObjectSubclass) - @extends gtk::Widget, gtk::FlowBoxChild, @implements gtk::Accessible; -} - -impl MessageReaction { - pub fn new(reaction_group: ReactionGroup) -> Self { - glib::Object::builder() - .property("group", &reaction_group) - .build() - } - - /// The reaction group to display. - pub fn group(&self) -> Option<&ReactionGroup> { - self.imp().group.get() - } - - /// Set the reaction group to display. - fn set_group(&self, group: ReactionGroup) { - let imp = self.imp(); - let key = group.key(); - imp.reaction_key.set_label(key); - - if EMOJI_REGEX.is_match(key) { - imp.reaction_key.add_css_class("emoji"); - } else { - imp.reaction_key.remove_css_class("emoji"); - } - - imp.button.set_action_target_value(Some(&key.to_variant())); - group - .bind_property("has-user", &*imp.button, "active") - .sync_create() - .build(); - group - .bind_property("count", &*imp.reaction_count, "label") - .sync_create() - .build(); - - imp.group.set(group).unwrap(); - } -} diff --git a/src/session/view/content/room_history/message_row/reaction/member_reaction_sender.rs b/src/session/view/content/room_history/message_row/reaction/member_reaction_sender.rs new file mode 100644 index 00000000..4a389e56 --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/member_reaction_sender.rs @@ -0,0 +1,155 @@ +use adw::subclass::prelude::*; +use gettextrs::gettext; +use gtk::{glib, prelude::*}; + +use crate::session::model::Member; + +mod imp { + use std::cell::Cell; + + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Debug, Default)] + pub struct MemberReactionSender { + /// The room member of this reaction sender. + pub member: glib::WeakRef, + /// The timestamp of when the reaction was sent, in seconds since Unix + /// Epoch, if any. + /// + /// A value of 0 means no timestamp. + pub timestamp: Cell, + } + + #[glib::object_subclass] + impl ObjectSubclass for MemberReactionSender { + const NAME: &'static str = "ContentMemberReactionSender"; + type Type = super::MemberReactionSender; + } + + impl ObjectImpl for MemberReactionSender { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecObject::builder::("member") + .construct_only() + .build(), + glib::ParamSpecUInt64::builder("timestamp") + .construct_only() + .build(), + glib::ParamSpecString::builder("datetime") + .read_only() + .build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + let obj = self.obj(); + + match pspec.name() { + "member" => obj.member().to_value(), + "timestamp" => obj.timestamp().to_value(), + "datetime" => obj.datetime().to_value(), + _ => unimplemented!(), + } + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let obj = self.obj(); + + match pspec.name() { + "member" => obj.set_member(value.get::>().unwrap().as_ref()), + "timestamp" => obj.set_timestamp(value.get().unwrap()), + _ => unimplemented!(), + } + } + } +} + +glib::wrapper! { + /// A reaction sender's room member. + pub struct MemberReactionSender(ObjectSubclass); +} + +impl MemberReactionSender { + /// Constructs a new `MemberReactionSender` with the given member. + pub fn new(member: &Member, timestamp: u64) -> Self { + glib::Object::builder() + .property("member", member) + .property("timestamp", timestamp) + .build() + } + + /// The room member of this reaction sender. + pub fn member(&self) -> Option { + self.imp().member.upgrade() + } + + /// Set the room member of this reaction sender. + fn set_member(&self, member: Option<&Member>) { + let Some(member) = member else { + // Ignore if there is no member. + return; + }; + + self.imp().member.set(Some(member)); + self.notify("member"); + } + + /// The timestamp of when the reaction was sent, in seconds since Unix + /// Epoch, if any. + /// + /// A value of 0 means no timestamp. + pub fn timestamp(&self) -> u64 { + self.imp().timestamp.get() + } + + /// Set the timestamp of when the reaction was sent. + pub fn set_timestamp(&self, ts: u64) { + if self.timestamp() == ts { + return; + } + + self.imp().timestamp.set(ts); + self.notify("timestamp"); + } + + /// The formatted date and time of when the reaction was sent. + pub fn datetime(&self) -> String { + let timestamp = self.timestamp(); + + if timestamp == 0 { + // No timestamp. + return String::new(); + } + + let datetime = glib::DateTime::from_unix_utc(timestamp as i64) + .and_then(|t| t.to_local()) + .unwrap(); + + // FIXME: Use system setting. + let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase(); + let is_12h_format = local_time.ends_with("am") || local_time.ends_with("pm"); + + let format = if is_12h_format { + // Translators: this is a date and a time in 12h format. + // For example, "May 5 at 1:20 PM". + // Do not change the time format as it will follow the system settings. + // Please use `-` before specifiers that add spaces on single digits. + // See `man strftime` or the documentation of g_date_time_format for the available specifiers: + gettext("%B %-e at %-l∶%M %p") + } else { + // Translators: this is a date and a time in 24h format. + // For example, "May 5 at 13:20". + // Do not change the time format as it will follow the system settings. + // Please use `-` before specifiers that add spaces on single digits. + // See `man strftime` or the documentation of g_date_time_format for the available specifiers: + gettext("%B %-e at %-k∶%M %p") + }; + datetime.format(&format).unwrap().to_string() + } +} diff --git a/src/session/view/content/room_history/message_row/reaction/mod.rs b/src/session/view/content/room_history/message_row/reaction/mod.rs new file mode 100644 index 00000000..70e19847 --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/mod.rs @@ -0,0 +1,238 @@ +use adw::subclass::prelude::*; +use gtk::{gio, glib, glib::clone, prelude::*, CompositeTemplate}; +use matrix_sdk_ui::timeline::ReactionSenderData as SdkReactionSenderData; + +mod member_reaction_sender; +mod reaction_popover; +mod reaction_sender_row; + +use self::{ + member_reaction_sender::MemberReactionSender, reaction_popover::ReactionPopover, + reaction_sender_row::ReactionSenderRow, +}; +use crate::{ + session::model::{MemberList, ReactionGroup}, + utils::{BoundObjectWeakRef, EMOJI_REGEX}, +}; + +mod imp { + use std::cell::RefCell; + + use glib::subclass::InitializingObject; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Debug, CompositeTemplate)] + #[template( + resource = "/org/gnome/Fractal/ui/session/view/content/room_history/message_row/reaction/mod.ui" + )] + pub struct MessageReaction { + /// The reaction senders (group) to display. + pub group: BoundObjectWeakRef, + /// The list of reaction senders as room members. + pub list: gio::ListStore, + /// The member list of the room of the reaction. + pub members: RefCell>, + #[template_child] + pub button: TemplateChild, + #[template_child] + pub reaction_key: TemplateChild, + #[template_child] + pub reaction_count: TemplateChild, + } + + impl Default for MessageReaction { + fn default() -> Self { + Self { + group: Default::default(), + list: gio::ListStore::new::(), + members: Default::default(), + button: Default::default(), + reaction_key: Default::default(), + reaction_count: Default::default(), + } + } + } + + #[glib::object_subclass] + impl ObjectSubclass for MessageReaction { + const NAME: &'static str = "ContentMessageReaction"; + type Type = super::MessageReaction; + type ParentType = gtk::FlowBoxChild; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + Self::Type::bind_template_callbacks(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for MessageReaction { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecObject::builder::("group") + .construct_only() + .build(), + glib::ParamSpecObject::builder::("list") + .read_only() + .build(), + glib::ParamSpecObject::builder::("members").build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "group" => { + self.obj().set_group(value.get().unwrap()); + } + "members" => self.obj().set_members(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "group" => self.obj().group().to_value(), + "list" => self.obj().list().to_value(), + "members" => self.obj().members().to_value(), + _ => unimplemented!(), + } + } + + fn dispose(&self) { + self.group.disconnect_signals(); + } + } + + impl WidgetImpl for MessageReaction {} + + impl FlowBoxChildImpl for MessageReaction {} +} + +glib::wrapper! { + /// A widget displaying the reactions of a message. + pub struct MessageReaction(ObjectSubclass) + @extends gtk::Widget, gtk::FlowBoxChild, @implements gtk::Accessible; +} + +#[gtk::template_callbacks] +impl MessageReaction { + pub fn new(members: MemberList, reaction_group: ReactionGroup) -> Self { + glib::Object::builder() + .property("group", reaction_group) + .property("members", members) + .build() + } + + // The reaction group to display. + pub fn group(&self) -> Option { + self.imp().group.obj() + } + + /// Set the reaction group to display. + fn set_group(&self, group: ReactionGroup) { + let imp = self.imp(); + let key = group.key(); + imp.reaction_key.set_label(key); + + if EMOJI_REGEX.is_match(key) { + imp.reaction_key.add_css_class("emoji"); + } else { + imp.reaction_key.remove_css_class("emoji"); + } + + imp.button.set_action_target_value(Some(&key.to_variant())); + group + .bind_property("has-user", &*imp.button, "active") + .sync_create() + .build(); + group + .bind_property("count", &*imp.reaction_count, "label") + .sync_create() + .build(); + + let items_changed_handler_id = group.connect_items_changed( + clone!(@weak self as obj => move |group, pos, removed, added| + obj.items_changed(group, pos, removed, added) + ), + ); + self.items_changed(&group, 0, self.list().n_items(), group.n_items()); + + imp.group.set(&group, vec![items_changed_handler_id]); + } + + /// The list of reaction senders as room members. + pub fn list(&self) -> &gio::ListStore { + &self.imp().list + } + + /// The member list of the room of the reaction. + pub fn members(&self) -> Option { + self.imp().members.borrow().clone() + } + + /// Set the members list of the room of the reaction. + pub fn set_members(&self, members: Option) { + let imp = self.imp(); + + if imp.members.borrow().as_ref() == members.as_ref() { + return; + } + + imp.members.replace(members); + self.notify("members"); + + if let Some(group) = imp.group.obj() { + self.items_changed(&group, 0, self.list().n_items(), group.n_items()); + } + } + + fn items_changed(&self, group: &ReactionGroup, pos: u32, removed: u32, added: u32) { + let Some(members) = &*self.imp().members.borrow() else { + return; + }; + + let mut new_senders = Vec::with_capacity(added as usize); + for i in pos..pos + added { + let Some(boxed) = group.item(i).and_downcast::() else { + break; + }; + + let sender_data = boxed.borrow::(); + let member = members.get_or_create(sender_data.sender_id.clone()); + let timestamp = sender_data.timestamp.as_secs().into(); + let sender = MemberReactionSender::new(&member, timestamp); + + new_senders.push(sender); + } + + self.list().splice(pos, removed, &new_senders); + } + + /// Handle a right click/long press on the reaction button. + /// + /// Shows a popover with the senders of that reaction, if there are any. + #[template_callback] + fn show_popover(&self) { + if self.list().n_items() == 0 { + // No popover. + return; + }; + + let button = &*self.imp().button; + let popover = ReactionPopover::new(self.list()); + popover.set_parent(button); + popover.connect_closed(clone!(@weak button => move |popover| { + popover.unparent(); + })); + popover.popup(); + } +} diff --git a/src/session/view/content/room_history/message_row/reaction.ui b/src/session/view/content/room_history/message_row/reaction/mod.ui similarity index 63% rename from src/session/view/content/room_history/message_row/reaction.ui rename to src/session/view/content/room_history/message_row/reaction/mod.ui index b677cbf6..5d4f6696 100644 --- a/src/session/view/content/room_history/message_row/reaction.ui +++ b/src/session/view/content/room_history/message_row/reaction/mod.ui @@ -28,7 +28,22 @@ + + + 3 + True + + + + + + True + True + + + + diff --git a/src/session/view/content/room_history/message_row/reaction/reaction_popover.rs b/src/session/view/content/room_history/message_row/reaction/reaction_popover.rs new file mode 100644 index 00000000..cbe42e17 --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/reaction_popover.rs @@ -0,0 +1,108 @@ +use adw::subclass::prelude::*; +use gtk::{gio, glib, prelude::*, CompositeTemplate}; + +use super::ReactionSenderRow; + +mod imp { + use glib::subclass::InitializingObject; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Debug, Default, CompositeTemplate)] + #[template( + resource = "/org/gnome/Fractal/ui/session/view/content/room_history/message_row/reaction/reaction_popover.ui" + )] + pub struct ReactionPopover { + #[template_child] + pub list: TemplateChild, + /// The reaction senders to display. + pub senders: glib::WeakRef, + } + + #[glib::object_subclass] + impl ObjectSubclass for ReactionPopover { + const NAME: &'static str = "ContentMessageReactionPopover"; + type Type = super::ReactionPopover; + type ParentType = gtk::Popover; + + fn class_init(klass: &mut Self::Class) { + ReactionSenderRow::static_type(); + Self::bind_template(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for ReactionPopover { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpecObject::builder::("senders") + .construct_only() + .build()] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let obj = self.obj(); + + match pspec.name() { + "senders" => obj.set_senders(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + let obj = self.obj(); + + match pspec.name() { + "senders" => obj.senders().to_value(), + _ => unimplemented!(), + } + } + + fn constructed(&self) { + self.parent_constructed(); + } + } + + impl WidgetImpl for ReactionPopover {} + + impl PopoverImpl for ReactionPopover {} +} + +glib::wrapper! { + /// A popover to display the senders of a reaction. + pub struct ReactionPopover(ObjectSubclass) + @extends gtk::Widget, gtk::Popover; +} + +impl ReactionPopover { + /// Constructs a new `ReactionPopover` with the given reaction senders. + pub fn new(senders: &gio::ListStore) -> Self { + glib::Object::builder().property("senders", senders).build() + } + + /// The reaction senders to display. + pub fn senders(&self) -> Option { + self.imp().senders.upgrade() + } + + /// Set the reaction senders to display. + fn set_senders(&self, senders: Option) { + let Some(senders) = senders else { + // Ignore missing reaction senders. + return; + }; + let imp = self.imp(); + + imp.senders.set(Some(&senders)); + imp.list + .set_model(Some(>k::NoSelection::new(Some(senders)))); + self.notify("senders"); + } +} diff --git a/src/session/view/content/room_history/message_row/reaction/reaction_popover.ui b/src/session/view/content/room_history/message_row/reaction/reaction_popover.ui new file mode 100644 index 00000000..e976e83d --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/reaction_popover.ui @@ -0,0 +1,42 @@ + + + + + diff --git a/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.rs b/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.rs new file mode 100644 index 00000000..cbf55076 --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.rs @@ -0,0 +1,105 @@ +use adw::{prelude::*, subclass::prelude::*}; +use gtk::{glib, CompositeTemplate}; + +use super::member_reaction_sender::MemberReactionSender; + +mod imp { + use glib::subclass::InitializingObject; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Debug, Default, CompositeTemplate)] + #[template( + resource = "/org/gnome/Fractal/ui/session/view/content/room_history/message_row/reaction/reaction_sender_row.ui" + )] + pub struct ReactionSenderRow { + /// The sender presented by this row. + pub sender: glib::WeakRef, + } + + #[glib::object_subclass] + impl ObjectSubclass for ReactionSenderRow { + const NAME: &'static str = "ContentMessageReactionSenderRow"; + type Type = super::ReactionSenderRow; + 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 ReactionSenderRow { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecObject::builder::("sender") + .explicit_notify() + .build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "sender" => self.obj().set_sender( + value + .get::>() + .unwrap() + .as_ref(), + ), + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "sender" => self.obj().sender().to_value(), + _ => unimplemented!(), + } + } + } + + impl WidgetImpl for ReactionSenderRow {} + + impl BinImpl for ReactionSenderRow {} +} + +glib::wrapper! { + /// A row displaying a reaction sender. + pub struct ReactionSenderRow(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} + +impl ReactionSenderRow { + pub fn new() -> Self { + glib::Object::new() + } + + /// The reaction sender presented by this row. + pub fn sender(&self) -> Option { + self.imp().sender.upgrade() + } + + /// Set the reaction sender presented by this row. + pub fn set_sender(&self, sender: Option<&MemberReactionSender>) { + if self.sender().as_ref() == sender { + return; + } + + self.imp().sender.set(sender); + self.notify("sender"); + } +} + +impl Default for ReactionSenderRow { + fn default() -> Self { + Self::new() + } +} diff --git a/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.ui b/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.ui new file mode 100644 index 00000000..2424e019 --- /dev/null +++ b/src/session/view/content/room_history/message_row/reaction/reaction_sender_row.ui @@ -0,0 +1,62 @@ + + + + + diff --git a/src/session/view/content/room_history/message_row/reaction_list.rs b/src/session/view/content/room_history/message_row/reaction_list.rs index 8af6cfb0..9bd0ca85 100644 --- a/src/session/view/content/room_history/message_row/reaction_list.rs +++ b/src/session/view/content/room_history/message_row/reaction_list.rs @@ -1,8 +1,8 @@ use adw::subclass::prelude::*; -use gtk::{glib, prelude::*, CompositeTemplate}; +use gtk::{glib, glib::clone, prelude::*, CompositeTemplate}; use super::reaction::MessageReaction; -use crate::session::model::ReactionList; +use crate::session::model::{MemberList, ReactionList}; mod imp { use glib::subclass::InitializingObject; @@ -52,9 +52,15 @@ impl MessageReactionList { glib::Object::new() } - pub fn set_reaction_list(&self, reaction_list: &ReactionList) { - self.imp().flow_box.bind_model(Some(reaction_list), |obj| { - MessageReaction::new(obj.clone().downcast().unwrap()).upcast() - }); + pub fn set_reaction_list(&self, members: &MemberList, reaction_list: &ReactionList) { + self.imp().flow_box.bind_model( + Some(reaction_list), + clone!( + @weak members => @default-return { gtk::FlowBoxChild::new().upcast() }, + move |obj| { + MessageReaction::new(members, obj.clone().downcast().unwrap()).upcast() + } + ), + ); } } diff --git a/src/ui-resources.gresource.xml b/src/ui-resources.gresource.xml index 7ad7a41b..64021f8b 100644 --- a/src/ui-resources.gresource.xml +++ b/src/ui-resources.gresource.xml @@ -67,7 +67,9 @@ session/view/content/room_history/message_row/location.ui session/view/content/room_history/message_row/media.ui session/view/content/room_history/message_row/mod.ui - session/view/content/room_history/message_row/reaction.ui + session/view/content/room_history/message_row/reaction/mod.ui + session/view/content/room_history/message_row/reaction/reaction_popover.ui + session/view/content/room_history/message_row/reaction/reaction_sender_row.ui session/view/content/room_history/message_row/reaction_list.ui session/view/content/room_history/message_row/reply.ui session/view/content/room_history/message_toolbar/attachment_dialog.ui @@ -100,3 +102,4 @@ window.ui +