diff --git a/libdino/src/service/call_state.vala b/libdino/src/service/call_state.vala index 65d33557..45d7c00d 100644 --- a/libdino/src/service/call_state.vala +++ b/libdino/src/service/call_state.vala @@ -60,7 +60,7 @@ public class Dino.CallState : Object { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; - Gee.List occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(muc, call.account); + Gee.List occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(muc, call.account); foreach (Jid occupant in occupants) { Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account); if (real_jid == null) continue; diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index da17a8d1..b5fb3d38 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -259,27 +259,46 @@ public class MucManager : StreamInteractionModule, Object { return is_groupchat(jid, account) && !is_private_room(account, jid); } - public Gee.List? get_occupants(Jid jid, Account account) { + public Gee.List? get_all_members(Jid jid, Account account) { if (is_groupchat(jid, account)) { Gee.List ret = new ArrayList(Jid.equals_func); - Gee.List? full_jids = get_offline_members(jid, account); + + // This should return all members of the chat + Gee.List? members = get_offline_members(jid, account); + if (members != null) { + ret.add_all(members); + } + + return ret; + } + + return null; + } + + public Gee.List? get_members(Jid jid, Account account) { + if (is_groupchat(jid, account)) { + Gee.List ret = new ArrayList(Jid.equals_func); + Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); if (full_jids != null) { ret.add_all(full_jids); // Remove eventual presence from bare jid ret.remove(jid); } + return ret; } + return null; } - public Gee.List? get_other_occupants(Jid jid, Account account) { - Gee.List? occupants = get_occupants(jid, account); + public Gee.List? get_other_members(Jid jid, Account account) { + Gee.List? members = get_members(jid, account); Jid? own_jid = get_own_jid(jid, account); - if (occupants != null && own_jid != null) { - occupants.remove(own_jid); + if (members != null && own_jid != null) { + members.remove(own_jid); } - return occupants; + + return members; } public bool is_groupchat(Jid jid, Account account) { diff --git a/main/data/occupant_list_item.ui b/main/data/occupant_list_item.ui index 47e63bc9..72586191 100644 --- a/main/data/occupant_list_item.ui +++ b/main/data/occupant_list_item.ui @@ -6,7 +6,7 @@ 7 3 7 - 10 + 5 30 @@ -25,5 +25,17 @@ + + + 1 + end + 1 + 0 + + 2 + 0 + + + \ No newline at end of file diff --git a/main/src/ui/chat_input/occupants_tab_completer.vala b/main/src/ui/chat_input/occupants_tab_completer.vala index e50fe831..f58c0ad5 100644 --- a/main/src/ui/chat_input/occupants_tab_completer.vala +++ b/main/src/ui/chat_input/occupants_tab_completer.vala @@ -107,7 +107,7 @@ public class OccupantsTabCompletor { Gee.List ret = generate_completions_from_messages(prefix); // Then, suggest other nicks in alphabetical order - Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); + Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(conversation.counterpart, conversation.account); Gee.List filtered_occupants = new ArrayList(); if (occupants != null) { foreach (Jid jid in occupants) { diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index ce5a1981..e2ef9227 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -17,7 +17,9 @@ public class List : Box { private Conversation conversation; private string[]? filter_values; - private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); + + // List of all chat members with corresponding widgets + private HashMap rows = new HashMap(); public HashMap row_wrappers = new HashMap(); public List(StreamInteractor stream_interactor, Conversation conversation) { @@ -27,18 +29,46 @@ public class List : Box { list_box.set_filter_func(filter); search_entry.search_changed.connect(refilter); - stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); + stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_received_online_presence); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect(on_received_offline_presence); initialize_for_conversation(conversation); } + public bool get_status(Jid jid, Account account) { + Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); + + debug("Get presence status for %s", jid.bare_jid.to_string()); + string presence_str = null; + if (full_jids != null){ + // Iterate over all connected devices + for (int i = 0; i < full_jids.size; i++) { + Jid full_jid = full_jids[i]; + presence_str = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, account); + switch(presence_str) { + case "online": { + // Return online status if user is online on at least one device + return true; + } + } + } + } else { + return false; + } + + return false; + } + public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; - Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); - if (occupants != null) { - foreach (Jid occupant in occupants) { - add_occupant(occupant); + + var identity = stream_interactor.get_module(MucManager.IDENTITY); + Gee.List? members = identity.get_all_members(conversation.counterpart, conversation.account); + if (members != null) { + // Add all members and their status to the list + foreach (Jid member in members) { + bool online = get_status(member, conversation.account); + add_member(member, online); } } list_box.invalidate_filter(); @@ -53,34 +83,75 @@ public class List : Box { list_box.invalidate_filter(); } - public void add_occupant(Jid jid) { - var row_wrapper = new ListRow(stream_interactor, conversation, jid); - var widget = row_wrapper.get_widget(); + public void add_member(Jid jid, bool online) { + // HACK: + // Here we track members based on their names (not jids) + // Sometimes the same member can be referenced with different jids, for example: + // When initializing the conversation (see initialize_for_conversation function), + // we reference members like this: + // test_user@test_domain (using a local part, without a resource) + // However when updating status, we get the jid in the following format + // local_domain@test_domain/test_user (using a resource) + string member_name = null; + if (jid.resourcepart != null) { + member_name = jid.resourcepart; + } else { + member_name = jid.localpart; + } - row_wrappers[widget] = row_wrapper; - rows[jid] = widget; - list_box.append(widget); - } + if (member_name == null) { + return; + } - public void remove_occupant(Jid jid) { - list_box.remove(rows[jid]); - rows.unset(jid); + if (!rows.has_key(member_name)) { + debug("adding new member %s", jid.to_string()); + debug("local %s", jid.localpart); + debug("domain %s", jid.domainpart); + debug("resource %s", jid.resourcepart); + + var row_wrapper = new ListRow(stream_interactor, conversation, jid); + var widget = row_wrapper.get_widget(); + + if (online) { + row_wrapper.set_online(); + } else { + row_wrapper.set_offline(); + } + + row_wrappers[widget] = row_wrapper; + rows[member_name] = widget; + list_box.append(widget); + } } private void on_received_offline_presence(Jid jid, Account account) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { - if (rows.has_key(jid)) { - remove_occupant(jid); + var member_name = jid.resourcepart; + if (member_name == null) { + return; + } + + if (rows.has_key(member_name)) { + row_wrappers[rows[member_name]].set_offline(); + debug("%s is now offline", jid.to_string()); } list_box.invalidate_filter(); } } - private void on_show_received(Jid jid, Account account) { + private void on_received_online_presence(Jid jid, Account account) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { - if (!rows.has_key(jid)) { - add_occupant(jid); + var member_name = jid.resourcepart; + if (member_name == null) { + return; } + + if (!rows.has_key(member_name)) { + add_member(jid, true); + } + + row_wrappers[rows[member_name]].set_online(); + debug("%s is now online", jid.to_string()); list_box.invalidate_filter(); } } diff --git a/main/src/ui/occupant_menu/list_row.vala b/main/src/ui/occupant_menu/list_row.vala index 476d9e61..bd6519ec 100644 --- a/main/src/ui/occupant_menu/list_row.vala +++ b/main/src/ui/occupant_menu/list_row.vala @@ -11,6 +11,9 @@ public class ListRow : Object { private AvatarPicture picture; public Label name_label; + // TODO: use something more visual for status + public Label status; + public Conversation? conversation; public Jid? jid; @@ -19,6 +22,8 @@ public class ListRow : Object { main_grid = (Grid) builder.get_object("main_grid"); picture = (AvatarPicture) builder.get_object("picture"); name_label = (Label) builder.get_object("name_label"); + status = (Label) builder.get_object("status_label"); + status.label = "(unknown)"; } public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { @@ -37,6 +42,14 @@ public class ListRow : Object { public Widget get_widget() { return main_grid; } + + public void set_online() { + status.label = (""); + } + + public void set_offline() { + status.label = ("(offline)"); + } } } diff --git a/plugins/omemo/src/ui/bad_messages_populator.vala b/plugins/omemo/src/ui/bad_messages_populator.vala index 8f087482..050b2fbc 100644 --- a/plugins/omemo/src/ui/bad_messages_populator.vala +++ b/plugins/omemo/src/ui/bad_messages_populator.vala @@ -154,7 +154,7 @@ public class BadMessagesWidget : Box { } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { who = jid.to_string(); // `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online) - var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); + var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account); if (occupants == null) return; foreach (Jid occupant in occupants) { if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) { diff --git a/plugins/openpgp/src/encryption_list_entry.vala b/plugins/openpgp/src/encryption_list_entry.vala index cf5da8c4..68a31f38 100644 --- a/plugins/openpgp/src/encryption_list_entry.vala +++ b/plugins/openpgp/src/encryption_list_entry.vala @@ -53,7 +53,7 @@ private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { } } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); - Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); + Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (offline_members != null) muc_jids.add_all(offline_members); diff --git a/plugins/openpgp/src/manager.vala b/plugins/openpgp/src/manager.vala index 6ec30acc..8b9a3a68 100644 --- a/plugins/openpgp/src/manager.vala +++ b/plugins/openpgp/src/manager.vala @@ -36,7 +36,7 @@ public class Manager : StreamInteractionModule, Object { keys.add(db.get_account_key(conversation.account)); if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); - Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); + Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(offline_members);