Skip to content

Commit 24d8652

Browse files
committed
Handle matrix.to room alias and room ID links.
1. Add support for clicking matrix.to links in room messages to navigate to rooms and resolve room aliases. 2. Implement async room alias resolution with proper error handling and user feedback through popup notifications.
1 parent 98c6155 commit 24d8652

File tree

3 files changed

+142
-34
lines changed

3 files changed

+142
-34
lines changed

src/home/room_screen.rs

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ use matrix_sdk_ui::timeline::{
1919
};
2020

2121
use crate::{
22-
app::RoomsPanelRestoreAction, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, 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, editing_pane::EditingPaneState, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::RoomsListRef}, location::init_location_subscriber, media_cache::{MediaCache, MediaCacheEntry}, profile::{
22+
app::{RoomsPanelRestoreAction, 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_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::{edited_indicator::EditedIndicatorWidgetRefExt, editing_pane::EditingPaneState, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::{RoomsListAction, RoomsListRef}}, location::init_location_subscriber, media_cache::{MediaCache, MediaCacheEntry}, profile::{
2323
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
2424
user_profile_cache,
2525
}, shared::{
2626
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{enqueue_popup_notification, PopupItem}, styles::COLOR_DANGER_RED, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt, typing_animation::TypingAnimationWidgetExt
2727
}, sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender, UserPowerLevels}, utils::{self, room_name_or_id, unix_time_millis_to_datetime, ImageFormat, MEDIA_THUMBNAIL_FORMAT}
2828
};
2929
use crate::home::event_reaction_list::ReactionListWidgetRefExt;
30+
use crate::room::ResolveRoomAliasAction;
3031
use crate::home::room_read_receipt::AvatarRowWidgetRefExt;
3132
use crate::room::room_input_bar::RoomInputBarWidgetExt;
3233
use crate::shared::mentionable_text_input::MentionableTextInputWidgetRefExt;
@@ -995,6 +996,43 @@ impl Widget for RoomScreen {
995996
);
996997
}
997998
}
999+
1000+
// Handle resolved room alias actions - only for requests from this widget
1001+
if let Some(ResolveRoomAliasAction::Resolved { requester_uid, room_alias: _ , room_id, servers: _ }) = action.downcast_ref() {
1002+
// Only handle this action if it was requested by this widget
1003+
if *requester_uid == room_screen_widget_uid {
1004+
if let Some(known_room) = get_client().and_then(|c| c.get_room(room_id)) {
1005+
if known_room.is_space() {
1006+
enqueue_popup_notification(PopupItem {
1007+
message: format!("Found space {} but it is a space, not a regular room.", room_id),
1008+
auto_dismissal_duration: Some(3.0)
1009+
});
1010+
} else {
1011+
cx.widget_action(room_screen_widget_uid, &scope.path, RoomsListAction::Selected(
1012+
SelectedRoom::JoinedRoom {
1013+
room_id: room_id.clone().into(),
1014+
room_name: known_room.name()
1015+
}
1016+
));
1017+
}
1018+
} else {
1019+
enqueue_popup_notification(PopupItem {
1020+
message: format!("Found room {} but you are not joined to it yet.", room_id),
1021+
auto_dismissal_duration: Some(3.0)
1022+
});
1023+
}
1024+
}
1025+
}
1026+
1027+
if let Some(ResolveRoomAliasAction::Failed { requester_uid, room_alias, error }) = action.downcast_ref() {
1028+
if *requester_uid == room_screen_widget_uid {
1029+
error!("Failed to resolve room alias {}: {:?}", room_alias, error);
1030+
enqueue_popup_notification(PopupItem {
1031+
message: format!("Could not find room with alias: {}", room_alias),
1032+
auto_dismissal_duration: None
1033+
});
1034+
}
1035+
}
9981036
}
9991037

