Browse Source

room-details: Use the new libadawaita 1.4 APIs

Now that it handles navigation with push and pop, we can reuse
AdwPreferencesWindow. Use also AdwNavigationPage and AdwToolbarView.
merge-requests/1461/head
Kévin Commaille 3 years ago
parent
commit
f93b794f5f
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
  1. 1
      po/POTFILES.in
  2. 91
      src/session/view/content/room_details/general_page/mod.rs
  3. 375
      src/session/view/content/room_details/general_page/mod.ui
  4. 6
      src/session/view/content/room_details/history_viewer/audio.rs
  5. 28
      src/session/view/content/room_details/history_viewer/audio.ui
  6. 6
      src/session/view/content/room_details/history_viewer/file.rs
  7. 26
      src/session/view/content/room_details/history_viewer/file.ui
  8. 6
      src/session/view/content/room_details/history_viewer/media.rs
  9. 25
      src/session/view/content/room_details/history_viewer/media.ui
  10. 88
      src/session/view/content/room_details/invite_subpage/mod.rs
  11. 19
      src/session/view/content/room_details/invite_subpage/mod.ui
  12. 34
      src/session/view/content/room_details/member_page/mod.rs
  13. 27
      src/session/view/content/room_details/member_page/mod.ui
  14. 267
      src/session/view/content/room_details/mod.rs
  15. 10
      src/session/view/content/room_details/mod.ui
  16. 15
      src/session/view/content/room_history/mod.rs
  17. 2
      src/session/view/content/room_history/mod.ui

1
po/POTFILES.in

@ -69,7 +69,6 @@ src/session/view/content/room_details/invite_subpage/mod.ui
src/session/view/content/room_details/member_page/member_menu.ui
src/session/view/content/room_details/member_page/mod.rs
src/session/view/content/room_details/member_page/mod.ui
src/session/view/content/room_details/mod.rs
src/session/view/content/room_details/mod.ui
src/session/view/content/room_history/attachment_dialog.ui
src/session/view/content/room_history/event_actions.rs

91
src/session/view/content/room_details/general_page/mod.rs

