diff --git a/src/app.rs b/src/app.rs index 39388125..bf6b3152 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use makepad_widgets::{makepad_micro_serde::*, *}; -use matrix_sdk::ruma::{OwnedRoomId, RoomId}; +use matrix_sdk::{RoomDisplayName, ruma::{OwnedRoomId, RoomId}}; use crate::{ avatar_cache::clear_avatar_cache, home::{ 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} @@ -20,6 +20,7 @@ use crate::{ }, sliding_sync::current_user_id, utils::{ room_name_or_id, OwnedRoomIdRon, + RoomName, }, verification::VerificationAction, verification_modal::{ VerificationModalAction, VerificationModalWidgetRefExt, @@ -579,7 +580,7 @@ impl App { // Select and scroll to the destination room in the rooms list. let new_selected_room = SelectedRoom::JoinedRoom { room_id: destination_room.room_id.clone().into(), - room_name: destination_room.room_name.clone(), + room_name: destination_room.room_name.clone().into(), }; enqueue_rooms_list_update(RoomsListUpdate::ScrollToRoom(destination_room.room_id.clone())); cx.widget_action( @@ -627,11 +628,11 @@ pub struct SavedDockState { pub enum SelectedRoom { JoinedRoom { room_id: OwnedRoomIdRon, - room_name: Option, + room_name: RoomName, }, InvitedRoom { room_id: OwnedRoomIdRon, - room_name: Option, + room_name: RoomName, }, } @@ -643,7 +644,7 @@ impl SelectedRoom { } } - pub fn room_name(&self) -> Option<&String> { + pub fn room_name(&self) -> &RoomDisplayName { match self { SelectedRoom::JoinedRoom { room_name, .. } => room_name.as_ref(), SelectedRoom::InvitedRoom { room_name, .. } => room_name.as_ref(), @@ -659,7 +660,7 @@ impl SelectedRoom { pub fn upgrade_invite_to_joined(&mut self, room_id: &RoomId) -> bool { match self { SelectedRoom::InvitedRoom { room_id: id, room_name } if id.0 == room_id => { - let name = room_name.take(); + let name = room_name.clone(); *self = SelectedRoom::JoinedRoom { room_id: id.clone(), room_name: name, diff --git a/src/home/invite_screen.rs b/src/home/invite_screen.rs index 292babd9..0416391c 100644 --- a/src/home/invite_screen.rs +++ b/src/home/invite_screen.rs @@ -6,9 +6,9 @@ use std::ops::Deref; use makepad_widgets::*; -use matrix_sdk::ruma::OwnedRoomId; +use matrix_sdk::{RoomDisplayName, 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, PopupItem, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt}, sliding_sync::{submit_async_request, MatrixRequest}, utils::{self, room_name_or_id}}; +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, PopupItem, PopupKind}, restore_status_view::RestoreStatusViewWidgetExt}, sliding_sync::{submit_async_request, MatrixRequest}, utils::{self, room_name_or_id, RoomName}}; use super::rooms_list::{InviteState, InviterInfo}; @@ -268,7 +268,7 @@ pub struct InviteScreen { /// The ID of the room that has been invited. /// This is used to wait for RoomsPanel #[rust] room_id: Option, - #[rust] room_name: String, + #[rust(RoomDisplayName::Empty)] room_name: RoomDisplayName, #[rust] is_loaded: bool, #[rust] all_rooms_loaded: bool, } @@ -351,7 +351,8 @@ impl Widget for InviteScreen { Some(JoinRoomResultAction::Failed { room_id, error }) if room_id == &info.room_id => { self.invite_state = InviteState::WaitingOnUserInput; if !self.has_shown_confirmation { - let msg = utils::stringify_join_leave_error(error, info.room_name.as_deref(), true, true); + let room_label = room_name_or_id(&info.room_name, &info.room_id); + let msg = utils::stringify_join_leave_error(error, Some(&room_label), true, true); enqueue_popup_notification(PopupItem { message: msg, kind: PopupKind::Error, auto_dismissal_duration: None }); } continue; @@ -397,7 +398,8 @@ impl Widget for InviteScreen { fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { if !self.is_loaded { let mut restore_status_view = self.view.restore_status_view(ids!(restore_status_view)); - restore_status_view.set_content(cx, self.all_rooms_loaded, &self.room_name); + let room_id = self.room_id.as_ref().map(|id| id.as_ref()); + restore_status_view.set_content(cx, self.all_rooms_loaded, self.room_name.clone().into(), room_id); return restore_status_view.draw(cx, scope); } let Some(info) = self.info.as_ref() else { @@ -467,10 +469,8 @@ impl Widget for InviteScreen { ); } } - room_view.label(ids!(room_name)).set_text( - cx, - info.room_name.as_deref().unwrap_or_else(|| info.room_id.as_str()), - ); + let invite_room_label = room_name_or_id(&info.room_name, &info.room_id); + room_view.label(ids!(room_name)).set_text(cx, &invite_room_label); // Third, set the buttons' text based on the invite state. let cancel_button = self.view.button(ids!(cancel_button)); @@ -516,10 +516,9 @@ impl Widget for InviteScreen { impl InviteScreen { /// Sets the ID of the invited room that will be displayed by this screen. - pub fn set_displayed_invite>>(&mut self, cx: &mut Cx, room_id: OwnedRoomId, room_name: S) { + pub fn set_displayed_invite(&mut self, cx: &mut Cx, room_id: OwnedRoomId, room_name: RoomDisplayName) { self.room_id = Some(room_id.clone()); - self.room_name = room_name_or_id(room_name.into(), &room_id); - + self.room_name = room_name; if let Some(invite) = super::rooms_list::get_invited_rooms(cx) .borrow() .get(&room_id) @@ -538,13 +537,23 @@ impl InviteScreen { self.all_rooms_loaded = true; self.redraw(cx); } - self.view.restore_status_view(ids!(restore_status_view)).set_visible(cx, !self.is_loaded); + self.view + .restore_status_view(ids!(restore_status_view)) + .set_content( + cx, + self.all_rooms_loaded, + RoomName::from(self.room_name.clone()), + Some(room_id.as_ref()), + ); + self.view + .restore_status_view(ids!(restore_status_view)) + .set_visible(cx, !self.is_loaded); } } impl InviteScreenRef { /// See [`InviteScreen::set_displayed_invite()`]. - pub fn set_displayed_invite>>(&self, cx: &mut Cx, room_id: OwnedRoomId, room_name: S) { + pub fn set_displayed_invite(&self, cx: &mut Cx, room_id: OwnedRoomId, room_name: RoomDisplayName) { if let Some(mut inner) = self.borrow_mut() { inner.set_displayed_invite(cx, room_id, room_name); } diff --git a/src/home/main_desktop_ui.rs b/src/home/main_desktop_ui.rs index 06fedf54..e9666f3d 100644 --- a/src/home/main_desktop_ui.rs +++ b/src/home/main_desktop_ui.rs @@ -1,5 +1,5 @@ use makepad_widgets::*; -use matrix_sdk::ruma::OwnedRoomId; +use matrix_sdk::{RoomDisplayName, ruma::OwnedRoomId}; use tokio::sync::Notify; use std::{collections::HashMap, sync::Arc}; @@ -132,11 +132,11 @@ impl MainDesktopUI { let (kind, name) = match &room { SelectedRoom::JoinedRoom { room_id, room_name } => ( id!(room_screen), - room_name_or_id(room_name.as_ref(), room_id), + room_name_or_id(room_name.clone().into_inner(), room_id), ), SelectedRoom::InvitedRoom { room_id, room_name } => ( id!(invite_screen), - room_name_or_id(room_name.as_ref(), room_id), + room_name_or_id(room_name.clone().into_inner(), room_id), ), }; let new_tab_widget = dock.create_and_select_tab( @@ -154,18 +154,18 @@ impl MainDesktopUI { if let Some(new_widget) = new_tab_widget { self.room_order.push(room.clone()); match &room { - SelectedRoom::JoinedRoom { room_id, .. } => { + SelectedRoom::JoinedRoom { room_id, room_name } => { new_widget.as_room_screen().set_displayed_room( cx, room_id.clone().into(), - room.room_name().cloned(), + room_name.as_ref().clone(), ); } SelectedRoom::InvitedRoom { room_id, room_name: _ } => { new_widget.as_invite_screen().set_displayed_invite( cx, room_id.clone().into(), - room.room_name().cloned() + room.room_name().clone() ); } } @@ -234,14 +234,14 @@ impl MainDesktopUI { cx: &mut Cx, _scope: &mut Scope, room_id: OwnedRoomId, - room_name: Option, + room_name: RoomDisplayName, ) { let dock = self.view.dock(ids!(dock)); let Some((new_widget, true)) = dock.replace_tab( cx, LiveId::from_str(room_id.as_str()), id!(room_screen), - Some(room_name_or_id(room_name.as_ref(), &room_id)), + Some(room_name_or_id(&room_name, &room_id)), false, ) else { // Nothing we can really do here except log an error. @@ -250,11 +250,9 @@ impl MainDesktopUI { }; // Set the info to be displayed in the newly-replaced RoomScreen.. - new_widget.as_room_screen().set_displayed_room( - cx, - room_id.clone(), - room_name.clone(), - ); + new_widget + .as_room_screen() + .set_displayed_room(cx, room_id.clone(), room_name.clone()); // Go through all existing `SelectedRoom` instances and replace the // `SelectedRoom::InvitedRoom`s with `SelectedRoom::JoinedRoom`s. @@ -366,14 +364,14 @@ impl WidgetMatchEvent for MainDesktopUI { widget.as_room_screen().set_displayed_room( cx, room_id.clone().into(), - room_name.clone(), + room_name.as_ref().clone(), ); } Some(SelectedRoom::InvitedRoom { room_id, room_name }) => { widget.as_invite_screen().set_displayed_invite( cx, room_id.clone().into(), - room_name.clone(), + room_name.as_ref().clone(), ); } _ => { } diff --git a/src/home/main_mobile_ui.rs b/src/home/main_mobile_ui.rs index 1772348b..82216e2a 100644 --- a/src/home/main_mobile_ui.rs +++ b/src/home/main_mobile_ui.rs @@ -80,7 +80,7 @@ impl Widget for MainMobileUI { // Get a reference to the `RoomScreen` widget and tell it which room's data to show. self.view .room_screen(ids!(room_screen)) - .set_displayed_room(cx, room_id.clone().into(), room_name.clone()); + .set_displayed_room(cx, room_id.clone().into(), room_name.as_ref().clone()); } Some(SelectedRoom::InvitedRoom { room_id, room_name }) => { show_welcome = false; @@ -88,7 +88,7 @@ impl Widget for MainMobileUI { show_invite = true; self.view .invite_screen(ids!(invite_screen)) - .set_displayed_invite(cx, room_id.clone().into(), room_name.clone()); + .set_displayed_invite(cx, room_id.clone().into(), room_name.clone().into()); } None => { show_welcome = true; diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index cab2772d..3a430a03 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -7,7 +7,7 @@ use bytesize::ByteSize; use imbl::Vector; use makepad_widgets::{image_cache::ImageBuffer, *}; use matrix_sdk::{ - room::RoomMember, ruma::{ + room::RoomMember, RoomDisplayName, ruma::{ events::{ receipt::Receipt, room::{ @@ -34,7 +34,7 @@ use crate::{ shared::{ 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 }, - 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} + sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineRequestSender, UserPowerLevels}, utils::{self, unix_time_millis_to_datetime, ImageFormat, MEDIA_THUMBNAIL_FORMAT, RoomName} }; use crate::home::event_reaction_list::ReactionListWidgetRefExt; use crate::home::room_read_receipt::AvatarRowWidgetRefExt; @@ -491,7 +491,6 @@ live_design! { draw_bg: { color: (COLOR_PRIMARY_DARKER) } - restore_status_view = {} // Widgets within this view will get shifted upwards when the on-screen keyboard is shown. @@ -557,7 +556,7 @@ pub struct RoomScreen { /// The room ID of the currently-shown room. #[rust] room_id: Option, /// The display name of the currently-shown room. - #[rust] room_name: String, + #[rust(RoomDisplayName::Empty)] room_name: RoomDisplayName, /// The persistent UI-relevant states for the room that this widget is currently displaying. #[rust] tl_state: Option, /// The set of pinned events in this room. @@ -699,7 +698,7 @@ impl Widget for RoomScreen { &user_profile_sliding_pane, UserProfilePaneInfo { profile_and_room_id, - room_name: self.room_name.clone(), + room_name: self.room_name.to_string(), room_member: None, }, ); @@ -797,7 +796,7 @@ impl Widget for RoomScreen { let (room_display_name, room_avatar_url) = get_client() .and_then(|client| client.get_room(&room_id)) .map(|room| ( - room.cached_display_name().map(|name| name.to_string()), + room.cached_display_name(), room.avatar_url() )) .unwrap_or((None, None)); @@ -815,7 +814,7 @@ impl Widget for RoomScreen { room_screen_widget_uid, room_id, room_members: None, - room_display_name: None, + room_display_name: Some(self.room_name.clone()), room_avatar_url: None, } } else { @@ -902,7 +901,9 @@ impl Widget for RoomScreen { // If the room isn't loaded yet, we show the restore status label only. if !self.is_loaded { let mut restore_status_view = self.view.restore_status_view(ids!(restore_status_view)); - restore_status_view.set_content(cx, self.all_rooms_loaded, &self.room_name); + let room_id = self.room_id.as_deref(); + let room_name = RoomName::from(self.room_name.clone()); + restore_status_view.set_content(cx, self.all_rooms_loaded, room_name, room_id); return restore_status_view.draw(cx, scope); } if self.tl_state.is_none() { @@ -1296,7 +1297,7 @@ impl RoomScreen { TimelineUpdate::PaginationError { error, direction } => { error!("Pagination error ({direction}) in room \"{}\", {}: {error:?}", self.room_name, tl.room_id); enqueue_popup_notification(PopupItem { - message: utils::stringify_pagination_error(&error, &self.room_name), + message: utils::stringify_pagination_error(&error, &self.room_name.to_string()), auto_dismissal_duration: None, kind: PopupKind::Error, }); @@ -1458,7 +1459,7 @@ impl RoomScreen { }, room_id: self.room_id.clone().unwrap(), }, - room_name: self.room_name.clone(), + room_name: self.room_name.to_string(), // TODO: use the extra `via` parameters room_member: None, }, @@ -1915,7 +1916,6 @@ impl RoomScreen { // and search our locally-known timeline history for the replied-to message. } self.redraw(cx); - } /// Shows the user profile sliding pane with the given avatar info. @@ -2044,7 +2044,7 @@ impl RoomScreen { // show/hide UI elements based on the user's permissions. // 2. Get the list of members in this room (from the SDK's local cache). // 3. Subscribe to our own user's read receipts so that we can update the - // read marker and properly send read receipts while scrolling through the timeline. + // read marker and properly send read receipts while scrolling through the timeline. // 4. Subscribe to typing notices again, now that the room is being shown. if self.is_loaded { submit_async_request(MatrixRequest::GetRoomPowerLevels { @@ -2056,7 +2056,7 @@ impl RoomScreen { // Fetch from the local cache, as we already requested to sync // the room members from the homeserver above. local_only: true, - }); + }); submit_async_request(MatrixRequest::SubscribeToTypingNotices { room_id: room_id.clone(), subscribe: true, @@ -2164,11 +2164,11 @@ impl RoomScreen { } /// Sets this `RoomScreen` widget to display the timeline for the given room. - pub fn set_displayed_room>>( + pub fn set_displayed_room( &mut self, cx: &mut Cx, room_id: OwnedRoomId, - room_name: S, + room_name: RoomDisplayName, ) { // If the room is already being displayed, then do nothing. if self.room_id.as_ref().is_some_and(|id| id == &room_id) { return; } @@ -2176,7 +2176,7 @@ impl RoomScreen { self.hide_timeline(); // Reset the the state of the inner loading pane. self.loading_pane(ids!(loading_pane)).take_state(); - self.room_name = room_name_or_id(room_name.into(), &room_id); + self.room_name = room_name; self.room_id = Some(room_id.clone()); // We initially tell every MentionableTextInput widget that the current user @@ -2291,11 +2291,11 @@ impl RoomScreen { impl RoomScreenRef { /// See [`RoomScreen::set_displayed_room()`]. - pub fn set_displayed_room>>( + pub fn set_displayed_room( &self, cx: &mut Cx, room_id: OwnedRoomId, - room_name: S, + room_name: RoomDisplayName, ) { let Some(mut inner) = self.borrow_mut() else { return }; inner.set_displayed_room(cx, room_id, room_name); @@ -2308,7 +2308,7 @@ pub struct RoomScreenProps { pub room_screen_widget_uid: WidgetUid, pub room_id: OwnedRoomId, pub room_members: Option>>, - pub room_display_name: Option, + pub room_display_name: Option, pub room_avatar_url: Option, } diff --git a/src/home/rooms_list.rs b/src/home/rooms_list.rs index c2f5142e..1eae8930 100644 --- a/src/home/rooms_list.rs +++ b/src/home/rooms_list.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use crossbeam_queue::SegQueue; use makepad_widgets::*; -use matrix_sdk::{ruma::{events::tag::Tags, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedRoomId, OwnedUserId}, RoomState}; +use matrix_sdk::{RoomDisplayName, ruma::{events::tag::Tags, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedRoomId, OwnedUserId}, RoomState}; use crate::{ app::{AppState, SelectedRoom}, room::{FetchedRoomAvatar, room_display_filter::{RoomDisplayFilter, RoomDisplayFilterBuilder, RoomFilterCriteria, SortFn}}, shared::{collapsible_header::{CollapsibleHeaderAction, CollapsibleHeaderWidgetRefExt, HeaderCategory}, jump_to_bottom_button::UnreadMessageCount, popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, room_filter_input_bar::RoomFilterAction}, sliding_sync::{MatrixRequest, PaginationDirection, submit_async_request}, utils::room_name_or_id }; @@ -130,7 +130,7 @@ pub enum RoomsListUpdate { /// Update the displayable name for the given room. UpdateRoomName { room_id: OwnedRoomId, - new_room_name: Option, + new_room_name: RoomDisplayName, }, /// Update the avatar (image) for the given room. UpdateRoomAvatar { @@ -184,7 +184,7 @@ pub enum RoomsListAction { /// to a `RoomScreen` to display now-joined room. InviteAccepted { room_id: OwnedRoomId, - room_name: Option, + room_name: RoomDisplayName, }, None, } @@ -199,7 +199,7 @@ pub struct JoinedRoomInfo { /// The matrix ID of this room. pub room_id: OwnedRoomId, /// The displayable name of this room, if known. - pub room_name: Option, + pub room_name: RoomDisplayName, /// The number of unread messages in this room. pub num_unread_messages: u64, /// The number of unread mentions in this room. @@ -238,7 +238,7 @@ pub struct InvitedRoomInfo { /// The matrix ID of this room. pub room_id: OwnedRoomId, /// The displayable name of this room, if known. - pub room_name: Option, + pub room_name: RoomDisplayName, /// The canonical alias for this room, if any. pub canonical_alias: Option, /// The alternative aliases for this room, if any. @@ -401,7 +401,6 @@ impl RoomsList { RoomsListUpdate::AddJoinedRoom(joined_room) => { let room_id = joined_room.room_id.clone(); let is_direct = joined_room.is_direct; - let room_name = joined_room.room_name.clone(); let should_display = (self.display_filter)(&joined_room); let replaced = self.all_joined_rooms.insert(room_id.clone(), joined_room); if replaced.is_none() { @@ -427,10 +426,17 @@ impl RoomsList { self.displayed_invited_rooms.iter() .position(|r| r == &room_id) .map(|index| self.displayed_invited_rooms.remove(index)); + let new_room_name = self.all_joined_rooms + .get(&room_id) + .map(|room| room.room_name.clone()) + .unwrap_or(RoomDisplayName::Empty); cx.widget_action( self.widget_uid(), &scope.path, - RoomsListAction::InviteAccepted { room_id, room_name } + RoomsListAction::InviteAccepted { + room_id: room_id.clone(), + room_name: new_room_name, + } ); } self.update_status_rooms_count(); @@ -462,9 +468,11 @@ impl RoomsList { } } RoomsListUpdate::UpdateRoomName { room_id, new_room_name } => { + // Try to update joined room first if let Some(room) = self.all_joined_rooms.get_mut(&room_id) { let was_displayed = (self.display_filter)(room); - room.room_name = new_room_name; + // Keep the full RoomDisplayName to preserve EmptyWas semantics + room.room_name = new_room_name.clone(); let should_display = (self.display_filter)(room); match (was_displayed, should_display) { // No need to update the displayed rooms list. @@ -484,14 +492,37 @@ impl RoomsList { // Room was not displayed but should now be displayed. (false, true) => { if room.is_direct { - self.displayed_direct_rooms.push(room_id); + self.displayed_direct_rooms.push(room_id.clone()); } else { - self.displayed_regular_rooms.push(room_id); + self.displayed_regular_rooms.push(room_id.clone()); } } } - } else { - error!("Error: couldn't find room {room_id} to update room name"); + } + // If not a joined room, try to update invited room + else { + let invited_rooms_ref = get_invited_rooms(cx); + let mut invited_rooms = invited_rooms_ref.borrow_mut(); + if let Some(invited_room) = invited_rooms.get_mut(&room_id) { + let was_displayed = (self.display_filter)(invited_room); + invited_room.room_name = new_room_name; + let should_display = (self.display_filter)(invited_room); + match (was_displayed, should_display) { + (true, true) | (false, false) => { } + (true, false) => { + self.displayed_invited_rooms.iter() + .position(|r| r == &room_id) + .map(|index| self.displayed_invited_rooms.remove(index)); + } + (false, true) => { + if !self.displayed_invited_rooms.contains(&room_id) { + self.displayed_invited_rooms.push(room_id.clone()); + } + } + } + } else { + warning!("Warning: couldn't find invited room {} to update room name", room_id); + } } } RoomsListUpdate::UpdateIsDirect { room_id, is_direct } => { @@ -499,9 +530,10 @@ impl RoomsList { if room.is_direct == is_direct { continue; } + let room_name_text = room_name_or_id(&room.room_name, &room.room_id); enqueue_popup_notification(PopupItem { message: format!("{} was changed from {} to {}.", - room_name_or_id(room.room_name.as_ref(), &room_id), + room_name_text, if room.is_direct { "direct" } else { "regular" }, if is_direct { "direct" } else { "regular" } ), @@ -848,12 +880,12 @@ impl Widget for RoomsList { let new_selected_room = if let Some(jr) = self.all_joined_rooms.get(&clicked_room_id) { SelectedRoom::JoinedRoom { room_id: jr.room_id.clone().into(), - room_name: jr.room_name.clone(), + room_name: jr.room_name.clone().into(), } } else if let Some(ir) = self.invited_rooms.borrow().get(&clicked_room_id) { SelectedRoom::InvitedRoom { room_id: ir.room_id.to_owned().into(), - room_name: ir.room_name.clone(), + room_name: ir.room_name.clone().into(), } } else { error!("BUG: couldn't find clicked room details for room {clicked_room_id}"); diff --git a/src/home/rooms_list_entry.rs b/src/home/rooms_list_entry.rs index 697d1ee2..5f351840 100644 --- a/src/home/rooms_list_entry.rs +++ b/src/home/rooms_list_entry.rs @@ -5,7 +5,7 @@ use crate::{ room::FetchedRoomAvatar, shared::{ avatar::AvatarWidgetExt, html_or_plaintext::HtmlOrPlaintextWidgetExt, unread_badge::UnreadBadgeWidgetExt as _, - }, utils::{self, relative_format} + }, utils::{self, relative_format, room_name_or_id} }; use super::rooms_list::{InvitedRoomInfo, InviterInfo, JoinedRoomInfo, RoomsListScopeProps}; @@ -297,9 +297,8 @@ impl RoomsListEntryContent { cx: &mut Cx, room_info: &JoinedRoomInfo, ) { - if let Some(ref name) = room_info.room_name { - self.view.label(ids!(room_name)).set_text(cx, name); - } + let display_name = room_name_or_id(&room_info.room_name, &room_info.room_id); + self.view.label(ids!(room_name)).set_text(cx, &display_name); if let Some((ts, msg)) = room_info.latest.as_ref() { if let Some(human_readable_date) = relative_format(*ts) { self.view @@ -325,11 +324,8 @@ impl RoomsListEntryContent { cx: &mut Cx, room_info: &InvitedRoomInfo, ) { - self.view.label(ids!(room_name)).set_text( - cx, - room_info.room_name.as_deref() - .unwrap_or("Invite to unnamed room"), - ); + let display_name = room_name_or_id(&room_info.room_name, &room_info.room_id); + self.view.label(ids!(room_name)).set_text(cx, &display_name); // Hide the timestamp field, and use the latest message field to show the inviter. self.view.label(ids!(timestamp)).set_text(cx, ""); let inviter_string = match &room_info.inviter_info { diff --git a/src/join_leave_room_modal.rs b/src/join_leave_room_modal.rs index 24760439..cf6e0e5f 100644 --- a/src/join_leave_room_modal.rs +++ b/src/join_leave_room_modal.rs @@ -3,7 +3,7 @@ //! Also used as a confirmation dialog for accepting or rejecting room invites. use makepad_widgets::*; -use matrix_sdk::ruma::OwnedRoomId; +use matrix_sdk::{RoomDisplayName, ruma::OwnedRoomId}; use crate::{home::invite_screen::{InviteDetails, JoinRoomResultAction, LeaveRoomResultAction}, room::BasicRoomDetails, shared::popup_list::{enqueue_popup_notification, PopupItem, PopupKind}, sliding_sync::{submit_async_request, MatrixRequest}, utils::{self, room_name_or_id}}; @@ -173,12 +173,12 @@ impl JoinLeaveModalKind { } } - pub fn room_name(&self) -> Option<&str> { + pub fn room_name(&self) -> &RoomDisplayName { match self { - JoinLeaveModalKind::AcceptInvite(invite) => invite.room_name.as_deref(), - JoinLeaveModalKind::RejectInvite(invite) => invite.room_name.as_deref(), - JoinLeaveModalKind::JoinRoom(room) => room.room_name.as_deref(), - JoinLeaveModalKind::LeaveRoom(room) => room.room_name.as_deref(), + JoinLeaveModalKind::AcceptInvite(invite) => &invite.room_name, + JoinLeaveModalKind::RejectInvite(invite) => &invite.room_name, + JoinLeaveModalKind::JoinRoom(room) => &room.room_name, + JoinLeaveModalKind::LeaveRoom(room) => &room.room_name, } } } @@ -249,7 +249,7 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { description = format!( "Accepting an invitation to join \"{}\".\n\n\ Waiting for confirmation from the homeserver...", - room_name_or_id(invite.room_name.as_ref(), &invite.room_id), + room_name_or_id(&invite.room_name, &invite.room_id), ); accept_button_text = "Joining..."; submit_async_request(MatrixRequest::JoinRoom { @@ -261,7 +261,7 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { description = format!( "Rejecting an invitation to join \"{}\".\n\n\ Waiting for confirmation from the homeserver...", - room_name_or_id(invite.room_name.as_ref(), &invite.room_id), + room_name_or_id(&invite.room_name, &invite.room_id), ); accept_button_text = "Rejecting..."; submit_async_request(MatrixRequest::LeaveRoom { @@ -273,7 +273,7 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { description = format!( "Joining \"{}\".\n\n\ Waiting for confirmation from the homeserver...", - room_name_or_id(room.room_name.as_ref(), &room.room_id), + room_name_or_id(&room.room_name, &room.room_id), ); accept_button_text = "Joining..."; submit_async_request(MatrixRequest::JoinRoom { @@ -285,7 +285,7 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { description = format!( "Leaving \"{}\".\n\n\ Waiting for confirmation from the homeserver...", - room_name_or_id(room.room_name.as_ref(), &room.room_id), + room_name_or_id(&room.room_name, &room.room_id), ); accept_button_text = "Leaving..."; submit_async_request(MatrixRequest::LeaveRoom { @@ -325,7 +325,8 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { Some(JoinRoomResultAction::Failed { room_id, error }) if room_id == kind.room_id() => { self.view.label(ids!(title)).set_text(cx, "Error joining room!"); let was_invite = matches!(kind, JoinLeaveModalKind::AcceptInvite(_) | JoinLeaveModalKind::RejectInvite(_)); - let msg = utils::stringify_join_leave_error(error, kind.room_name(), true, was_invite); + let room_label = room_name_or_id(kind.room_name(), room_id); + let msg = utils::stringify_join_leave_error(error, Some(room_label.as_str()), true, was_invite); self.view.label(ids!(description)).set_text(cx, &msg); enqueue_popup_notification(PopupItem { message: msg, @@ -376,15 +377,16 @@ impl WidgetMatchEvent for JoinLeaveRoomModal { let title: &str; let description: String; let popup_msg: String; + let room_label = room_name_or_id(kind.room_name(), room_id); match kind { JoinLeaveModalKind::AcceptInvite(_) | JoinLeaveModalKind::RejectInvite(_) => { title = "Error rejecting invite!"; - description = utils::stringify_join_leave_error(error, kind.room_name(), false, true); + description = utils::stringify_join_leave_error(error, Some(room_label.as_str()), false, true); popup_msg = "Failed to reject invite.".into(); } JoinLeaveModalKind::JoinRoom(_) | JoinLeaveModalKind::LeaveRoom(_) => { title = "Error leaving room!"; - description = utils::stringify_join_leave_error(error, kind.room_name(), false, false); + description = utils::stringify_join_leave_error(error, Some(room_label.as_str()), false, false); popup_msg = "Failed to leave room.".into(); } } @@ -430,7 +432,7 @@ impl JoinLeaveRoomModal { title = "Accept this invite?"; description = format!( "Are you sure you want to accept this invite to join \"{}\"?", - room_name_or_id(invite.room_name.as_ref(), &invite.room_id), + room_name_or_id(&invite.room_name, &invite.room_id), ); tip_button = "Join"; } @@ -440,7 +442,7 @@ impl JoinLeaveRoomModal { "Are you sure you want to reject this invite to join \"{}\"?\n\n\ If this is a private room, you won't be able to join this room \ without being re-invited to it.", - room_name_or_id(invite.room_name.as_ref(), &invite.room_id) + room_name_or_id(&invite.room_name, &invite.room_id) ); tip_button = "Reject"; } @@ -448,7 +450,7 @@ impl JoinLeaveRoomModal { title = "Join this room?"; description = format!( "Are you sure you want to join \"{}\"?", - room_name_or_id(room.room_name.as_ref(), &room.room_id) + room_name_or_id(&room.room_name, &room.room_id) ); tip_button = "Join"; } @@ -458,7 +460,7 @@ impl JoinLeaveRoomModal { "Are you sure you want to leave \"{}\"?\n\n\ If this is a private room, you won't be able to join this room \ without being re-invited to it.", - room_name_or_id(room.room_name.as_ref(), &room.room_id) + room_name_or_id(&room.room_name, &room.room_id) ); tip_button = "Leave"; } diff --git a/src/room/mod.rs b/src/room/mod.rs index 77181c45..c2da8150 100644 --- a/src/room/mod.rs +++ b/src/room/mod.rs @@ -1,6 +1,6 @@ use std::{ops::Deref, sync::Arc}; use makepad_widgets::Cx; -use matrix_sdk::{room_preview::RoomPreview, ruma::OwnedRoomId, SuccessorRoom}; +use matrix_sdk::{room_preview::RoomPreview, ruma::OwnedRoomId, RoomDisplayName, SuccessorRoom}; use crate::utils::avatar_from_room_name; @@ -22,7 +22,7 @@ pub fn live_design(cx: &mut Cx) { #[derive(Clone, Debug)] pub struct BasicRoomDetails { pub room_id: OwnedRoomId, - pub room_name: Option, + pub room_name: RoomDisplayName, pub room_avatar: FetchedRoomAvatar, } impl From<&SuccessorRoom> for BasicRoomDetails { @@ -30,15 +30,18 @@ impl From<&SuccessorRoom> for BasicRoomDetails { BasicRoomDetails { room_id: successor_room.room_id.clone(), room_avatar: avatar_from_room_name(None), - room_name: None, + room_name: RoomDisplayName::Empty, } } } impl From<&FetchedRoomPreview> for BasicRoomDetails { fn from(frp: &FetchedRoomPreview) -> Self { + let room_name = frp.name.clone() + .map(RoomDisplayName::Named) + .unwrap_or(RoomDisplayName::Empty); BasicRoomDetails { room_id: frp.room_id.clone(), - room_name: frp.name.clone(), + room_name, room_avatar: frp.room_avatar.clone(), } } diff --git a/src/room/room_display_filter.rs b/src/room/room_display_filter.rs index fe9b7efa..b33c2afe 100644 --- a/src/room/room_display_filter.rs +++ b/src/room/room_display_filter.rs @@ -7,7 +7,7 @@ use matrix_sdk::ruma::{ OwnedRoomAliasId, RoomAliasId, RoomId, }; -use crate::home::{rooms_list::{InvitedRoomInfo, JoinedRoomInfo}, spaces_bar::JoinedSpaceInfo}; +use crate::{home::rooms_list::{InvitedRoomInfo, JoinedRoomInfo}, home::spaces_bar::JoinedSpaceInfo, utils::room_name_or_id}; static EMPTY_TAGS: Tags = BTreeMap::new(); @@ -29,7 +29,7 @@ impl FilterableRoom for JoinedRoomInfo { } fn room_name(&self) -> Cow<'_, str> { - self.room_name.as_deref().map(Into::into).unwrap_or_default() + Cow::Owned(room_name_or_id(&self.room_name, &self.room_id)) } fn unread_mentions(&self) -> u64 { @@ -63,7 +63,7 @@ impl FilterableRoom for InvitedRoomInfo { } fn room_name(&self) -> Cow<'_, str> { - self.room_name.as_deref().map(Into::into).unwrap_or_default() + Cow::Owned(room_name_or_id(&self.room_name, &self.room_id)) } fn unread_mentions(&self) -> u64 { diff --git a/src/shared/mentionable_text_input.rs b/src/shared/mentionable_text_input.rs index a36fac7a..81d5d952 100644 --- a/src/shared/mentionable_text_input.rs +++ b/src/shared/mentionable_text_input.rs @@ -489,6 +489,7 @@ impl MentionableTextInput { // Get room avatar fallback text from room display name let room_name_first_char = room_props.room_display_name .as_ref() + .map(|name| name.to_string()) .and_then(|name| name.graphemes(true).next().map(|s| s.to_uppercase())) .filter(|s| s != "@" && s.chars().all(|c| c.is_alphabetic())) .unwrap_or_else(|| "R".to_string()); diff --git a/src/shared/restore_status_view.rs b/src/shared/restore_status_view.rs index f3c48b68..6903b398 100644 --- a/src/shared/restore_status_view.rs +++ b/src/shared/restore_status_view.rs @@ -6,6 +6,9 @@ //! the current room no longer exists. use makepad_widgets::*; +use matrix_sdk::ruma::RoomId; + +use crate::utils::RoomName; live_design! { use link::theme::*; @@ -90,16 +93,28 @@ impl RestoreStatusViewRef { /// is still being loaded from the homeserver. /// /// The `room_name` parameter is used to fill in the room name in the error message. - pub fn set_content(&self, cx: &mut Cx, all_rooms_loaded: bool, room_name: &str) { + pub fn set_content( + &self, + cx: &mut Cx, + all_rooms_loaded: bool, + room_name: RoomName, + room_id: Option<&RoomId>, + ) { let Some(inner) = self.borrow() else { return }; let restore_status_spinner = inner.view.view(ids!(restore_status_spinner)); let restore_status_label = inner.view.label(ids!(restore_status_label)); if all_rooms_loaded { restore_status_spinner.set_visible(cx, false); + let display_name = match room_id { + Some(id) => room_name.display_with_fallback(id), + None => room_name.to_string(), + }; restore_status_label.set_text( cx, - &format!("Room \"{room_name}\" was not found in the homeserver's list \ - of all rooms.\n\nYou may close this screen.") + &format!( + "Room \"{display_name}\" was not found in the homeserver's list \ + of all rooms.\n\nYou may close this screen." + ), ); } else { restore_status_spinner.set_visible(cx, true); @@ -110,4 +125,3 @@ impl RestoreStatusViewRef { } } } - diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index 851a033d..043dc74a 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -38,7 +38,7 @@ use crate::{ html_or_plaintext::MatrixLinkPillState, jump_to_bottom_button::UnreadMessageCount, popup_list::{PopupItem, PopupKind, enqueue_popup_notification} - }, space_service_sync::space_service_loop, utils::{self, AVATAR_THUMBNAIL_FORMAT, avatar_from_room_name}, verification::add_verification_event_handlers_and_sync_client + }, space_service_sync::space_service_loop, utils::{self, AVATAR_THUMBNAIL_FORMAT, RoomName, avatar_from_room_name}, verification::add_verification_event_handlers_and_sync_client }; #[derive(Parser, Debug, Default)] @@ -2270,9 +2270,15 @@ async fn update_room( } if old_room.display_name != new_room.display_name { log!("Updating room {} name: {:?} --> {:?}", new_room_id, old_room.display_name, new_room.display_name); + + let new_name = new_room + .display_name + .clone() + .unwrap_or(RoomDisplayName::Empty); + enqueue_rooms_list_update(RoomsListUpdate::UpdateRoomName { room_id: new_room_id.clone(), - new_room_name: new_room.display_name.as_ref().map(|n| n.to_string()), + new_room_name: new_name, }); } @@ -2431,8 +2437,11 @@ async fn add_new_room( let latest = latest_event.as_ref().map( |ev| get_latest_event_details(ev, &new_room.room_id) ); - let room_name = new_room.display_name.as_ref().map(|n| n.to_string()); - let room_avatar = room_avatar(&new_room.room, room_name.as_deref()).await; + let room_display_name = new_room + .display_name + .clone() + .unwrap_or(RoomDisplayName::Empty); + let room_avatar = room_avatar(&new_room.room, RoomName::from(room_display_name.clone())).await; let inviter_info = if let Some(inviter) = invite_details.and_then(|d| d.inviter) { Some(InviterInfo { @@ -2450,7 +2459,7 @@ async fn add_new_room( }; rooms_list::enqueue_rooms_list_update(RoomsListUpdate::AddInvitedRoom(InvitedRoomInfo { room_id: new_room.room_id.clone(), - room_name, + room_name: room_display_name, inviter_info, room_avatar, canonical_alias: new_room.room.canonical_alias(), @@ -2509,7 +2518,11 @@ async fn add_new_room( // We need to add the room to the `ALL_JOINED_ROOMS` list before we can // send the `AddJoinedRoom` update to the UI, because the UI might immediately // issue a `MatrixRequest` that relies on that room being in `ALL_JOINED_ROOMS`. - let room_name = new_room.display_name.as_ref().map(|n| n.to_string()); + let room_display_name = new_room + .display_name + .clone() + .unwrap_or(RoomDisplayName::Empty); + let room_name_for_avatar = RoomName::from(room_display_name.clone()); rooms_list::enqueue_rooms_list_update(RoomsListUpdate::AddJoinedRoom(JoinedRoomInfo { room_id: new_room.room_id.clone(), latest, @@ -2517,8 +2530,8 @@ async fn add_new_room( num_unread_messages: new_room.num_unread_messages, num_unread_mentions: new_room.num_unread_mentions, // start with a basic text avatar; the avatar image will be fetched asynchronously below. - avatar: avatar_from_room_name(room_name.as_deref()), - room_name, + avatar: avatar_from_room_name(room_name_for_avatar.as_str()), + room_name: room_display_name, canonical_alias: new_room.room.canonical_alias(), alt_aliases: new_room.room.alt_aliases(), has_been_paginated: false, @@ -3145,10 +3158,10 @@ async fn update_latest_event(room: &Room) { /// Spawn a new async task to fetch the room's new avatar. fn spawn_fetch_room_avatar(room: &RoomListServiceRoomInfo) { let room_id = room.room_id.clone(); - let room_name = room.display_name.as_ref().map(|n| n.to_string()); + let room_name = room.display_name.clone().map(RoomName::from).unwrap_or_default(); let inner_room = room.room.clone(); Handle::current().spawn(async move { - let avatar = room_avatar(&inner_room, room_name.as_deref()).await; + let avatar = room_avatar(&inner_room, room_name).await; rooms_list::enqueue_rooms_list_update(RoomsListUpdate::UpdateRoomAvatar { room_id, avatar, @@ -3158,7 +3171,7 @@ fn spawn_fetch_room_avatar(room: &RoomListServiceRoomInfo) { /// Fetches and returns the avatar image for the given room (if one exists), /// otherwise returns a text avatar string of the first character of the room name. -async fn room_avatar(room: &Room, room_name: Option<&str>) -> FetchedRoomAvatar { +async fn room_avatar(room: &Room, room_name: RoomName) -> FetchedRoomAvatar { match room.avatar(AVATAR_THUMBNAIL_FORMAT.into()).await { Ok(Some(avatar)) => FetchedRoomAvatar::Image(avatar.into()), _ => { @@ -3171,7 +3184,7 @@ async fn room_avatar(room: &Room, room_name: Option<&str>) -> FetchedRoomAvatar } } } - utils::avatar_from_room_name(room_name) + utils::avatar_from_room_name(room_name.as_str()) } } } diff --git a/src/utils.rs b/src/utils.rs index 7135fcd3..292e120f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,7 +7,7 @@ use url::Url; use unicode_segmentation::UnicodeSegmentation; use chrono::{DateTime, Duration, Local, TimeZone}; use makepad_widgets::{error, image_cache::ImageError, makepad_micro_serde::{DeRon, DeRonErr, DeRonState, SerRon, SerRonState}, Cx, Event, ImageRef}; -use matrix_sdk::{media::{MediaFormat, MediaThumbnailSettings}, ruma::{api::client::media::get_content_thumbnail::v3::Method, MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId}}; +use matrix_sdk::{media::{MediaFormat, MediaThumbnailSettings}, ruma::{api::client::media::get_content_thumbnail::v3::Method, MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId}, RoomDisplayName}; use matrix_sdk_ui::timeline::{EventTimelineItem, PaginationError, TimelineDetails}; use crate::{room::FetchedRoomAvatar, sliding_sync::{submit_async_request, MatrixRequest}}; @@ -240,13 +240,10 @@ pub fn stringify_pagination_error( /// Returns a string representation of the room name or ID. pub fn room_name_or_id( - room_name: Option>, + room_name: impl IntoRoomName, room_id: impl AsRef, ) -> String { - room_name.map_or_else( - || format!("Room ID {}", room_id.as_ref()), - |name| name.into(), - ) + room_name.into_room_name().display_with_fallback(room_id) } /// Formats a given Unix timestamp in milliseconds into a relative human-readable date. @@ -726,6 +723,166 @@ impl Display for OwnedRoomIdRon { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoomName(pub RoomDisplayName); + +impl RoomName { + pub fn into_inner(self) -> RoomDisplayName { + self.0 + } + + pub fn as_str(&self) -> Option<&str> { + room_display_name_str(&self.0) + } + + pub fn display_with_fallback(&self, room_id: impl AsRef) -> String { + match &self.0 { + RoomDisplayName::Empty => format!("Unnamed (ID {})", room_id.as_ref()), + RoomDisplayName::EmptyWas(name) => format!("Empty Room (was \"{name}\")"), + other => other.to_string(), + } + } +} + +impl Default for RoomName { + fn default() -> Self { + Self(RoomDisplayName::Empty) + } +} + +impl std::ops::Deref for RoomName { + type Target = RoomDisplayName; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::borrow::Borrow for RoomName { + fn borrow(&self) -> &RoomDisplayName { + &self.0 + } +} + +impl AsRef for RoomName { + fn as_ref(&self) -> &RoomDisplayName { + &self.0 + } +} + +impl std::fmt::Display for RoomName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +impl From for RoomName { + fn from(value: RoomDisplayName) -> Self { + Self(value) + } +} + +impl From<&RoomDisplayName> for RoomName { + fn from(value: &RoomDisplayName) -> Self { + Self(value.clone()) + } +} + +impl From for RoomDisplayName { + fn from(value: RoomName) -> Self { + value.0 + } +} + +pub fn room_display_name_str(display_name: &RoomDisplayName) -> Option<&str> { + match display_name { + RoomDisplayName::Empty => None, + RoomDisplayName::EmptyWas(name) + | RoomDisplayName::Named(name) + | RoomDisplayName::Aliased(name) + | RoomDisplayName::Calculated(name) => Some(name.as_str()), + } +} + +pub trait IntoRoomName { + fn into_room_name(self) -> RoomName; +} + +impl IntoRoomName for RoomName { + fn into_room_name(self) -> RoomName { + self + } +} + +impl IntoRoomName for &RoomName { + fn into_room_name(self) -> RoomName { + self.clone() + } +} + +impl IntoRoomName for RoomDisplayName { + fn into_room_name(self) -> RoomName { + RoomName(self) + } +} + +impl IntoRoomName for &RoomDisplayName { + fn into_room_name(self) -> RoomName { + RoomName(self.clone()) + } +} + +impl IntoRoomName for String { + fn into_room_name(self) -> RoomName { + RoomName(RoomDisplayName::Named(self)) + } +} + +impl IntoRoomName for &str { + fn into_room_name(self) -> RoomName { + RoomName(RoomDisplayName::Named(self.to_owned())) + } +} + +impl<'a> IntoRoomName for Cow<'a, str> { + fn into_room_name(self) -> RoomName { + RoomName(RoomDisplayName::Named(self.into_owned())) + } +} + +impl IntoRoomName for Option +where + T: IntoRoomName, +{ + fn into_room_name(self) -> RoomName { + self.map(IntoRoomName::into_room_name).unwrap_or_default() + } +} + +impl SerRon for RoomName { + fn ser_ron(&self, d: usize, s: &mut SerRonState) { + let serialized = serde_json::to_string(&self.0).unwrap_or_else(|e| { + error!("Failed to serialize RoomDisplayName to ron: {e}"); + serde_json::to_string(&RoomDisplayName::Empty) + .expect("serialization of Empty must succeed") + }); + serialized.ser_ron(d, s); + } +} + +impl DeRon for RoomName { + fn de_ron(s: &mut DeRonState, i: &mut Chars) -> Result { + let serialized = String::de_ron(s, i)?; + serde_json::from_str::(&serialized) + .map(RoomName) + .map_err(|e| DeRonErr { + msg: e.to_string(), + line: s.line, + col: s.col, + }) + } +} + /// Returns a text avatar string containing the first character of the room name. /// /// Skips the first character if it is a `#` or `!`, the sigils used for Room aliases and Room IDs.