diff options
Diffstat (limited to 'main/src/ui/conversation_content_view')
4 files changed, 264 insertions, 32 deletions
diff --git a/main/src/ui/conversation_content_view/call_widget.vala b/main/src/ui/conversation_content_view/call_widget.vala new file mode 100644 index 00000000..74525d11 --- /dev/null +++ b/main/src/ui/conversation_content_view/call_widget.vala @@ -0,0 +1,215 @@ +using Gee; +using Gdk; +using Gtk; +using Pango; + +using Dino.Entities; + +namespace Dino.Ui { + + public class CallMetaItem : ConversationSummary.ContentMetaItem { + + private StreamInteractor stream_interactor; + + public CallMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { + base(content_item); + this.stream_interactor = stream_interactor; + } + + public override Object? get_widget(Plugins.WidgetType type) { + CallItem call_item = content_item as CallItem; + return new CallWidget(stream_interactor, call_item.call, call_item.conversation) { visible=true }; + } + + public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; } + } + + [GtkTemplate (ui = "/im/dino/Dino/call_widget.ui")] + public class CallWidget : SizeRequestBox { + + [GtkChild] public Image image; + [GtkChild] public Label title_label; + [GtkChild] public Label subtitle_label; + [GtkChild] public Revealer incoming_call_revealer; + [GtkChild] public Button accept_call_button; + [GtkChild] public Button reject_call_button; + + private StreamInteractor stream_interactor; + private Call call; + private Conversation conversation; + public Call.State call_state { get; set; } // needs to be public for binding + private uint time_update_handler_id = 0; + + construct { + margin_top = 4; + size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; + } + + public CallWidget(StreamInteractor stream_interactor, Call call, Conversation conversation) { + this.stream_interactor = stream_interactor; + this.call = call; + this.conversation = conversation; + + size_allocate.connect((allocation) => { + if (allocation.height > parent.get_allocated_height()) { + Idle.add(() => { parent.queue_resize(); return false; }); + } + }); + + call.bind_property("state", this, "call-state"); + this.notify["call-state"].connect(update_widget); + + accept_call_button.clicked.connect(() => { + stream_interactor.get_module(Calls.IDENTITY).accept_call(call); + + var call_window = new CallWindow(); + call_window.controller = new CallWindowController(call_window, call, stream_interactor); + call_window.present(); + }); + + reject_call_button.clicked.connect(() => { + stream_interactor.get_module(Calls.IDENTITY).reject_call(call); + }); + + update_widget(); + } + + private void update_widget() { + incoming_call_revealer.reveal_child = false; + incoming_call_revealer.get_style_context().remove_class("incoming"); + + switch (call.state) { + case Call.State.RINGING: + image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR); + if (call.direction == Call.DIRECTION_INCOMING) { + bool video = stream_interactor.get_module(Calls.IDENTITY).should_we_send_video(call); + title_label.label = video ? _("Video call incoming") : _("Call incoming"); + subtitle_label.label = "Ring ring…!"; + incoming_call_revealer.reveal_child = true; + incoming_call_revealer.get_style_context().add_class("incoming"); + } else { + title_label.label = _("Establishing call"); + subtitle_label.label = "Ring ring…?"; + } + break; + case Call.State.ESTABLISHING: + image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR); + if (call.direction == Call.DIRECTION_INCOMING) { + bool video = stream_interactor.get_module(Calls.IDENTITY).should_we_send_video(call); + title_label.label = video ? _("Video call establishing") : _("Call establishing"); + subtitle_label.label = "Connecting…"; + } + break; + case Call.State.IN_PROGRESS: + image.set_from_icon_name("dino-phone-in-talk-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call in progress…"); + string duration = get_duration_string((new DateTime.now_utc()).difference(call.local_time)); + subtitle_label.label = _("Started %s ago").printf(duration); + + time_update_handler_id = Timeout.add_seconds(get_next_time_change() + 1, () => { + Source.remove(time_update_handler_id); + time_update_handler_id = 0; + update_widget(); + return true; + }); + + break; + case Call.State.OTHER_DEVICE_ACCEPTED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = call.direction == Call.DIRECTION_INCOMING ? _("Incoming call") : _("Outgoing call"); + subtitle_label.label = _("You handled this call on another device"); + + break; + case Call.State.ENDED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call ended"); + string formated_end = Util.format_time(call.end_time, _("%H∶%M"), _("%l∶%M %p")); + string duration = get_duration_string(call.end_time.difference(call.local_time)); + subtitle_label.label = _("Ended at %s").printf(formated_end) + + " · " + + _("Lasted for %s").printf(duration); + break; + case Call.State.MISSED: + image.set_from_icon_name("dino-phone-missed-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call missed"); + string who = null; + if (call.direction == Call.DIRECTION_INCOMING) { + who = "You"; + } else { + who = Util.get_participant_display_name(stream_interactor, conversation, call.to); + } + subtitle_label.label = "%s missed this call".printf(who); + break; + case Call.State.DECLINED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call declined"); + string who = null; + if (call.direction == Call.DIRECTION_INCOMING) { + who = "You"; + } else { + who = Util.get_participant_display_name(stream_interactor, conversation, call.to); + } + subtitle_label.label = "%s declined this call".printf(who); + break; + case Call.State.FAILED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call failed"); + subtitle_label.label = "Call failed to establish"; + break; + } + } + + private string get_duration_string(TimeSpan duration) { + DateTime a = new DateTime.now_utc(); + DateTime b = new DateTime.now_utc(); + a.difference(b); + + TimeSpan remainder_duration = duration; + + int hours = (int) Math.floor(remainder_duration / TimeSpan.HOUR); + remainder_duration -= hours * TimeSpan.HOUR; + + int minutes = (int) Math.floor(remainder_duration / TimeSpan.MINUTE); + remainder_duration -= minutes * TimeSpan.MINUTE; + + string ret = ""; + + if (hours > 0) { + ret += n("%i hour", "%i hours", hours).printf(hours); + } + + if (minutes > 0) { + if (ret.length > 0) { + ret += " "; + } + ret += n("%i minute", "%i minutes", minutes).printf(minutes); + } + + if (ret.length > 0) { + return ret; + } + + return _("seconds"); + } + + private int get_next_time_change() { + DateTime now = new DateTime.now_local(); + DateTime item_time = call.local_time; + + 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()); + } + } + + public override void dispose() { + base.dispose(); + + if (time_update_handler_id != 0) { + Source.remove(time_update_handler_id); + time_update_handler_id = 0; + } + } + } +} diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala index 97f15bf9..ef859bde 100644 --- a/main/src/ui/conversation_content_view/content_populator.vala +++ b/main/src/ui/conversation_content_view/content_populator.vala @@ -68,7 +68,10 @@ public class ContentProvider : ContentItemCollection, Object { return new MessageMetaItem(content_item, stream_interactor); } else if (content_item.type_ == FileItem.TYPE) { return new FileMetaItem(content_item, stream_interactor); + } else if (content_item.type_ == CallItem.TYPE) { + return new CallMetaItem(content_item, stream_interactor); } + critical("Got unknown content item type %s", content_item.type_); return null; } } @@ -85,6 +88,7 @@ public abstract class ContentMetaItem : Plugins.MetaConversationItem { this.mark = content_item.mark; content_item.bind_property("mark", this, "mark"); + content_item.bind_property("encryption", this, "encryption"); this.can_merge = true; this.requires_avatar = true; 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 c0099bf4..bcb6864e 100644 --- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala @@ -104,7 +104,7 @@ public class ItemMetaDataHeader : Box { [GtkChild] public Label dot_label; [GtkChild] public Label time_label; public Image received_image = new Image() { opacity=0.4 }; - public Image? unencrypted_image = null; + public Widget? encryption_image = null; public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON", 17, 12); @@ -124,50 +124,66 @@ public class ItemMetaDataHeader : Box { update_name_label(); name_label.style_updated.connect(update_name_label); + conversation.notify["encryption"].connect(update_unencrypted_icon); + item.notify["encryption"].connect(update_encryption_icon); + update_encryption_icon(); + + this.add(received_image); + + if (item.time != null) { + update_time(); + } + + item.bind_property("mark", this, "item-mark"); + this.notify["item-mark"].connect_after(update_received_mark); + update_received_mark(); + } + + private void update_encryption_icon() { Application app = GLib.Application.get_default() as Application; ContentMetaItem ci = item as ContentMetaItem; - if (ci != null) { + if (item.encryption != Encryption.NONE && ci != null) { + Widget? widget = null; foreach(var e in app.plugin_registry.encryption_list_entries) { if (e.encryption == item.encryption) { - Object? w = e.get_encryption_icon(conversation, ci.content_item); - if (w != null) { - this.add(w as Widget); - } else { - Image image = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; - this.add(image); - } + widget = e.get_encryption_icon(conversation, ci.content_item) as Widget; break; } } + if (widget == null) { + widget = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; + } + update_encryption_image(widget); } if (item.encryption == Encryption.NONE) { - conversation.notify["encryption"].connect(update_unencrypted_icon); update_unencrypted_icon(); } + } - this.add(received_image); - - if (item.time != null) { - update_time(); + private void update_unencrypted_icon() { + if (item.encryption != Encryption.NONE) return; + + if (conversation.encryption != Encryption.NONE && encryption_image == null) { + Image image = new Image() { opacity=0.4, visible = true }; + image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER); + image.tooltip_text = _("Unencrypted"); + update_encryption_image(image); + Util.force_error_color(image); + } else if (conversation.encryption == Encryption.NONE && encryption_image != null) { + update_encryption_image(null); } - - item.bind_property("mark", this, "item-mark"); - this.notify["item-mark"].connect_after(update_received_mark); - update_received_mark(); } - private void update_unencrypted_icon() { - if (conversation.encryption != Encryption.NONE && unencrypted_image == null) { - unencrypted_image = new Image() { opacity=0.4, visible = true }; - unencrypted_image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER); - unencrypted_image.tooltip_text = _("Unencrypted"); - this.add(unencrypted_image); - this.reorder_child(unencrypted_image, 3); - Util.force_error_color(unencrypted_image); - } else if (conversation.encryption == Encryption.NONE && unencrypted_image != null) { - this.remove(unencrypted_image); - unencrypted_image = null; + private void update_encryption_image(Widget? widget) { + if (encryption_image != null) { + this.remove(encryption_image); + encryption_image = null; + } + if (widget != null) { + this.add(widget); + this.reorder_child(widget, 3); + encryption_image = widget; } } diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 9b748876..7d77ba11 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -32,9 +32,6 @@ public class FileWidget : SizeRequestBox { DEFAULT } - private const int MAX_HEIGHT = 300; - private const int MAX_WIDTH = 600; - private StreamInteractor stream_interactor; private FileTransfer file_transfer; public FileTransfer.State file_transfer_state { get; set; } |