2017-08-27 21:55:49 +00:00
|
|
|
|
using Gee;
|
|
|
|
|
using Gdk;
|
|
|
|
|
using Gtk;
|
|
|
|
|
using Markup;
|
|
|
|
|
|
|
|
|
|
using Dino.Entities;
|
|
|
|
|
|
|
|
|
|
namespace Dino.Ui.ConversationSummary {
|
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface, Object {
|
|
|
|
|
|
|
|
|
|
public Grid main_grid { get; set; }
|
|
|
|
|
public Label name_label { get; set; }
|
|
|
|
|
public Label time_label { get; set; }
|
|
|
|
|
public AvatarImage avatar_image { get; set; }
|
|
|
|
|
public Image encryption_image { get; set; }
|
|
|
|
|
public Image received_image { get; set; }
|
|
|
|
|
|
2023-01-06 12:19:42 +00:00
|
|
|
|
private HashMap<int, Widget> content_widgets = new HashMap<int, Widget>();
|
2017-08-27 21:55:49 +00:00
|
|
|
|
|
2022-05-14 12:45:59 +00:00
|
|
|
|
private bool show_skeleton_ = false;
|
|
|
|
|
public bool show_skeleton {
|
|
|
|
|
get { return show_skeleton_; }
|
|
|
|
|
set {
|
|
|
|
|
show_skeleton_ = value && content_meta_item != null && content_meta_item.requires_header && content_meta_item.requires_avatar; }
|
|
|
|
|
}
|
2019-06-01 14:00:21 +00:00
|
|
|
|
|
2017-08-27 21:55:49 +00:00
|
|
|
|
public StreamInteractor stream_interactor;
|
|
|
|
|
public Conversation conversation { get; set; }
|
2019-06-01 14:00:21 +00:00
|
|
|
|
public Plugins.MetaConversationItem item;
|
2020-05-28 15:31:31 +00:00
|
|
|
|
public bool item_in_edit_mode { get; set; }
|
2021-08-14 18:22:52 +00:00
|
|
|
|
public Entities.Message.Marked item_mark { get; set; }
|
2022-05-14 12:45:59 +00:00
|
|
|
|
public ContentMetaItem content_meta_item = null;
|
2020-04-03 20:49:59 +00:00
|
|
|
|
public Widget? widget = null;
|
2022-10-11 11:37:48 +00:00
|
|
|
|
private ReactionsController? reactions_controller = null;
|
2017-08-27 21:55:49 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
private uint time_update_timeout = 0;
|
|
|
|
|
private ulong updated_roster_handler_id = 0;
|
2017-08-27 21:55:49 +00:00
|
|
|
|
|
2020-03-21 19:53:10 +00:00
|
|
|
|
public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item, bool initial_item) {
|
2017-08-27 21:55:49 +00:00
|
|
|
|
this.stream_interactor = stream_interactor;
|
2019-09-10 23:19:24 +00:00
|
|
|
|
this.conversation = conversation;
|
|
|
|
|
this.item = item;
|
2020-04-03 20:49:59 +00:00
|
|
|
|
this.content_meta_item = item as ContentMetaItem;
|
|
|
|
|
|
2022-11-26 21:26:25 +00:00
|
|
|
|
item.bind_property("in-edit-mode", this, "item-in-edit-mode");
|
|
|
|
|
this.notify["item-in-edit-mode"].connect(update_edit_mode);
|
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
Builder builder = new Builder.from_resource("/im/dino/Dino/conversation_item_widget.ui");
|
|
|
|
|
main_grid = (Grid) builder.get_object("main_grid");
|
2022-05-14 12:45:59 +00:00
|
|
|
|
main_grid.add_css_class("message-box");
|
2022-02-14 13:55:59 +00:00
|
|
|
|
name_label = (Label) builder.get_object("name_label");
|
|
|
|
|
time_label = (Label) builder.get_object("time_label");
|
|
|
|
|
avatar_image = (AvatarImage) builder.get_object("avatar_image");
|
|
|
|
|
encryption_image = (Image) builder.get_object("encrypted_image");
|
|
|
|
|
received_image = (Image) builder.get_object("marked_image");
|
2021-08-14 18:22:52 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
widget = item.get_widget(this, Plugins.WidgetType.GTK4) as Widget;
|
2017-11-26 18:28:44 +00:00
|
|
|
|
if (widget != null) {
|
2019-06-16 13:17:08 +00:00
|
|
|
|
widget.valign = Align.END;
|
2023-01-06 12:19:42 +00:00
|
|
|
|
set_widget(widget, Plugins.WidgetType.GTK4, 2);
|
2017-08-27 21:55:49 +00:00
|
|
|
|
}
|
2017-11-21 21:17:04 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
if (item.requires_header) {
|
|
|
|
|
avatar_image.set_conversation_participant(stream_interactor, conversation, item.jid);
|
2019-06-01 14:00:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.notify["show-skeleton"].connect(update_margin);
|
2022-02-14 13:55:59 +00:00
|
|
|
|
this.notify["show-skeleton"].connect(set_header);
|
2019-06-01 14:00:21 +00:00
|
|
|
|
|
2022-10-11 11:37:48 +00:00
|
|
|
|
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) => {
|
2023-01-06 12:19:42 +00:00
|
|
|
|
set_widget(widget, Plugins.WidgetType.GTK4, 3);
|
2022-10-11 11:37:48 +00:00
|
|
|
|
});
|
|
|
|
|
reactions_controller.init();
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 14:00:21 +00:00
|
|
|
|
update_margin();
|
2017-08-27 21:55:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
private void set_header() {
|
2022-05-14 12:45:59 +00:00
|
|
|
|
if (!show_skeleton) return;
|
2022-02-14 13:55:59 +00:00
|
|
|
|
|
|
|
|
|
update_name_label();
|
|
|
|
|
// name_label.style_updated.connect(update_name_label);
|
|
|
|
|
updated_roster_handler_id = stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => {
|
|
|
|
|
if (this.conversation.account.equals(account) && this.conversation.counterpart.equals(jid)) {
|
|
|
|
|
update_name_label();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
item.notify["encryption"].connect(update_encryption_icon);
|
|
|
|
|
update_encryption_icon();
|
2020-03-24 12:58:25 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
if (item.time != null) {
|
|
|
|
|
update_time();
|
2020-03-24 12:58:25 +00:00
|
|
|
|
}
|
2022-02-14 13:55:59 +00:00
|
|
|
|
|
|
|
|
|
item.bind_property("mark", this, "item-mark", BindingFlags.SYNC_CREATE);
|
|
|
|
|
this.notify["item-mark"].connect_after(update_received_mark);
|
|
|
|
|
update_received_mark();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-06 12:19:42 +00:00
|
|
|
|
public void set_widget(Object object, Plugins.WidgetType type, int priority) {
|
|
|
|
|
foreach (var content_widget in content_widgets.values) {
|
|
|
|
|
content_widget.unparent();
|
|
|
|
|
}
|
2022-02-14 13:55:59 +00:00
|
|
|
|
|
2023-01-06 12:19:42 +00:00
|
|
|
|
content_widgets[priority] = (Widget) object;
|
|
|
|
|
int row_no = 1;
|
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
|
|
|
if (!content_widgets.has_key(i)) continue;
|
|
|
|
|
main_grid.attach(content_widgets[i], 1, row_no, 4, 1);
|
|
|
|
|
row_no++;
|
|
|
|
|
}
|
2022-02-14 13:55:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void update_margin() {
|
|
|
|
|
avatar_image.visible = show_skeleton;
|
|
|
|
|
name_label.visible = show_skeleton;
|
|
|
|
|
time_label.visible = show_skeleton;
|
|
|
|
|
encryption_image.visible = show_skeleton;
|
|
|
|
|
received_image.visible = show_skeleton;
|
|
|
|
|
|
2022-05-14 12:45:59 +00:00
|
|
|
|
if (show_skeleton || content_meta_item == null) {
|
|
|
|
|
main_grid.add_css_class("has-skeleton");
|
2019-06-01 14:00:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-17 20:50:23 +00:00
|
|
|
|
|
|
|
|
|
private void update_edit_mode() {
|
|
|
|
|
if (item.in_edit_mode) {
|
2022-05-14 12:45:59 +00:00
|
|
|
|
main_grid.add_css_class("edit-mode");
|
2021-02-17 20:50:23 +00:00
|
|
|
|
} else {
|
2022-05-14 12:45:59 +00:00
|
|
|
|
main_grid.remove_css_class("edit-mode");
|
2021-02-17 20:50:23 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-14 18:22:52 +00:00
|
|
|
|
|
|
|
|
|
private void update_error_mode() {
|
|
|
|
|
if (item_mark == Message.Marked.ERROR) {
|
2022-05-14 12:45:59 +00:00
|
|
|
|
main_grid.add_css_class("error");
|
2021-08-14 18:22:52 +00:00
|
|
|
|
} else {
|
2022-05-14 12:45:59 +00:00
|
|
|
|
main_grid.remove_css_class("error");
|
2021-04-08 10:07:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void update_encryption_icon() {
|
2020-03-28 13:46:51 +00:00
|
|
|
|
Application app = GLib.Application.get_default() as Application;
|
|
|
|
|
|
|
|
|
|
ContentMetaItem ci = item as ContentMetaItem;
|
2022-01-04 11:34:16 +00:00
|
|
|
|
if (item.encryption != Encryption.NONE && item.encryption != Encryption.UNKNOWN && ci != null) {
|
2022-02-14 13:55:59 +00:00
|
|
|
|
string? icon_name = null;
|
2022-08-20 22:10:59 +00:00
|
|
|
|
var encryption_entry = app.plugin_registry.encryption_list_entries[item.encryption];
|
|
|
|
|
icon_name = encryption_entry.get_encryption_icon_name(conversation, ci.content_item);
|
2022-05-14 12:45:59 +00:00
|
|
|
|
encryption_image.icon_name = icon_name ?? "changes-prevent-symbolic";
|
|
|
|
|
encryption_image.visible = true;
|
2020-03-28 18:55:22 +00:00
|
|
|
|
}
|
2017-11-26 18:28:44 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
if (item.encryption == Encryption.NONE) {
|
|
|
|
|
if (conversation.encryption != Encryption.NONE) {
|
2022-08-20 22:10:59 +00:00
|
|
|
|
encryption_image.icon_name = "changes-allow-symbolic";
|
2022-07-29 17:54:54 +00:00
|
|
|
|
encryption_image.tooltip_text = Util.string_if_tooltips_active(_("Unencrypted"));
|
2022-02-14 13:55:59 +00:00
|
|
|
|
Util.force_error_color(encryption_image);
|
2022-05-14 12:45:59 +00:00
|
|
|
|
encryption_image.visible = true;
|
2022-02-14 13:55:59 +00:00
|
|
|
|
} else if (conversation.encryption == Encryption.NONE) {
|
|
|
|
|
encryption_image.icon_name = null;
|
|
|
|
|
encryption_image.visible = false;
|
|
|
|
|
}
|
2020-03-28 18:55:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 14:27:48 +00:00
|
|
|
|
private void update_time() {
|
2020-12-04 18:11:27 +00:00
|
|
|
|
time_label.label = get_relative_time(item.time.to_local()).to_string();
|
2020-03-26 14:27:48 +00:00
|
|
|
|
|
2020-05-28 15:31:31 +00:00
|
|
|
|
time_update_timeout = Timeout.add_seconds((int) get_next_time_change(), () => {
|
2022-02-14 13:55:59 +00:00
|
|
|
|
if (this.main_grid.parent == null) return false;
|
2020-03-26 14:27:48 +00:00
|
|
|
|
update_time();
|
|
|
|
|
return false;
|
|
|
|
|
});
|
2017-11-26 18:28:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void update_name_label() {
|
2022-01-23 18:32:28 +00:00
|
|
|
|
name_label.label = Util.get_participant_display_name(stream_interactor, conversation, item.jid);
|
2017-08-27 21:55:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-21 21:17:04 +00:00
|
|
|
|
private void update_received_mark() {
|
2022-02-14 13:55:59 +00:00
|
|
|
|
switch (content_meta_item.mark) {
|
|
|
|
|
case Message.Marked.RECEIVED: received_image.icon_name = "dino-tick-symbolic"; break;
|
|
|
|
|
case Message.Marked.READ: received_image.icon_name = "dino-double-tick-symbolic"; break;
|
|
|
|
|
case Message.Marked.WONTSEND:
|
|
|
|
|
received_image.icon_name = "dialog-warning-symbolic";
|
2022-05-14 12:45:59 +00:00
|
|
|
|
Util.force_error_color(received_image);
|
|
|
|
|
Util.force_error_color(time_label);
|
2022-07-29 17:54:54 +00:00
|
|
|
|
string error_text = Util.string_if_tooltips_active(_("Unable to send message"));
|
2022-05-14 12:45:59 +00:00
|
|
|
|
received_image.tooltip_text = error_text;
|
|
|
|
|
time_label.tooltip_text = error_text;
|
2022-02-14 13:55:59 +00:00
|
|
|
|
break;
|
|
|
|
|
default: received_image.icon_name = null; break;
|
2017-08-27 21:55:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 14:27:48 +00:00
|
|
|
|
private int get_next_time_change() {
|
|
|
|
|
DateTime now = new DateTime.now_local();
|
2020-12-04 18:11:27 +00:00
|
|
|
|
DateTime item_time = item.time;
|
2020-03-26 14:27:48 +00:00
|
|
|
|
TimeSpan timespan = now.difference(item_time);
|
|
|
|
|
|
|
|
|
|
if (timespan < 10 * TimeSpan.MINUTE) {
|
|
|
|
|
if (now.get_second() < item_time.get_second()) {
|
|
|
|
|
return item_time.get_second() - now.get_second();
|
|
|
|
|
} else {
|
|
|
|
|
return 60 - (now.get_second() - item_time.get_second());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return (23 - now.get_hour()) * 3600 + (59 - now.get_minute()) * 60 + (59 - now.get_second());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-26 18:28:44 +00:00
|
|
|
|
public static string format_time(DateTime datetime, string format_24h, string format_12h) {
|
2017-08-29 19:51:08 +00:00
|
|
|
|
string format = Util.is_24h_format() ? format_24h : format_12h;
|
|
|
|
|
if (!get_charset(null)) {
|
|
|
|
|
// No UTF-8 support, use simple colon for time instead
|
|
|
|
|
format = format.replace("∶", ":");
|
|
|
|
|
}
|
|
|
|
|
return datetime.format(format);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-09 22:31:39 +00:00
|
|
|
|
public static string get_relative_time(DateTime datetime) {
|
2017-08-27 21:55:49 +00:00
|
|
|
|
DateTime now = new DateTime.now_local();
|
|
|
|
|
TimeSpan timespan = now.difference(datetime);
|
|
|
|
|
if (timespan > 365 * TimeSpan.DAY) {
|
2017-08-29 19:51:08 +00:00
|
|
|
|
return format_time(datetime,
|
|
|
|
|
/* xgettext:no-c-format */ /* Date + time in 24h format (w/o seconds) */ _("%x, %H∶%M"),
|
|
|
|
|
/* xgettext:no-c-format */ /* Date + time in 12h format (w/o seconds)*/ _("%x, %l∶%M %p"));
|
2017-08-27 21:55:49 +00:00
|
|
|
|
} else if (timespan > 7 * TimeSpan.DAY) {
|
2017-08-29 19:51:08 +00:00
|
|
|
|
return format_time(datetime,
|
|
|
|
|
/* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %H∶%M"),
|
|
|
|
|
/* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %l∶%M %p"));
|
2017-08-29 22:03:37 +00:00
|
|
|
|
} else if (datetime.get_day_of_month() != now.get_day_of_month()) {
|
2017-08-29 19:51:08 +00:00
|
|
|
|
return format_time(datetime,
|
|
|
|
|
/* xgettext:no-c-format */ /* Day of week and time in 24h format (w/o seconds) */ _("%a, %H∶%M"),
|
|
|
|
|
/* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */_("%a, %l∶%M %p"));
|
2017-08-27 21:55:49 +00:00
|
|
|
|
} else if (timespan > 9 * TimeSpan.MINUTE) {
|
2017-08-29 19:51:08 +00:00
|
|
|
|
return format_time(datetime,
|
|
|
|
|
/* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%H∶%M"),
|
|
|
|
|
/* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%l∶%M %p"));
|
2017-08-27 21:55:49 +00:00
|
|
|
|
} else if (timespan > TimeSpan.MINUTE) {
|
|
|
|
|
ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE);
|
|
|
|
|
/* xgettext:this is the beginning of a sentence. */
|
|
|
|
|
return n("%i min ago", "%i mins ago", mins).printf(mins);
|
|
|
|
|
} else {
|
|
|
|
|
return _("Just now");
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-28 15:31:31 +00:00
|
|
|
|
|
2022-02-14 13:55:59 +00:00
|
|
|
|
public Widget get_widget() {
|
|
|
|
|
return main_grid;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-28 15:31:31 +00:00
|
|
|
|
public override void dispose() {
|
|
|
|
|
if (time_update_timeout != 0) {
|
|
|
|
|
Source.remove(time_update_timeout);
|
|
|
|
|
time_update_timeout = 0;
|
|
|
|
|
}
|
2021-08-24 17:35:00 +00:00
|
|
|
|
if (updated_roster_handler_id != 0){
|
|
|
|
|
stream_interactor.get_module(RosterManager.IDENTITY).disconnect(updated_roster_handler_id);
|
|
|
|
|
updated_roster_handler_id = 0;
|
|
|
|
|
}
|
|
|
|
|
base.dispose();
|
2020-05-28 15:31:31 +00:00
|
|
|
|
}
|
2017-08-27 21:55:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|