Skip to content

Commit ab943f6

Browse files
authored
Merge pull request #895 from kevinaboos/pick_photos_videos
Move room upload media/location buttons into a popup menu
2 parents fc1c0f8 + 3b48517 commit ab943f6

6 files changed

Lines changed: 385 additions & 70 deletions

File tree

resources/icons/add.svg

Lines changed: 4 additions & 1 deletion
Loading

src/home/room_screen.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::{
3232
},
3333
room::{BasicRoomDetails, room_input_bar::{RoomInputBarState, RoomInputBarWidgetRefExt}, typing_notice::TypingNoticeWidgetExt},
3434
shared::{
35-
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
35+
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
3636
},
3737
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}
3838
};
@@ -693,6 +693,10 @@ script_mod! {
693693
// to finish loading, e.g., when loading an older replied-to message.
694694
loading_pane := LoadingPane { }
695695

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

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

770775
// Handle actions here before processing timeline updates.
771776
// Normally (in most other widgets), the order of event handling doesn't matter much.
772777
// However, since actions may refer to a specific timeline item's index,
773778
// we want to handle those before processing any updates that might change
774779
// the set of timeline indices (which would invalidate the index values in any actions).
775780
if let Event::Actions(actions) = event {
781+
if let Some(action) = room_input_popup_menu.selected(actions) {
782+
self.handle_room_input_popup_menu_action(cx, action);
783+
}
784+
776785
for (index, wr) in portal_list.items_with_actions(actions) {
777786
// Handle a hover-in action on the reaction list: show a reaction summary.
778787
let reaction_list = wr.reaction_list(cx, ids!(reaction_list));
@@ -1016,7 +1025,27 @@ impl Widget for RoomScreen {
10161025
//
10171026
let is_interactive_hit = utils::is_interactive_hit_event(event);
10181027
let is_pane_shown: bool;
1019-
if loading_pane.is_currently_shown(cx) {
1028+
let mut close_room_input_popup_menu_after_forwarding = false;
1029+
if room_input_popup_menu.is_open() {
1030+
if event.back_pressed() || matches!(event, Event::KeyUp(KeyEvent { key_code: KeyCode::Escape, .. })) {
1031+
room_input_popup_menu.close(cx);
1032+
is_pane_shown = true;
1033+
}
1034+
else if is_interactive_hit {
1035+
if room_input_popup_menu.is_event_within_popup_menu(cx, event) {
1036+
is_pane_shown = true;
1037+
room_input_popup_menu.handle_event(cx, event, scope);
1038+
} else {
1039+
// Let outside clicks, hovers, and mouse moves fall through to the underlying UI.
1040+
close_room_input_popup_menu_after_forwarding =
1041+
room_input_popup_menu.should_dismiss_for_outside_event(cx, event);
1042+
is_pane_shown = false;
1043+
}
1044+
} else {
1045+
is_pane_shown = false;
1046+
}
1047+
}
1048+
else if loading_pane.is_currently_shown(cx) {
10201049
is_pane_shown = true;
10211050
if is_interactive_hit {
10221051
loading_pane.handle_event(cx, event, scope);
@@ -1102,6 +1131,15 @@ impl Widget for RoomScreen {
11021131
return false;
11031132
}
11041133

1134+
// Handle actions related to the room input popup menu.
1135+
match action.as_widget_action().cast() {
1136+
RoomInputPopupMenuAction::None => {}
1137+
room_popup_menu_action => {
1138+
self.handle_room_input_popup_menu_action(cx, room_popup_menu_action);
1139+
return false;
1140+
}
1141+
}
1142+
11051143
// Handle the action that requests to show the user profile sliding pane.
11061144
if let ShowUserProfileAction::ShowUserProfile(profile_and_room_id) = action.as_widget_action().cast() {
11071145
self.show_user_profile(
@@ -1160,10 +1198,17 @@ impl Widget for RoomScreen {
11601198
});
11611199
// Add back any unhandled actions to the global action list.
11621200
cx.extend_actions(actions_generated_within_this_room_screen);
1201+
1202+
if close_room_input_popup_menu_after_forwarding {
1203+
let room_input_popup_menu =
1204+
self.room_input_popup_menu(cx, ids!(room_input_popup_menu));
1205+
if room_input_popup_menu.is_open() {
1206+
room_input_popup_menu.close(cx);
1207+
}
1208+
}
11631209
}
11641210
}
11651211

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

1423+
fn show_room_input_popup_menu(&mut self, cx: &mut Cx, button_rect: Rect) {
1424+
let popup_menu = self.room_input_popup_menu(cx, ids!(room_input_popup_menu));
1425+
let room_screen_rect = self.view(cx, ids!(room_screen_wrapper)).area().rect(cx);
1426+
let margin = Inset {
1427+
left: button_rect.pos.x - room_screen_rect.pos.x,
1428+
top: 0.0,
1429+
right: 0.0,
1430+
bottom: room_screen_rect.pos.y + room_screen_rect.size.y
1431+
- button_rect.pos.y
1432+
+ 9.0
1433+
};
1434+
1435+
let mut main_content = popup_menu.view(cx, ids!(main_content));
1436+
script_apply_eval!(cx, main_content, {
1437+
margin: #(margin)
1438+
});
1439+
popup_menu.show(cx);
1440+
self.view.redraw(cx);
1441+
}
1442+
1443+
fn handle_room_input_popup_menu_action(
1444+
&mut self,
1445+
cx: &mut Cx,
1446+
action: RoomInputPopupMenuAction,
1447+
) {
1448+
let room_input_bar = self.view.room_input_bar(cx, ids!(room_input_bar));
1449+
match action {
1450+
RoomInputPopupMenuAction::Show { button_rect } => {
1451+
self.show_room_input_popup_menu(cx, button_rect);
1452+
}
1453+
RoomInputPopupMenuAction::UploadPhotoOrVideo => {
1454+
let Some(timeline_kind) = self.timeline_kind.clone() else { return };
1455+
room_input_bar.open_photo_video_picker(cx, timeline_kind);
1456+
}
1457+
RoomInputPopupMenuAction::UploadFile => {
1458+
let Some(timeline_kind) = self.timeline_kind.clone() else { return };
1459+
room_input_bar.open_file_picker(cx, timeline_kind);
1460+
}
1461+
RoomInputPopupMenuAction::SendCurrentLocation => {
1462+
room_input_bar.show_current_location_preview(cx);
1463+
}
1464+
RoomInputPopupMenuAction::None => {}
1465+
}
1466+
}
1467+
13781468
/// Processes all pending background updates to the currently-shown timeline.
13791469
///
13801470
/// Redraws this RoomScreen view if any updates were applied.

src/room/room_input_bar.rs

Lines changed: 51 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! The widgets included in the RoomInputBar are:
66
//! * a preview of the message the user is replying to.
77
//! * the location preview (which allows you to send your current location to the room),
8-
//! and a button to show the location preview.
8+
//! plus a menu item to show the location preview.
9+
//! * a button that opens the RoomScreen-level popup menu for uploads/location.
910
//! * If TSP is enabled, a checkbox to enable TSP signing for the outgoing message.
1011
//! * A MentionableTextInput, which allows the user to type a message
1112
//! and mention other users via the `@` key.
@@ -21,16 +22,13 @@ use matrix_sdk::room::reply::{EnforceThread, Reply};
2122
use ruma::events::room::message::AddMentions;
2223
use matrix_sdk_ui::timeline::{EmbeddedEvent, EventTimelineItem, TimelineEventItemId};
2324
use ruma::{events::room::message::{LocationMessageEventContent, MessageType, ReplyWithinThread, RoomMessageEventContent}, OwnedEventId, OwnedRoomId};
24-
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};
25+
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};
2526

2627
script_mod! {
2728
use mod.prelude.widgets.*
2829
use mod.widgets.*
2930

3031

31-
mod.widgets.ICON_LOCATION_PIN = crate_resource("self://resources/icons/location-pin.svg")
32-
33-
3432
mod.widgets.RoomInputBar = set_type_default() do #(RoomInputBar::register_widget(vm)) {
3533
..mod.widgets.RoundedView
3634

@@ -83,29 +81,11 @@ script_mod! {
8381
align: Align{y: 1.0},
8482
padding: 6,
8583

86-
// Attachment button for uploading files.
87-
send_attachment_button := RobrixIconButton {
84+
open_popup_menu_button := RobrixIconButton {
8885
margin: 4
8986
spacing: 0,
9087
draw_icon +: {
91-
svg: (ICON_ADD_ATTACHMENT)
92-
color: (COLOR_ACTIVE_PRIMARY_DARKER)
93-
},
94-
draw_bg +: {
95-
color: (COLOR_BG_PREVIEW)
96-
color_hover: #E0E8F0
97-
color_down: #D0D8E8
98-
}
99-
icon_walk: Walk{width: 21, height: 21}
100-
text: "",
101-
}
102-
103-
// Photo/video button for uploading media from the native media picker.
104-
send_photo_video_button := RobrixIconButton {
105-
margin: 4
106-
spacing: 0,
107-
draw_icon +: {
108-
svg: (ICON_ADD_PHOTO)
88+
svg: (ICON_ADD)
10989
color: (COLOR_ACTIVE_PRIMARY_DARKER)
11090
},
11191
draw_bg +: {
@@ -117,22 +97,6 @@ script_mod! {
11797
text: "",
11898
}
11999

120-
location_button := RobrixIconButton {
121-
margin: 4
122-
spacing: 0,
123-
draw_icon +: {
124-
svg: (mod.widgets.ICON_LOCATION_PIN)
125-
color: (COLOR_ACTIVE_PRIMARY_DARKER)
126-
},
127-
draw_bg +: {
128-
color: (COLOR_BG_PREVIEW)
129-
color_hover: #E0E8F0
130-
color_down: #D0D8E8
131-
}
132-
icon_walk: Walk{width: 21, height: 21}
133-
text: "",
134-
}
135-
136100
// A checkbox that enables TSP signing for the outgoing message.
137101
// If TSP is not enabled, this will be an empty invisible view.
138102
tsp_sign_checkbox := TspSignAnycastCheckbox {
@@ -319,31 +283,13 @@ impl RoomInputBar {
319283
self.redraw(cx);
320284
}
321285

322-
// Handle the add attachment button being clicked.
323-
if self.button(cx, ids!(send_attachment_button)).clicked(actions) {
324-
log!("Add attachment button clicked; opening file picker...");
325-
self.open_file_picker(cx, room_screen_props.timeline_kind.clone());
326-
}
327-
328-
// Handle the add photo/video button being clicked.
329-
if self.button(cx, ids!(send_photo_video_button)).clicked(actions) {
330-
log!("Add photo/video button clicked; opening media picker...");
331-
self.open_photo_video_picker(cx, room_screen_props.timeline_kind.clone());
332-
}
333-
334-
// Handle the add location button being clicked.
335-
if self.button(cx, ids!(location_button)).clicked(actions) {
336-
log!("Add location button clicked; requesting current location...");
337-
if let Err(_e) = init_location_subscriber(cx) {
338-
error!("Failed to initialize location subscriber");
339-
enqueue_popup_notification(
340-
"Failed to initialize location services.",
341-
PopupKind::Error,
342-
None,
343-
);
344-
}
345-
self.view.location_preview(cx, ids!(location_preview)).show();
346-
self.redraw(cx);
286+
let open_popup_menu_button = self.button(cx, ids!(open_popup_menu_button));
287+
if open_popup_menu_button.clicked(actions) {
288+
let button_rect = open_popup_menu_button.area().rect(cx);
289+
cx.widget_action(
290+
room_screen_props.room_screen_widget_uid,
291+
RoomInputPopupMenuAction::Show { button_rect },
292+
);
347293
}
348294

349295
// Handle the send location button being clicked.
@@ -471,6 +417,19 @@ impl RoomInputBar {
471417
}
472418
}
473419

420+
fn show_current_location_preview(&mut self, cx: &mut Cx) {
421+
if let Err(_e) = init_location_subscriber(cx) {
422+
error!("Failed to initialize location subscriber");
423+
enqueue_popup_notification(
424+
"Failed to initialize location services.",
425+
PopupKind::Error,
426+
None,
427+
);
428+
}
429+
self.view.location_preview(cx, ids!(location_preview)).show();
430+
self.redraw(cx);
431+
}
432+
474433
/// Shows a preview of the given event that the user is currently replying to
475434
/// above the message input bar.
476435
///
@@ -796,6 +755,32 @@ impl RoomInputBarRef {
796755
inner.update_tombstone_footer(cx, tombstoned_room_id, successor_room_details);
797756
}
798757

758+
/// Opens the native picker to upload a photo or video into this room.
759+
pub fn open_photo_video_picker(
760+
&self,
761+
cx: &mut Cx,
762+
timeline_kind: TimelineKind,
763+
) {
764+
let Some(mut inner) = self.borrow_mut() else { return };
765+
inner.open_photo_video_picker(cx, timeline_kind);
766+
}
767+
768+
/// Opens the native picker to upload a file into this room.
769+
pub fn open_file_picker(
770+
&self,
771+
cx: &mut Cx,
772+
timeline_kind: TimelineKind,
773+
) {
774+
let Some(mut inner) = self.borrow_mut() else { return };
775+
inner.open_file_picker(cx, timeline_kind);
776+
}
777+
778+
/// Shows the preview flow for sending the current location into this room.
779+
pub fn show_current_location_preview(&self, cx: &mut Cx) {
780+
let Some(mut inner) = self.borrow_mut() else { return };
781+
inner.show_current_location_preview(cx);
782+
}
783+
799784
/// Forwards the result of an edit request to the `EditingPane` widget
800785
/// within this `RoomInputBar`.
801786
pub fn handle_edit_result(

src/shared/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod navigation_bar_button;
1515
pub mod popup_list;
1616
pub mod progress_bar;
1717
pub mod room_filter_input_bar;
18+
pub mod room_input_popup_menu;
1819
pub mod styles;
1920
pub mod text_or_image;
2021
pub mod timestamp;
@@ -37,6 +38,7 @@ pub fn script_mod(vm: &mut ScriptVm) {
3738
collapsible_header::script_mod(vm);
3839
timestamp::script_mod(vm);
3940
room_filter_input_bar::script_mod(vm);
41+
room_input_popup_menu::script_mod(vm);
4042
avatar::script_mod(vm);
4143
text_or_image::script_mod(vm);
4244
html_or_plaintext::script_mod(vm);

0 commit comments

Comments
 (0)