Browse Source

media: Do not use voice message filename in UI

The filename is usually randomly generated so it is not very useful to
identify the audio message. Instead we default to "Voice Message", and
use the timestamp when saving it to a file.
fractal-13
Kévin Commaille 6 months ago
parent
commit
722e2c88fb
No known key found for this signature in database
GPG Key ID: F26F4BE20A08255B
  1. 2
      src/components/media/audio_player/mod.blp
  2. 20
      src/components/media/audio_player/mod.rs
  3. 7
      src/session/view/content/room_details/history_viewer/event.rs
  4. 4
      src/session/view/content/room_details/history_viewer/file_row.rs
  5. 4
      src/session/view/content/room_history/event_actions/group.rs
  6. 2
      src/session/view/content/room_history/message_row/audio.blp
  7. 22
      src/session/view/content/room_history/message_row/audio.rs
  8. 2
      src/session/view/content/room_history/message_row/content.rs
  9. 9
      src/session/view/media_viewer.rs
  10. 82
      src/utils/matrix/media_message.rs

2
src/components/media/audio_player/mod.blp

@ -65,7 +65,7 @@ template $AudioPlayer: Adw.BreakpointBin {
}; };
} }
Gtk.Label filename_label { Gtk.Label name_label {
hexpand: true; hexpand: true;
xalign: 0.0; xalign: 0.0;
ellipsize: end; ellipsize: end;

20
src/components/media/audio_player/mod.rs

@ -49,7 +49,7 @@ mod imp {
#[template_child] #[template_child]
play_button: TemplateChild<gtk::Button>, play_button: TemplateChild<gtk::Button>,
#[template_child] #[template_child]
filename_label: TemplateChild<gtk::Label>, name_label: TemplateChild<gtk::Label>,
#[template_child] #[template_child]
position_label_narrow: TemplateChild<gtk::Label>, position_label_narrow: TemplateChild<gtk::Label>,
/// The source to play. /// The source to play.
@ -172,7 +172,7 @@ mod imp {
} }
)); ));
self.update_source_filename(); self.update_source_name();
} }
self.update_play_button(); self.update_play_button();
@ -206,7 +206,7 @@ mod imp {
self.position_label.set_visible(!narrow); self.position_label.set_visible(!narrow);
self.remaining_label.set_visible(!narrow); self.remaining_label.set_visible(!narrow);
self.filename_label.set_visible(!standalone); self.name_label.set_visible(!standalone);
self.position_label_narrow self.position_label_narrow
.set_visible(narrow && !standalone); .set_visible(narrow && !standalone);
@ -284,15 +284,15 @@ mod imp {
} }
/// Update the name of the source. /// Update the name of the source.
fn update_source_filename(&self) { fn update_source_name(&self) {
let filename = self let name = self
.source .source
.borrow() .borrow()
.as_ref() .as_ref()
.map(AudioPlayerSource::filename) .map(AudioPlayerSource::name)
.unwrap_or_default(); .unwrap_or_default();
self.filename_label.set_label(&filename); self.name_label.set_label(&name);
} }
/// Update the labels displaying the position in the audio stream. /// Update the labels displaying the position in the audio stream.
@ -542,14 +542,14 @@ pub(crate) enum AudioPlayerSource {
} }
impl AudioPlayerSource { impl AudioPlayerSource {
/// Get the filename of the source. /// Get the name of the source.
fn filename(&self) -> String { fn name(&self) -> String {
match self { match self {
Self::File(file) => file Self::File(file) => file
.path() .path()
.and_then(|path| path.file_name().map(|s| s.to_string_lossy().into_owned())) .and_then(|path| path.file_name().map(|s| s.to_string_lossy().into_owned()))
.unwrap_or_default(), .unwrap_or_default(),
Self::Message(message) => message.message.filename(), Self::Message(message) => message.message.display_name(),
} }
} }

7
src/session/view/content/room_details/history_viewer/event.rs

@ -10,7 +10,7 @@ use ruma::{
use crate::{ use crate::{
session::model::Room, session::model::Room,
utils::matrix::{MediaMessage, VisualMediaMessage}, utils::matrix::{MediaMessage, VisualMediaMessage, timestamp_to_date},
}; };
/// The types of events that can be displayed in the history viewers. /// The types of events that can be displayed in the history viewers.
@ -141,6 +141,11 @@ impl HistoryViewerEvent {
self.matrix_event().event_id.clone() self.matrix_event().event_id.clone()
} }
/// The timestamp of this event, as a `GDateTime`.
pub(crate) fn timestamp(&self) -> glib::DateTime {
timestamp_to_date(self.matrix_event().origin_server_ts)
}
/// The media message content of this event. /// The media message content of this event.
pub(crate) fn media_message(&self) -> MediaMessage { pub(crate) fn media_message(&self) -> MediaMessage {
MediaMessage::from_message(&self.matrix_event().content.msgtype) MediaMessage::from_message(&self.matrix_event().content.msgtype)

4
src/session/view/content/room_details/history_viewer/file_row.rs

@ -64,7 +64,7 @@ mod imp {
if let Some(event) = &event { if let Some(event) = &event {
let media_message = event.media_message(); let media_message = event.media_message();
if let MediaMessage::File(file) = &media_message { if let MediaMessage::File(file) = &media_message {
let filename = media_message.filename(); let filename = media_message.filename(&event.timestamp());
self.title_label.set_label(&filename); self.title_label.set_label(&filename);
self.button self.button
@ -136,7 +136,7 @@ mod imp {
return; return;
} }
}; };
let filename = event.media_message().filename(); let filename = event.media_message().filename(&event.timestamp());
let parent_window = obj.root().and_downcast::<gtk::Window>(); let parent_window = obj.root().and_downcast::<gtk::Window>();
let dialog = gtk::FileDialog::builder() let dialog = gtk::FileDialog::builder()

4
src/session/view/content/room_history/event_actions/group.rs

@ -553,7 +553,9 @@ pub(crate) trait EventActionsGroup: ObjectSubclass {
}; };
let client = session.client(); let client = session.client();
media_message.save_to_file(&client, &*self.obj()).await; media_message
.save_to_file(&event.timestamp(), &client, &*self.obj())
.await;
} }
/// Redact the event of this row. /// Redact the event of this row.

2
src/session/view/content/room_history/message_row/audio.blp

@ -16,7 +16,7 @@ template $ContentMessageAudio: Gtk.Box {
ellipsize: end; ellipsize: end;
xalign: 0.0; xalign: 0.0;
hexpand: true; hexpand: true;
label: bind template.filename; label: bind template.name;
} }
} }

