diff --git a/src/session/view/content/room_details/room_upgrade_dialog.rs b/src/session/view/content/room_details/room_upgrade_dialog.rs index d956d1b3..37228ede 100644 --- a/src/session/view/content/room_details/room_upgrade_dialog.rs +++ b/src/session/view/content/room_details/room_upgrade_dialog.rs @@ -2,24 +2,23 @@ use std::{cmp::Ordering, str::FromStr}; use adw::prelude::*; use gettextrs::gettext; -use gtk::{gio, glib, subclass::prelude::*}; +use gtk::{gio, glib, pango, subclass::prelude::*}; use ruma::{ api::client::discovery::get_capabilities::{RoomVersionStability, RoomVersionsCapability}, RoomVersionId, }; - -use crate::gettext_f; +use tracing::error; /// Show a dialog to confirm the room upgrade and select a room version. /// /// Returns the selected room version, or `None` if the user didn't confirm. -pub async fn confirm_room_upgrade( +pub(crate) async fn confirm_room_upgrade( capability: RoomVersionsCapability, parent: &impl IsA, ) -> Option { - // Build the model. + // Build the lists. let default = capability.default; - let mut list = capability + let (mut stable_list, mut experimental_list) = capability .available .into_iter() .map(|(id, stability)| { @@ -32,34 +31,71 @@ pub async fn confirm_room_upgrade( RoomVersion::new(id, stability) }) - .collect::>(); + .partition::, _>(|version| *version.stability() == RoomVersionStability::Stable); - // Correctly sort numbers (string comparison will sort `1, 10, 2`, we want `1, - // 2, 10`). - list.sort_unstable_by(|a, b| { - match ( - i64::from_str(a.id().as_str()), - i64::from_str(b.id().as_str()), - ) { - (Ok(a), Ok(b)) => a.cmp(&b), - (Ok(_), _) => Ordering::Less, - (_, Ok(_)) => Ordering::Greater, - _ => a.id().cmp(b.id()), - } - }); + stable_list.sort_unstable_by(RoomVersion::cmp_ids); + experimental_list.sort_unstable_by(RoomVersion::cmp_ids); - let default_pos = list + let default_pos = stable_list .iter() .position(|v| *v.id() == default) .unwrap_or_default(); - let model = list.into_iter().collect::(); + + // Construct the list models for the combo row. + let stable_model = stable_list.into_iter().collect::(); + let experimental_model = experimental_list.into_iter().collect::(); + + let model_list = gio::ListStore::new::(); + model_list.append(&stable_model); + model_list.append(&experimental_model); + let flatten_model = gtk::FlattenListModel::new(Some(model_list)); + + // Construct the header factory to separate stable from experimental versions. + let header_factory = gtk::SignalListItemFactory::new(); + header_factory.connect_setup(|_, header| { + let Some(header) = header.downcast_ref::() else { + error!("List item factory did not receive a list header: {header:?}"); + return; + }; + + let label = gtk::Label::builder() + .margin_start(12) + .xalign(0.0) + .ellipsize(pango::EllipsizeMode::End) + .css_classes(["heading"]) + .build(); + header.set_child(Some(&label)); + }); + header_factory.connect_bind(|_, header| { + let Some(header) = header.downcast_ref::() else { + error!("List item factory did not receive a list header: {header:?}"); + return; + }; + let Some(label) = header.child().and_downcast::() else { + error!("List header does not have a child GtkLabel"); + return; + }; + let Some(version) = header.item().and_downcast::() else { + error!("List header does not have a RoomVersion item"); + return; + }; + + let text = match version.stability() { + // Translators: As in 'Stable version'. + RoomVersionStability::Stable => gettext("Stable"), + // Translators: As in 'Experimental version'. + _ => gettext("Experimental"), + }; + label.set_label(&text); + }); // Add an entry for the optional reason. let version_combo = adw::ComboRow::builder() .title(gettext("Version")) .selectable(false) - .expression(RoomVersion::this_expression("display-string")) - .model(&model) + .expression(RoomVersion::this_expression("id-string")) + .header_factory(&header_factory) + .model(&flatten_model) .selected(default_pos.try_into().unwrap_or(u32::MAX)) .build(); let list_box = gtk::ListBox::builder() @@ -102,12 +138,12 @@ mod imp { #[properties(wrapper_type = super::RoomVersion)] pub struct RoomVersion { /// The ID of the version. - pub id: OnceCell, + id: OnceCell, + /// The ID of the version as a string. + #[property(get = Self::id_string)] + id_string: PhantomData, /// The stability of the version. - pub stability: OnceCell, - /// The string used to display this version. - #[property(get = Self::display_string)] - display_string: PhantomData, + stability: OnceCell, } #[glib::object_subclass] @@ -120,28 +156,31 @@ mod imp { impl ObjectImpl for RoomVersion {} impl RoomVersion { + /// Set the ID of this version. + pub(super) fn set_id(&self, id: RoomVersionId) { + self.id.set(id).expect("id is uninitialized"); + } + /// The ID of this version. pub(super) fn id(&self) -> &RoomVersionId { self.id.get().expect("id is initialized") } - /// The stability of this version. - fn stability(&self) -> &RoomVersionStability { - self.stability.get().expect("stability is initialized") + /// The ID of this version as a string. + fn id_string(&self) -> String { + self.id().to_string() } - /// The string used to display this version. - fn display_string(&self) -> String { - let id = self.id(); - let stability = self.stability(); + /// Set the stability of this version. + pub(super) fn set_stability(&self, stability: RoomVersionStability) { + self.stability + .set(stability) + .expect("stability is uninitialized"); + } - if *stability == RoomVersionStability::Stable { - id.to_string() - } else { - // Translators: Do NOT translate the content between '{' and '}', this is a - // variable name. - gettext_f("{version} (unstable)", &[("version", id.as_str())]) - } + /// The stability of this version. + pub(super) fn stability(&self) -> &RoomVersionStability { + self.stability.get().expect("stability is initialized") } } } @@ -157,8 +196,8 @@ impl RoomVersion { let obj = glib::Object::new::(); let imp = obj.imp(); - imp.id.set(id).unwrap(); - imp.stability.set(stability).unwrap(); + imp.set_id(id); + imp.set_stability(stability); obj } @@ -167,4 +206,25 @@ impl RoomVersion { pub(crate) fn id(&self) -> &RoomVersionId { self.imp().id() } + + /// The stability of this version. + pub(crate) fn stability(&self) -> &RoomVersionStability { + self.imp().stability() + } + + /// Compare the IDs of the two given `RoomVersion`s. + /// + /// Correctly sorts numbers: string comparison will sort `1, 10, 2`, we want + /// `1, 2, 10`. + fn cmp_ids(a: &RoomVersion, b: &RoomVersion) -> Ordering { + match ( + i64::from_str(a.id().as_str()), + i64::from_str(b.id().as_str()), + ) { + (Ok(a), Ok(b)) => a.cmp(&b), + (Ok(_), _) => Ordering::Less, + (_, Ok(_)) => Ordering::Greater, + _ => a.id().cmp(b.id()), + } + } }