Fix focusing before loading on recommended pages (#220)

Basically now will wait until a row is loaded before sending the focus
request
This commit is contained in:
damontecres 2025-11-14 11:40:38 -05:00 committed by GitHub
parent b7cb6efc4f
commit c5a5345f6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 83 additions and 39 deletions

View file

@ -12,7 +12,7 @@ import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
@ -26,6 +26,8 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -95,8 +97,8 @@ class RecommendedMovieViewModel
),
)
withContext(Dispatchers.Main) {
loading.value = LoadingState.Success
if (resumeItems.isNotEmpty()) {
loading.setValueOnMain(LoadingState.Success)
}
} catch (ex: Exception) {
Timber.e(ex, "Exception fetching movie recommendations")
@ -159,9 +161,9 @@ class RecommendedMovieViewModel
R.string.suggestions to suggestedItems,
R.string.top_unwatched to unwatchedTopRatedItems,
)
rows.forEachIndexed { index, (title, pager) ->
viewModelScope.launchIO {
rows
.mapIndexed { index, (title, pager) ->
viewModelScope.async(Dispatchers.IO + ExceptionHandler()) {
val title = context.getString(title)
val result =
try {
@ -175,8 +177,16 @@ class RecommendedMovieViewModel
HomeRowLoadingState.Error(title, null, ex)
}
update(index + 1, result)
if (result is HomeRowLoadingState.Success && result.items.isNotEmpty() &&
(loading.value == LoadingState.Loading || loading.value == LoadingState.Pending)
) {
loading.setValueOnMain(LoadingState.Success)
}
}
}.awaitAll()
if (loading.value == LoadingState.Loading || loading.value == LoadingState.Pending) {
loading.setValueOnMain(LoadingState.Success)
}
}
}

View file

@ -12,7 +12,7 @@ import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
@ -27,6 +27,8 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -123,8 +125,8 @@ class RecommendedTvShowViewModel
),
)
withContext(Dispatchers.Main) {
loading.value = LoadingState.Success
if (resumeItems.isNotEmpty() || nextUpItems.isNotEmpty()) {
loading.setValueOnMain(LoadingState.Success)
}
} catch (ex: Exception) {
Timber.e(ex, "Exception fetching tv recommendations")
@ -188,20 +190,32 @@ class RecommendedTvShowViewModel
R.string.top_unwatched to unwatchedTopRatedItems,
)
rows.forEachIndexed { index, (title, pager) ->
viewModelScope.launchIO {
rows
.mapIndexed { index, (title, pager) ->
viewModelScope.async(Dispatchers.IO + ExceptionHandler()) {
val title = context.getString(title)
val result =
try {
pager.init()
if (pager.isNotEmpty()) {
pager.getBlocking(0)
}
HomeRowLoadingState.Success(title, pager)
} catch (ex: Exception) {
Timber.e(ex, "Error fetching %s", title)
HomeRowLoadingState.Error(title, null, ex)
}
update(index + 2, result)
if (result is HomeRowLoadingState.Success && result.items.isNotEmpty() &&
(loading.value == LoadingState.Loading || loading.value == LoadingState.Pending)
) {
loading.setValueOnMain(LoadingState.Success)
}
}
}.awaitAll()
if (loading.value == LoadingState.Loading || loading.value == LoadingState.Pending) {
loading.setValueOnMain(LoadingState.Success)
}
}
}

View file

@ -197,8 +197,20 @@ fun HomePageContent(
loadingState: LoadingState? = null,
) {
val scope = rememberCoroutineScope()
val firstRow =
remember {
homeRows
.indexOfFirst {
when (it) {
is HomeRowLoadingState.Error -> false
is HomeRowLoadingState.Loading -> true
is HomeRowLoadingState.Pending -> true
is HomeRowLoadingState.Success -> it.items.isNotEmpty()
}
}.coerceAtLeast(0)
}
var position by rememberSaveable(stateSaver = RowColumnSaver) {
mutableStateOf(RowColumn(0, 0))
mutableStateOf(RowColumn(firstRow, 0))
}
var focusedItem =
position.let {
@ -208,11 +220,19 @@ fun HomePageContent(
val listState = rememberLazyListState()
val focusRequester = remember { FocusRequester() }
val positionFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
var focused by remember { mutableStateOf(false) }
LaunchedEffect(homeRows) {
if (!focused) {
homeRows
.indexOfFirst { it is HomeRowLoadingState.Success && it.items.isNotEmpty() }
.takeIf { it >= 0 }
?.let {
positionFocusRequester.tryRequestFocus()
// Hacky, but mostly works
delay(50)
listState.animateScrollToItem(position.row)
focused = true
}
}
}
LaunchedEffect(position) {
listState.animateScrollToItem(position.row)