[fractal] event: Subclass events to separate those that fail to deserialize



commit 36eee80e4feae39e9892ec6af41bc37d4d300bc9
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Mon Jun 13 13:44:47 2022 +0200

    event: Subclass events to separate those that fail to deserialize

 src/prelude.rs                                     |   5 +-
 src/session/content/room_history/item_row.rs       |  30 +-
 .../content/room_history/message_row/mod.rs        |  18 +-
 src/session/content/room_history/mod.rs            |   4 +-
 src/session/media_viewer.rs                        |  12 +-
 src/session/mod.rs                                 |   4 +-
 src/session/room/event/mod.rs                      | 419 +++++++++++++++++
 .../room/{event.rs => event/supported_event.rs}    | 519 ++++++++-------------
 src/session/room/event/unsupported_event.rs        |  56 +++
 src/session/room/event_actions.rs                  | 227 ++++-----
 src/session/room/mod.rs                            |  30 +-
 src/session/room/reaction_group.rs                 |  12 +-
 src/session/room/reaction_list.rs                  |  12 +-
 src/session/room/timeline/mod.rs                   | 184 ++++----
 src/session/room/timeline/timeline_item.rs         |  26 +-
 15 files changed, 970 insertions(+), 588 deletions(-)
---
diff --git a/src/prelude.rs b/src/prelude.rs
index e9c522d6a..5b22279de 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -1 +1,4 @@
-pub use crate::session::UserExt;
+pub use crate::session::{
+    room::{EventExt, TimelineItemExt},
+    UserExt,
+};
diff --git a/src/session/content/room_history/item_row.rs b/src/session/content/room_history/item_row.rs
index cd32f29e1..2711d10c5 100644
--- a/src/session/content/room_history/item_row.rs
+++ b/src/session/content/room_history/item_row.rs
@@ -5,11 +5,12 @@ use matrix_sdk::ruma::events::AnySyncRoomEvent;
 
 use crate::{
     components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
+    prelude::*,
     session::{
         content::room_history::{message_row::MessageRow, DividerRow, RoomHistory, StateRow},
         room::{
-            Event, EventActions, TimelineDayDivider, TimelineItem, TimelineNewMessagesDivider,
-            TimelineSpinner,
+            Event, EventActions, SupportedEvent, TimelineDayDivider, TimelineItem,
+            TimelineNewMessagesDivider, TimelineSpinner,
         },
     },
 };
