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
581 changes: 568 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ hashbrown = { version = "0.16", features = ["raw-entry"] }
htmlize = "1.0.5"
indexmap = "2.6.0"
imghdr = "0.7.0"
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
mime = "0.3"
mime_guess = "2.0"
linkify = "0.10.0"
matrix-sdk-base = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main" }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main", default-features = false, features = [
Expand Down Expand Up @@ -100,6 +103,10 @@ reqwest = { version = "0.12", default-features = false, features = [
"macos-system-configuration",
] }

# Desktop-only file dialog (doesn't work on iOS/Android)
[target.'cfg(not(any(target_os = "ios", target_os = "android")))'.dependencies]
rfd = "0.15"


[features]
default = []
Expand Down
3 changes: 3 additions & 0 deletions resources/icons/add_attachment.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions resources/icons/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 63 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

use std::{cell::RefCell, collections::HashMap};
use makepad_widgets::*;
use makepad_widgets::SignalToUI;
use matrix_sdk::{RoomState, ruma::{OwnedEventId, OwnedRoomId, RoomId}};
use serde::{Deserialize, Serialize};
use crate::{
avatar_cache::clear_avatar_cache, home::{
event_source_modal::{EventSourceModalAction, EventSourceModalWidgetRefExt}, invite_modal::{InviteModalAction, InviteModalWidgetRefExt}, main_desktop_ui::MainDesktopUiAction, navigation_tab_bar::{NavigationBarAction, SelectedTab}, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_context_menu::RoomContextMenuWidgetRefExt, room_screen::{InviteAction, MessageAction, clear_timeline_states}, rooms_list::{RoomsListAction, RoomsListRef, RoomsListUpdate, clear_all_invited_rooms, enqueue_rooms_list_update}
event_source_modal::{EventSourceModalAction, EventSourceModalWidgetRefExt}, invite_modal::{InviteModalAction, InviteModalWidgetRefExt}, main_desktop_ui::MainDesktopUiAction, navigation_tab_bar::{NavigationBarAction, SelectedTab}, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_context_menu::RoomContextMenuWidgetRefExt, room_screen::{InviteAction, MessageAction, TimelineUpdate, clear_timeline_states}, rooms_list::{RoomsListAction, RoomsListRef, RoomsListUpdate, clear_all_invited_rooms, enqueue_rooms_list_update}
}, join_leave_room_modal::{
JoinLeaveModalKind, JoinLeaveRoomModalAction, JoinLeaveRoomModalWidgetRefExt
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::{confirmation_modal::{ConfirmationModalContent, ConfirmationModalWidgetRefExt}, image_viewer::{ImageViewerAction, LoadState}, popup_list::{PopupKind, enqueue_popup_notification}}, sliding_sync::{DirectMessageRoomAction, MatrixRequest, current_user_id, submit_async_request}, utils::RoomNameId, verification::VerificationAction, verification_modal::{
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::{confirmation_modal::{ConfirmationModalContent, ConfirmationModalWidgetRefExt}, file_upload_modal::{FilePreviewerAction, FileUploadModalWidgetRefExt}, image_viewer::{ImageViewerAction, LoadState}, popup_list::{PopupKind, enqueue_popup_notification}}, sliding_sync::{DirectMessageRoomAction, MatrixRequest, current_user_id, get_timeline_update_sender, submit_async_request}, utils::RoomNameId, verification::VerificationAction, verification_modal::{
VerificationModalAction,
VerificationModalWidgetRefExt,
}
Expand Down Expand Up @@ -148,6 +149,15 @@ script_mod! {
}
}

// A modal to preview and confirm file uploads.
file_upload_modal := Modal {
content +: {
width: Fit, height: Fit,
align: Align{x: 0.5, y: 0.5},
file_upload_modal_inner := FileUploadModal {}
}
}

PopupList {}

// Tooltips must be shown in front of all other UI elements,
Expand Down Expand Up @@ -308,6 +318,36 @@ impl MatchEvent for App {
continue;
}

// Handle file upload modal actions
match action.downcast_ref() {
Some(FilePreviewerAction::Show(file_data)) => {
self.ui.file_upload_modal(cx, ids!(file_upload_modal_inner))
.set_file_data(cx, file_data.clone());
self.ui.modal(cx, ids!(file_upload_modal)).open(cx);
continue;
}
Some(FilePreviewerAction::Hide) => {
self.ui.modal(cx, ids!(file_upload_modal)).close(cx);
continue;
}
Some(FilePreviewerAction::UploadConfirmed(file_data)) => {
// Send the file upload request to the currently selected room
if let Some(selected_room) = &self.app_state.selected_room {
if let Some(timeline_kind) = selected_room.timeline_kind() {
if let Some(sender) = get_timeline_update_sender(&timeline_kind) {
let _ = sender.send(TimelineUpdate::FileUploadConfirmed(file_data.clone()));
SignalToUI::set_ui_signal();
}
}
}
continue;
}
Some(FilePreviewerAction::Cancelled) => {
continue;
}
_ => {}
}

// Handle an action requesting to open the new message context menu.
if let MessageAction::OpenMessageContextMenu { details, abs_pos } = action.as_widget_action().cast() {
self.ui.callout_tooltip(cx, ids!(app_tooltip)).hide(cx);
Expand Down Expand Up @@ -630,6 +670,7 @@ impl AppMain for App {
crate::home::location_preview::script_mod(vm);
crate::home::tombstone_footer::script_mod(vm);
crate::home::editing_pane::script_mod(vm);
crate::home::upload_progress::script_mod(vm);
crate::room::script_mod(vm);
crate::join_leave_room_modal::script_mod(vm);
crate::verification_modal::script_mod(vm);
Expand Down Expand Up @@ -925,6 +966,26 @@ impl SelectedRoom {
SelectedRoom::Thread { room_name_id, .. } => format!("[Thread] {room_name_id}"),
}
}

/// Returns the `TimelineKind` for this selected room.
///
/// Returns `None` for `InvitedRoom` and `Space` variants, as they don't have timelines.
pub fn timeline_kind(&self) -> Option<crate::sliding_sync::TimelineKind> {
match self {
SelectedRoom::JoinedRoom { room_name_id } => {
Some(crate::sliding_sync::TimelineKind::MainRoom {
room_id: room_name_id.room_id().clone(),
})
}
SelectedRoom::Thread { room_name_id, thread_root_event_id } => {
Some(crate::sliding_sync::TimelineKind::Thread {
room_id: room_name_id.room_id().clone(),
thread_root_event_id: thread_root_event_id.clone(),
})
}
SelectedRoom::InvitedRoom { .. } | SelectedRoom::Space { .. } => None,
}
}
}

impl PartialEq for SelectedRoom {
Expand Down
2 changes: 2 additions & 0 deletions src/home/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod new_message_context_menu;
pub mod room_context_menu;
pub mod link_preview;
pub mod room_image_viewer;
pub mod upload_progress;

pub fn script_mod(vm: &mut ScriptVm) {
search_messages::script_mod(vm);
Expand Down Expand Up @@ -58,6 +59,7 @@ pub fn script_mod(vm: &mut ScriptVm) {
main_desktop_ui::script_mod(vm);
spaces_bar::script_mod(vm);
navigation_tab_bar::script_mod(vm);
upload_progress::script_mod(vm);
// Keep HomeScreen last, it references many widgets registered above.
home_screen::script_mod(vm);
}
44 changes: 44 additions & 0 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,34 @@ impl RoomScreen {
tl.tombstone_info = Some(successor_room_details);
}
TimelineUpdate::LinkPreviewFetched => {}
TimelineUpdate::FileUploadConfirmed(file_data) => {
let room_input_bar = self.view.room_input_bar(cx, ids!(room_input_bar));
if let Some(replied_to) = room_input_bar.handle_file_upload_confirmed(cx, &file_data.name) {
submit_async_request(MatrixRequest::SendAttachment {
timeline_kind: tl.kind.clone(),
file_data,
replied_to,
#[cfg(feature = "tsp")]
sign_with_tsp: room_input_bar.is_tsp_signing_enabled(cx),
});
}
}
TimelineUpdate::FileUploadUpdate { current, total } => {
self.view.room_input_bar(cx, ids!(room_input_bar))
.set_upload_progress(cx, current, total);
}
TimelineUpdate::FileUploadAbortHandle(handle) => {
self.view.room_input_bar(cx, ids!(room_input_bar))
.set_upload_abort_handle(handle);
}
TimelineUpdate::FileUploadError { error, file_data } => {
self.view.room_input_bar(cx, ids!(room_input_bar))
.show_upload_error(cx, &error, file_data);
}
TimelineUpdate::FileUploadComplete => {
self.view.room_input_bar(cx, ids!(room_input_bar))
.hide_upload_progress(cx);
}
}
}

Expand Down Expand Up @@ -2738,6 +2766,22 @@ pub enum TimelineUpdate {
Tombstoned(SuccessorRoomDetails),
/// A notice that link preview data for a URL has been fetched and is now available.
LinkPreviewFetched,
/// User confirmed a file upload via the file upload modal.
FileUploadConfirmed(crate::shared::file_upload_modal::FileData),
/// Progress update for an ongoing file upload.
FileUploadUpdate {
current: u64,
total: u64,
},
/// The abort handle for an in-progress file upload.
FileUploadAbortHandle(tokio::task::AbortHandle),
/// An error occurred during file upload.
FileUploadError {
error: String,
file_data: crate::shared::file_upload_modal::FileData,
},
/// File upload completed successfully.
FileUploadComplete,
}

thread_local! {
Expand Down
Loading
Loading