1+ use std:: io:: Read ;
2+
13use adw:: prelude:: * ;
24use adw:: subclass:: prelude:: * ;
35use chrono:: NaiveDateTime ;
4- use gtk:: glib;
6+ use gdk_pixbuf:: Pixbuf ;
7+ use gtk:: gdk_pixbuf;
8+ use gtk:: { gdk, gio, glib} ;
59use ntfy_daemon:: models;
10+ use tracing:: error;
611
712mod imp {
813 use super :: * ;
@@ -41,6 +46,7 @@ impl MessageRow {
4146 self . set_margin_end ( 8 ) ;
4247 self . set_column_spacing ( 8 ) ;
4348 self . set_row_spacing ( 8 ) ;
49+ let mut row = 0 ;
4450
4551 let time = gtk:: Label :: builder ( )
4652 . label (
@@ -51,7 +57,7 @@ impl MessageRow {
5157 . xalign ( 0.0 )
5258 . build ( ) ;
5359 time. add_css_class ( "caption" ) ;
54- self . attach ( & time, 0 , 0 , 1 , 1 ) ;
60+ self . attach ( & time, 0 , row , 1 , 1 ) ;
5561
5662 if let Some ( p) = msg. priority {
5763 let text = format ! (
@@ -76,6 +82,7 @@ impl MessageRow {
7682 priority. set_halign ( gtk:: Align :: End ) ;
7783 self . attach ( & priority, 1 , 0 , 2 , 1 ) ;
7884 }
85+ row += 1 ;
7986
8087 if let Some ( title) = msg. display_title ( ) {
8188 let label = gtk:: Label :: builder ( )
@@ -86,7 +93,8 @@ impl MessageRow {
8693 . selectable ( true )
8794 . build ( ) ;
8895 label. add_css_class ( "heading" ) ;
89- self . attach ( & label, 0 , 1 , 3 , 1 ) ;
96+ self . attach ( & label, 0 , row, 3 , 1 ) ;
97+ row += 1 ;
9098 }
9199
92100 if let Some ( message) = msg. display_message ( ) {
@@ -98,7 +106,15 @@ impl MessageRow {
98106 . selectable ( true )
99107 . hexpand ( true )
100108 . build ( ) ;
101- self . attach ( & label, 0 , 2 , 3 , 1 ) ;
109+ self . attach ( & label, 0 , row, 3 , 1 ) ;
110+ row += 1 ;
111+ }
112+
113+ if let Some ( attachment) = msg. attachment {
114+ if attachment. is_image ( ) {
115+ self . attach ( & self . build_image ( attachment. url . to_string ( ) ) , 0 , row, 3 , 1 ) ;
116+ row += 1 ;
117+ }
102118 }
103119
104120 if msg. actions . len ( ) > 0 {
@@ -114,7 +130,8 @@ impl MessageRow {
114130 action_btns. append ( & btn) ;
115131 }
116132
117- self . attach ( & action_btns, 0 , 3 , 3 , 1 ) ;
133+ self . attach ( & action_btns, 0 , row, 3 , 1 ) ;
134+ row += 1 ;
118135 }
119136 if msg. tags . len ( ) > 0 {
120137 let mut tags_text = String :: from ( "tags: " ) ;
@@ -125,9 +142,62 @@ impl MessageRow {
125142 . wrap ( true )
126143 . wrap_mode ( gtk:: pango:: WrapMode :: WordChar )
127144 . build ( ) ;
128- self . attach ( & tags, 0 , 4 , 3 , 1 ) ;
145+ self . attach ( & tags, 0 , row , 3 , 1 ) ;
129146 }
130147 }
148+ fn build_image ( & self , url : String ) -> gtk:: Picture {
149+ let ( tx, rx) = glib:: MainContext :: channel ( Default :: default ( ) ) ;
150+ gio:: spawn_blocking ( move || {
151+ let path = glib:: user_cache_dir ( ) . join ( "com.ranfdev.Notify" ) . join ( & url) ;
152+ let bytes = if path. exists ( ) {
153+ match std:: fs:: read ( & path) {
154+ Ok ( v) => v,
155+ Err ( e) => {
156+ error ! ( error = %e, path = %path. display( ) , "reading image from disk" ) ;
157+ return glib:: ControlFlow :: Break ;
158+ }
159+ }
160+ } else {
161+ let res = match ureq:: get ( & url) . call ( ) {
162+ Ok ( res) => res,
163+ Err ( e) => {
164+ error ! ( error = %e, "fetching image" ) ;
165+ return glib:: ControlFlow :: Break ;
166+ }
167+ } ;
168+ let mut bytes = vec ! [ ] ;
169+ if let Err ( e) = res
170+ . into_reader ( )
171+ . take ( 5 * 1_000_000 ) // 5 MB
172+ . read_to_end ( & mut bytes)
173+ {
174+ error ! ( error = %e, "reading image data" ) ;
175+ return glib:: ControlFlow :: Break ;
176+ }
177+ bytes
178+ } ;
179+
180+ tx. send ( glib:: Bytes :: from_owned ( bytes) ) . unwrap ( ) ;
181+ glib:: ControlFlow :: Break
182+ } ) ;
183+ let picture = gtk:: Picture :: new ( ) ;
184+ picture. set_can_shrink ( true ) ;
185+ picture. set_height_request ( 350 ) ;
186+ let picturec = picture. clone ( ) ;
187+ rx. attach ( Default :: default ( ) , move |b| {
188+ let stream = gio:: MemoryInputStream :: from_bytes ( & b) ;
189+ let pixbuf = match Pixbuf :: from_stream ( & stream, gio:: Cancellable :: NONE ) {
190+ Ok ( res) => res,
191+ Err ( e) => {
192+ error ! ( error = %e, "parsing image contents" ) ;
193+ return glib:: ControlFlow :: Break ;
194+ }
195+ } ;
196+ picturec. set_paintable ( Some ( & gdk:: Texture :: for_pixbuf ( & pixbuf) ) ) ;
197+ glib:: ControlFlow :: Break
198+ } ) ;
199+ picture
200+ }
131201 fn build_action_btn ( & self , action : models:: Action ) -> gtk:: Button {
132202 let btn = gtk:: Button :: new ( ) ;
133203 match & action {
0 commit comments