diff --git a/src/app.rs b/src/app.rs index b8fb1b1c..d90b5d64 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,7 +13,7 @@ use matrix_sdk::{RoomState, ruma::{OwnedEventId, OwnedRoomId, OwnedUserId, RoomI use serde::{Deserialize, Serialize}; use crate::{ avatar_cache::clear_avatar_cache, room_preview_cache::clear_room_preview_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, invalidate_timeline_state}, rooms_list::{RoomsListAction, RoomsListRef, RoomsListUpdate, clear_all_invited_rooms, enqueue_rooms_list_update} + event_source_modal::{EventSourceModalAction, EventSourceModalWidgetRefExt}, invite_modal::{InviteModalAction, InviteModalWidgetRefExt}, invite_screen::LeaveRoomResultAction, 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, invalidate_timeline_state}, 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, settings::app_preferences::{AppPreferences, UiZoom}, shared::{confirmation_modal::{ConfirmationModalContent, ConfirmationModalWidgetRefExt}, image_viewer::{ImageViewerAction, LoadState}, popup_list::{PopupKind, enqueue_popup_notification}}, sliding_sync::{DirectMessageRoomAction, MatrixRequest, TimelineKind, current_user_id, submit_async_request}, utils::RoomNameId, verification::VerificationAction, verification_modal::{ @@ -527,6 +527,13 @@ impl MatchEvent for App { _ => {} } + // When a room has been successfully left (e.g. an invite was rejected), + // close its open tab, if any, and hide it from the rooms list. + if let Some(LeaveRoomResultAction::Left { room_id }) = action.downcast_ref() { + self.close_room(cx, room_id); + continue; + } + // Handle DirectMessageRoomActions match action.downcast_ref() { Some(DirectMessageRoomAction::FoundExisting { room_name_id, .. }) => { @@ -891,6 +898,19 @@ impl App { self.ui.view(cx, ids!(home_screen_view)).set_visible(cx, !show_login); } + /// Closes the open tab for the given `room_id` (if any) and hides it from the rooms list. + /// + /// Used when a room has been successfully left (e.g. an invite was rejected), + /// so the now-defunct room doesn't linger in an open tab or the rooms list. + fn close_room(&mut self, cx: &mut Cx, room_id: &OwnedRoomId) { + let tab_id = LiveId::from_str(room_id.as_str()); + cx.widget_action( + self.ui.widget_uid(), + DockAction::TabCloseWasPressed(tab_id), + ); + enqueue_rooms_list_update(RoomsListUpdate::HideRoom { room_id: room_id.clone() }); + } + /// Navigates to the given `destination_room`, optionally closing the `room_to_close`. fn navigate_to_room( &mut self, diff --git a/src/home/invite_screen.rs b/src/home/invite_screen.rs index 02e54c6e..9d39bfc9 100644 --- a/src/home/invite_screen.rs +++ b/src/home/invite_screen.rs @@ -8,7 +8,7 @@ use std::ops::Deref; use makepad_widgets::*; use matrix_sdk::ruma::OwnedRoomId; -use crate::{app::AppStateAction, home::rooms_list::RoomsListRef, join_leave_room_modal::{JoinLeaveModalKind, JoinLeaveRoomModalAction}, room::{BasicRoomDetails, FetchedRoomAvatar}, shared::{avatar::AvatarWidgetRefExt, popup_list::{enqueue_popup_notification, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt}, sliding_sync::{submit_async_request, MatrixRequest}, utils::{self, RoomNameId}}; +use crate::{app::AppStateAction, home::rooms_list::RoomsListRef, join_leave_room_modal::{JoinLeaveModalKind, JoinLeaveRoomModalAction}, room::{BasicRoomDetails, FetchedRoomAvatar}, shared::{avatar::AvatarWidgetRefExt, bouncing_dots::BouncingDotsWidgetExt, popup_list::{enqueue_popup_notification, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt}, sliding_sync::{submit_async_request, MatrixRequest}, utils::{self, RoomNameId}}; use super::rooms_list::{InviteState, InviterInfo}; @@ -165,10 +165,41 @@ script_mod! { accept_button := RobrixPositiveIconButton { align: Align{x: 0.5, y: 0.5} padding: 15, - draw_icon.svg: (ICON_CHECKMARK) + draw_icon.svg: (ICON_JOIN_ROOM) icon_walk: Walk{width: 16, height: 16, margin: Inset{left: -2, right: -1} } text: "Join Room" } + + // Shown in place of the `accept_button` while a join is in progress. + // A spinner inside a frame that mimics the disabled button's appearance. + joining_view := RoundedView { + visible: false + width: Fit, height: Fit + align: Align{x: 0.5, y: 0.5} + padding: 15, + spacing: 10, + flow: Right + + show_bg: true + draw_bg +: { + color: (COLOR_BG_DISABLED) + border_radius: 4.0 + } + + Label { + align: Align{x: 0.5, y: 0.5} + draw_text +: { + color: (COLOR_FG_DISABLED) + text_style: REGULAR_TEXT {font_size: 10} + } + text: "Joining..." + } + + joining_spinner := BouncingDots { + margin: Inset{top: 1.0} + draw_bg.color: (COLOR_FG_DISABLED) + } + } } completion_label := Label { @@ -454,35 +485,57 @@ impl Widget for InviteScreen { // Third, set the buttons' text based on the invite state. let cancel_button = self.view.button(cx, ids!(cancel_button)); - let accept_button = self.view.button(cx, ids!(accept_button)); + let mut accept_button = self.view.button(cx, ids!(accept_button)); + let joining_view = self.view.view(cx, ids!(joining_view)); + let joining_spinner = self.view.bouncing_dots(cx, ids!(joining_spinner)); match self.invite_state { InviteState::WaitingOnUserInput => { cancel_button.set_enabled(cx, true); accept_button.set_enabled(cx, true); + accept_button.set_visible(cx, true); + joining_view.set_visible(cx, false); + joining_spinner.stop_animation(cx); cancel_button.set_text(cx, "Reject Invite"); accept_button.set_text(cx, "Join Room"); + script_apply_eval!(cx, accept_button, { + draw_icon.svg: mod.widgets.ICON_JOIN_ROOM, + }); } InviteState::WaitingForJoinResult => { cancel_button.set_enabled(cx, false); - accept_button.set_enabled(cx, false); cancel_button.set_text(cx, "Reject Invite"); - accept_button.set_text(cx, "Joining..."); + // Hide the join button and show the spinner in its place. + accept_button.set_visible(cx, false); + joining_view.set_visible(cx, true); + joining_spinner.start_animation(cx); } InviteState::WaitingForLeaveResult => { cancel_button.set_enabled(cx, false); accept_button.set_enabled(cx, false); + accept_button.set_visible(cx, true); + joining_view.set_visible(cx, false); + joining_spinner.stop_animation(cx); cancel_button.set_text(cx, "Rejecting..."); accept_button.set_text(cx, "Join Room"); } InviteState::WaitingForJoinedRoom => { cancel_button.set_enabled(cx, false); accept_button.set_enabled(cx, false); + accept_button.set_visible(cx, true); + joining_view.set_visible(cx, false); + joining_spinner.stop_animation(cx); cancel_button.set_text(cx, "Reject Invite"); - accept_button.set_text(cx, "Joined!"); + accept_button.set_text(cx, "✅ Joined!"); + script_apply_eval!(cx, accept_button, { + draw_icon.svg: mod.widgets.ICON_CHECKMARK, + draw_icon.color: (COLOR_FG_ACCEPT_GREEN), + }); } InviteState::RoomLeft => { cancel_button.set_visible(cx, false); accept_button.set_visible(cx, false); + joining_view.set_visible(cx, false); + joining_spinner.stop_animation(cx); self.view.label(cx, ids!(completion_label)).set_text( cx, "Invite successfully rejected. You may close this invite.",