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);
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) {
Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account);
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);
}
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)) {
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) {
ret.add_all(full_jids);
// Remove eventual presence from bare jid
ret.remove(jid);
}
return ret;
}
return null;
}
public Gee.List<Jid>? get_other_occupants(Jid jid, Account account) {
Gee.List<Jid>? occupants = get_occupants(jid, account);
public Gee.List<Jid>? get_other_members(Jid jid, Account account) {
Gee.List<Jid>? 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) {

View file

@ -6,7 +6,7 @@
<property name="margin-start">7</property>
<property name="margin-bottom">3</property>
<property name="margin-end">7</property>
<property name="column-spacing">10</property>
<property name="column-spacing">5</property>
<child>
<object class="DinoUiAvatarPicture" id="picture">
<property name="height-request">30</property>
@ -25,5 +25,17 @@
</layout>
</object>
</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>
</interface>

View file

@ -107,7 +107,7 @@ public class OccupantsTabCompletor {
Gee.List<string> ret = generate_completions_from_messages(prefix);
// 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>();
if (occupants != null) {
foreach (Jid jid in occupants) {

View file

@ -17,7 +17,9 @@ public class List : Box {
private Conversation conversation;
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 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<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) {
this.conversation = conversation;
Gee.List<Jid>? 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<Jid>? 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();
}
}

View file

@ -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)");
}
}
}

View file

@ -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))) {

View file

@ -53,7 +53,7 @@ private class EncryptionListEntry : Plugins.EncryptionListEntry, Object {
}
} else if (conversation.type_ == Conversation.Type.GROUPCHAT) {
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);
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);

View file

@ -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<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);
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);