Skip to content

Commit

Permalink
voice handling in moderated groups (#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfieanmol authored Apr 22, 2020
1 parent 51a2372 commit 2631a9b
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 2 deletions.
4 changes: 3 additions & 1 deletion libdino/src/plugin/interfaces.vala
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,13 @@ public class InputFieldStatus : Object {
public string? message;
public MessageType message_type;
public InputState input_state;
public bool contains_markup;

public InputFieldStatus(string? message, MessageType message_type, InputState input_state) {
public InputFieldStatus(string? message, MessageType message_type, InputState input_state, bool contains_markup = false) {
this.message = message;
this.message_type = message_type;
this.input_state = input_state;
this.contains_markup = contains_markup;
}
}

Expand Down
30 changes: 30 additions & 0 deletions libdino/src/service/muc_manager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class MucManager : StreamInteractionModule, Object {
public signal void room_info_updated(Account account, Jid muc_jid);
public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant);
public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void voice_request_received(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label);
public signal void received_occupant_role(Account account, Jid jid, Xep.Muc.Role? role);
public signal void bookmarks_updated(Account account, Set<Conference> conferences);
public signal void conference_added(Account account, Conference conference);
public signal void conference_removed(Account account, Jid jid);
Expand Down Expand Up @@ -118,6 +120,16 @@ public class MucManager : StreamInteractionModule, Object {
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, jid.bare_jid, nick, role);
}

public void change_role(Account account, Jid jid, string nick, string role) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_role(stream, jid.bare_jid, nick, role);
}

public void request_voice(Account account, Jid jid) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).request_voice(stream, jid.bare_jid);
}

public bool kick_possible(Account account, Jid occupant) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant);
Expand All @@ -137,6 +149,18 @@ public class MucManager : StreamInteractionModule, Object {
return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY);
}

public bool is_moderated_room(Account account, Jid jid) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) {
return false;
}
Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY);
if (flag == null) {
return false;
}
return flag.has_room_feature(jid, Xep.Muc.Feature.MODERATED);
}

public bool is_public_room(Account account, Jid jid) {
return is_groupchat(jid, account) && !is_private_room(account, jid);
}
Expand Down Expand Up @@ -285,6 +309,12 @@ public class MucManager : StreamInteractionModule, Object {
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).invite_received.connect( (stream, room_jid, from_jid, password, reason) => {
invite_received(account, room_jid, from_jid, password, reason);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).voice_request_received.connect( (stream, room_jid, from_jid, nick, role, label) => {
voice_request_received(account, room_jid, from_jid, nick, role, label);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_role.connect( (stream, from_jid, role) => {
received_occupant_role(account, from_jid, role);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_info_updated.connect( (stream, muc_jid) => {
room_info_updated(account, muc_jid);
});
Expand Down
2 changes: 2 additions & 0 deletions libdino/src/service/notification_events.vala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
public signal void notify_subscription_request(Conversation conversation);
public signal void notify_connection_error(Account account, ConnectionManager.ConnectionError error);
public signal void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void notify_voice_request(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label);

private StreamInteractor stream_interactor;

Expand All @@ -27,6 +28,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received);
stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect(on_received_subscription_request);
stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => notify_muc_invite(account, room_jid, from_jid, password, reason));
stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick, role, label) => notify_voice_request(account, room_jid, from_jid, nick, role, label));
stream_interactor.connection_manager.connection_error.connect((account, error) => notify_connection_error(account, error));
}

Expand Down
1 change: 1 addition & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ SOURCES

src/ui/contact_details/blocking_provider.vala
src/ui/contact_details/settings_provider.vala
src/ui/contact_details/permissions_provider.vala
src/ui/contact_details/dialog.vala
src/ui/contact_details/muc_config_form_provider.vala

Expand Down
8 changes: 8 additions & 0 deletions main/src/ui/application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
add_action(accept_muc_invite_action);

