message-row: Show the sending status of messages
Also logs if a sending error is encountered
This commit is contained in:
parent
b1de0cee42
commit
71611bc34e
10 changed files with 367 additions and 55 deletions
2
data/resources/icons/scalable/status/done-symbolic.svg
Normal file
2
data/resources/icons/scalable/status/done-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -160 -80)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -160 -80)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -160 -80)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><path d="m 8 0 c -4.417969 0 -8 3.582031 -8 8 s 3.582031 8 8 8 s 8 -3.582031 8 -8 s -3.582031 -8 -8 -8 z m 3.164062 5.859375 c 0.640626 0.046875 0.933594 0.824219 0.476563 1.28125 l -3.640625 3.640625 c -0.292969 0.292969 -0.769531 0.292969 -1.0625 0 l -2.175781 -2.109375 c -0.707031 -0.710937 0.355469 -1.773437 1.0625 -1.0625 l 1.644531 1.578125 l 3.109375 -3.109375 c 0.15625 -0.152344 0.367187 -0.234375 0.585937 -0.21875 z m 0 0" fill="#2e3436"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -43,6 +43,7 @@
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/checkmark-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/checkmark-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/document-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/document-symbolic.svg</file>
|
||||||
|
<file preprocess="xml-stripblanks">icons/scalable/status/done-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/empty-page-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/empty-page-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/error-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/error-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">icons/scalable/status/explore-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">icons/scalable/status/explore-symbolic.svg</file>
|
||||||
|
|
|
@ -84,7 +84,8 @@ src/session/view/content/room_history/message_row/content.rs
|
||||||
src/session/view/content/room_history/message_row/file.ui
|
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/location.rs
|
||||||
src/session/view/content/room_history/message_row/media.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/message_state_stack.rs
|
||||||
|
src/session/view/content/room_history/message_row/message_state_stack.ui
|
||||||
src/session/view/content/room_history/message_toolbar/attachment_dialog.ui
|
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.rs
|
||||||
src/session/view/content/room_history/message_toolbar/mod.ui
|
src/session/view/content/room_history/message_toolbar/mod.ui
|
||||||
|
|
|
@ -12,10 +12,10 @@ pub use self::{
|
||||||
avatar::{AvatarData, AvatarImage, AvatarUriSource},
|
avatar::{AvatarData, AvatarImage, AvatarUriSource},
|
||||||
notifications::Notifications,
|
notifications::Notifications,
|
||||||
room::{
|
room::{
|
||||||
Event, EventKey, HighlightFlags, Member, MemberList, MemberRole, Membership, PowerLevel,
|
Event, EventKey, HighlightFlags, Member, MemberList, MemberRole, Membership, MessageState,
|
||||||
ReactionGroup, ReactionList, Room, RoomType, Timeline, TimelineItem, TimelineItemExt,
|
PowerLevel, ReactionGroup, ReactionList, Room, RoomType, Timeline, TimelineItem,
|
||||||
TimelineState, TypingList, UserReadReceipt, VirtualItem, VirtualItemKind, POWER_LEVEL_MAX,
|
TimelineItemExt, TimelineState, TypingList, UserReadReceipt, VirtualItem, VirtualItemKind,
|
||||||
POWER_LEVEL_MIN,
|
POWER_LEVEL_MAX, POWER_LEVEL_MIN,
|
||||||
},
|
},
|
||||||
room_list::RoomList,
|
room_list::RoomList,
|
||||||
session::{Session, SessionState},
|
session::{Session, SessionState},
|
||||||
|
|
|
@ -3,14 +3,15 @@ use std::{borrow::Cow, fmt};
|
||||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
|
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use matrix_sdk_ui::timeline::{
|
use matrix_sdk_ui::timeline::{
|
||||||
AnyOtherFullStateEventContent, Error as TimelineError, EventTimelineItem, RepliedToEvent,
|
AnyOtherFullStateEventContent, Error as TimelineError, EventSendState, EventTimelineItem,
|
||||||
TimelineDetails, TimelineItemContent,
|
RepliedToEvent, TimelineDetails, TimelineItemContent,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent},
|
events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
|
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
|
||||||
};
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
mod reaction_group;
|
mod reaction_group;
|
||||||
mod reaction_list;
|
mod reaction_list;
|
||||||
|
@ -67,6 +68,18 @@ impl glib::FromVariant for EventKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
|
||||||
|
#[repr(u32)]
|
||||||
|
#[enum_type(name = "MessageState")]
|
||||||
|
pub enum MessageState {
|
||||||
|
#[default]
|
||||||
|
None = 0,
|
||||||
|
Sending = 1,
|
||||||
|
Error = 2,
|
||||||
|
Cancelled = 3,
|
||||||
|
Edited = 4,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, glib::Boxed)]
|
#[derive(Clone, Debug, glib::Boxed)]
|
||||||
#[boxed_type(name = "BoxedEventTimelineItem")]
|
#[boxed_type(name = "BoxedEventTimelineItem")]
|
||||||
pub struct BoxedEventTimelineItem(EventTimelineItem);
|
pub struct BoxedEventTimelineItem(EventTimelineItem);
|
||||||
|
@ -79,7 +92,7 @@ pub struct UserReadReceipt {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
use glib::object::WeakRef;
|
use glib::object::WeakRef;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -99,6 +112,9 @@ mod imp {
|
||||||
|
|
||||||
/// The read receipts on this event.
|
/// The read receipts on this event.
|
||||||
pub read_receipts: gio::ListStore,
|
pub read_receipts: gio::ListStore,
|
||||||
|
|
||||||
|
/// The state of this event.
|
||||||
|
pub state: Cell<MessageState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Event {
|
impl Default for Event {
|
||||||
|
@ -108,6 +124,7 @@ mod imp {
|
||||||
room: Default::default(),
|
room: Default::default(),
|
||||||
reactions: Default::default(),
|
reactions: Default::default(),
|
||||||
read_receipts: gio::ListStore::new::<glib::BoxedAnyObject>(),
|
read_receipts: gio::ListStore::new::<glib::BoxedAnyObject>(),
|
||||||
|
state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +163,9 @@ mod imp {
|
||||||
glib::ParamSpecBoolean::builder("has-read-receipts")
|
glib::ParamSpecBoolean::builder("has-read-receipts")
|
||||||
.read_only()
|
.read_only()
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecEnum::builder::<MessageState>("state")
|
||||||
|
.read_only()
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -179,6 +199,7 @@ mod imp {
|
||||||
"is-highlighted" => obj.is_highlighted().to_value(),
|
"is-highlighted" => obj.is_highlighted().to_value(),
|
||||||
"read-receipts" => obj.read_receipts().to_value(),
|
"read-receipts" => obj.read_receipts().to_value(),
|
||||||
"has-read-receipts" => obj.has_read_receipts().to_value(),
|
"has-read-receipts" => obj.has_read_receipts().to_value(),
|
||||||
|
"state" => obj.state().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,6 +312,7 @@ impl Event {
|
||||||
if self.is_highlighted() != was_highlighted {
|
if self.is_highlighted() != was_highlighted {
|
||||||
self.notify("is-highlighted");
|
self.notify("is-highlighted");
|
||||||
}
|
}
|
||||||
|
self.update_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The raw JSON source for this `Event`, if it has been echoed back
|
/// The raw JSON source for this `Event`, if it has been echoed back
|
||||||
|
@ -447,6 +469,51 @@ impl Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state of this `Event`.
|
||||||
|
pub fn state(&self) -> MessageState {
|
||||||
|
self.imp().state.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the current state of this `Event`.
|
||||||
|
fn compute_state(&self) -> MessageState {
|
||||||
|
let item_ref = self.imp().item.borrow();
|
||||||
|
let Some(item) = item_ref.as_ref() else {
|
||||||
|
return MessageState::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(send_state) = item.send_state() {
|
||||||
|
match send_state {
|
||||||
|
EventSendState::NotSentYet => return MessageState::Sending,
|
||||||
|
EventSendState::SendingFailed { error } => {
|
||||||
|
if self.state() != MessageState::Error {
|
||||||
|
error!("Failed to send message: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageState::Error;
|
||||||
|
}
|
||||||
|
EventSendState::Cancelled => return MessageState::Cancelled,
|
||||||
|
EventSendState::Sent { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match item.content() {
|
||||||
|
TimelineItemContent::Message(msg) if msg.is_edited() => MessageState::Edited,
|
||||||
|
_ => MessageState::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the state of this `Event`.
|
||||||
|
fn update_state(&self) {
|
||||||
|
let state = self.compute_state();
|
||||||
|
|
||||||
|
if self.state() == state {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imp().state.set(state);
|
||||||
|
self.notify("state");
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this `Event` should be highlighted.
|
/// Whether this `Event` should be highlighted.
|
||||||
pub fn is_highlighted(&self) -> bool {
|
pub fn is_highlighted(&self) -> bool {
|
||||||
let item_ref = self.imp().item.borrow();
|
let item_ref = self.imp().item.borrow();
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
use adw::subclass::prelude::*;
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
|
||||||
|
|
||||||
|
use crate::session::model::MessageState;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
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/message_state_stack.ui"
|
||||||
|
)]
|
||||||
|
pub struct MessageStateStack {
|
||||||
|
/// The state that is currently displayed.
|
||||||
|
pub state: Cell<MessageState>,
|
||||||
|
#[template_child]
|
||||||
|
pub stack: TemplateChild<gtk::Stack>,
|
||||||
|
#[template_child]
|
||||||
|
pub error_image: TemplateChild<gtk::Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for MessageStateStack {
|
||||||
|
const NAME: &'static str = "MessageStateStack";
|
||||||
|
type Type = super::MessageStateStack;
|
||||||
|
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 MessageStateStack {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpecEnum::builder::<MessageState>("state")
|
||||||
|
.explicit_notify()
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
match pspec.name() {
|
||||||
|
"state" => {
|
||||||
|
obj.set_state(value.get().unwrap());
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
match pspec.name() {
|
||||||
|
"state" => obj.state().to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WidgetImpl for MessageStateStack {}
|
||||||
|
impl BinImpl for MessageStateStack {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
/// A stack to display the different message states.
|
||||||
|
pub struct MessageStateStack(ObjectSubclass<imp::MessageStateStack>)
|
||||||
|
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageStateStack {
|
||||||
|
/// Create a new `MessageStateStack`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state that is currently displayed.
|
||||||
|
pub fn state(&self) -> MessageState {
|
||||||
|
self.imp().state.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the state to display.
|
||||||
|
pub fn set_state(&self, state: MessageState) {
|
||||||
|
let prev_state = self.state();
|
||||||
|
|
||||||
|
if prev_state == state {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imp = self.imp();
|
||||||
|
let stack = &*imp.stack;
|
||||||
|
match state {
|
||||||
|
MessageState::None => {
|
||||||
|
if matches!(
|
||||||
|
prev_state,
|
||||||
|
MessageState::Sending | MessageState::Error | MessageState::Cancelled
|
||||||
|
) {
|
||||||
|
// Show the sent icon for 2 seconds.
|
||||||
|
stack.set_visible_child_name("sent");
|
||||||
|
|
||||||
|
glib::timeout_add_seconds_local_once(
|
||||||
|
2,
|
||||||
|
clone!(@weak self as obj => move || {
|
||||||
|
obj.set_visible(false);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MessageState::Sending => {
|
||||||
|
stack.set_visible_child_name("sending");
|
||||||
|
self.set_visible(true);
|
||||||
|
}
|
||||||
|
MessageState::Error => {
|
||||||
|
imp.error_image
|
||||||
|
.set_tooltip_text(Some(&gettext("Could not send the message")));
|
||||||
|
stack.set_visible_child_name("error");
|
||||||
|
self.set_visible(true);
|
||||||
|
}
|
||||||
|
MessageState::Cancelled => {
|
||||||
|
imp.error_image
|
||||||
|
.set_tooltip_text(Some(&gettext("An error occurred with the sending queue")));
|
||||||
|
stack.set_visible_child_name("error");
|
||||||
|
self.set_visible(true);
|
||||||
|
}
|
||||||
|
MessageState::Edited => {
|
||||||
|
if matches!(
|
||||||
|
prev_state,
|
||||||
|
MessageState::Sending | MessageState::Error | MessageState::Cancelled
|
||||||
|
) {
|
||||||
|
// Show the sent icon for 2 seconds.
|
||||||
|
stack.set_visible_child_name("sent");
|
||||||
|
|
||||||
|
glib::timeout_add_seconds_local_once(
|
||||||
|
2,
|
||||||
|
clone!(@weak stack => move || {
|
||||||
|
stack.set_visible_child_name("edited");
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stack.set_visible_child_name("edited");
|
||||||
|
self.set_visible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imp.state.set(state);
|
||||||
|
self.notify("state");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="MessageStateStack" parent="AdwBin">
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="stack">
|
||||||
|
<property name="transition-type">crossfade</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">sending</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="Spinner" id="spinner">
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<!-- Translators: As in 'Sending message…'. -->
|
||||||
|
<property name="tooltip-text" translatable="yes">Sending…</property>
|
||||||
|
<accessibility>
|
||||||
|
<!-- Translators: As in 'Sending message…'. -->
|
||||||
|
<property name="label" translatable="yes">Sending…</property>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">error</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkImage" id="error_image">
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="icon-name">error-symbolic</property>
|
||||||
|
<style>
|
||||||
|
<class name="error"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">sent</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="icon-name">done-symbolic</property>
|
||||||
|
<!-- Translators: As in 'Sent message'. -->
|
||||||
|
<property name="tooltip-text" translatable="yes">Sent</property>
|
||||||
|
<accessibility>
|
||||||
|
<!-- Translators: As in 'Sent message'. -->
|
||||||
|
<property name="label" translatable="yes">Sent</property>
|
||||||
|
</accessibility>
|
||||||
|
<style>
|
||||||
|
<class name="success"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">edited</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkImage">
|
||||||
|
<style>
|
||||||
|
<class name="dim-label"/>
|
||||||
|
</style>
|
||||||
|
<property name="icon-name">edit-symbolic</property>
|
||||||
|
<!-- Translators: As in 'Edited message'. -->
|
||||||
|
<property name="tooltip-text" translatable="yes">Edited</property>
|
||||||
|
<accessibility>
|
||||||
|
<!-- Translators: As in 'Edited message'. -->
|
||||||
|
<property name="label" translatable="yes">Edited</property>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -3,24 +3,23 @@ mod content;
|
||||||
mod file;
|
mod file;
|
||||||
mod location;
|
mod location;
|
||||||
mod media;
|
mod media;
|
||||||
|
mod message_state_stack;
|
||||||
mod reaction;
|
mod reaction;
|
||||||
mod reaction_list;
|
mod reaction_list;
|
||||||
mod reply;
|
mod reply;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
use adw::{prelude::*, subclass::prelude::*};
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
use gtk::{
|
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
|
||||||
gdk, glib,
|
|
||||||
glib::{clone, signal::SignalHandlerId},
|
|
||||||
CompositeTemplate,
|
|
||||||
};
|
|
||||||
use matrix_sdk::ruma::events::room::message::MessageType;
|
use matrix_sdk::ruma::events::room::message::MessageType;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub use self::content::{ContentFormat, MessageContent};
|
pub use self::content::{ContentFormat, MessageContent};
|
||||||
use self::{media::MessageMedia, reaction_list::MessageReactionList};
|
use self::{
|
||||||
|
media::MessageMedia, message_state_stack::MessageStateStack, reaction_list::MessageReactionList,
|
||||||
|
};
|
||||||
use super::ReadReceiptsList;
|
use super::ReadReceiptsList;
|
||||||
use crate::{components::Avatar, prelude::*, session::model::Event, Window};
|
use crate::{components::Avatar, prelude::*, session::model::Event, utils::BoundObject, Window};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -46,12 +45,13 @@ mod imp {
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub content: TemplateChild<MessageContent>,
|
pub content: TemplateChild<MessageContent>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
|
pub message_state: TemplateChild<MessageStateStack>,
|
||||||
|
#[template_child]
|
||||||
pub reactions: TemplateChild<MessageReactionList>,
|
pub reactions: TemplateChild<MessageReactionList>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub read_receipts: TemplateChild<ReadReceiptsList>,
|
pub read_receipts: TemplateChild<ReadReceiptsList>,
|
||||||
pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
|
|
||||||
pub bindings: RefCell<Vec<glib::Binding>>,
|
pub bindings: RefCell<Vec<glib::Binding>>,
|
||||||
pub event: RefCell<Option<Event>>,
|
pub event: BoundObject<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -118,6 +118,14 @@ mod imp {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dispose(&self) {
|
||||||
|
self.event.disconnect_signals();
|
||||||
|
|
||||||
|
while let Some(binding) = self.bindings.borrow_mut().pop() {
|
||||||
|
binding.unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImpl for MessageRow {}
|
impl WidgetImpl for MessageRow {}
|
||||||
|
@ -164,20 +172,16 @@ impl MessageRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&self) -> Option<Event> {
|
pub fn event(&self) -> Option<Event> {
|
||||||
self.imp().event.borrow().clone()
|
self.imp().event.obj()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_event(&self, event: Event) {
|
pub fn set_event(&self, event: Event) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
// Remove signals and bindings from the previous event
|
|
||||||
if let Some(event) = imp.event.take() {
|
|
||||||
if let Some(source_changed_handler) = imp.source_changed_handler.take() {
|
|
||||||
event.disconnect(source_changed_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(binding) = imp.bindings.borrow_mut().pop() {
|
// Remove signals and bindings from the previous event.
|
||||||
binding.unbind();
|
imp.event.disconnect_signals();
|
||||||
}
|
while let Some(binding) = imp.bindings.borrow_mut().pop() {
|
||||||
|
binding.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
imp.avatar
|
imp.avatar
|
||||||
|
@ -199,25 +203,30 @@ impl MessageRow {
|
||||||
.sync_create()
|
.sync_create()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
let state_binding = event
|
||||||
|
.bind_property("state", &*imp.message_state, "state")
|
||||||
|
.sync_create()
|
||||||
|
.build();
|
||||||
|
|
||||||
imp.bindings.borrow_mut().append(&mut vec![
|
imp.bindings.borrow_mut().append(&mut vec![
|
||||||
display_name_binding,
|
display_name_binding,
|
||||||
show_header_binding,
|
show_header_binding,
|
||||||
timestamp_binding,
|
timestamp_binding,
|
||||||
|
state_binding,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
imp.source_changed_handler
|
let source_handler = event.connect_notify_local(
|
||||||
.replace(Some(event.connect_notify_local(
|
Some("source"),
|
||||||
Some("source"),
|
clone!(@weak self as obj => move |event, _| {
|
||||||
clone!(@weak self as obj => move |event, _| {
|
obj.update_content(event);
|
||||||
obj.update_content(event);
|
}),
|
||||||
}),
|
);
|
||||||
)));
|
|
||||||
self.update_content(&event);
|
self.update_content(&event);
|
||||||
|
|
||||||
imp.reactions
|
imp.reactions
|
||||||
.set_reaction_list(&event.room().get_or_create_members(), event.reactions());
|
.set_reaction_list(&event.room().get_or_create_members(), event.reactions());
|
||||||
imp.read_receipts.set_source(event.read_receipts());
|
imp.read_receipts.set_source(event.read_receipts());
|
||||||
imp.event.replace(Some(event));
|
imp.event.set(event, vec![source_handler]);
|
||||||
self.notify("event");
|
self.notify("event");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,12 +241,10 @@ impl MessageRow {
|
||||||
|
|
||||||
/// Open the media viewer with the media content of this row.
|
/// Open the media viewer with the media content of this row.
|
||||||
fn show_media(&self) {
|
fn show_media(&self) {
|
||||||
let imp = self.imp();
|
|
||||||
let Some(window) = self.root().and_downcast::<Window>() else {
|
let Some(window) = self.root().and_downcast::<Window>() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let borrowed_event = imp.event.borrow();
|
let Some(event) = self.event() else {
|
||||||
let Some(event) = borrowed_event.as_ref() else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(message) = event.message() else {
|
let Some(message) = event.message() else {
|
||||||
|
@ -245,13 +252,17 @@ impl MessageRow {
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(message, MessageType::Image(_) | MessageType::Video(_)) {
|
if matches!(message, MessageType::Image(_) | MessageType::Video(_)) {
|
||||||
let Some(media_widget) = imp.content.content_widget().and_downcast::<MessageMedia>()
|
let Some(media_widget) = self
|
||||||
|
.imp()
|
||||||
|
.content
|
||||||
|
.content_widget()
|
||||||
|
.and_downcast::<MessageMedia>()
|
||||||
else {
|
else {
|
||||||
warn!("Trying to show media of a non-media message");
|
warn!("Trying to show media of a non-media message");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.session_view().show_media(event, &media_widget);
|
window.session_view().show_media(&event, &media_widget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,22 +62,8 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="MessageStateStack" id="message_state">
|
||||||
<style>
|
<property name="visible">false</property>
|
||||||
<class name="dim-label"/>
|
|
||||||
</style>
|
|
||||||
<property name="icon-name">edit-symbolic</property>
|
|
||||||
<!-- Translators: As in 'Edited message'. -->
|
|
||||||
<property name="tooltip-text" translatable="yes">Edited</property>
|
|
||||||
<accessibility>
|
|
||||||
<!-- Translators: As in 'Edited message'. -->
|
|
||||||
<property name="label" translatable="yes">Edited</property>
|
|
||||||
</accessibility>
|
|
||||||
<binding name="visible">
|
|
||||||
<lookup name="is-edited" type="RoomEvent">
|
|
||||||
<lookup name="event">ContentMessageRow</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">2</property>
|
<property name="column">2</property>
|
||||||
<property name="row">1</property>
|
<property name="row">1</property>
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/file.ui</file>
|
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/file.ui</file>
|
||||||
<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/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/media.ui</file>
|
||||||
|
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/message_row/message_state_stack.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/mod.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/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_popover.ui</file>
|
||||||
|
|
Loading…
Reference in a new issue