Skip to content
Open
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
Binary file added resources/img/default_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 82 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use crate::{
}, join_leave_room_modal::{
JoinLeaveRoomModalAction,
JoinLeaveRoomModalWidgetRefExt,
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, shared::callout_tooltip::{
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, shared::{callout_tooltip::{
CalloutTooltipOptions,
CalloutTooltipWidgetRefExt,
TooltipAction,
}, sliding_sync::current_user_id, utils::{
}, image_viewer_modal::ImageViewerModalWidgetRefExt}, sliding_sync::current_user_id, utils::{
room_name_or_id,
OwnedRoomIdRon,
}, verification::VerificationAction, verification_modal::{
Expand All @@ -44,7 +44,84 @@ live_design! {
use crate::shared::popup_list::*;
use crate::home::new_message_context_menu::*;
use crate::shared::callout_tooltip::CalloutTooltip;
use crate::shared::image_viewer_modal::ImageViewerModal;

APP_TAB_COLOR = #344054
APP_TAB_COLOR_HOVER = #636e82
APP_TAB_COLOR_ACTIVE = #091

AppTab = <RadioButton> {
width: Fit,
height: Fill,
flow: Down,
align: {x: 0.5, y: 0.5},

icon_walk: {width: 20, height: 20, margin: 0.0}
label_walk: {margin: 0.0}

draw_bg: {
radio_type: Tab,

// Draws a horizontal line under the tab when selected or hovered.
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size);
sdf.box(
20.0,
self.rect_size.y - 2.5,
self.rect_size.x - 40,
self.rect_size.y - 4,
0.5
);
sdf.fill(
mix(
mix(
#0000,
(APP_TAB_COLOR_HOVER),
self.hover
),
(APP_TAB_COLOR_ACTIVE),
self.active
)
);
return sdf.result;
}
}

draw_text: {
color: (APP_TAB_COLOR)
color_hover: (APP_TAB_COLOR_HOVER)
color_active: (APP_TAB_COLOR_ACTIVE)

fn get_color(self) -> vec4 {
return mix(
mix(
self.color,
self.color_hover,
self.hover
),
self.color_active,
self.active
)
}
}

draw_icon: {
instance color: (APP_TAB_COLOR)
instance color_hover: (APP_TAB_COLOR_HOVER)
instance color_active: (APP_TAB_COLOR_ACTIVE)
fn get_color(self) -> vec4 {
return mix(
mix(
self.color,
self.color_hover,
self.hover
),
self.color_active,
self.selected
)
}
}
}

App = {{App}} {
ui: <Root>{
Expand Down Expand Up @@ -122,6 +199,8 @@ live_design! {
// Tooltips must be shown in front of all other UI elements,
// since they can be shown as a hover atop any other widget.
app_tooltip = <CalloutTooltip> {}

image_viewer_modal = <ImageViewerModal> {}
}
} // end of body
}
Expand Down Expand Up @@ -184,6 +263,7 @@ impl LiveHook for App {
// Here we set the global singleton for the PopupList widget,
// which is used to access PopupList Widget from anywhere in the app.
crate::shared::popup_list::set_global_popup_list(cx, &self.ui);
crate::shared::image_viewer_modal::set_global_image_viewer_modal(cx, self.ui.image_viewer_modal(id!(image_viewer_modal)));
}
}

Expand Down
68 changes: 60 additions & 8 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use bytesize::ByteSize;
use imbl::Vector;
use makepad_widgets::{image_cache::ImageBuffer, *};
use matrix_sdk::{
room::{reply::{EnforceThread, Reply}, RoomMember},
ruma::{
media::MediaFormat, room::{reply::{EnforceThread, Reply}, RoomMember}, ruma::{
events::{
receipt::Receipt,
room::{
Expand All @@ -20,8 +19,7 @@ use matrix_sdk::{
sticker::{StickerEventContent, StickerMediaSource},
},
matrix_uri::MatrixId, uint, EventId, MatrixToUri, MatrixUri, OwnedEventId, OwnedMxcUri, OwnedRoomId, UserId
},
OwnedServerName,
}, OwnedServerName
};
use matrix_sdk_ui::timeline::{
self, EmbeddedEvent, EncryptedMessage, EventTimelineItem, InReplyToDetails, MemberProfileChange, MsgLikeContent, MsgLikeKind, PollState, RoomMembershipChange, TimelineDetails, TimelineEventItemId, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem
Expand All @@ -32,7 +30,7 @@ use crate::{
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
user_profile_cache,
}, shared::{
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{enqueue_popup_notification, PopupItem, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt, styles::COLOR_FG_DANGER_RED, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt, typing_animation::TypingAnimationWidgetExt
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer_modal::{get_global_image_viewer_modal, handle_media_cache_entry, initialize_image_modal_with_uri, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{enqueue_popup_notification, PopupItem, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt, styles::COLOR_FG_DANGER_RED, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt, typing_animation::TypingAnimationWidgetExt
}, sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender, UserPowerLevels}, utils::{self, room_name_or_id, unix_time_millis_to_datetime, ImageFormat, MEDIA_THUMBNAIL_FORMAT}
};
use crate::home::event_reaction_list::ReactionListWidgetRefExt;
Expand Down Expand Up @@ -812,6 +810,8 @@ pub struct RoomScreen {
#[rust] is_loaded: bool,
/// Whether or not all rooms have been loaded (received from the homeserver).
#[rust] all_rooms_loaded: bool,
/// Timer for displaying `timeout` in the image viewer modal.
#[rust] image_viewer_timeout_timer: Timer
}
impl Drop for RoomScreen {
fn drop(&mut self) {
Expand Down Expand Up @@ -979,6 +979,15 @@ impl Widget for RoomScreen {
);
}
}

if let TextOrImageAction::Clicked(room_id, mxc_uri) = action.as_widget_action().cast() {
if let Some(tl) = &mut self.tl_state {
// Only handle the action if it matches the current room
if tl.room_id == room_id {
populate_image_modal(cx, &mut self.image_viewer_timeout_timer, Some(mxc_uri), tl);
}
}
}
}

/*
Expand Down Expand Up @@ -1676,6 +1685,10 @@ impl RoomScreen {
log!("Timeline::handle_event(): media fetched for room {}", tl.room_id);
// Here, to be most efficient, we could redraw only the media items in the timeline,
// but for now we just fall through and let the final `redraw()` call re-draw the whole timeline view.
if let LoadState::Loaded = populate_image_modal(cx, &mut self.image_viewer_timeout_timer, None, tl) {
let image_viewer_modal = get_global_image_viewer_modal(cx);
image_viewer_modal.set_image_loaded();
}
}
TimelineUpdate::MessageEdited { timeline_event_id, result } => {
self.view.editing_pane(id!(editing_pane))
Expand Down Expand Up @@ -3541,7 +3554,7 @@ fn populate_image_message_content(
let mut fetch_and_show_image_uri = |cx: &mut Cx2d, mxc_uri: OwnedMxcUri, image_info: Box<ImageInfo>| {
match media_cache.try_get_media_or_fetch(mxc_uri.clone(), MEDIA_THUMBNAIL_FORMAT.into()) {
(MediaCacheEntry::Loaded(data), _media_format) => {
let show_image_result = text_or_image_ref.show_image(cx, |cx, img| {
let show_image_result = text_or_image_ref.show_image(cx, mxc_uri.clone(), |cx, img| {
utils::load_png_or_jpg(&img, cx, &data)
.map(|()| img.size_in_pixels(cx).unwrap_or_default())
});
Expand All @@ -3557,7 +3570,7 @@ fn populate_image_message_content(
(MediaCacheEntry::Requested, _media_format) => {
// If the image is being fetched, we try to show its blurhash.
if let (Some(ref blurhash), Some(width), Some(height)) = (image_info.blurhash.clone(), image_info.width, image_info.height) {
let show_image_result = text_or_image_ref.show_image(cx, |cx, img| {
let show_image_result = text_or_image_ref.show_image(cx, mxc_uri.clone(), |cx, img| {
let (Ok(width), Ok(height)) = (width.try_into(), height.try_into()) else {
return Err(image_cache::ImageError::EmptyData)
};
Expand Down Expand Up @@ -4398,4 +4411,43 @@ pub fn clear_timeline_states(_cx: &mut Cx) {
TIMELINE_STATES.with_borrow_mut(|states| {
states.clear();
});
}
}

/// Populates the image modal with media content and handles various loading states
///
/// This function manages the complete lifecycle of loading and displaying an image in the modal:
/// 1. Optionally initializes the modal with a new MXC URI
/// 2. Attempts to fetch or retrieve cached media
/// 3. Updates the UI based on the current media state (loading, loaded, failed)
fn populate_image_modal(
cx: &mut Cx,
timer: &mut Timer,
mxc_uri: Option<OwnedMxcUri>,
tl: &mut TimelineUiState
) -> LoadState {
// Initialize modal with new URI if provided
if let Some(mxc_uri) = mxc_uri {
initialize_image_modal_with_uri(cx, timer, mxc_uri, tl.room_id.clone());
}

let image_viewer_modal = get_global_image_viewer_modal(cx);

// Only proceed if media fetching is active
if !image_viewer_modal.get_media_or_fetch(tl.room_id.clone()) {
return LoadState::Loading;
}

// Get required modal components
let Some(view_set) = image_viewer_modal.get_view_set() else {
return LoadState::Error;
};
let Some(mxc_uri) = image_viewer_modal.get_mxc_uri() else {
return LoadState::Error;
};

// Try to get media from cache or trigger fetch
let media_entry = tl.media_cache.try_get_media_or_fetch(mxc_uri, MediaFormat::File);

// Handle the different media states
handle_media_cache_entry(cx, timer, media_entry, view_set)
}
Loading