Fix always listing next up/continue watching rows first (#1288)

## Description
Previously the home page always tried to load the "watching rows" (Next
up, Continue watching, or combined) first. This meant that even if the
user's customized home page did not put these rows at the top, they
would always be listed first anyway.

This PR fixes this so rows on the home page are always listed in the
right order.

### Related issues
Fixes #1275

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-04-22 13:34:43 -04:00 committed by GitHub
parent c9cab7a5c2
commit 0e37084a16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
@ -87,26 +88,30 @@ class HomeViewModel
// Refreshing if a load has already occurred and the rows haven't significantly changed
val refresh =
state.loadingState == LoadingState.Success && state.settings == settings
Timber.v("refresh=$refresh, state.loadingState=${state.loadingState}")
Timber.v(
"refresh=%s, state.loadingState=%s, %s rows",
refresh,
state.loadingState,
settings.rows.size,
)
_state.update {
it.copy(
loadingState = if (refresh) LoadingState.Success else LoadingState.Loading,
refreshState = LoadingState.Loading,
settings = settings,
homeRows =
if (refresh) {
it.homeRows
} else {
List(settings.rows.size) { HomeRowLoadingState.Pending("") }
},
)
}
val semaphore = Semaphore(4)
val watchingRowIndexes =
settings.rows
.mapIndexedNotNull { index, row ->
if (isWatchingRow(row.config)) index else null
}
val deferred =
settings.rows
// Load the watching rows first
.sortedByDescending { isWatchingRow(it.config) }
.map { row ->
viewModelScope.async(Dispatchers.IO) {
semaphore.withPermit {
@ -123,32 +128,53 @@ class HomeViewModel
)
} catch (ex: Exception) {
Timber.e(ex, "Error on row %s", row)
HomeRowLoadingState.Error(row.title, exception = ex)
HomeRowLoadingState.Error(
row.title,
exception = ex,
)
}
}
}
}
if (refresh && state.homeRows.isNotEmpty() && watchingRowIndexes.isNotEmpty()) {
// Replace watching rows first
Timber.v("Refreshing rows: %s", watchingRowIndexes)
val rows =
deferred
.filterIndexed { index, _ -> index in watchingRowIndexes }
.awaitAll()
_state.update {
if (refresh) {
// Replace rows as they complete
val remaining = deferred.withIndex().toMutableList()
while (remaining.isNotEmpty()) {
val (rowIndex, rowData) =
select {
// "Return" the first remaining that is completed
remaining
.forEach { (rowIndex, deferred) ->
deferred.onAwait { rowIndex to it }
}
}
Timber.v("Got row data index=%s", rowIndex)
remaining.removeIf { it.index == rowIndex }
// Include only errors & non-empty successes
if (rowData is HomeRowLoadingState.Error ||
(rowData is HomeRowLoadingState.Success && rowData.items.isNotEmpty())
) {
_state.update { state ->
val newRows =
it.homeRows.toMutableList().apply {
rows.forEachIndexed { index, row ->
set(watchingRowIndexes[index], row)
state.homeRows.toMutableList().apply {
set(rowIndex, rowData)
}
}
it.copy(
loadingState = LoadingState.Success,
state.copy(
homeRows = newRows,
)
}
} else {
Timber.d("Skipping invalid row %s: %s", rowIndex, rowData)
}
}
_state.update {
it.copy(
loadingState = LoadingState.Success,
refreshState = LoadingState.Success,
)
}
} else {
val rows =
deferred
.awaitAll()
@ -166,6 +192,8 @@ class HomeViewModel
)
}
}
Timber.d("Home page load complete")
}
} catch (ex: Exception) {
Timber.e(ex, "Exception during home page loading")
if (state.value.loadingState == LoadingState.Success) {