@@ -95,7 +96,7 @@ mod imp {
                 .item
                 .borrow()
                 .as_ref()
-                .and_then(|item| item.downcast_ref::<Event>())
+                .and_then(|item| item.downcast_ref::<SupportedEvent>())
             {
                 if let Some(handler) = self.notify_handler.borrow_mut().take() {
                     event.disconnect(handler);
@@ -113,7 +114,10 @@ mod imp {
                 let room_history = obj.room_history();
                 let popover = room_history.item_context_menu().to_owned();
 
-                if event.content().is_some() {
+                if let Some(event) = event
+                    .downcast_ref::<SupportedEvent>()
+                    .filter(|event| event.content().is_some())
+                {
                     let menu_model = Self::Type::event_message_menu_model();
                     let reaction_chooser = room_history.item_reaction_chooser();
                     if popover.menu_model().as_ref() != Some(menu_model) {
@@ -189,7 +193,7 @@ impl ItemRow {
             .item
             .borrow()
             .as_ref()
-            .and_then(|item| item.downcast_ref::<Event>())
+            .and_then(|item| item.downcast_ref::<SupportedEvent>())
         {
             if let Some(handler) = priv_.notify_handler.borrow_mut().take() {
                 event.disconnect(handler);
@@ -199,15 +203,13 @@ impl ItemRow {
         }
 
         if let Some(ref item) = item {
-            if let Some(event) = item.downcast_ref::<Event>() {
-                self.set_action_group(self.set_event_actions(Some(event)));
+            if let Some(event) = item.downcast_ref::<SupportedEvent>() {
+                self.set_action_group(self.set_event_actions(Some(event.upcast_ref())));
 
-                let notify_handler = event.connect_notify_local(
-                    Some("event"),
-                    clone!(@weak self as obj => move |event, _| {
+                let notify_handler =
+                    event.connect_pure_event_notify(clone!(@weak self as obj => move |event| {
                         obj.set_event_widget(event);
-                    }),
-                );
+                    }));
                 priv_.notify_handler.replace(Some(notify_handler));
 
                 self.set_event_widget(event);
@@ -265,9 +267,9 @@ impl ItemRow {
         priv_.item.replace(item);
     }
 
-    fn set_event_widget(&self, event: &Event) {
+    fn set_event_widget(&self, event: &SupportedEvent) {
         match event.matrix_event() {
-            Some(AnySyncRoomEvent::State(state)) => {
+            AnySyncRoomEvent::State(state) => {
                 let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::<StateRow>())
                 {
                     child
diff --git a/src/session/content/room_history/message_row/mod.rs 
b/src/session/content/room_history/message_row/mod.rs
index 2804a4014..8668aaf28 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -25,7 +25,7 @@ use self::{
     reaction_list::MessageReactionList, reply::MessageReply, text::MessageText,
 };
 use crate::{
-    components::Avatar, prelude::*, session::room::Event, spawn, utils::filename_for_mime,
+    components::Avatar, prelude::*, session::room::SupportedEvent, spawn, utils::filename_for_mime,
 };
 
 mod imp {
@@ -53,7 +53,7 @@ mod imp {
         pub reactions: TemplateChild<MessageReactionList>,
         pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
         pub bindings: RefCell<Vec<glib::Binding>>,
-        pub event: RefCell<Option<Event>>,
+        pub event: RefCell<Option<SupportedEvent>>,
     }
 
     #[glib::object_subclass]
@@ -144,7 +144,7 @@ impl MessageRow {
         self.notify("show-header");
     }
 
-    pub fn set_event(&self, event: Event) {
+    pub fn set_event(&self, event: SupportedEvent) {
         let priv_ = self.imp();
         // Remove signals and bindings from the previous event
         if let Some(event) = priv_.event.take() {
@@ -195,14 +195,20 @@ impl MessageRow {
         priv_.event.replace(Some(event));
     }
 
-    fn update_content(&self, event: &Event) {
+    fn update_content(&self, event: &SupportedEvent) {
         if event.is_reply() {
             spawn!(
                 glib::PRIORITY_HIGH,
                 clone!(@weak self as obj, @weak event => async move {
                     let priv_ = obj.imp();
 
-                    if let Ok(Some(related_event)) = event.reply_to_event().await {
+                    if let Some(related_event) = event
+                        .reply_to_event()
+                        .await
+                        .ok()
+                        .flatten()
+                        .and_then(|event| event.downcast::<SupportedEvent>().ok())
+                    {
                         let reply = MessageReply::new();
                         reply.set_related_content_sender(related_event.sender().upcast());
                         build_content(reply.related_content(), &related_event, true);
@@ -229,7 +235,7 @@ impl Default for MessageRow {
 ///
 /// If `compact` is true, the content should appear in a smaller format without
 /// interactions, if possible.
-fn build_content(parent: &adw::Bin, event: &Event, compact: bool) {
+fn build_content(parent: &adw::Bin, event: &SupportedEvent, compact: bool) {
     match event.content() {
         Some(AnyMessageLikeEventContent::RoomMessage(message)) => {
             let msgtype = if let Some(Relation::Replacement(replacement)) = message.relates_to {
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index d2d0ca337..a4e72a43e 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -37,7 +37,7 @@ use crate::{
     i18n::gettext_f,
     session::{
         content::{MarkdownPopover, RoomDetails},
-        room::{Event, Room, RoomType, Timeline, TimelineItem, TimelineState},
+        room::{Room, RoomType, SupportedEvent, Timeline, TimelineItem, TimelineState},
         user::UserExt,
     },
     spawn, toast,
@@ -280,7 +280,7 @@ mod imp {
                         .model()
                         .and_then(|model| model.item(pos))
                         .as_ref()
-                        .and_then(|o| o.downcast_ref::<Event>())
+                        .and_then(|o| o.downcast_ref::<SupportedEvent>())
                     {
                         if let Some(room) = obj.room() {
                             room.session().show_media(event);
diff --git a/src/session/media_viewer.rs b/src/session/media_viewer.rs
index 4a5bdce3a..3f67d5560 100644
--- a/src/session/media_viewer.rs
+++ b/src/session/media_viewer.rs
@@ -6,7 +6,7 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
 use super::room::EventActions;
 use crate::{
     components::{ContentType, MediaContentViewer},
-    session::room::Event,
+    session::room::SupportedEvent,
     spawn,
     utils::cache_dir,
     Window,
@@ -24,7 +24,7 @@ mod imp {
     #[template(resource = "/org/gnome/Fractal/media-viewer.ui")]
     pub struct MediaViewer {
         pub fullscreened: Cell<bool>,
-        pub event: RefCell<Option<WeakRef<Event>>>,
+        pub event: RefCell<Option<WeakRef<SupportedEvent>>>,
         pub body: RefCell<Option<String>>,
         #[template_child]
         pub flap: TemplateChild<adw::Flap>,
@@ -91,7 +91,7 @@ mod imp {
                         "event",
                         "Event",
                         "The media event to display",
-                        Event::static_type(),
+                        SupportedEvent::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                     glib::ParamSpecString::new(
@@ -164,7 +164,7 @@ impl MediaViewer {
         glib::Object::new(&[]).expect("Failed to create MediaViewer")
     }
 
-    pub fn event(&self) -> Option<Event> {
+    pub fn event(&self) -> Option<SupportedEvent> {
         self.imp()
             .event
             .borrow()
@@ -172,7 +172,7 @@ impl MediaViewer {
             .and_then(|event| event.upgrade())
     }
 
-    pub fn set_event(&self, event: Option<Event>) {
+    pub fn set_event(&self, event: Option<SupportedEvent>) {
         if event == self.event() {
             return;
         }
@@ -226,7 +226,7 @@ impl MediaViewer {
         self.imp().media.show_loading();
 
         if let Some(event) = self.event() {
-            self.set_event_actions(Some(&event));
+            self.set_event_actions(Some(event.upcast_ref()));
             if let Some(AnyMessageLikeEventContent::RoomMessage(content)) = event.content() {
                 match content.msgtype {
                     MessageType::Image(image) => {
diff --git a/src/session/mod.rs b/src/session/mod.rs
index b3e17d58d..d26c049b7 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -63,7 +63,7 @@ use self::{
 };
 pub use self::{
     avatar::Avatar,
-    room::{Event, Room},
+    room::{Room, SupportedEvent},
     room_creation::RoomCreation,
     user::{User, UserActions, UserExt},
 };
@@ -896,7 +896,7 @@ impl Session {
     }
 
     /// Show a media event
-    pub fn show_media(&self, event: &Event) {
+    pub fn show_media(&self, event: &SupportedEvent) {
         let priv_ = self.imp();
         priv_.media_viewer.set_event(Some(event.clone()));
 
diff --git a/src/session/room/event/mod.rs b/src/session/room/event/mod.rs
new file mode 100644
index 000000000..004cc3441
--- /dev/null
+++ b/src/session/room/event/mod.rs
@@ -0,0 +1,419 @@
+use gtk::{glib, prelude::*, subclass::prelude::*};
+use log::warn;
+use matrix_sdk::{
+    deserialized_responses::SyncRoomEvent,
+    ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId},
+};
+
+use super::{
+    timeline::{TimelineItem, TimelineItemImpl},
+    Member, Room,
+};
+
+mod supported_event;
+mod unsupported_event;
+
+pub use supported_event::SupportedEvent;
+pub use unsupported_event::UnsupportedEvent;
+
+#[derive(Clone, Debug, glib::Boxed)]
+#[boxed_type(name = "BoxedSyncRoomEvent")]
+pub struct BoxedSyncRoomEvent(SyncRoomEvent);
+
+mod imp {
+    use std::cell::RefCell;
+
+    use glib::{object::WeakRef, Class};
+    use once_cell::{sync::Lazy, unsync::OnceCell};
+
+    use super::*;
+
+    #[repr(C)]
+    pub struct EventClass {
+        pub parent_class: Class<TimelineItem>,
+        pub source: fn(&super::Event) -> String,
+        pub event_id: fn(&super::Event) -> Option<OwnedEventId>,
+        pub sender_id: fn(&super::Event) -> Option<OwnedUserId>,
+        pub origin_server_ts: fn(&super::Event) -> Option<MilliSecondsSinceUnixEpoch>,
+    }
+
+    unsafe impl ClassStruct for EventClass {
+        type Type = Event;
+    }
+
+    pub(super) fn event_source(this: &super::Event) -> String {
+        let klass = this.class();
+        (klass.as_ref().source)(this)
+    }
+
+    pub(super) fn event_event_id(this: &super::Event) -> Option<OwnedEventId> {
+        let klass = this.class();
+        (klass.as_ref().event_id)(this)
+    }
+
+    pub(super) fn event_sender_id(this: &super::Event) -> Option<OwnedUserId> {
+        let klass = this.class();
+        (klass.as_ref().sender_id)(this)
+    }
+
+    pub(super) fn event_origin_server_ts(
+        this: &super::Event,
+    ) -> Option<MilliSecondsSinceUnixEpoch> {
+        let klass = this.class();
+        (klass.as_ref().origin_server_ts)(this)
+    }
+
+    #[derive(Debug, Default)]
+    pub struct Event {
+        /// The SDK event containing encryption information and the serialized
+        /// event as `Raw`.
+        pub pure_event: RefCell<Option<SyncRoomEvent>>,
+
+        /// The room containing this `Event`.
+        pub room: OnceCell<WeakRef<Room>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for Event {
+        const NAME: &'static str = "RoomEvent";
+        const ABSTRACT: bool = true;
+        type Type = super::Event;
+        type ParentType = TimelineItem;
+        type Class = EventClass;
+    }
+
+    impl ObjectImpl for Event {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![
+                    glib::ParamSpecBoxed::new(
+                        "pure-event",
+                        "Pure Event",
+                        "The pure Matrix event of this Event",
+                        BoxedSyncRoomEvent::static_type(),
+                        glib::ParamFlags::WRITABLE,
+                    ),
+                    glib::ParamSpecString::new(
+                        "source",
+                        "Source",
+                        "The JSON source of this Event",
+                        None,
+                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                    glib::ParamSpecObject::new(
+                        "room",
+                        "Room",
+                        "The room containing this Event",
+                        Room::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpecString::new(
+                        "time",
+                        "Time",
+                        "The locally formatted time of this Matrix event",
+                        None,
+                        glib::ParamFlags::READABLE,
+                    ),
+                ]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "pure-event" => {
+                    let event = value.get::<BoxedSyncRoomEvent>().unwrap();
+                    obj.set_pure_event(event.0);
+                }
+                "room" => {
+                    self.room
+                        .set(value.get::<Room>().unwrap().downgrade())
+                        .unwrap();
+                }
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "source" => obj.source().to_value(),
+                "room" => obj.room().to_value(),
+                "time" => obj.time().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl TimelineItemImpl for Event {
+        fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
+            Some(obj.room().members().member_by_id(obj.sender_id()?))
+        }
+
+        fn selectable(&self, _obj: &Self::Type) -> bool {
+            true
+        }
+    }
+}
+
+glib::wrapper! {
+    /// GObject representation of a Matrix room event.
+    pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
+}
+
+impl Event {
+    /// Create an `Event` with the given pure SDK event and room.
+    ///
+    /// Constructs the proper subtype according to the event.
+    pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
+        SupportedEvent::try_from_event(pure_event.clone(), room)
+            .map(|event| event.upcast())
+            .unwrap_or_else(|_| {
+                warn!("Failed to deserialize event: {:?}", pure_event);
+                UnsupportedEvent::new(pure_event, room).upcast()
+            })
+    }
+}
+
+/// Public trait containing implemented methods for everything that derives from
+/// `Event`.
+///
+/// To override the behavior of these methods, override the corresponding method
+/// of `EventImpl`.
+pub trait EventExt: 'static {
+    /// The `Room` where this `Event` was sent.
+    fn room(&self) -> Room;
+
+    /// The pure SDK event of this `Event`.
+    fn pure_event(&self) -> SyncRoomEvent;
+
+    /// Set the pure SDK event of this `Event`.
+    fn set_pure_event(&self, pure_event: SyncRoomEvent);
+
+    /// The source JSON of this `Event`.
+    fn original_source(&self) -> String;
+
+    /// The source JSON displayed for this `Event`.
+    ///
+    /// Defaults to the `original_source`.
+    fn source(&self) -> String;
+
+    /// The event ID of this `Event`, if it was found.
+    fn event_id(&self) -> Option<OwnedEventId>;
+
+    /// The user ID of the sender of this `Event`, if it was found.
+    fn sender_id(&self) -> Option<OwnedUserId>;
+
+    /// The timestamp on the origin server when this `Event` was sent as
+    /// `MilliSecondsSinceUnixEpoch`, if it was found.
+    fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
+
+    /// The timestamp on the origin server when this `Event` was sent as
+    /// `glib::DateTime`.
+    ///
+    /// This is computed from the `origin_server_ts`.
+    fn timestamp(&self) -> Option<glib::DateTime> {
+        glib::DateTime::from_unix_utc(self.origin_server_ts()?.as_secs().into())
+            .and_then(|t| t.to_local())
+            .ok()
+    }
+
+    /// The formatted time when this `Event` was sent.
+    ///
+    /// This is computed from the `origin_server_ts`.
+    fn time(&self) -> Option<String> {
+        let datetime = self.timestamp()?;
+
+        // FIXME Is there a cleaner to find out if we should use 24h format?
+        let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
+
+        let time = if local_time.ends_with("am") || local_time.ends_with("pm") {
+            // Use 12h time format (AM/PM)
+            datetime.format("%l∶%M %p").unwrap().to_string()
+        } else {
+            // Use 24 time format
+            datetime.format("%R").unwrap().to_string()
+        };
+        Some(time)
+    }
+
+    fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId;
+}
+
+impl<O: IsA<Event>> EventExt for O {
+    fn room(&self) -> Room {
+        self.upcast_ref()
+            .imp()
+            .room
+            .get()
+            .unwrap()
+            .upgrade()
+            .unwrap()
+    }
+
+    fn pure_event(&self) -> SyncRoomEvent {
+        self.upcast_ref().imp().pure_event.borrow().clone().unwrap()
+    }
+
+    fn set_pure_event(&self, pure_event: SyncRoomEvent) {
+        let priv_ = self.upcast_ref().imp();
+        priv_.pure_event.replace(Some(pure_event));
+
+        self.notify("pure-event");
+        self.notify("source");
+    }
+
+    fn original_source(&self) -> String {
+        let pure_event = self.upcast_ref().imp().pure_event.borrow();
+        let raw = pure_event.as_ref().unwrap().event.json().get();
+
+        // We have to convert it to a Value, because a RawValue cannot be
+        // pretty-printed.
+        if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw) {
+            serde_json::to_string_pretty(&json).unwrap()
+        } else {
+            raw.to_owned()
+        }
+    }
+
+    fn source(&self) -> String {
+        imp::event_source(self.upcast_ref())
+    }
+
+    fn event_id(&self) -> Option<OwnedEventId> {
+        imp::event_event_id(self.upcast_ref())
+    }
+
+    fn sender_id(&self) -> Option<OwnedUserId> {
+        imp::event_sender_id(self.upcast_ref())
+    }
+
+    fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
+        imp::event_origin_server_ts(self.upcast_ref())
+    }
+
+    fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+        self.connect_notify_local(Some("pure-event"), move |this, _| {
+            f(this);
+        })
+    }
+}
+
+/// Public trait that must be implemented for everything that derives from
+/// `Event`.
+///
+/// Overriding a method from this trait overrides also its behavior in
+/// `EventExt`.
+pub trait EventImpl: ObjectImpl {
+    fn source(&self, obj: &Self::Type) -> String {
+        obj.dynamic_cast_ref::<Event>()
+            .map(|event| event.original_source())
+            .unwrap_or_default()
+    }
+
+    fn event_id(&self, obj: &Self::Type) -> Option<OwnedEventId> {
+        obj.dynamic_cast_ref::<Event>().and_then(|event| {
+            event
+                .imp()
+                .pure_event
+                .borrow()
+                .as_ref()
+                .unwrap()
+                .event
+                .get_field::<OwnedEventId>("event_id")
+                .ok()
+                .flatten()
+        })
+    }
+
+    fn sender_id(&self, obj: &Self::Type) -> Option<OwnedUserId> {
+        obj.dynamic_cast_ref::<Event>().and_then(|event| {
+            event
+                .imp()
+                .pure_event
+                .borrow()
+                .as_ref()
+                .unwrap()
+                .event
+                .get_field::<OwnedUserId>("sender")
+                .ok()
+                .flatten()
+        })
+    }
+
+    fn origin_server_ts(&self, obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
+        obj.dynamic_cast_ref::<Event>().and_then(|event| {
+            event
+                .imp()
+                .pure_event
+                .borrow()
+                .as_ref()
+                .unwrap()
+                .event
+                .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
+                .ok()
+                .flatten()
+        })
+    }
+}
+
+// Make `Event` subclassable.
+unsafe impl<T> IsSubclassable<T> for Event
+where
+    T: TimelineItemImpl + EventImpl,
+    T::Type: IsA<TimelineItem> + IsA<Event>,
+{
+    fn class_init(class: &mut glib::Class<Self>) {
+        Self::parent_class_init::<T>(class.upcast_ref_mut());
+
+        let klass = class.as_mut();
+
+        klass.source = source_trampoline::<T>;
+        klass.event_id = event_id_trampoline::<T>;
+        klass.sender_id = sender_id_trampoline::<T>;
+        klass.origin_server_ts = origin_server_ts_trampoline::<T>;
+    }
+}
+
+// Virtual method implementation trampolines.
+fn source_trampoline<T>(this: &Event) -> String
+where
+    T: ObjectSubclass + EventImpl,
+    T::Type: IsA<Event>,
+{
+    let this = this.downcast_ref::<T::Type>().unwrap();
+    this.imp().source(this)
+}
+
+fn event_id_trampoline<T>(this: &Event) -> Option<OwnedEventId>
+where
+    T: ObjectSubclass + EventImpl,
+    T::Type: IsA<Event>,
+{
+    let this = this.downcast_ref::<T::Type>().unwrap();
+    this.imp().event_id(this)
+}
+
+fn sender_id_trampoline<T>(this: &Event) -> Option<OwnedUserId>
+where
+    T: ObjectSubclass + EventImpl,
+    T::Type: IsA<Event>,
+{
+    let this = this.downcast_ref::<T::Type>().unwrap();
+    this.imp().sender_id(this)
+}
+
+fn origin_server_ts_trampoline<T>(this: &Event) -> Option<MilliSecondsSinceUnixEpoch>
+where
+    T: ObjectSubclass + EventImpl,
+    T::Type: IsA<Event>,
+{
+    let this = this.downcast_ref::<T::Type>().unwrap();
+    this.imp().origin_server_ts(this)
+}
diff --git a/src/session/room/event.rs b/src/session/room/event/supported_event.rs
similarity index 58%
rename from src/session/room/event.rs
rename to src/session/room/event/supported_event.rs
index 5d91b1339..e5496831e 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event/supported_event.rs
@@ -1,9 +1,4 @@
-use gtk::{
-    glib,
-    glib::{clone, DateTime},
-    prelude::*,
-    subclass::prelude::*,
-};
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
 use log::warn;
 use matrix_sdk::{
     deserialized_responses::SyncRoomEvent,
@@ -16,89 +11,67 @@ use matrix_sdk::{
                 redaction::SyncRoomRedactionEvent,
             },
             AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncRoomEvent,
-            AnySyncStateEvent, MessageLikeUnsigned, SyncMessageLikeEvent, SyncStateEvent,
+            AnySyncStateEvent, SyncMessageLikeEvent, SyncStateEvent,
         },
         serde::Raw,
         MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
     },
     Error as MatrixError,
 };
+use serde_json::Error as JsonError;
 
-use super::{
-    timeline::{TimelineItem, TimelineItemImpl},
-    Member, ReactionList, Room,
-};
+use super::{BoxedSyncRoomEvent, Event, EventImpl};
 use crate::{
+    prelude::*,
+    session::room::{
+        timeline::{TimelineItem, TimelineItemImpl},
+        Member, ReactionList, Room,
+    },
     spawn, spawn_tokio,
     utils::{filename_for_mime, media_type_uid},
 };
 
 #[derive(Clone, Debug, glib::Boxed)]
-#[boxed_type(name = "BoxedSyncRoomEvent")]
-pub struct BoxedSyncRoomEvent(SyncRoomEvent);
+#[boxed_type(name = "BoxedAnySyncRoomEvent")]
+pub struct BoxedAnySyncRoomEvent(AnySyncRoomEvent);
 
 mod imp {
     use std::cell::RefCell;
 
-    use glib::{object::WeakRef, SignalHandlerId};
-    use once_cell::{sync::Lazy, unsync::OnceCell};
+    use glib::SignalHandlerId;
+    use once_cell::sync::Lazy;
 
     use super::*;
 
     #[derive(Debug, Default)]
-    pub struct Event {
-        /// The deserialized matrix event
-        pub event: RefCell<Option<AnySyncRoomEvent>>,
-        /// The SDK event containing encryption information and the serialized
-        /// event as `Raw`
-        pub pure_event: RefCell<Option<SyncRoomEvent>>,
+    pub struct SupportedEvent {
+        /// The deserialized Matrix event.
+        pub matrix_event: RefCell<Option<AnySyncRoomEvent>>,
         /// Events that replace this one, in the order they arrive.
-        pub replacing_events: RefCell<Vec<super::Event>>,
+        pub replacing_events: RefCell<Vec<super::SupportedEvent>>,
         pub reactions: ReactionList,
-        pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
         pub keys_handle: RefCell<Option<SignalHandlerId>>,
-        pub room: OnceCell<WeakRef<Room>>,
+        pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
     }
 
     #[glib::object_subclass]
-    impl ObjectSubclass for Event {
-        const NAME: &'static str = "RoomEvent";
-        type Type = super::Event;
-        type ParentType = TimelineItem;
+    impl ObjectSubclass for SupportedEvent {
+        const NAME: &'static str = "RoomSupportedEvent";
+        type Type = super::SupportedEvent;
+        type ParentType = Event;
     }
 
-    impl ObjectImpl for Event {
+    impl ObjectImpl for SupportedEvent {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
                 vec![
                     glib::ParamSpecBoxed::new(
-                        "event",
-                        "event",
-                        "The matrix event of this Event",
-                        BoxedSyncRoomEvent::static_type(),
+                        "matrix-event",
+                        "Matrix Event",
+                        "The deserialized Matrix event of this Event",
+                        BoxedAnySyncRoomEvent::static_type(),
                         glib::ParamFlags::WRITABLE,
                     ),
-                    glib::ParamSpecString::new(
-                        "source",
-                        "Source",
-                        "The source (JSON) of this Event",
-                        None,
-                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                    ),
-                    glib::ParamSpecObject::new(
-                        "room",
-                        "Room",
-                        "The room containing this event",
-                        Room::static_type(),
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
-                    ),
-                    glib::ParamSpecString::new(
-                        "time",
-                        "Time",
-                        "The locally formatted time of this matrix event",
-                        None,
-                        glib::ParamFlags::READABLE,
-                    ),
                     glib::ParamSpecObject::new(
                         "reactions",
                         "Reactions",
@@ -120,14 +93,9 @@ mod imp {
             pspec: &glib::ParamSpec,
         ) {
             match pspec.name() {
-                "event" => {
-                    let event = value.get::<BoxedSyncRoomEvent>().unwrap();
-                    obj.set_matrix_pure_event(event.0);
-                }
-                "room" => {
-                    self.room
-                        .set(value.get::<Room>().unwrap().downgrade())
-                        .unwrap();
+                "matrix-event" => {
+                    let matrix_event = value.get::<BoxedAnySyncRoomEvent>().unwrap();
+                    obj.set_matrix_event(matrix_event.0);
                 }
                 _ => unimplemented!(),
             }
@@ -135,20 +103,13 @@ mod imp {
 
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
-                "source" => obj.source().to_value(),
-                "room" => obj.room().to_value(),
-                "time" => obj.time().to_value(),
                 "reactions" => obj.reactions().to_value(),
                 _ => unimplemented!(),
             }
         }
     }
 
-    impl TimelineItemImpl for Event {
-        fn selectable(&self, _obj: &Self::Type) -> bool {
-            true
-        }
-
+    impl TimelineItemImpl for SupportedEvent {
         fn activatable(&self, obj: &Self::Type) -> bool {
             match obj.original_content() {
                 // The event can be activated to open the media viewer if it's an image or a video.
@@ -181,72 +142,79 @@ mod imp {
             }
         }
 
-        fn sender(&self, obj: &Self::Type) -> Option<Member> {
-            Some(obj.room().members().member_by_id(obj.matrix_sender()))
+        fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
+            Some(obj.sender())
+        }
+    }
+
+    impl EventImpl for SupportedEvent {
+        fn source(&self, obj: &Self::Type) -> String {
+            obj.replacement()
+                .map(|replacement| replacement.source())
+                .unwrap_or_else(|| obj.original_source())
+        }
+
+        fn origin_server_ts(&self, _obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
+            Some(
+                self.matrix_event
+                    .borrow()
+                    .as_ref()
+                    .unwrap()
+                    .origin_server_ts(),
+            )
         }
     }
 }
 
 glib::wrapper! {
-    /// GObject representation of a Matrix room event.
-    pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
+    /// GObject representation of a supported Matrix room event.
+    pub struct SupportedEvent(ObjectSubclass<imp::SupportedEvent>) @extends TimelineItem, Event;
 }
 
 // TODO:
-// - [ ] implement operations for events: forward, reply, delete...
-
-impl Event {
-    pub fn new(event: SyncRoomEvent, room: &Room) -> Self {
-        let event = BoxedSyncRoomEvent(event);
-        glib::Object::new(&[("event", &event), ("room", room)]).expect("Failed to create Event")
-    }
-
-    pub fn sender(&self) -> Member {
-        self.room().members().member_by_id(self.matrix_sender())
-    }
-
-    pub fn room(&self) -> Room {
-        self.imp().room.get().unwrap().upgrade().unwrap()
-    }
+// - [ ] implement operations for events: forward, reply, edit...
 
-    /// Get the matrix event
+impl SupportedEvent {
+    /// Try to construct a new `SupportedEvent` with the given pure event and
+    /// room.
     ///
-    /// If the `SyncRoomEvent` couldn't be deserialized this is `None`
-    pub fn matrix_event(&self) -> Option<AnySyncRoomEvent> {
-        self.imp().event.borrow().clone()
-    }
-
-    pub fn matrix_pure_event(&self) -> SyncRoomEvent {
-        self.imp().pure_event.borrow().clone().unwrap()
-    }
-
-    pub fn set_matrix_pure_event(&self, event: SyncRoomEvent) {
-        let priv_ = self.imp();
-
-        if let Ok(deserialized) = event.event.deserialize() {
-            if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
-                SyncMessageLikeEvent::Original(_),
-            )) = deserialized
-            {
-                let raw_event = event.event.clone();
-                spawn!(clone!(@weak self as obj => async move {
-                    obj.try_to_decrypt(raw_event.cast()).await;
-                }));
-            }
-
-            priv_.event.replace(Some(deserialized));
-        } else {
-            warn!("Failed to deserialize event: {:?}", event);
+    /// Returns an error if the pure event fails to deserialize.
+    pub fn try_from_event(pure_event: SyncRoomEvent, room: &Room) -> Result<Self, JsonError> {
+        let matrix_event = BoxedAnySyncRoomEvent(pure_event.event.deserialize()?);
+        let pure_event = BoxedSyncRoomEvent(pure_event);
+        Ok(glib::Object::new(&[
+            ("pure-event", &pure_event),
+            ("matrix-event", &matrix_event),
+            ("room", room),
+        ])
+        .expect("Failed to create SupportedEvent"))
+    }
+
+    /// Set the deserialized Matrix event of this `SupportedEvent`.
+    fn set_matrix_event(&self, matrix_event: AnySyncRoomEvent) {
+        if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
+            SyncMessageLikeEvent::Original(_),
+        )) = matrix_event
+        {
+            spawn!(clone!(@weak self as obj => async move {
+                obj.try_to_decrypt(obj.pure_event().event.cast()).await;
+            }));
         }
 
-        priv_.pure_event.replace(Some(event));
-
-        self.notify("event");
+        self.imp().matrix_event.replace(Some(matrix_event));
         self.notify("activatable");
-        self.notify("source");
     }
 
-    async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
+    /// The deserialized Matrix event of this `SupportedEvent`.
+    pub fn matrix_event(&self) -> AnySyncRoomEvent {
+        self.imp().matrix_event.borrow().clone().unwrap()
+    }
+
+    /// Try to decrypt this `SupportedEvent` with the current room keys.
+    ///
+    /// If decryption fails, it will be retried everytime we receive new room
+    /// keys.
+    pub async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
         let priv_ = self.imp();
         let room = self.room().matrix_room();
         let handle = spawn_tokio!(async move { room.decrypt_event(&event).await });
@@ -256,7 +224,10 @@ impl Event {
                 if let Some(keys_handle) = priv_.keys_handle.take() {
                     self.room().disconnect(keys_handle);
                 }
-                self.set_matrix_pure_event(decrypted.into());
+                let pure_event = SyncRoomEvent::from(decrypted);
+                let matrix_event = pure_event.event.deserialize().unwrap();
+                self.set_pure_event(pure_event);
+                self.set_matrix_event(matrix_event);
             }
             Err(error) => {
                 warn!("Failed to decrypt event: {}", error);
@@ -264,7 +235,7 @@ impl Event {
                     let handle = self.room().connect_new_encryption_keys(
                         clone!(@weak self as obj => move |_| {
                             // Try to decrypt the event again
-                            obj.set_matrix_pure_event(obj.matrix_pure_event());
+                            obj.set_matrix_event(obj.matrix_event());
                         }),
                     );
 
@@ -274,142 +245,50 @@ impl Event {
         }
     }
 
-    pub fn matrix_sender(&self) -> OwnedUserId {
-        let priv_ = self.imp();
-
-        if let Some(event) = priv_.event.borrow().as_ref() {
-            event.sender().into()
-        } else {
-            priv_
-                .pure_event
-                .borrow()
-                .as_ref()
-                .unwrap()
-                .event
-                .get_field::<OwnedUserId>("sender")
-                .unwrap()
-                .unwrap()
-        }
-    }
-
-    pub fn matrix_event_id(&self) -> OwnedEventId {
-        let priv_ = self.imp();
-
-        if let Some(event) = priv_.event.borrow().as_ref() {
-            event.event_id().to_owned()
-        } else {
-            priv_
-                .pure_event
-                .borrow()
-                .as_ref()
-                .unwrap()
-                .event
-                .get_field::<OwnedEventId>("event_id")
-                .unwrap()
-                .unwrap()
-        }
-    }
-
-    pub fn matrix_transaction_id(&self) -> Option<OwnedTransactionId> {
+    /// The event ID of this `SupportedEvent`.
+    pub fn event_id(&self) -> OwnedEventId {
         self.imp()
-            .pure_event
+            .matrix_event
             .borrow()
             .as_ref()
             .unwrap()
-            .event
-            .get_field::<MessageLikeUnsigned>("unsigned")
-            .ok()
-            .flatten()
-            .and_then(|unsigned| unsigned.transaction_id)
+            .event_id()
+            .to_owned()
     }
 
-    /// The original timestamp of this event.
-    pub fn matrix_origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
-        let priv_ = self.imp();
-        if let Some(event) = priv_.event.borrow().as_ref() {
-            event.origin_server_ts().to_owned()
-        } else {
-            priv_
-                .pure_event
-                .borrow()
-                .as_ref()
-                .unwrap()
-                .event
-                .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
-                .unwrap()
-                .unwrap()
-        }
+    /// The user ID of the sender of this `SupportedEvent`.
+    pub fn sender_id(&self) -> OwnedUserId {
+        self.imp()
+            .matrix_event
+            .borrow()
+            .as_ref()
+            .unwrap()
+            .sender()
+            .to_owned()
     }
 
-    /// The pretty-formatted JSON of this matrix event.
-    pub fn original_source(&self) -> String {
-        // We have to convert it to a Value, because a RawValue cannot be
-        // pretty-printed.
-        let json: serde_json::Value = serde_json::from_str(
-            self.imp()
-                .pure_event
-                .borrow()
-                .as_ref()
-                .unwrap()
-                .event
-                .json()
-                .get(),
-        )
-        .unwrap();
-
-        serde_json::to_string_pretty(&json).unwrap()
+    /// The room member that sent this `SupportedEvent`.
+    pub fn sender(&self) -> Member {
+        self.room().members().member_by_id(self.sender_id())
     }
 
-    /// The pretty-formatted JSON used for this matrix event.
+    /// The transaction ID of this `SupportedEvent`, if any.
     ///
-    /// If this matrix event has been replaced, returns the replacing `Event`'s
-    /// source.
-    pub fn source(&self) -> String {
-        self.replacement()
-            .map(|replacement| replacement.source())
-            .unwrap_or_else(|| self.original_source())
-    }
-
-    pub fn timestamp(&self) -> DateTime {
-        let priv_ = self.imp();
-        let ts = if let Some(event) = priv_.event.borrow().as_ref() {
-            event.origin_server_ts().as_secs()
-        } else {
-            priv_
-                .pure_event
-                .borrow()
-                .as_ref()
-                .unwrap()
-                .event
-                .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
-                .unwrap()
-                .unwrap()
-                .as_secs()
-        };
-
-        DateTime::from_unix_utc(ts.into())
-            .and_then(|t| t.to_local())
+    /// This is the random string sent with the event, if it was sent from this
+    /// session.
+    pub fn transaction_id(&self) -> Option<OwnedTransactionId> {
+        self.imp()
+            .matrix_event
+            .borrow()
+            .as_ref()
             .unwrap()
+            .transaction_id()
+            .map(|txn_id| txn_id.to_owned())
     }
 
-    pub fn time(&self) -> String {
-        let datetime = self.timestamp();
-
-        // FIXME Is there a cleaner way to do that?
-        let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
-
-        if local_time.ends_with("am") || local_time.ends_with("pm") {
-            // Use 12h time format (AM/PM)
-            datetime.format("%l∶%M %p").unwrap().to_string()
-        } else {
-            // Use 24 time format
-            datetime.format("%R").unwrap().to_string()
-        }
-    }
-
-    /// Find the related event if any
-    pub fn related_matrix_event(&self) -> Option<OwnedEventId> {
-        match self.imp().event.borrow().as_ref()? {
+    /// The ID of the event this `SupportedEvent` relates to, if any.
+    pub fn related_event_id(&self) -> Option<OwnedEventId> {
+        match self.imp().matrix_event.borrow().as_ref()? {
             AnySyncRoomEvent::MessageLike(ref message) => match message {
                 AnySyncMessageLikeEvent::RoomRedaction(SyncRoomRedactionEvent::Original(event)) => {
                     Some(event.redacts.clone())
@@ -420,8 +299,6 @@ impl Event {
                 AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(event)) => {
                     match &event.content.relates_to {
                         Some(relates_to) => match relates_to {
-                            // TODO: Figure out Relation::Annotation(), Relation::Reference() but
-                            // they are pre-specs for now See: 
https://github.com/uhoreg/matrix-doc/blob/aggregations-reactions/proposals/2677-reactions.md
                             Relation::Reply { in_reply_to } => Some(in_reply_to.event_id.clone()),
                             Relation::Replacement(replacement) => {
                                 Some(replacement.event_id.clone())
@@ -438,63 +315,26 @@ impl Event {
         }
     }
 
-    /// Whether this event is hidden from the user or displayed in the room
-    /// history.
-    pub fn is_hidden_event(&self) -> bool {
-        let priv_ = self.imp();
-
-        if self.related_matrix_event().is_some() {
-            if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
-                SyncMessageLikeEvent::Original(message),
-            ))) = priv_.event.borrow().as_ref()
-            {
-                if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        let event = priv_.event.borrow();
-
-        // List of all events to be shown.
-        match event.as_ref() {
-            Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
-                message,
-                AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
-                    | AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
-                    | AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
-            ),
-            Some(AnySyncRoomEvent::State(state)) => !matches!(
-                state,
-                AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
-                    | AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
-                    | AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
-                    | AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
-            ),
-            _ => true,
-        }
-    }
-
-    /// Whether this is a replacing `Event`.
+    /// Whether this `SupportedEvent` replaces another one.
     ///
-    /// Replacing matrix events are:
+    /// Replacing Matrix events are:
     ///
     /// - `RoomRedaction`
     /// - `RoomMessage` with `Relation::Replacement`
     pub fn is_replacing_event(&self) -> bool {
-        match self.matrix_event() {
-            Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
+        match self.imp().matrix_event.borrow().as_ref().unwrap() {
+            AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
                 SyncMessageLikeEvent::Original(message),
-            ))) => {
+            )) => {
                 matches!(message.content.relates_to, Some(Relation::Replacement(_)))
             }
-            Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))) => true,
+            AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_)) => true,
             _ => false,
         }
     }
 
-    pub fn prepend_replacing_events(&self, events: Vec<Event>) {
+    /// Prepend the given events to the list of replacing events.
+    pub fn prepend_replacing_events(&self, events: Vec<SupportedEvent>) {
         let priv_ = self.imp();
         priv_.replacing_events.borrow_mut().splice(..0, events);
         if self.redacted() {
@@ -502,7 +342,8 @@ impl Event {
         }
     }
 
-    pub fn append_replacing_events(&self, events: Vec<Event>) {
+    /// Append the given events to the list of replacing events.
+    pub fn append_replacing_events(&self, events: Vec<SupportedEvent>) {
         let priv_ = self.imp();
         let old_replacement = self.replacement();
 
@@ -536,15 +377,14 @@ impl Event {
         }
     }
 
-    pub fn replacing_events(&self) -> Vec<Event> {
+    /// The replacing events of this `SupportedEvent`, in the order of the
+    /// timeline.
+    pub fn replacing_events(&self) -> Vec<SupportedEvent> {
         self.imp().replacing_events.borrow().clone()
     }
 
-    /// The `Event` that replaces this one, if any.
-    ///
-    /// If this matrix event has been redacted or replaced, returns the
-    /// corresponding `Event`, otherwise returns `None`.
-    pub fn replacement(&self) -> Option<Event> {
+    /// The event that replaces this `SupportedEvent`, if any.
+    pub fn replacement(&self) -> Option<SupportedEvent> {
         self.replacing_events()
             .iter()
             .rev()
@@ -552,21 +392,19 @@ impl Event {
             .cloned()
     }
 
-    /// Whether this matrix event has been redacted.
+    /// Whether this `SupportedEvent` has been redacted.
     pub fn redacted(&self) -> bool {
         self.replacement()
             .filter(|event| {
                 matches!(
                     event.matrix_event(),
-                    Some(AnySyncRoomEvent::MessageLike(
-                        AnySyncMessageLikeEvent::RoomRedaction(_)
-                    ))
+                    AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))
                 )
             })
             .is_some()
     }
 
-    /// Whether this is a reaction.
+    /// Whether this `SupportedEvent` is a reaction.
     pub fn is_reaction(&self) -> bool {
         matches!(
             self.original_content(),
@@ -574,41 +412,38 @@ impl Event {
         )
     }
 
-    /// The reactions for this event.
+    /// The reactions for this `SupportedEvent`.
     pub fn reactions(&self) -> &ReactionList {
         &self.imp().reactions
     }
 
-    /// Add reactions to this event.
-    pub fn add_reactions(&self, reactions: Vec<Event>) {
+    /// Add reactions to this `SupportedEvent`.
+    pub fn add_reactions(&self, reactions: Vec<SupportedEvent>) {
         if !self.redacted() {
             self.imp().reactions.add_reactions(reactions);
         }
     }
 
-    /// The content of this matrix event.
-    ///
-    /// Returns `None` if this is not a message-like event.
+    /// The content of this `SupportedEvent`, if this is a message-like event.
     pub fn original_content(&self) -> Option<AnyMessageLikeEventContent> {
-        match self.matrix_event()? {
+        match self.matrix_event() {
             AnySyncRoomEvent::MessageLike(message) => message.original_content(),
             _ => None,
         }
     }
 
-    /// The content to display for this `Event`.
-    ///
-    /// If this matrix event has been replaced, returns the replacing `Event`'s
-    /// content.
+    /// The content to display for this `SupportedEvent`, if this is a
+    /// message-like event.
     ///
-    /// Returns `None` if this is not a message-like event.
+    /// If this event has been replaced, returns the replacing
+    /// `SupportedEvent`'s content.
     pub fn content(&self) -> Option<AnyMessageLikeEventContent> {
         self.replacement()
             .and_then(|replacement| replacement.content())
             .or_else(|| self.original_content())
     }
 
-    /// The content of a media message.
+    /// Fetch the content of the media message in this `SupportedEvent`.
     ///
     /// Compatible events:
     ///
@@ -617,9 +452,11 @@ impl Event {
     /// - Video message (`MessageType::Video`).
     /// - Audio message (`MessageType::Audio`).
     ///
-    /// Returns `Ok((uid, filename, binary_content))` on success, `Err` if an
-    /// error occurred while fetching the content. Panics on an incompatible
-    /// event. `uid` is a unique identifier for this media.
+    /// Returns `Ok((uid, filename, binary_content))` on success. `uid` is a
+    /// unique identifier for this media.
+    ///
+    /// Returns `Err` if an error occurred while fetching the content. Panics on
+    /// an incompatible event.
     pub async fn get_media_content(&self) -> Result<(String, String, Vec<u8>), matrix_sdk::Error> {
         if let AnyMessageLikeEventContent::RoomMessage(content) = self.original_content().unwrap() {
             let client = self.room().session().client();
@@ -704,7 +541,7 @@ impl Event {
         panic!("Trying to get the media content of an event of incompatible type");
     }
 
-    /// Get the id of the event this `Event` replies to, if any.
+    /// Get the ID of the event this `SupportedEvent` replies to, if any.
     pub fn reply_to_id(&self) -> Option<OwnedEventId> {
         match self.original_content()? {
             AnyMessageLikeEventContent::RoomMessage(message) => {
@@ -718,12 +555,12 @@ impl Event {
         }
     }
 
-    /// Whether this `Event` is a reply to another event.
+    /// Whether this `SupportedEvent` is a reply to another event.
     pub fn is_reply(&self) -> bool {
         self.reply_to_id().is_some()
     }
 
-    /// Get the `Event` this `Event` replies to, if any.
+    /// Get the `Event` this `SupportedEvent` replies to, if any.
     ///
     /// Returns `Ok(None)` if this event is not a reply.
     pub async fn reply_to_event(&self) -> Result<Option<Event>, MatrixError> {
@@ -740,4 +577,40 @@ impl Event {
             .await?;
         Ok(Some(event))
     }
+
+    /// Whether this `SupportedEvent` is hidden from the user or displayed in
+    /// the room history.
+    pub fn is_hidden_event(&self) -> bool {
+        let priv_ = self.imp();
+
+        if self.related_event_id().is_some() {
+            if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
+                SyncMessageLikeEvent::Original(message),
+            ))) = priv_.matrix_event.borrow().as_ref()
+            {
+                if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // List of all events to be shown.
+        match priv_.matrix_event.borrow().as_ref() {
+            Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
+                message,
+                AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
+                    | AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
+                    | AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
+            ),
+            Some(AnySyncRoomEvent::State(state)) => !matches!(
+                state,
+                AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
+                    | AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
+                    | AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
+                    | AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
+            ),
+            _ => true,
+        }
+    }
 }
diff --git a/src/session/room/event/unsupported_event.rs b/src/session/room/event/unsupported_event.rs
new file mode 100644
index 000000000..1a6a616e3
--- /dev/null
+++ b/src/session/room/event/unsupported_event.rs
@@ -0,0 +1,56 @@
+use gtk::{glib, prelude::*, subclass::prelude::*};
+use matrix_sdk::{deserialized_responses::SyncRoomEvent, ruma::events::RoomEventType};
+
+use super::{BoxedSyncRoomEvent, Event, EventImpl};
+use crate::session::room::{
+    timeline::{TimelineItem, TimelineItemImpl},
+    Room,
+};
+
+mod imp {
+    use super::*;
+
+    #[derive(Debug, Default)]
+    pub struct UnsupportedEvent {}
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for UnsupportedEvent {
+        const NAME: &'static str = "RoomUnsupportedEvent";
+        type Type = super::UnsupportedEvent;
+        type ParentType = Event;
+    }
+
+    impl ObjectImpl for UnsupportedEvent {}
+
+    impl TimelineItemImpl for UnsupportedEvent {}
+
+    impl EventImpl for UnsupportedEvent {}
+}
+
+glib::wrapper! {
+    /// GObject representation of an unsupported Matrix room event.
+    pub struct UnsupportedEvent(ObjectSubclass<imp::UnsupportedEvent>) @extends TimelineItem, Event;
+}
+
+impl UnsupportedEvent {
+    /// Construct an `UnsupportedEvent` from the given pure event and room.
+    pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
+        let pure_event = BoxedSyncRoomEvent(pure_event);
+        glib::Object::new(&[("pure-event", &pure_event), ("room", room)])
+            .expect("Failed to create UnsupportedEvent")
+    }
+
+    /// The type of this `UnsupportedEvent`, if the field is found.
+    pub fn event_type(&self) -> Option<RoomEventType> {
+        self.upcast_ref::<Event>()
+            .imp()
+            .pure_event
+            .borrow()
+            .as_ref()
+            .unwrap()
+            .event
+            .get_field::<RoomEventType>("type")
+            .ok()
+            .flatten()
+    }
+}
diff --git a/src/session/room/event_actions.rs b/src/session/room/event_actions.rs
index 5fc36d51e..7128486db 100644
--- a/src/session/room/event_actions.rs
+++ b/src/session/room/event_actions.rs
@@ -5,10 +5,10 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
 use once_cell::sync::Lazy;
 
 use crate::{
+    prelude::*,
     session::{
         event_source_dialog::EventSourceDialog,
-        room::{Event, RoomAction},
-        user::UserExt,
+        room::{Event, RoomAction, SupportedEvent},
     },
     spawn, toast,
     utils::cache_dir,
@@ -90,119 +90,122 @@ where
             })
         );
 
-        if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
-            let user_id = event
-                .room()
-                .session()
-                .user()
-                .map(|user| user.user_id())
-                .unwrap();
-            let user = event.room().members().member_by_id(user_id);
-            if event.sender() == user
-                || event
+        if let Some(event) = event.downcast_ref::<SupportedEvent>() {
+            if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
+                let user_id = event
                     .room()
-                    .power_levels()
-                    .min_level_for_room_action(&RoomAction::Redact)
-                    <= user.power_level()
-            {
-                // Remove message
-                gtk_macros::action!(
-                    &action_group,
-                    "remove",
-                    clone!(@weak event, => move |_, _| {
-                        event.room().redact(event.matrix_event_id(), None);
-                    })
-                );
-            }
-            // Send/redact a reaction
-            gtk_macros::action!(
-                &action_group,
-                "toggle-reaction",
-                Some(&String::static_variant_type()),
-                clone!(@weak event => move |_, variant| {
-                    let key: String = variant.unwrap().get().unwrap();
-                    let room = event.room();
-
-                    let reaction_group = event.reactions().reaction_group_by_key(&key);
-
-                    if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
-                        // The user already sent that reaction, redact it.
-                        room.redact(reaction.matrix_event_id(), None);
-                    } else {
-                        // The user didn't send that redaction, send it.
-                        room.send_reaction(key, event.matrix_event_id());
-                    }
-                })
-            );
-            match message.msgtype {
-                // Copy Text-Message
-                MessageType::Text(text_message) => {
+                    .session()
+                    .user()
+                    .map(|user| user.user_id())
+                    .unwrap();
+                let user = event.room().members().member_by_id(user_id);
+                if event.sender() == user
+                    || event
+                        .room()
+                        .power_levels()
+                        .min_level_for_room_action(&RoomAction::Redact)
+                        <= user.power_level()
+                {
+                    // Remove message
                     gtk_macros::action!(
                         &action_group,
-                        "copy-text",
-                        clone!(@weak self as widget => move |_, _| {
-                            widget.clipboard().set_text(&text_message.body);
+                        "remove",
+                        clone!(@weak event, => move |_, _| {
+                            event.room().redact(event.event_id(), None);
                         })
                     );
                 }
-                MessageType::File(_) => {
-                    // Save message's file
-                    gtk_macros::action!(
-                        &action_group,
-                        "file-save",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
-                        widget.save_event_file(event);
-                        })
-                    );
+                // Send/redact a reaction
+                gtk_macros::action!(
+                    &action_group,
+                    "toggle-reaction",
+                    Some(&String::static_variant_type()),
+                    clone!(@weak event => move |_, variant| {
+                        let key: String = variant.unwrap().get().unwrap();
+                        let room = event.room();
 
-                    // Open message's file
-                    gtk_macros::action!(
-                        &action_group,
-                        "file-open",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
-                        widget.open_event_file(event);
-                        })
-                    );
-                }
-                MessageType::Emote(message) => {
-                    gtk_macros::action!(
-                        &action_group,
-                        "copy-text",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
-                            let display_name = event.sender().display_name();
-                            let message = display_name + " " + &message.body;
-                            widget.clipboard().set_text(&message);
-                        })
-                    );
-                }
-                MessageType::Image(_) => {
-                    gtk_macros::action!(
-                        &action_group,
-                        "save-image",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
-                            widget.save_event_file(event);
-                        })
-                    );
-                }
-                MessageType::Video(_) => {
-                    gtk_macros::action!(
-                        &action_group,
-                        "save-video",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
-                            widget.save_event_file(event);
-                        })
-                    );
-                }
-                MessageType::Audio(_) => {
-                    gtk_macros::action!(
-                        &action_group,
-                        "save-audio",
-                        clone!(@weak self as widget, @weak event => move |_, _| {
+                        let reaction_group = event.reactions().reaction_group_by_key(&key);
+
+                        if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
+                            // The user already sent that reaction, redact it.
+                            room.redact(reaction.event_id(), None);
+                        } else {
+                            // The user didn't send that redaction, send it.
+                            room.send_reaction(key, event.event_id());
+                        }
+                    })
+                );
+                match message.msgtype {
+                    // Copy Text-Message
+                    MessageType::Text(text_message) => {
+                        gtk_macros::action!(
+                            &action_group,
+                            "copy-text",
+                            clone!(@weak self as widget => move |_, _| {
+                                widget.clipboard().set_text(&text_message.body);
+                            })
+                        );
+                    }
+                    MessageType::File(_) => {
+                        // Save message's file
+                        gtk_macros::action!(
+                            &action_group,
+                            "file-save",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
                             widget.save_event_file(event);
-                        })
-                    );
+                            })
+                        );
+
+                        // Open message's file
+                        gtk_macros::action!(
+                            &action_group,
+                            "file-open",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
+                            widget.open_event_file(event);
+                            })
+                        );
+                    }
+                    MessageType::Emote(message) => {
+                        gtk_macros::action!(
+                            &action_group,
+                            "copy-text",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
+                                let display_name = event.sender().display_name();
+                                let message = display_name + " " + &message.body;
+                                widget.clipboard().set_text(&message);
+                            })
+                        );
+                    }
+
+                    MessageType::Image(_) => {
+                        gtk_macros::action!(
+                            &action_group,
+                            "save-image",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
+                                widget.save_event_file(event);
+                            })
+                        );
+                    }
+                    MessageType::Video(_) => {
+                        gtk_macros::action!(
+                            &action_group,
+                            "save-video",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
+                                widget.save_event_file(event);
+                            })
+                        );
+                    }
+                    MessageType::Audio(_) => {
+                        gtk_macros::action!(
+                            &action_group,
+                            "save-audio",
+                            clone!(@weak self as widget, @weak event => move |_, _| {
+                                widget.save_event_file(event);
+                            })
+                        );
+                    }
+                    _ => {}
                 }
-                _ => {}
             }
         }
         self.insert_action_group("event", Some(&action_group));
@@ -211,9 +214,9 @@ where
 
     /// Save the file in `event`.
     ///
-    /// See `Event::get_media_content` for compatible events. Panics on an
-    /// incompatible event.
-    fn save_event_file(&self, event: Event) {
+    /// See [`SupportedEvent::get_media_content()`] for compatible events.
+    /// Panics on an incompatible event.
+    fn save_event_file(&self, event: SupportedEvent) {
         let window: Window = self.root().unwrap().downcast().unwrap();
         spawn!(
             glib::PRIORITY_LOW,
@@ -260,9 +263,9 @@ where
 
     /// Open the file in `event`.
     ///
-    /// See `Event::get_media_content` for compatible events. Panics on an
-    /// incompatible event.
-    fn open_event_file(&self, event: Event) {
+    /// See [`SupportedEvent::get_media_content()`] for compatible events.
+    /// Panics on an incompatible event.
+    fn open_event_file(&self, event: SupportedEvent) {
         spawn!(
             glib::PRIORITY_LOW,
             clone!(@weak self as obj => async move {
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index dc9eb0fd3..b1faba8c9 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -46,7 +46,7 @@ use matrix_sdk::{
 use ruma::events::SyncEphemeralRoomEvent;
 
 pub use self::{
-    event::Event,
+    event::*,
     event_actions::EventActions,
     highlight_flags::HighlightFlags,
     member::{Member, Membership},
@@ -55,10 +55,7 @@ pub use self::{
     reaction_group::ReactionGroup,
     reaction_list::ReactionList,
     room_type::RoomType,
-    timeline::{
-        Timeline, TimelineDayDivider, TimelineItem, TimelineItemExt, TimelineNewMessagesDivider,
-        TimelineSpinner, TimelineState,
-    },
+    timeline::*,
 };
 use super::verification::IdentityVerification;
 use crate::{
@@ -103,7 +100,7 @@ mod imp {
         /// The event of the user's read receipt for this room.
         pub read_receipt: RefCell<Option<Event>>,
         /// The latest read event in the room's timeline.
-        pub latest_read: RefCell<Option<Event>>,
+        pub latest_read: RefCell<Option<SupportedEvent>>,
         /// The highlight state of the room,
         pub highlight: Cell<HighlightFlags>,
         pub predecessor: OnceCell<OwnedRoomId>,
@@ -827,7 +824,7 @@ impl Room {
         if Some(event_id)
             == self
                 .read_receipt()
-                .map(|event| event.matrix_event_id())
+                .and_then(|event| event.event_id())
                 .as_deref()
         {
             return;
@@ -872,7 +869,7 @@ impl Room {
                 timeline
                     .item(i)
                     .as_ref()
-                    .and_then(|obj| obj.downcast_ref::<Event>())
+                    .and_then(|obj| obj.downcast_ref::<SupportedEvent>())
                     .and_then(|event| {
                         // The user sent the event so it's the latest read event.
                         // Necessary because we don't get read receipts for the user's own events.
@@ -886,9 +883,8 @@ impl Room {
                         }
 
                         // The event is older than the read receipt so it has been read.
-                        if event.matrix_event().filter(count_as_unread).is_some()
-                            && event.matrix_origin_server_ts()
-                                <= read_receipt.matrix_origin_server_ts()
+                        if count_as_unread(&event.matrix_event())
+                            && event.origin_server_ts() <= read_receipt.origin_server_ts()
                         {
                             return Some(event.to_owned());
                         }
@@ -902,12 +898,12 @@ impl Room {
     }
 
     /// The latest read event in the room's timeline.
-    pub fn latest_read(&self) -> Option<Event> {
+    pub fn latest_read(&self) -> Option<SupportedEvent> {
         self.imp().latest_read.borrow().clone()
     }
 
     /// Set the latest read event.
-    fn set_latest_read(&self, latest_read: Option<Event>) {
+    fn set_latest_read(&self, latest_read: Option<SupportedEvent>) {
         if latest_read == self.latest_read() {
             return;
         }
@@ -974,7 +970,7 @@ impl Room {
                     if let Some(event) = timeline
                         .item(i)
                         .as_ref()
-                        .and_then(|obj| obj.downcast_ref::<Event>())
+                        .and_then(|obj| obj.downcast_ref::<SupportedEvent>())
                     {
                         // This is the event corresponding to the read receipt so there's no unread
                         // messages.
@@ -983,7 +979,7 @@ impl Room {
                         }
 
                         // The user hasn't read the latest message.
-                        if event.matrix_event().filter(count_as_unread).is_some() {
+                        if count_as_unread(&event.matrix_event()) {
                             return false;
                         }
                     }
@@ -1306,7 +1302,7 @@ impl Room {
             };
 
             let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&matrix_event).unwrap().cast();
-            let event = Event::new(raw_event.into(), self);
+            let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
             self.imp()
                 .timeline
                 .get()
@@ -1357,7 +1353,7 @@ impl Room {
 
         if let MatrixRoom::Joined(matrix_room) = self.matrix_room() {
             let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&event).unwrap().cast();
-            let event = Event::new(raw_event.into(), self);
+            let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
             self.imp()
                 .timeline
                 .get()
diff --git a/src/session/room/reaction_group.rs b/src/session/room/reaction_group.rs
index 339f7269d..6df0dbd44 100644
--- a/src/session/room/reaction_group.rs
+++ b/src/session/room/reaction_group.rs
@@ -1,7 +1,7 @@
 use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use super::Event;
-use crate::session::UserExt;
+use super::SupportedEvent;
+use crate::prelude::*;
 
 mod imp {
     use std::cell::RefCell;
@@ -16,7 +16,7 @@ mod imp {
         /// The key of the group.
         pub key: OnceCell<String>,
         /// The reactions in the group.
-        pub reactions: RefCell<IndexSet<Event>>,
+        pub reactions: RefCell<IndexSet<SupportedEvent>>,
     }
 
     #[glib::object_subclass]
@@ -108,14 +108,14 @@ impl ReactionGroup {
     }
 
     /// The reaction in this group sent by this user, if any.
-    pub fn user_reaction(&self) -> Option<Event> {
+    pub fn user_reaction(&self) -> Option<SupportedEvent> {
         let reactions = self.imp().reactions.borrow();
         if let Some(user) = reactions
             .first()
             .and_then(|event| event.room().session().user().cloned())
         {
             for reaction in reactions.iter().filter(|event| !event.redacted()) {
-                if reaction.matrix_sender() == user.user_id() {
+                if reaction.sender_id() == user.user_id() {
                     return Some(reaction.clone());
                 }
             }
@@ -129,7 +129,7 @@ impl ReactionGroup {
     }
 
     /// Add new reactions to this group.
-    pub fn add_reactions(&self, new_reactions: Vec<Event>) {
+    pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
         let prev_has_user = self.has_user();
         let mut added_reactions = Vec::with_capacity(new_reactions.len());
 
diff --git a/src/session/room/reaction_list.rs b/src/session/room/reaction_list.rs
index 8f711464a..a3d60072e 100644
--- a/src/session/room/reaction_list.rs
+++ b/src/session/room/reaction_list.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 use matrix_sdk::ruma::events::AnyMessageLikeEventContent;
 
-use super::{Event, ReactionGroup};
+use super::{ReactionGroup, SupportedEvent};
 
 mod imp {
     use std::cell::RefCell;
@@ -45,7 +45,7 @@ mod imp {
 }
 
 glib::wrapper! {
-    /// List of all `ReactionGroup`s for an `Event`. Implements `ListModel`.
+    /// List of all `ReactionGroup`s for a `SupportedEvent`. Implements `ListModel`.
     ///
     /// `ReactionGroup`s are sorted in "insertion order".
     pub struct ReactionList(ObjectSubclass<imp::ReactionList>)
@@ -57,15 +57,15 @@ impl ReactionList {
         glib::Object::new(&[]).expect("Failed to create ReactionList")
     }
 
-    /// Add reactions with the given reaction `Event`s.
+    /// Add reactions with the given reaction `SupportedEvent`s.
     ///
-    /// Ignores `Event`s that are not reactions.
-    pub fn add_reactions(&self, new_reactions: Vec<Event>) {
+    /// Ignores `SupportedEvent`s that are not reactions.
+    pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
         let mut reactions = self.imp().reactions.borrow_mut();
         let prev_len = reactions.len();
 
         // Group reactions by key
-        let mut grouped_reactions: HashMap<String, Vec<Event>> = HashMap::new();
+        let mut grouped_reactions: HashMap<String, Vec<SupportedEvent>> = HashMap::new();
         for event in new_reactions {
             if let Some(AnyMessageLikeEventContent::Reaction(reaction)) = event.content() {
                 let relation = reaction.relates_to;
diff --git a/src/session/room/timeline/mod.rs b/src/session/room/timeline/mod.rs
index 1e47d51ad..32018a1f0 100644
--- a/src/session/room/timeline/mod.rs
+++ b/src/session/room/timeline/mod.rs
@@ -23,10 +23,8 @@ pub use timeline_new_messages_divider::TimelineNewMessagesDivider;
 pub use timeline_spinner::TimelineSpinner;
 use tokio::task::JoinHandle;
 
-use crate::{
-    session::room::{Event, Room},
-    spawn_tokio,
-};
+use super::{Event, Room, SupportedEvent, UnsupportedEvent};
+use crate::{prelude::*, spawn_tokio};
 
 #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
 #[repr(u32)]
@@ -195,7 +193,7 @@ impl Timeline {
             let mut previous_timestamp = if position > 0 {
                 list.get(position - 1)
                     .and_then(|item| item.downcast_ref::<Event>())
-                    .map(|event| event.timestamp())
+                    .and_then(|event| event.timestamp())
             } else {
                 None
             };
@@ -204,7 +202,7 @@ impl Timeline {
             for current in list.range(position..position + added) {
                 if let Some(current_timestamp) = current
                     .downcast_ref::<Event>()
-                    .map(|event| event.timestamp())
+                    .and_then(|event| event.timestamp())
                 {
                     if Some(current_timestamp.ymd()) != previous_timestamp.as_ref().map(|t| t.ymd())
                     {
@@ -252,13 +250,13 @@ impl Timeline {
             let mut previous_sender = if position > 0 {
                 list.get(position - 1)
                     .filter(|item| item.can_hide_header())
-                    .and_then(|item| item.sender())
+                    .and_then(|item| item.event_sender())
             } else {
                 None
             };
 
             for current in list.range(position..position + added) {
-                let current_sender = current.sender();
+                let current_sender = current.event_sender();
 
                 if !current.can_hide_header() {
                     current.set_show_header(false);
@@ -281,7 +279,7 @@ impl Timeline {
 
                 // Once the sender changes we can be sure that the visibility for headers will
                 // be correct
-                if next.sender() != previous_sender {
+                if next.event_sender() != previous_sender {
                     next.set_show_header(true);
                     break;
                 }
@@ -300,15 +298,16 @@ impl Timeline {
 
             for event in list
                 .range(position as usize..(position + added) as usize)
-                .filter_map(|item| item.downcast_ref::<Event>())
+                .filter_map(|item| item.downcast_ref::<SupportedEvent>())
             {
-                if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
-                    let mut replacing_events: Vec<Event> = vec![];
-                    let mut reactions: Vec<Event> = vec![];
+                if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
+                    let mut replacing_events = vec![];
+                    let mut reactions = vec![];
 
                     for relation_event_id in relates_to {
                         let relation = self
                             .event_by_id(&relation_event_id)
+                            .and_then(|event| event.downcast::<SupportedEvent>().ok())
                             .expect("Previously known event has disappeared");
 
                         if relation.is_replacing_event() {
@@ -326,7 +325,7 @@ impl Timeline {
                     event.add_reactions(reactions);
 
                     if event.redacted() {
-                        redacted_events.insert(event.matrix_event_id());
+                        redacted_events.insert(event.event_id());
                     }
                 }
             }
@@ -352,8 +351,8 @@ impl Timeline {
             let mut list = list.iter();
 
             while let Some(item) = list.next_back() {
-                if let Some(event) = item.downcast_ref::<Event>() {
-                    if redacted_events.remove(&event.matrix_event_id()) {
+                if let Some(event) = item.downcast_ref::<SupportedEvent>() {
+                    if redacted_events.remove(&event.event_id()) {
                         redacted_events_pos.push(i - 1);
                     }
                     if redacted_events.is_empty() {
@@ -412,20 +411,21 @@ impl Timeline {
         }
     }
 
-    fn add_hidden_events(&self, events: Vec<Event>, at_front: bool) {
+    fn add_hidden_events(&self, events: Vec<SupportedEvent>, at_front: bool) {
         let priv_ = self.imp();
         let mut relates_to_events = priv_.relates_to_events.borrow_mut();
 
         // Group events by related event
-        let mut new_relations: HashMap<OwnedEventId, Vec<Event>> = HashMap::new();
+        let mut new_relations: HashMap<_, Vec<_>> = HashMap::new();
         for event in events {
-            if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
-                let mut replacing_events: Vec<Event> = vec![];
-                let mut reactions: Vec<Event> = vec![];
+            if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
+                let mut replacing_events = vec![];
+                let mut reactions = vec![];
 
                 for relation_event_id in relates_to {
                     let relation = self
                         .event_by_id(&relation_event_id)
+                        .and_then(|event| event.downcast::<SupportedEvent>().ok())
                         .expect("Previously known event has disappeared");
 
                     if relation.is_replacing_event() {
@@ -443,7 +443,7 @@ impl Timeline {
                 event.add_reactions(reactions);
             }
 
-            if let Some(relates_to_event) = event.related_matrix_event() {
+            if let Some(relates_to_event) = event.related_event_id() {
                 let relations = new_relations.entry(relates_to_event).or_default();
                 relations.push(event);
             }
@@ -454,53 +454,54 @@ impl Timeline {
         for (relates_to_event_id, new_relations) in new_relations {
             if let Some(relates_to_event) = self.event_by_id(&relates_to_event_id) {
                 // Get the relations in relates_to_event otherwise they will be added in
-                // in items_changed and they might not be added at the right place.
-                let mut relations: Vec<Event> = relates_to_events
-                    .remove(&relates_to_event.matrix_event_id())
+                // items_changed and they might not be added at the right place.
+                let mut relations: Vec<_> = relates_to_events
+                    .remove(&relates_to_event_id)
                     .unwrap_or_default()
                     .into_iter()
                     .map(|event_id| {
                         self.event_by_id(&event_id)
+                            .and_then(|event| event.downcast::<SupportedEvent>().ok())
                             .expect("Previously known event has disappeared")
                     })
                     .collect();
 
-                if at_front {
-                    relations.splice(..0, new_relations);
-                } else {
-                    relations.extend(new_relations);
-                }
+                if let Some(relates_to_event) = relates_to_event.downcast_ref::<SupportedEvent>() {
+                    if at_front {
+                        relations.splice(..0, new_relations);
+                    } else {
+                        relations.extend(new_relations);
+                    }
 
-                let mut replacing_events: Vec<Event> = vec![];
-                let mut reactions: Vec<Event> = vec![];
+                    let mut replacing_events = vec![];
+                    let mut reactions = vec![];
 
-                for relation in relations {
-                    if relation.is_replacing_event() {
-                        replacing_events.push(relation);
-                    } else if relation.is_reaction() {
-                        reactions.push(relation);
+                    for relation in relations {
+                        if relation.is_replacing_event() {
+                            replacing_events.push(relation);
+                        } else if relation.is_reaction() {
+                            reactions.push(relation);
+                        }
                     }
-                }
 
-                if !at_front || relates_to_event.replacing_events().is_empty() {
-                    relates_to_event.append_replacing_events(replacing_events);
-                } else {
-                    relates_to_event.prepend_replacing_events(replacing_events);
-                }
-                relates_to_event.add_reactions(reactions);
+                    if !at_front || relates_to_event.replacing_events().is_empty() {
+                        relates_to_event.append_replacing_events(replacing_events);
+                    } else {
+                        relates_to_event.prepend_replacing_events(replacing_events);
+                    }
+                    relates_to_event.add_reactions(reactions);
 
-                if relates_to_event.redacted() {
-                    redacted_events.insert(relates_to_event.matrix_event_id());
+                    if relates_to_event.redacted() {
+                        redacted_events.insert(relates_to_event.event_id());
+                    }
                 }
             } else {
                 // Store the new event if the `related_to` event isn't known, we will update the
                 // `relates_to` once the `related_to` event is added to the list
                 let relates_to_event = relates_to_events.entry(relates_to_event_id).or_default();
 
-                let relations_ids: Vec<OwnedEventId> = new_relations
-                    .iter()
-                    .map(|event| event.matrix_event_id())
-                    .collect();
+                let relations_ids: Vec<_> =
+                    new_relations.iter().map(|event| event.event_id()).collect();
                 if at_front {
                     relates_to_event.splice(..0, relations_ids);
                 } else {
@@ -648,30 +649,43 @@ impl Timeline {
             };
 
             let mut pending_events = priv_.pending_events.borrow_mut();
-            let mut hidden_events: Vec<Event> = vec![];
-
-            for event in batch.into_iter() {
-                let event_id = event.matrix_event_id();
+            let mut hidden_events = vec![];
 
-                if let Some(pending_id) = event
-                    .matrix_transaction_id()
-                    .and_then(|txn_id| pending_events.remove(&txn_id))
+            for event in batch {
+                if let Some(event_id) = event
+                    .downcast_ref::<UnsupportedEvent>()
+                    .and_then(|event| event.event_id())
                 {
-                    let mut event_map = priv_.event_map.borrow_mut();
-
-                    if let Some(pending_event) = event_map.remove(&pending_id) {
-                        pending_event.set_matrix_pure_event(event.matrix_pure_event());
-                        event_map.insert(event_id, pending_event);
-                    };
+                    priv_.event_map.borrow_mut().insert(event_id, event);
                     added -= 1;
-                } else {
-                    priv_.event_map.borrow_mut().insert(event_id, event.clone());
-                    if event.is_hidden_event() {
-                        hidden_events.push(event);
+                } else if let Ok(event) = event.downcast::<SupportedEvent>() {
+                    let event_id = event.event_id();
+
+                    if let Some(pending_id) = event
+                        .transaction_id()
+                        .and_then(|txn_id| pending_events.remove(&txn_id))
+                    {
+                        let mut event_map = priv_.event_map.borrow_mut();
+
+                        if let Some(pending_event) = event_map.remove(&pending_id) {
+                            pending_event.set_pure_event(event.pure_event());
+                            event_map.insert(event_id, pending_event);
+                        };
                         added -= 1;
                     } else {
-                        priv_.list.borrow_mut().push_back(event.upcast());
+                        priv_
+                            .event_map
+                            .borrow_mut()
+                            .insert(event_id, event.clone().upcast());
+                        if event.is_hidden_event() {
+                            hidden_events.push(event);
+                            added -= 1;
+                        } else {
+                            priv_.list.borrow_mut().push_back(event.upcast());
+                        }
                     }
+                } else {
+                    added -= 1;
                 }
             }
 
@@ -684,18 +698,18 @@ impl Timeline {
     }
 
     /// Append an event that wasn't yet fully sent and received via a sync
-    pub fn append_pending(&self, txn_id: &TransactionId, event: Event) {
+    pub fn append_pending(&self, txn_id: &TransactionId, event: SupportedEvent) {
         let priv_ = self.imp();
 
         priv_
             .event_map
             .borrow_mut()
-            .insert(event.matrix_event_id(), event.clone());
+            .insert(event.event_id(), event.clone().upcast());
 
         priv_
             .pending_events
             .borrow_mut()
-            .insert(txn_id.to_owned(), event.matrix_event_id());
+            .insert(txn_id.to_owned(), event.event_id());
 
         let index = {
             let mut list = priv_.list.borrow_mut();
@@ -756,22 +770,32 @@ impl Timeline {
         let mut added = batch.len();
 
         {
-            let mut hidden_events: Vec<Event> = vec![];
+            let mut hidden_events: Vec<_> = vec![];
             // Extend the size of the list so that rust doesn't need to reallocate memory
             // multiple times
             priv_.list.borrow_mut().reserve(added);
 
             for event in batch {
-                priv_
-                    .event_map
-                    .borrow_mut()
-                    .insert(event.matrix_event_id(), event.clone());
-
-                if event.is_hidden_event() {
-                    hidden_events.push(event);
+                if let Some(event_id) = event
+                    .downcast_ref::<UnsupportedEvent>()
+                    .and_then(|event| event.event_id())
+                {
+                    priv_.event_map.borrow_mut().insert(event_id, event);
                     added -= 1;
+                } else if let Ok(event) = event.downcast::<SupportedEvent>() {
+                    priv_
+                        .event_map
+                        .borrow_mut()
+                        .insert(event.event_id(), event.clone().upcast());
+
+                    if event.is_hidden_event() {
+                        hidden_events.push(event);
+                        added -= 1;
+                    } else {
+                        priv_.list.borrow_mut().push_front(event.upcast());
+                    }
                 } else {
-                    priv_.list.borrow_mut().push_front(event.upcast());
+                    added -= 1;
                 }
             }
             self.add_hidden_events(hidden_events, true);
diff --git a/src/session/room/timeline/timeline_item.rs b/src/session/room/timeline/timeline_item.rs
index 55ef72656..d4cd32b67 100644
--- a/src/session/room/timeline/timeline_item.rs
+++ b/src/session/room/timeline/timeline_item.rs
@@ -15,7 +15,7 @@ mod imp {
         pub selectable: fn(&super::TimelineItem) -> bool,
         pub activatable: fn(&super::TimelineItem) -> bool,
         pub can_hide_header: fn(&super::TimelineItem) -> bool,
-        pub sender: fn(&super::TimelineItem) -> Option<Member>,
+        pub event_sender: fn(&super::TimelineItem) -> Option<Member>,
     }
 
     unsafe impl ClassStruct for TimelineItemClass {
@@ -37,9 +37,9 @@ mod imp {
         (klass.as_ref().can_hide_header)(this)
     }
 
-    pub(super) fn timeline_item_sender(this: &super::TimelineItem) -> Option<Member> {
+    pub(super) fn timeline_item_event_sender(this: &super::TimelineItem) -> Option<Member> {
         let klass = this.class();
-        (klass.as_ref().sender)(this)
+        (klass.as_ref().event_sender)(this)
     }
 
     #[derive(Debug, Default)]
@@ -88,8 +88,8 @@ mod imp {
                         glib::ParamFlags::READABLE,
                     ),
                     glib::ParamSpecObject::new(
-                        "sender",
-                        "Sender",
+                        "event-sender",
+                        "Event Sender",
                         "If this item is a Matrix event, the sender of the event.",
                         Member::static_type(),
                         glib::ParamFlags::READABLE,
@@ -119,7 +119,7 @@ mod imp {
                 "activatable" => obj.activatable().to_value(),
                 "show-header" => obj.show_header().to_value(),
                 "can-hide-header" => obj.can_hide_header().to_value(),
-                "sender" => obj.sender().to_value(),
+                "event-sender" => obj.event_sender().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -163,7 +163,7 @@ pub trait TimelineItemExt: 'static {
     /// If this is a Matrix event, the sender of the event.
     ///
     /// Defaults to `None`.
-    fn sender(&self) -> Option<Member>;
+    fn event_sender(&self) -> Option<Member>;
 }
 
 impl<O: IsA<TimelineItem>> TimelineItemExt for O {
@@ -194,8 +194,8 @@ impl<O: IsA<TimelineItem>> TimelineItemExt for O {
         imp::timeline_item_can_hide_header(self.upcast_ref())
     }
 
-    fn sender(&self) -> Option<Member> {
-        imp::timeline_item_sender(self.upcast_ref())
+    fn event_sender(&self) -> Option<Member> {
+        imp::timeline_item_event_sender(self.upcast_ref())
     }
 }
 
@@ -217,7 +217,7 @@ pub trait TimelineItemImpl: ObjectImpl {
         false
     }
 
-    fn sender(&self, _obj: &Self::Type) -> Option<Member> {
+    fn event_sender(&self, _obj: &Self::Type) -> Option<Member> {
         None
     }
 }
@@ -236,7 +236,7 @@ where
         klass.selectable = selectable_trampoline::<T>;
         klass.activatable = activatable_trampoline::<T>;
         klass.can_hide_header = can_hide_header_trampoline::<T>;
-        klass.sender = sender_trampoline::<T>;
+        klass.event_sender = event_sender_trampoline::<T>;
     }
 }
 
@@ -268,11 +268,11 @@ where
     this.imp().can_hide_header(this)
 }
 
-fn sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
+fn event_sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
 where
     T: ObjectSubclass + TimelineItemImpl,
     T::Type: IsA<TimelineItem>,
 {
     let this = this.downcast_ref::<T::Type>().unwrap();
-    this.imp().sender(this)
+    this.imp().event_sender(this)
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]