From c5a5345f6ca6ff1db8b6ab1eaa3652325c894011 Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:40:38 -0500 Subject: [PATCH] Fix focusing before loading on recommended pages (#220) Basically now will wait until a row is loaded before sending the focus request --- .../ui/components/RecommendedMovie.kt | 46 +++++++++++-------- .../ui/components/RecommendedTvShow.kt | 44 ++++++++++++------ .../damontecres/wholphin/ui/main/HomePage.kt | 32 ++++++++++--- 3 files changed, 83 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt index 22cd6175..06159073 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt @@ -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,23 +161,31 @@ class RecommendedMovieViewModel R.string.suggestions to suggestedItems, R.string.top_unwatched to unwatchedTopRatedItems, ) - - rows.forEachIndexed { index, (title, pager) -> - viewModelScope.launchIO { - val title = context.getString(title) - val result = - try { - pager.init() - if (pager.isNotEmpty()) { - pager.getBlocking(0) + 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) } - HomeRowLoadingState.Success(title, pager) - } catch (ex: Exception) { - Timber.e(ex, "Error fetching %s", title) - 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) } - update(index + 1, result) - } + } + }.awaitAll() + if (loading.value == LoadingState.Loading || loading.value == LoadingState.Pending) { + loading.setValueOnMain(LoadingState.Success) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt index 7144d010..e1a18517 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt @@ -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,19 +190,31 @@ class RecommendedTvShowViewModel R.string.top_unwatched to unwatchedTopRatedItems, ) - rows.forEachIndexed { index, (title, pager) -> - viewModelScope.launchIO { - val title = context.getString(title) - val result = - try { - pager.init() - HomeRowLoadingState.Success(title, pager) - } catch (ex: Exception) { - Timber.e(ex, "Error fetching %s", title) - HomeRowLoadingState.Error(title, null, ex) + 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) } - update(index + 2, result) - } + } + }.awaitAll() + if (loading.value == LoadingState.Loading || loading.value == LoadingState.Pending) { + loading.setValueOnMain(LoadingState.Success) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 72b84490..24764384 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -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) { - positionFocusRequester.tryRequestFocus() - // Hacky, but mostly works - delay(50) - listState.animateScrollToItem(position.row) + 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() + delay(50) + listState.animateScrollToItem(position.row) + focused = true + } + } } LaunchedEffect(position) { listState.animateScrollToItem(position.row)