room-history: Show error page on loading errors

This only shows the error page when the timeline is empty.
Adds also a enum for the timeline state.
This commit is contained in:
Julian Sparber 2022-02-10 12:03:47 +01:00
parent f43b1850fd
commit 762973ce19
4 changed files with 103 additions and 63 deletions

View file

@ -107,6 +107,26 @@
</style>
</object>
</child>
<child>
<object class="AdwStatusPage" id="error">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="icon-name">dialog-error-symbolic</property>
<property name="title" translatable="yes">Unable to load room</property>
<property name="description" translatable="yes">Check your network connection.</property>
<property name="child">
<object class="GtkButton">
<property name="label" translatable="yes">Try Again</property>
<property name="action-name">room-history.try-again</property>
<property name="halign">center</property>
<style>
<class name="pill"/>
</style>
</object>
</property>
</object>
</child>
<child>
<object class="GtkOverlay" id="content">
<child type="overlay">
@ -239,3 +259,4 @@
</child>
</template>
</interface>

View file

@ -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<Option<Room>>,
pub category_handler: RefCell<Option<SignalHandlerId>>,
pub empty_timeline_handler: RefCell<Option<SignalHandlerId>>,
pub loading_timeline_handler: RefCell<Option<SignalHandlerId>>,
pub state_timeline_handler: RefCell<Option<SignalHandlerId>>,
pub md_enabled: Cell<bool>,
pub is_auto_scrolling: Cell<bool>,
pub sticky: Cell<bool>,
@ -73,6 +73,8 @@ mod imp {
#[template_child]
pub loading: TemplateChild<gtk::Spinner>,
#[template_child]
pub error: TemplateChild<adw::StatusPage>,
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
}
@ -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::<bool>("scroll-child", &[&gtk::ScrollType::End, &false]);
}
fn try_again(&self) {
if let Some(room) = self.room() {
room.timeline().load_previous_events();
}
}
}
impl Default for RoomHistory {

View file

@ -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},

View file

@ -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<HashMap<String, Box<EventId>>>,
/// A Hashset of `EventId`s that where just redacted.
pub redacted_events: RefCell<HashSet<Box<EventId>>>,
pub loading: Cell<bool>,
pub complete: Cell<bool>,
pub oldest_event: RefCell<Option<Box<EventId>>>,
pub state: Cell<TimelineState>,
/// The most recent verification request event
pub verification: RefCell<Option<IdentityVerification>>,
}
@ -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<Box<EventId>> {
@ -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);
})
);
}