From e15814f798e293209908c8a4f716ae8a156b81c3 Mon Sep 17 00:00:00 2001 From: aaravlu Date: Sun, 26 Jan 2025 21:19:07 +0800 Subject: [PATCH 1/4] Fix thumbnail --- src/home/room_screen.rs | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 5cb96412..fd5d8c9a 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -368,7 +368,7 @@ live_design! { text: "" } } - + message = { } // { @@ -380,7 +380,7 @@ live_design! { reaction_list = { } avatar_row = {} } - + } } } @@ -439,7 +439,7 @@ live_design! { avatar_row = {} } } - + } } @@ -457,7 +457,7 @@ live_design! { avatar_row = {} } } - + } } @@ -980,7 +980,7 @@ live_design! { /* * This is broken currently, so I'm disabling it. - * + * message_action_bar_popup = { align: {x: 0.0, y: 0.0} content: { @@ -1096,8 +1096,8 @@ impl Widget for RoomScreen { tooltip.hide(cx); } let avatar_row_ref = wr.avatar_row(id!(avatar_row)); - if let RoomScreenTooltipActions::HoverInReadReceipt { - tooltip_pos, + if let RoomScreenTooltipActions::HoverInReadReceipt { + tooltip_pos, tooltip_width , callout_offset, pointing_up, @@ -1121,7 +1121,7 @@ impl Widget for RoomScreen { if avatar_row_ref.hover_out(actions) { tooltip.hide(cx); } - } + } for action in actions { // Handle actions on a message, e.g., clicking the reply button or clicking the reply preview. match action.as_widget_action().widget_uid_eq(widget_uid).cast() { @@ -2400,16 +2400,16 @@ pub enum RoomScreenTooltipActions { tooltip_pos: DVec2, tooltip_width: f64, /// Pointed arrow position relative to the tooltip. - /// + /// /// It is calculated from the right corner of tooltip to position arrow. /// to point towards the center of the hovered widget. callout_offset: f64, /// Data that is bound together the widget - /// + /// /// Includes the list of users who have seen this event read_receipts: indexmap::IndexMap, /// Boolean indicating if the callout should be pointing up. - /// + /// /// If false, it is pointing left pointing_up: bool }, @@ -3361,7 +3361,6 @@ fn populate_image_message_content( } }; - let mut media_format = Some(MEDIA_THUMBNAIL_FORMAT.into()); let mut fetch_and_show_media_source = |cx: &mut Cx2d, media_source: MediaSource, media_format: Option| { match media_source { MediaSource::Encrypted(encrypted) => { @@ -3377,20 +3376,23 @@ fn populate_image_message_content( } }; - match image_info_source { - Some((None, media_source)) => { - media_format = None; - // We fetch the original (full-size) media if it does not have a thumbnail. + Some((image_info, media_source)) => { + let thumbnail_source = image_info.and_then(|image_info|{image_info.thumbnail_source}); + + // Only image doesnot have a thumbnail source, we fetch its origin source and apply the format `MEDIA_THUMBNAIL_FORMAT`. + // We don't apply any format if it has a thumbnail source. + // When an inmage's size is smaller than about 500KB, it won't have a thumbnail source, by my test. + let (media_source, media_format) = + if let Some(thumbnail_source) = thumbnail_source { + (thumbnail_source, None) + } + else { + (media_source, Some(MEDIA_THUMBNAIL_FORMAT.into())) + }; + fetch_and_show_media_source(cx, media_source, media_format); - }, - Some((Some(image_info), media_source)) => { - if let Some(thumbnail_source) = image_info.thumbnail_source { - fetch_and_show_media_source(cx, thumbnail_source, media_format); - } else { - fetch_and_show_media_source(cx, media_source, media_format); - } - }, + } None => { text_or_image_ref.show_text(cx, "{body}\n\nImage message had no source URL."); fully_drawn = true; @@ -3425,7 +3427,7 @@ fn populate_file_message_content( // TODO: add a button to download the file message_content_widget.show_html( - cx, + cx, format!("{filename}{size}{caption}
File download not yet supported."), ); true @@ -3583,7 +3585,7 @@ fn draw_replied_to_message( match &in_reply_to_details.event { TimelineDetails::Ready(replied_to_event) => { - let (in_reply_to_username, is_avatar_fully_drawn) = + let (in_reply_to_username, is_avatar_fully_drawn) = replied_to_message_view .avatar(id!(replied_to_message_content.reply_preview_avatar)) .set_avatar_and_get_username( @@ -4343,4 +4345,4 @@ pub fn room_screen_tooltip_position_helper(widget_rect: Rect, window_geom: &even 10.0 }; (tooltip_pos, callout_offset, too_close_to_right) -} \ No newline at end of file +} From 439bc70826984cfae10c66f832c5014be20c8e0f Mon Sep 17 00:00:00 2001 From: aaravlu Date: Sun, 26 Jan 2025 22:51:55 +0800 Subject: [PATCH 2/4] Distinct the format between 'timeline image' and 'avatar' --- src/home/room_screen.rs | 28 +++++++++++----------------- src/sliding_sync.rs | 24 ++++++++++++------------ src/utils.rs | 16 ++++++++++++++-- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index fd5d8c9a..0dbc3740 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -7,7 +7,7 @@ use bytesize::ByteSize; use imbl::Vector; use makepad_widgets::*; use matrix_sdk::{ - media::MediaFormat, ruma::{ + ruma::{ events::{receipt::Receipt, room::{ message::{ AudioMessageEventContent, CustomEventContent, EmoteMessageEventContent, FileMessageEventContent, FormattedBody, ImageMessageEventContent, KeyVerificationRequestEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, ServerNoticeMessageEventContent, TextMessageEventContent, VideoMessageEventContent @@ -26,7 +26,7 @@ use crate::{ user_profile_cache, }, shared::{ avatar::AvatarWidgetRefExt, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::enqueue_popup_notification, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt - }, sliding_sync::{self, get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender}, utils::{self, unix_time_millis_to_datetime, ImageFormat, MediaFormatConst, MEDIA_THUMBNAIL_FORMAT}, + }, sliding_sync::{self, get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender}, utils::{self, unix_time_millis_to_datetime, ImageFormat, MediaFormatConst, TIMELINE_IMAGE_THUMBNAIL_FORMAT}, }; use crate::home::event_reaction_list::ReactionListWidgetRefExt; use crate::home::room_read_receipt::AvatarRowWidgetRefExt; @@ -3331,8 +3331,8 @@ fn populate_image_message_content( // A closure that fetches and shows the image from the given `mxc_uri`, // marking it as fully drawn if the image was available. - let mut fetch_and_show_image_uri = |cx: &mut Cx2d, mxc_uri: OwnedMxcUri, media_format: Option| - match media_cache.try_get_media_or_fetch(mxc_uri.clone(), media_format) { + let mut fetch_and_show_image_uri = |cx: &mut Cx2d, mxc_uri: OwnedMxcUri| + match media_cache.try_get_media_or_fetch(mxc_uri.clone(), Some(TIMELINE_IMAGE_THUMBNAIL_FORMAT.into())) { MediaCacheEntry::Loaded(data) => { let show_image_result = text_or_image_ref.show_image(cx, |cx, img| { utils::load_png_or_jpg(&img, cx, &data) @@ -3361,7 +3361,7 @@ fn populate_image_message_content( } }; - let mut fetch_and_show_media_source = |cx: &mut Cx2d, media_source: MediaSource, media_format: Option| { + let mut fetch_and_show_media_source = |cx: &mut Cx2d, media_source: MediaSource| { match media_source { MediaSource::Encrypted(encrypted) => { // We consider this as "fully drawn" since we don't yet support encryption. @@ -3371,27 +3371,21 @@ fn populate_image_message_content( ); }, MediaSource::Plain(mxc_uri) => { - fetch_and_show_image_uri(cx, mxc_uri, media_format) + fetch_and_show_image_uri(cx, mxc_uri) } } }; match image_info_source { - Some((image_info, media_source)) => { + Some((image_info, origin_source)) => { let thumbnail_source = image_info.and_then(|image_info|{image_info.thumbnail_source}); - // Only image doesnot have a thumbnail source, we fetch its origin source and apply the format `MEDIA_THUMBNAIL_FORMAT`. - // We don't apply any format if it has a thumbnail source. // When an inmage's size is smaller than about 500KB, it won't have a thumbnail source, by my test. - let (media_source, media_format) = - if let Some(thumbnail_source) = thumbnail_source { - (thumbnail_source, None) - } - else { - (media_source, Some(MEDIA_THUMBNAIL_FORMAT.into())) - }; + // We must apply a format anyhow the image possess a thumbnail source. + let media_source = + if let Some(thumbnail_source) = thumbnail_source { thumbnail_source } else { origin_source }; - fetch_and_show_media_source(cx, media_source, media_format); + fetch_and_show_media_source(cx, media_source); } None => { text_or_image_ref.show_text(cx, "{body}\n\nImage message had no source URL."); diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index 50633dae..b6aaa02b 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -31,7 +31,7 @@ use crate::{ }, login::login_screen::LoginAction, media_cache::MediaCacheEntry, persistent_state::{self, ClientSessionPersisted}, profile::{ user_profile::{AvatarState, UserProfile}, user_profile_cache::{enqueue_user_profile_update, UserProfileUpdate}, - }, shared::{jump_to_bottom_button::UnreadMessageCount, popup_list::enqueue_popup_notification}, utils::MEDIA_THUMBNAIL_FORMAT, verification::add_verification_event_handlers_and_sync_client + }, shared::{jump_to_bottom_button::UnreadMessageCount, popup_list::enqueue_popup_notification}, utils::AVATAR_THUMBNAIL_FORMAT, verification::add_verification_event_handlers_and_sync_client }; #[derive(Parser, Debug, Default)] @@ -740,7 +740,7 @@ async fn async_worker( let (timeline, sender) = { let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); - let Some(room_info) = all_room_info.get_mut(&room_id) else { + let Some(room_info) = all_room_info.get_mut(&room_id) else { log!("BUG: room info not found for subscribe to own user read receipts changed request, room {room_id}"); continue; }; @@ -764,8 +764,8 @@ async fn async_worker( if let Some(client_user_id) = current_user_id() { if let Some((event_id, receipt)) = timeline.latest_user_read_receipt(&client_user_id).await { log!("Received own user read receipt: {receipt:?} {event_id:?}"); - if let Err(e) = sender.send(TimelineUpdate::OwnUserReadReceipt(receipt)) { - error!("Failed to get own user read receipt: {e:?}"); + if let Err(e) = sender.send(TimelineUpdate::OwnUserReadReceipt(receipt)) { + error!("Failed to get own user read receipt: {e:?}"); } } while (update_receiver.next().await).is_some() { @@ -776,8 +776,8 @@ async fn async_worker( break; } if let Some((_, receipt)) = timeline.latest_user_read_receipt(&client_user_id).await { - if let Err(e) = sender.send(TimelineUpdate::OwnUserReadReceipt(receipt)) { - error!("Failed to get own user read receipt: {e:?}"); + if let Err(e) = sender.send(TimelineUpdate::OwnUserReadReceipt(receipt)) { + error!("Failed to get own user read receipt: {e:?}"); } } } @@ -802,7 +802,7 @@ async fn async_worker( // log!("Sending fetch avatar request for {mxc_uri:?}..."); let media_request = MediaRequest { source: MediaSource::Plain(mxc_uri.clone()), - format: MEDIA_THUMBNAIL_FORMAT.into(), + format: AVATAR_THUMBNAIL_FORMAT.into(), }; let res = client.media().get_media_content(&media_request, true).await; // log!("Fetched avatar for {mxc_uri:?}, succeeded? {}", res.is_ok()); @@ -889,7 +889,7 @@ async fn async_worker( }; let _send_frr_task = Handle::current().spawn(async move { match timeline.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id.clone()).await { - Ok(sent) => log!("{} fully read receipt to room {room_id} for event {event_id}", + Ok(sent) => log!("{} fully read receipt to room {room_id} for event {event_id}", if sent { "Sent" } else { "Already sent" } ), Err(_e) => error!("Failed to send fully read receipt to room {room_id} for event {event_id}; error: {_e:?}"), @@ -1526,7 +1526,7 @@ async fn add_new_room(room: &room_list_service::Room, room_list_service: &RoomLi // log!("Room {room_id} is now fully synced? {}", room.is_state_fully_synced()); // } - // This will sync the room + // This will sync the room room_list_service.subscribe_to_rooms(&[&room_id]); // Do not add tombstoned rooms to the rooms list; they require special handling. @@ -2160,13 +2160,13 @@ fn spawn_fetch_room_avatar(room: Room) { /// Fetches and returns the avatar image for the given room (if one exists), /// otherwise returns a text avatar string of the first character of the room name. async fn room_avatar(room: &Room, room_name: &Option) -> RoomPreviewAvatar { - match room.avatar(MEDIA_THUMBNAIL_FORMAT.into()).await { + match room.avatar(AVATAR_THUMBNAIL_FORMAT.into()).await { Ok(Some(avatar)) => RoomPreviewAvatar::Image(avatar), _ => { if let Ok(room_members) = room.members(RoomMemberships::ACTIVE).await { if room_members.len() == 2 { if let Some(non_account_member) = room_members.iter().find(|m| !m.is_account_user()) { - return match non_account_member.avatar(MEDIA_THUMBNAIL_FORMAT.into()).await { + return match non_account_member.avatar(AVATAR_THUMBNAIL_FORMAT.into()).await { Ok(Some(avatar)) => RoomPreviewAvatar::Image(avatar), _ => avatar_from_room_name(room_name.as_deref().unwrap_or_default()), }; @@ -2185,7 +2185,7 @@ fn avatar_from_room_name(room_name: &str) -> RoomPreviewAvatar { RoomPreviewAvatar::Text( room_name .graphemes(true) - .find(|&g| g != "@") + .find(|&g| g != "@") .map(ToString::to_string) .unwrap_or_default() ) diff --git a/src/utils.rs b/src/utils.rs index 01d9949f..29ab1f3d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -197,8 +197,8 @@ impl From for MediaThumbnailSize { } } -/// The default media format to use for thumbnail requests. -pub const MEDIA_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail( +/// The avatar format to use for thumbnail requests. +pub const AVATAR_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail( MediaThumbnailSettingsConst { size: MediaThumbnailSizeConst { method: Method::Scale, @@ -209,6 +209,18 @@ pub const MEDIA_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail } ); +/// The timeline image format to use for thumbnail requests. +pub const TIMELINE_IMAGE_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail( + MediaThumbnailSettingsConst { + size: MediaThumbnailSizeConst { + method: Method::Scale, + width: 400, + height: 400, + }, + animated: false, + } +); + /// Removes leading whitespace and HTML whitespace tags (`