10001038
/*
@@ -1731,6 +1769,7 @@ impl RoomScreen {
17311769
action: &Action,
17321770
pane: &UserProfileSlidingPaneRef,
17331771
) -> bool {
1772+
let uid = self.widget_uid();
17341773
// A closure that handles both MatrixToUri and MatrixUri links,
17351774
// and returns whether the link was handled.
17361775
let mut handle_matrix_link = |id: &MatrixId, _via: &[OwnedServerName]| -> bool {
@@ -1763,26 +1802,59 @@ impl RoomScreen {
17631802
}
17641803
MatrixId::Room(room_id) => {
17651804
if self.room_id.as_ref() == Some(room_id) {
1766-
enqueue_popup_notification(PopupItem {
1767-
message: "You are already viewing that room.".into(),
1768-
auto_dismissal_duration: None
1805+
enqueue_popup_notification(PopupItem {
1806+
message: "You are already viewing that room.".into(),
1807+
auto_dismissal_duration: Some(3.0)
17691808
});
17701809
return true;
17711810
}
1772-
if let Some(_known_room) = get_client().and_then(|c| c.get_room(room_id)) {
1773-
log!("TODO: jump to known room {}", room_id);
1811+
if let Some(known_room) = get_client().and_then(|c| c.get_room(room_id)) {
1812+
cx.widget_action(uid, &Scope::empty().path, RoomsListAction::Selected(
1813+
SelectedRoom::JoinedRoom {
1814+
room_id: room_id.clone().into(),
1815+
room_name: known_room.name()
1816+
}
1817+
));
17741818
} else {
1775-
log!("TODO: fetch and display room preview for room {}", room_id);
1819+
// TODO: fetch and display a room preview for the given room ID.
1820+
enqueue_popup_notification(PopupItem {
1821+
message: "You are not joined to this room yet.".into(),
1822+
auto_dismissal_duration: Some(3.0)
1823+
});
17761824
}
1777-
false
1825+
true
17781826
}
17791827
MatrixId::RoomAlias(room_alias) => {
1780-
log!("TODO: open room alias {}", room_alias);
1781-
// TODO: open a room loading screen that shows a spinner
1782-
// while our background async task calls Client::resolve_room_alias()
1783-
// and then either jumps to the room if known, or fetches and displays
1784-
// a room preview for that room.
1785-
false
1828+
// Check if we already have this room alias in the client
1829+
if let Some(known_room) = get_client().and_then(|c| {
1830+
c.rooms().into_iter().find(|room| {
1831+
room.canonical_alias().as_ref() == Some(room_alias) ||
1832+
room.alt_aliases().contains(room_alias)
1833+
})
1834+
}) {
1835+
let room_id = known_room.room_id();
1836+
if self.room_id.as_ref() == Some(&room_id.to_owned()) {
1837+
enqueue_popup_notification(PopupItem {
1838+
message: "You are already viewing that room.".into(),
1839+
auto_dismissal_duration: Some(3.0)
1840+
});
1841+
} else {
1842+
cx.widget_action(uid, &Scope::empty().path, RoomsListAction::Selected(
1843+
SelectedRoom::JoinedRoom {
1844+
room_id: room_id.to_owned().into(),
1845+
room_name: known_room.name()
1846+
}
1847+
));
1848+
}
1849+
return true;
1850+
}
1851+
// TODO: open a room loading screen that shows a spinner.
1852+
// Submit async request to resolve the room alias
1853+
submit_async_request(MatrixRequest::ResolveRoomAlias {
1854+
room_alias: room_alias.clone(),
1855+
requester_uid: uid,
1856+
});
1857+
true
17861858
}
17871859
MatrixId::Event(room_id, event_id) => {
17881860
log!("TODO: open event {} in room {}", event_id, room_id);
@@ -1795,15 +1867,10 @@ impl RoomScreen {
17951867
}
17961868
};
17971869

1798-
if let HtmlLinkAction::Clicked { url, .. } = action.as_widget_action().cast() {
1799-
let mut link_was_handled = false;
1800-
if let Ok(matrix_to_uri) = MatrixToUri::parse(&url) {
1801-
link_was_handled |= handle_matrix_link(matrix_to_uri.id(), matrix_to_uri.via());
1802-
}
1803-
else if let Ok(matrix_uri) = MatrixUri::parse(&url) {
1804-
link_was_handled |= handle_matrix_link(matrix_uri.id(), matrix_uri.via());
1805-
}
1806-
1870+
// Prioritize handling RobrixHtmlLinkAction::ClickedMatrixLink over HtmlLinkAction::Clicked
1871+
// to avoid duplicate processing of the same Matrix link
1872+
if let RobrixHtmlLinkAction::ClickedMatrixLink { url, matrix_id, via, .. } = action.as_widget_action().cast() {
1873+
let link_was_handled = handle_matrix_link(&matrix_id, &via);
18071874
if !link_was_handled {
18081875
log!("Opening URL \"{}\"", url);
18091876
if let Err(e) = robius_open::Uri::new(&url).open() {
@@ -1816,8 +1883,15 @@ impl RoomScreen {
18161883
}
18171884
true
18181885
}
1819-
else if let RobrixHtmlLinkAction::ClickedMatrixLink { url, matrix_id, via, .. } = action.as_widget_action().cast() {
1820-
let link_was_handled = handle_matrix_link(&matrix_id, &via);
1886+
else if let HtmlLinkAction::Clicked { url, .. } = action.as_widget_action().cast() {
1887+
let mut link_was_handled = false;
1888+
if let Ok(matrix_to_uri) = MatrixToUri::parse(&url) {
1889+
link_was_handled |= handle_matrix_link(matrix_to_uri.id(), matrix_to_uri.via());
1890+
}
1891+
else if let Ok(matrix_uri) = MatrixUri::parse(&url) {
1892+
link_was_handled |= handle_matrix_link(matrix_uri.id(), matrix_uri.via());
1893+
}
1894+
18211895
if !link_was_handled {
18221896
log!("Opening URL \"{}\"", url);
18231897
if let Err(e) = robius_open::Uri::new(&url).open() {

src/room/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::sync::Arc;
2-
use makepad_widgets::Cx;
3-
use matrix_sdk::ruma::OwnedRoomId;
2+
use makepad_widgets::{Cx, WidgetUid};
3+
use matrix_sdk::{ruma::{OwnedRoomAliasId, OwnedRoomId}, OwnedServerName};
44

55
pub mod room_input_bar;
66
pub mod room_member_manager;
@@ -10,6 +10,22 @@ pub fn live_design(cx: &mut Cx) {
1010
room_input_bar::live_design(cx)
1111
}
1212

13+
/// Actions sent from the backend task as a result of a [`MatrixRequest::ResolveRoomAlias`].
14+
#[derive(Debug)]
15+
pub enum ResolveRoomAliasAction {
16+
Resolved {
17+
requester_uid: WidgetUid,
18+
room_alias: OwnedRoomAliasId,
19+
room_id: OwnedRoomId,
20+
servers: Vec<OwnedServerName>,
21+
},
22+
Failed {
23+
requester_uid: WidgetUid,
24+
room_alias: OwnedRoomAliasId,
25+
error: matrix_sdk::Error,
26+
}
27+
}
28+
1329
/// Basic details about a room, used for displaying a preview of it.
1430
#[derive(Clone, Debug)]
1531
pub struct BasicRoomDetails {

src/sliding_sync.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use eyeball::Subscriber;
55
use eyeball_im::VectorDiff;
66
use futures_util::{pin_mut, StreamExt};
77
use imbl::Vector;
8-
use makepad_widgets::{error, log, warning, Cx, SignalToUI};
8+
use makepad_widgets::{error, log, warning, Cx, SignalToUI, WidgetUid};
99
use matrix_sdk::{
1010
config::RequestConfig, encryption::EncryptionSettings, event_handler::EventHandlerDropGuard, media::MediaRequestParameters, room::{edit::EditedContent, reply::Reply, RoomMember}, ruma::{
1111
api::client::receipt::create_receipt::v3::ReceiptType, events::{
@@ -33,7 +33,7 @@ use crate::{
3333
}, login::login_screen::LoginAction, media_cache::{MediaCacheEntry, MediaCacheEntryRef}, persistent_state::{self, load_rooms_panel_state, ClientSessionPersisted}, profile::{
3434
user_profile::{AvatarState, UserProfile},
3535
user_profile_cache::{enqueue_user_profile_update, UserProfileUpdate},
36-
}, room::RoomPreviewAvatar, shared::{html_or_plaintext::MatrixLinkPillState, jump_to_bottom_button::UnreadMessageCount, popup_list::{enqueue_popup_notification, PopupItem}}, utils::{self, AVATAR_THUMBNAIL_FORMAT}, verification::add_verification_event_handlers_and_sync_client
36+
}, room::{ResolveRoomAliasAction, RoomPreviewAvatar}, shared::{html_or_plaintext::MatrixLinkPillState, jump_to_bottom_button::UnreadMessageCount, popup_list::{enqueue_popup_notification, PopupItem}}, utils::{self, AVATAR_THUMBNAIL_FORMAT}, verification::add_verification_event_handlers_and_sync_client
3737
};
3838

3939
#[derive(Parser, Debug, Default)]
@@ -295,7 +295,10 @@ pub enum MatrixRequest {
295295
room_id: OwnedRoomId,
296296
},
297297
/// Request to resolve a room alias into a room ID and the servers that know about that room.
298-
ResolveRoomAlias(OwnedRoomAliasId),
298+
ResolveRoomAlias {
299+
requester_uid: WidgetUid,
300+
room_alias: OwnedRoomAliasId,
301+
},
299302
/// Request to fetch an Avatar image from the server.
300303
/// Upon completion of the async media request, the `on_fetched` function
301304
/// will be invoked with the content of an `AvatarUpdate`.
@@ -898,13 +901,28 @@ async fn async_worker(
898901
MatrixRequest::SpawnSSOServer { brand, homeserver_url, identity_provider_id} => {
899902
spawn_sso_server(brand, homeserver_url, identity_provider_id, login_sender.clone()).await;
900903
}
901-
MatrixRequest::ResolveRoomAlias(room_alias) => {
904+
MatrixRequest::ResolveRoomAlias { room_alias, requester_uid } => {
902905
let Some(client) = CLIENT.get() else { continue };
903906
let _resolve_task = Handle::current().spawn(async move {
904907
log!("Sending resolve room alias request for {room_alias}...");
905-
let res = client.resolve_room_alias(&room_alias).await;
906-
log!("Resolved room alias {room_alias} to: {res:?}");
907-
todo!("Send the resolved room alias back to the UI thread somehow.");
908+
let result_action = match client.resolve_room_alias(&room_alias).await {
909+
Ok(response) => {
910+
ResolveRoomAliasAction::Resolved {
911+
requester_uid,
912+
room_alias,
913+
room_id: response.room_id,
914+
servers: response.servers
915+
}
916+
}
917+
Err(e) => {
918+
ResolveRoomAliasAction::Failed {
919+
requester_uid,
920+
room_alias,
921+
error: e.into()
922+
}
923+
}
924+
};
925+
Cx::post_action(result_action);
908926
});
909927
}
910928
MatrixRequest::FetchAvatar { mxc_uri, on_fetched } => {

0 commit comments

Comments
 (0)