fractal/session/view/content/room_details/
room_upgrade_dialog.rs1use std::{cmp::Ordering, str::FromStr};
2
3use adw::prelude::*;
4use gettextrs::gettext;
5use gtk::{gio, glib, pango, subclass::prelude::*};
6use ruma::{
7 api::client::discovery::get_capabilities::{RoomVersionStability, RoomVersionsCapability},
8 RoomVersionId,
9};
10use tracing::error;
11
12pub(crate) async fn confirm_room_upgrade(
16 capability: RoomVersionsCapability,
17 parent: &impl IsA<gtk::Widget>,
18) -> Option<RoomVersionId> {
19 let default = capability.default;
21 let (mut stable_list, mut experimental_list) = capability
22 .available
23 .into_iter()
24 .map(|(id, stability)| {
25 let stability = if id == default {
26 RoomVersionStability::Stable
28 } else {
29 stability
30 };
31
32 RoomVersion::new(id, stability)
33 })
34 .partition::<Vec<_>, _>(|version| *version.stability() == RoomVersionStability::Stable);
35
36 stable_list.sort_unstable_by(RoomVersion::cmp_ids);
37 experimental_list.sort_unstable_by(RoomVersion::cmp_ids);
38
39 let default_pos = stable_list
40 .iter()
41 .position(|v| *v.id() == default)
42 .unwrap_or_default();
43
44 let stable_model = stable_list.into_iter().collect::<gio::ListStore>();
46 let experimental_model = experimental_list.into_iter().collect::<gio::ListStore>();
47
48 let model_list = gio::ListStore::new::<gio::ListStore>();
49 model_list.append(&stable_model);
50 model_list.append(&experimental_model);
51 let flatten_model = gtk::FlattenListModel::new(Some(model_list));
52
53 let header_factory = gtk::SignalListItemFactory::new();
55 header_factory.connect_setup(|_, header| {
56 let Some(header) = header.downcast_ref::<gtk::ListHeader>() else {
57 error!("List item factory did not receive a list header: {header:?}");
58 return;
59 };
60
61 let label = gtk::Label::builder()
62 .margin_start(12)
63 .xalign(0.0)
64 .ellipsize(pango::EllipsizeMode::End)
65 .css_classes(["heading"])
66 .build();
67 header.set_child(Some(&label));
68 });
69 header_factory.connect_bind(|_, header| {
70 let Some(header) = header.downcast_ref::<gtk::ListHeader>() else {
71 error!("List item factory did not receive a list header: {header:?}");
72 return;
73 };
74 let Some(label) = header.child().and_downcast::<gtk::Label>() else {
75 error!("List header does not have a child GtkLabel");
76 return;
77 };
78 let Some(version) = header.item().and_downcast::<RoomVersion>() else {
79 error!("List header does not have a RoomVersion item");
80 return;
81 };
82
83 let text = match version.stability() {
84 RoomVersionStability::Stable => gettext("Stable"),
86 _ => gettext("Experimental"),
88 };
89 label.set_label(&text);
90 });
91
92 let version_combo = adw::ComboRow::builder()
94 .title(gettext("Version"))
95 .selectable(false)
96 .expression(RoomVersion::this_expression("id-string"))
97 .header_factory(&header_factory)
98 .model(&flatten_model)
99 .selected(default_pos.try_into().unwrap_or(u32::MAX))
100 .build();
101 let list_box = gtk::ListBox::builder()
102 .css_classes(["boxed-list"])
103 .margin_top(6)
104 .accessible_role(gtk::AccessibleRole::Group)
105 .build();
106 list_box.append(&version_combo);
107
108 let upgrade_dialog = adw::AlertDialog::builder()
110 .default_response("cancel")
111 .heading(gettext("Upgrade Room"))
112 .body(gettext("Upgrading a room to a more recent version allows to benefit from new features from the Matrix specification. It can also be used to reset the room state, which should make the room faster to join. However it should be used sparingly because it can be disruptive, as room members need to join the new room manually."))
113 .extra_child(&list_box)
114 .build();
115 upgrade_dialog.add_responses(&[
116 ("cancel", &gettext("Cancel")),
117 ("upgrade", &gettext("Upgrade")),
119 ]);
120 upgrade_dialog.set_response_appearance("upgrade", adw::ResponseAppearance::Destructive);
121
122 if upgrade_dialog.choose_future(parent).await != "upgrade" {
123 return None;
124 }
125
126 version_combo
127 .selected_item()
128 .and_downcast::<RoomVersion>()
129 .map(|v| v.id().clone())
130}
131
132mod imp {
133 use std::{cell::OnceCell, marker::PhantomData};
134
135 use super::*;
136
137 #[derive(Debug, Default, glib::Properties)]
138 #[properties(wrapper_type = super::RoomVersion)]
139 pub struct RoomVersion {
140 id: OnceCell<RoomVersionId>,
142 #[property(get = Self::id_string)]
144 id_string: PhantomData<String>,
145 stability: OnceCell<RoomVersionStability>,
147 }
148
149 #[glib::object_subclass]
150 impl ObjectSubclass for RoomVersion {
151 const NAME: &'static str = "RoomUpgradeDialogRoomVersion";
152 type Type = super::RoomVersion;
153 }
154
155 #[glib::derived_properties]
156 impl ObjectImpl for RoomVersion {}
157
158 impl RoomVersion {
159 pub(super) fn set_id(&self, id: RoomVersionId) {
161 self.id.set(id).expect("id is uninitialized");
162 }
163
164 pub(super) fn id(&self) -> &RoomVersionId {
166 self.id.get().expect("id is initialized")
167 }
168
169 fn id_string(&self) -> String {
171 self.id().to_string()
172 }
173
174 pub(super) fn set_stability(&self, stability: RoomVersionStability) {
176 self.stability
177 .set(stability)
178 .expect("stability is uninitialized");
179 }
180
181 pub(super) fn stability(&self) -> &RoomVersionStability {
183 self.stability.get().expect("stability is initialized")
184 }
185 }
186}
187
188glib::wrapper! {
189 pub struct RoomVersion(ObjectSubclass<imp::RoomVersion>);
191}
192
193impl RoomVersion {
194 pub fn new(id: RoomVersionId, stability: RoomVersionStability) -> Self {
196 let obj = glib::Object::new::<Self>();
197
198 let imp = obj.imp();
199 imp.set_id(id);
200 imp.set_stability(stability);
201
202 obj
203 }
204
205 pub(crate) fn id(&self) -> &RoomVersionId {
207 self.imp().id()
208 }
209
210 pub(crate) fn stability(&self) -> &RoomVersionStability {
212 self.imp().stability()
213 }
214
215 fn cmp_ids(a: &RoomVersion, b: &RoomVersion) -> Ordering {
220 match (
221 i64::from_str(a.id().as_str()),
222 i64::from_str(b.id().as_str()),
223 ) {
224 (Ok(a), Ok(b)) => a.cmp(&b),
225 (Ok(_), _) => Ordering::Less,
226 (_, Ok(_)) => Ordering::Greater,
227 _ => a.id().cmp(b.id()),
228 }
229 }
230}