fractal/session/view/content/room_details/
mod.rs

1// FIXME: AdwPreferencesWindow is deprecated but we cannot use
2// AdwPreferencesDialog yet because we need to be able to open the media viewer.
3#![allow(deprecated)]
4
5use adw::{prelude::*, subclass::prelude::*};
6use gettextrs::gettext;
7use gtk::{CompositeTemplate, glib, glib::clone};
8use ruma::UserId;
9
10mod addresses_subpage;
11mod edit_details_subpage;
12mod general_page;
13mod history_viewer;
14mod invite_subpage;
15mod member_row;
16mod members_page;
17mod membership_lists;
18mod membership_subpage_item;
19mod permissions;
20mod room_upgrade_dialog;
21
22use self::{
23    addresses_subpage::AddressesSubpage,
24    edit_details_subpage::EditDetailsSubpage,
25    general_page::GeneralPage,
26    history_viewer::{
27        AudioHistoryViewer, FileHistoryViewer, HistoryViewerTimeline, VisualMediaHistoryViewer,
28    },
29    invite_subpage::InviteSubpage,
30    member_row::MemberRow,
31    members_page::MembersPage,
32    membership_lists::MembershipLists,
33    membership_subpage_item::MembershipSubpageItem,
34    permissions::PermissionsSubpage,
35};
36use crate::{components::UserPage, session::model::Room, toast};
37
38/// The possible subpages of the room details.
39#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Variant)]
40pub(crate) enum SubpageName {
41    /// The page to edit the name, topic and avatar of the room.
42    EditDetails,
43    /// The list of members of the room.
44    Members,
45    /// The form to invite new members.
46    Invite,
47    /// The history of visual media.
48    VisualMediaHistory,
49    /// The history of files.
50    FileHistory,
51    /// The history of audio.
52    AudioHistory,
53    /// The page to edit the public addresses of the room.
54    Addresses,
55    /// The page to edit the permissions of the room.
56    Permissions,
57}
58
59mod imp {
60    use std::{
61        cell::{OnceCell, RefCell},
62        collections::HashMap,
63    };
64
65    use glib::subclass::InitializingObject;
66
67    use super::*;
68
69    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
70    #[template(resource = "/org/gnome/Fractal/ui/session/view/content/room_details/mod.ui")]
71    #[properties(wrapper_type = super::RoomDetails)]
72    pub struct RoomDetails {
73        /// The room to show the details for.
74        #[property(get, set = Self::set_room, construct_only)]
75        room: OnceCell<Room>,
76        /// The lists of members filtered by membership.
77        #[property(get)]
78        membership_lists: MembershipLists,
79        /// The timeline for the history viewers.
80        #[property(get)]
81        timeline: OnceCell<HistoryViewerTimeline>,
82        /// The general page.
83        general_page: OnceCell<GeneralPage>,
84        /// The subpages that are loaded.
85        ///
86        /// We keep them around to avoid reloading them if the user reopens the
87        /// same subpage.
88        subpages: RefCell<HashMap<SubpageName, adw::NavigationPage>>,
89    }
90
91    #[glib::object_subclass]
92    impl ObjectSubclass for RoomDetails {
93        const NAME: &'static str = "RoomDetails";
94        type Type = super::RoomDetails;
95        type ParentType = adw::PreferencesWindow;
96
97        fn class_init(klass: &mut Self::Class) {
98            Self::bind_template(klass);
99
100            klass.install_action(
101                "details.show-subpage",
102                Some(&String::static_variant_type()),
103                |obj, _, param| {
104                    let subpage = param
105                        .and_then(glib::Variant::get::<SubpageName>)
106                        .expect("The parameter should be a valid subpage name");
107
108                    obj.imp().show_subpage(subpage, false);
109                },
110            );
111
112            klass.install_action(
113                "details.show-member",
114                Some(&String::static_variant_type()),
115                |obj, _, param| {
116                    let Some(user_id) = param
117                        .and_then(glib::Variant::get::<String>)
118                        .and_then(|s| UserId::parse(s).ok())
119                    else {
120                        return;
121                    };
122
123                    let member = obj.membership_lists().members().get_or_create(user_id);
124                    let user_page = UserPage::new(&member);
125                    user_page.connect_close(clone!(
126                        #[weak]
127                        obj,
128                        move |_| {
129                            obj.pop_subpage();
130                            toast!(
131                                obj,
132                                gettext("The user is not in the room members list anymore"),
133                            );
134                        }
135                    ));
136
137                    obj.push_subpage(&user_page);
138                },
139            );
140
141            klass.install_action("win.toggle-fullscreen", None, |obj, _, _| {
142                if obj.is_fullscreen() {
143                    obj.unfullscreen();
144                } else {
145                    obj.fullscreen();
146                }
147            });
148        }
149
150        fn instance_init(obj: &InitializingObject<Self>) {
151            obj.init_template();
152        }
153    }
154
155    #[glib::derived_properties]
156    impl ObjectImpl for RoomDetails {}
157
158    impl WidgetImpl for RoomDetails {
159        fn map(&self) {
160            self.parent_map();
161
162            self.general_page
163                .get()
164                .expect("general page is initialized")
165                .unselect_topic();
166        }
167    }
168
169    impl WindowImpl for RoomDetails {}
170    impl AdwWindowImpl for RoomDetails {}
171    impl PreferencesWindowImpl for RoomDetails {}
172
173    impl RoomDetails {
174        /// Set the room to show the details for.
175        fn set_room(&self, room: Room) {
176            let room = self.room.get_or_init(|| room);
177
178            // Initialize the media history viewers timeline.
179            self.timeline
180                .set(HistoryViewerTimeline::new(room))
181                .expect("timeline is uninitialized");
182
183            // Keep a strong reference to members list.
184            self.membership_lists
185                .set_members(room.get_or_create_members());
186
187            // Initialize the general page.
188            let general_page = self
189                .general_page
190                .get_or_init(|| GeneralPage::new(room, &self.membership_lists));
191            self.obj().add(general_page);
192        }
193
194        /// The timeline for the history viewers.
195        fn timeline(&self) -> &HistoryViewerTimeline {
196            self.timeline.get().expect("timeline is initialized")
197        }
198
199        /// Show the subpage with the given name.
200        pub(super) fn show_subpage(&self, name: SubpageName, is_initial: bool) {
201            let room = self.room.get().expect("room is initialized");
202
203            let mut subpages = self.subpages.borrow_mut();
204            let subpage = subpages.entry(name).or_insert_with(|| match name {
205                SubpageName::EditDetails => EditDetailsSubpage::new(room).upcast(),
206                SubpageName::Members => MembersPage::new(room, &self.membership_lists).upcast(),
207                SubpageName::Invite => InviteSubpage::new(room).upcast(),
208                SubpageName::VisualMediaHistory => {
209                    VisualMediaHistoryViewer::new(self.timeline()).upcast()
210                }
211                SubpageName::FileHistory => FileHistoryViewer::new(self.timeline()).upcast(),
212                SubpageName::AudioHistory => AudioHistoryViewer::new(self.timeline()).upcast(),
213                SubpageName::Addresses => AddressesSubpage::new(room).upcast(),
214                SubpageName::Permissions => PermissionsSubpage::new(&room.permissions()).upcast(),
215            });
216
217            if is_initial {
218                subpage.set_can_pop(false);
219            }
220
221            self.obj().push_subpage(subpage);
222        }
223    }
224}
225
226glib::wrapper! {
227    /// Preference Window to display and update room details.
228    pub struct RoomDetails(ObjectSubclass<imp::RoomDetails>)
229        @extends gtk::Widget, gtk::Window, adw::Window, gtk::Root, adw::PreferencesWindow,
230        @implements gtk::Accessible;
231}
232
233impl RoomDetails {
234    /// Construct a `RoomDetails` for the given room with the given parent
235    /// window.
236    pub fn new(parent_window: Option<&gtk::Window>, room: &Room) -> Self {
237        glib::Object::builder()
238            .property("transient-for", parent_window)
239            .property("room", room)
240            .build()
241    }
242
243    /// Show the given subpage as the initial page.
244    pub(crate) fn show_initial_subpage(&self, name: SubpageName) {
245        self.imp().show_subpage(name, true);
246    }
247}