10 changed files with 390 additions and 2 deletions
@ -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> |
||||
@ -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> |
||||
@ -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() |
||||
} |
||||
} |
||||
@ -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() |
||||
} |
||||
} |
||||
Loading…
Reference in new issue