` and `
`) from the given `text`. pub fn trim_start_html_whitespace(mut text: &str) -> &str { let mut prev_text_len = text.len(); From f4198ed56e07fe155904a45c75929754d255a97a Mon Sep 17 00:00:00 2001 From: aaravlu Date: Sun, 26 Jan 2025 23:02:06 +0800 Subject: [PATCH 3/4] Optimize related logic --- src/home/room_screen.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 0dbc3740..c75b54f1 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -3378,12 +3378,11 @@ fn populate_image_message_content( match image_info_source { Some((image_info, origin_source)) => { - let thumbnail_source = image_info.and_then(|image_info|{image_info.thumbnail_source}); - // When an inmage's size is smaller than about 500KB, it won't have a thumbnail source, by my test. // We must apply a format anyhow the image possess a thumbnail source. - let media_source = - if let Some(thumbnail_source) = thumbnail_source { thumbnail_source } else { origin_source }; + let media_source = image_info + .and_then(|image_info|{image_info.thumbnail_source}) + .unwrap_or(origin_source); fetch_and_show_media_source(cx, media_source); } From 4d4f589314ac9ce72af69e59925c7e357686855b Mon Sep 17 00:00:00 2001 From: Kevin Boos <1139460+kevinaboos@users.noreply.github.com> Date: Sun, 26 Jan 2025 11:48:25 -0800 Subject: [PATCH 4/4] clarify comments --- src/home/room_screen.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index c75b54f1..9dd3ec01 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -3377,13 +3377,11 @@ fn populate_image_message_content( }; match image_info_source { - Some((image_info, origin_source)) => { - // When an inmage's size is smaller than about 500KB, it won't have a thumbnail source, by my test. - // We must apply a format anyhow the image possess a thumbnail source. + Some((image_info, original_source)) => { + // Use the provided thumbnail URI if it exists; otherwise use the original URI. let media_source = image_info - .and_then(|image_info|{image_info.thumbnail_source}) - .unwrap_or(origin_source); - + .and_then(|image_info| image_info.thumbnail_source) + .unwrap_or(original_source); fetch_and_show_media_source(cx, media_source); } None => {