SimpleAction accept_voice_request_action = new SimpleAction("accept-voice-request", VariantType.INT32);
accept_voice_request_action.activate.connect((variant) => {
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32());
if (conversation == null) return;
stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, conversation.nickname, "participant");
});
add_action(accept_voice_request_action);

SimpleAction loop_conversations_action = new SimpleAction("loop_conversations", null);
loop_conversations_action.activate.connect(() => { window.loop_conversations(false); });
add_action(loop_conversations_action);
Expand Down
35 changes: 34 additions & 1 deletion main/src/ui/chat_input/chat_input_controller.vala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using Gtk;
using Dino.Entities;

namespace Dino.Ui {
private const string OPEN_CONVERSATION_DETAILS_URI = "x-dino:open-conversation-details";

public class ChatInputController : Object {

Expand Down Expand Up @@ -38,8 +39,19 @@ public class ChatInputController : Object {
chat_text_view_controller.send_text.connect(send_text);

chat_input.encryption_widget.encryption_changed.connect(on_encryption_changed);

chat_input.file_button.clicked.connect(() => file_picker_selected());

stream_interactor.get_module(MucManager.IDENTITY).received_occupant_role.connect(update_moderated_input_status);
stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect(update_moderated_input_status);

status_description_label.activate_link.connect((uri) => {
if (uri == OPEN_CONVERSATION_DETAILS_URI){
ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation);
contact_details_dialog.present();
}
return true;
});
}

public void set_conversation(Conversation conversation) {
Expand All @@ -51,6 +63,10 @@ public class ChatInputController : Object {

chat_input.initialize_for_conversation(conversation);
chat_text_view_controller.initialize_for_conversation(conversation);

Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);

update_moderated_input_status(conversation.account, own_jid);
}

public void set_file_upload_active(bool active) {
Expand All @@ -69,6 +85,10 @@ public class ChatInputController : Object {
input_field_status = status;

chat_input.set_input_state(status.message_type);

if (status.contains_markup) status_description_label.use_markup = true;
else status_description_label.use_markup = false;

status_description_label.label = status.message;

chat_input.file_button.sensitive = status.input_state == Plugins.InputFieldStatus.InputState.NORMAL;
Expand Down Expand Up @@ -147,6 +167,19 @@ public class ChatInputController : Object {
stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation);
}
}

private void update_moderated_input_status(Account account, Xmpp.Jid jid) {
Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
if (conversation.type_ == conversation.Type.GROUPCHAT){
if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) &&
stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account)==Xmpp.Xep.Muc.Role.VISITOR) {
set_input_field_status(new Plugins.InputFieldStatus(_("This conference does not allow you to send messages. %s").printf("<a href=\"" + OPEN_CONVERSATION_DETAILS_URI + "\">" + _("Request permission") + "</a>"),
Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND, true));
} else {
reset_input_field_status();
}
}
}

private bool on_text_input_key_press(EventKey event) {
if (event.keyval == Gdk.Key.Up && chat_input.chat_text_view.text_view.buffer.text == "") {
Expand Down
1 change: 1 addition & 0 deletions main/src/ui/contact_details/dialog.vala
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class Dialog : Gtk.Dialog {
app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new BlockingProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new PermissionsProvider(stream_interactor));

foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) {
provider.populate(conversation, contact_details, Plugins.WidgetType.GTK);
Expand Down
29 changes: 29 additions & 0 deletions main/src/ui/contact_details/permissions_provider.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Gtk;

using Dino.Entities;

namespace Dino.Ui.ContactDetails {

public class PermissionsProvider : Plugins.ContactDetailsProvider, Object {
public string id { get { return "permissions"; } }

private StreamInteractor stream_interactor;

public PermissionsProvider(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
}

public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) {
if (type != Plugins.WidgetType.GTK) return;

Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
if (stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account)==Xmpp.Xep.Muc.Role.VISITOR){
Button voice_request = new Button() {visible=true, label=_("Request")};
voice_request.clicked.connect(()=>stream_interactor.get_module(MucManager.IDENTITY).request_voice(conversation.account, conversation.counterpart));
contact_details.add(_("Permissions"), _("Request permission to send messages"), "", voice_request);

}
}
}

}
1 change: 1 addition & 0 deletions main/src/ui/contact_details/settings_provider.vala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object {
combobox.append("on", get_notify_setting_string(Conversation.NotifySetting.ON));
combobox.append("off", get_notify_setting_string(Conversation.NotifySetting.OFF));
contact_details.add(DETAILS_HEADLINE_ROOM, _("Notifications"), "", combobox);

combobox.active_id = get_notify_setting_id(conversation.notify_setting);
combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } );
}
Expand Down
23 changes: 23 additions & 0 deletions main/src/ui/notifications.vala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class Notifications : Object {
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_subscription_request.connect(notify_subscription_request);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_connection_error.connect(notify_connection_error);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_muc_invite.connect(on_invite_received);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_voice_request.connect(on_voice_request_received);
}

