From 89ed5648221d88fc8660af075a20dac352aa06e3 Mon Sep 17 00:00:00 2001 From: Marco Melorio Date: Thu, 11 Aug 2022 19:16:41 +0200 Subject: [PATCH] history-viewer: Implement FileHistoryViewer Also add it as a RoomDetails' subpage. --- data/resources/resources.gresource.xml | 2 + data/resources/style.css | 7 ++ .../ui/content-file-history-viewer-row.ui | 45 ++++++++ .../ui/content-file-history-viewer.ui | 66 +++++++++++ .../ui/content-room-details-general-page.ui | 15 +++ po/POTFILES.in | 2 + .../room_details/history_viewer/file.rs | 108 +++++++++++++++++ .../room_details/history_viewer/file_row.rs | 109 ++++++++++++++++++ .../room_details/history_viewer/mod.rs | 5 +- src/session/content/room_details/mod.rs | 24 +++- 10 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 data/resources/ui/content-file-history-viewer-row.ui create mode 100644 data/resources/ui/content-file-history-viewer.ui create mode 100644 src/session/content/room_details/history_viewer/file.rs create mode 100644 src/session/content/room_details/history_viewer/file_row.rs diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index a1de5846..56621076 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -81,6 +81,8 @@ ui/content-explore-server-row.ui ui/content-explore-servers-popover.ui ui/content-explore.ui + ui/content-file-history-viewer-row.ui + ui/content-file-history-viewer.ui ui/content-invite-subpage.ui ui/content-invite.ui ui/content-invitee-item.ui diff --git a/data/resources/style.css b/data/resources/style.css index 33c1c902..69649669 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -659,6 +659,13 @@ mediahistoryvieweritem > overlay > image { } +/* File History Viewer */ + +filehistoryviewer listview > row { + border-radius: 0; +} + + /* Room Details */ .room-details listview { diff --git a/data/resources/ui/content-file-history-viewer-row.ui b/data/resources/ui/content-file-history-viewer-row.ui new file mode 100644 index 00000000..ea0c619d --- /dev/null +++ b/data/resources/ui/content-file-history-viewer-row.ui @@ -0,0 +1,45 @@ + + + + diff --git a/data/resources/ui/content-file-history-viewer.ui b/data/resources/ui/content-file-history-viewer.ui new file mode 100644 index 00000000..a9cfcb37 --- /dev/null +++ b/data/resources/ui/content-file-history-viewer.ui @@ -0,0 +1,66 @@ + + + + diff --git a/data/resources/ui/content-room-details-general-page.ui b/data/resources/ui/content-room-details-general-page.ui index 625cf0ac..b5a31780 100644 --- a/data/resources/ui/content-room-details-general-page.ui +++ b/data/resources/ui/content-room-details-general-page.ui @@ -156,6 +156,21 @@ + + + File + details.next-page + 'file-history' + True + + + center + center + go-next-symbolic + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 1f1b68db..a9debd9f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,6 +20,7 @@ data/resources/ui/components-auth-dialog.ui data/resources/ui/components-loading-listbox-row.ui data/resources/ui/content-explore-servers-popover.ui data/resources/ui/content-explore.ui +data/resources/ui/content-file-history-viewer.ui data/resources/ui/content-invite-subpage.ui data/resources/ui/content-invite.ui data/resources/ui/content-media-history-viewer.ui @@ -73,6 +74,7 @@ src/session/account_settings/user_page/mod.rs src/session/content/explore/public_room_row.rs src/session/content/invite.rs src/session/content/room_details/general_page/mod.rs +src/session/content/room_details/history_viewer/file_row.rs src/session/content/room_details/invite_subpage/invitee_list.rs src/session/content/room_details/member_page/mod.rs src/session/content/room_details/mod.rs diff --git a/src/session/content/room_details/history_viewer/file.rs b/src/session/content/room_details/history_viewer/file.rs new file mode 100644 index 00000000..e984b9f9 --- /dev/null +++ b/src/session/content/room_details/history_viewer/file.rs @@ -0,0 +1,108 @@ +use adw::{prelude::*, subclass::prelude::*}; +use gtk::{glib, glib::clone, CompositeTemplate}; + +use crate::{ + session::{ + content::room_details::history_viewer::{FileRow, Timeline, TimelineFilter}, + Room, + }, + spawn, +}; + +const MIN_N_ITEMS: u32 = 20; + +mod imp { + use glib::subclass::InitializingObject; + use once_cell::{sync::Lazy, unsync::OnceCell}; + + use super::*; + + #[derive(Debug, Default, CompositeTemplate)] + #[template(resource = "/org/gnome/Fractal/content-file-history-viewer.ui")] + pub struct FileHistoryViewer { + pub room_timeline: OnceCell, + #[template_child] + pub list_view: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for FileHistoryViewer { + const NAME: &'static str = "ContentFileHistoryViewer"; + type Type = super::FileHistoryViewer; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + FileRow::static_type(); + Self::bind_template(klass); + + klass.set_css_name("filehistoryviewer"); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for FileHistoryViewer { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpecObject::builder::("room") + .construct_only() + .build()] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "room" => self.obj().set_room(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "room" => self.obj().room().to_value(), + _ => unimplemented!(), + } + } + } + + impl WidgetImpl for FileHistoryViewer {} + impl BinImpl for FileHistoryViewer {} +} + +glib::wrapper! { + pub struct FileHistoryViewer(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} + +impl FileHistoryViewer { + pub fn new(room: &Room) -> Self { + glib::Object::builder().property("room", room).build() + } + + fn set_room(&self, room: &Room) { + let imp = self.imp(); + + let timeline = Timeline::new(room, TimelineFilter::Files); + let model = gtk::NoSelection::new(Some(timeline.clone())); + imp.list_view.set_model(Some(&model)); + + // Load an initial number of items + spawn!(clone!(@weak timeline => async move { + while timeline.n_items() < MIN_N_ITEMS { + if !timeline.load().await { + break; + } + } + })); + + imp.room_timeline.set(timeline).unwrap(); + } + + pub fn room(&self) -> &Room { + self.imp().room_timeline.get().unwrap().room() + } +} diff --git a/src/session/content/room_details/history_viewer/file_row.rs b/src/session/content/room_details/history_viewer/file_row.rs new file mode 100644 index 00000000..d624f44c --- /dev/null +++ b/src/session/content/room_details/history_viewer/file_row.rs @@ -0,0 +1,109 @@ +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::{glib, CompositeTemplate}; +use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventContent}; + +use crate::session::content::room_details::history_viewer::HistoryViewerEvent; + +mod imp { + use std::cell::RefCell; + + use glib::subclass::InitializingObject; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Debug, Default, CompositeTemplate)] + #[template(resource = "/org/gnome/Fractal/content-file-history-viewer-row.ui")] + pub struct FileRow { + pub event: RefCell>, + #[template_child] + pub title_label: TemplateChild, + #[template_child] + pub size_label: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for FileRow { + const NAME: &'static str = "ContentFileHistoryViewerRow"; + type Type = super::FileRow; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for FileRow { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecObject::builder::("event") + .explicit_notify() + .build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "event" => self.obj().set_event(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "event" => self.obj().event().to_value(), + _ => unimplemented!(), + } + } + } + + impl WidgetImpl for FileRow {} + impl BinImpl for FileRow {} +} + +glib::wrapper! { + pub struct FileRow(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} + +impl FileRow { + pub fn set_event(&self, event: Option) { + let imp = self.imp(); + + if self.event() == event { + return; + } + + if let Some(ref event) = event { + if let Some(AnyMessageLikeEventContent::RoomMessage(content)) = event.original_content() + { + if let MessageType::File(file) = content.msgtype { + imp.title_label.set_label(&file.body); + + if let Some(size) = file.info.and_then(|i| i.size) { + let size = glib::format_size(size.into()); + imp.size_label.set_label(&size); + } else { + imp.size_label.set_label(&gettext("Unknown size")); + } + } + } + } + + imp.event.replace(event); + self.notify("event"); + } + + pub fn event(&self) -> Option { + self.imp().event.borrow().clone() + } +} diff --git a/src/session/content/room_details/history_viewer/mod.rs b/src/session/content/room_details/history_viewer/mod.rs index 3e5ac2ad..0aae7670 100644 --- a/src/session/content/room_details/history_viewer/mod.rs +++ b/src/session/content/room_details/history_viewer/mod.rs @@ -1,11 +1,14 @@ mod event; +mod file; +mod file_row; mod media; mod media_item; mod timeline; -pub use self::media::MediaHistoryViewer; use self::{ event::HistoryViewerEvent, + file_row::FileRow, media_item::MediaItem, timeline::{Timeline, TimelineFilter}, }; +pub use self::{file::FileHistoryViewer, media::MediaHistoryViewer}; diff --git a/src/session/content/room_details/mod.rs b/src/session/content/room_details/mod.rs index 002d6da3..12540fcb 100644 --- a/src/session/content/room_details/mod.rs +++ b/src/session/content/room_details/mod.rs @@ -14,7 +14,10 @@ pub use self::{general_page::GeneralPage, invite_subpage::InviteSubpage, member_ use crate::{ components::ToastableWindow, prelude::*, - session::{content::room_details::history_viewer::MediaHistoryViewer, Room}, + session::{ + content::room_details::history_viewer::{FileHistoryViewer, MediaHistoryViewer}, + Room, + }, }; #[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)] @@ -27,6 +30,7 @@ pub enum PageName { Members, Invite, MediaHistory, + FileHistory, } impl glib::variant::StaticVariantType for PageName { @@ -42,6 +46,7 @@ impl glib::variant::FromVariant for PageName { "members" => Some(PageName::Members), "invite" => Some(PageName::Invite), "media-history" => Some(PageName::MediaHistory), + "file-history" => Some(PageName::FileHistory), "" => Some(PageName::None), _ => None, } @@ -56,6 +61,7 @@ impl glib::variant::ToVariant for PageName { PageName::Members => "members", PageName::Invite => "invite", PageName::MediaHistory => "media-history", + PageName::FileHistory => "file-history", } .to_variant() } @@ -257,6 +263,22 @@ impl RoomDetails { 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::(); + 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::None => { warn!("Can't switch to PageName::None"); }