@ -35,7 +35,6 @@ mod imp {
use std::cell::{Cell, RefCell};
use glib::subclass::InitializingObject;
use once_cell::unsync::OnceCell;
use super::*;
@ -44,8 +43,8 @@ mod imp {
resource = "/org/gnome/Fractal/ui/session/view/content/room_details/general_page/mod.ui"
)]
pub struct GeneralPage {
pub room: OnceCell<Room>,
pub room_members: OnceCell<MemberList>,
pub room: glib::WeakRef<Room>,
pub room_members: RefCell<Option<MemberList>>,
#[template_child]
pub avatar: TemplateChild<EditableAvatar>,
#[template_child]
@ -73,7 +72,7 @@ mod imp {
impl ObjectSubclass for GeneralPage {
const NAME: &'static str = "ContentRoomDetailsGeneralPage";
type Type = super::GeneralPage;
type ParentType = adw::Bin;
type ParentType = adw::PreferencesPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -92,7 +91,7 @@ mod imp {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Room>("room")
.construct_only()
.explicit_notify()
.build(),
glib::ParamSpecBoolean::builder("edit-mode-enabled")
.explicit_notify()
@ -117,36 +116,21 @@ mod imp {
let obj = self.obj();
match pspec.name() {
"room" => self.room.get().to_value(),
"room" => obj.room().to_value(),
"edit-mode-enabled" => obj.edit_mode_enabled().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.init_avatar();
obj.init_edit_mode();
let members = obj.room_members();
members.connect_items_changed(clone!(@weak obj => move |members, _, _, _| {
obj.member_count_changed(members.n_items());
}));
obj.member_count_changed(members.n_items());
}
}
impl WidgetImpl for GeneralPage {}
impl BinImpl for GeneralPage {}
impl PreferencesPageImpl for GeneralPage {}
}
glib::wrapper! {
/// Preference Window to display and update room details.
pub struct GeneralPage(ObjectSubclass<imp::GeneralPage>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::PreferencesPage, @implements gtk::Accessible;
}
#[gtk::template_callbacks]
@ -156,13 +140,18 @@ impl GeneralPage {
}
/// The room backing all the details of the preference window.
pub fn room(&self) -> &Room {
// Use unwrap because room property is CONSTRUCT_ONLY.
self.imp().room.get().unwrap()
pub fn room(&self) -> Option<Room> {
self.imp().room.upgrade()
}
/// Set the room backing all the details of the preference window.
fn set_room(&self, room: Room) {
fn set_room(&self, room: Option<&Room>) {
let Some(room) = room else {
// Just ignore when room is missing.
return;
};
let imp = self.imp();
let avatar_data = room.avatar_data();
AvatarData::this_expression("image")
.chain_property::<AvatarImage>("uri")
@ -185,23 +174,28 @@ impl GeneralPage {
}),
);
let imp = self.imp();
// Keep a strong reference to the members list.
imp.room_members
.set(room.get_or_create_members())
.expect("Room members already initialized");
imp.room.set(room).expect("Room already initialized");
self.init_avatar(room);
self.init_edit_mode(room);
let members = room.get_or_create_members();
members.connect_items_changed(clone!(@weak self as obj => move |members, _, _, _| {
obj.member_count_changed(members.n_items());
}));
self.member_count_changed(members.n_items());
// Keep strong reference to members list.
imp.room_members.replace(Some(members));
imp.room.set(Some(room));
self.notify("room");
}
/// The members of the room.
pub fn room_members(&self) -> &MemberList {
self.imp()
.room_members
.get()
.expect("Room members are CONSTRUCT")
pub fn room_members(&self) -> MemberList {
self.imp().room_members.borrow().clone().unwrap()
}
fn init_avatar(&self) {
fn init_avatar(&self, room: &Room) {
let avatar = &*self.imp().avatar;
avatar.connect_edit_avatar(clone!(@weak self as obj => move |_, file| {
spawn!(
@ -219,7 +213,6 @@ impl GeneralPage {
}));
// Hide avatar controls when the user is not eligible to perform the actions.
let room = self.room();
let room_avatar_changeable = room
.own_user_is_allowed_to_expr(PowerLevelAction::SendState(StateEventType::RoomAvatar));
@ -251,7 +244,10 @@ impl GeneralPage {
}
async fn change_avatar(&self, file: gio::File) {
let room = self.room();
let Some(room) = self.room() else {
error!("Cannot change avatar with missing room");
return;
};
let matrix_room = room.matrix_room();
if matrix_room.state() != RoomState::Joined {
error!("Cannot change avatar of room not joined");
@ -314,7 +310,10 @@ impl GeneralPage {
}
async fn remove_avatar(&self) {
let room = self.room();
let Some(room) = self.room() else {
error!("Cannot remove avatar with missing room");
return;
};
let matrix_room = room.matrix_room();
if matrix_room.state() != RoomState::Joined {
error!("Cannot remove avatar of room not joined");
@ -383,13 +382,12 @@ impl GeneralPage {
}
}
fn init_edit_mode(&self) {
fn init_edit_mode(&self, room: &Room) {
let imp = self.imp();
self.enable_details(false);
// Hide edit controls when the user is not eligible to perform the actions.
let room = self.room();
let room_name_changeable =
room.own_user_is_allowed_to_expr(PowerLevelAction::SendState(StateEventType::RoomName));
let room_topic_changeable = room
@ -480,8 +478,11 @@ impl GeneralPage {
}
async fn save_details(&self) {
let Some(room) = self.room() else {
error!("Cannot save details with missing room");
return;
};
let imp = self.imp();
let room = self.room();
let raw_name = imp.room_name_entry.text().to_string();
let trimmed_name = raw_name.trim();

375
src/session/view/content/room_details/general_page/mod.ui

@ -1,208 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentRoomDetailsGeneralPage" parent="AdwBin">
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar"/>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<child>
<object class="AdwClamp">
<property name="maximum-size">424</property>
<property name="tightening-threshold">424</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">24</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<child>
<object class="AdwPreferencesGroup">
<style>
<class name="room-details-group"/>
</style>
<child>
<object class="ComponentsEditableAvatar" id="avatar">
<binding name="data">
<lookup name="avatar-data">
<template class="ContentRoomDetailsGeneralPage" parent="AdwPreferencesPage">
<property name="title" translatable="yes">Room Details</property>
<child>
<object class="AdwPreferencesGroup">
<style>
<class name="room-details-group"/>
</style>
<child>
<object class="ComponentsEditableAvatar" id="avatar">
<binding name="data">
<lookup name="avatar-data">
<lookup name="room">ContentRoomDetailsGeneralPage</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">6</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkEntry" id="room_name_entry">
<property name="sensitive">false</property>
<property name="activates-default">True</property>
<property name="xalign">0.5</property>
<property name="buffer">
<object class="GtkEntryBuffer" id="room_name_buffer">
<binding name="text">
<lookup name="display-name">
<lookup name="room">ContentRoomDetailsGeneralPage</lookup>
</lookup>
</binding>
</object>
</property>
<style>
<class name="room-details-name"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="room_topic_label">
<property name="visible">false</property>
<property name="margin-top">12</property>
<property name="label" translatable="yes">Description</property>
<property name="halign">start</property>
<style>
<class name="dim-label"/>
<class name="caption-heading"/>
</style>
</object>
</child>
<child>
<object class="CustomEntry" id="room_topic_entry">
<property name="sensitive">false</property>
<property name="margin-bottom">18</property>
<child>
<object class="GtkTextView" id="room_topic_text_view">
<property name="justification">center</property>
<property name="wrap-mode">word-char</property>
<property name="accepts-tab">False</property>
<property name="top-margin">7</property>
<property name="bottom-margin">7</property>
<property name="buffer">
<object class="GtkTextBuffer" id="room_topic_buffer">
<binding name="text">
<closure type="gchararray" function="unwrap_string_or_empty">
<lookup name="topic">
<lookup name="room">ContentRoomDetailsGeneralPage</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">6</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkEntry" id="room_name_entry">
<property name="sensitive">false</property>
<property name="activates-default">True</property>
<property name="xalign">0.5</property>
<property name="buffer">
<object class="GtkEntryBuffer" id="room_name_buffer">
<binding name="text">
<lookup name="display-name">
<lookup name="room">ContentRoomDetailsGeneralPage</lookup>
</lookup>
</binding>
</object>
</property>
<style>
<class name="room-details-name"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="room_topic_label">
<property name="visible">false</property>
<property name="margin-top">12</property>
<property name="label" translatable="yes">Description</property>
<property name="halign">start</property>
<style>
<class name="dim-label"/>
<class name="caption-heading"/>
</style>
</object>
</child>
<child>
<object class="CustomEntry" id="room_topic_entry">
<property name="sensitive">false</property>
<property name="margin-bottom">18</property>
<child>
<object class="GtkTextView" id="room_topic_text_view">
<property name="justification">center</property>
<property name="wrap-mode">word-char</property>
<property name="accepts-tab">False</property>
<property name="top-margin">7</property>
<property name="bottom-margin">7</property>
<property name="buffer">
<object class="GtkTextBuffer" id="room_topic_buffer">
<binding name="text">
<closure type="gchararray" function="unwrap_string_or_empty">
<lookup name="topic">
<lookup name="room">ContentRoomDetailsGeneralPage</lookup>
</lookup>
</closure>
</binding>
</object>
</property>
</object>
</child>
<style>
<class name="room-details-topic"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="edit_details_btn">
<property name="halign">center</property>
<property name="label" translatable="yes">Edit Details</property>
<signal name="clicked" handler="edit_details_clicked" swapped="yes"/>
</object>
</child>
<child>
<object class="SpinnerButton" id="save_details_btn">
<property name="visible" bind-source="ContentRoomDetailsGeneralPage" bind-property="edit-mode-enabled" bind-flags="sync-create"/>
<property name="halign">center</property>
<property name="label" translatable="yes">Save Details</property>
<signal name="clicked" handler="save_details_clicked" swapped="yes"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Members</property>
<property name="icon-name">system-users-symbolic</property>
<property name="action-name">details.next-page</property>
<property name="action-target">'members'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkLabel" id="members_count">
<property name="valign">center</property>
<property name="halign">center</property>
</object>
</child>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Media</property>
<property name="action-name">details.next-page</property>
<property name="action-target">'media-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">File</property>
<property name="action-name">details.next-page</property>
<property name="action-target">'file-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<!-- Translators: As in 'Audio file'. -->
<property name="title" translatable="yes">Audio</property>
<property name="action-name">details.next-page</property>
<property name="action-target">'audio-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</property>
</closure>
</binding>
</object>
</property>
</object>
</child>
<style>
<class name="room-details-topic"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="edit_details_btn">
<property name="halign">center</property>
<property name="label" translatable="yes">Edit Details</property>
<signal name="clicked" handler="edit_details_clicked" swapped="yes"/>
</object>
</child>
<child>
<object class="SpinnerButton" id="save_details_btn">
<property name="visible" bind-source="ContentRoomDetailsGeneralPage" bind-property="edit-mode-enabled" bind-flags="sync-create"/>
<property name="halign">center</property>
<property name="label" translatable="yes">Save Details</property>
<signal name="clicked" handler="save_details_clicked" swapped="yes"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Members</property>
<property name="icon-name">system-users-symbolic</property>
<property name="action-name">details.show-subpage</property>
<property name="action-target">'members'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkLabel" id="members_count">
<property name="valign">center</property>
<property name="halign">center</property>
</object>
</child>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Media</property>
<property name="action-name">details.show-subpage</property>
<property name="action-target">'media-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">File</property>
<property name="action-name">details.show-subpage</property>
<property name="action-target">'file-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<!-- Translators: As in 'Audio file'. -->
<property name="title" translatable="yes">Audio</property>
<property name="action-name">details.show-subpage</property>
<property name="action-target">'audio-history'</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

6
src/session/view/content/room_details/history_viewer/audio.rs

@ -26,7 +26,7 @@ mod imp {
impl ObjectSubclass for AudioHistoryViewer {
const NAME: &'static str = "ContentAudioHistoryViewer";
type Type = super::AudioHistoryViewer;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
AudioRow::static_type();
@ -67,12 +67,12 @@ mod imp {
}
impl WidgetImpl for AudioHistoryViewer {}
impl BinImpl for AudioHistoryViewer {}
impl NavigationPageImpl for AudioHistoryViewer {}
}
glib::wrapper! {
pub struct AudioHistoryViewer(ObjectSubclass<imp::AudioHistoryViewer>)
@extends gtk::Widget, adw::Bin;
@extends gtk::Widget, adw::NavigationPage;
}
impl AudioHistoryViewer {

28
src/session/view/content/room_details/history_viewer/audio.ui

@ -1,26 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentAudioHistoryViewer" parent="AdwBin">
<template class="ContentAudioHistoryViewer" parent="AdwNavigationPage">
<!-- Translators: As in 'Audio file'. -->
<property name="title" translatable="yes">Audio</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<property name="title-widget">
<object class="AdwWindowTitle">
<!-- Translators: As in 'Audio file'. -->
<property name="title" translatable="yes">Audio</property>
</object>
</property>
<child type="start">
<object class="GtkButton">
<property name="action-name">details.previous-page</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
@ -60,7 +48,7 @@
</object>
</child>
</object>
</child>
</property>
</object>
</child>
</template>

6
src/session/view/content/room_details/history_viewer/file.rs

@ -26,7 +26,7 @@ mod imp {
impl ObjectSubclass for FileHistoryViewer {
const NAME: &'static str = "ContentFileHistoryViewer";
type Type = super::FileHistoryViewer;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
FileRow::static_type();
@ -67,12 +67,12 @@ mod imp {
}
impl WidgetImpl for FileHistoryViewer {}
impl BinImpl for FileHistoryViewer {}
impl NavigationPageImpl for FileHistoryViewer {}
}
glib::wrapper! {
pub struct FileHistoryViewer(ObjectSubclass<imp::FileHistoryViewer>)
@extends gtk::Widget, adw::Bin;
@extends gtk::Widget, adw::NavigationPage;
}
impl FileHistoryViewer {

26
src/session/view/content/room_details/history_viewer/file.ui

@ -1,25 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentFileHistoryViewer" parent="AdwBin">
<template class="ContentFileHistoryViewer" parent="AdwNavigationPage">
<property name="title" translatable="yes">File</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<property name="title-widget">
<object class="AdwWindowTitle">
<property name="title" translatable="yes">Files</property>
</object>
</property>
<child type="start">
<object class="GtkButton">
<property name="action-name">details.previous-page</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
@ -59,7 +47,7 @@
</object>
</child>
</object>
</child>
</property>
</object>
</child>
</template>

6
src/session/view/content/room_details/history_viewer/media.rs

@ -33,7 +33,7 @@ mod imp {
impl ObjectSubclass for MediaHistoryViewer {
const NAME: &'static str = "ContentMediaHistoryViewer";
type Type = super::MediaHistoryViewer;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
MediaItem::static_type();
@ -74,12 +74,12 @@ mod imp {
}
impl WidgetImpl for MediaHistoryViewer {}
impl BinImpl for MediaHistoryViewer {}
impl NavigationPageImpl for MediaHistoryViewer {}
}
glib::wrapper! {
pub struct MediaHistoryViewer(ObjectSubclass<imp::MediaHistoryViewer>)
@extends gtk::Widget, adw::Bin;
@extends gtk::Widget, adw::NavigationPage;
}
impl MediaHistoryViewer {

25
src/session/view/content/room_details/history_viewer/media.ui

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentMediaHistoryViewer" parent="AdwBin">
<template class="ContentMediaHistoryViewer" parent="AdwNavigationPage">
<property name="title" translatable="yes">Media</property>
<child>
<object class="GtkOverlay">
<child type="overlay">
@ -9,27 +10,15 @@
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<style>
<class name="osd"/>
</style>
<property name="title-widget">
<object class="AdwWindowTitle">
<property name="title" translatable="yes">Media</property>
</object>
</property>
<child type="start">
<object class="GtkButton">
<property name="action-name">details.previous-page</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
@ -70,7 +59,7 @@
</object>
</child>
</object>
</child>
</property>
</object>
</child>
</object>

88
src/session/view/content/room_details/invite_subpage/mod.rs

@ -1,6 +1,6 @@
use adw::subclass::prelude::*;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::ngettext;
use gtk::{gdk, glib, glib::clone, prelude::*, CompositeTemplate};
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
mod invitee;
use self::invitee::Invitee;
@ -18,8 +18,6 @@ use crate::{
};
mod imp {
use std::cell::RefCell;
use glib::subclass::InitializingObject;
use super::*;
@ -29,7 +27,7 @@ mod imp {
resource = "/org/gnome/Fractal/ui/session/view/content/room_details/invite_subpage/mod.ui"
)]
pub struct InviteSubpage {
pub room: RefCell<Option<Room>>,
pub room: glib::WeakRef<Room>,
#[template_child]
pub list_view: TemplateChild<gtk::ListView>,
#[template_child]
@ -58,7 +56,7 @@ mod imp {
impl ObjectSubclass for InviteSubpage {
const NAME: &'static str = "ContentInviteSubpage";
type Type = super::InviteSubpage;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
InviteeRow::static_type();
@ -85,7 +83,7 @@ mod imp {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Room>("room")
.explicit_notify()
.construct_only()
.build()]
});
@ -179,13 +177,13 @@ mod imp {
}
impl WidgetImpl for InviteSubpage {}
impl BinImpl for InviteSubpage {}
impl NavigationPageImpl for InviteSubpage {}
}
glib::wrapper! {
/// Preference Window to display and update room details.
pub struct InviteSubpage(ObjectSubclass<imp::InviteSubpage>)
@extends gtk::Widget, gtk::Window, adw::Window, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, gtk::Window, adw::NavigationPage, @implements gtk::Accessible;
}
impl InviteSubpage {
@ -195,56 +193,56 @@ impl InviteSubpage {
/// The room users will be invited to.
pub fn room(&self) -> Option<Room> {
self.imp().room.borrow().clone()
self.imp().room.upgrade()
}
/// Set the room users will be invited to.
fn set_room(&self, room: Option<Room>) {
fn set_room(&self, room: &Room) {
let imp = self.imp();
if self.room() == room {
return;
}
let user_list = InviteeList::new(room);
user_list.connect_invitee_added(clone!(@weak self as obj => move |_, invitee| {
obj.add_user_pill(invitee);
}));
if let Some(ref room) = room {
let user_list = InviteeList::new(room);
user_list.connect_invitee_added(clone!(@weak self as obj => move |_, invitee| {
obj.add_user_pill(invitee);
}));
user_list.connect_invitee_removed(clone!(@weak self as obj => move |_, invitee| {
obj.remove_user_pill(invitee);
}));
user_list.connect_notify_local(
Some("state"),
clone!(@weak self as obj => move |_, _| {
obj.update_view();
}),
);
user_list.connect_invitee_removed(clone!(@weak self as obj => move |_, invitee| {
obj.remove_user_pill(invitee);
}));
imp.text_buffer
.bind_property("text", &user_list, "search-term")
.sync_create()
.build();
user_list.connect_notify_local(
Some("state"),
clone!(@weak self as obj => move |_, _| {
obj.update_view();
}),
);
user_list
.bind_property("has-selected", &*imp.invite_button, "sensitive")
.sync_create()
.build();
imp.text_buffer
.bind_property("text", &user_list, "search-term")
.sync_create()
.build();
imp.list_view
.set_model(Some(&gtk::NoSelection::new(Some(user_list))));
} else {
imp.list_view.set_model(gtk::SelectionModel::NONE);
}
user_list
.bind_property("has-selected", &*imp.invite_button, "sensitive")
.sync_create()
.build();
imp.list_view
.set_model(Some(&gtk::NoSelection::new(Some(user_list))));
imp.room.replace(room);
imp.room.set(Some(room));
self.notify("room");
}
fn close(&self) {
self.activate_action("details.previous-page", None).unwrap();
let window = self
.root()
.and_downcast::<adw::PreferencesWindow>()
.unwrap();
if self.can_pop() {
window.pop_subpage();
} else {
window.close();
}
}
fn add_user_pill(&self, user: &Invitee) {

19
src/session/view/content/room_details/invite_subpage/mod.ui

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentInviteSubpage" parent="AdwBin">
<template class="ContentInviteSubpage" parent="AdwNavigationPage">
<property name="title" translatable="yes">Invite New Members</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<property name="show-title-buttons">false</property>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-back-button">false</property>
<property name="show-end-title-buttons">false</property>
<child type="start">
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">_Cancel</property>
@ -25,7 +26,7 @@
</child>
</object>
</child>
<child>
<child type="top">
<object class="GtkSearchBar">
<property name="search-mode-enabled">True</property>
<child>
@ -76,7 +77,7 @@
</child>
</object>
</child>
<child>
<property name="content">
<object class="GtkStack" id="stack">
<child>
<object class="AdwStatusPage" id="no_search_page">
@ -148,7 +149,7 @@
</object>
</child>
</object>
</child>
</property>
</object>
</property>
</template>

34
src/session/view/content/room_details/member_page/mod.rs

@ -1,7 +1,4 @@
use adw::{
prelude::*,
subclass::{bin::BinImpl, prelude::*},
};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
gio,
@ -39,7 +36,7 @@ mod imp {
resource = "/org/gnome/Fractal/ui/session/view/content/room_details/member_page/mod.ui"
)]
pub struct MemberPage {
pub room: RefCell<Option<Room>>,
pub room: glib::WeakRef<Room>,
#[template_child]
pub members_search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
@ -56,7 +53,7 @@ mod imp {
impl ObjectSubclass for MemberPage {
const NAME: &'static str = "ContentMemberPage";
type Type = super::MemberPage;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -98,7 +95,7 @@ mod imp {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Room>("room")
.explicit_notify()
.construct_only()
.build(),
glib::ParamSpecObject::builder::<MemberMenu>("member-menu")
.read_only()
@ -142,12 +139,12 @@ mod imp {
}
impl WidgetImpl for MemberPage {}
impl BinImpl for MemberPage {}
impl NavigationPageImpl for MemberPage {}
}
glib::wrapper! {
pub struct MemberPage(ObjectSubclass<imp::MemberPage>)
@extends gtk::Widget, adw::Bin;
@extends gtk::Widget, adw::NavigationPage;
}
impl MemberPage {
@ -157,29 +154,22 @@ impl MemberPage {
/// The room backing all the details of the member page.
pub fn room(&self) -> Option<Room> {
self.imp().room.borrow().as_ref().cloned()
self.imp().room.upgrade()
}
/// Set the room backing all the details of the member page.
pub fn set_room(&self, room: Option<Room>) {
fn set_room(&self, room: &Room) {
let imp = self.imp();
let prev_room = self.room();
if prev_room == room {
return;
}
if let Some(invite_action) = imp.invite_action_watch.take() {
invite_action.unwatch();
}
if let Some(room) = room.as_ref() {
self.init_members_list(room);
self.init_invite_button(room);
self.set_state(Membership::Join);
}
self.init_members_list(room);
self.init_invite_button(room);
self.set_state(Membership::Join);
imp.room.replace(room);
imp.room.set(Some(room));
self.notify("room");
}

27
src/session/view/content/room_details/member_page/mod.ui

@ -1,17 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ContentMemberPage" parent="AdwBin">
<template class="ContentMemberPage" parent="AdwNavigationPage">
<property name="title">Room Members</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<child type="start">
<object class="GtkButton">
<property name="icon-name">go-previous-symbolic</property>
<property name="action_name">members.previous</property>
</object>
</child>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="search_button">
<property name="icon-name">system-search-symbolic</property>
@ -22,7 +16,7 @@
</child>
</object>
</child>
<child>
<child type="top">
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active"/>
<property name="child">
@ -42,7 +36,7 @@
</property>
</object>
</child>
<child>
<property name="content">
<object class="GtkOverlay">
<child>
<object class="GtkStack" id="list_stack">
@ -54,8 +48,8 @@
<property name="valign">end</property>
<property name="halign">center</property>
<property name="margin-bottom">24</property>
<property name="action-name">details.next-page</property>
<property name="action-target">&apos;invite&apos;</property>
<property name="action-name">details.show-subpage</property>
<property name="action-target">'invite'</property>
<property name="child">
<object class="GtkBox">
<property name="spacing">6</property>
@ -78,9 +72,8 @@
</object>
</child>
</object>
</child>
</property>
</object>
</child>
</template>
</interface>

267
src/session/view/content/room_details/mod.rs

@ -6,9 +6,7 @@ mod member_page;
use std::convert::From;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, CompositeTemplate};
use tracing::warn;
pub use self::{
general_page::GeneralPage,
@ -16,15 +14,10 @@ pub use self::{
invite_subpage::InviteSubpage,
member_page::MemberPage,
};
use crate::{components::ToastableWindow, prelude::*, session::model::Room};
use crate::session::model::Room;
#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "RoomDetailsPageName")]
pub enum PageName {
#[default]
None,
General,
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
pub enum SubpageName {
Members,
Invite,
MediaHistory,
@ -32,47 +25,27 @@ pub enum PageName {
AudioHistory,
}
impl glib::variant::StaticVariantType for PageName {
impl glib::variant::StaticVariantType for SubpageName {
fn static_variant_type() -> std::borrow::Cow<'static, glib::VariantTy> {
String::static_variant_type()
}
}
impl glib::variant::FromVariant for PageName {
impl glib::variant::FromVariant for SubpageName {
fn from_variant(variant: &glib::variant::Variant) -> Option<Self> {
match variant.str()? {
"general" => Some(PageName::General),
"members" => Some(PageName::Members),
"invite" => Some(PageName::Invite),
"media-history" => Some(PageName::MediaHistory),
"file-history" => Some(PageName::FileHistory),
"audio-history" => Some(PageName::AudioHistory),
"" => Some(PageName::None),
"members" => Some(Self::Members),
"invite" => Some(Self::Invite),
"media-history" => Some(Self::MediaHistory),
"file-history" => Some(Self::FileHistory),
"audio-history" => Some(Self::AudioHistory),
_ => None,
}
}
}
impl glib::variant::ToVariant for PageName {
fn to_variant(&self) -> glib::variant::Variant {
match self {
PageName::None => "",
PageName::General => "general",
PageName::Members => "members",
PageName::Invite => "invite",
PageName::MediaHistory => "media-history",
PageName::FileHistory => "file-history",
PageName::AudioHistory => "audio-history",
}
.to_variant()
}
}
mod imp {
use std::{
cell::{Cell, RefCell},
collections::HashMap,
};
use std::{cell::RefCell, collections::HashMap};
use glib::subclass::InitializingObject;
use once_cell::unsync::OnceCell;
@ -82,34 +55,36 @@ mod imp {
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/content/room_details/mod.ui")]
pub struct RoomDetails {
/// The room to show the details for.
pub room: OnceCell<Room>,
#[template_child]
pub main_stack: TemplateChild<gtk::Stack>,
pub list_stack_children: RefCell<HashMap<PageName, glib::WeakRef<gtk::Widget>>>,
pub visible_page: Cell<PageName>,
pub previous_visible_page: RefCell<Vec<PageName>>,
/// The subpages that are loaded.
///
/// We keep them around to avoid reloading them if the user reopens the
/// same subpage.
pub subpages: RefCell<HashMap<SubpageName, adw::NavigationPage>>,
}
#[glib::object_subclass]
impl ObjectSubclass for RoomDetails {
const NAME: &'static str = "RoomDetails";
type Type = super::RoomDetails;
type ParentType = ToastableWindow;
type ParentType = adw::PreferencesWindow;
fn class_init(klass: &mut Self::Class) {
GeneralPage::static_type();
Self::bind_template(klass);
klass.install_action("details.next-page", Some("s"), move |widget, _, param| {
let page = param
.and_then(|variant| variant.get::<PageName>())
.expect("The parameter need to be a valid PageName");
widget.next_page(page);
});
klass.install_action("details.previous-page", None, move |widget, _, _| {
widget.previous_page();
});
klass.install_action(
"details.show-subpage",
Some("s"),
move |widget, _, param| {
let subpage = param
.and_then(|variant| variant.get::<SubpageName>())
.expect("The parameter should be a valid subpage name");
widget.show_subpage(subpage, false);
},
);
}
fn instance_init(obj: &InitializingObject<Self>) {
@ -121,14 +96,9 @@ mod imp {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Room>("room")
.construct_only()
.build(),
glib::ParamSpecEnum::builder::<PageName>("visible-page")
.explicit_notify()
.build(),
]
vec![glib::ParamSpecObject::builder::<Room>("room")
.construct_only()
.build()]
});
PROPERTIES.as_ref()
@ -139,7 +109,6 @@ mod imp {
match pspec.name() {
"room" => obj.set_room(value.get().unwrap()),
"visible-page" => obj.set_visible_page(value.get().unwrap()),
_ => unimplemented!(),
}
}
@ -147,7 +116,6 @@ mod imp {
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"room" => self.room.get().to_value(),
"visible-page" => self.obj().visible_page().to_value(),
_ => unimplemented!(),
}
}
@ -156,13 +124,13 @@ mod imp {
impl WidgetImpl for RoomDetails {}
impl WindowImpl for RoomDetails {}
impl AdwWindowImpl for RoomDetails {}
impl ToastableWindowImpl for RoomDetails {}
impl PreferencesWindowImpl for RoomDetails {}
}
glib::wrapper! {
/// Preference Window to display and update room details.
pub struct RoomDetails(ObjectSubclass<imp::RoomDetails>)
@extends gtk::Widget, gtk::Window, adw::Window, gtk::Root, ToastableWindow, @implements gtk::Accessible;
@extends gtk::Widget, gtk::Window, adw::Window, gtk::Root, adw::PreferencesWindow, @implements gtk::Accessible;
}
impl RoomDetails {
@ -182,157 +150,32 @@ impl RoomDetails {
/// Set the room backing all the details of the preference window.
fn set_room(&self, room: Room) {
self.imp().room.set(room).expect("Room already initialized");
self.notify("room");
}
/// The page that is currently visible.
pub fn visible_page(&self) -> PageName {
self.imp().visible_page.get()
}
/// Set the page that is currently visible.
pub fn set_visible_page(&self, name: PageName) {
let imp = self.imp();
let prev_name = self.visible_page();
let mut list_stack_children = imp.list_stack_children.borrow_mut();
if prev_name == name {
return;
}
match name {
PageName::General => {
let general_page = if let Some(general_page) = list_stack_children
.get(&PageName::General)
.and_then(glib::object::WeakRef::upgrade)
{
general_page
} else {
let general_page = GeneralPage::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::General, general_page.downgrade());
self.imp().main_stack.add_child(&general_page);
general_page
};
self.set_title(Some(&gettext("Room Details")));
imp.main_stack.set_visible_child(&general_page);
}
PageName::Members => {
let members_page = if let Some(members_page) = list_stack_children
.get(&PageName::Members)
.and_then(glib::object::WeakRef::upgrade)
{
members_page
} else {
let members_page = MemberPage::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::Members, members_page.downgrade());
self.imp().main_stack.add_child(&members_page);
members_page
};
self.set_title(Some(&gettext("Room Members")));
imp.main_stack.set_visible_child(&members_page);
}
PageName::Invite => {
let invite_page = if let Some(invite_page) = list_stack_children
.get(&PageName::Invite)
.and_then(glib::object::WeakRef::upgrade)
{
invite_page
} else {
let invite_page = InviteSubpage::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::Invite, invite_page.downgrade());
imp.main_stack.add_child(&invite_page);
invite_page
};
self.set_title(Some(&gettext("Invite new Members")));
imp.main_stack.set_visible_child(&invite_page);
}
PageName::MediaHistory => {
let media_page = if let Some(media_page) = list_stack_children
.get(&PageName::MediaHistory)
.and_then(glib::object::WeakRef::upgrade)
{
media_page
} else {
let media_page = MediaHistoryViewer::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::MediaHistory, media_page.downgrade());
imp.main_stack.add_child(&media_page);
media_page
};
self.set_title(Some(&gettext("Media")));
imp.main_stack.set_visible_child(&media_page);
}
PageName::FileHistory => {
let file_page = if let Some(file_page) = list_stack_children
.get(&PageName::FileHistory)
.and_then(glib::object::WeakRef::upgrade)
{
file_page
} else {
let file_page = FileHistoryViewer::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::FileHistory, file_page.downgrade());
imp.main_stack.add_child(&file_page);
file_page
};
self.set_title(Some(&gettext("File")));
imp.main_stack.set_visible_child(&file_page);
}
PageName::AudioHistory => {
let audio_page = if let Some(audio_page) = list_stack_children
.get(&PageName::AudioHistory)
.and_then(glib::object::WeakRef::upgrade)
{
audio_page
} else {
let audio_page = AudioHistoryViewer::new(self.room()).upcast::<gtk::Widget>();
list_stack_children.insert(PageName::AudioHistory, audio_page.downgrade());
imp.main_stack.add_child(&audio_page);
audio_page
};
// Translators: As in 'Audio file'.
self.set_title(Some(&gettext("Audio")));
imp.main_stack.set_visible_child(&audio_page);
}
PageName::None => {
warn!("Can’t switch to PageName::None");
}
}
imp.visible_page.set(name);
self.notify("visible-page");
}
fn next_page(&self, next_page: PageName) {
/// Show the subpage with the given name.
fn show_subpage(&self, name: SubpageName, is_initial: bool) {
let imp = self.imp();
let prev_page = self.visible_page();
if prev_page == next_page {
return;
let room = self.room();
let mut subpages = imp.subpages.borrow_mut();
let subpage = subpages.entry(name).or_insert_with(|| match name {
SubpageName::Members => MemberPage::new(room).upcast(),
SubpageName::Invite => InviteSubpage::new(room).upcast(),
SubpageName::MediaHistory => MediaHistoryViewer::new(room).upcast(),
SubpageName::FileHistory => FileHistoryViewer::new(room).upcast(),
SubpageName::AudioHistory => AudioHistoryViewer::new(room).upcast(),
});
if is_initial {
subpage.set_can_pop(false);
}
imp.main_stack
.set_transition_type(gtk::StackTransitionType::SlideLeft);
imp.previous_visible_page.borrow_mut().push(prev_page);
self.set_visible_page(next_page);
self.push_subpage(subpage);
}
fn previous_page(&self) {
let imp = self.imp();
imp.main_stack
.set_transition_type(gtk::StackTransitionType::SlideRight);
if let Some(prev_page) = imp.previous_visible_page.borrow_mut().pop() {
self.set_visible_page(prev_page);
} else {
// If there isn't any previous page close the dialog since it was opened on a
// specific page
self.close();
};
/// Show the given subpage as the initial page.
pub fn show_initial_subpage(&self, name: SubpageName) {
self.show_subpage(name, true);
}
}

10
src/session/view/content/room_details/mod.ui

@ -1,14 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="RoomDetails" parent="ToastableWindow">
<template class="RoomDetails" parent="AdwPreferencesWindow">
<property name="title" translatable="yes">Room Details</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="default-width">640</property>
<property name="default-height">576</property>
<property name="child-content">
<object class="GtkStack" id="main_stack" />
</property>
<child>
<object class="ContentRoomDetailsGeneralPage">
<property name="room" bind-source="RoomDetails" bind-property="room" bind-flags="sync-create"/>
</object>
</child>
<style>
<class name="room-details"/>
</style>

15
src/session/view/content/room_history/mod.rs

@ -202,10 +202,10 @@ mod imp {
});
klass.install_action("room-history.details", None, move |widget, _, _| {
widget.open_room_details(room_details::PageName::General);
widget.open_room_details(None);
});
klass.install_action("room-history.invite-members", None, move |widget, _, _| {
widget.open_room_details(room_details::PageName::Invite);
widget.open_room_details(Some(room_details::SubpageName::Invite));
});
klass.install_action("room-history.scroll-down", None, move |widget, _, _| {
@ -985,11 +985,16 @@ impl RoomHistory {
};
}
/// Opens the room details on the page with the given name.
pub fn open_room_details(&self, page_name: room_details::PageName) {
/// Opens the room details.
///
/// If `subpage_name` is set, the room details will be opened on the given
/// subpage.
pub fn open_room_details(&self, subpage_name: Option<room_details::SubpageName>) {
if let Some(room) = self.room() {
let window = RoomDetails::new(&self.parent_window(), &room);
window.set_visible_page(page_name);
if let Some(subpage_name) = subpage_name {
window.show_initial_subpage(subpage_name);
}
window.present();
}
}

2
src/session/view/content/room_history/mod.ui

@ -8,7 +8,7 @@
<attribute name="hidden-when">action-disabled</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Invite New Member</attribute>
<attribute name="label" translatable="yes">_Invite New Members</attribute>
<attribute name="action">room-history.invite-members</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>

Loading…
Cancel
Save