feat(chat): unread count + sidebar badge (Q1)
Track a per-conversation "last seen" watermark (message timestamp) on App: - chatUnreadCount() sums incoming messages newer than each conversation's watermark; surfaced as SidebarStatus.chatUnreadCount → a badge on the Chat nav item (mirrors the History/Peers badges). - Viewing a thread marks it seen (markChatConversationSeen while displayed). - Baseline on load: existing stored messages are marked seen, so only messages that arrive while the app is open badge as unread. - Wiped in resetChatSession so unread state never leaks across a wallet switch. In-memory only (resets on app restart); persistence is a later refinement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1227,6 +1227,26 @@ void App::processWalletSwitchRevert()
|
||||
daemon_restarting_ = false; // the connect loop now restarts the previous wallet's daemon
|
||||
}
|
||||
|
||||
int App::chatUnreadCount() const
|
||||
{
|
||||
if (!chat::hushChatFeatureEnabledAtBuild() || !chat_service_.hasIdentity()) return 0;
|
||||
int unread = 0;
|
||||
const auto& store = chat_service_.store();
|
||||
for (const auto& cid : store.conversationIds()) {
|
||||
std::int64_t seen = 0;
|
||||
const auto it = chat_seen_watermark_.find(cid);
|
||||
if (it != chat_seen_watermark_.end()) seen = it->second;
|
||||
for (const auto& m : store.conversation(cid))
|
||||
if (m.direction == chat::ChatDirection::Incoming && m.timestamp > seen) ++unread;
|
||||
}
|
||||
return unread;
|
||||
}
|
||||
|
||||
void App::markChatConversationSeen(const std::string& cid, std::int64_t latestTs)
|
||||
{
|
||||
if (latestTs > 0) chat_seen_watermark_[cid] = latestTs;
|
||||
}
|
||||
|
||||
void App::wipePendingTransactionHistoryCachePassphrase()
|
||||
{
|
||||
if (!pending_transaction_history_cache_passphrase_.empty()) {
|
||||
@@ -2658,6 +2678,13 @@ void App::provisionChatIdentityFromSecret(std::string secret)
|
||||
chat_service_.setPersistence(&chat_db_);
|
||||
if (chat_db_.unlockWithSecret(trimmed)) {
|
||||
chat_service_.loadFromDatabase();
|
||||
// Baseline unread: treat everything already in the store at load as read, so only messages
|
||||
// that arrive while the app is open badge as unread (Q1).
|
||||
const auto& store = chat_service_.store();
|
||||
for (const auto& cid : store.conversationIds()) {
|
||||
const auto msgs = store.conversation(cid);
|
||||
if (!msgs.empty()) chat_seen_watermark_[cid] = msgs.back().timestamp;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chat_identity_unavailable_ = true;
|
||||
@@ -2677,6 +2704,7 @@ void App::resetChatSession()
|
||||
// Drop identity + decrypted plaintext in RAM, lock the seed-encrypted DB, re-arm provisioning.
|
||||
chat_service_.clearIdentity();
|
||||
chat_service_.store().clear();
|
||||
chat_seen_watermark_.clear(); // unread state is per-wallet — don't leak it across a switch
|
||||
ui::ResetChatTab(); // wipe the chat UI's typed plaintext + selection so it can't leak into the next wallet
|
||||
chat_db_.lock();
|
||||
chat_identity_provisioned_ = false;
|
||||
|
||||
Reference in New Issue
Block a user