private async void notify_content_item(ContentItem content_item, Conversation conversation) {
Expand Down Expand Up @@ -140,6 +141,28 @@ public class Notifications : Object {
notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", group_conversation.id);
GLib.Application.get_default().send_notification(null, notification);
}

private async void on_voice_request_received(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label) {
Conversation? direct_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(room_jid, account, Conversation.Type.GROUPCHAT);
if (direct_conversation == null) return;
string display_name = Util.get_participant_display_name(stream_interactor, direct_conversation, from_jid);
string display_room = room_jid.bare_jid.to_string();
Notification notification = new Notification(_("Permission request"));
string body = _("%s requests the permission to write in %s").printf(display_name, display_room);
notification.set_body(body);

try {
Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, direct_conversation)).size(40, 40).draw_image_surface();
notification.set_icon(get_pixbuf_icon(jid_avatar));
} catch (Error e) { }

Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT);
group_conversation.nickname = nick;
notification.set_default_action_and_target_value("app.accept-voice-request", new Variant.int32(group_conversation.id));
notification.add_button_with_target_value(_("Deny"), "app.deny-voice-request", group_conversation.id);
notification.add_button_with_target_value(_("Accept"), "app.accept-voice-request", group_conversation.id);
GLib.Application.get_default().send_notification(null, notification);
}

private Icon get_pixbuf_icon(Cairo.ImageSurface surface) throws Error {
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
Expand Down
21 changes: 21 additions & 0 deletions main/src/ui/occupant_menu/view.vala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ public class View : Popover {
outer_box.add(kick_button);
kick_button.clicked.connect(kick_button_clicked);
}
if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) && role == Xmpp.Xep.Muc.Role.MODERATOR){
if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR) {
ModelButton voice_button = new ModelButton() { active=true, text=_("Grant write permission"), visible=true };
outer_box.add(voice_button);
voice_button.clicked.connect(() =>
voice_button_clicked("participant"));
}
else if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.PARTICIPANT){
ModelButton voice_button = new ModelButton() { active=true, text=_("Revoke write permission"), visible=true };
outer_box.add(voice_button);
voice_button.clicked.connect(() =>
voice_button_clicked("visitor"));
}

}

if (jid_menu != null) jid_menu.destroy();
stack.add_named(outer_box, "menu");
Expand All @@ -119,6 +134,12 @@ public class View : Popover {

stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, selected_jid.resourcepart);
}

private void voice_button_clicked(string role) {
if (selected_jid == null) return;

stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, selected_jid.resourcepart, role);
}
}

}
1 change: 1 addition & 0 deletions xmpp-vala/src/module/xep/0004_data_forms.vala
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public class DataForm {
public ListSingleField(StanzaNode node) {
base.from_node(node);
type_ = Type.LIST_SINGLE;
node.set_attribute("type", "list-single");
}
}

Expand Down
Loading

0 comments on commit 2631a9b

Please sign in to comment.