[fractal/fractal-next] media-message: Handle media loading and errors
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] media-message: Handle media loading and errors
- Date: Mon, 13 Dec 2021 17:10:12 +0000 (UTC)
commit 78f0ae1f389acfb1e9c2b5c65680770759334e1d
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Mon Dec 13 17:49:48 2021 +0100
media-message: Handle media loading and errors
data/resources/resources.gresource.xml | 1 +
data/resources/style.css | 7 ++
data/resources/ui/content-message-media.ui | 39 +++++++
po/POTFILES.in | 1 +
.../content/room_history/message_row/media.rs | 130 ++++++++++++++++-----
5 files changed, 152 insertions(+), 26 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 94155cfb..ee70046e 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -17,6 +17,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invite-subpage.ui">ui/content-invite-subpage.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invitee-item.ui">ui/content-invitee-item.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invitee-row.ui">ui/content-invitee-row.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="content-message-media.ui">ui/content-message-media.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-row.ui">ui/content-message-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-divider-row.ui">ui/content-divider-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-room-details.ui">ui/content-room-details.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 6b4a32cb..a62eefb3 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -212,6 +212,13 @@ headerbar.flat {
.room-history .event-content .thumbnail {
border-radius: 6px;
+ background-color: @light_3;
+}
+
+.room-history .event-content .thumbnail .osd.circular {
+ min-width: 64px;
+ min-height: 64px;
+ border-radius: 32px;
}
.room-history .event-content .thumbnail .timestamp {
diff --git a/data/resources/ui/content-message-media.ui b/data/resources/ui/content-message-media.ui
new file mode 100644
index 00000000..235a682a
--- /dev/null
+++ b/data/resources/ui/content-message-media.ui
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ContentMessageMedia" parent="GtkWidget">
+ <property name="focusable">True</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkOverlay" id="media">
+ <style>
+ <class name="thumbnail"/>
+ </style>
+ <property name="overflow">GTK_OVERFLOW_HIDDEN</property>
+ <child type="overlay">
+ <object class="GtkSpinner" id="overlay_spinner">
+ <property name="halign">GTK_ALIGN_CENTER</property>
+ <property name="valign">GTK_ALIGN_CENTER</property>
+ <layout>
+ <property name="measure">true</property>
+ </layout>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkImage" id="overlay_error">
+ <style>
+ <class name="osd"/>
+ <class name="circular"/>
+ </style>
+ <property name="halign">GTK_ALIGN_CENTER</property>
+ <property name="valign">GTK_ALIGN_CENTER</property>
+ <property name="icon-name">dialog-error-symbolic</property>
+ <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
+ <layout>
+ <property name="measure">true</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ada3f705..2ebe8994 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,7 @@ data/resources/ui/content-invite.ui
data/resources/ui/content-markdown-popover.ui
data/resources/ui/content-member-row.ui
data/resources/ui/content-message-file.ui
+data/resources/ui/content-message-media.ui
data/resources/ui/content-message-row.ui
data/resources/ui/content-room-details.ui
data/resources/ui/content-room-history.ui
diff --git a/src/session/content/room_history/message_row/media.rs
b/src/session/content/room_history/message_row/media.rs
index bab06e6c..41532c1f 100644
--- a/src/session/content/room_history/message_row/media.rs
+++ b/src/session/content/room_history/message_row/media.rs
@@ -6,6 +6,7 @@ use gtk::{
gio,
glib::{self, clone},
subclass::prelude::*,
+ CompositeTemplate,
};
use log::warn;
use matrix_sdk::{
@@ -47,14 +48,32 @@ impl Default for MediaType {
}
}
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[repr(u32)]
+#[genum(type_name = "MediaState")]
+pub enum MediaState {
+ Initial = 0,
+ Loading = 1,
+ Ready = 2,
+ Error = 3,
+}
+
+impl Default for MediaState {
+ fn default() -> Self {
+ Self::Initial
+ }
+}
+
mod imp {
use std::cell::{Cell, RefCell};
+ use glib::subclass::InitializingObject;
use once_cell::sync::Lazy;
use super::*;
- #[derive(Debug, Default)]
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/content-message-media.ui")]
pub struct MessageMedia {
/// The type of media previewed with this image.
pub media_type: Cell<MediaType>,
@@ -64,13 +83,29 @@ mod imp {
pub height: Cell<i32>,
/// The "body" of the image to show as a tooltip. Only used for stickers.
pub body: RefCell<Option<String>>,
+ /// The state of the media.
+ pub state: Cell<MediaState>,
+ #[template_child]
+ pub media: TemplateChild<gtk::Overlay>,
+ #[template_child]
+ pub overlay_error: TemplateChild<gtk::Image>,
+ #[template_child]
+ pub overlay_spinner: TemplateChild<gtk::Spinner>,
}
#[glib::object_subclass]
impl ObjectSubclass for MessageMedia {
const NAME: &'static str = "ContentMessageMedia";
type Type = super::MessageMedia;
- type ParentType = adw::Bin;
+ type ParentType = gtk::Widget;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
}
impl ObjectImpl for MessageMedia {
@@ -110,6 +145,14 @@ mod imp {
None,
glib::ParamFlags::READWRITE,
),
+ glib::ParamSpec::new_enum(
+ "state",
+ "State",
+ "The state of the media",
+ MediaState::static_type(),
+ MediaState::default() as i32,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
]
});
@@ -118,7 +161,7 @@ mod imp {
fn set_property(
&self,
- _obj: &Self::Type,
+ obj: &Self::Type,
_id: usize,
value: &glib::Value,
pspec: &glib::ParamSpec,
@@ -136,6 +179,9 @@ mod imp {
"body" => {
self.body.replace(value.get().unwrap());
}
+ "state" => {
+ obj.set_state(value.get().unwrap());
+ }
_ => unimplemented!(),
}
}
@@ -146,22 +192,20 @@ mod imp {
"width" => self.width.get().to_value(),
"height" => self.height.get().to_value(),
"body" => obj.body().to_value(),
+ "state" => obj.state().to_value(),
_ => unimplemented!(),
}
}
- fn constructed(&self, obj: &Self::Type) {
- self.parent_constructed(obj);
-
- // We need to control the value returned by `measure`.
- obj.set_layout_manager(gtk::NONE_LAYOUT_MANAGER);
+ fn dispose(&self, _obj: &Self::Type) {
+ self.media.unparent();
}
}
impl WidgetImpl for MessageMedia {
fn measure(
&self,
- obj: &Self::Type,
+ _obj: &Self::Type,
orientation: gtk::Orientation,
for_size: i32,
) -> (i32, i32, i32, i32) {
@@ -194,7 +238,7 @@ mod imp {
// We don't want the paintable to be upscaled.
let other = other.min(original_other);
other * original / original_other
- } else if let Some(child) = obj.child() {
+ } else if let Some(child) = self.media.child() {
// Get the natural size of the data.
child.measure(orientation, other).1
} else {
@@ -210,8 +254,8 @@ mod imp {
gtk::SizeRequestMode::HeightForWidth
}
- fn size_allocate(&self, obj: &Self::Type, _width: i32, height: i32, baseline: i32) {
- if let Some(child) = obj.child() {
+ fn size_allocate(&self, _obj: &Self::Type, width: i32, height: i32, baseline: i32) {
+ if let Some(child) = self.media.child() {
// We need to allocate just enough width to the child so it doesn't expand.
let original_width = self.width.get();
let original_height = self.height.get();
@@ -222,18 +266,18 @@ mod imp {
child.measure(gtk::Orientation::Horizontal, height).1
};
- child.allocate(width, height, baseline, None);
+ self.media.allocate(width, height, baseline, None);
+ } else {
+ self.media.allocate(width, height, baseline, None)
}
}
}
-
- impl BinImpl for MessageMedia {}
}
glib::wrapper! {
/// A widget displaying a media message in the timeline.
pub struct MessageMedia(ObjectSubclass<imp::MessageMedia>)
- @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+ @extends gtk::Widget, @implements gtk::Accessible;
}
impl MessageMedia {
@@ -290,10 +334,43 @@ impl MessageMedia {
priv_.body.borrow().clone()
}
+ pub fn state(&self) -> MediaState {
+ let priv_ = imp::MessageMedia::from_instance(self);
+ priv_.state.get()
+ }
+
+ fn set_state(&self, state: MediaState) {
+ let priv_ = imp::MessageMedia::from_instance(self);
+
+ if self.state() == state {
+ return;
+ }
+
+ match state {
+ MediaState::Loading | MediaState::Initial => {
+ priv_.overlay_spinner.set_visible(true);
+ priv_.overlay_error.set_visible(false);
+ }
+ MediaState::Ready => {
+ priv_.overlay_spinner.set_visible(false);
+ priv_.overlay_error.set_visible(false);
+ }
+ MediaState::Error => {
+ priv_.overlay_spinner.set_visible(false);
+ priv_.overlay_error.set_visible(true);
+ }
+ }
+
+ priv_.state.set(state);
+ self.notify("state");
+ }
+
fn build<C>(&self, content: C, session: &Session)
where
C: MediaEventContent + Send + Sync + 'static,
{
+ self.set_state(MediaState::Loading);
+
let media_type = self.media_type();
let client = session.client();
let handle = if media_type != MediaType::Video && content.thumbnail().is_some() {
@@ -317,6 +394,8 @@ impl MessageMedia {
spawn!(
glib::PRIORITY_LOW,
clone!(@weak self as obj => async move {
+ let priv_ = imp::MessageMedia::from_instance(&obj);
+
match handle.await.unwrap() {
Ok(Some(data)) => {
let child: gtk::Widget = match media_type {
@@ -329,9 +408,7 @@ impl MessageMedia {
if media_type == MediaType::Sticker {
child.set_tooltip_text(obj.body().as_deref());
- } else {
- child.add_css_class("thumbnail");
- child.set_overflow(gtk::Overflow::Hidden);
+ priv_.media.remove_css_class("thumbnail");
}
child.upcast()
}
@@ -358,17 +435,18 @@ impl MessageMedia {
}
};
- obj.set_child(Some(&child));
+ priv_.media.set_child(Some(&child));
+ obj.set_state(MediaState::Ready);
}
Ok(None) => {
- warn!("Could not retrieve invalid image file");
- let child = gtk::Label::new(Some(&gettext("Could not retrieve image")));
- obj.set_child(Some(&child));
+ warn!("Could not retrieve invalid media file");
+ priv_.overlay_error.set_tooltip_text(Some(&gettext("Could not retrieve media")));
+ obj.set_state(MediaState::Error);
}
Err(error) => {
- warn!("Could not retrieve image file: {}", error);
- let child = gtk::Label::new(Some(&gettext("Could not retrieve image")));
- obj.set_child(Some(&child));
+ warn!("Could not retrieve media file: {}", error);
+ priv_.overlay_error.set_tooltip_text(Some(&gettext("Could not retrieve media")));
+ obj.set_state(MediaState::Error);
}
}
})
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]