aboutsummaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/ui/conversation_content_view/conversation_item_skeleton.vala10
-rw-r--r--main/src/ui/conversation_content_view/conversation_view.vala87
-rw-r--r--main/src/ui/conversation_content_view/message_widget.vala26
-rw-r--r--main/src/ui/conversation_content_view/reactions_widget.vala192
4 files changed, 294 insertions, 21 deletions
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 e4e6b804..21aca876 100644
--- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
+++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
@@ -32,6 +32,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
public Entities.Message.Marked item_mark { get; set; }
public ContentMetaItem content_meta_item = null;
public Widget? widget = null;
+ private ReactionsController? reactions_controller = null;
private uint time_update_timeout = 0;
private ulong updated_roster_handler_id = 0;
@@ -64,6 +65,15 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
this.notify["show-skeleton"].connect(update_margin);
this.notify["show-skeleton"].connect(set_header);
+ ContentMetaItem? content_meta_item = item as ContentMetaItem;
+ if (content_meta_item != null) {
+ reactions_controller = new ReactionsController(conversation, content_meta_item.content_item, stream_interactor);
+ reactions_controller.box_activated.connect((widget) => {
+ main_grid.attach(widget, 1, 2, 4, 1);
+ });
+ reactions_controller.init();
+ }
+
update_margin();
}
diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala
index 4babbdb4..caeee09a 100644
--- a/main/src/ui/conversation_content_view/conversation_view.vala
+++ b/main/src/ui/conversation_content_view/conversation_view.vala
@@ -15,19 +15,20 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
[GtkChild] public unowned ScrolledWindow scrolled;
[GtkChild] private unowned Revealer notification_revealer;
[GtkChild] private unowned Box message_menu_box;
- [GtkChild] private unowned Button button1;
- [GtkChild] private unowned Image button1_icon;
[GtkChild] private unowned Box notifications;
[GtkChild] private unowned Box main;
[GtkChild] private unowned Box main_wrap_box;
[GtkChild] private unowned Stack stack;
+ private ArrayList<Widget> action_buttons = new ArrayList<Widget>();
+ private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
+
private StreamInteractor stream_interactor;
private Gee.TreeSet<Plugins.MetaConversationItem> content_items = new Gee.TreeSet<Plugins.MetaConversationItem>(compare_meta_items);
private Gee.TreeSet<Plugins.MetaConversationItem> meta_items = new TreeSet<Plugins.MetaConversationItem>(compare_meta_items);
private Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton> item_item_skeletons = new Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton>();
private Gee.HashMap<Plugins.MetaConversationItem, Widget> widgets = new Gee.HashMap<Plugins.MetaConversationItem, Widget>();
- private Gee.List<ConversationItemSkeleton> item_skeletons = new Gee.ArrayList<ConversationItemSkeleton>();
+ private Gee.List<Widget> widget_order = new Gee.ArrayList<Widget>();
private ContentProvider content_populator;
private SubscriptionNotitication subscription_notification;
@@ -81,11 +82,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
main.add_controller(main_motion_events);
main_motion_events.motion.connect(update_highlight);
- button1.clicked.connect(() => {
- current_meta_item.get_item_actions(Plugins.WidgetType.GTK4)[0].callback(button1, current_meta_item, currently_highlighted);
- update_message_menu();
- });
-
return this;
}
@@ -107,7 +103,20 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
}
}
+ private bool is_highlight_fixed() {
+ foreach (Widget widget in action_buttons) {
+ MenuButton? menu_button = widget as MenuButton;
+ if (menu_button != null && menu_button.popover.visible) return true;
+
+ ToggleButton? toggle_button = widget as ToggleButton;
+ if (toggle_button != null && toggle_button.active) return true;
+ }
+ return false;
+ }
+
private void on_leave_notify_event() {
+ if (is_highlight_fixed()) return;
+
if (currently_highlighted != null) {
currently_highlighted.remove_css_class("highlight");
currently_highlighted = null;
@@ -116,6 +125,8 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
}
private void update_highlight(double x, double y) {
+ if (is_highlight_fixed()) return;
+
if (currently_highlighted != null && (last_y - y).abs() <= 2) {
return;
}
@@ -174,11 +185,42 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
return;
}
- var actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
- message_menu_box.visible = actions != null && actions.size > 0;
- if (actions != null && actions.size == 1) {
- button1.visible = true;
- button1_icon.set_from_icon_name(actions[0].icon_name);
+ foreach (Widget widget in action_buttons) {
+ message_menu_box.remove(widget);
+ }
+ action_buttons.clear();
+
+ message_actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
+
+ if (message_actions != null) {
+ 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) {
+ MenuButton button = new MenuButton();
+ button.icon_name = message_actions[i].icon_name;
+ button.set_popover(message_actions[i].popover as Popover);
+ action_buttons.add(button);
+ }
+
+ if (message_actions[i].callback != null) {
+ var message_action = message_actions[i];
+ Button button = new Button();
+ button.icon_name = message_action.icon_name;
+ button.clicked.connect(() => {
+ print(@"$(current_meta_item.jid) skdfj \n");
+ message_action.callback(button, current_meta_item, currently_highlighted);
+ });
+ action_buttons.add(button);
+ }
+ }
+
+ foreach (Widget widget in action_buttons) {
+ message_menu_box.append(widget);
+ }
+ } else {
+ message_menu_box.visible = false;
}
}
@@ -309,7 +351,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
if (skeleton != null) {
main.remove(skeleton.get_widget());
widgets.unset(item);
- item_skeletons.remove(skeleton);
+ widget_order.remove(skeleton.get_widget());
item_item_skeletons.unset(item);
content_items.remove(item);
@@ -353,8 +395,8 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
// Fill datastructure
ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item, !animate);
item_item_skeletons[item] = item_skeleton;
- int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0;
- item_skeletons.insert(index, item_skeleton);
+ int index = lower_item != null ? widget_order.index_of(item_item_skeletons[lower_item].get_widget()) + 1 : 0;
+ widget_order.insert(index, item_skeleton.get_widget());
// Insert widget
widgets[item] = item_skeleton.get_widget();
@@ -382,7 +424,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
// If an item from the past was added, add everything between that item and the (post-)first present item
if (index == 0) {
Dino.Application app = Dino.Application.get_default();
- if (item_skeletons.size == 1) {
+ if (widget_order.size == 1) {
foreach (Plugins.ConversationAdditionPopulator populator in app.plugin_registry.conversation_addition_populators) {
populator.populate_timespan(conversation, item.time, new DateTime.now_utc());
}
@@ -404,6 +446,15 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
(upper_item.mark == Message.Marked.WONTSEND) == (lower_item.mark == Message.Marked.WONTSEND);
}
+ private void on_action_button_clicked(ToggleButton button) {
+ int button_idx = action_buttons.index_of(button);
+ print(button_idx.to_string() + "\n");
+ Plugins.MessageAction message_action = message_actions[button_idx];
+ if (message_action.callback != null) {
+ message_action.callback(button, current_meta_item, currently_highlighted);
+ }
+ }
+
private void on_upper_notify() {
if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size
if (at_current_content) {
@@ -471,7 +522,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
was_page_size = null;
content_items.clear();
meta_items.clear();
- item_skeletons.clear();
+ widget_order.clear();
item_item_skeletons.clear();
foreach (Widget widget in widgets.values) {
main.remove(widget);
diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala
index 3da76226..1f027c89 100644
--- a/main/src/ui/conversation_content_view/message_widget.vala
+++ b/main/src/ui/conversation_content_view/message_widget.vala
@@ -22,6 +22,7 @@ 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;
@@ -35,6 +36,8 @@ 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;
@@ -68,6 +71,10 @@ 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;
@@ -187,11 +194,13 @@ public class MessageMetaItem : ContentMetaItem {
}
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
- if (content_item as FileItem != null) return null;
+ if (content_item as FileItem != null || this.in_edit_mode) return null;
+ if (in_edit_mode) return null;
- bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>();
- if (allowed && !in_edit_mode) {
+
+ bool correction_allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
+ if (correction_allowed) {
Plugins.MessageAction action1 = new Plugins.MessageAction();
action1.icon_name = "document-edit-symbolic";
action1.callback = (button, content_meta_item_activated, widget) => {
@@ -199,6 +208,17 @@ public class MessageMetaItem : ContentMetaItem {
};
actions.add(action1);
}
+
+ if (supports_reaction) {
+ 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(message_item.conversation, message_item, emoji);
+ });
+ action2.popover = chooser;
+ actions.add(action2);
+ }
return actions;
}
diff --git a/main/src/ui/conversation_content_view/reactions_widget.vala b/main/src/ui/conversation_content_view/reactions_widget.vala
new file mode 100644
index 00000000..83b3204e
--- /dev/null
+++ b/main/src/ui/conversation_content_view/reactions_widget.vala
@@ -0,0 +1,192 @@
+using Gee;
+using Gtk;
+
+using Dino.Entities;
+using Xmpp;
+
+namespace Dino.Ui.ConversationSummary {
+
+public class ReactionsController : Object {
+ public signal void box_activated(Widget widget);
+
+ private Conversation conversation;
+ private Account account;
+ private ContentItem content_item;
+ private StreamInteractor stream_interactor;
+
+ private HashMap<string, Gee.List<Jid>> reactions = new HashMap<string, Gee.List<Jid>>();
+
+ private ReactionsWidget? widget = null;
+
+ public ReactionsController(Conversation conversation, ContentItem content_item, StreamInteractor stream_interactor) {
+ this.conversation = conversation;
+ this.account = conversation.account;
+ this.content_item = content_item;
+ this.stream_interactor = stream_interactor;
+ }
+
+ public void init() {
+ Gee.List<ReactionUsers> reactions = stream_interactor.get_module(Reactions.IDENTITY).get_item_reactions(conversation, content_item);
+ if (reactions.size > 0) {
+ initialize_widget();
+ }
+ foreach (ReactionUsers reaction_users in reactions) {
+ foreach (Jid jid in reaction_users.jids) {
+ reaction_added(reaction_users.reaction, jid);
+ }
+ }
+
+ stream_interactor.get_module(Reactions.IDENTITY).reaction_added.connect((account, content_item_id, jid, reaction) => {
+ if (this.content_item.id == content_item_id) {
+ reaction_added(reaction, jid);
+ }
+ });
+ stream_interactor.get_module(Reactions.IDENTITY).reaction_removed.connect((account, content_item_id, jid, reaction) => {
+ if (this.content_item.id == content_item_id) {
+ reaction_removed(reaction, jid);
+ }
+ });
+ }
+
+ private void initialize_widget() {
+ widget = new ReactionsWidget() { visible=true };
+ widget.emoji_picked.connect((emoji) => {
+ stream_interactor.get_module(Reactions.IDENTITY).add_reaction(conversation, content_item, emoji);
+ });
+ widget.emoji_clicked.connect((emoji) => {
+ if (account.bare_jid in reactions[emoji]) {
+ stream_interactor.get_module(Reactions.IDENTITY).remove_reaction(conversation, content_item, emoji);
+ } else {
+ stream_interactor.get_module(Reactions.IDENTITY).add_reaction(conversation, content_item, emoji);
+ }
+ });
+ box_activated(widget);
+ }
+
+ public void reaction_added(string reaction, Jid jid) {
+ if (widget == null) {
+ initialize_widget();
+ }
+
+ if (!reactions.has_key(reaction)) {
+ reactions[reaction] = new ArrayList<Jid>(Jid.equals_func);
+ }
+ if (jid.equals_bare(account.bare_jid) && reactions[reaction].contains(jid)) {
+ return;
+ }
+ reactions[reaction].add(jid);
+
+ if (reactions[reaction].size == 0) return;
+
+ widget.update_reaction(reaction, reactions[reaction].size, reactions[reaction].contains(account.bare_jid), update_tooltip(reaction));
+ }
+
+ public void reaction_removed(string reaction, Jid jid) {
+ if (!reactions.has_key(reaction)) return;
+ reactions[reaction].remove(jid);
+
+ if (reactions[reaction].size > 0) {
+ widget.update_reaction(reaction, reactions[reaction].size, reactions[reaction].contains(account.bare_jid), update_tooltip(reaction));
+ } else {
+ widget.remove_reaction(reaction);
+ reactions.unset(reaction);
+ }
+
+ if (reactions.size == 0) {
+ widget.unparent();
+ widget = null;
+ }
+ }
+
+ private Gee.List<string> update_tooltip(string reaction) {
+ var name_list = new ArrayList<string>();
+ if (reactions[reaction].size > 0) {
+ if (account.bare_jid in reactions[reaction]) {
+ name_list.add(_("You"));
+ }
+ foreach (Jid jid in reactions[reaction]) {
+ if (jid.equals(account.bare_jid)) continue;
+
+ name_list.add(Util.get_participant_display_name(stream_interactor, conversation, jid));
+ }
+ }
+ return name_list;
+ }
+}
+
+public class ReactionsWidget : Grid {
+
+ public signal void emoji_picked(string emoji);
+ public signal void emoji_clicked(string emoji);
+
+ private HashMap<string, Label> reaction_counts = new HashMap<string, Label>();
+ private HashMap<string, Button> reaction_buttons = new HashMap<string, Button>();
+ private MenuButton add_button;
+
+ public ReactionsWidget() {
+ this.row_spacing = this.column_spacing = 5;
+ this.margin_top = 2;
+
+ add_button = new MenuButton() { tooltip_text= _("Add reaction"), visible=true };
+ add_button.get_style_context().add_class("reaction-box");
+ Image add_image = new Image.from_icon_name("dino-emoticon-add-symbolic") { margin_start=5, margin_end=5, visible=true };
+ add_button.set_child(add_image);
+
+ EmojiChooser chooser = new EmojiChooser();
+ chooser.emoji_picked.connect((emoji) => {
+ emoji_picked(emoji);
+ });
+ add_button.set_popover(chooser);
+ }
+
+ public void update_reaction(string reaction, int count, bool own, Gee.List<string> names) {
+ if (!reaction_buttons.has_key(reaction)) {
+ Label reaction_label = new Label("<span size='small'>" + reaction + "</span>") { use_markup=true, visible=true };
+ Label count_label = new Label("") { use_markup=true, visible=true };
+ Button button = new Button() { visible=true };
+ button.get_style_context().add_class("reaction-box");
+ Box reaction_box = new Box(Orientation.HORIZONTAL, 4) { visible=true };
+ reaction_box.append(reaction_label);
+ reaction_box.append(count_label);
+ button.set_child(reaction_box);
+
+ reaction_counts[reaction] = count_label;
+ reaction_buttons[reaction] = button;
+
+ this.attach(button, (reaction_buttons.size - 1) % 10, (reaction_buttons.size - 1) / 10, 1, 1);
+ if (add_button.get_parent() != null) this.remove(add_button);
+ this.attach(add_button, reaction_buttons.size % 10, reaction_buttons.size / 10, 1, 1);
+
+
+ button.clicked.connect(() => {
+ emoji_clicked(reaction);
+ });
+ }
+
+ reaction_counts[reaction].label = "<span size='small'>" + count.to_string() + "</span>";
+ if (own) {
+ reaction_buttons[reaction].get_style_context().add_class("own-reaction");
+ } else {
+ reaction_buttons[reaction].get_style_context().remove_class("own-reaction");
+ }
+
+ // Build tooltip
+ StringBuilder tooltip_builder = new StringBuilder ();
+ for (int i = 0; i < names.size - 1; i++) {
+ tooltip_builder.append(names[i]);
+ if (i < names.size - 2) tooltip_builder.append(", ");
+ }
+ if (names.size > 1) {
+ tooltip_builder.append(" and ");
+ }
+ tooltip_builder.append(names[names.size - 1]);
+ tooltip_builder.append(" reacted with " + reaction);
+ reaction_buttons[reaction].set_tooltip_text(tooltip_builder.str);
+ }
+
+ public void remove_reaction(string reaction) {
+ reaction_buttons[reaction].unparent();
+ }
+}
+
+} \ No newline at end of file