fix(android): refresh the phone's own row when it reports

The fleet card reads last-seen from the claw, and only a landed report
moves it. Periodic work is deferred while the device idles — Doze
stretches the fifteen-minute floor to hours — so a phone sitting in the
human's hand could show itself as "last seen 3h ago" with nothing wrong
on the claw.

Kick a status run on resume, the same way the commands worker already
is, and reload the fleet whenever a run lands. The landed-run signal is
a new clock in StatusRepository, written in the same edit that saves the
snapshot (which only happens once every post of the run succeeded), so
the card reloads on a report that actually reached the claw.
This commit is contained in:
vhaudiquet committed 2026-10-20 07:40:43 +02:00
1 parent 4a91635730
commit 385788c935
4 files changed
+39 -5

No files matched your search

@@ -3,6 +3,7 @@ package rs.rhema.phone.data
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@@ -38,6 +39,7 @@ class StatusRepository(private val store: DataStore<Preferences>, private val js
private object Keys {
val snapshot = stringPreferencesKey("last_phone_snapshot")
val reportedAt = longPreferencesKey("last_phone_report_at")
val notificationCursor = stringPreferencesKey("last_notification_id")
}
@@ -46,10 +48,20 @@ class StatusRepository(private val store: DataStore<Preferences>, private val js
runCatching { json.decodeFromString<PhoneSnapshot>(raw) }.getOrNull()
}
// When the last run landed every post, epoch millis; 0 before the
// first one. The fleet card watches this so it reloads after a report
// lands: the last-seen it draws is the claw's clock, which only moves
// when a run has actually reached it.
val lastReportAt: Flow<Long> =
store.data.map { it[Keys.reportedAt] ?: 0L }
// Only called after every ingest post of a run succeeded, so a failed run
// retries the whole transition set next time instead of swallowing it.
suspend fun setSnapshot(snapshot: PhoneSnapshot) {
store.edit { it[Keys.snapshot] = json.encodeToString(snapshot) }
store.edit {
it[Keys.snapshot] = json.encodeToString(snapshot)
it[Keys.reportedAt] = System.currentTimeMillis()
}
}
// Cursor into the claw's notification list: the last id this device showed.