diff --git a/data/resources/style-dark.css b/data/resources/style-dark.css index e932ba802..815f73aea 100644 --- a/data/resources/style-dark.css +++ b/data/resources/style-dark.css @@ -19,6 +19,10 @@ messagebubble.outgoing { background-color: #2c52ac; } +messagebubble.document.outgoing .file > overlay > statusindicator { + color: white; +} + .event-row { background-color: alpha(#404040, 0.8); } diff --git a/data/resources/style.css b/data/resources/style.css index 80198e73e..1c9574c9d 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -196,32 +196,30 @@ messagebubble.document .file > overlay { min-height: 40px; } -messagebubble.document .file > overlay > image { - background: @accent_color; - color: @window_bg_color; - transition: opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); +messagebubble.document .file > overlay > statusindicator > image { padding: 12px; +} + +messagebubble.document .file > overlay > statusindicator { + color: @accent_color; + transition: opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); border-radius: 9999px; } -messagebubble.document.outgoing .file > overlay > image { - background: @accent_fg_color; - color: @accent_bg_color; +messagebubble.document.outgoing .file > overlay > statusindicator { + /* depends on bubble color */ + color: #79c271; } -messagebubble.document .file:hover > overlay > image { +messagebubble.document .file:hover > overlay > statusindicator { opacity: 0.85; } -messagebubble.document .file:active > overlay > image { +messagebubble.document .file:active > overlay > statusindicator { opacity: 0.7; } -messagebubble.document .file.with-thumbnail > overlay > image { - background-color: alpha(black, 0.6); - color: white; -} -messagebubble.document .file.with-thumbnail > overlay > image { +messagebubble.document .file.with-thumbnail > overlay > statusindicator { background-color: alpha(black, 0.6); color: white; } @@ -251,16 +249,6 @@ messagebubble.media.with-reply mediapicture { margin-top: 3px; } -messagebubble.document .file.with-thumbnail > overlay > image { - background-color: alpha(black, 0.6); - color: white; -} - -messagebubble.document .file.with-thumbnail > overlay { - min-width: 60px; - min-height: 60px; -} - messagebubble.document .file.with-thumbnail > overlay > picture { border-radius: 6px; } diff --git a/data/resources/ui/content-message-document.blp b/data/resources/ui/content-message-document.blp index 6fbba86af..cd4f3714d 100644 --- a/data/resources/ui/content-message-document.blp +++ b/data/resources/ui/content-message-document.blp @@ -22,7 +22,7 @@ template $MessageDocument : $MessageBase { } [overlay] - Image file_status_image { + $MessageDocumentStatusIndicator status_indicator { halign: center; valign: center; } diff --git a/src/session/content/message_row/document/file_status.rs b/src/session/content/message_row/document/file_status.rs new file mode 100644 index 000000000..a0b1934c4 --- /dev/null +++ b/src/session/content/message_row/document/file_status.rs @@ -0,0 +1,34 @@ +use tdlib::types::File; +use FileStatus::*; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum FileStatus { + Downloading(f64), + Uploading(f64), + CanBeDownloaded, + Downloaded, +} + +impl From<&File> for FileStatus { + fn from(file: &File) -> Self { + let local = &file.local; + let remote = &file.remote; + + let size = file.size.max(file.expected_size) as u64; + + if local.is_downloading_active { + let progress = local.downloaded_size as f64 / size as f64; + Downloading(progress) + } else if remote.is_uploading_active { + let progress = remote.uploaded_size as f64 / size as f64; + Uploading(progress) + } else if local.is_downloading_completed { + Downloaded + } else if local.can_be_downloaded { + CanBeDownloaded + } else { + dbg!(file); + unimplemented!("unknown file status"); + } + } +} diff --git a/src/session/content/message_row/document.rs b/src/session/content/message_row/document/mod.rs similarity index 85% rename from src/session/content/message_row/document.rs rename to src/session/content/message_row/document/mod.rs index 1792f822e..5f9d2e21d 100644 --- a/src/session/content/message_row/document.rs +++ b/src/session/content/message_row/document/mod.rs @@ -1,5 +1,9 @@ use std::cell::RefCell; +mod file_status; +mod status_indicator; +use file_status::FileStatus; +use file_status::FileStatus::*; use glib::clone; use gtk::gdk; use gtk::gio; @@ -8,6 +12,7 @@ use gtk::prelude::*; use gtk::subclass::prelude::*; use gtk::CompositeTemplate; use once_cell::sync::Lazy; +use status_indicator::StatusIndicator; use tdlib::enums::MessageContent; use tdlib::types::File; @@ -39,7 +44,7 @@ mod imp { #[template_child] pub(super) file_thumbnail_picture: TemplateChild, #[template_child] - pub(super) file_status_image: TemplateChild, + pub(super) status_indicator: TemplateChild, #[template_child] pub(super) file_name_label: TemplateChild, #[template_child] @@ -128,39 +133,6 @@ impl MessageBaseExt for MessageDocument { } } -#[derive(PartialEq)] -enum FileStatus { - Downloading(f64), - Uploading(f64), - CanBeDownloaded, - Downloaded, -} -use FileStatus::*; - -impl From<&File> for FileStatus { - fn from(file: &File) -> Self { - let local = &file.local; - let remote = &file.remote; - - let size = file.size.max(file.expected_size) as u64; - - if local.is_downloading_active { - let progress = local.downloaded_size as f64 / size as f64; - Downloading(progress) - } else if remote.is_uploading_active { - let progress = remote.uploaded_size as f64 / size as f64; - Uploading(progress) - } else if local.is_downloading_completed { - Downloaded - } else if local.can_be_downloaded { - CanBeDownloaded - } else { - dbg!(file); - unimplemented!("unknown file status"); - } - } -} - impl MessageDocument { fn update_document(&self, message: &Message) { if let MessageContent::MessageDocument(data) = message.content().0 { @@ -183,26 +155,26 @@ impl MessageDocument { let size = file.size.max(file.expected_size) as u64; - self.update_size_label(&status, size); - self.update_button(file, session, &status); + self.update_size_label(status, size); + self.update_button(file, session, status); status } - fn update_button(&self, file: File, session: Session, status: &FileStatus) { + fn update_button(&self, file: File, session: Session, status: FileStatus) { let imp = self.imp(); let click = &*imp.click; - let image = &*imp.file_status_image; + let indicator = &*imp.status_indicator; let file_id = file.id; - let handler_id = match *status { + let handler_id = match status { Downloading(_progress) | Uploading(_progress) => { return; // Show loading indicator } CanBeDownloaded => { // Download file - image.set_icon_name(Some("document-save-symbolic")); + indicator.set_status(CanBeDownloaded); click.connect_released(clone!(@weak self as obj, @weak session => move |click, _, _, _| { // TODO: Fix bug mentioned here // https://github.com/paper-plane-developers/paper-plane/pull/372#discussion_r968841370 @@ -210,7 +182,7 @@ impl MessageDocument { obj.update_status(file, session); })); - obj.imp().file_status_image.set_icon_name(Some("media-playback-stop-symbolic")); + obj.imp().status_indicator.set_status(Downloading(0.0)); let handler_id = click.connect_released(clone!(@weak session => move |_, _, _, _| { session.cancel_download_file(file_id); })); @@ -221,9 +193,9 @@ impl MessageDocument { } Downloaded => { // Open file - image.set_icon_name(Some("folder-documents-symbolic")); + indicator.set_status(Downloaded); if imp.file_thumbnail_picture.file().is_some() { - image.set_visible(false); + indicator.set_visible(false); } let gio_file = gio::File::for_path(&file.local.path); click.connect_released(move |_, _, _, _| { @@ -242,7 +214,7 @@ impl MessageDocument { } } - fn update_size_label(&self, status: &FileStatus, size: u64) { + fn update_size_label(&self, status: FileStatus, size: u64) { let size_label = &self.imp().file_size_label; match status { @@ -262,6 +234,7 @@ impl MessageDocument { if let MessageContent::MessageDocument(data) = message.content().0 { let imp = self.imp(); if let Some(thumbnail) = data.document.thumbnail { + imp.status_indicator.set_masked(false); imp.file_thumbnail_picture.set_visible(true); imp.file_box.add_css_class("with-thumbnail"); if thumbnail.file.local.is_downloading_completed { @@ -288,7 +261,8 @@ impl MessageDocument { })); } } else { - imp.file_thumbnail_picture.set_visible(true); + imp.status_indicator.set_masked(true); + imp.file_thumbnail_picture.set_visible(false); imp.file_thumbnail_picture .set_paintable(gdk::Paintable::NONE); } diff --git a/src/session/content/message_row/document/status_indicator.rs b/src/session/content/message_row/document/status_indicator.rs new file mode 100644 index 000000000..c72a7500c --- /dev/null +++ b/src/session/content/message_row/document/status_indicator.rs @@ -0,0 +1,117 @@ +use std::cell::Cell; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use gtk::glib; +use gtk::gsk; +use gtk::CompositeTemplate; + +use super::file_status::FileStatus; + +mod imp { + use super::*; + + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] + #[template(string = r#" + template $MessageDocumentStatusIndicator { + layout-manager: BinLayout {}; + overflow: hidden; + + Image status_image {} + } + "#)] + #[properties(wrapper_type = super::StatusIndicator)] + pub(crate) struct StatusIndicator { + #[property(get, set = Self::set_masked, explicit_notify)] + pub(super) masked: Cell, + #[template_child] + pub(super) status_image: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for StatusIndicator { + const NAME: &'static str = "MessageDocumentStatusIndicator"; + type Type = super::StatusIndicator; + type ParentType = gtk::Widget; + + fn class_init(klass: &mut Self::Class) { + klass.set_css_name("statusindicator"); + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for StatusIndicator { + fn properties() -> &'static [glib::ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + self.derived_set_property(id, value, pspec) + } + + fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value { + self.derived_property(id, pspec) + } + } + + impl WidgetImpl for StatusIndicator { + fn snapshot(&self, snapshot: >k::Snapshot) { + if self.masked.get() { + let widget = self.obj(); + + snapshot.push_mask(gsk::MaskMode::InvertedAlpha); + + self.parent_snapshot(snapshot); + + snapshot.pop(); + + snapshot.append_color( + &widget.color(), + >k::graphene::Rect::new( + 0.0, + 0.0, + widget.width() as f32, + widget.height() as f32, + ), + ); + + snapshot.pop(); + } else { + self.parent_snapshot(snapshot); + } + } + } + + impl BinImpl for StatusIndicator {} + + impl StatusIndicator { + fn set_masked(&self, masked: bool) { + if self.masked.replace(masked) != masked { + let obj = self.obj(); + obj.queue_draw(); + obj.notify("masked") + } + } + } +} + +glib::wrapper! { + pub(crate) struct StatusIndicator(ObjectSubclass) + @extends gtk::Widget; +} + +impl StatusIndicator { + pub(crate) fn set_status(&self, status: FileStatus) { + let icon_name = match status { + FileStatus::Downloading(_) | FileStatus::Uploading(_) => "media-playback-stop-symbolic", + FileStatus::CanBeDownloaded => "document-save-symbolic", + FileStatus::Downloaded => "folder-documents-symbolic", + }; + + self.imp().status_image.set_icon_name(Some(icon_name)); + } +}