Show image attachment

This commit is contained in:
ranfdev
2023-10-27 00:05:57 +02:00
parent c8faf2b1e9
commit 3b41ed55ed
2 changed files with 100 additions and 8 deletions

View File

@ -36,9 +36,9 @@ pub struct Message {
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i8>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub attach: Vec<String>,
pub attachment: Option<Attachment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -109,6 +109,28 @@ pub struct MinMessage {
pub time: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Attachment {
pub name: String,
pub url: url::Url,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub atype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<usize>,
}
impl Attachment {
pub fn is_image(&self) -> bool {
let Some(ext) = self.name.split('.').last() else {
return false;
};
["jpeg", "jpg", "png", "webp", "gif"].contains(&ext)
}
}
#[derive(Clone, Debug)]
pub struct Subscription {
pub server: String,

View File

@ -1,8 +1,13 @@
use std::io::Read;
use adw::prelude::*;
use adw::subclass::prelude::*;
use chrono::NaiveDateTime;
use gtk::glib;
use gdk_pixbuf::Pixbuf;
use gtk::gdk_pixbuf;
use gtk::{gdk, gio, glib};
use ntfy_daemon::models;
use tracing::error;
mod imp {
use super::*;
@ -41,6 +46,7 @@ impl MessageRow {
self.set_margin_end(8);
self.set_column_spacing(8);
self.set_row_spacing(8);
let mut row = 0;
let time = gtk::Label::builder()
.label(
@ -51,7 +57,7 @@ impl MessageRow {
.xalign(0.0)
.build();
time.add_css_class("caption");
self.attach(&time, 0, 0, 1, 1);
self.attach(&time, 0, row, 1, 1);
if let Some(p) = msg.priority {
let text = format!(
@ -76,6 +82,7 @@ impl MessageRow {
priority.set_halign(gtk::Align::End);
self.attach(&priority, 1, 0, 2, 1);
}
row += 1;
if let Some(title) = msg.display_title() {
let label = gtk::Label::builder()
@ -86,7 +93,8 @@ impl MessageRow {
.selectable(true)
.build();
label.add_css_class("heading");
self.attach(&label, 0, 1, 3, 1);
self.attach(&label, 0, row, 3, 1);
row += 1;
}
if let Some(message) = msg.display_message() {
@ -98,7 +106,15 @@ impl MessageRow {
.selectable(true)
.hexpand(true)
.build();
self.attach(&label, 0, 2, 3, 1);
self.attach(&label, 0, row, 3, 1);
row += 1;
}
if let Some(attachment) = msg.attachment {
if attachment.is_image() {
self.attach(&self.build_image(attachment.url.to_string()), 0, row, 3, 1);
row += 1;
}
}
if msg.actions.len() > 0 {
@ -114,7 +130,8 @@ impl MessageRow {
action_btns.append(&btn);
}
self.attach(&action_btns, 0, 3, 3, 1);
self.attach(&action_btns, 0, row, 3, 1);
row += 1;
}
if msg.tags.len() > 0 {
let mut tags_text = String::from("tags: ");
@ -125,9 +142,62 @@ impl MessageRow {
.wrap(true)
.wrap_mode(gtk::pango::WrapMode::WordChar)
.build();
self.attach(&tags, 0, 4, 3, 1);
self.attach(&tags, 0, row, 3, 1);
}
}
fn build_image(&self, url: String) -> gtk::Picture {
let (tx, rx) = glib::MainContext::channel(Default::default());
gio::spawn_blocking(move || {
let path = glib::user_cache_dir().join("com.ranfdev.Notify").join(&url);
let bytes = if path.exists() {
match std::fs::read(&path) {
Ok(v) => v,
Err(e) => {
error!(error = %e, path = %path.display(), "reading image from disk");
return glib::ControlFlow::Break;
}
}
} else {
let res = match ureq::get(&url).call() {
Ok(res) => res,
Err(e) => {
error!(error = %e, "fetching image");
return glib::ControlFlow::Break;
}
};
let mut bytes = vec![];
if let Err(e) = res
.into_reader()
.take(5 * 1_000_000) // 5 MB
.read_to_end(&mut bytes)
{
error!(error = %e, "reading image data");
return glib::ControlFlow::Break;
}
bytes
};
tx.send(glib::Bytes::from_owned(bytes)).unwrap();
glib::ControlFlow::Break
});
let picture = gtk::Picture::new();
picture.set_can_shrink(true);
picture.set_height_request(350);
let picturec = picture.clone();
rx.attach(Default::default(), move |b| {
let stream = gio::MemoryInputStream::from_bytes(&b);
let pixbuf = match Pixbuf::from_stream(&stream, gio::Cancellable::NONE) {
Ok(res) => res,
Err(e) => {
error!(error = %e, "parsing image contents");
return glib::ControlFlow::Break;
}
};
picturec.set_paintable(Some(&gdk::Texture::for_pixbuf(&pixbuf)));
glib::ControlFlow::Break
});
picture
}
fn build_action_btn(&self, action: models::Action) -> gtk::Button {
let btn = gtk::Button::new();
match &action {