From 447464f4d1ac0c184764f103ac9e51f7ff2dce91 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sat, 14 Aug 2021 20:22:52 +0200 Subject: [PATCH] Display message delivery error, color text using theme colors fixes #672 --- libdino/src/entity/message.vala | 5 +- libdino/src/service/message_processor.vala | 13 ++++ main/data/theme.css | 8 +++ .../conversation_item_skeleton.vala | 13 ++++ .../message_widget.vala | 63 +++++++++++++++---- main/src/ui/util/helper.vala | 30 ++++++++- xmpp-vala/src/module/message/module.vala | 7 ++- xmpp-vala/src/module/stanza.vala | 2 +- xmpp-vala/src/module/stanza_error.vala | 7 ++- 9 files changed, 127 insertions(+), 21 deletions(-) diff --git a/libdino/src/entity/message.vala b/libdino/src/entity/message.vala index 7192f6aa..9c062f73 100644 --- a/libdino/src/entity/message.vala +++ b/libdino/src/entity/message.vala @@ -16,9 +16,12 @@ public class Message : Object { UNSENT, WONTSEND, SENDING, - SENT + SENT, + ERROR } + public static Marked[] MARKED_RECEIVED = new Marked[] { Marked.READ, Marked.RECEIVED, Marked.ACKNOWLEDGED }; + public enum Type { ERROR, CHAT, diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 669aa193..fcabeba6 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -144,6 +144,19 @@ public class MessageProcessor : StreamInteractionModule, Object { hitted_range[query_id] = -2; } }); + stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_error.connect((stream, message_stanza, error_stanza) => { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(message_stanza.from.bare_jid, account); + if (conversation == null) return; + Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(message_stanza.id, conversation); + if (message == null) return; + // We don't care about delivery errors if our counterpart already ACKed the message. + if (message.marked in Message.MARKED_RECEIVED) return; + + warning("Message delivery error from %s. Type: %s, Condition: %s, Text: %s", message_stanza.from.to_string(), error_stanza.type_ ?? "-", error_stanza.condition, error_stanza.text ?? "-"); + if (error_stanza.condition == Xmpp.ErrorStanza.CONDITION_RECIPIENT_UNAVAILABLE && error_stanza.type_ == Xmpp.ErrorStanza.TYPE_CANCEL) return; + + message.marked = Message.Marked.ERROR; + }); convert_sending_to_unsent_msgs(account); } diff --git a/main/data/theme.css b/main/data/theme.css index 454bd2c1..cf57ae96 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -86,6 +86,14 @@ window.dino-main .dino-conversation .message-box.edit-mode:hover { background: alpha(@theme_selected_bg_color, 0.12); } +window.dino-main .dino-conversation .message-box.error { + background: alpha(@error_color, 0.1); +} + +window.dino-main .dino-conversation .message-box.error:hover { + background: alpha(@error_color, 0.12); +} + window.dino-main .file-box-outer, window.dino-main .call-box-outer { background: @theme_base_color; diff --git a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala index bcb6864e..343c6631 100644 --- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala @@ -16,6 +16,7 @@ public class ConversationItemSkeleton : EventBox { public Conversation conversation { get; set; } public Plugins.MetaConversationItem item; public bool item_in_edit_mode { get; set; } + public Entities.Message.Marked item_mark { get; set; } public ContentMetaItem? content_meta_item = null; public Widget? widget = null; @@ -34,6 +35,10 @@ public class ConversationItemSkeleton : EventBox { item.bind_property("in-edit-mode", this, "item-in-edit-mode"); this.notify["item-in-edit-mode"].connect(update_edit_mode); + item.bind_property("mark", this, "item-mark", BindingFlags.SYNC_CREATE); + this.notify["item-mark"].connect(update_error_mode); + update_error_mode(); + widget = item.get_widget(Plugins.WidgetType.GTK) as Widget; if (widget != null) { widget.valign = Align.END; @@ -96,6 +101,14 @@ public class ConversationItemSkeleton : EventBox { this.get_style_context().remove_class("edit-mode"); } } + + private void update_error_mode() { + if (item_mark == Message.Marked.ERROR) { + this.get_style_context().add_class("error"); + } else { + this.get_style_context().remove_class("error"); + } + } } [GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/item_metadata_header.ui")] diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala index 44584709..93e48848 100644 --- a/main/src/ui/conversation_content_view/message_widget.vala +++ b/main/src/ui/conversation_content_view/message_widget.vala @@ -77,6 +77,12 @@ public class MessageItemWidget : SizeRequestBin { public signal void edit_cancelled(); public signal void edit_sent(string text); + enum AdditionalInfo { + NONE, + PENDING, + DELIVERY_FAILED + } + StreamInteractor stream_interactor; public ContentItem content_item; public Message.Marked marked { get; set; } @@ -84,6 +90,7 @@ public class MessageItemWidget : SizeRequestBin { Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; MessageItemEditMode? edit_mode = null; ChatTextViewController? controller = null; + AdditionalInfo additional_info = AdditionalInfo.NONE; ulong realize_id = -1; ulong style_updated_id = -1; @@ -99,6 +106,34 @@ public class MessageItemWidget : SizeRequestBin { this.stream_interactor = stream_interactor; this.content_item = content_item; + Message message = ((MessageItem) content_item).message; + if (message.direction == Message.DIRECTION_SENT && !(message.marked in Message.MARKED_RECEIVED)) { + var binding = message.bind_property("marked", this, "marked"); + marked_notify_handler_id = this.notify["marked"].connect(() => { + // Currently "pending", but not anymore + if (additional_info == AdditionalInfo.PENDING && + message.marked != Message.Marked.SENDING && message.marked != Message.Marked.UNSENT) { + update_label(); + } + + // Currently "error", but not anymore + if (additional_info == AdditionalInfo.DELIVERY_FAILED && message.marked != Message.Marked.ERROR) { + update_label(); + } + + // Currently not error, but should be + if (additional_info != AdditionalInfo.DELIVERY_FAILED && message.marked == Message.Marked.ERROR) { + update_label(); + } + + // Nothing bad can happen anymore + if (message.marked in Message.MARKED_RECEIVED) { + binding.unbind(); + this.disconnect(marked_notify_handler_id); + } + }); + } + update_label(); } @@ -181,32 +216,34 @@ public class MessageItemWidget : SizeRequestBin { markup_text = @"" + markup_text + ""; } - string gray_color = Util.is_dark_theme(label) ? "#808080" : "#909090"; + string gray_color = Util.rgba_to_hex(Util.get_label_pango_class_color(label, "dim-label")); if (message.edit_to != null) { - markup_text += " (%s)".printf(gray_color, _("edited")); + markup_text += " (%s)".printf(gray_color, _("edited")); theme_dependent = true; } - // Append "pending..." iff message has not been sent yet + // Append message status info + additional_info = AdditionalInfo.NONE; if (message.direction == Message.DIRECTION_SENT && (message.marked == Message.Marked.SENDING || message.marked == Message.Marked.UNSENT)) { + // Append "pending..." iff message has not been sent yet if (message.time.compare(new DateTime.now_utc().add_seconds(-10)) < 0) { - markup_text += " %s".printf(gray_color, "pending…"); - - // Update the label as soon as the sent state changes - var binding = message.bind_property("marked", this, "marked"); - marked_notify_handler_id = this.notify["marked"].connect(() => { - binding.unbind(); - this.disconnect(marked_notify_handler_id); - update_label(); - }); + markup_text += " %s".printf(gray_color, _("pending…")); + theme_dependent = true; + additional_info = AdditionalInfo.PENDING; } else { int time_diff = (- (int) message.time.difference(new DateTime.now_utc()) / 1000); Timeout.add(10000 - time_diff, () => { - update_label(); + update_label(); return false; }); } + } else if (message.direction == Message.DIRECTION_SENT && message.marked == Message.Marked.ERROR) { + // Append "delivery failed" if there was a server error + string error_color = Util.rgba_to_hex(Util.get_label_pango_color(label, "@error_color")); + markup_text += " %s".printf(error_color, _("delivery failed")); + theme_dependent = true; + additional_info = AdditionalInfo.DELIVERY_FAILED; } if (theme_dependent && realize_id == -1) { diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index 07c81167..d5967ef3 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -154,10 +154,33 @@ public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, image.set_from_surface(surface); } +public static Gdk.RGBA get_label_pango_color(Label label, string css_color) { + Gtk.CssProvider provider = force_color(label, css_color); + Gdk.RGBA color_rgba = label.get_style_context().get_color(StateFlags.NORMAL); + label.get_style_context().remove_provider(provider); + return color_rgba; +} + +public static Gdk.RGBA get_label_pango_class_color(Label label, string css_class) { + label.get_style_context().add_class(css_class); + Gdk.RGBA color_rgba = label.get_style_context().get_color(StateFlags.NORMAL); + label.get_style_context().remove_class(css_class); + return color_rgba; +} + +public static string rgba_to_hex(Gdk.RGBA rgba) { + return "#%02x%02x%02x%02x".printf( + (uint)(Math.round(rgba.red*255)), + (uint)(Math.round(rgba.green*255)), + (uint)(Math.round(rgba.blue*255)), + (uint)(Math.round(rgba.alpha*255))) + .up(); +} + private const string force_background_css = "%s { background-color: %s; }"; private const string force_color_css = "%s { color: %s; }"; -public static void force_css(Gtk.Widget widget, string css) { +public static Gtk.CssProvider force_css(Gtk.Widget widget, string css) { var p = new Gtk.CssProvider(); try { p.load_from_data(css); @@ -165,14 +188,15 @@ public static void force_css(Gtk.Widget widget, string css) { } catch (GLib.Error err) { // handle err } + return p; } public static void force_background(Gtk.Widget widget, string color, string selector = "*") { force_css(widget, force_background_css.printf(selector, color)); } -public static void force_color(Gtk.Widget widget, string color, string selector = "*") { - force_css(widget, force_color_css.printf(selector, color)); +public static Gtk.CssProvider force_color(Gtk.Widget widget, string color, string selector = "*") { + return force_css(widget, force_color_css.printf(selector, color)); } public static void force_error_color(Gtk.Widget widget, string selector = "*") { diff --git a/xmpp-vala/src/module/message/module.vala b/xmpp-vala/src/module/message/module.vala index 5f7d40f0..2eced5c1 100644 --- a/xmpp-vala/src/module/message/module.vala +++ b/xmpp-vala/src/module/message/module.vala @@ -12,6 +12,7 @@ namespace Xmpp { public StanzaListenerHolder send_pipeline = new StanzaListenerHolder(); public signal void received_message(XmppStream stream, MessageStanza message); + public signal void received_error(XmppStream stream, MessageStanza message, ErrorStanza error); public signal void received_message_unprocessed(XmppStream stream, MessageStanza message); public async void send_message(XmppStream stream, MessageStanza message) throws IOStreamError { @@ -24,7 +25,11 @@ namespace Xmpp { received_message_unprocessed(stream, message); - if (!message.is_error()) { + if (message.is_error()) { + ErrorStanza? error_stanza = message.get_error(); + if (error_stanza == null) return; + received_error(stream, message, error_stanza); + } else { bool abort = yield received_pipeline.run(stream, message); if (abort) return; diff --git a/xmpp-vala/src/module/stanza.vala b/xmpp-vala/src/module/stanza.vala index f4e40f84..3b029197 100644 --- a/xmpp-vala/src/module/stanza.vala +++ b/xmpp-vala/src/module/stanza.vala @@ -74,7 +74,7 @@ public class Stanza : Object { } public ErrorStanza? get_error() { - return new ErrorStanza.from_stanza(this.stanza); + return ErrorStanza.from_stanza(this.stanza); } } diff --git a/xmpp-vala/src/module/stanza_error.vala b/xmpp-vala/src/module/stanza_error.vala index a5c3956c..5e872291 100644 --- a/xmpp-vala/src/module/stanza_error.vala +++ b/xmpp-vala/src/module/stanza_error.vala @@ -60,8 +60,11 @@ namespace Xmpp { public StanzaNode error_node; - public ErrorStanza.from_stanza(StanzaNode stanza) { - error_node = stanza.get_subnode("error"); + public static ErrorStanza? from_stanza(StanzaNode stanza) { + var error_stanza = new ErrorStanza(); + error_stanza.error_node = stanza.get_subnode("error"); + if (error_stanza.error_node == null) return null; + return error_stanza; } public ErrorStanza.build(string type, string condition, string? human_readable, StanzaNode? application_condition) {