fractal/session/view/content/explore/
public_room.rs

1use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
2use matrix_sdk::ruma::directory::PublicRoomsChunk;
3
4use crate::{
5    components::{AvatarData, AvatarImage, AvatarUriSource},
6    session::model::{Room, RoomList},
7};
8
9mod imp {
10    use std::cell::{Cell, OnceCell, RefCell};
11
12    use glib::signal::SignalHandlerId;
13
14    use super::*;
15
16    #[derive(Debug, Default, glib::Properties)]
17    #[properties(wrapper_type = super::PublicRoom)]
18    pub struct PublicRoom {
19        /// The list of rooms in this session.
20        #[property(get, construct_only)]
21        pub room_list: OnceCell<RoomList>,
22        /// The server that returned the room.
23        #[property(get, construct_only)]
24        pub server: OnceCell<String>,
25        pub matrix_public_room: OnceCell<PublicRoomsChunk>,
26        /// The [`AvatarData`] of this room.
27        #[property(get)]
28        pub avatar_data: OnceCell<AvatarData>,
29        /// The `Room` object for this room, if the user is already a member of
30        /// this room.
31        #[property(get)]
32        pub room: RefCell<Option<Room>>,
33        /// Whether the room is pending.
34        ///
35        /// A room is pending when the user clicked to join it.
36        #[property(get)]
37        pub pending: Cell<bool>,
38        pub room_handler: RefCell<Option<SignalHandlerId>>,
39    }
40
41    #[glib::object_subclass]
42    impl ObjectSubclass for PublicRoom {
43        const NAME: &'static str = "PublicRoom";
44        type Type = super::PublicRoom;
45    }
46
47    #[glib::derived_properties]
48    impl ObjectImpl for PublicRoom {
49        fn constructed(&self) {
50            self.parent_constructed();
51            let obj = self.obj();
52
53            let avatar_data = if let Some(session) = obj.room_list().session() {
54                AvatarData::with_image(AvatarImage::new(
55                    &session,
56                    AvatarUriSource::Room,
57                    None,
58                    None,
59                ))
60            } else {
61                AvatarData::new()
62            };
63
64            self.avatar_data.set(avatar_data).unwrap();
65
66            obj.room_list().connect_pending_rooms_changed(clone!(
67                #[weak]
68                obj,
69                move |_| {
70                    let Some(matrix_public_room) = obj.matrix_public_room() else {
71                        return;
72                    };
73
74                    obj.set_pending(
75                        obj.room_list()
76                            .is_pending_room((*matrix_public_room.room_id).into()),
77                    );
78                }
79            ));
80        }
81
82        fn dispose(&self) {
83            if let Some(handler_id) = self.room_handler.take() {
84                self.obj().room_list().disconnect(handler_id);
85            }
86        }
87    }
88}
89
90glib::wrapper! {
91    /// A room in a homeserver's public directory.
92    pub struct PublicRoom(ObjectSubclass<imp::PublicRoom>);
93}
94
95impl PublicRoom {
96    pub fn new(room_list: &RoomList, server: &str) -> Self {
97        glib::Object::builder()
98            .property("room-list", room_list)
99            .property("server", server)
100            .build()
101    }
102
103    /// Set the `Room` object for this room.
104    fn set_room(&self, room: Room) {
105        self.imp().room.replace(Some(room));
106        self.notify_room();
107    }
108
109    /// Set whether this room is pending.
110    fn set_pending(&self, pending: bool) {
111        if self.pending() == pending {
112            return;
113        }
114
115        self.imp().pending.set(pending);
116        self.notify_pending();
117    }
118
119    pub fn set_matrix_public_room(&self, room: PublicRoomsChunk) {
120        let imp = self.imp();
121
122        if let Some(display_name) = room.name.clone() {
123            self.avatar_data().set_display_name(display_name);
124        }
125        self.avatar_data()
126            .image()
127            .unwrap()
128            .set_uri_and_info(room.avatar_url.clone(), None);
129
130        if let Some(room) = self.room_list().get(&room.room_id) {
131            self.set_room(room);
132        } else {
133            let room_id = room.room_id.clone();
134            let handler_id = self.room_list().connect_items_changed(clone!(
135                #[weak(rename_to = obj)]
136                self,
137                move |room_list, _, _, _| {
138                    if let Some(room) = room_list.get(&room_id) {
139                        if let Some(handler_id) = obj.imp().room_handler.take() {
140                            obj.set_room(room);
141                            room_list.disconnect(handler_id);
142                        }
143                    }
144                }
145            ));
146
147            imp.room_handler.replace(Some(handler_id));
148        }
149
150        self.set_pending(self.room_list().is_pending_room((*room.room_id).into()));
151
152        imp.matrix_public_room.set(room).unwrap();
153    }
154
155    pub fn matrix_public_room(&self) -> Option<&PublicRoomsChunk> {
156        self.imp().matrix_public_room.get()
157    }
158
159    /// The display name for this room.
160    ///
161    /// Returns an empty string if there is no matrix public room.
162    pub fn display_name(&self) -> String {
163        let Some(matrix_public_room) = self.matrix_public_room() else {
164            return String::new();
165        };
166
167        matrix_public_room
168            .name
169            .as_deref()
170            .or(matrix_public_room
171                .canonical_alias
172                .as_ref()
173                .map(|a| a.as_str()))
174            .unwrap_or_else(|| matrix_public_room.room_id.as_str())
175            .to_owned()
176    }
177}