Skip to content

Commit bd14ffa

Browse files
committed
Add room preview support and "Cannot Preview" screen; support PreviewedRoom in Dock
- Add src/room/can_not_preview_screen.rs: a UI for rooms that cannot be previewed or joined, with a Join button / messaging. - Introduce PreviewedRoom variant in SelectedRoom and propagate through persistence and UI: - src/app.rs: import reordering and PreviewedRoom handling for room_id/room_name getters. - src/persistence/app_state.rs: include PreviewedRoom when saving dock state. - Desktop & mobile UI updates for room previews: - src/home/main_desktop_ui.rs: register CanNotPreviewScreen, implement focus_or_crate_preview_screen_tab to create/select preview tabs, and handle room preview fetch results (show preview or can't-preview screen). - src/home/main_mobile_ui.rs: small logic fix (handle None | _ case). - Improved matrix link handling in room UI: - src/home/room_screen.rs: enhanced handling of Matrix links (user, room id/alias, event links) — jump to known rooms (handling different RoomState cases) or request room preview for unknown rooms. - Serialization / utils: - src/utils.rs: add OwnedServerNameRon wrapper to support RON (de)serialization of OwnedServerName and adjust imports. - Misc: - Register new can_not_preview_screen module in src/room/mod.rs. - Tidy imports and adapt code to new flow. Rationale: Improve UX when clicking room links by showing a room preview or a clear message when the room cannot be previewed or joined. Treat previewed rooms as first-class tabs in the Dock and persist them in app state for consistent restore behaviour.
1 parent dd6747f commit bd14ffa

File tree

8 files changed

+416
-28
lines changed

8 files changed

+416
-28
lines changed

src/app.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@ use makepad_widgets::{makepad_micro_serde::*, *};
1010
use matrix_sdk::ruma::{OwnedRoomId, RoomId};
1111
use crate::{
1212
avatar_cache::clear_avatar_cache, home::{
13-
main_desktop_ui::MainDesktopUiAction, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_screen::{clear_timeline_states, MessageAction}, rooms_list::{clear_all_invited_rooms, enqueue_rooms_list_update, RoomsListAction, RoomsListRef, RoomsListUpdate}
13+
main_desktop_ui::MainDesktopUiAction, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_screen::{MessageAction, clear_timeline_states}, rooms_list::{RoomsListAction, RoomsListRef, RoomsListUpdate, clear_all_invited_rooms, enqueue_rooms_list_update}
1414
}, join_leave_room_modal::{
1515
JoinLeaveModalKind, JoinLeaveRoomModalAction, JoinLeaveRoomModalWidgetRefExt
1616
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::callout_tooltip::{
1717
CalloutTooltipOptions,
1818
CalloutTooltipWidgetRefExt,
1919
TooltipAction,
2020
}, sliding_sync::current_user_id, utils::{
21-
room_name_or_id,
22-
OwnedRoomIdRon,
21+
OwnedRoomIdRon, room_name_or_id
2322
}, verification::VerificationAction, verification_modal::{
2423
VerificationModalAction,
2524
VerificationModalWidgetRefExt,
@@ -630,20 +629,26 @@ pub enum SelectedRoom {
630629
room_id: OwnedRoomIdRon,
631630
room_name: Option<String>,
632631
},
632+
PreviewedRoom {
633+
room_id: OwnedRoomIdRon,
634+
room_name: Option<String>,
635+
},
633636
}
634637

635638
impl SelectedRoom {
636639
pub fn room_id(&self) -> &OwnedRoomId {
637640
match self {
638641
SelectedRoom::JoinedRoom { room_id, .. } => room_id,
639642
SelectedRoom::InvitedRoom { room_id, .. } => room_id,
643+
SelectedRoom::PreviewedRoom { room_id, .. } => room_id,
640644
}
641645
}
642646

643647
pub fn room_name(&self) -> Option<&String> {
644648
match self {
645649
SelectedRoom::JoinedRoom { room_name, .. } => room_name.as_ref(),
646650
SelectedRoom::InvitedRoom { room_name, .. } => room_name.as_ref(),
651+
SelectedRoom::PreviewedRoom { room_name, .. } => room_name.as_ref(),
647652
}
648653
}
649654

src/home/main_desktop_ui.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use matrix_sdk::ruma::OwnedRoomId;
33
use tokio::sync::Notify;
44
use std::{collections::HashMap, sync::Arc};
55

6-
use crate::{app::{AppState, AppStateAction, SelectedRoom}, utils::room_name_or_id};
6+
use crate::{app::{AppState, AppStateAction, SelectedRoom}, room::{BasicRoomDetails, FetchedRoomPreview, RoomPreviewAction, can_not_preview_screen::{CanNotPreviewDetails, CanNotPreviewScreenWidgetRefExt}}, shared::popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, utils::room_name_or_id};
77
use super::{invite_screen::InviteScreenWidgetRefExt, room_screen::RoomScreenWidgetRefExt, rooms_list::RoomsListAction};
88

99
live_design! {
@@ -17,6 +17,7 @@ live_design! {
1717
use crate::home::welcome_screen::WelcomeScreen;
1818
use crate::home::room_screen::RoomScreen;
1919
use crate::home::invite_screen::InviteScreen;
20+
use crate::room::can_not_preview_screen::CanNotPreviewScreen;
2021

2122
pub MainDesktopUI = {{MainDesktopUI}} {
2223
dock = <Dock> {
@@ -54,6 +55,7 @@ live_design! {
5455
welcome_screen = <WelcomeScreen> {}
5556
room_screen = <RoomScreen> {}
5657
invite_screen = <InviteScreen> {}
58+
can_not_preview_screen = <CanNotPreviewScreen> {}
5759
}
5860
}
5961
}
@@ -138,6 +140,7 @@ impl MainDesktopUI {
138140
id!(invite_screen),
139141
room_name_or_id(room_name.as_ref(), room_id),
140142
),
143+
_ => return
141144
};
142145
let new_tab_widget = dock.create_and_select_tab(
143146
cx,
@@ -168,6 +171,7 @@ impl MainDesktopUI {
168171
room.room_name().cloned()
169172
);
170173
}
174+
_ => {}
171175
}
172176
cx.action(MainDesktopUiAction::SaveDockIntoAppState);
173177
} else {
@@ -178,6 +182,77 @@ impl MainDesktopUI {
178182
self.most_recently_selected_room = Some(room);
179183
}
180184

185+
fn focus_or_crate_preview_screen_tab(&mut self, cx: &mut Cx, preview_info: &FetchedRoomPreview) {
186+
let room_id = preview_info.room_id.clone();
187+
let room_name = preview_info.name.clone();
188+
let is_world_readable = preview_info.room_preview.is_world_readable.unwrap_or(false);
189+
190+
let dock = self.view.dock(ids!(dock));
191+
let selected_room = SelectedRoom::PreviewedRoom { room_id: room_id.to_owned().into(), room_name: room_name.clone() };
192+
193+
// Do nothing if the room to select is already created and focused.
194+
if self.most_recently_selected_room.as_ref().is_some_and(|r| r == &selected_room) {
195+
return;
196+
}
197+
198+
// If the room is already open, select (jump to) its existing tab
199+
let room_id_as_live_id = LiveId::from_str(room_id.as_str());
200+
if self.open_rooms.contains_key(&room_id_as_live_id) {
201+
dock.select_tab(cx, room_id_as_live_id);
202+
self.most_recently_selected_room = Some(selected_room);
203+
return;
204+
}
205+
206+
207+
// Create a new tab for the room preview
208+
let (tab_bar, _pos) = dock.find_tab_bar_of_tab(id!(home_tab)).unwrap();
209+
let (kind, name) = if is_world_readable {
210+
(id!(room_screen), room_name_or_id(room_name.as_ref(), &room_id))
211+
} else {
212+
(id!(can_not_preview_screen), room_name_or_id(room_name.as_ref(), &room_id))
213+
};
214+
215+
let new_tab_widget = dock.create_and_select_tab(
216+
cx,
217+
tab_bar,
218+
room_id_as_live_id,
219+
kind,
220+
name,
221+
id!(CloseableTab),
222+
None,
223+
);
224+
225+
if let Some(new_widget) = new_tab_widget {
226+
self.room_order.push(selected_room.clone());
227+
if is_world_readable {
228+
new_widget.as_room_screen().set_displayed_room(
229+
cx,
230+
room_id.clone().into(),
231+
selected_room.room_name().cloned(),
232+
);
233+
} else {
234+
new_widget.as_can_not_preview_screen().set_displayed(
235+
cx,
236+
room_id.clone().into(),
237+
selected_room.room_name().cloned(),
238+
CanNotPreviewDetails {
239+
room_basic_details: BasicRoomDetails {
240+
room_id: room_id.clone(),
241+
room_name: room_name.clone(),
242+
room_avatar: preview_info.room_avatar.clone(),
243+
},
244+
join_rule: preview_info.room_preview.join_rule.clone(),
245+
},
246+
);
247+
}
248+
cx.action(MainDesktopUiAction::SaveDockIntoAppState);
249+
} else {
250+
error!("BUG: failed to create tab for {selected_room:?}");
251+
}
252+
self.open_rooms.insert(room_id_as_live_id, selected_room.clone());
253+
self.most_recently_selected_room = Some(selected_room);
254+
}
255+
181256
/// Closes a tab in the dock and focuses on the latest open room.
182257
fn close_tab(&mut self, cx: &mut Cx, tab_id: LiveId) {
183258
let dock = self.view.dock(ids!(dock));
@@ -282,6 +357,20 @@ impl WidgetMatchEvent for MainDesktopUI {
282357
continue;
283358
}
284359

360+
if let Some(RoomPreviewAction::Fetched(res)) = action.downcast_ref() {
361+
match res {
362+
Ok(room_preview) => self.focus_or_crate_preview_screen_tab(cx, room_preview),
363+
Err(err) => {
364+
error!("Failed to fetch room preview: {}", err);
365+
enqueue_popup_notification(PopupItem {
366+
message: "This room preview is not available.".to_string(),
367+
kind: PopupKind::Error,
368+
auto_dismissal_duration: None
369+
});
370+
},
371+
}
372+
}
373+
285374
// Handle actions emitted by the dock within the MainDesktopUI
286375
match widget_action.cast() { // TODO: don't we need to call `widget_uid_eq(dock.widget_uid())` here?
287376
// Whenever a tab (except for the home_tab) is pressed, notify the app state.

src/home/main_mobile_ui.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl Widget for MainMobileUI {
9090
.invite_screen(ids!(invite_screen))
9191
.set_displayed_invite(cx, room_id.clone().into(), room_name.clone());
9292
}
93-
None => {
93+
None | _ => {
9494
show_welcome = true;
9595
show_room = false;
9696
show_invite = false;

src/home/room_screen.rs

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,32 @@ use bytesize::ByteSize;
77
use imbl::Vector;
88
use makepad_widgets::{image_cache::ImageBuffer, *};
99
use matrix_sdk::{
10-
room::RoomMember, ruma::{
11-
events::{
10+
OwnedServerName, RoomState, room::RoomMember, ruma::{
11+
EventId, MatrixToUri, MatrixUri, OwnedEventId, OwnedMxcUri, OwnedRoomId, UserId, events::{
1212
receipt::Receipt,
1313
room::{
14-
message::{
14+
ImageInfo, MediaSource, message::{
1515
AudioMessageEventContent, EmoteMessageEventContent, FileMessageEventContent, FormattedBody, ImageMessageEventContent, KeyVerificationRequestEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, TextMessageEventContent, VideoMessageEventContent
16-
},
17-
ImageInfo, MediaSource
16+
}
1817
},
1918
sticker::{StickerEventContent, StickerMediaSource},
20-
},
21-
matrix_uri::MatrixId, uint, EventId, MatrixToUri, MatrixUri, OwnedEventId, OwnedMxcUri, OwnedRoomId, UserId
22-
}, OwnedServerName
19+
}, matrix_uri::MatrixId, uint
20+
}
2321
};
2422
use matrix_sdk_ui::timeline::{
2523
self, EmbeddedEvent, EncryptedMessage, EventTimelineItem, InReplyToDetails, MemberProfileChange, MsgLikeContent, MsgLikeKind, OtherMessageLike, PollState, RoomMembershipChange, TimelineDetails, TimelineEventItemId, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem
2624
};
2725

2826
use crate::{
29-
app::AppStateAction, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, text_preview_of_other_message_like, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::{edited_indicator::EditedIndicatorWidgetRefExt, link_preview::{LinkPreviewCache, LinkPreviewRef, LinkPreviewWidgetRefExt}, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::RoomsListRef, tombstone_footer::SuccessorRoomDetails}, media_cache::{MediaCache, MediaCacheEntry}, profile::{
27+
app::{AppStateAction, SelectedRoom}, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, text_preview_of_other_message_like, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::{edited_indicator::EditedIndicatorWidgetRefExt, link_preview::{LinkPreviewCache, LinkPreviewRef, LinkPreviewWidgetRefExt}, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::{RoomsListAction, RoomsListRef}, tombstone_footer::SuccessorRoomDetails}, media_cache::{MediaCache, MediaCacheEntry}, profile::{
3028
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
3129
user_profile_cache,
3230
},
3331
room::{room_input_bar::RoomInputBarState, typing_notice::TypingNoticeWidgetExt},
3432
shared::{
35-
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{enqueue_popup_notification, PopupItem, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt
33+
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt
3634
},
37-
sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineRequestSender, UserPowerLevels}, utils::{self, room_name_or_id, unix_time_millis_to_datetime, ImageFormat, MEDIA_THUMBNAIL_FORMAT}
35+
sliding_sync::{BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineRequestSender, UserPowerLevels, get_client, submit_async_request, take_timeline_endpoints}, utils::{self, ImageFormat, MEDIA_THUMBNAIL_FORMAT, room_name_or_id, unix_time_millis_to_datetime}
3836
};
3937
use crate::home::event_reaction_list::ReactionListWidgetRefExt;
4038
use crate::home::room_read_receipt::AvatarRowWidgetRefExt;
@@ -1435,9 +1433,10 @@ impl RoomScreen {
14351433
action: &Action,
14361434
pane: &UserProfileSlidingPaneRef,
14371435
) -> bool {
1436+
let uid = self.widget_uid();
14381437
// A closure that handles both MatrixToUri and MatrixUri links,
14391438
// and returns whether the link was handled.
1440-
let mut handle_matrix_link = |id: &MatrixId, _via: &[OwnedServerName]| -> bool {
1439+
let mut handle_matrix_link = |id: &MatrixId, via: &[OwnedServerName]| -> bool {
14411440
match id {
14421441
MatrixId::User(user_id) => {
14431442
// There is no synchronous way to get the user's full profile info
@@ -1474,20 +1473,86 @@ impl RoomScreen {
14741473
});
14751474
return true;
14761475
}
1477-
if let Some(_known_room) = get_client().and_then(|c| c.get_room(room_id)) {
1478-
log!("TODO: jump to known room {}", room_id);
1476+
if let Some(known_room) = get_client().and_then(|c| c.get_room(room_id)) {
1477+
if known_room.is_space() {
1478+
// TODO: Show space home page
1479+
enqueue_popup_notification(PopupItem {
1480+
message: "Showing a space's home page is not yet supported.".into(),
1481+
kind: PopupKind::Warning,
1482+
auto_dismissal_duration: Some(3.0)
1483+
});
1484+
}
1485+
1486+
if known_room.is_tombstoned() {
1487+
// TODO: To join the successor room, we need to: known_room.tombstone_content()
1488+
enqueue_popup_notification(PopupItem {
1489+
message: "This room has been replaced by another room. You must join the new room.".into(),
1490+
kind: PopupKind::Warning,
1491+
auto_dismissal_duration: None
1492+
});
1493+
}
1494+
1495+
match known_room.state() {
1496+
RoomState::Joined => {
1497+
cx.widget_action(
1498+
uid,
1499+
&Scope::empty().path,
1500+
RoomsListAction::Selected(
1501+
SelectedRoom::JoinedRoom {
1502+
room_id: known_room.room_id().to_owned().into(),
1503+
room_name: known_room.name(),
1504+
}
1505+
)
1506+
);
1507+
}
1508+
RoomState::Invited => {
1509+
cx.widget_action(
1510+
uid,
1511+
&Scope::empty().path,
1512+
RoomsListAction::Selected(
1513+
SelectedRoom::InvitedRoom {
1514+
room_id: known_room.room_id().to_owned().into(),
1515+
room_name: known_room.name(),
1516+
}
1517+
)
1518+
);
1519+
}
1520+
RoomState::Knocked => {
1521+
enqueue_popup_notification(PopupItem {
1522+
message: "Already knocked. Waiting for approval.".into(),
1523+
kind: PopupKind::Info,
1524+
auto_dismissal_duration: None
1525+
});
1526+
}
1527+
RoomState::Banned => {
1528+
enqueue_popup_notification(PopupItem {
1529+
message: "You are banned from that room. You cannot join. Unless the admin lifts the ban.".into(),
1530+
kind: PopupKind::Error,
1531+
auto_dismissal_duration: None
1532+
});
1533+
}
1534+
RoomState::Left => {
1535+
enqueue_popup_notification(PopupItem {
1536+
message: "You have left that room. You must be re-invited to join again.".into(),
1537+
kind: PopupKind::Info,
1538+
auto_dismissal_duration: None
1539+
});
1540+
}
1541+
}
14791542
} else {
1480-
log!("TODO: fetch and display room preview for room {}", room_id);
1543+
submit_async_request(MatrixRequest::GetRoomPreview {
1544+
room_or_alias_id: room_id.to_owned().into(),
1545+
via: via.to_owned(),
1546+
});
14811547
}
1482-
false
1548+
true
14831549
}
14841550
MatrixId::RoomAlias(room_alias) => {
1485-
log!("TODO: open room alias {}", room_alias);
1486-
// TODO: open a room loading screen that shows a spinner
1487-
// while our background async task calls Client::resolve_room_alias()
1488-
// and then either jumps to the room if known, or fetches and displays
1489-
// a room preview for that room.
1490-
false
1551+
submit_async_request(MatrixRequest::GetRoomPreview {
1552+
room_or_alias_id: room_alias.to_owned().into(),
1553+
via: via.to_owned(),
1554+
});
1555+
true
14911556
}
14921557
MatrixId::Event(room_id, event_id) => {
14931558
log!("TODO: open event {} in room {}", event_id, room_id);

src/persistence/app_state.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ pub fn save_app_state(
3333
for (tab_id, room) in &app_state.saved_dock_state.open_rooms {
3434
match room {
3535
SelectedRoom::JoinedRoom { room_id, .. }
36-
| SelectedRoom::InvitedRoom { room_id, .. } => {
36+
| SelectedRoom::InvitedRoom { room_id, .. }
37+
| SelectedRoom::PreviewedRoom { room_id, .. } => {
3738
if !app_state.saved_dock_state.dock_items.contains_key(tab_id) {
3839
error!("Room id: {} already in dock state", room_id);
3940
}

0 commit comments

Comments
 (0)