experimental: display online/offline status for conversation members

This commit is contained in:
Konstantin Kuznetsov 2024-03-28 17:15:27 +03:00 committed by Maxim Logaev
parent f2096694c6
commit 7976859639
9 changed files with 149 additions and 34 deletions

View file

@ -60,7 +60,7 @@ public class Dino.CallState : Object {
XmppStream stream = stream_interactor.get_stream(call.account); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; if (stream == null) return;
Gee.List<Jid> occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(muc, call.account); Gee.List<Jid> occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(muc, call.account);
foreach (Jid occupant in occupants) { foreach (Jid occupant in occupants) {
Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account); Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account);
if (real_jid == null) continue; if (real_jid == null) continue;

View file

@ -259,27 +259,46 @@ public class MucManager : StreamInteractionModule, Object {
return is_groupchat(jid, account) && !is_private_room(account, jid); return is_groupchat(jid, account) && !is_private_room(account, jid);
} }
public Gee.List<Jid>? get_occupants(Jid jid, Account account) { public Gee.List<Jid>? get_all_members(Jid jid, Account account) {
if (is_groupchat(jid, account)) { if (is_groupchat(jid, account)) {
Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func); Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
Gee.List<Jid>? full_jids = get_offline_members(jid, account);
// This should return all members of the chat
Gee.List<Jid>? members = get_offline_members(jid, account);
if (members != null) {
ret.add_all(members);
}
return ret;
}
return null;
}
public Gee.List<Jid>? get_members(Jid jid, Account account) {
if (is_groupchat(jid, account)) {
Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
if (full_jids != null) { if (full_jids != null) {
ret.add_all(full_jids); ret.add_all(full_jids);
// Remove eventual presence from bare jid // Remove eventual presence from bare jid
ret.remove(jid); ret.remove(jid);
} }
return ret; return ret;
} }
return null; return null;
} }
public Gee.List<Jid>? get_other_occupants(Jid jid, Account account) { public Gee.List<Jid>? get_other_members(Jid jid, Account account) {
Gee.List<Jid>? occupants = get_occupants(jid, account); Gee.List<Jid>? members = get_members(jid, account);
Jid? own_jid = get_own_jid(jid, account); Jid? own_jid = get_own_jid(jid, account);
if (occupants != null && own_jid != null) { if (members != null && own_jid != null) {
occupants.remove(own_jid); members.remove(own_jid);
} }
return occupants;
return members;
} }
public bool is_groupchat(Jid jid, Account account) { public bool is_groupchat(Jid jid, Account account) {

View file

@ -6,7 +6,7 @@
<property name="margin-start">7</property> <property name="margin-start">7</property>
<property name="margin-bottom">3</property> <property name="margin-bottom">3</property>
<property name="margin-end">7</property> <property name="margin-end">7</property>
<property name="column-spacing">10</property> <property name="column-spacing">5</property>
<child> <child>
<object class="DinoUiAvatarPicture" id="picture"> <object class="DinoUiAvatarPicture" id="picture">
<property name="height-request">30</property> <property name="height-request">30</property>
@ -25,5 +25,17 @@
</layout> </layout>
</object> </object>
</child> </child>
<child>
<object class="GtkLabel" id="status_label">
<property name="max_width_chars">1</property>
<property name="ellipsize">end</property>
<property name="hexpand">1</property>
<property name="xalign">0</property>
<layout>
<property name="column">2</property>
<property name="row">0</property>
</layout>
</object>
</child>
</object> </object>
</interface> </interface>

View file

@ -107,7 +107,7 @@ public class OccupantsTabCompletor {
Gee.List<string> ret = generate_completions_from_messages(prefix); Gee.List<string> ret = generate_completions_from_messages(prefix);
// Then, suggest other nicks in alphabetical order // Then, suggest other nicks in alphabetical order
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(conversation.counterpart, conversation.account);
Gee.List<string> filtered_occupants = new ArrayList<string>(); Gee.List<string> filtered_occupants = new ArrayList<string>();
if (occupants != null) { if (occupants != null) {
foreach (Jid jid in occupants) { foreach (Jid jid in occupants) {

View file

@ -17,7 +17,9 @@ public class List : Box {
private Conversation conversation; private Conversation conversation;
private string[]? filter_values; private string[]? filter_values;
private HashMap<Jid, Widget> rows = new HashMap<Jid, Widget>(Jid.hash_func, Jid.equals_func);
// List of all chat members with corresponding widgets
private HashMap<string, Widget> rows = new HashMap<string, Widget>();
public HashMap<Widget, ListRow> row_wrappers = new HashMap<Widget, ListRow>(); public HashMap<Widget, ListRow> row_wrappers = new HashMap<Widget, ListRow>();
public List(StreamInteractor stream_interactor, Conversation conversation) { public List(StreamInteractor stream_interactor, Conversation conversation) {
@ -27,18 +29,46 @@ public class List : Box {
list_box.set_filter_func(filter); list_box.set_filter_func(filter);
search_entry.search_changed.connect(refilter); 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); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect(on_received_offline_presence);
initialize_for_conversation(conversation); initialize_for_conversation(conversation);
} }
public bool get_status(Jid jid, Account account) {
Gee.List<Jid>? 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) { public void initialize_for_conversation(Conversation conversation) {
this.conversation = conversation; this.conversation = conversation;
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account);
if (occupants != null) { var identity = stream_interactor.get_module(MucManager.IDENTITY);
foreach (Jid occupant in occupants) { Gee.List<Jid>? members = identity.get_all_members(conversation.counterpart, conversation.account);
add_occupant(occupant); 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(); list_box.invalidate_filter();
@ -53,34 +83,75 @@ public class List : Box {
list_box.invalidate_filter(); list_box.invalidate_filter();
} }
public void add_occupant(Jid jid) { 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;
}
if (member_name == null) {
return;
}
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 row_wrapper = new ListRow(stream_interactor, conversation, jid);
var widget = row_wrapper.get_widget(); var widget = row_wrapper.get_widget();
row_wrappers[widget] = row_wrapper; if (online) {
rows[jid] = widget; row_wrapper.set_online();
list_box.append(widget); } else {
row_wrapper.set_offline();
} }
public void remove_occupant(Jid jid) { row_wrappers[widget] = row_wrapper;
list_box.remove(rows[jid]); rows[member_name] = widget;
rows.unset(jid); list_box.append(widget);
}
} }
private void on_received_offline_presence(Jid jid, Account account) { private void on_received_offline_presence(Jid jid, Account account) {
if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
if (rows.has_key(jid)) { var member_name = jid.resourcepart;
remove_occupant(jid); 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(); 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 (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
if (!rows.has_key(jid)) { var member_name = jid.resourcepart;
add_occupant(jid); 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(); list_box.invalidate_filter();
} }
} }

View file

@ -11,6 +11,9 @@ public class ListRow : Object {
private AvatarPicture picture; private AvatarPicture picture;
public Label name_label; public Label name_label;
// TODO: use something more visual for status
public Label status;
public Conversation? conversation; public Conversation? conversation;
public Jid? jid; public Jid? jid;
@ -19,6 +22,8 @@ public class ListRow : Object {
main_grid = (Grid) builder.get_object("main_grid"); main_grid = (Grid) builder.get_object("main_grid");
picture = (AvatarPicture) builder.get_object("picture"); picture = (AvatarPicture) builder.get_object("picture");
name_label = (Label) builder.get_object("name_label"); 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) { public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
@ -37,6 +42,14 @@ public class ListRow : Object {
public Widget get_widget() { public Widget get_widget() {
return main_grid; return main_grid;
} }
public void set_online() {
status.label = ("");
}
public void set_offline() {
status.label = ("(offline)");
}
} }
} }

View file

@ -154,7 +154,7 @@ public class BadMessagesWidget : Box {
} else if (conversation.type_ == Conversation.Type.GROUPCHAT) { } else if (conversation.type_ == Conversation.Type.GROUPCHAT) {
who = jid.to_string(); who = jid.to_string();
// `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online) // `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; if (occupants == null) return;
foreach (Jid occupant in occupants) { foreach (Jid occupant in occupants) {
if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) { if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) {

View file

@ -53,7 +53,7 @@ private class EncryptionListEntry : Plugins.EncryptionListEntry, Object {
} }
} else if (conversation.type_ == Conversation.Type.GROUPCHAT) { } else if (conversation.type_ == Conversation.Type.GROUPCHAT) {
Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>(); Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>();
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account);
if (occupants != null) muc_jids.add_all(occupants); if (occupants != null) muc_jids.add_all(occupants);
Gee.List<Jid>? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); Gee.List<Jid>? 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); if (offline_members != null) muc_jids.add_all(offline_members);

View file

@ -36,7 +36,7 @@ public class Manager : StreamInteractionModule, Object {
keys.add(db.get_account_key(conversation.account)); keys.add(db.get_account_key(conversation.account));
if (conversation.type_ == Conversation.Type.GROUPCHAT) { if (conversation.type_ == Conversation.Type.GROUPCHAT) {
Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>(); Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>();
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account);
if (occupants != null) muc_jids.add_all(occupants); if (occupants != null) muc_jids.add_all(occupants);
Gee.List<Jid>? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); Gee.List<Jid>? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account);
if (occupants != null) muc_jids.add_all(offline_members); if (occupants != null) muc_jids.add_all(offline_members);