From 1c6c11b996f636866e9a57ffeef7fb6a2eed3e73 Mon Sep 17 00:00:00 2001 From: aneeshaa1 Date: Mon, 22 Jun 2026 00:18:05 -0400 Subject: [PATCH 1/4] Use stateful icons for the InviteScreen join button --- src/home/invite_screen.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/home/invite_screen.rs b/src/home/invite_screen.rs index 02e54c6e4..294919a3d 100644 --- a/src/home/invite_screen.rs +++ b/src/home/invite_screen.rs @@ -165,7 +165,7 @@ 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" } @@ -461,6 +461,9 @@ impl Widget for InviteScreen { accept_button.set_enabled(cx, true); 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); @@ -478,7 +481,11 @@ impl Widget for InviteScreen { cancel_button.set_enabled(cx, false); accept_button.set_enabled(cx, false); 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); From 897d17e7e6a1932c2c957243ca5eec4d40d1ac6a Mon Sep 17 00:00:00 2001 From: aneeshaa1 Date: Mon, 22 Jun 2026 00:29:12 -0400 Subject: [PATCH 2/4] error fix --- src/home/invite_screen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/home/invite_screen.rs b/src/home/invite_screen.rs index 294919a3d..0a24da38d 100644 --- a/src/home/invite_screen.rs +++ b/src/home/invite_screen.rs @@ -454,7 +454,7 @@ 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)); match self.invite_state { InviteState::WaitingOnUserInput => { cancel_button.set_enabled(cx, true); From 25e7941873dda2b2f1bc81669d25055fc22a37bc Mon Sep 17 00:00:00 2001 From: aneeshaa1 Date: Mon, 29 Jun 2026 02:43:17 -0400 Subject: [PATCH 3/4] Show a loading spinner while joining a room from the InviteScreen --- src/home/invite_screen.rs | 52 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/home/invite_screen.rs b/src/home/invite_screen.rs index 0a24da38d..9d39bfc93 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}; @@ -169,6 +169,37 @@ script_mod! { 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 { @@ -455,10 +486,15 @@ 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 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, { @@ -467,19 +503,27 @@ impl Widget for InviteScreen { } 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!"); script_apply_eval!(cx, accept_button, { @@ -490,6 +534,8 @@ impl Widget for InviteScreen { 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.", From 28961eb5e3eccbb7850dec4efc4968bd07f7d605 Mon Sep 17 00:00:00 2001 From: aneeshaa1 Date: Mon, 29 Jun 2026 02:56:34 -0400 Subject: [PATCH 4/4] Close a room's tab when it has been successfully left --- src/app.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index b8fb1b1cb..d90b5d64e 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,