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
5 changes: 4 additions & 1 deletion resources/icons/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
96 changes: 93 additions & 3 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{
},
room::{BasicRoomDetails, room_input_bar::{RoomInputBarState, RoomInputBarWidgetRefExt}, typing_notice::TypingNoticeWidgetExt},
shared::{
attachment_download::{enqueue_already_downloading_notification, DownloadDisplayState, DownloadKind, DownloadableAttachment, PendingDownload, PendingDownloadState, media_source_mxc, start_attachment_download}, avatar::{AvatarState, AvatarWidgetRefExt}, confirmation_modal::ConfirmationModalContent, file_upload_modal::FileUploadAttemptId, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageStatus, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt
attachment_download::{enqueue_already_downloading_notification, DownloadDisplayState, DownloadKind, DownloadableAttachment, PendingDownload, PendingDownloadState, media_source_mxc, start_attachment_download}, avatar::{AvatarState, AvatarWidgetRefExt}, confirmation_modal::ConfirmationModalContent, file_upload_modal::FileUploadAttemptId, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, room_input_popup_menu::{RoomInputPopupMenuAction, RoomInputPopupMenuWidgetExt}, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageStatus, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt
},
sliding_sync::{BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineKind, TimelineRequestSender, UserPowerLevels, get_client, submit_async_request, take_timeline_endpoints}, utils::{self, ImageFormat, MEDIA_THUMBNAIL_FORMAT, RoomNameId, unix_time_millis_to_datetime}
};
Expand Down Expand Up @@ -693,6 +693,10 @@ script_mod! {
// to finish loading, e.g., when loading an older replied-to message.
loading_pane := LoadingPane { }

// The popup menu for uploading/sending other content to this room,
// which is controlled by actions from the RoomInputBar.
room_input_popup_menu := RoomInputPopupMenu { }


/*
* TODO: add the action bar back in as a series of floating buttons.
Expand Down Expand Up @@ -766,13 +770,18 @@ impl Widget for RoomScreen {
let portal_list = self.portal_list(cx, ids!(timeline.list));
let user_profile_sliding_pane = self.user_profile_sliding_pane(cx, ids!(user_profile_sliding_pane));
let loading_pane = self.loading_pane(cx, ids!(loading_pane));
let room_input_popup_menu = self.room_input_popup_menu(cx, ids!(room_input_popup_menu));

// Handle actions here before processing timeline updates.
// Normally (in most other widgets), the order of event handling doesn't matter much.
// However, since actions may refer to a specific timeline item's index,
// we want to handle those before processing any updates that might change
// the set of timeline indices (which would invalidate the index values in any actions).
if let Event::Actions(actions) = event {
if let Some(action) = room_input_popup_menu.selected(actions) {
self.handle_room_input_popup_menu_action(cx, action);
}

for (index, wr) in portal_list.items_with_actions(actions) {
// Handle a hover-in action on the reaction list: show a reaction summary.
let reaction_list = wr.reaction_list(cx, ids!(reaction_list));
Expand Down Expand Up @@ -1016,7 +1025,27 @@ impl Widget for RoomScreen {
//
let is_interactive_hit = utils::is_interactive_hit_event(event);
let is_pane_shown: bool;
if loading_pane.is_currently_shown(cx) {
let mut close_room_input_popup_menu_after_forwarding = false;
if room_input_popup_menu.is_open() {
if event.back_pressed() || matches!(event, Event::KeyUp(KeyEvent { key_code: KeyCode::Escape, .. })) {
room_input_popup_menu.close(cx);
is_pane_shown = true;
}
else if is_interactive_hit {
if room_input_popup_menu.is_event_within_popup_menu(cx, event) {
is_pane_shown = true;
room_input_popup_menu.handle_event(cx, event, scope);
} else {
// Let outside clicks, hovers, and mouse moves fall through to the underlying UI.
close_room_input_popup_menu_after_forwarding =
room_input_popup_menu.should_dismiss_for_outside_event(cx, event);
is_pane_shown = false;
}
} else {
is_pane_shown = false;
}
}
else if loading_pane.is_currently_shown(cx) {
is_pane_shown = true;
if is_interactive_hit {
loading_pane.handle_event(cx, event, scope);
Expand Down Expand Up @@ -1102,6 +1131,15 @@ impl Widget for RoomScreen {
return false;
}

// Handle actions related to the room input popup menu.
match action.as_widget_action().cast() {
RoomInputPopupMenuAction::None => {}
room_popup_menu_action => {
self.handle_room_input_popup_menu_action(cx, room_popup_menu_action);
return false;
}
}

// Handle the action that requests to show the user profile sliding pane.
if let ShowUserProfileAction::ShowUserProfile(profile_and_room_id) = action.as_widget_action().cast() {
self.show_user_profile(
Expand Down Expand Up @@ -1160,10 +1198,17 @@ impl Widget for RoomScreen {
});
// Add back any unhandled actions to the global action list.
cx.extend_actions(actions_generated_within_this_room_screen);

if close_room_input_popup_menu_after_forwarding {
let room_input_popup_menu =
self.room_input_popup_menu(cx, ids!(room_input_popup_menu));
if room_input_popup_menu.is_open() {
room_input_popup_menu.close(cx);
}
}
}
}


fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
// If the room isn't loaded yet, we show the restore status label only.
if !self.is_loaded {
Expand Down Expand Up @@ -1375,6 +1420,51 @@ impl RoomScreen {
self.room_name_id.as_ref().map(|r| r.room_id())
}

fn show_room_input_popup_menu(&mut self, cx: &mut Cx, button_rect: Rect) {
let popup_menu = self.room_input_popup_menu(cx, ids!(room_input_popup_menu));
let room_screen_rect = self.view(cx, ids!(room_screen_wrapper)).area().rect(cx);
let margin = Inset {
left: button_rect.pos.x - room_screen_rect.pos.x,
top: 0.0,
right: 0.0,
bottom: room_screen_rect.pos.y + room_screen_rect.size.y
- button_rect.pos.y
+ 9.0
};

let mut main_content = popup_menu.view(cx, ids!(main_content));
script_apply_eval!(cx, main_content, {
margin: #(margin)
});
popup_menu.show(cx);
self.view.redraw(cx);
}

fn handle_room_input_popup_menu_action(
&mut self,
cx: &mut Cx,
action: RoomInputPopupMenuAction,
) {
let room_input_bar = self.view.room_input_bar(cx, ids!(room_input_bar));
match action {
RoomInputPopupMenuAction::Show { button_rect } => {
self.show_room_input_popup_menu(cx, button_rect);
}
RoomInputPopupMenuAction::UploadPhotoOrVideo => {
let Some(timeline_kind) = self.timeline_kind.clone() else { return };
room_input_bar.open_photo_video_picker(cx, timeline_kind);
}
RoomInputPopupMenuAction::UploadFile => {
let Some(timeline_kind) = self.timeline_kind.clone() else { return };
room_input_bar.open_file_picker(cx, timeline_kind);
}
RoomInputPopupMenuAction::SendCurrentLocation => {
room_input_bar.show_current_location_preview(cx);
}
RoomInputPopupMenuAction::None => {}
}
}

/// Processes all pending background updates to the currently-shown timeline.
///
/// Redraws this RoomScreen view if any updates were applied.
Expand Down
117 changes: 51 additions & 66 deletions src/room/room_input_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
//! The widgets included in the RoomInputBar are:
//! * a preview of the message the user is replying to.
//! * the location preview (which allows you to send your current location to the room),
//! and a button to show the location preview.
//! plus a menu item to show the location preview.
//! * a button that opens the RoomScreen-level popup menu for uploads/location.
//! * If TSP is enabled, a checkbox to enable TSP signing for the outgoing message.
//! * A MentionableTextInput, which allows the user to type a message
//! and mention other users via the `@` key.
Expand All @@ -21,16 +22,13 @@ use matrix_sdk::room::reply::{EnforceThread, Reply};
use ruma::events::room::message::AddMentions;
use matrix_sdk_ui::timeline::{EmbeddedEvent, EventTimelineItem, TimelineEventItemId};
use ruma::{events::room::message::{LocationMessageEventContent, MessageType, ReplyWithinThread, RoomMessageEventContent}, OwnedEventId, OwnedRoomId};
use crate::{home::{editing_pane::{EditingPaneState, EditingPaneWidgetExt, EditingPaneWidgetRefExt}, location_preview::{LocationPreviewWidgetExt, LocationPreviewWidgetRefExt}, room_screen::{MessageAction, RoomScreenProps, populate_preview_of_timeline_item}, tombstone_footer::{SuccessorRoomDetails, TombstoneFooterWidgetExt}, upload_progress::UploadProgressViewWidgetRefExt}, location::init_location_subscriber, settings::app_preferences::{AppPreferencesAction, AppPreferencesGlobal}, shared::{avatar::AvatarWidgetRefExt, file_upload_modal::{AttachmentUpload, FilePreviewerAction, FileUploadAttemptId, load_selected_file}, html_or_plaintext::HtmlOrPlaintextWidgetRefExt, mentionable_text_input::MentionableTextInputWidgetExt, popup_list::{PopupKind, enqueue_popup_notification}, styles::*}, sliding_sync::{MatrixRequest, TimelineKind, UserPowerLevels, submit_async_request}, utils};
use crate::{home::{editing_pane::{EditingPaneState, EditingPaneWidgetExt, EditingPaneWidgetRefExt}, location_preview::{LocationPreviewWidgetExt, LocationPreviewWidgetRefExt}, room_screen::{MessageAction, RoomScreenProps, populate_preview_of_timeline_item}, tombstone_footer::{SuccessorRoomDetails, TombstoneFooterWidgetExt}, upload_progress::UploadProgressViewWidgetRefExt}, location::init_location_subscriber, settings::app_preferences::{AppPreferencesAction, AppPreferencesGlobal}, shared::{avatar::AvatarWidgetRefExt, file_upload_modal::{AttachmentUpload, FilePreviewerAction, FileUploadAttemptId, load_selected_file}, html_or_plaintext::HtmlOrPlaintextWidgetRefExt, mentionable_text_input::MentionableTextInputWidgetExt, popup_list::{PopupKind, enqueue_popup_notification}, room_input_popup_menu::RoomInputPopupMenuAction, styles::*}, sliding_sync::{MatrixRequest, TimelineKind, UserPowerLevels, submit_async_request}, utils};

script_mod! {
use mod.prelude.widgets.*
use mod.widgets.*


mod.widgets.ICON_LOCATION_PIN = crate_resource("self://resources/icons/location-pin.svg")


mod.widgets.RoomInputBar = set_type_default() do #(RoomInputBar::register_widget(vm)) {
..mod.widgets.RoundedView

Expand Down Expand Up @@ -83,29 +81,11 @@ script_mod! {
align: Align{y: 1.0},
padding: 6,

// Attachment button for uploading files.
send_attachment_button := RobrixIconButton {
open_popup_menu_button := RobrixIconButton {
margin: 4
spacing: 0,
draw_icon +: {
svg: (ICON_ADD_ATTACHMENT)
color: (COLOR_ACTIVE_PRIMARY_DARKER)
},
draw_bg +: {
color: (COLOR_BG_PREVIEW)
color_hover: #E0E8F0
color_down: #D0D8E8
}
icon_walk: Walk{width: 21, height: 21}
text: "",
}

// Photo/video button for uploading media from the native media picker.
send_photo_video_button := RobrixIconButton {
margin: 4
spacing: 0,
draw_icon +: {
svg: (ICON_ADD_PHOTO)
svg: (ICON_ADD)
color: (COLOR_ACTIVE_PRIMARY_DARKER)
},
draw_bg +: {
Expand All @@ -117,22 +97,6 @@ script_mod! {
text: "",
}

location_button := RobrixIconButton {
margin: 4
spacing: 0,
draw_icon +: {
svg: (mod.widgets.ICON_LOCATION_PIN)
color: (COLOR_ACTIVE_PRIMARY_DARKER)
},
draw_bg +: {
color: (COLOR_BG_PREVIEW)
color_hover: #E0E8F0
color_down: #D0D8E8
}
icon_walk: Walk{width: 21, height: 21}
text: "",
}

// A checkbox that enables TSP signing for the outgoing message.
// If TSP is not enabled, this will be an empty invisible view.
tsp_sign_checkbox := TspSignAnycastCheckbox {
Expand Down Expand Up @@ -319,31 +283,13 @@ impl RoomInputBar {
self.redraw(cx);
}

// Handle the add attachment button being clicked.
if self.button(cx, ids!(send_attachment_button)).clicked(actions) {
log!("Add attachment button clicked; opening file picker...");
self.open_file_picker(cx, room_screen_props.timeline_kind.clone());
}

// Handle the add photo/video button being clicked.
if self.button(cx, ids!(send_photo_video_button)).clicked(actions) {
log!("Add photo/video button clicked; opening media picker...");
self.open_photo_video_picker(cx, room_screen_props.timeline_kind.clone());
}

// Handle the add location button being clicked.
if self.button(cx, ids!(location_button)).clicked(actions) {
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(
"Failed to initialize location services.",
PopupKind::Error,
None,
);
}
self.view.location_preview(cx, ids!(location_preview)).show();
self.redraw(cx);
let open_popup_menu_button = self.button(cx, ids!(open_popup_menu_button));
if open_popup_menu_button.clicked(actions) {
let button_rect = open_popup_menu_button.area().rect(cx);
cx.widget_action(
room_screen_props.room_screen_widget_uid,
RoomInputPopupMenuAction::Show { button_rect },
);
}

// Handle the send location button being clicked.
Expand Down Expand Up @@ -471,6 +417,19 @@ impl RoomInputBar {
}
}

fn show_current_location_preview(&mut self, cx: &mut Cx) {
if let Err(_e) = init_location_subscriber(cx) {
error!("Failed to initialize location subscriber");
enqueue_popup_notification(
"Failed to initialize location services.",
PopupKind::Error,
None,
);
}
self.view.location_preview(cx, ids!(location_preview)).show();
self.redraw(cx);
}

/// Shows a preview of the given event that the user is currently replying to
/// above the message input bar.
///
Expand Down Expand Up @@ -796,6 +755,32 @@ impl RoomInputBarRef {
inner.update_tombstone_footer(cx, tombstoned_room_id, successor_room_details);
}

/// Opens the native picker to upload a photo or video into this room.
pub fn open_photo_video_picker(
&self,
cx: &mut Cx,
timeline_kind: TimelineKind,
) {
let Some(mut inner) = self.borrow_mut() else { return };
inner.open_photo_video_picker(cx, timeline_kind);
}

/// Opens the native picker to upload a file into this room.
pub fn open_file_picker(
&self,
cx: &mut Cx,
timeline_kind: TimelineKind,
) {
let Some(mut inner) = self.borrow_mut() else { return };
inner.open_file_picker(cx, timeline_kind);
}

/// Shows the preview flow for sending the current location into this room.
pub fn show_current_location_preview(&self, cx: &mut Cx) {
let Some(mut inner) = self.borrow_mut() else { return };
inner.show_current_location_preview(cx);
}

/// Forwards the result of an edit request to the `EditingPane` widget
/// within this `RoomInputBar`.
pub fn handle_edit_result(
Expand Down
2 changes: 2 additions & 0 deletions src/shared/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod navigation_bar_button;
pub mod popup_list;
pub mod progress_bar;
pub mod room_filter_input_bar;
pub mod room_input_popup_menu;
pub mod styles;
pub mod text_or_image;
pub mod timestamp;
Expand All @@ -37,6 +38,7 @@ pub fn script_mod(vm: &mut ScriptVm) {
collapsible_header::script_mod(vm);
timestamp::script_mod(vm);
room_filter_input_bar::script_mod(vm);
room_input_popup_menu::script_mod(vm);
avatar::script_mod(vm);
text_or_image::script_mod(vm);
html_or_plaintext::script_mod(vm);
Expand Down
Loading
Loading