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:
parent
f43b1850fd
commit
762973ce19
4 changed files with 103 additions and 63 deletions
|
@ -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>
|
||||
|
||||
|
|
|
@ -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", &[>k::ScrollType::End, &false]);
|
||||
}
|
||||
|
||||
fn try_again(&self) {
|
||||
if let Some(room) = self.room() {
|
||||
room.timeline().load_previous_events();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RoomHistory {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue