From 49d04959d276bd76cf0640cf93998fae1630f975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 30 Dec 2024 12:45:22 +0100 Subject: [PATCH] room-details: Separate stable and experimental room versions in sections Appending "(unstable)" to the room version does not work well because it might be ellipsized, especially considering that unstable room versions usually have a long name. --- .../room_details/room_upgrade_dialog.rs | 150 ++++++++++++------ 1 file changed, 105 insertions(+), 45 deletions(-) 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()), + } + } }