event: Provide more data for read receipts
This commit is contained in:
parent
33d06a93e8
commit
24d6a7ce32
12 changed files with 405 additions and 163 deletions
|
@ -14,7 +14,8 @@ pub use self::{
|
|||
room::{
|
||||
Event, EventKey, HighlightFlags, Member, MemberList, MemberRole, Membership, PowerLevel,
|
||||
ReactionGroup, ReactionList, Room, RoomType, Timeline, TimelineItem, TimelineItemExt,
|
||||
TimelineState, TypingList, VirtualItem, VirtualItemKind, POWER_LEVEL_MAX, POWER_LEVEL_MIN,
|
||||
TimelineState, TypingList, UserReadReceipt, VirtualItem, VirtualItemKind, POWER_LEVEL_MAX,
|
||||
POWER_LEVEL_MIN,
|
||||
},
|
||||
room_list::RoomList,
|
||||
session::{Session, SessionState},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
|
||||
use indexmap::IndexMap;
|
||||
use matrix_sdk_ui::timeline::{
|
||||
AnyOtherFullStateEventContent, Error as TimelineError, EventTimelineItem, RepliedToEvent,
|
||||
|
@ -71,6 +71,13 @@ impl glib::FromVariant for EventKey {
|
|||
#[boxed_type(name = "BoxedEventTimelineItem")]
|
||||
pub struct BoxedEventTimelineItem(EventTimelineItem);
|
||||
|
||||
/// A user's read receipt.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserReadReceipt {
|
||||
pub user_id: OwnedUserId,
|
||||
pub receipt: Receipt,
|
||||
}
|
||||
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
@ -79,7 +86,7 @@ mod imp {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct Event {
|
||||
/// The underlying SDK timeline item.
|
||||
pub item: RefCell<Option<EventTimelineItem>>,
|
||||
|
@ -91,7 +98,18 @@ mod imp {
|
|||
pub reactions: ReactionList,
|
||||
|
||||
/// The read receipts on this event.
|
||||
pub read_receipts: gtk::StringList,
|
||||
pub read_receipts: gio::ListStore,
|
||||
}
|
||||
|
||||
impl Default for Event {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
item: Default::default(),
|
||||
room: Default::default(),
|
||||
reactions: Default::default(),
|
||||
read_receipts: gio::ListStore::new::<glib::BoxedAnyObject>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -122,6 +140,9 @@ mod imp {
|
|||
glib::ParamSpecBoolean::builder("is-highlighted")
|
||||
.read_only()
|
||||
.build(),
|
||||
glib::ParamSpecObject::builder::<gio::ListStore>("read-receipts")
|
||||
.read_only()
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("has-read-receipts")
|
||||
.read_only()
|
||||
.build(),
|
||||
|
@ -156,6 +177,7 @@ mod imp {
|
|||
"reactions" => obj.reactions().to_value(),
|
||||
"is-edited" => obj.is_edited().to_value(),
|
||||
"is-highlighted" => obj.is_highlighted().to_value(),
|
||||
"read-receipts" => obj.read_receipts().to_value(),
|
||||
"has-read-receipts" => obj.has_read_receipts().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
@ -441,7 +463,7 @@ impl Event {
|
|||
}
|
||||
|
||||
/// The read receipts on this event.
|
||||
pub fn read_receipts(&self) -> >k::StringList {
|
||||
pub fn read_receipts(&self) -> &gio::ListStore {
|
||||
&self.imp().read_receipts
|
||||
}
|
||||
|
||||
|
@ -456,21 +478,18 @@ impl Event {
|
|||
let old_count = read_receipts.n_items();
|
||||
let new_count = new_read_receipts.len() as u32;
|
||||
|
||||
let new_user_ids = new_read_receipts
|
||||
.keys()
|
||||
.map(|u| u.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if old_count == new_count {
|
||||
let mut is_all_same = true;
|
||||
for i in 0..old_count {
|
||||
let Some(old_user_id) = read_receipts.string(i) else {
|
||||
for (i, new_user_id) in new_read_receipts.keys().enumerate() {
|
||||
let Some(old_receipt) = read_receipts
|
||||
.item(i as u32)
|
||||
.and_downcast::<glib::BoxedAnyObject>()
|
||||
else {
|
||||
is_all_same = false;
|
||||
break;
|
||||
};
|
||||
let new_user_id = new_user_ids[i as usize];
|
||||
|
||||
if old_user_id != new_user_id {
|
||||
if old_receipt.borrow::<UserReadReceipt>().user_id != *new_user_id {
|
||||
is_all_same = false;
|
||||
break;
|
||||
}
|
||||
|
@ -481,7 +500,16 @@ impl Event {
|
|||
}
|
||||
}
|
||||
|
||||
read_receipts.splice(0, old_count, &new_user_ids);
|
||||
let new_read_receipts = new_read_receipts
|
||||
.into_iter()
|
||||
.map(|(user_id, receipt)| {
|
||||
glib::BoxedAnyObject::new(UserReadReceipt {
|
||||
user_id: user_id.clone(),
|
||||
receipt: receipt.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
read_receipts.splice(0, old_count, &new_read_receipts);
|
||||
|
||||
let had_read_receipts = old_count > 0;
|
||||
let has_read_receipts = new_count > 0;
|
||||
|
|
|
@ -825,6 +825,7 @@ impl Room {
|
|||
} else {
|
||||
let list = MemberList::new(self);
|
||||
members.set(Some(&list));
|
||||
self.notify("members");
|
||||
list
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,8 +216,7 @@ impl MessageRow {
|
|||
|
||||
imp.reactions
|
||||
.set_reaction_list(&event.room().get_or_create_members(), event.reactions());
|
||||
imp.read_receipts
|
||||
.set_list(&event.room(), event.read_receipts());
|
||||
imp.read_receipts.set_source(event.read_receipts());
|
||||
imp.event.replace(Some(event));
|
||||
self.notify("event");
|
||||
}
|
||||
|
|
|
@ -99,6 +99,13 @@
|
|||
<lookup name="event">ContentMessageRow</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<binding name="members">
|
||||
<lookup name="members" type="Room">
|
||||
<lookup name="room" type="RoomEvent">
|
||||
<lookup name="event">ContentMessageRow</lookup>
|
||||
</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">3</property>
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
use adw::subclass::prelude::*;
|
||||
use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
|
||||
use ruma::UserId;
|
||||
|
||||
use crate::{
|
||||
components::{Avatar, OverlappingBox},
|
||||
prelude::*,
|
||||
session::model::Room,
|
||||
utils::BoundObjectWeakRef,
|
||||
};
|
||||
|
||||
// Keep in sync with the `max-children` property of the `overlapping_box` in the
|
||||
// UI file.
|
||||
const MAX_RECEIPTS_SHOWN: u32 = 10;
|
||||
|
||||
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/read_receipts_list.ui"
|
||||
)]
|
||||
pub struct ReadReceiptsList {
|
||||
#[template_child]
|
||||
pub label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub overlapping_box: TemplateChild<OverlappingBox>,
|
||||
|
||||
/// The read receipts that are bound, if any.
|
||||
pub bound_receipts: BoundObjectWeakRef<gtk::StringList>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ReadReceiptsList {
|
||||
const NAME: &'static str = "ContentReadReceiptsList";
|
||||
type Type = super::ReadReceiptsList;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
Self::bind_template(klass);
|
||||
klass.set_css_name("read-receipts-list");
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ReadReceiptsList {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<gtk::StringList>("list")
|
||||
.read_only()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"list" => obj.list().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
self.bound_receipts.disconnect_signals();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ReadReceiptsList {}
|
||||
|
||||
impl BinImpl for ReadReceiptsList {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A widget displaying the read receipts on a message.
|
||||
pub struct ReadReceiptsList(ObjectSubclass<imp::ReadReceiptsList>)
|
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||
}
|
||||
|
||||
impl ReadReceiptsList {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Option<gtk::StringList> {
|
||||
self.imp().bound_receipts.obj()
|
||||
}
|
||||
|
||||
pub fn set_list(&self, room: &Room, read_receipts: >k::StringList) {
|
||||
let imp = self.imp();
|
||||
|
||||
imp.overlapping_box.bind_model(
|
||||
Some(read_receipts),
|
||||
clone!(@weak room => @default-return { Avatar::new().upcast() }, move |item| {
|
||||
let user_id = UserId::parse(
|
||||
item.downcast_ref::<gtk::StringObject>()
|
||||
.unwrap()
|
||||
.string()
|
||||
)
|
||||
.expect("Strings in read receipts list are valid UserIds");
|
||||
// We should have a strong reference to the list in the RoomHistory so we can use `get_or_create_members()`.
|
||||
let member = room.get_or_create_members().get_or_create(user_id);
|
||||
|
||||
let avatar_data = member.avatar_data();
|
||||
let avatar = Avatar::new();
|
||||
avatar.set_size(20);
|
||||
avatar.set_data(Some(avatar_data.clone()));
|
||||
|
||||
let cutout = adw::Bin::builder().child(&avatar).css_classes(["cutout"]).build();
|
||||
cutout.upcast()
|
||||
}),
|
||||
);
|
||||
|
||||
let items_changed_handler_id = read_receipts.connect_items_changed(
|
||||
clone!(@weak self as obj => move |read_receipts, _, _, _| {
|
||||
obj.update_label(read_receipts);
|
||||
}),
|
||||
);
|
||||
|
||||
imp.bound_receipts
|
||||
.set(read_receipts, vec![items_changed_handler_id]);
|
||||
self.update_label(read_receipts);
|
||||
self.notify("list");
|
||||
}
|
||||
|
||||
fn update_label(&self, read_receipts: >k::StringList) {
|
||||
let label = &self.imp().label;
|
||||
let n_items = read_receipts.n_items();
|
||||
if n_items > MAX_RECEIPTS_SHOWN {
|
||||
label.set_text(&format!("{} +", n_items - MAX_RECEIPTS_SHOWN));
|
||||
} else {
|
||||
label.set_text("");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
use adw::subclass::prelude::*;
|
||||
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 MemberReadReceipt {
|
||||
/// The room member of this read receipt.
|
||||
pub member: glib::WeakRef<Member>,
|
||||
/// The timestamp of this read receipt, in milliseconds since Unix
|
||||
/// Epoch, if any.
|
||||
///
|
||||
/// A value of 0 means no timestamp.
|
||||
pub timestamp: Cell<u64>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MemberReadReceipt {
|
||||
const NAME: &'static str = "ContentMemberReadReceipt";
|
||||
type Type = super::MemberReadReceipt;
|
||||
}
|
||||
|
||||
impl ObjectImpl for MemberReadReceipt {
|
||||
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(),
|
||||
]
|
||||
});
|
||||
|
||||
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(),
|
||||
_ => 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 room member's read receipt.
|
||||
pub struct MemberReadReceipt(ObjectSubclass<imp::MemberReadReceipt>);
|
||||
}
|
||||
|
||||
impl MemberReadReceipt {
|
||||
/// Constructs a new `MemberReadReceipt` with the given member and
|
||||
/// timestamp.
|
||||
pub fn new(member: &Member, timestamp: Option<u64>) -> Self {
|
||||
glib::Object::builder()
|
||||
.property("member", member)
|
||||
.property("timestamp", timestamp.unwrap_or_default())
|
||||
.build()
|
||||
}
|
||||
|
||||
/// The room member of this read receipt.
|
||||
pub fn member(&self) -> Option<Member> {
|
||||
self.imp().member.upgrade()
|
||||
}
|
||||
|
||||
/// Set the room member of this read receipt.
|
||||
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 this read receipt, in milliseconds 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 this read receipt.
|
||||
pub fn set_timestamp(&self, ts: u64) {
|
||||
if self.timestamp() == ts {
|
||||
return;
|
||||
}
|
||||
|
||||
self.imp().timestamp.set(ts);
|
||||
self.notify("timestamp");
|
||||
}
|
||||
}
|
227
src/session/view/content/room_history/read_receipts_list/mod.rs
Normal file
227
src/session/view/content/room_history/read_receipts_list/mod.rs
Normal file
|
@ -0,0 +1,227 @@
|
|||
use adw::subclass::prelude::*;
|
||||
use gtk::{gio, glib, glib::clone, prelude::*, CompositeTemplate};
|
||||
|
||||
mod member_read_receipt;
|
||||
|
||||
use self::member_read_receipt::MemberReadReceipt;
|
||||
use crate::{
|
||||
components::{Avatar, OverlappingBox},
|
||||
prelude::*,
|
||||
session::model::{MemberList, UserReadReceipt},
|
||||
utils::BoundObjectWeakRef,
|
||||
};
|
||||
|
||||
// Keep in sync with the `max-children` property of the `overlapping_box` in the
|
||||
// UI file.
|
||||
const MAX_RECEIPTS_SHOWN: u32 = 10;
|
||||
|
||||
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/read_receipts_list/mod.ui"
|
||||
)]
|
||||
pub struct ReadReceiptsList {
|
||||
#[template_child]
|
||||
pub label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub overlapping_box: TemplateChild<OverlappingBox>,
|
||||
/// The list of room members.
|
||||
pub members: RefCell<Option<MemberList>>,
|
||||
/// The list of read receipts.
|
||||
pub list: gio::ListStore,
|
||||
/// The read receipts used as a source.
|
||||
pub source: BoundObjectWeakRef<gio::ListStore>,
|
||||
}
|
||||
|
||||
impl Default for ReadReceiptsList {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: Default::default(),
|
||||
overlapping_box: Default::default(),
|
||||
members: Default::default(),
|
||||
list: gio::ListStore::new::<MemberReadReceipt>(),
|
||||
source: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ReadReceiptsList {
|
||||
const NAME: &'static str = "ContentReadReceiptsList";
|
||||
type Type = super::ReadReceiptsList;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
Self::bind_template(klass);
|
||||
klass.set_css_name("read-receipts-list");
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ReadReceiptsList {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<MemberList>("members").build(),
|
||||
glib::ParamSpecObject::builder::<gio::ListStore>("list")
|
||||
.read_only()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"members" => obj.members().to_value(),
|
||||
"list" => obj.list().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"members" => obj.set_members(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
|
||||
self.overlapping_box.bind_model(
|
||||
Some(&self.list),
|
||||
clone!(@weak obj => @default-return { Avatar::new().upcast() }, move |item| {
|
||||
let avatar = Avatar::new();
|
||||
avatar.set_size(20);
|
||||
|
||||
if let Some(member) = item.downcast_ref::<MemberReadReceipt>().and_then(|r| r.member()) {
|
||||
avatar.set_data(Some(member.avatar_data().clone()));
|
||||
}
|
||||
|
||||
let cutout = adw::Bin::builder().child(&avatar).css_classes(["cutout"]).build();
|
||||
cutout.upcast()
|
||||
}),
|
||||
);
|
||||
|
||||
self.list
|
||||
.connect_items_changed(clone!(@weak obj => move |_, _,_,_| {
|
||||
obj.update_label();
|
||||
}));
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
self.source.disconnect_signals();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ReadReceiptsList {}
|
||||
|
||||
impl BinImpl for ReadReceiptsList {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A widget displaying the read receipts on a message.
|
||||
pub struct ReadReceiptsList(ObjectSubclass<imp::ReadReceiptsList>)
|
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||
}
|
||||
|
||||
impl ReadReceiptsList {
|
||||
pub fn new(members: &MemberList) -> Self {
|
||||
glib::Object::builder().property("members", members).build()
|
||||
}
|
||||
|
||||
/// The list of room members.
|
||||
pub fn members(&self) -> Option<MemberList> {
|
||||
self.imp().members.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the list of room members.
|
||||
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(source) = imp.source.obj() {
|
||||
self.items_changed(&source, 0, self.list().n_items(), source.n_items());
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of read receipts to present.
|
||||
pub fn list(&self) -> &gio::ListStore {
|
||||
&self.imp().list
|
||||
}
|
||||
|
||||
/// Set the read receipts that are used as a source of data.
|
||||
pub fn set_source(&self, source: &gio::ListStore) {
|
||||
let imp = self.imp();
|
||||
|
||||
let items_changed_handler_id = source.connect_items_changed(
|
||||
clone!(@weak self as obj => move |source, pos, removed, added| {
|
||||
obj.items_changed(source, pos, removed, added);
|
||||
}),
|
||||
);
|
||||
self.items_changed(source, 0, self.list().n_items(), source.n_items());
|
||||
|
||||
imp.source.set(source, vec![items_changed_handler_id]);
|
||||
}
|
||||
|
||||
fn items_changed(&self, source: &gio::ListStore, pos: u32, removed: u32, added: u32) {
|
||||
let mut new_receipts = Vec::with_capacity(added as usize);
|
||||
|
||||
{
|
||||
let Some(members) = &*self.imp().members.borrow() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for i in pos..pos + added {
|
||||
let Some(boxed) = source.item(i).and_downcast::<glib::BoxedAnyObject>() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let source_receipt = boxed.borrow::<UserReadReceipt>();
|
||||
let member = members.get_or_create(source_receipt.user_id.clone());
|
||||
let receipt = MemberReadReceipt::new(
|
||||
&member,
|
||||
source_receipt.receipt.ts.map(|ts| ts.0.into()),
|
||||
);
|
||||
|
||||
new_receipts.push(receipt);
|
||||
}
|
||||
}
|
||||
|
||||
self.list().splice(pos, removed, &new_receipts);
|
||||
}
|
||||
|
||||
fn update_label(&self) {
|
||||
let label = &self.imp().label;
|
||||
let n_items = self.list().n_items();
|
||||
|
||||
if n_items > MAX_RECEIPTS_SHOWN {
|
||||
label.set_text(&format!("{} +", n_items - MAX_RECEIPTS_SHOWN));
|
||||
} else {
|
||||
label.set_text("");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -121,8 +121,7 @@ impl StateRow {
|
|||
}
|
||||
|
||||
let imp = self.imp();
|
||||
imp.read_receipts
|
||||
.set_list(&event.room(), event.read_receipts());
|
||||
imp.read_receipts.set_source(event.read_receipts());
|
||||
imp.event.replace(Some(event));
|
||||
self.notify("event");
|
||||
}
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
<lookup name="event">ContentStateRow</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<binding name="members">
|
||||
<lookup name="members" type="Room">
|
||||
<lookup name="room" type="RoomEvent">
|
||||
<lookup name="event">ContentStateRow</lookup>
|
||||
</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_toolbar/completion/completion_row.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_toolbar/mod.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/mod.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/read_receipts_list.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/read_receipts_list/mod.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/state_row/creation.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/state_row/mod.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/state_row/tombstone.ui</file>
|
||||
|
|
Loading…
Reference in a new issue