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
This commit is contained in:
Paul van Tilburg 2023-11-06 19:47:31 +01:00
parent 7bb3790d7a
commit cd1911fce8
No known key found for this signature in database
GPG key ID: C6DE073EDA9EEC4D
12 changed files with 744 additions and 125 deletions

View file

@ -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

View file

@ -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));

View file

@ -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<ReactionGroup>,
#[template_child]
pub button: TemplateChild<gtk::ToggleButton>,
#[template_child]
pub reaction_key: TemplateChild<gtk::Label>,
#[template_child]
pub reaction_count: TemplateChild<gtk::Label>,
}
#[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<Self>) {
obj.init_template();
}
}
impl ObjectImpl for MessageReaction {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<ReactionGroup>("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<imp::MessageReaction>)
@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();
}
}

View file

@ -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<Member>,
/// 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<u64>,
}
#[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<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Member>("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::<Option<Member>>().unwrap().as_ref()),
"timestamp" => obj.set_timestamp(value.get().unwrap()),
_ => unimplemented!(),
}
}
}
}
glib::wrapper! {
/// A reaction sender's room member.
pub struct MemberReactionSender(ObjectSubclass<imp::MemberReactionSender>);
}
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<Member> {
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: <https://docs.gtk.org/glib/method.DateTime.format.html>
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: <https://docs.gtk.org/glib/method.DateTime.format.html>
gettext("%B %-e at %-k%M %p")
};
datetime.format(&format).unwrap().to_string()
}
}

View file

@ -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<ReactionGroup>,
/// The list of reaction senders as room members.
pub list: gio::ListStore,
/// The member list of the room of the reaction.
pub members: RefCell<Option<MemberList>>,
#[template_child]
pub button: TemplateChild<gtk::ToggleButton>,
#[template_child]
pub reaction_key: TemplateChild<gtk::Label>,
#[template_child]
pub reaction_count: TemplateChild<gtk::Label>,
}
impl Default for MessageReaction {
fn default() -> Self {
Self {
group: Default::default(),
list: gio::ListStore::new::<MemberReactionSender>(),
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<Self>) {
obj.init_template();
}
}
impl ObjectImpl for MessageReaction {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<ReactionGroup>("group")
.construct_only()
.build(),
glib::ParamSpecObject::builder::<gio::ListStore>("list")
.read_only()
.build(),
glib::ParamSpecObject::builder::<MemberList>("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<imp::MessageReaction>)
@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<ReactionGroup> {
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<MemberList> {
self.imp().members.borrow().clone()
}
/// Set the members list of the room of the reaction.
pub fn set_members(&self, members: Option<MemberList>) {
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::<glib::BoxedAnyObject>() else {
break;
};
let sender_data = boxed.borrow::<SdkReactionSenderData>();
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();
}
}

View file

@ -28,7 +28,22 @@
</child>
</object>
</child>
<child>
<object class="GtkGestureClick" id="click_gesture">
<property name="button">3</property>
<property name="exclusive">True</property>
<signal name="released" handler="show_popover" swapped="true"/>
</object>
</child>
<child>
<object class="GtkGestureLongPress" id="long_press_gesture">
<property name="touch_only">True</property>
<property name="exclusive">True</property>
<signal name="pressed" handler="show_popover" swapped="true"/>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -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<gtk::ListView>,
/// The reaction senders to display.
pub senders: glib::WeakRef<gio::ListStore>,
}
#[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<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ReactionPopover {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<gio::ListStore>("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<imp::ReactionPopover>)
@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<gio::ListStore> {
self.imp().senders.upgrade()
}
/// Set the reaction senders to display.
fn set_senders(&self, senders: Option<gio::ListStore>) {
let Some(senders) = senders else {
// Ignore missing reaction senders.
return;
};
let imp = self.imp();
imp.senders.set(Some(&senders));
imp.list
.set_model(Some(&gtk::NoSelection::new(Some(senders))));
self.notify("senders");
}
}

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentMessageReactionPopover" parent="GtkPopover">
<style>
<class name="list-popover"/>
</style>
<property name="autohide">true</property>
<property name="width-request">260</property>
<property name="child">
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="propagate-natural-height">true</property>
<property name="hscrollbar-policy">never</property>
<property name="max-content-height">280</property>
<property name="child">
<object class="GtkListView" id="list">
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="activatable">False</property>
<property name="selectable">False</property>
<property name="child">
<object class="ContentMessageReactionSenderRow">
<binding name="sender">
<lookup name="item">GtkListItem</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</property>
</object>
</property>
</template>
</interface>

View file

@ -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<MemberReactionSender>,
}
#[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<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ReactionSenderRow {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<MemberReactionSender>("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::<Option<MemberReactionSender>>()
.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<imp::ReactionSenderRow>)
@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<MemberReactionSender> {
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()
}
}

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentMessageReactionSenderRow" parent="AdwBin">
<style>
<class name="list-popover-row"/>
</style>
<child>
<object class="GtkBox">
<property name="spacing">10</property>
<child>
<object class="ComponentsAvatar">
<property name="size">36</property>
<binding name="data">
<lookup name="avatar-data">
<lookup name="member">
<lookup name="sender">ContentMessageReactionSenderRow</lookup>
</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">3</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="xalign">0.0</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="display-name">
<lookup name="member">
<lookup name="sender">ContentMessageReactionSenderRow</lookup>
</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="xalign">1.0</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="datetime">
<lookup name="sender">ContentMessageReactionSenderRow</lookup>
</lookup>
</binding>
<style>
<class name="dim-label"/>
<class name="caption"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -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()
}
),
);
}
}

View file

@ -67,7 +67,9 @@
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/location.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/media.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reaction.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reaction/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reaction/reaction_popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reaction/reaction_sender_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reaction_list.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/reply.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_toolbar/attachment_dialog.ui</file>
@ -100,3 +102,4 @@
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
</gresource>
</gresources>