From 3c11d4ba12ecef3706c4b9c45eaef72a6774b2f9 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:37:32 -0500 Subject: [PATCH] Fix some issues with restoring focus between pages (#613) ## Description Yet more focus issue fixes: - Restore focus when going back to home page from a later row - Don't jump to episode row when going back to series overview (such as from a person page) This PR also changes how backgrounding the app during playback works. Now the only goes back to the previous page when the app comes back to the foreground. This helps with focusing back on the page properly. There's also a bit more clean up performed. --- .../services/PlaybackLifecycleObserver.kt | 7 ++--- .../damontecres/wholphin/ui/Extensions.kt | 10 +++---- .../ui/detail/series/SeriesOverview.kt | 16 ++++++----- .../ui/detail/series/SeriesOverviewContent.kt | 2 -- .../damontecres/wholphin/ui/main/HomePage.kt | 4 ++- .../wholphin/ui/playback/PlaybackViewModel.kt | 27 ++++++++++--------- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt index 86906280..3c32a497 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt @@ -20,6 +20,10 @@ class PlaybackLifecycleObserver private var wasPlaying: Boolean? = null override fun onStart(owner: LifecycleOwner) { + val lastDest = navigationManager.backStack.lastOrNull() + if (lastDest is Destination.Playback || lastDest is Destination.PlaybackList) { + navigationManager.goBack() + } wasPlaying = null } @@ -40,9 +44,6 @@ class PlaybackLifecycleObserver } override fun onStop(owner: LifecycleOwner) { - if (navigationManager.backStack.lastOrNull() is Destination.Playback) { - navigationManager.goBack() - } themeSongPlayer.stop() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 4eba1b32..31754ef9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -28,9 +28,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.compose.LifecycleEventEffect import androidx.media3.common.Player import coil3.request.ErrorResult import com.github.damontecres.wholphin.data.model.BaseItem @@ -182,10 +180,10 @@ fun RequestOrRestoreFocus( debugKey?.let { Timber.v("RequestOrRestoreFocus: %s", it) } focusRequester.tryRequestFocus() } - LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { - debugKey?.let { Timber.v("RequestOrRestoreFocus onResume: %s", it) } - focusRequester.tryRequestFocus() - } +// LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { +// debugKey?.let { Timber.v("RequestOrRestoreFocus onResume: %s", it) } +// focusRequester.tryRequestFocus() +// } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index 9377aa1d..b5d4ea4b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.map import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -37,7 +38,6 @@ import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.seasonEpisode -import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.util.LoadingState import kotlinx.coroutines.flow.update import kotlinx.serialization.Serializable @@ -158,13 +158,15 @@ fun SeriesOverview( LoadingState.Success -> { series?.let { series -> - LaunchedEffect(Unit) { + RequestOrRestoreFocus( when (rowFocused) { - EPISODE_ROW -> episodeRowFocusRequester.tryRequestFocus() - CAST_AND_CREW_ROW -> castCrewRowFocusRequester.tryRequestFocus() - GUEST_STAR_ROW -> guestStarRowFocusRequester.tryRequestFocus() - } - } + EPISODE_ROW -> episodeRowFocusRequester + CAST_AND_CREW_ROW -> castCrewRowFocusRequester + GUEST_STAR_ROW -> guestStarRowFocusRequester + else -> episodeRowFocusRequester + }, + "series_overview", + ) LifecycleStartEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt index 75d8344b..1495285d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt @@ -49,7 +49,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.AspectRatios -import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.PersonRow import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -182,7 +181,6 @@ fun SeriesOverviewContent( is EpisodeList.Success -> { val state = rememberLazyListState(position.episodeRowIndex) - RequestOrRestoreFocus(firstItemFocusRequester) LazyRow( state = state, 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 31a4ecf0..6cc69dcd 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 @@ -234,7 +234,7 @@ fun HomePageContent( val listState = rememberLazyListState() val rowFocusRequesters = remember(homeRows.size) { List(homeRows.size) { FocusRequester() } } - var focused by remember { mutableStateOf(false) } + var focused by rememberSaveable { mutableStateOf(false) } LaunchedEffect(homeRows) { if (!focused) { homeRows @@ -246,6 +246,8 @@ fun HomePageContent( listState.animateScrollToItem(position.row) focused = true } + } else { + rowFocusRequesters.getOrNull(position.row)?.tryRequestFocus() } } LaunchedEffect(position) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 39cee993..064434c4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -157,6 +157,7 @@ class PlaybackViewModel internal lateinit var item: BaseItem internal var forceTranscoding: Boolean = false private var activityListener: TrackActivityPlaybackListener? = null + private val jobs = mutableListOf() val nextUp = MutableLiveData() private var isPlaylist = false @@ -166,20 +167,22 @@ class PlaybackViewModel val subtitleSearchLanguage = MutableLiveData(Locale.current.language) init { - viewModelScope.launch(ExceptionHandler()) { controllerViewState.observe() } - player.addListener(this) - (player as? ExoPlayer)?.addAnalyticsListener(this) - addCloseable { player.removeListener(this@PlaybackViewModel) } - addCloseable { (player as? ExoPlayer)?.removeAnalyticsListener(this@PlaybackViewModel) } addCloseable { + player.removeListener(this@PlaybackViewModel) + (player as? ExoPlayer)?.removeAnalyticsListener(this@PlaybackViewModel) + this@PlaybackViewModel.activityListener?.let { it.release() player.removeListener(it) } + jobs.forEach { it.cancel() } + player.release() } - addCloseable { player.release() } - subscribe() - listenForTranscodeReason() + viewModelScope.launch(ExceptionHandler()) { controllerViewState.observe() } + player.addListener(this) + (player as? ExoPlayer)?.addAnalyticsListener(this) + jobs.add(subscribe()) + jobs.add(listenForTranscodeReason()) } /** @@ -874,7 +877,7 @@ class PlaybackViewModel } } - private fun listenForTranscodeReason() { + private fun listenForTranscodeReason(): Job = viewModelScope.launchIO { currentPlayback.collectLatest { if (it != null) { @@ -905,7 +908,6 @@ class PlaybackViewModel } } } - } private var lastInteractionDate: Date = Date() @@ -1026,11 +1028,13 @@ class PlaybackViewModel } fun release() { + Timber.v("release") activityListener?.release() player.release() + activityListener = null } - fun subscribe() { + fun subscribe(): Job = api.webSocket .subscribe() .onEach { message -> @@ -1085,7 +1089,6 @@ class PlaybackViewModel } } }.launchIn(viewModelScope) - } /** * Atomically update [currentMediaInfo]