calls badge clears on view; outbox tests track the directory-first send

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-28 15:21:03 -04:00
co-authored by Claude Opus 4.8
parent 7727cfa432
commit 31548674de
3 changed files with 38 additions and 7 deletions
@@ -533,8 +533,10 @@ private fun BottomNav(
KitIcon.Chats, KitIcon.ChatsActive, "Messages", phase.unread.size, "nav-messages")
NavButton(model, phase, MessengerViewModel.Phase.Screen.CALLS,
KitIcon.Call, KitIcon.Call, "Calls",
// Missed calls are worth a badge; answered ones are not news.
phase.calls.count { !it.connected && it.incoming }, "nav-calls")
// Missed calls SINCE the tab was last opened — it used to count
// every missed call in the log for ever, a badge nothing could
// clear. Opening the tab is what "seeing" them means.
model.missedCallsUnseen(phase), "nav-calls")
NavButton(model, phase, MessengerViewModel.Phase.Screen.STORIES,
KitIcon.Stories, KitIcon.Stories, "Stories", 0, "nav-stories")
// Alerts left the bar 2026-08-28 — five tabs plus it was crowded, and
@@ -709,7 +709,11 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
fun closeChat() = update { it.copy(openChat = null) }
fun showScreen(screen: Phase.Screen) = update { it.copy(screen = screen, error = null) }
fun showScreen(screen: Phase.Screen) {
// Opening the Calls tab is what clears its missed badge.
if (screen == Phase.Screen.CALLS) markCallsSeen()
update { it.copy(screen = screen, error = null) }
}
// -- Contacts: the rest of the CRUD --------------------------------------
@@ -1109,6 +1113,19 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
/** Remembered across the privacy prompt, so "Understood, call" keeps it. */
private var pendingCallVideo = false
/** Missed incoming calls newer than the last time the Calls tab was open. */
fun missedCallsUnseen(phase: Phase.SignedIn): Int {
val seenAt = callPrefs().getLong(CALLS_SEEN_AT, 0L)
return phase.calls.count { !it.connected && it.incoming && it.at > seenAt }
}
/** The Calls tab was opened: everything currently missed counts as seen. */
fun markCallsSeen() {
callPrefs().edit().putLong(CALLS_SEEN_AT, System.currentTimeMillis()).apply()
// Nudge the state so the badge re-reads the pref.
update { it.copy() }
}
fun requestCall(peerId: Long, video: Boolean = false) {
pendingCallVideo = video
if (callPrefs().getBoolean(CALL_PRIVACY_ACKNOWLEDGED, false)) placeCall(peerId, video)
@@ -1656,6 +1673,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
}
private companion object {
const val CALLS_SEEN_AT = "calls_seen_at"
const val POLL_INTERVAL_MS = 5_000L
const val CALL_SETUP_POLL_MS = 1_000L
}