22
src/session/view/content/room_history/message_row/audio.rs

@ -22,9 +22,9 @@ mod imp {
pub struct MessageAudio { pub struct MessageAudio {
#[template_child] #[template_child]
player: TemplateChild<AudioPlayer>, player: TemplateChild<AudioPlayer>,
/// The filename of the audio file. /// The name of the audio file.
#[property(get)] #[property(get)]
filename: RefCell<String>, name: RefCell<String>,
/// Whether to display this audio message in a compact format. /// Whether to display this audio message in a compact format.
#[property(get)] #[property(get)]
compact: Cell<bool>, compact: Cell<bool>,
@ -52,24 +52,24 @@ mod imp {
impl BoxImpl for MessageAudio {} impl BoxImpl for MessageAudio {}
impl MessageAudio { impl MessageAudio {
/// Set the filename of the audio file. /// Set the name of the audio file.
fn set_filename(&self, filename: Option<String>) { fn set_name(&self, name: Option<String>) {
let filename = filename.unwrap_or_default(); let name = name.unwrap_or_default();
if *self.filename.borrow() == filename { if *self.name.borrow() == name {
return; return;
} }
let obj = self.obj(); let obj = self.obj();
let accessible_label = if filename.is_empty() { let accessible_label = if name.is_empty() {
gettext("Audio") gettext("Audio")
} else { } else {
gettext_f("Audio: {filename}", &[("filename", &filename)]) gettext_f("Audio: {filename}", &[("filename", &name)])
}; };
obj.update_property(&[gtk::accessible::Property::Label(&accessible_label)]); obj.update_property(&[gtk::accessible::Property::Label(&accessible_label)]);
self.filename.replace(filename); self.name.replace(name);
obj.notify_filename(); obj.notify_name();
} }
/// Set the compact format of this audio message. /// Set the compact format of this audio message.
@ -82,7 +82,7 @@ mod imp {
/// Display the given `audio` message. /// Display the given `audio` message.
pub(super) fn set_audio_message(&self, message: AudioPlayerMessage, format: ContentFormat) { pub(super) fn set_audio_message(&self, message: AudioPlayerMessage, format: ContentFormat) {
self.set_filename(Some(message.message.filename())); self.set_name(Some(message.message.display_name()));
let compact = matches!(format, ContentFormat::Compact | ContentFormat::Ellipsized); let compact = matches!(format, ContentFormat::Compact | ContentFormat::Ellipsized);
self.set_compact(compact); self.set_compact(compact);

2
src/session/view/content/room_history/message_row/content.rs

@ -449,7 +449,7 @@ trait MessageContentContainer: ChildPropertyExt {
let widget = self.child_or_default::<MessageFile>(); let widget = self.child_or_default::<MessageFile>();
let media_message = MediaMessage::from(file); let media_message = MediaMessage::from(file);
widget.set_filename(Some(media_message.filename())); widget.set_filename(Some(media_message.display_name()));
widget.set_format(format); widget.set_format(format);
} }
MediaMessage::Image(image) => { MediaMessage::Image(image) => {

9
src/session/view/media_viewer.rs

@ -491,7 +491,14 @@ mod imp {
}; };
let client = session.client(); let client = session.client();
media_message.save_to_file(&client, &*self.obj()).await; media_message
.save_to_file(
// The timestamp should be unused for visual media messages.
&glib::DateTime::now_local().expect("Getting local time should work"),
&client,
&*self.obj(),
)
.await;
} }
/// Copy the permalink of the event of the media message to the /// Copy the permalink of the event of the media message to the

82
src/utils/matrix/media_message.rs

@ -1,5 +1,5 @@
use gettextrs::gettext; use gettextrs::gettext;
use gtk::{gio, prelude::*}; use gtk::{gio, glib, prelude::*};
use matrix_sdk::Client; use matrix_sdk::Client;
use ruma::events::{ use ruma::events::{
room::message::{ room::message::{
@ -12,6 +12,7 @@ use tracing::{debug, error};
use crate::{ use crate::{
components::ContentType, components::ContentType,
gettext_f,
prelude::*, prelude::*,
toast, toast,
utils::{ utils::{
@ -74,12 +75,63 @@ impl MediaMessage {
} }
} }
/// The filename of the media. /// The name of the media, as displayed in the interface.
/// ///
/// For a sticker, this returns the description of the sticker. /// This is usually the filename in the message, except:
pub(crate) fn filename(&self) -> String { ///
/// - For a voice message, it's a placeholder string because file names are
/// usually generated randomly.
/// - For a sticker, this returns the description of the sticker, because
/// they do not have a filename.
pub(crate) fn display_name(&self) -> String {
match self {
Self::Audio(c) => {
if c.voice.is_some() {
gettext("Voice Message")
} else {
filename!(c, Some(mime::AUDIO))
}
}
Self::File(c) => filename!(c, None),
Self::Image(c) => filename!(c, Some(mime::IMAGE)),
Self::Video(c) => filename!(c, Some(mime::VIDEO)),
Self::Sticker(c) => c.body.clone(),
}
}
/// The filename of the media, used when saving the file.
///
/// This is usually the filename in the message, except:
///
/// - For a voice message, it's a generated name that uses the timestamp of
/// the message.
/// - For a sticker, this returns the description of the sticker, because
/// they do not have a filename.
pub(crate) fn filename(&self, timestamp: &glib::DateTime) -> String {
match self { match self {
Self::Audio(c) => filename!(c, Some(mime::AUDIO)), Self::Audio(c) => {
let mut filename = filename!(c, Some(mime::AUDIO));
if c.voice.is_some() {
let datetime = timestamp
.to_local()
.and_then(|local_timestamp| local_timestamp.format("%Y-%m-%d %H-%M-%S"))
// Fallback to the timestamp in seconds.
.map_or_else(|_| timestamp.second().to_string(), String::from);
// Translators: this is the name of the file that the voice message is saved as.
// Do NOT translate the content between '{' and '}', this is a variable name
// corresponding to a date and time, e.g. "2017-05-21 12-24-03".
let name =
gettext_f("Voice Message From {datetime}", &[("datetime", &datetime)]);
filename = filename
.rsplit_once('.')
.map(|(_, extension)| format!("{name}.{extension}"))
.unwrap_or(name);
}
filename
}
Self::File(c) => filename!(c, None), Self::File(c) => filename!(c, None),
Self::Image(c) => filename!(c, Some(mime::IMAGE)), Self::Image(c) => filename!(c, Some(mime::IMAGE)),
Self::Video(c) => filename!(c, Some(mime::VIDEO)), Self::Video(c) => filename!(c, Some(mime::VIDEO)),
@ -157,8 +209,13 @@ impl MediaMessage {
/// Save the content of the media to a file selected by the user. /// Save the content of the media to a file selected by the user.
/// ///
/// Shows a dialog to the user to select a file on the system. /// Shows a dialog to the user to select a file on the system.
pub(crate) async fn save_to_file(self, client: &Client, parent: &impl IsA<gtk::Widget>) { pub(crate) async fn save_to_file(
let filename = self.filename(); self,
timestamp: &glib::DateTime,
client: &Client,
parent: &impl IsA<gtk::Widget>,
) {
let filename = self.filename(timestamp);
let data = match self.into_content(client).await { let data = match self.into_content(client).await {
Ok(data) => data, Ok(data) => data,
@ -374,8 +431,15 @@ impl VisualMediaMessage {
/// Save the content of the media to a file selected by the user. /// Save the content of the media to a file selected by the user.
/// ///
/// Shows a dialog to the user to select a file on the system. /// Shows a dialog to the user to select a file on the system.
pub(crate) async fn save_to_file(self, client: &Client, parent: &impl IsA<gtk::Widget>) { pub(crate) async fn save_to_file(
MediaMessage::from(self).save_to_file(client, parent).await; self,
timestamp: &glib::DateTime,
client: &Client,
parent: &impl IsA<gtk::Widget>,
) {
MediaMessage::from(self)
.save_to_file(timestamp, client, parent)
.await;
} }
} }

Loading…
Cancel
Save