2021-03-19 22:09:56 +00:00
|
|
|
|
using Gee;
|
|
|
|
|
using Gdk;
|
|
|
|
|
using Gtk;
|
|
|
|
|
using Pango;
|
2021-11-04 16:35:11 +00:00
|
|
|
|
using Xmpp;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
|
|
|
|
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;
|
2021-11-04 16:35:11 +00:00
|
|
|
|
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call_item.call];
|
|
|
|
|
return new CallWidget(stream_interactor, call_item.call, call_state, call_item.conversation) { visible=true };
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
2021-10-11 21:08:50 +00:00
|
|
|
|
[GtkChild] public unowned Image image;
|
|
|
|
|
[GtkChild] public unowned Label title_label;
|
|
|
|
|
[GtkChild] public unowned Label subtitle_label;
|
|
|
|
|
[GtkChild] public unowned Revealer incoming_call_revealer;
|
2021-11-04 16:35:11 +00:00
|
|
|
|
[GtkChild] public unowned Box outer_additional_box;
|
|
|
|
|
[GtkChild] public unowned Box incoming_call_box;
|
|
|
|
|
[GtkChild] public unowned Box multiparty_peer_box;
|
2021-10-11 21:08:50 +00:00
|
|
|
|
[GtkChild] public unowned Button accept_call_button;
|
|
|
|
|
[GtkChild] public unowned Button reject_call_button;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
|
|
|
|
private StreamInteractor stream_interactor;
|
2021-11-04 16:35:11 +00:00
|
|
|
|
private CallState call_manager;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-04 16:35:11 +00:00
|
|
|
|
/** @param call_state Null if it's an old call and we can't interact with it anymore */
|
|
|
|
|
public CallWidget(StreamInteractor stream_interactor, Call call, CallState? call_state, Conversation conversation) {
|
2021-03-19 22:09:56 +00:00
|
|
|
|
this.stream_interactor = stream_interactor;
|
2021-11-04 16:35:11 +00:00
|
|
|
|
this.call_manager = call_state;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
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");
|
2021-11-04 16:35:11 +00:00
|
|
|
|
this.notify["call-state"].connect(update_call_state);
|
|
|
|
|
|
|
|
|
|
if (call_manager != null && (call.state == Call.State.ESTABLISHING || call.state == Call.State.IN_PROGRESS)) {
|
|
|
|
|
call_manager.peer_joined.connect(update_counterparts);
|
|
|
|
|
}
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
|
|
|
|
accept_call_button.clicked.connect(() => {
|
2021-11-04 16:35:11 +00:00
|
|
|
|
call_manager.accept();
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
|
|
|
|
var call_window = new CallWindow();
|
2021-11-04 16:35:11 +00:00
|
|
|
|
call_window.controller = new CallWindowController(call_window, call_state, stream_interactor);
|
2021-03-19 22:09:56 +00:00
|
|
|
|
call_window.present();
|
|
|
|
|
});
|
|
|
|
|
|
2021-11-04 16:35:11 +00:00
|
|
|
|
reject_call_button.clicked.connect(call_manager.reject);
|
|
|
|
|
|
|
|
|
|
update_call_state();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void update_counterparts() {
|
|
|
|
|
if (call.state != Call.State.IN_PROGRESS && call.state != Call.State.ENDED) return;
|
|
|
|
|
if (call.counterparts.size <= 1) return;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
2021-11-04 16:35:11 +00:00
|
|
|
|
multiparty_peer_box.foreach((widget) => { multiparty_peer_box.remove(widget); });
|
|
|
|
|
|
|
|
|
|
foreach (Jid counterpart in call.counterparts) {
|
|
|
|
|
AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
|
|
|
|
|
image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid);
|
|
|
|
|
multiparty_peer_box.add(image);
|
|
|
|
|
}
|
|
|
|
|
AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
|
|
|
|
|
image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid);
|
|
|
|
|
multiparty_peer_box.add(image2);
|
|
|
|
|
|
|
|
|
|
outer_additional_box.get_style_context().add_class("multiparty-participants");
|
|
|
|
|
|
|
|
|
|
multiparty_peer_box.visible = true;
|
|
|
|
|
incoming_call_box.visible = false;
|
|
|
|
|
incoming_call_revealer.reveal_child = true;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-04 16:35:11 +00:00
|
|
|
|
private void update_call_state() {
|
2021-03-19 22:09:56 +00:00
|
|
|
|
incoming_call_revealer.reveal_child = false;
|
|
|
|
|
incoming_call_revealer.get_style_context().remove_class("incoming");
|
2021-11-04 16:35:11 +00:00
|
|
|
|
outer_additional_box.get_style_context().remove_class("incoming-call-box");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
|
|
|
|
|
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) {
|
2021-11-04 16:35:11 +00:00
|
|
|
|
bool video = call_manager.should_we_send_video();
|
2021-09-25 14:04:13 +00:00
|
|
|
|
title_label.label = video ? _("Incoming video call") : _("Incoming call");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
subtitle_label.label = "Ring ring…!";
|
2021-11-04 16:35:11 +00:00
|
|
|
|
incoming_call_box.visible = true;
|
2021-03-19 22:09:56 +00:00
|
|
|
|
incoming_call_revealer.reveal_child = true;
|
|
|
|
|
incoming_call_revealer.get_style_context().add_class("incoming");
|
2021-11-04 16:35:11 +00:00
|
|
|
|
outer_additional_box.get_style_context().add_class("incoming-call-box");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
} else {
|
2021-09-25 14:04:13 +00:00
|
|
|
|
title_label.label = _("Calling…");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
subtitle_label.label = "Ring ring…?";
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Call.State.ESTABLISHING:
|
|
|
|
|
case Call.State.IN_PROGRESS:
|
|
|
|
|
image.set_from_icon_name("dino-phone-in-talk-symbolic", IconSize.LARGE_TOOLBAR);
|
2021-09-25 14:04:13 +00:00
|
|
|
|
title_label.label = _("Call started");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
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, () => {
|
2021-11-04 16:35:11 +00:00
|
|
|
|
if (time_update_handler_id != 0) {
|
|
|
|
|
Source.remove(time_update_handler_id);
|
|
|
|
|
time_update_handler_id = 0;
|
|
|
|
|
update_call_state();
|
|
|
|
|
}
|
2021-03-19 22:09:56 +00:00
|
|
|
|
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) +
|
|
|
|
|
" · " +
|
2021-09-25 14:04:13 +00:00
|
|
|
|
_("Lasted %s").printf(duration);
|
2021-03-19 22:09:56 +00:00
|
|
|
|
break;
|
|
|
|
|
case Call.State.MISSED:
|
|
|
|
|
image.set_from_icon_name("dino-phone-missed-symbolic", IconSize.LARGE_TOOLBAR);
|
|
|
|
|
title_label.label = _("Call missed");
|
|
|
|
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
2021-09-25 14:04:13 +00:00
|
|
|
|
subtitle_label.label = _("You missed this call");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
} else {
|
2021-11-04 16:35:11 +00:00
|
|
|
|
string who = Util.get_conversation_display_name(stream_interactor, conversation);
|
2021-09-25 14:04:13 +00:00
|
|
|
|
subtitle_label.label = _("%s missed this call").printf(who);
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Call.State.DECLINED:
|
|
|
|
|
image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
|
|
|
|
|
title_label.label = _("Call declined");
|
|
|
|
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
2021-09-25 14:04:13 +00:00
|
|
|
|
subtitle_label.label = _("You declined this call");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
} else {
|
2021-11-04 16:35:11 +00:00
|
|
|
|
string who = Util.get_conversation_display_name(stream_interactor, conversation);
|
2021-09-25 14:04:13 +00:00
|
|
|
|
subtitle_label.label = _("%s declined this call").printf(who);
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Call.State.FAILED:
|
|
|
|
|
image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
|
|
|
|
|
title_label.label = _("Call failed");
|
2021-04-29 13:56:22 +00:00
|
|
|
|
subtitle_label.label = "Call failed to establish";
|
2021-03-19 22:09:56 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2021-11-04 16:35:11 +00:00
|
|
|
|
|
|
|
|
|
update_counterparts();
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-25 14:04:13 +00:00
|
|
|
|
return _("a few seconds");
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2021-11-04 16:35:11 +00:00
|
|
|
|
if (call_manager != null) {
|
|
|
|
|
call_manager.peer_joined.disconnect(update_counterparts);
|
|
|
|
|
}
|
2021-03-19 22:09:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|