feat(chat): mute conversations (Q10)

Per-conversation mute toggle in the thread header. Muted conversations (tracked
by cid in settings, so it persists) are skipped by chatUnreadCount(), so they
neither raise the nav-item unread badge nor the new-message toast — the toast now
gates on a chatUnreadCount() delta across ingest, which already skips muted cids,
so mute is respected for free. "Block" (rejecting a peer's inbound memos) is a
larger ingest-filter change and is intentionally left out of this pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:48:57 -05:00
parent fecc9015fb
commit 24c661f743
4 changed files with 40 additions and 4 deletions

View File

@@ -1233,6 +1233,7 @@ int App::chatUnreadCount() const
int unread = 0;
const auto& store = chat_service_.store();
for (const auto& cid : store.conversationIds()) {
if (settings_ && settings_->isChatMuted(cid)) continue; // muted conversations don't badge (Q10)
std::int64_t seen = 0;
const auto it = chat_seen_watermark_.find(cid);
if (it != chat_seen_watermark_.end()) seen = it->second;
@@ -1688,9 +1689,12 @@ void App::refreshTransactionData()
!result.hushChatMetadata.empty()) {
std::unordered_map<std::string, std::int64_t> chatTxTimes;
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
const int unreadBefore = chatUnreadCount();
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat)
ui::Notifications::instance().info(TR("chat_new_message_toast")); // Q4: alert when not on the Chat tab
// Toast only when a non-muted conversation gained an unread message — chatUnreadCount()
// already skips muted cids, so a delta cleanly respects mute (Q10 + Q4).
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat && chatUnreadCount() > unreadBefore)
ui::Notifications::instance().info(TR("chat_new_message_toast"));
}
NetworkRefreshService::applyTransactionRefreshResult(
state_, cacheUpdate, std::move(result), std::time(nullptr));
@@ -1748,9 +1752,12 @@ void App::refreshRecentTransactionData()
!result.hushChatMetadata.empty()) {
std::unordered_map<std::string, std::int64_t> chatTxTimes;
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
const int unreadBefore = chatUnreadCount();
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat)
ui::Notifications::instance().info(TR("chat_new_message_toast")); // Q4: alert when not on the Chat tab
// Toast only when a non-muted conversation gained an unread message — chatUnreadCount()
// already skips muted cids, so a delta cleanly respects mute (Q10 + Q4).
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat && chatUnreadCount() > unreadBefore)
ui::Notifications::instance().info(TR("chat_new_message_toast"));
}
NetworkRefreshService::applyTransactionRefreshResult(
state_, cacheUpdate, std::move(result), std::time(nullptr));