@@ -26,7 +26,7 @@ use matrix_sdk_ui::timeline::{
2626} ;
2727
2828use crate :: {
29- app:: AppStateAction , avatar_cache, event_preview:: { plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, text_preview_of_other_message_like, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item} , home:: { edited_indicator:: EditedIndicatorWidgetRefExt , editing_pane:: EditingPaneState , loading_pane:: { LoadingPaneState , LoadingPaneWidgetExt } , rooms_list:: RoomsListRef , tombstone_footer:: TombstoneFooterWidgetExt } , location:: init_location_subscriber, media_cache:: { MediaCache , MediaCacheEntry } , profile:: {
29+ app:: AppStateAction , avatar_cache, event_preview:: { plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, text_preview_of_other_message_like, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item} , home:: { edited_indicator:: EditedIndicatorWidgetRefExt , editing_pane:: EditingPaneState , link_preview :: { LinkPreviewCache , LinkPreviewRef , LinkPreviewWidgetRefExt } , loading_pane:: { LoadingPaneState , LoadingPaneWidgetExt } , rooms_list:: RoomsListRef , tombstone_footer:: TombstoneFooterWidgetExt } , location:: init_location_subscriber, media_cache:: { MediaCache , MediaCacheEntry } , profile:: {
3030 user_profile:: { AvatarState , ShowUserProfileAction , UserProfile , UserProfileAndRoomId , UserProfilePaneInfo , UserProfileSlidingPaneRef , UserProfileSlidingPaneWidgetExt } ,
3131 user_profile_cache,
3232 } , room:: typing_notice:: TypingNoticeWidgetExt , shared:: {
@@ -81,6 +81,7 @@ live_design! {
8181 use crate :: home:: tombstone_footer:: TombstoneFooter ;
8282 use crate :: rooms_list:: * ;
8383 use crate :: shared:: restore_status_view:: * ;
84+ use crate :: home:: link_preview:: LinkPreview ;
8485 use link:: tsp_link:: TspSignIndicator ;
8586
8687 IMG_DEFAULT_AVATAR = dep( "crate://self/resources/img/default_avatar.png" )
@@ -340,6 +341,7 @@ live_design! {
340341 }
341342
342343 message = <HtmlOrPlaintext > { }
344+ link_preview_view = <LinkPreview > { }
343345
344346 // <LineH> {
345347 // margin: {top: 13.0, bottom: 5.0}
@@ -384,6 +386,7 @@ live_design! {
384386 padding: { left: 10.0 }
385387
386388 message = <HtmlOrPlaintext > { }
389+ link_preview_view = <LinkPreview > { }
387390 <View > {
388391 width: Fill ,
389392 height: Fit
@@ -1292,6 +1295,7 @@ impl Widget for RoomScreen {
12921295 msg_like_content,
12931296 prev_event,
12941297 & mut tl_state. media_cache ,
1298+ & mut tl_state. link_preview_cache ,
12951299 & tl_state. user_power ,
12961300 item_drawn_status,
12971301 room_screen_widget_uid,
@@ -2290,7 +2294,8 @@ impl RoomScreen {
22902294 profile_drawn_since_last_update : RangeSet :: new ( ) ,
22912295 update_receiver,
22922296 request_sender,
2293- media_cache : MediaCache :: new ( Some ( update_sender) ) ,
2297+ media_cache : MediaCache :: new ( Some ( update_sender. clone ( ) ) ) ,
2298+ link_preview_cache : LinkPreviewCache :: new ( Some ( update_sender) ) ,
22942299 replying_to : None ,
22952300 saved_state : SavedState :: default ( ) ,
22962301 message_highlight_animation_state : MessageHighlightAnimationState :: default ( ) ,
@@ -2832,6 +2837,8 @@ struct TimelineUiState {
28322837 /// Currently this excludes avatars, as those are shared across multiple rooms.
28332838 media_cache : MediaCache ,
28342839
2840+ /// Cache for link preview data indexed by URL to avoid redundant network requests.
2841+ link_preview_cache : LinkPreviewCache ,
28352842 /// Info about the event currently being replied to, if any.
28362843 replying_to : Option < ( EventTimelineItem , EmbeddedEvent ) > ,
28372844
@@ -3000,6 +3007,7 @@ fn populate_message_view(
30003007 msg_like_content : & MsgLikeContent ,
30013008 prev_event : Option < & Arc < TimelineItem > > ,
30023009 media_cache : & mut MediaCache ,
3010+ link_preview_cache : & mut LinkPreviewCache ,
30033011 user_power_levels : & UserPowerLevels ,
30043012 item_drawn_status : ItemDrawnStatus ,
30053013 room_screen_widget_uid : WidgetUid ,
@@ -3045,13 +3053,15 @@ fn populate_message_view(
30453053 if existed && item_drawn_status. content_drawn {
30463054 ( item, true )
30473055 } else {
3048- populate_text_message_content (
3056+ new_drawn_status . content_drawn = populate_text_message_content (
30493057 cx,
30503058 & item. html_or_plaintext ( id ! ( content. message) ) ,
30513059 body,
30523060 formatted. as_ref ( ) ,
3061+ Some ( & mut item. link_preview ( id ! ( content. link_preview_view) ) ) ,
3062+ Some ( media_cache) ,
3063+ Some ( link_preview_cache) ,
30533064 ) ;
3054- new_drawn_status. content_drawn = true ;
30553065 ( item, false )
30563066 }
30573067 }
@@ -3081,13 +3091,15 @@ fn populate_message_view(
30813091 }
30823092 }
30833093 ) ) ;
3084- populate_text_message_content (
3094+ new_drawn_status . content_drawn = populate_text_message_content (
30853095 cx,
30863096 & html_or_plaintext_ref,
30873097 body,
30883098 formatted. as_ref ( ) ,
3099+ Some ( & mut item. link_preview ( id ! ( content. link_preview_view) ) ) ,
3100+ Some ( media_cache) ,
3101+ Some ( link_preview_cache) ,
30893102 ) ;
3090- new_drawn_status. content_drawn = true ;
30913103 ( item, false )
30923104 }
30933105 }
@@ -3121,16 +3133,18 @@ fn populate_message_view(
31213133 . map( |c| format!( "\n <i>Admin contact:</i> {}" , c) )
31223134 . unwrap_or_default( ) ,
31233135 ) ;
3124- populate_text_message_content (
3136+ new_drawn_status . content_drawn = populate_text_message_content (
31253137 cx,
31263138 & html_or_plaintext_ref,
31273139 & sn. body ,
31283140 Some ( & FormattedBody {
31293141 format : MessageFormat :: Html ,
31303142 body : formatted,
31313143 } ) ,
3144+ Some ( & mut item. link_preview ( id ! ( content. link_preview_view) ) ) ,
3145+ Some ( media_cache) ,
3146+ Some ( link_preview_cache) ,
31323147 ) ;
3133- new_drawn_status. content_drawn = true ;
31343148 ( item, false )
31353149 }
31363150 }
@@ -3168,14 +3182,17 @@ fn populate_message_view(
31683182 } else {
31693183 ( Cow :: from ( format ! ( "* {} {}" , & username, body) ) , None )
31703184 } ;
3171- populate_text_message_content (
3185+ let link_previews_drawn = populate_text_message_content (
31723186 cx,
31733187 & item. html_or_plaintext ( id ! ( content. message) ) ,
31743188 & body,
31753189 formatted. as_ref ( ) ,
3190+ Some ( & mut item. link_preview ( id ! ( content. link_preview_view) ) ) ,
3191+ Some ( media_cache) ,
3192+ Some ( link_preview_cache) ,
31763193 ) ;
31773194 set_username_and_get_avatar_retval = Some ( ( username, profile_drawn) ) ;
3178- new_drawn_status. content_drawn = true ;
3195+ new_drawn_status. content_drawn = link_previews_drawn ;
31793196 ( item, false )
31803197 }
31813198 }
@@ -3302,13 +3319,15 @@ fn populate_message_view(
33023319 ) ,
33033320 } ;
33043321
3305- populate_text_message_content (
3322+ new_drawn_status . content_drawn = populate_text_message_content (
33063323 cx,
33073324 & item. html_or_plaintext ( id ! ( content. message) ) ,
33083325 & verification. body ,
33093326 Some ( & formatted) ,
3327+ Some ( & mut item. link_preview ( id ! ( content. link_preview_view) ) ) ,
3328+ Some ( media_cache) ,
3329+ Some ( link_preview_cache) ,
33103330 ) ;
3311- new_drawn_status. content_drawn = true ;
33123331 ( item, false )
33133332 }
33143333 }
@@ -3516,38 +3535,61 @@ fn populate_message_view(
35163535}
35173536
35183537/// Draws the Html or plaintext body of the given Text or Notice message into the `message_content_widget`.
3538+ /// Also populates link previews if a link_preview_ref is provided.
3539+ /// Returns whether the text items were fully drawn.
35193540fn populate_text_message_content (
35203541 cx : & mut Cx ,
35213542 message_content_widget : & HtmlOrPlaintextRef ,
35223543 body : & str ,
35233544 formatted_body : Option < & FormattedBody > ,
3524- ) {
3545+ link_preview_ref : Option < & mut LinkPreviewRef > ,
3546+ media_cache : Option < & mut MediaCache > ,
3547+ link_preview_cache : Option < & mut LinkPreviewCache > ,
3548+ ) -> bool {
35253549 // The message was HTML-formatted rich text.
3526- if let Some ( fb) = formatted_body. as_ref ( )
3550+ let links = if let Some ( fb) = formatted_body. as_ref ( )
35273551 . and_then ( |fb| ( fb. format == MessageFormat :: Html ) . then_some ( fb) )
35283552 {
3553+ let ( linkified_html, links) = utils:: linkify_get_urls (
3554+ utils:: trim_start_html_whitespace ( & fb. body ) ,
3555+ true ,
3556+ ) ;
35293557 message_content_widget. show_html (
35303558 cx,
3531- utils:: linkify (
3532- utils:: trim_start_html_whitespace ( & fb. body ) ,
3533- true ,
3534- )
3559+ linkified_html
35353560 ) ;
3561+ links
35363562 }
35373563 // The message was non-HTML plaintext.
35383564 else {
3539- match utils:: linkify ( body, false ) {
3565+ let ( linkified_html, links) = utils:: linkify_get_urls ( body, false ) ;
3566+ match linkified_html {
35403567 Cow :: Owned ( linkified_html) => message_content_widget. show_html ( cx, & linkified_html) ,
35413568 Cow :: Borrowed ( plaintext) => message_content_widget. show_plaintext ( cx, plaintext) ,
35423569 }
3570+ links
3571+ } ;
3572+
3573+ // Populate link previews if all required parameters are provided
3574+ if let ( Some ( link_preview_ref) , Some ( media_cache) , Some ( link_preview_cache) ) =
3575+ ( link_preview_ref, media_cache, link_preview_cache) {
3576+ link_preview_ref. populate_below_message (
3577+ cx,
3578+ & links,
3579+ media_cache,
3580+ link_preview_cache,
3581+ & populate_image_message_content,
3582+ )
3583+ } else {
3584+ true
35433585 }
35443586}
35453587
35463588/// Draws the given image message's content into the `message_content_widget`.
35473589///
35483590/// Returns whether the image message content was fully drawn.
35493591fn populate_image_message_content (
3550- cx : & mut Cx2d ,
3592+ cx : & mut Cx ,
35513593 text_or_image_ref : & TextOrImageRef ,
35523594 image_info_source : Option < Box < ImageInfo > > ,
35533595 original_source : MediaSource ,
@@ -3576,7 +3618,7 @@ fn populate_image_message_content(
35763618
35773619 // A closure that fetches and shows the image from the given `mxc_uri`,
35783620 // marking it as fully drawn if the image was available.
3579- let mut fetch_and_show_image_uri = |cx : & mut Cx2d , mxc_uri : OwnedMxcUri , image_info : Box < ImageInfo > | {
3621+ let mut fetch_and_show_image_uri = |cx : & mut Cx , mxc_uri : OwnedMxcUri , image_info : Box < ImageInfo > | {
35803622 match media_cache. try_get_media_or_fetch ( mxc_uri. clone ( ) , MEDIA_THUMBNAIL_FORMAT . into ( ) ) {
35813623 ( MediaCacheEntry :: Loaded ( data) , _media_format) => {
35823624 let show_image_result = text_or_image_ref. show_image ( cx, |cx, img| {
@@ -3640,6 +3682,10 @@ fn populate_image_message_content(
36403682 fully_drawn = false ;
36413683 }
36423684 ( MediaCacheEntry :: Failed , _media_format) => {
3685+ if text_or_image_ref. view ( id ! ( default_image_view) ) . visible ( ) {
3686+ fully_drawn = true ;
3687+ return ;
3688+ }
36433689 text_or_image_ref
36443690 . show_text ( cx, format ! ( "{body}\n \n Failed to fetch image from {:?}" , mxc_uri) ) ;
36453691 // For now, we consider this as being "complete". In the future, we could support
@@ -3649,7 +3695,7 @@ fn populate_image_message_content(
36493695 }
36503696 } ;
36513697
3652- let mut fetch_and_show_media_source = |cx : & mut Cx2d , media_source : MediaSource , image_info : Box < ImageInfo > | {
3698+ let mut fetch_and_show_media_source = |cx : & mut Cx , media_source : MediaSource , image_info : Box < ImageInfo > | {
36533699 match media_source {
36543700 MediaSource :: Encrypted ( encrypted) => {
36553701 // We consider this as "fully drawn" since we don't yet support encryption.
@@ -3841,6 +3887,7 @@ fn populate_location_message_content(
38413887 true
38423888}
38433889
3890+
38443891/// Draws a ReplyPreview above a message if it was in-reply to another message.
38453892///
38463893/// If the given `in_reply_to` details are `None`,
@@ -3948,7 +3995,8 @@ fn populate_preview_of_timeline_item(
39483995 match m. msgtype ( ) {
39493996 MessageType :: Text ( TextMessageEventContent { body, formatted, .. } )
39503997 | MessageType :: Notice ( NoticeMessageEventContent { body, formatted, .. } ) => {
3951- return populate_text_message_content ( cx, widget_out, body, formatted. as_ref ( ) ) ;
3998+ let _ = populate_text_message_content ( cx, widget_out, body, formatted. as_ref ( ) , None , None , None ) ;
3999+ return ;
39524000 }
39534001 _ => { } // fall through to the general case for all timeline items below.
39544002 }
0 commit comments