diff --git a/src/app.rs b/src/app.rs index b29d4e16..378edbcf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use makepad_widgets::*; use matrix_sdk::ruma::OwnedRoomId; use crate::{ - home::{main_desktop_ui::RoomsPanelAction, room_screen::MessageAction, rooms_list::RoomListAction}, login::login_screen::LoginAction, verification::VerificationAction, verification_modal::{VerificationModalAction, VerificationModalWidgetRefExt} + home::{main_desktop_ui::RoomsPanelAction, room_screen::MessageAction, rooms_list::RoomListAction}, login::login_screen::LoginAction, shared::popup_list::PopupNotificationAction, verification::VerificationAction, verification_modal::{VerificationModalAction, VerificationModalWidgetRefExt} }; live_design! { @@ -15,7 +15,8 @@ live_design! { use crate::profile::my_profile_screen::MyProfileScreen; use crate::verification_modal::VerificationModal; use crate::login::login_screen::LoginScreen; - + use crate::shared::popup_list::PopupList; + ICON_CHAT = dep("crate://self/resources/icons/chat.svg") ICON_CONTACTS = dep("crate://self/resources/icons/contacts.svg") ICON_DISCOVER = dep("crate://self/resources/icons/discover.svg") @@ -118,12 +119,18 @@ live_design! { visible: true login_screen = {} } - + popup = { + margin: {top: 45, right: 13}, + content: { + {} + } + } verification_modal = { content: { verification_modal_inner = {} } } + // message_source_modal = { // content: { // message_source_modal_inner = {} @@ -188,7 +195,15 @@ impl MatchEvent for App { self.update_login_visibility(cx); self.ui.redraw(cx); } - + match action.downcast_ref() { + Some(PopupNotificationAction::Open) => { + self.ui.popup_notification(id!(popup)).open(cx); + } + Some(PopupNotificationAction::Close) => { + self.ui.popup_notification(id!(popup)).close(cx); + } + _ => {} + } match action.as_widget_action().cast() { // A room has been selected, update the app state and navigate to the main content view. RoomListAction::Selected { diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 7f87a68e..94136bb8 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -21,13 +21,12 @@ use matrix_sdk_ui::timeline::{ use robius_location::Coordinates; use crate::{ - avatar_cache::{self, AvatarCacheEntry}, event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::loading_modal::LoadingModalWidgetExt, location::{get_latest_location, init_location_subscriber, request_location_update, LocationAction, LocationRequest, LocationUpdate}, media_cache::{MediaCache, MediaCacheEntry}, profile::{ + avatar_cache::{self, AvatarCacheEntry}, event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::{loading_modal::LoadingModalWidgetExt, message_context_menu::MessageActionBarWidgetRefExt}, location::{get_latest_location, init_location_subscriber, request_location_update, LocationAction, LocationRequest, LocationUpdate}, media_cache::{MediaCache, MediaCacheEntry}, profile::{ user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt}, user_profile_cache, }, shared::{ - avatar::{AvatarRef, AvatarWidgetRefExt}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt + avatar::{AvatarRef, 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}, - home::message_context_menu::MessageActionBarWidgetRefExt, }; use rangemap::RangeSet; @@ -1187,6 +1186,7 @@ impl Widget for RoomScreen { log!("Add location button clicked; requesting current location..."); if let Err(_e) = init_location_subscriber(cx) { error!("Failed to initialize location subscriber"); + enqueue_popup_notification(String::from("Failed to initialize location services.")); } self.show_location_preview(cx); } @@ -1832,6 +1832,7 @@ impl RoomScreen { log!("Opening URL \"{}\"", url); if let Err(e) = robius_open::Uri::new(&url).open() { error!("Failed to open URL {:?}. Error: {:?}", url, e); + enqueue_popup_notification("Could not open URL: {url}".to_string()); } } true @@ -3063,20 +3064,22 @@ fn populate_image_message_content( ) .unwrap_or_default(); - // If we have a known mimetype and it's not an image (e.g., an animated) - // then show a message about it being unsupported. + // If we have a known mimetype and it's not a static image, + // then show a message about it being unsupported (e.g., for animated gifs). if let Some(mime) = mimetype.as_ref() { if ImageFormat::from_mimetype(mime).is_none() { text_or_image_ref.show_text(format!( "{body}\n\nImages/Stickers of type {mime:?} are not yet supported.", )); - return true; + return true; // consider this as fully drawn } } let mut fully_drawn = false; - let mut fetch_and_show_image = |mxc_uri: OwnedMxcUri| + // 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 = |mxc_uri: OwnedMxcUri| match media_cache.try_get_media_or_fetch(mxc_uri.clone(), None) { MediaCacheEntry::Loaded(data) => { let show_image_result = text_or_image_ref.show_image(|img| { @@ -3089,53 +3092,54 @@ fn populate_image_message_content( text_or_image_ref.show_text(&err_str); } - // We're done drawing thumbnail of the image message content, so mark it as fully drawn. - fully_drawn = true + // We're done drawing the image, so mark it as fully drawn. + fully_drawn = true; } MediaCacheEntry::Requested => { text_or_image_ref.show_text(format!("{body}\n\nFetching image from {:?}", mxc_uri)); // Do not consider this thumbnail as being fully drawn, as we're still fetching it. - fully_drawn = true + fully_drawn = false; } MediaCacheEntry::Failed => { text_or_image_ref .show_text(format!("{body}\n\nFailed to fetch image from {:?}", mxc_uri)); // For now, we consider this as being "complete". In the future, we could support // retrying to fetch thumbnail of the image on a user click/tap. - fully_drawn = true + fully_drawn = true; } }; - let mut match_media_source = |media_source: MediaSource| { + let mut fetch_and_show_media_source = |media_source: MediaSource| { match media_source { MediaSource::Encrypted(encrypted) => { - // We consider this as "fully drawn" since we don't yet support encryption. - text_or_image_ref.show_text(format!( - "{body}\n\n[TODO] fetch encrypted image at {:?}", - encrypted.url - )); + // We consider this as "fully drawn" since we don't yet support encryption. + text_or_image_ref.show_text(format!( + "{body}\n\n[TODO] fetch encrypted image at {:?}", + encrypted.url + )); }, MediaSource::Plain(mxc_uri) => { - fetch_and_show_image(mxc_uri) + fetch_and_show_image_uri(mxc_uri) } } }; match image_info_source { Some((None, media_source)) => { - // We fetch the origin of the media if its thumbnail doesnot exist. - match_media_source(media_source); + // We fetch the original (full-size) media if it does not have a thumbnail. + fetch_and_show_media_source(media_source); }, - Some((Some(image_info), media_source)) => { - if let Some(media_source) = image_info.thumbnail_source { - match_media_source(media_source); + if let Some(thumbnail_source) = image_info.thumbnail_source { + fetch_and_show_media_source(thumbnail_source); } else { - match_media_source(media_source); + fetch_and_show_media_source(media_source); } }, - - None => { } + None => { + text_or_image_ref.show_text("{body}\n\nImage message had no source URL."); + fully_drawn = true; + } } fully_drawn diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 2f7c296b..e517cb66 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -10,6 +10,7 @@ pub mod search_bar; pub mod styles; pub mod text_or_image; pub mod typing_animation; +pub mod popup_list; pub mod verification_badge; pub fn live_design(cx: &mut Cx) { @@ -23,6 +24,7 @@ pub fn live_design(cx: &mut Cx) { html_or_plaintext::live_design(cx); typing_animation::live_design(cx); jump_to_bottom_button::live_design(cx); + popup_list::live_design(cx); verification_badge::live_design(cx); color_tooltip::live_design(cx); } diff --git a/src/shared/popup_list.rs b/src/shared/popup_list.rs new file mode 100644 index 00000000..f2a914b9 --- /dev/null +++ b/src/shared/popup_list.rs @@ -0,0 +1,202 @@ +use crossbeam_queue::SegQueue; +use makepad_widgets::*; + +static POPUP_NOTIFICATION: SegQueue = SegQueue::new(); + +/// Displays a new popup notification with the given message. +/// +/// Popup notifications will be shown in the order they were enqueued, +/// and are currently only removed when manually closed by the user. +pub fn enqueue_popup_notification(message: String) { + POPUP_NOTIFICATION.push(message); + Cx::post_action(PopupNotificationAction::Open); +} + +live_design! { + use link::theme::*; + use link::shaders::*; + use link::widgets::*; + + use crate::shared::styles::*; + use crate::shared::icon_button::RobrixIconButton; + ICO_CLOSE = dep("crate://self/resources/icons/close.svg") + + PopupDialog = { + width: 275 + height: Fit + margin: {top: 20, right: 20} + padding: 0 + + show_bg: true + draw_bg: { + color: #fff + instance border_radius: 4.0 + fn pixel(self) -> vec4 { + let border_color = #d4; + let border_width = 1; + let sdf = Sdf2d::viewport(self.pos * self.rect_size); + let body = #fff + + sdf.box( + 1., + 1., + self.rect_size.x - 2.0, + self.rect_size.y - 2.0, + self.border_radius + ) + sdf.fill_keep(body) + + sdf.stroke( + border_color, + border_width + ) + return sdf.result + } + } + } + + pub PopupList = {{PopupList}} { + width: 275, + height: Fit + flow: Down + spacing: 0, + popup_content: { + flow: Right + padding: {top: 5, right: 5, bottom: 5, left: 10} + align: {y: 0.0} + + { + width: Fill, + height: Fit, + align: {x: 0.0, y: 0.5} + padding: {left: 5, top: 10, bottom: 10, right: 0} + popup_label =