history-viewer: Implement AudioHistoryViewer
Also add it as a RoomDetails' subpage.
This commit is contained in:
parent
89ed564822
commit
a5ef975ff3
10 changed files with 390 additions and 2 deletions
|
@ -74,6 +74,8 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks" alias="components-reaction-chooser.ui">ui/components-reaction-chooser.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="components-toastable-window.ui">ui/components-toastable-window.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="components-video-player.ui">ui/components-video-player.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="content-audio-history-viewer-row.ui">ui/content-audio-history-viewer-row.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="content-audio-history-viewer.ui">ui/content-audio-history-viewer.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="content-completion-popover.ui">ui/content-completion-popover.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="content-completion-row.ui">ui/content-completion-row.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="content-divider-row.ui">ui/content-divider-row.ui</file>
|
||||
|
|
|
@ -666,6 +666,13 @@ filehistoryviewer listview > row {
|
|||
}
|
||||
|
||||
|
||||
/* Audio History Viewer */
|
||||
|
||||
audiohistoryviewer listview > row {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Room Details */
|
||||
|
||||
.room-details listview {
|
||||
|
|
45
data/resources/ui/content-audio-history-viewer-row.ui
Normal file
45
data/resources/ui/content-audio-history-viewer-row.ui
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="ContentAudioHistoryViewerRow" parent="AdwBin">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0</property>
|
||||
<style>
|
||||
<class name="heading"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="duration_label">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
<class name="numeric"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
66
data/resources/ui/content-audio-history-viewer.ui
Normal file
66
data/resources/ui/content-audio-history-viewer.ui
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="ContentAudioHistoryViewer" parent="AdwBin">
|
||||
<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">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>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="AdwClampScrollable">
|
||||
<property name="maximum-size">400</property>
|
||||
<property name="tightening-threshold">400</property>
|
||||
<child>
|
||||
<object class="GtkListView" id="list_view">
|
||||
<property name="show-separators">True</property>
|
||||
<property name="factory">
|
||||
<object class="GtkBuilderListItemFactory">
|
||||
<property name="bytes"><![CDATA[
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="GtkListItem">
|
||||
<property name="activatable">False</property>
|
||||
<property name="child">
|
||||
<object class="ContentAudioHistoryViewerRow">
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<binding name="event">
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
]]></property>
|
||||
</object>
|
||||
</property>
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -171,6 +171,21 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<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>
|
||||
|
|
|
@ -18,6 +18,7 @@ data/resources/ui/account-settings.ui
|
|||
data/resources/ui/attachment-dialog.ui
|
||||
data/resources/ui/components-auth-dialog.ui
|
||||
data/resources/ui/components-loading-listbox-row.ui
|
||||
data/resources/ui/content-audio-history-viewer.ui
|
||||
data/resources/ui/content-explore-servers-popover.ui
|
||||
data/resources/ui/content-explore.ui
|
||||
data/resources/ui/content-file-history-viewer.ui
|
||||
|
@ -74,6 +75,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/audio_row.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
|
||||
|
|
108
src/session/content/room_details/history_viewer/audio.rs
Normal file
108
src/session/content/room_details/history_viewer/audio.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::{glib, glib::clone, CompositeTemplate};
|
||||
|
||||
use crate::{
|
||||
session::{
|
||||
content::room_details::history_viewer::{AudioRow, 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-audio-history-viewer.ui")]
|
||||
pub struct AudioHistoryViewer {
|
||||
pub room_timeline: OnceCell<Timeline>,
|
||||
#[template_child]
|
||||
pub list_view: TemplateChild<gtk::ListView>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for AudioHistoryViewer {
|
||||
const NAME: &'static str = "ContentAudioHistoryViewer";
|
||||
type Type = super::AudioHistoryViewer;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
AudioRow::static_type();
|
||||
Self::bind_template(klass);
|
||||
|
||||
klass.set_css_name("audiohistoryviewer");
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for AudioHistoryViewer {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<Room>("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 AudioHistoryViewer {}
|
||||
impl BinImpl for AudioHistoryViewer {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct AudioHistoryViewer(ObjectSubclass<imp::AudioHistoryViewer>)
|
||||
@extends gtk::Widget, adw::Bin;
|
||||
}
|
||||
|
||||
impl AudioHistoryViewer {
|
||||
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::Audio);
|
||||
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()
|
||||
}
|
||||
}
|
119
src/session/content/room_details/history_viewer/audio_row.rs
Normal file
119
src/session/content/room_details/history_viewer/audio_row.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
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-audio-history-viewer-row.ui")]
|
||||
pub struct AudioRow {
|
||||
pub event: RefCell<Option<HistoryViewerEvent>>,
|
||||
#[template_child]
|
||||
pub title_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub duration_label: TemplateChild<gtk::Label>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for AudioRow {
|
||||
const NAME: &'static str = "ContentAudioHistoryViewerRow";
|
||||
type Type = super::AudioRow;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
Self::bind_template(klass);
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for AudioRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<HistoryViewerEvent>("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 AudioRow {}
|
||||
impl BinImpl for AudioRow {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct AudioRow(ObjectSubclass<imp::AudioRow>)
|
||||
@extends gtk::Widget, adw::Bin;
|
||||
}
|
||||
|
||||
impl AudioRow {
|
||||
pub fn set_event(&self, event: Option<HistoryViewerEvent>) {
|
||||
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::Audio(audio) = content.msgtype {
|
||||
imp.title_label.set_label(&audio.body);
|
||||
|
||||
if let Some(duration) = audio.info.and_then(|i| i.duration) {
|
||||
let duration_secs = duration.as_secs();
|
||||
let secs = duration_secs % 60;
|
||||
let mins = (duration_secs % (60 * 60)) / 60;
|
||||
let hours = duration_secs / (60 * 60);
|
||||
|
||||
let duration = if hours > 0 {
|
||||
format!("{hours:02}:{mins:02}:{secs:02}")
|
||||
} else {
|
||||
format!("{mins:02}:{secs:02}")
|
||||
};
|
||||
|
||||
imp.duration_label.set_label(&duration);
|
||||
} else {
|
||||
imp.duration_label.set_label(&gettext("Unknown duration"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imp.event.replace(event);
|
||||
self.notify("event");
|
||||
}
|
||||
|
||||
pub fn event(&self) -> Option<HistoryViewerEvent> {
|
||||
self.imp().event.borrow().clone()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
mod audio;
|
||||
mod audio_row;
|
||||
mod event;
|
||||
mod file;
|
||||
mod file_row;
|
||||
|
@ -5,10 +7,11 @@ mod media;
|
|||
mod media_item;
|
||||
mod timeline;
|
||||
|
||||
pub use self::{audio::AudioHistoryViewer, file::FileHistoryViewer, media::MediaHistoryViewer};
|
||||
use self::{
|
||||
audio_row::AudioRow,
|
||||
event::HistoryViewerEvent,
|
||||
file_row::FileRow,
|
||||
media_item::MediaItem,
|
||||
timeline::{Timeline, TimelineFilter},
|
||||
};
|
||||
pub use self::{file::FileHistoryViewer, media::MediaHistoryViewer};
|
||||
|
|
|
@ -15,7 +15,9 @@ use crate::{
|
|||
components::ToastableWindow,
|
||||
prelude::*,
|
||||
session::{
|
||||
content::room_details::history_viewer::{FileHistoryViewer, MediaHistoryViewer},
|
||||
content::room_details::history_viewer::{
|
||||
AudioHistoryViewer, FileHistoryViewer, MediaHistoryViewer,
|
||||
},
|
||||
Room,
|
||||
},
|
||||
};
|
||||
|
@ -31,6 +33,7 @@ pub enum PageName {
|
|||
Invite,
|
||||
MediaHistory,
|
||||
FileHistory,
|
||||
AudioHistory,
|
||||
}
|
||||
|
||||
impl glib::variant::StaticVariantType for PageName {
|
||||
|
@ -47,6 +50,7 @@ impl glib::variant::FromVariant for PageName {
|
|||
"invite" => Some(PageName::Invite),
|
||||
"media-history" => Some(PageName::MediaHistory),
|
||||
"file-history" => Some(PageName::FileHistory),
|
||||
"audio-history" => Some(PageName::AudioHistory),
|
||||
"" => Some(PageName::None),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -62,6 +66,7 @@ impl glib::variant::ToVariant for PageName {
|
|||
PageName::Invite => "invite",
|
||||
PageName::MediaHistory => "media-history",
|
||||
PageName::FileHistory => "file-history",
|
||||
PageName::AudioHistory => "audio-history",
|
||||
}
|
||||
.to_variant()
|
||||
}
|
||||
|
@ -279,6 +284,22 @@ impl RoomDetails {
|
|||
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
|
||||
};
|
||||
|
||||
self.set_title(Some(&gettext("Audio")));
|
||||
imp.main_stack.set_visible_child(&audio_page);
|
||||
}
|
||||
PageName::None => {
|
||||
warn!("Can't switch to PageName::None");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue