From a17a85a383843085cac54c58fec4be118903a19d Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 7 Sep 2026 01:40:53 +0300 Subject: [PATCH] =?UTF-8?q?chat:=20deleted-=D0=B0=D0=BA=D0=BA=D0=B0=D1=83?= =?UTF-8?q?=D0=BD=D1=82=D1=8B=20=E2=80=94=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD?= =?UTF-8?q?=D0=BA=D0=B0=20deleted,=20=D1=81=D0=BA=D1=80=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D0=B5/=D0=B7=D0=B0=D1=87=D1=91=D1=80=D0=BA=D0=B8=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5,=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B0=D0=B5=D0=BC=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_member.c | 6 +- src/chat/chat_member.h | 1 + src/routing_layer/topo_node_sqlite.c | 74 ++++++++++++------- .../utun/chat/ui/screens/MemberListScreen.kt | 2 + .../com/utun/chat/viewmodel/ChatViewModel.kt | 2 +- tools/chatgui/src/accountdelegate.cpp | 11 ++- tools/chatgui/src/accountlist.cpp | 14 ++-- tools/chatgui/src/flagpainter.h | 1 + tools/chatgui/src/memberlistmodel.cpp | 23 +++++- tools/chatgui/src/memberlistmodel.h | 3 +- 10 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/chat/chat_member.c b/src/chat/chat_member.c index 1d7c5300..781cb845 100644 --- a/src/chat/chat_member.c +++ b/src/chat/chat_member.c @@ -108,7 +108,7 @@ int chat_core_get_member_list(const char* ch_id, uint8_t** out, int* count) { snprintf(sql, sizeof(sql), "SELECT p.node_id, COALESCE(n.name,'')," " COALESCE(p.local_nick,''), COALESCE(p.adm_tags,'')," - " COALESCE(p.storage,0)" + " COALESCE(p.storage,0), COALESCE(p.deleted,0)" " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id" " ORDER BY p.node_id ASC", peers_tbl); @@ -137,6 +137,7 @@ int chat_core_get_member_list(const char* ch_id, uint8_t** out, int* count) { m->storage = (uint8_t)sqlite3_column_int(st, 4); m->is_self = (uint8_t)(m->node_id == my_id ? 1 : 0); m->flags = parse_adm_tags_flags(adm_tags); + if (sqlite3_column_int(st, 5)) m->flags |= CHAT_MEMBER_FLAG_DELETED; m->rtt = 0xFFFF; m->nat_type = 0xFF; @@ -228,7 +229,7 @@ int chat_core_get_single_member(const char* ch_id, uint64_t node_id, uint8_t* ou snprintf(sql, sizeof(sql), "SELECT COALESCE(n.name,'')," " COALESCE(p.local_nick,''), COALESCE(p.adm_tags,'')," - " COALESCE(p.storage,0)" + " COALESCE(p.storage,0), COALESCE(p.deleted,0)" " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id" " WHERE p.node_id=?", peers_tbl); @@ -245,6 +246,7 @@ int chat_core_get_single_member(const char* ch_id, uint64_t node_id, uint8_t* ou m.storage = (uint8_t)sqlite3_column_int(st, 3); m.is_self = (uint8_t)(node_id == my_id ? 1 : 0); m.flags = parse_adm_tags_flags(adm_tags); + if (sqlite3_column_int(st, 4)) m.flags |= CHAT_MEMBER_FLAG_DELETED; m.rtt = 0xFFFF; m.nat_type = 0xFF; diff --git a/src/chat/chat_member.h b/src/chat/chat_member.h index 945db58e..60d8c59d 100644 --- a/src/chat/chat_member.h +++ b/src/chat/chat_member.h @@ -29,6 +29,7 @@ extern "C" { #define CHAT_MEMBER_FLAG_SUPERNODE 0x01 #define CHAT_MEMBER_FLAG_ADMIN 0x02 #define CHAT_MEMBER_FLAG_MODER 0x04 +#define CHAT_MEMBER_FLAG_DELETED 0x08 #define CHAT_MEMBER_DISPLAY_SIZE 79 diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 923e3afc..3875c264 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -32,6 +32,12 @@ static void peers_table_name(const char* ch_id, char* buf, size_t sz) { snprintf(buf, sz, "peers_%s", san); } +static int adm_tags_deleted(const char* tags) { + if (!tags || !tags[0]) return 0; + char buf[16]; + return json_flat_get(tags, "deleted", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0; +} + /* ── Классификация адресов (локально, по IP) ── */ static int addr_type_from_ip(int family, const uint8_t* addr) { @@ -292,6 +298,9 @@ int topo_node_sqlite_init(sqlite3* db) { snprintf(isql, sizeof(isql), "ALTER TABLE \"%s\" ADD COLUMN source INTEGER NOT NULL DEFAULT 0", tbl); sqlite3_exec(db, isql, NULL, NULL, NULL); + snprintf(isql, sizeof(isql), + "ALTER TABLE \"%s\" ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0", tbl); + sqlite3_exec(db, isql, NULL, NULL, NULL); } sqlite3_finalize(s); } @@ -338,6 +347,7 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, " node_RTT INTEGER," /* RTT до узла в мс, часть индекса (node_type, node_RTT) */ " connected INTEGER NOT NULL DEFAULT 0," /* 1 = было активное подключение к узлу */ " storage INTEGER NOT NULL DEFAULT 0," /* 1 = приоритетный хранитель аттачей */ + " deleted INTEGER NOT NULL DEFAULT 0," /* 1 = аккаунт удалён владельцем (не отображаем, не подключаемся) */ " local_nick TEXT," /* локальный ник (GUI only, не синхронизируется) */ " source INTEGER NOT NULL DEFAULT 0" /* 0=полная merkle-запись 1=упрощённый плейсхолдер topo_group (не участвует в merkle-хеше, дополняется полной записью) */ @@ -424,7 +434,7 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no if (adm_tags || adm_tags_sig || storage > 0) { snprintf(sql, sizeof(sql), - "UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=? WHERE node_id=?", peers_tbl); + "UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=?, deleted=? WHERE node_id=?", peers_tbl); sqlite3_stmt* u = NULL; if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) { if (adm_tags && adm_tags[0]) sqlite3_bind_text(u, 1, adm_tags, -1, SQLITE_STATIC); @@ -432,7 +442,8 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no if (adm_tags_sig) sqlite3_bind_blob(u, 2, adm_tags_sig, 64, SQLITE_STATIC); else sqlite3_bind_null(u, 2); sqlite3_bind_int(u, 3, storage); - sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id); + sqlite3_bind_int(u, 4, adm_tags_deleted(adm_tags)); + sqlite3_bind_int64(u, 5, (sqlite3_int64)node_id); sqlite3_step(u); sqlite3_finalize(u); } } @@ -512,7 +523,7 @@ int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint6 char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); char sql[256]; snprintf(sql, sizeof(sql), - "UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=? WHERE node_id=?", peers_tbl); + "UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=?, deleted=? WHERE node_id=?", peers_tbl); sqlite3_stmt* u = NULL; if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) != SQLITE_OK) return -1; if (adm_tags && adm_tags[0]) sqlite3_bind_text(u, 1, adm_tags, -1, SQLITE_STATIC); @@ -520,7 +531,8 @@ int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint6 if (adm_tags_sig) sqlite3_bind_blob(u, 2, adm_tags_sig, 64, SQLITE_STATIC); else sqlite3_bind_null(u, 2); sqlite3_bind_int(u, 3, storage); - sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id); + sqlite3_bind_int(u, 4, adm_tags_deleted(adm_tags)); + sqlite3_bind_int64(u, 5, (sqlite3_int64)node_id); sqlite3_step(u); sqlite3_finalize(u); return 0; } @@ -785,32 +797,34 @@ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) { while (sqlite3_step(s) == SQLITE_ROW) { const char* tbl = (const char*)sqlite3_column_text(s, 0); - int node_type = base_type; int is_storage = 0; - if (best_at == ADDR_TYPE_DIRECT && base_type == 1) { - snprintf(sql, sizeof(sql), - "SELECT adm_tags FROM \"%s\" WHERE node_id=?", tbl); - sqlite3_stmt* a = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &a, NULL) == SQLITE_OK) { - sqlite3_bind_int64(a, 1, (sqlite3_int64)node_id); - if (sqlite3_step(a) == SQLITE_ROW) { - const char* tags = (const char*)sqlite3_column_text(a, 0); - if (tags) { - char buf[64]; + int node_type = base_type; int is_storage = 0; int is_deleted = 0; + snprintf(sql, sizeof(sql), + "SELECT adm_tags FROM \"%s\" WHERE node_id=?", tbl); + sqlite3_stmt* a = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &a, NULL) == SQLITE_OK) { + sqlite3_bind_int64(a, 1, (sqlite3_int64)node_id); + if (sqlite3_step(a) == SQLITE_ROW) { + const char* tags = (const char*)sqlite3_column_text(a, 0); + if (tags) { + char buf[64]; + if (best_at == ADDR_TYPE_DIRECT && base_type == 1) { if (json_flat_get(tags, "supernode", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) node_type = 4; if (json_flat_get(tags, "storage", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) is_storage = 1; } + is_deleted = adm_tags_deleted(tags); } - sqlite3_finalize(a); } + sqlite3_finalize(a); } snprintf(sql, sizeof(sql), - "UPDATE \"%s\" SET node_type=?, storage=? WHERE node_id=?", tbl); + "UPDATE \"%s\" SET node_type=?, storage=?, deleted=? WHERE node_id=?", tbl); sqlite3_stmt* u = NULL; if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) { sqlite3_bind_int(u, 1, node_type); sqlite3_bind_int(u, 2, is_storage); - sqlite3_bind_int64(u, 3, (sqlite3_int64)node_id); + sqlite3_bind_int(u, 3, is_deleted); + sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id); sqlite3_step(u); sqlite3_finalize(u); } } @@ -1013,7 +1027,7 @@ int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, if (!db || !channel_id || !out_ids || !out_count) return -1; *out_ids = NULL; *out_count = 0; char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); - char sql[128]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1", peers_tbl); + char sql[192]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", peers_tbl); sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1; int cnt = 0; @@ -1030,13 +1044,15 @@ int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id) { - if (!db || !out_ids || !out_count) return -1; + if (!db || !channel_id || !out_ids || !out_count) return -1; *out_ids = NULL; *out_count = 0; - (void)channel_id; - const char* sql = + char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); + char sql[384]; snprintf(sql, sizeof(sql), "SELECT DISTINCT na.node_id FROM node_addresses na" " WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (1,2)" - " AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"; + " AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)" + " AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", + peers_tbl); sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id); @@ -1058,7 +1074,7 @@ int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, *out_ids = NULL; *out_count = 0; char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); char sql[256]; snprintf(sql, sizeof(sql), - "SELECT p.node_id FROM \"%s\" p WHERE p.node_type=4" + "SELECT p.node_id FROM \"%s\" p WHERE p.node_type=4 AND p.deleted=0" " AND EXISTS (SELECT 1 FROM node_addresses na WHERE na.node_id=p.node_id AND na.family IN (4,6))", peers_tbl); sqlite3_stmt* st = NULL; @@ -1077,13 +1093,15 @@ int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id) { - if (!db || !out_ids || !out_count) return -1; + if (!db || !channel_id || !out_ids || !out_count) return -1; *out_ids = NULL; *out_count = 0; - (void)channel_id; - const char* sql = + char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); + char sql[384]; snprintf(sql, sizeof(sql), "SELECT DISTINCT na.node_id FROM node_addresses na" " WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (0,3)" - " AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"; + " AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)" + " AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", + peers_tbl); sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id); diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt index 7edbf23f..69b48bd6 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -176,6 +177,7 @@ private fun MemberListItem( fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, + textDecoration = if (member.isDeleted) TextDecoration.LineThrough else TextDecoration.None, color = if (member.isSelf) Color(0xFF4CAF50) else Color.Unspecified ) val parts = mutableListOf() diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 5a9c18f7..73b44fd2 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -382,7 +382,7 @@ class ChatViewModel : ViewModel() { viewModelScope.launch { val (raw, owner) = withContext(Dispatchers.IO) { r.getMembers(chId) to r.isChannelOwner(chId) } _channelOwner.value = owner - _members.value = raw.map { m -> + _members.value = raw.filter { owner || !it.isDeleted }.map { m -> val ns = statuses[m.nodeId] val base = if (ns != null) m.copy( online = ns.online, rtt = ns.bestRtt, diff --git a/tools/chatgui/src/accountdelegate.cpp b/tools/chatgui/src/accountdelegate.cpp index eebed280..e95fce41 100644 --- a/tools/chatgui/src/accountdelegate.cpp +++ b/tools/chatgui/src/accountdelegate.cpp @@ -29,9 +29,12 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio const int w = option.rect.right() - x - margin; const int yMid = option.rect.top() + option.rect.height() / 2; + uint8_t flags = (uint8_t)(index.data(Qt::UserRole + 3).toInt() & 0xFF); + QFont nameFont = painter->font(); nameFont.setBold(true); nameFont.setPixelSize(13); + if (flags & MEMBER_FLAG_DELETED) nameFont.setStrikeOut(true); painter->setFont(nameFont); QColor textColor = option.palette.color(QPalette::Text); @@ -39,12 +42,12 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio if (index.data(Qt::UserRole + 2).toBool()) textColor = QColor("#4CAF50"); painter->setPen(textColor); - uint8_t flags = (uint8_t)(index.data(Qt::UserRole + 3).toInt() & 0xFF); + const uint8_t visualFlags = flags & (MEMBER_FLAG_SUPERNODE | MEMBER_FLAG_ADMIN | MEMBER_FLAG_MODER); int flagX = x; - if (flags) { + if (visualFlags) { FlagPainter::draw(painter, flags, flagX, option.rect.top() + (option.rect.height() - nameFont.pixelSize()) / 2, nameFont.pixelSize()); - flagX += nameFont.pixelSize() * ((flags & 0x01 ? 1 : 0) + (flags & 0x02 ? 1 : 0) + (flags & 0x04 ? 1 : 0)) - + ((__builtin_popcount(flags) - 1) * 2); + int n = __builtin_popcount(visualFlags); + flagX += nameFont.pixelSize() * n + (n - 1) * 2; } QString name = index.data(Qt::DisplayRole).toString(); diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index 1767b10a..28cd4313 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -149,12 +149,6 @@ AccountList::~AccountList() { void AccountList::setChannel(const QString& channelId) { m_channelId = channelId; - m_model->setChannel(channelId); - m_detailTimer->stop(); - m_linkTimer->stop(); - m_detailNodeId = 0; - m_detailHeader->setText("Select a member"); - m_detailText->clear(); bool isOwner = false; if (m_db && m_db->isOpen()) { @@ -163,6 +157,14 @@ void AccountList::setChannel(const QString& channelId) { if (ch.channelId == channelId) { isOwner = (ch.ed25519_privkey.size() == 32); break; } } } + + m_model->setChannel(channelId, isOwner); + m_detailTimer->stop(); + m_linkTimer->stop(); + m_detailNodeId = 0; + m_detailHeader->setText("Select a member"); + m_detailText->clear(); + m_header->setText(isOwner ? QString::fromUtf8("Members \xF0\x9F\x94\x91") : QStringLiteral("Members")); m_header->setToolTip(isOwner ? QStringLiteral("Channel owner (has private key)") : QString()); } diff --git a/tools/chatgui/src/flagpainter.h b/tools/chatgui/src/flagpainter.h index 6ae05b57..e52ea2f5 100644 --- a/tools/chatgui/src/flagpainter.h +++ b/tools/chatgui/src/flagpainter.h @@ -8,6 +8,7 @@ #define MEMBER_FLAG_SUPERNODE 0x01 #define MEMBER_FLAG_ADMIN 0x02 #define MEMBER_FLAG_MODER 0x04 +#define MEMBER_FLAG_DELETED 0x08 class FlagPainter { public: diff --git a/tools/chatgui/src/memberlistmodel.cpp b/tools/chatgui/src/memberlistmodel.cpp index 2ed9cf2c..4c68d011 100644 --- a/tools/chatgui/src/memberlistmodel.cpp +++ b/tools/chatgui/src/memberlistmodel.cpp @@ -56,9 +56,10 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const { return {}; } -void MemberListModel::setChannel(const QString& channelId) { +void MemberListModel::setChannel(const QString& channelId, bool isOwner) { beginResetModel(); m_channelId = channelId; + m_isOwner = isOwner; m_totalCount = 0; m_cache.clear(); endResetModel(); @@ -70,7 +71,7 @@ void MemberListModel::setChannel(const QString& channelId) { } void MemberListModel::refresh() { - if (!m_channelId.isEmpty()) setChannel(m_channelId); + if (!m_channelId.isEmpty()) setChannel(m_channelId, m_isOwner); } void MemberListModel::refreshRtt() { @@ -119,14 +120,17 @@ void MemberListModel::onMemberList(const QString& chId, int count, const uint8_t if (chId != m_channelId) return; beginResetModel(); - m_totalCount = count; + m_totalCount = 0; m_cache.clear(); + int row = 0; for (int i = 0; i < count; i++) { MemberListItem item; deserialize_member(data + i * CHAT_MEMBER_DISPLAY_SIZE, item); - m_cache[i] = item; + if ((item.flags & CHAT_MEMBER_FLAG_DELETED) && !m_isOwner) continue; + m_cache[row++] = item; } + m_totalCount = row; endResetModel(); } @@ -142,6 +146,17 @@ void MemberListModel::onMemberUpdated(const QString& chId, const uint8_t* data) if (it->nodeId == item.nodeId) { row = it.key(); break; } } + if ((item.flags & CHAT_MEMBER_FLAG_DELETED) && !m_isOwner) { + /* deleted member — remove from non-owner's list */ + if (row >= 0) { + beginRemoveRows(QModelIndex(), row, row); + m_cache.remove(row); + m_totalCount--; + endRemoveRows(); + } + return; + } + if (row >= 0) { m_cache[row] = item; emit dataChanged(index(row), index(row)); diff --git a/tools/chatgui/src/memberlistmodel.h b/tools/chatgui/src/memberlistmodel.h index 6682e211..501b4aaa 100644 --- a/tools/chatgui/src/memberlistmodel.h +++ b/tools/chatgui/src/memberlistmodel.h @@ -29,7 +29,7 @@ public: int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - void setChannel(const QString& channelId); + void setChannel(const QString& channelId, bool isOwner); void refresh(); void refreshRtt(); quint64 nodeIdAt(int row) const; @@ -44,6 +44,7 @@ private: QPixmap makeAvatar(QColor color, QChar letter) const; QString m_channelId; + bool m_isOwner = false; int m_totalCount = 0; mutable QMap m_cache; QTimer* m_rttTimer;