diff --git a/libdino/src/entity/message.vala b/libdino/src/entity/message.vala index 3c5d03d3..e41e3646 100644 --- a/libdino/src/entity/message.vala +++ b/libdino/src/entity/message.vala @@ -47,7 +47,7 @@ public class Message : Object { public Marked marked { get { return marked_; } set { - if (marked == Marked.RECEIVED && marked == Marked.READ) return; + if (marked == Marked.RECEIVED && value == Marked.READ) return; marked_ = value; } } diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 852aa596..07cc759c 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -98,8 +98,8 @@ public abstract class MetaConversationItem : Object { } public interface ConversationItemCollection : Object { - public abstract void insert_item(MetaConversationItem item); - public abstract void remove_item(MetaConversationItem item); + public signal void insert_item(MetaConversationItem item); + public signal void remove_item(MetaConversationItem item); } public interface MessageDisplayProvider : Object { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 4fd55a47..3e23fffc 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -101,6 +101,7 @@ SOURCES src/ui/conversation_summary/chat_state_populator.vala src/ui/conversation_summary/conversation_item_skeleton.vala src/ui/conversation_summary/conversation_view.vala + src/ui/conversation_summary/date_separator_populator.vala src/ui/conversation_summary/default_message_display.vala src/ui/conversation_summary/file_populator.vala src/ui/conversation_summary/image_display.vala diff --git a/main/src/ui/conversation_summary/conversation_item_skeleton.vala b/main/src/ui/conversation_summary/conversation_item_skeleton.vala index 03e9facb..c31b7901 100644 --- a/main/src/ui/conversation_summary/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_summary/conversation_item_skeleton.vala @@ -67,7 +67,9 @@ public class ConversationItemSkeleton : Grid { private void setup(Plugins.MetaConversationItem item) { update_time(); - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).set_greyscale(item.dim).draw_jid(stream_interactor, item.jid, conversation.account)); + if (item.requires_avatar) { + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).set_greyscale(item.dim).draw_jid(stream_interactor, item.jid, conversation.account)); + } if (item.requires_header) { set_default_title_widget(item.jid); } diff --git a/main/src/ui/conversation_summary/conversation_view.vala b/main/src/ui/conversation_summary/conversation_view.vala index 15a86ca7..c50a5e3c 100644 --- a/main/src/ui/conversation_summary/conversation_view.vala +++ b/main/src/ui/conversation_summary/conversation_view.vala @@ -37,9 +37,13 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { message_item_populator = new MessagePopulator(stream_interactor); + insert_item.connect(on_insert_item); + remove_item.connect(on_remove_item); + Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_conversation_item_populator(new ChatStatePopulator(stream_interactor)); app.plugin_registry.register_conversation_item_populator(new FilePopulator(stream_interactor)); + app.plugin_registry.register_conversation_item_populator(new DateSeparatorPopulator(stream_interactor)); Timeout.add_seconds(60, () => { foreach (ConversationItemSkeleton item_skeleton in item_skeletons) { @@ -52,6 +56,12 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { } public void initialize_for_conversation(Conversation? conversation) { + Dino.Application app = Dino.Application.get_default(); + if (this.conversation != null) { + foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) { + populator.close(conversation); + } + } this.conversation = conversation; stack.set_visible_child_name("void"); clear(); @@ -60,7 +70,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { animate = false; Timeout.add(20, () => { animate = true; return false; }); - Dino.Application app = Dino.Application.get_default(); foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } @@ -70,7 +79,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { stack.set_visible_child_name("main"); } - public void insert_item(Plugins.MetaConversationItem item) { + public void on_insert_item(Plugins.MetaConversationItem item) { lock (meta_items) { if (!item.can_merge || !merge_back(item)) { insert_new(item); @@ -78,7 +87,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { } } - public void remove_item(Plugins.MetaConversationItem item) { + public void on_remove_item(Plugins.MetaConversationItem item) { lock (meta_items) { ConversationItemSkeleton? skeleton = item_item_skeletons[item]; if (skeleton.items.size > 1) { diff --git a/main/src/ui/conversation_summary/date_separator_populator.vala b/main/src/ui/conversation_summary/date_separator_populator.vala new file mode 100644 index 00000000..34005ab6 --- /dev/null +++ b/main/src/ui/conversation_summary/date_separator_populator.vala @@ -0,0 +1,107 @@ +using Gee; +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui.ConversationSummary { + +class DateSeparatorPopulator : Plugins.ConversationItemPopulator, Object { + + public string id { get { return "date_separator"; } } + + private StreamInteractor stream_interactor; + private Conversation? current_conversation; + private Plugins.ConversationItemCollection? item_collection; + private Gee.TreeSet insert_times; + + + public DateSeparatorPopulator(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { + current_conversation = conversation; + this.item_collection = item_collection; + item_collection.insert_item.connect(on_insert_item); + this.insert_times = new TreeSet((a, b) => { + return a.compare(b); + }); + } + + public void close(Conversation conversation) { + item_collection.insert_item.disconnect(on_insert_item); + } + + public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { } + + public void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { } + + private void on_insert_item(Plugins.MetaConversationItem item) { + if (item.display_time == null) return; + + DateTime time = item.sort_time.to_local(); + DateTime msg_date = new DateTime.local(time.get_year(), time.get_month(), time.get_day_of_month(), 0, 0, 0); + if (!insert_times.contains(msg_date)) { + if (insert_times.lower(msg_date) != null) { + item_collection.insert_item(new MetaDateItem(msg_date.to_utc())); + } else if (insert_times.size > 0) { + item_collection.insert_item(new MetaDateItem(insert_times.first().to_utc())); + } + insert_times.add(msg_date); + } + } +} + +public class MetaDateItem : Plugins.MetaConversationItem { + public override DateTime? sort_time { get; set; } + + public override bool can_merge { get; set; default=false; } + public override bool requires_avatar { get; set; default=false; } + public override bool requires_header { get; set; default=false; } + + private DateTime date; + + public MetaDateItem(DateTime date) { + this.date = date; + this.sort_time = date; + } + + public override Object? get_widget(Plugins.WidgetType widget_type) { + Box box = new Box(Orientation.HORIZONTAL, 10) { width_request=300, halign=Align.CENTER, visible=true }; + box.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); + string date_str = get_relative_time(date); + Label label = new Label(@"$date_str") { use_markup=true, halign=Align.CENTER, hexpand=false, visible=true }; + label.get_style_context().add_class("dim-label"); + box.add(label); + box.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); + return box; + } + + private static string get_relative_time(DateTime time) { + DateTime time_local = time.to_local(); + DateTime now_local = new DateTime.now_local(); + if (time_local.get_year() == now_local.get_year() && + time_local.get_month() == now_local.get_month() && + time_local.get_day_of_month() == now_local.get_day_of_month()) { + return _("Today"); + } + DateTime now_local_minus = now_local.add_days(-1); + if (time_local.get_year() == now_local_minus.get_year() && + time_local.get_month() == now_local_minus.get_month() && + time_local.get_day_of_month() == now_local_minus.get_day_of_month()) { + return _("Yesterday"); + } + if (time_local.get_year() != now_local.get_year()) { + return time_local.format("%x"); + } + TimeSpan timespan = now_local.difference(time_local); + if (timespan < 7 * TimeSpan.DAY) { + return time_local.format(_("%a, %b %d")); + } else { + return time_local.format(_("%b %d")); + } + } +} + +} diff --git a/xmpp-vala/src/module/iq/stanza.vala b/xmpp-vala/src/module/iq/stanza.vala index 561c5866..8f114c9f 100644 --- a/xmpp-vala/src/module/iq/stanza.vala +++ b/xmpp-vala/src/module/iq/stanza.vala @@ -23,6 +23,7 @@ public class Stanza : Xmpp.Stanza { public Stanza.result(Stanza request, StanzaNode? stanza_node = null) { this(request.id); + this.to = request.from; this.type_ = TYPE_RESULT; if (stanza_node != null) { stanza.put_node(stanza_node); diff --git a/xmpp-vala/src/module/xep/0030_service_discovery/info_result.vala b/xmpp-vala/src/module/xep/0030_service_discovery/info_result.vala index 4ae917dc..ca0fba5b 100644 --- a/xmpp-vala/src/module/xep/0030_service_discovery/info_result.vala +++ b/xmpp-vala/src/module/xep/0030_service_discovery/info_result.vala @@ -41,7 +41,6 @@ public class InfoResult { public InfoResult(Iq.Stanza iq_request) { iq = new Iq.Stanza.result(iq_request); - iq.to = iq_request.from; iq.stanza.put_node(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns()); } diff --git a/xmpp-vala/src/module/xep/0199_ping.vala b/xmpp-vala/src/module/xep/0199_ping.vala index 4902b0c7..ac467b35 100644 --- a/xmpp-vala/src/module/xep/0199_ping.vala +++ b/xmpp-vala/src/module/xep/0199_ping.vala @@ -5,7 +5,7 @@ using Xmpp.Core; namespace Xmpp.Xep.Ping { private const string NS_URI = "urn:xmpp:ping"; - public class Module : XmppStreamModule { + public class Module : XmppStreamModule, Iq.Handler { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0199_ping"); public delegate void OnResult(XmppStream stream); @@ -18,19 +18,20 @@ namespace Xmpp.Xep.Ping { } public override void attach(XmppStream stream) { - stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI, new IqHandlerImpl()); + stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI, this); + stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); } - public override void detach(XmppStream stream) { } + public override void detach(XmppStream stream) { + stream.get_module(Iq.Module.IDENTITY).unregister_from_namespace(NS_URI, this); + } + + public void on_iq_get(XmppStream stream, Iq.Stanza iq) { + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq)); + } + public void on_iq_set(XmppStream stream, Iq.Stanza iq) { } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } - - private class IqHandlerImpl : Iq.Handler, Object { - public void on_iq_get(XmppStream stream, Iq.Stanza iq) { - stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq)); - } - public void on_iq_set(XmppStream stream, Iq.Stanza iq) { } - } } }