fractal/utils/matrix/
ext_traits.rsuse std::borrow::Cow;
use gtk::{glib, prelude::*};
use matrix_sdk_ui::timeline::{
AnyOtherFullStateEventContent, EventTimelineItem, Message, TimelineEventItemId,
TimelineItemContent,
};
use ruma::{
events::{room::message::MessageType, AnySyncTimelineEvent},
serde::Raw,
};
use serde::Deserialize;
pub(crate) trait AtMentionExt {
fn can_contain_at_room(&self) -> bool;
}
impl AtMentionExt for TimelineItemContent {
fn can_contain_at_room(&self) -> bool {
match self {
TimelineItemContent::Message(msg) => msg.can_contain_at_room(),
_ => false,
}
}
}
impl AtMentionExt for Message {
fn can_contain_at_room(&self) -> bool {
let Some(mentions) = self.mentions() else {
return true;
};
mentions.room
}
}
pub(crate) trait TimelineEventItemIdExt: Sized {
fn static_variant_type() -> Cow<'static, glib::VariantTy>;
fn to_variant(&self) -> glib::Variant;
fn from_variant(variant: &glib::Variant) -> Option<Self>;
}
impl TimelineEventItemIdExt for TimelineEventItemId {
fn static_variant_type() -> Cow<'static, glib::VariantTy> {
Cow::Borrowed(glib::VariantTy::STRING)
}
fn to_variant(&self) -> glib::Variant {
let s = match self {
Self::TransactionId(txn_id) => format!("transaction_id:{txn_id}"),
Self::EventId(event_id) => format!("event_id:{event_id}"),
};
s.to_variant()
}
fn from_variant(variant: &glib::Variant) -> Option<Self> {
let s = variant.str()?;
if let Some(s) = s.strip_prefix("transaction_id:") {
Some(Self::TransactionId(s.into()))
} else if let Some(s) = s.strip_prefix("event_id:") {
s.try_into().ok().map(Self::EventId)
} else {
None
}
}
}
pub(crate) trait TimelineItemContentExt {
fn counts_as_unread(&self) -> bool;
fn can_show_header(&self) -> bool;
fn is_edited(&self) -> bool;
}
impl TimelineItemContentExt for TimelineItemContent {
fn counts_as_unread(&self) -> bool {
match self {
TimelineItemContent::Message(message) => {
!matches!(message.msgtype(), MessageType::Notice(_))
}
TimelineItemContent::Sticker(_) => true,
TimelineItemContent::OtherState(state) => matches!(
state.content(),
AnyOtherFullStateEventContent::RoomTombstone(_)
),
_ => false,
}
}
fn can_show_header(&self) -> bool {
match self {
TimelineItemContent::Message(message) => {
matches!(
message.msgtype(),
MessageType::Audio(_)
| MessageType::File(_)
| MessageType::Image(_)
| MessageType::Location(_)
| MessageType::Notice(_)
| MessageType::Text(_)
| MessageType::Video(_)
)
}
TimelineItemContent::Sticker(_) => true,
_ => false,
}
}
fn is_edited(&self) -> bool {
match self {
TimelineItemContent::Message(msg) => msg.is_edited(),
_ => false,
}
}
}
pub(crate) trait EventTimelineItemExt {
fn latest_edit_raw(&self) -> Option<Raw<AnySyncTimelineEvent>>;
}
impl EventTimelineItemExt for EventTimelineItem {
fn latest_edit_raw(&self) -> Option<Raw<AnySyncTimelineEvent>> {
if let Some(raw) = self.latest_edit_json() {
return Some(raw.clone());
}
self.original_json()?
.get_field::<RawUnsigned>("unsigned")
.ok()
.flatten()?
.relations?
.replace
}
}
#[derive(Debug, Clone, Deserialize)]
struct RawUnsigned {
#[serde(rename = "m.relations")]
relations: Option<RawBundledRelations>,
}
#[derive(Debug, Clone, Deserialize)]
struct RawBundledRelations {
#[serde(rename = "m.replace")]
replace: Option<Raw<AnySyncTimelineEvent>>,
}