diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui index d257dd09..abefde08 100644 --- a/data/resources/ui/content-room-history.ui +++ b/data/resources/ui/content-room-history.ui @@ -107,6 +107,26 @@ + + + True + True + True + dialog-error-symbolic + Unable to load room + Check your network connection. + + + Try Again + room-history.try-again + center + + + + + @@ -239,3 +259,4 @@ + diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs index 7fdb6074..aaefc2fc 100644 --- a/src/session/content/room_history/mod.rs +++ b/src/session/content/room_history/mod.rs @@ -26,7 +26,7 @@ use crate::{ components::{CustomEntry, Pill, RoomTitle}, session::{ content::{MarkdownPopover, RoomDetails}, - room::{Item, Room, RoomType, Timeline}, + room::{Item, Room, RoomType, Timeline, TimelineState}, user::UserExt, }, }; @@ -46,7 +46,7 @@ mod imp { pub room: RefCell>, pub category_handler: RefCell>, pub empty_timeline_handler: RefCell>, - pub loading_timeline_handler: RefCell>, + pub state_timeline_handler: RefCell>, pub md_enabled: Cell, pub is_auto_scrolling: Cell, pub sticky: Cell, @@ -73,6 +73,8 @@ mod imp { #[template_child] pub loading: TemplateChild, #[template_child] + pub error: TemplateChild, + #[template_child] pub stack: TemplateChild, } @@ -101,6 +103,10 @@ mod imp { widget.leave(); }); + klass.install_action("room-history.try-again", None, move |widget, _, _| { + widget.try_again(); + }); + klass.install_action("room-history.details", None, move |widget, _, _| { widget.open_room_details("general"); }); @@ -324,9 +330,9 @@ impl RoomHistory { } } - if let Some(loading_timeline_handler) = priv_.loading_timeline_handler.take() { - if let Some(room) = self.room() { - room.timeline().disconnect(loading_timeline_handler); + if let Some(room) = self.room() { + if let Some(state_timeline_handler) = priv_.state_timeline_handler.take() { + room.timeline().disconnect(state_timeline_handler); } } @@ -343,24 +349,20 @@ impl RoomHistory { let handler_id = room.timeline().connect_notify_local( Some("empty"), clone!(@weak self as obj => move |_, _| { - obj.set_empty_timeline(); + obj.update_view(); }), ); priv_.empty_timeline_handler.replace(Some(handler_id)); let handler_id = room.timeline().connect_notify_local( - Some("loading"), - clone!(@weak self as obj => move |timeline, _| { - // We need to make sure that we loaded enough events to fill the `ScrolledWindow` - if !timeline.loading() { - let adj = obj.imp().listview.vadjustment().unwrap(); - obj.load_more_messages(&adj); - } + Some("state"), + clone!(@weak self as obj => move |_, _| { + obj.update_view(); }), ); - priv_.loading_timeline_handler.replace(Some(handler_id)); + priv_.state_timeline_handler.replace(Some(handler_id)); room.load_members(); } @@ -374,8 +376,8 @@ impl RoomHistory { priv_.room.replace(room); let adj = priv_.listview.vadjustment().unwrap(); self.load_more_messages(&adj); + self.update_view(); self.update_room_state(); - self.set_empty_timeline(); self.notify("room"); self.notify("empty"); } @@ -519,12 +521,16 @@ impl RoomHistory { } } - fn set_empty_timeline(&self) { + fn update_view(&self) { let priv_ = self.imp(); if let Some(room) = &*priv_.room.borrow() { if room.timeline().is_empty() { - priv_.stack.set_visible_child(&*priv_.loading); + if room.timeline().state() == TimelineState::Error { + priv_.stack.set_visible_child(&*priv_.error); + } else { + priv_.stack.set_visible_child(&*priv_.loading); + } } else { priv_.stack.set_visible_child(&*priv_.content); } @@ -577,6 +583,12 @@ impl RoomHistory { .scrolled_window .emit_by_name::("scroll-child", &[>k::ScrollType::End, &false]); } + + fn try_again(&self) { + if let Some(room) = self.room() { + room.timeline().load_previous_events(); + } + } } impl Default for RoomHistory { diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs index 6e447364..ff201db9 100644 --- a/src/session/room/mod.rs +++ b/src/session/room/mod.rs @@ -54,7 +54,7 @@ pub use self::{ reaction_group::ReactionGroup, reaction_list::ReactionList, room_type::RoomType, - timeline::Timeline, + timeline::{Timeline, TimelineState}, }; use crate::{ components::{LabelWithWidgets, Pill}, diff --git a/src/session/room/timeline.rs b/src/session/room/timeline.rs index 197051b1..899e59e9 100644 --- a/src/session/room/timeline.rs +++ b/src/session/room/timeline.rs @@ -23,6 +23,23 @@ use crate::{ spawn, spawn_tokio, }; +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)] +#[repr(u32)] +#[enum_type(name = "TimelineState")] +pub enum TimelineState { + Initial, + Loading, + Ready, + Error, + Complete, +} + +impl Default for TimelineState { + fn default() -> Self { + TimelineState::Initial + } +} + mod imp { use std::{ cell::{Cell, RefCell}, @@ -48,9 +65,8 @@ mod imp { pub pending_events: RefCell>>, /// A Hashset of `EventId`s that where just redacted. pub redacted_events: RefCell>>, - pub loading: Cell, - pub complete: Cell, pub oldest_event: RefCell>>, + pub state: Cell, /// The most recent verification request event pub verification: RefCell>, } @@ -74,13 +90,6 @@ mod imp { Room::static_type(), glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY, ), - glib::ParamSpecBoolean::new( - "loading", - "Loading", - "Whether a response is loaded or not", - false, - glib::ParamFlags::READABLE, - ), glib::ParamSpecBoolean::new( "empty", "Empty", @@ -88,11 +97,12 @@ mod imp { false, glib::ParamFlags::READABLE, ), - glib::ParamSpecBoolean::new( - "complete", - "Complete", - "Whether the full timeline is loaded", - false, + glib::ParamSpecEnum::new( + "state", + "State", + "The state the timeline is in", + TimelineState::static_type(), + TimelineState::default() as i32, glib::ParamFlags::READABLE, ), glib::ParamSpecObject::new( @@ -127,9 +137,8 @@ mod imp { fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "room" => obj.room().to_value(), - "loading" => obj.loading().to_value(), "empty" => obj.is_empty().to_value(), - "complete" => obj.is_complete().to_value(), + "state" => obj.state().to_value(), "verification" => obj.verification().to_value(), _ => unimplemented!(), } @@ -680,41 +689,27 @@ impl Timeline { self.imp().room.get().unwrap().upgrade().unwrap() } - fn set_loading(&self, loading: bool) { + fn set_state(&self, state: TimelineState) { let priv_ = self.imp(); - if loading == priv_.loading.get() { + if state == self.state() { return; } - priv_.loading.set(loading); + priv_.state.set(state); - self.notify("loading"); + self.notify("state"); } - fn set_complete(&self, complete: bool) { - let priv_ = self.imp(); - - if complete == priv_.complete.get() { - return; - } - - priv_.complete.set(complete); - self.notify("complete"); - } - - // Whether the timeline is fully loaded - pub fn is_complete(&self) -> bool { - self.imp().complete.get() - } - - pub fn loading(&self) -> bool { - self.imp().loading.get() + // The state of the timeline + pub fn state(&self) -> TimelineState { + self.imp().state.get() } pub fn is_empty(&self) -> bool { let priv_ = self.imp(); - priv_.list.borrow().is_empty() || (priv_.list.borrow().len() == 1 && self.loading()) + priv_.list.borrow().is_empty() + || (priv_.list.borrow().len() == 1 && self.state() == TimelineState::Loading) } fn oldest_event(&self) -> Option> { @@ -734,11 +729,14 @@ impl Timeline { } pub fn load_previous_events(&self) { - if self.loading() || self.is_complete() { + if matches!( + self.state(), + TimelineState::Loading | TimelineState::Complete + ) { return; } - self.set_loading(true); + self.set_state(TimelineState::Loading); self.add_loading_spinner(); let matrix_room = self.room().matrix_room(); @@ -770,15 +768,24 @@ impl Timeline { .into_iter() .map(|event| Event::new(event, &obj.room())).collect() }; - obj.set_complete(events.iter().any(|event| matches!(event.matrix_event(), Some(AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_)))))); - obj.prepend(events) + + if events.iter().any(|event| matches!(event.matrix_event(), Some(AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_))))) { + obj.set_state(TimelineState::Complete); + } else { + obj.set_state(TimelineState::Ready); + } + + obj.prepend(events); }, Ok(None) => { error!("The start event wasn't found in the timeline for room {}.", obj.room().room_id()); + obj.set_state(TimelineState::Error); }, - Err(error) => error!("Couldn't load previous events for room {}: {}", error, obj.room().room_id()), + Err(error) => { + error!("Couldn't load previous events for room {}: {}", error, obj.room().room_id()); + obj.set_state(TimelineState::Error); + } } - obj.set_loading(false); }) ); }