Keep compact transcript history loading
This commit is contained in:
1 parent
22f263ccce
commit
6226a1cb43
3 files changed
+105
-41
No files matched your search
@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.selection.rememberSelectionState
|
||||
@@ -156,6 +157,16 @@ private const val RESTORE_PAGE_MAX = 4000
|
||||
*/
|
||||
private const val RESTORE_PAGE_CUSHION = 400
|
||||
|
||||
/** Every input that can make the history boundary need another request. */
|
||||
private data class HistoryLoadSignal(
|
||||
val layout: LazyListLayoutInfo,
|
||||
val restoring: Boolean,
|
||||
val moreHistory: Boolean,
|
||||
val loading: Boolean,
|
||||
val error: String?,
|
||||
val oldestSeq: Long,
|
||||
)
|
||||
|
||||
/**
|
||||
* Which row was asked to hold its top edge, and how tall it was when it last measured.
|
||||
*
|
||||
@@ -369,6 +380,7 @@ fun SessionScreen(
|
||||
val running = sessionWorking(status)
|
||||
var moreHistory by remember { mutableStateOf(true) }
|
||||
var loadingHistory by remember { mutableStateOf(false) }
|
||||
var historyError by remember(address, epoch) { mutableStateOf<String?>(null) }
|
||||
var ready by remember { mutableStateOf(false) }
|
||||
// Replies parsed ahead of the rows that draw them; see [ParsedReplies].
|
||||
val replies = remember(address) { ParsedReplies() }
|
||||
@@ -429,6 +441,7 @@ fun SessionScreen(
|
||||
held = listOf()
|
||||
oldestSeq = 0L
|
||||
moreHistory = true
|
||||
historyError = null
|
||||
// A send the server could not accept is this phone's only copy. A reset replaces server
|
||||
// state, not that local outbox, so dropping it here would eat the message a second time.
|
||||
replaceQueued(queued.filter { it.local })
|
||||
@@ -659,6 +672,21 @@ fun SessionScreen(
|
||||
return true
|
||||
}
|
||||
|
||||
/** One guarded history request, shared by automatic paging and the explicit retry control. */
|
||||
suspend fun requestOlderPage(): Boolean {
|
||||
if (loadingHistory) return false
|
||||
loadingHistory = true
|
||||
historyError = null
|
||||
return try {
|
||||
loadOlderPage()
|
||||
} catch (e: ApiException) {
|
||||
historyError = e.message ?: "Unknown error"
|
||||
false
|
||||
} finally {
|
||||
loadingHistory = false
|
||||
}
|
||||
}
|
||||
|
||||
// A call opened on its own stays open when a second call in the same run turns it into a group.
|
||||
// Until this, watching a Bash call and having the session make another one shut the one being
|
||||
// read and folded it behind "Called 2 tools".
|
||||
@@ -844,14 +872,7 @@ fun SessionScreen(
|
||||
// scroll met it and waited a round trip. So the first full page goes right behind it, while
|
||||
// the screen is already up. A restore skips this: it has just paged as deep as it needed.
|
||||
if (savedAnchor == null && moreHistory && !loadingHistory) {
|
||||
loadingHistory = true
|
||||
try {
|
||||
loadOlderPage()
|
||||
} catch (_: ApiException) {
|
||||
// The next scroll asks again.
|
||||
} finally {
|
||||
loadingHistory = false
|
||||
}
|
||||
requestOlderPage()
|
||||
}
|
||||
// Last, and off this thread: this session is what must not be evicted, so it is marked as
|
||||
// visited before the budget is applied, and both are a walk of the cache directory.
|
||||
@@ -1041,18 +1062,38 @@ fun SessionScreen(
|
||||
// There is no correction beside this one. Following the newest message is not an effect: the
|
||||
// list is reversed, so an arriving message extends the end the viewport is pinned to.
|
||||
val unitSizes = remember(address) { HashMap<Any, Int>() }
|
||||
// A successful page advances `oldestSeq` even when its collapsed rows add too little height to
|
||||
// produce another layout after this collector returns. Restarting on that cursor makes the
|
||||
// promised re-check happen; a failed page leaves it unchanged and still waits for a scroll.
|
||||
LaunchedEffect(listState, moreHistory, oldestSeq) {
|
||||
snapshotFlow { listState.layoutInfo }
|
||||
.collect { info ->
|
||||
// Every state that can make another request useful is part of the collected value. In
|
||||
// particular, the opening prefetch used to advance `oldestSeq` while `loadingHistory` was still
|
||||
// true; the effect restarted, declined to overlap it, and never noticed when loading became
|
||||
// false. A tool expansion happened to cause the next layout and unstick it. Collecting the
|
||||
// loading transition itself makes a compact page immediately ask for its successor.
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow {
|
||||
HistoryLoadSignal(
|
||||
listState.layoutInfo,
|
||||
restoring,
|
||||
moreHistory,
|
||||
loadingHistory,
|
||||
historyError,
|
||||
oldestSeq,
|
||||
)
|
||||
}
|
||||
.collect { signal ->
|
||||
val info = signal.layout
|
||||
val visible = info.visibleItemsInfo
|
||||
if (visible.isEmpty()) return@collect
|
||||
// Before the guards below, so sizes keep accumulating while a page is in flight and
|
||||
// the next estimate starts better informed.
|
||||
visible.forEach { unitSizes[it.key] = it.size }
|
||||
if (restoring || !moreHistory || loadingHistory) return@collect
|
||||
if (
|
||||
signal.restoring ||
|
||||
!signal.moreHistory ||
|
||||
signal.loading ||
|
||||
signal.error != null ||
|
||||
signal.oldestSeq == 0L
|
||||
) {
|
||||
return@collect
|
||||
}
|
||||
val viewport = info.viewportSize.height
|
||||
if (viewport == 0) return@collect
|
||||
val loaded = currentUnits
|
||||
@@ -1066,16 +1107,11 @@ fun SessionScreen(
|
||||
room += unitSizes[loaded[index].key] ?: average
|
||||
if (room >= cushion) return@collect
|
||||
}
|
||||
loadingHistory = true
|
||||
try {
|
||||
// One page, and then this fires again if it was not enough -- the estimate is
|
||||
// re-made from what the page actually added.
|
||||
loadOlderPage()
|
||||
} catch (_: ApiException) {
|
||||
// Leave `moreHistory` alone: the next scroll asks again.
|
||||
} finally {
|
||||
loadingHistory = false
|
||||
}
|
||||
// One page, and then this fires again if it was not enough -- the estimate is
|
||||
// re-made when loading returns to false, even when folding the page did not change
|
||||
// the list's height at all. A failure leaves `moreHistory` alone but records an
|
||||
// error, stopping this loop until the boundary's Try again control is pressed.
|
||||
requestOlderPage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1476,6 +1512,11 @@ fun SessionScreen(
|
||||
units = units,
|
||||
state = listState,
|
||||
moreHistory = moreHistory,
|
||||
historyError = historyError,
|
||||
// A button means "make the request", not merely "let the estimator decide
|
||||
// again". Its measurements can be stale after the tall error row is
|
||||
// replaced by the spinner, and that used to make a press do nothing.
|
||||
onRetryHistory = { scope.launch { requestOlderPage() } },
|
||||
selection = selection,
|
||||
modifier =
|
||||
Modifier.fillMaxSize().drawWithContent { if (settled) drawContent() },
|
||||
|
||||
Reference in new issue
Block a user