From b0b81b88c6948dcfd2b1b82a9fe7357316a3af1f Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sun, 29 Jan 2023 23:06:01 +0100 Subject: [PATCH] Always display reaction+reply buttons, disable if not possible --- libdino/src/plugin/interfaces.vala | 1 + libdino/src/service/entity_info.vala | 26 +++++++--- libdino/src/service/fallback_body.vala | 16 ++++++ libdino/src/service/message_processor.vala | 12 +---- libdino/src/service/reactions.vala | 23 +++------ libdino/src/service/replies.vala | 2 - main/CMakeLists.txt | 1 + .../conversation_view.vala | 19 ++++--- .../file_widget.vala | 17 +------ .../item_actions.vala | 50 +++++++++++++++++++ .../message_widget.vala | 27 +--------- 11 files changed, 108 insertions(+), 86 deletions(-) create mode 100644 main/src/ui/conversation_content_view/item_actions.vala diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 0fef0134..a73cb5f7 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -153,6 +153,7 @@ public interface ConversationItemWidgetInterface: Object { public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget); public class MessageAction : Object { + public bool sensitive = true; public string icon_name; public string? tooltip; public Object? popover; diff --git a/libdino/src/service/entity_info.vala b/libdino/src/service/entity_info.vala index d7d0bc9e..d1217e81 100644 --- a/libdino/src/service/entity_info.vala +++ b/libdino/src/service/entity_info.vala @@ -79,22 +79,34 @@ public class EntityInfo : StreamInteractionModule, Object { } public async bool has_feature(Account account, Jid jid, string feature) { + int has_feature_cached = has_feature_cached_int(account, jid, feature); + if (has_feature_cached != -1) { + return has_feature_cached == 1; + } + + ServiceDiscovery.InfoResult? info_result = yield get_info_result(account, jid, entity_caps_hashes[jid]); + if (info_result == null) return false; + + return info_result.features.contains(feature); + } + + public bool has_feature_cached(Account account, Jid jid, string feature) { + return has_feature_cached_int(account, jid, feature) == 1; + } + + private int has_feature_cached_int(Account account, Jid jid, string feature) { if (jid_features.has_key(jid)) { - return jid_features[jid].contains(feature); + return jid_features[jid].contains(feature) ? 1 : 0; } string? hash = entity_caps_hashes[jid]; if (hash != null) { Gee.List? features = get_stored_features(hash); if (features != null) { - return features.contains(feature); + return features.contains(feature) ? 1 : 0; } } - - ServiceDiscovery.InfoResult? info_result = yield get_info_result(account, jid, hash); - if (info_result == null) return false; - - return info_result.features.contains(feature); + return -1; } private void on_received_available_presence(Account account, Presence.Stanza presence) { diff --git a/libdino/src/service/fallback_body.vala b/libdino/src/service/fallback_body.vala index cc9ba9a6..13323427 100644 --- a/libdino/src/service/fallback_body.vala +++ b/libdino/src/service/fallback_body.vala @@ -64,4 +64,20 @@ public class Dino.FallbackBody : StreamInteractionModule, Object { return false; } } + + public static string get_quoted_fallback_body(ContentItem content_item) { + string fallback = "> "; + + if (content_item.type_ == MessageItem.TYPE) { + Message? quoted_message = ((MessageItem) content_item).message; + fallback += Dino.message_body_without_reply_fallback(quoted_message); + fallback = fallback.replace("\n", "\n> "); + } else if (content_item.type_ == FileItem.TYPE) { + FileTransfer? quoted_file = ((FileItem) content_item).file_transfer; + fallback += quoted_file.file_name; + } + fallback += "\n"; + + return fallback; + } } \ No newline at end of file diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 770ae0a6..247206f3 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -484,17 +484,7 @@ public class MessageProcessor : StreamInteractionModule, Object { Xep.Replies.set_reply_to(new_stanza, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id)); } - string fallback = "> "; - - if (content_item.type_ == MessageItem.TYPE) { - Message? quoted_message = ((MessageItem) content_item).message; - fallback += Dino.message_body_without_reply_fallback(quoted_message); - fallback = fallback.replace("\n", "\n> "); - } else if (content_item.type_ == FileItem.TYPE) { - FileTransfer? quoted_file = ((FileItem) content_item).file_transfer; - fallback += quoted_file.file_name; - } - fallback += "\n"; + string fallback = FallbackBody.get_quoted_fallback_body(content_item); long fallback_length = fallback.length; var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length); diff --git a/libdino/src/service/reactions.vala b/libdino/src/service/reactions.vala index be98293f..5deaabac 100644 --- a/libdino/src/service/reactions.vala +++ b/libdino/src/service/reactions.vala @@ -57,30 +57,21 @@ public class Dino.Reactions : StreamInteractionModule, Object { } } - public async bool conversation_supports_reactions(Conversation conversation) { + public bool conversation_supports_reactions(Conversation conversation) { if (conversation.type_ == Conversation.Type.CHAT) { - Gee.List? resources = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account); - if (resources == null) return false; - - foreach (Jid full_jid in resources) { - bool? has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(conversation.account, full_jid, Xep.Reactions.NS_URI); - if (has_feature == true) { - return true; - } - } + return true; } else { // The MUC server needs to 1) support stable stanza ids 2) either support occupant ids or be a private room (where we know real jids) var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); - bool server_supports_sid = (yield entity_info.has_feature(conversation.account, conversation.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || - (yield entity_info.has_feature(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2)); + bool server_supports_sid = (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || + (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2)); if (!server_supports_sid) return false; - bool? supports_occupant_ids = yield entity_info.has_feature(conversation.account, conversation.counterpart, Xep.OccupantIds.NS_URI); + bool? supports_occupant_ids = entity_info.has_feature_cached(conversation.account, conversation.counterpart, Xep.OccupantIds.NS_URI); if (supports_occupant_ids) return true; return stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart); } - return false; } private void send_reactions(Conversation conversation, ContentItem content_item, Gee.List reactions) throws SendError { @@ -96,7 +87,7 @@ public class Dino.Reactions : StreamInteractionModule, Object { reactions_module.send_reaction.begin(stream, conversation.counterpart, "groupchat", message_id, reactions); // We save the reaction when it gets reflected back to us } else if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - reactions_module.send_reaction(stream, conversation.counterpart, "chat", message_id, reactions); + reactions_module.send_reaction.begin(stream, conversation.counterpart, "chat", message_id, reactions); } else if (conversation.type_ == Conversation.Type.CHAT) { int64 now_millis = GLib.get_real_time () / 1000; reactions_module.send_reaction.begin(stream, conversation.counterpart, "chat", message_id, reactions, (_, res) => { @@ -240,7 +231,7 @@ public class Dino.Reactions : StreamInteractionModule, Object { if (stanza.type_ == MessageStanza.TYPE_GROUPCHAT) { // Apply the same restrictions for incoming reactions as we do on sending them Conversation muc_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(from_jid, account.bare_jid, account, MessageStanza.TYPE_GROUPCHAT); - bool muc_supports_reactions = yield conversation_supports_reactions(muc_conversation); + bool muc_supports_reactions = conversation_supports_reactions(muc_conversation); if (!muc_supports_reactions) return; } diff --git a/libdino/src/service/replies.vala b/libdino/src/service/replies.vala index 97db70ee..2bb10e0b 100644 --- a/libdino/src/service/replies.vala +++ b/libdino/src/service/replies.vala @@ -51,8 +51,6 @@ public class Dino.Replies : StreamInteractionModule, Object { private void on_incoming_message(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { // Check if a previous message was in reply to this one - string relevant_id = conversation.type_ == Conversation.Type.GROUPCHAT ? message.server_id : message.stanza_id; - var reply_qry = db.reply.select(); if (conversation.type_ == Conversation.Type.GROUPCHAT) { reply_qry.with(db.reply.quoted_message_stanza_id, "=", message.server_id); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b8f9d942..301e1d95 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -165,6 +165,7 @@ SOURCES src/ui/conversation_content_view/file_default_widget.vala src/ui/conversation_content_view/file_image_widget.vala src/ui/conversation_content_view/file_widget.vala + src/ui/conversation_content_view/item_actions.vala src/ui/conversation_content_view/message_widget.vala src/ui/conversation_content_view/quote_widget.vala src/ui/conversation_content_view/reactions_widget.vala diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala index 4d978132..36e19474 100644 --- a/main/src/ui/conversation_content_view/conversation_view.vala +++ b/main/src/ui/conversation_content_view/conversation_view.vala @@ -203,23 +203,22 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug message_menu_box.visible = true; // Configure as many buttons as we need with the actions for the current meta item - for (int i = 0; i < message_actions.size; i++) { - if (message_actions[i].popover != null) { + foreach (var message_action in message_actions) { + if (message_action.popover != null) { MenuButton button = new MenuButton(); - button.icon_name = message_actions[i].icon_name; - button.set_popover(message_actions[i].popover as Popover); - button.tooltip_text = Util.string_if_tooltips_active(message_actions[i].tooltip); + button.sensitive = message_action.sensitive; + button.icon_name = message_action.icon_name; + button.set_popover(message_action.popover as Popover); + button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip); action_buttons.add(button); - } - - if (message_actions[i].callback != null) { - var message_action = message_actions[i]; + } else if (message_action.callback != null) { Button button = new Button(); + button.sensitive = message_action.sensitive; button.icon_name = message_action.icon_name; button.clicked.connect(() => { message_action.callback(button, current_meta_item, currently_highlighted); }); - button.tooltip_text = Util.string_if_tooltips_active(message_actions[i].tooltip); + button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip); action_buttons.add(button); } } diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 8c36475a..543eb169 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -32,21 +32,8 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem { Gee.List actions = new ArrayList(); if (stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(file_item.conversation, content_item) != null) { - Plugins.MessageAction reply_action = new Plugins.MessageAction(); - reply_action.icon_name = "mail-reply-sender-symbolic"; - reply_action.callback = (button, content_meta_item_activated, widget) => { - GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(file_item.conversation.id), new GLib.Variant.int32(content_item.id) })); - }; - actions.add(reply_action); - - Plugins.MessageAction action2 = new Plugins.MessageAction(); - action2.icon_name = "dino-emoticon-add-symbolic"; - EmojiChooser chooser = new EmojiChooser(); - chooser.emoji_picked.connect((emoji) => { - stream_interactor.get_module(Reactions.IDENTITY).add_reaction(file_item.conversation, content_item, emoji); - }); - action2.popover = chooser; - actions.add(action2); + actions.add(get_reply_action(content_item, file_item.conversation, stream_interactor)); + actions.add(get_reaction_action(content_item, file_item.conversation, stream_interactor)); } return actions; } diff --git a/main/src/ui/conversation_content_view/item_actions.vala b/main/src/ui/conversation_content_view/item_actions.vala new file mode 100644 index 00000000..2cca7565 --- /dev/null +++ b/main/src/ui/conversation_content_view/item_actions.vala @@ -0,0 +1,50 @@ +using Dino.Entities; +using Gtk; + +namespace Dino.Ui { + public Plugins.MessageAction get_reaction_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) { + Plugins.MessageAction action = new Plugins.MessageAction(); + action.icon_name = "dino-emoticon-add-symbolic"; + action.tooltip = _("Add reaction"); + + EmojiChooser chooser = new EmojiChooser(); + chooser.emoji_picked.connect((emoji) => { + stream_interactor.get_module(Reactions.IDENTITY).add_reaction(conversation, content_item, emoji); + }); + action.popover = chooser; + + // Disable the button if reaction aren't possible. + bool supports_reactions = stream_interactor.get_module(Reactions.IDENTITY).conversation_supports_reactions(conversation); + string? message_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item); + + if (!supports_reactions) { + action.tooltip = _("This conversation does not support reactions."); + action.sensitive = false; + } else if (message_id == null) { + action.tooltip = "This message does not support reactions."; + action.sensitive = false; + } + return action; + } + + public Plugins.MessageAction get_reply_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) { + Plugins.MessageAction action = new Plugins.MessageAction(); + action.icon_name = "mail-reply-sender-symbolic"; + action.tooltip = _("Reply"); + action.callback = (button, content_meta_item_activated, widget) => { + GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(conversation.id), new GLib.Variant.int32(content_item.id) })); + }; + + // Disable the button if replies aren't possible. + string? message_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item); + if (message_id == null) { + action.sensitive = false; + if (conversation.type_.is_muc_semantic()) { + action.tooltip = _("This conversation does not support replies."); + } else { + action.tooltip = "This message does not support replies."; + } + } + return action; + } +} \ No newline at end of file diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala index 900525fe..b05fe850 100644 --- a/main/src/ui/conversation_content_view/message_widget.vala +++ b/main/src/ui/conversation_content_view/message_widget.vala @@ -22,7 +22,6 @@ public class MessageMetaItem : ContentMetaItem { MessageItemEditMode? edit_mode = null; ChatTextViewController? controller = null; - private bool supports_reaction = false; AdditionalInfo additional_info = AdditionalInfo.NONE; ulong realize_id = -1; @@ -36,8 +35,6 @@ public class MessageMetaItem : ContentMetaItem { message_item = content_item as MessageItem; this.stream_interactor = stream_interactor; - init.begin(); - label.activate_link.connect(on_label_activate_link); Message message = ((MessageItem) content_item).message; @@ -71,10 +68,6 @@ public class MessageMetaItem : ContentMetaItem { update_label(); } - private async void init() { - supports_reaction = yield stream_interactor.get_module(Reactions.IDENTITY).conversation_supports_reactions(message_item.conversation); - } - private string generate_markup_text(ContentItem item) { MessageItem message_item = item as MessageItem; Conversation conversation = message_item.conversation; @@ -224,25 +217,9 @@ public class MessageMetaItem : ContentMetaItem { actions.add(action1); } - Plugins.MessageAction reply_action = new Plugins.MessageAction(); - reply_action.icon_name = "mail-reply-sender-symbolic"; - reply_action.tooltip = _("Reply"); - reply_action.callback = (button, content_meta_item_activated, widget) => { - GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(content_item.id) })); - }; - actions.add(reply_action); + actions.add(get_reply_action(content_item, message_item.conversation, stream_interactor)); + actions.add(get_reaction_action(content_item, message_item.conversation, stream_interactor)); - if (supports_reaction) { - Plugins.MessageAction action2 = new Plugins.MessageAction(); - action2.icon_name = "dino-emoticon-add-symbolic"; - action2.tooltip = _("Add reaction"); - EmojiChooser chooser = new EmojiChooser(); - chooser.emoji_picked.connect((emoji) => { - stream_interactor.get_module(Reactions.IDENTITY).add_reaction(message_item.conversation, message_item, emoji); - }); - action2.popover = chooser; - actions.add(action2); - } return actions; }