Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 20 additions & 30 deletions src/home/link_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
};

use makepad_widgets::*;
use crate::{LivePtr, widget_ref_from_live_ptr};
use crate::{LivePtr, utils, widget_ref_from_live_ptr};
use matrix_sdk::ruma::{events::room::{ImageInfo, MediaSource}, OwnedMxcUri, UInt};
use serde::Deserialize;
use url::Url;
Expand All @@ -19,8 +19,6 @@ use crate::{
sliding_sync::{submit_async_request, MatrixRequest, UrlPreviewError},
};

/// Maximum length for link preview descriptions before truncation
const MAX_DESCRIPTION_LENGTH: usize = 180;
/// Maximum number of cache entries before cleanup is triggered
const MAX_CACHE_ENTRIES_BEFORE_CLEANUP: usize = 100;
/// Maximum age for cache entries in seconds (1 hour)
Expand Down Expand Up @@ -165,7 +163,7 @@ script_mod! {
width: Fill, height: Fill,
flow: Down,

View {
inner_content_view := View {
width: Fit, height: Fit,
flow: Flow.Right{wrap: true},

Expand Down Expand Up @@ -193,21 +191,18 @@ script_mod! {
}
}

View {
width: Fill, height: Fit,

description_label := Label {
width: Fill, height: Fit,
flow: Flow.Right{wrap: true},
padding: Inset{ left: 0.0 }
max_lines: 2
text_overflow: Ellipsis
draw_text +: {
text_style: mod.widgets.LINK_PREVIEW_MESSAGE_TEXT_STYLE {
font_size: 11.0,
},
color: #666666,
}
description_label := Label {
width: Fill, height: Fit,
flow: Flow.Right{wrap: true},
padding: Inset{ left: 0.0 }
max_lines: 2
text_overflow: Ellipsis
draw_text +: {
text_style: mod.widgets.LINK_PREVIEW_MESSAGE_TEXT_STYLE {
font_size: 11.0,
},
color: #666666,
}
}
}
Expand Down Expand Up @@ -408,24 +403,19 @@ impl LinkPreviewRef {
// Set site name
if let Some(site_name) = &link_preview_data.site_name {
view_ref
.view(cx, ids!(content_view))
.label(cx, ids!(site_name_label))
.set_text(cx, site_name);
}

// Set description with size limit
if let Some(description) = &link_preview_data.description {
let mut description = description.clone();
description = description.replace("\n\n", " ");
let truncated_description = if description.len() > MAX_DESCRIPTION_LENGTH {
format!("{}...", &description[..(MAX_DESCRIPTION_LENGTH - 3)])
} else {
description
};
// Set description; the description_label uses max_lines: 2 with
// text_overflow: Ellipsis to wrap and truncate automatically.
// Collapse all whitespace runs into single spaces so hard line breaks
// don't burn one of the two available lines.
if let Some(description_raw) = &link_preview_data.description {
let description = utils::replace_linebreaks_separators(description_raw, false);
view_ref
.view(cx, ids!(content_view))
.label(cx, ids!(description_label))
.set_text(cx, &truncated_description);
.set_text(cx, &description);
}

// Handle image through closure
Expand Down
140 changes: 47 additions & 93 deletions src/shared/image_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ script_mod! {
width: Fill, height: Fit
flow: Right
align: Align{y: 0.5, x: 0.0}
padding: 13
padding: Inset{top: 13, bottom: 8, left: 13, right: 13}
spacing: 8,

show_bg: true
Expand All @@ -214,14 +214,13 @@ script_mod! {
color: (COLOR_IMAGE_VIEWER_META_BACKGROUND)
}

// Display user profile view below the button group when the width is not enough.
user_profile_view := View {
width: Fit,
height: Fit,
flow: Right,
spacing: 13,
align: Align{ y: 0.5 }

avatar_timestamp_view := View {
width: Fit
height: Fit
flow: Down
spacing: 2
align: Align{x: 0.5, y: 0.0}

avatar := Avatar {
width: 45, height: 45,
text_view +: {
Expand All @@ -232,55 +231,54 @@ script_mod! {
}
}
}

content := View {
width: Fit
timestamp := Timestamp {
width: Fit,
height: Fit,
align: Align{ y: 0.5 }
spacing: 3
flow: Down,

username := Label {
width: Fit
height: Fit,
padding: 0
margin: 0
flow: Right
ts_label := Label {
draw_text +: {
text_style: REGULAR_TEXT {font_size: 12},
text_style: theme.font_regular {font_size: 9.5},
color: (COLOR_TEXT)
}
}
}
}

timestamp_view := View {
width: Fit
height: Fit

timestamp := Timestamp {
width: Fit,
height: Fit,
ts_label := Label {
draw_text +: {
text_style: theme.font_regular {font_size: 9.5},
color: (COLOR_TEXT)
}
}
}
username_label_view := View {
width: Fill{weight: 0.35},
// width: Fill,
height: Fit,
flow: Right,
align: Align{ y: 0.5 }

username := Label {
width: Fill,
height: Fit,
padding: 0
margin: 0
flow: Flow.Right{wrap: true}
max_lines: 2
text_overflow: Ellipsis
draw_text +: {
text_style: REGULAR_TEXT {font_size: 12},
color: (COLOR_TEXT)
}
}
}

// Display image name and size below the user_profile_view if the width is not enough.
// Display image name and size below the username when the width is not enough.
image_name_and_size_view := View {
width: Fill
width: Fill{weight: 0.65},
// width: Fill
height: Fit,
align: Align{x: 0.5, y: 0.5}
align: Align{x: 0, y: 0.5}
flow: Right
image_name_and_size := Label {
width: Fill,
height: Fit,
align: Align{x: 0.5, y: 0.5}
align: Align{x: 0, y: 0.5}
flow: Flow.Right{wrap: true}
max_lines: 2
text_overflow: Ellipsis
draw_text +: {
text_style: REGULAR_TEXT {font_size: 13},
color: (COLOR_TEXT),
Expand Down Expand Up @@ -1099,45 +1097,33 @@ impl ImageViewer {

/// Sets the metadata view in the image viewer with the provided metadata.
///
/// The metadata view is updated with the truncated image name and the human-readable size of the image.
///
/// The image name is truncated to 24 characters and appended with "..." if it exceeds the limit.
/// The human-readable size is calculated based on the image size in bytes.
/// The image_name_and_size and username labels handle their own overflow
/// via `max_lines: 2` + `text_overflow: Ellipsis` in the layout.
pub fn set_metadata(&mut self, cx: &mut Cx, metadata: &ImageViewerMetaData) {
let meta_view = self.view.view(cx, ids!(metadata_view));
let truncated_name = truncate_image_name(&metadata.image_name);
let human_readable_size = format_file_size(metadata.image_file_size);
let display_text = format!("{} ({})", truncated_name, human_readable_size);
let display_text = format!("{} ({})", metadata.image_name, human_readable_size);
meta_view
.label(cx, ids!(image_name_and_size))
.set_text(cx, &display_text);
if let Some(timestamp) = metadata.timestamp {
meta_view
.view(cx, ids!(user_profile_view.content.timestamp_view))
.set_visible(cx, true);
meta_view
.timestamp(cx, ids!(user_profile_view.content.timestamp_view.timestamp))
.timestamp(cx, ids!(avatar_timestamp_view.timestamp))
.set_date_time(cx, timestamp);
}

if let Some((timeline_kind, event_timeline_item)) = &metadata.avatar_parameter {
let (sender, _) = self.view.avatar(cx, ids!(user_profile_view.avatar)).set_avatar_and_get_username(
let (sender, _) = self.view.avatar(cx, ids!(avatar_timestamp_view.avatar)).set_avatar_and_get_username(
cx,
timeline_kind,
event_timeline_item.sender(),
Some(event_timeline_item.sender_profile()),
event_timeline_item.event_id(),
false,
);
if sender.len() > MAX_USERNAME_LENGTH {
meta_view
.label(cx, ids!(user_profile_view.content.username))
.set_text(cx, &format!("{}...", &sender[..MAX_USERNAME_LENGTH - 3]));
} else {
meta_view
.label(cx, ids!(user_profile_view.content.username))
.set_text(cx, &sender);
};
meta_view
.label(cx, ids!(username_label_view.username))
.set_text(cx, &sender);
}
}
}
Expand Down Expand Up @@ -1217,38 +1203,6 @@ pub struct ImageViewerMetaData {
pub image_file_size: u64,
}

/// Maximum image name length to be displayed
const MAX_IMAGE_NAME_LENGTH: usize = 50;
/// Maximum username length to be displayed
const MAX_USERNAME_LENGTH: usize = 50;

/// Truncate image name while preserving file extension
fn truncate_image_name(image_name: &str) -> String {
let max_length = MAX_IMAGE_NAME_LENGTH;

if image_name.len() <= max_length {
return image_name.to_string();
}

// Find the last dot to separate name and extension
if let Some(dot_pos) = image_name.rfind('.') {
let name_part = &image_name[..dot_pos];
let extension = &image_name[dot_pos..];

// Reserve space for "..." and the extension
let available_length = max_length.saturating_sub(3 + extension.len());

if available_length > 0 && name_part.len() > available_length {
format!("{}...{}", &name_part[..available_length], extension)
} else {
image_name.to_string()
}
} else {
// No extension found, just truncate the name
format!("{}...", &image_name[..max_length.saturating_sub(3)])
}
}

/// Convert bytes to human-readable file size format
fn format_file_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
Expand Down
2 changes: 0 additions & 2 deletions src/shared/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ script_mod! {


mod.widgets.Timestamp = #(Timestamp::register_widget(vm)) {


width: Fit, height: Fit
flow: Right,

Expand Down
Loading