From 65954a99eb0aa318d9f88813d49768bd95cf37ee Mon Sep 17 00:00:00 2001 From: Damontecres Date: Mon, 28 Sep 2026 13:18:41 -0400 Subject: [PATCH] Fix unnecessary home page reloads (#2008) ## Description Fixes the home page reloading from scratch when the app is resumed This happened because the `userDto` is updated on app resume and since it has a last accessed timestamp, it would always different and trigger a reload. ### Related issues Bug introduced by #1981 ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None --- .../wholphin/data/ServerRepository.kt | 12 ++++---- .../wholphin/data/model/ServerUserConfig.kt | 28 +++++++++++++++++++ .../wholphin/services/HomeSettingsService.kt | 3 +- .../wholphin/services/NavDrawerService.kt | 3 +- .../wholphin/ui/main/HomeViewModel.kt | 14 +++++++--- .../services/MediaManagementServiceTest.kt | 12 ++------ .../wholphin/services/ServerRepositoryTest.kt | 10 ++++--- .../services/StreamChoiceServiceTest.kt | 7 ++--- .../ui/playback/PlaybackViewModelTests.kt | 8 ++---- 9 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/data/model/ServerUserConfig.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt index e6d9c468..a86335be 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt @@ -6,6 +6,7 @@ import androidx.core.content.edit import androidx.datastore.core.DataStore import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.services.hilt.IoDispatcher import com.github.damontecres.wholphin.ui.toServerString @@ -28,7 +29,6 @@ import org.jellyfin.sdk.api.client.extensions.quickConnectApi import org.jellyfin.sdk.api.client.extensions.systemApi import org.jellyfin.sdk.api.client.extensions.userApi import org.jellyfin.sdk.model.api.AuthenticationResult -import org.jellyfin.sdk.model.api.UserDto import org.jellyfin.sdk.model.serializer.toUUIDOrNull import timber.log.Timber import java.time.ZonedDateTime @@ -53,9 +53,9 @@ class ServerRepository private var _current = MutableStateFlow(null) val current: StateFlow = _current - private var _currentUserDto = MutableStateFlow(null) - val currentUserDto: UserDto? get() = _currentUserDto.value - val currentUserDtoFlow: StateFlow get() = _currentUserDto + private var _currentUserDto = MutableStateFlow(null) + val currentUserDto: ServerUserConfig? get() = _currentUserDto.value + val currentUserDtoFlow: StateFlow get() = _currentUserDto val currentServer: JellyfinServer? get() = _current.value?.server val currentServerFlow: Flow get() = _current.map { it?.server } @@ -126,7 +126,7 @@ class ServerRepository val currentUser = CurrentUser(updatedServer, updatedUser) withContext(WholphinDispatchers.Main) { _current.value = currentUser - _currentUserDto.value = userDto + _currentUserDto.value = ServerUserConfig(userDto) } getServerSharedPreferences(context).edit(true) { putString(SERVER_URL_KEY, updatedServer.url) @@ -319,7 +319,7 @@ class ServerRepository suspend fun updateUserDto() { val userDto by apiClient.userApi.getCurrentUser() _currentUserDto.update { - if (it?.id == userDto.id && currentUser?.id == userDto.id) userDto else it + if (it?.id == userDto.id && currentUser?.id == userDto.id) ServerUserConfig(userDto) else it } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/ServerUserConfig.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/ServerUserConfig.kt new file mode 100644 index 00000000..0eaaa848 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/ServerUserConfig.kt @@ -0,0 +1,28 @@ +package com.github.damontecres.wholphin.data.model + +import org.jellyfin.sdk.model.api.UserConfiguration +import org.jellyfin.sdk.model.api.UserDto +import org.jellyfin.sdk.model.api.UserPolicy +import org.jellyfin.sdk.model.serializer.toUUIDOrNull +import java.util.UUID + +/** + * Server side configuration for a user, similar to [UserDto] but without the timestamps allowing for more reliable equals + */ +data class ServerUserConfig( + val id: UUID, + val name: String? = null, + val serverId: UUID? = null, + val configuration: UserConfiguration? = null, + val policy: UserPolicy? = null, +) { + constructor(userDto: UserDto) : this( + id = userDto.id, + name = userDto.name, + serverId = userDto.serverId?.toUUIDOrNull(), + configuration = userDto.configuration, + policy = userDto.policy, + ) + + val tvAccess: Boolean get() = policy?.enableLiveTvAccess == true +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index 3371f45e..c070b37e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -9,6 +9,7 @@ import com.github.damontecres.wholphin.data.model.HomePageSettings import com.github.damontecres.wholphin.data.model.HomeRowConfig import com.github.damontecres.wholphin.data.model.HomeRowViewOptions import com.github.damontecres.wholphin.data.model.SUPPORTED_HOME_PAGE_SETTINGS_VERSION +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.data.model.createGenreDestination import com.github.damontecres.wholphin.data.model.createStudioDestination import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration @@ -591,7 +592,7 @@ class HomeSettingsService row: HomeRowConfig, scope: CoroutineScope, prefs: HomePagePreferences, - userDto: UserDto, + userDto: ServerUserConfig, libraries: List, limit: Int = prefs.maxItemsPerRow, isRefresh: Boolean, diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt index 6b8701ee..ea45cd75 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt @@ -5,6 +5,7 @@ import com.github.damontecres.wholphin.data.ServerPreferencesDao import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.NavPinType +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope import com.github.damontecres.wholphin.ui.collectLatestIn import com.github.damontecres.wholphin.ui.launchDefault @@ -188,7 +189,7 @@ class NavDrawerService */ suspend fun updateNavDrawer( user: JellyfinUser, - userDto: UserDto, + userDto: ServerUserConfig, discoverActive: Boolean, ) { val builtins = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt index 9e405196..847e2e69 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.HomeRowConfig +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.DatePlayedService @@ -19,7 +20,6 @@ import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.ServerReportService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.deleteItem -import com.github.damontecres.wholphin.services.tvAccess import com.github.damontecres.wholphin.ui.collectLatestIn import com.github.damontecres.wholphin.ui.combinePair import com.github.damontecres.wholphin.ui.data.RowColumn @@ -48,7 +48,6 @@ import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import org.jellyfin.sdk.api.client.exception.InvalidStatusException import org.jellyfin.sdk.model.api.BaseItemKind -import org.jellyfin.sdk.model.api.UserDto import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -85,20 +84,27 @@ class HomeViewModel userDto?.id, settings != HomePageResolvedSettings.EMPTY, ) - _state.update { HomeState.EMPTY } +// Timber.v("userDto=%s", userDto) dataLoadingJob?.cancel() if (userDto == null) { Timber.d("UserDto is null") + _state.update { HomeState.EMPTY } return@collectLatestIn } if (settings == HomePageResolvedSettings.EMPTY) { Timber.d("Home settings are empty") + _state.update { HomeState.EMPTY } return@collectLatestIn } if (userDto.id != settings.userId) { Timber.d("User IDs don't match: %s vs %s", userDto?.id, settings.userId) + _state.update { HomeState.EMPTY } return@collectLatestIn } + if (state.value.settings.userId != settings.userId) { + Timber.d("User changed") + _state.update { HomeState.EMPTY } + } dataLoadingJob = viewModelScope.launchIO { loadHomeRows(userDto, settings) @@ -133,7 +139,7 @@ class HomeViewModel } suspend fun loadHomeRows( - userDto: UserDto, + userDto: ServerUserConfig, settings: HomePageResolvedSettings, ) { Timber.i("Starting loadHomeRows") diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/MediaManagementServiceTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/MediaManagementServiceTest.kt index e88c2b81..e42600c4 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/MediaManagementServiceTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/MediaManagementServiceTest.kt @@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.services import android.content.Context import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.InterfacePreferences import com.github.damontecres.wholphin.ui.successResponse @@ -26,7 +27,6 @@ import org.jellyfin.sdk.api.operations.LibraryApi import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind -import org.jellyfin.sdk.model.api.UserDto import org.jellyfin.sdk.model.api.UserPolicy import org.junit.After import org.junit.Assert @@ -87,11 +87,8 @@ class MediaManagementServiceTest { ) private val userWithLiveTv = - UserDto( + ServerUserConfig( id = UUID.randomUUID(), - hasPassword = true, - hasConfiguredPassword = true, - hasConfiguredEasyPassword = false, policy = mockk { every { enableLiveTvManagement } returns true @@ -99,11 +96,8 @@ class MediaManagementServiceTest { ) private val userWithoutLiveTv = - UserDto( + ServerUserConfig( id = UUID.randomUUID(), - hasPassword = true, - hasConfiguredPassword = true, - hasConfiguredEasyPassword = false, policy = mockk { every { enableLiveTvManagement } returns false diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/ServerRepositoryTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/ServerRepositoryTest.kt index 06402b88..499208d6 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/ServerRepositoryTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/ServerRepositoryTest.kt @@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinServerUsers import com.github.damontecres.wholphin.data.model.JellyfinUser +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferencesSerializer import com.github.damontecres.wholphin.test.nonBlankString @@ -143,6 +144,7 @@ class ServerRepositoryTest { hasConfiguredPassword = true, hasConfiguredEasyPassword = false, ) + private val serverUserConfig = ServerUserConfig(userDto) private fun setUpCurrentUser( serverRepository: ServerRepository, @@ -190,7 +192,7 @@ class ServerRepositoryTest { Assert.assertEquals(server, serverRepository.currentServer) Assert.assertEquals(user, serverRepository.currentUser) - Assert.assertEquals(userDto, serverRepository.currentUserDto) + Assert.assertEquals(serverUserConfig, serverRepository.currentUserDto) val appPreferences = dataStore.data.first() Assert.assertEquals(serverId.toServerString(), appPreferences.currentServerId) @@ -221,7 +223,7 @@ class ServerRepositoryTest { } Assert.assertEquals(server, serverRepository.currentServer) Assert.assertEquals(user, serverRepository.currentUser) - Assert.assertEquals(userDto, serverRepository.currentUserDto) + Assert.assertEquals(serverUserConfig, serverRepository.currentUserDto) } @Test @@ -383,7 +385,7 @@ class ServerRepositoryTest { Assert.assertEquals(server, serverRepository.currentServer) Assert.assertEquals(user, serverRepository.currentUser) - Assert.assertEquals(userDto, serverRepository.currentUserDto) + Assert.assertEquals(serverUserConfig, serverRepository.currentUserDto) val appPreferences = dataStore.data.first() Assert.assertEquals(serverId.toServerString(), appPreferences.currentServerId) @@ -426,7 +428,7 @@ class ServerRepositoryTest { Assert.assertEquals(server, serverRepository.currentServer) Assert.assertEquals(user, serverRepository.currentUser) - Assert.assertEquals(userDto, serverRepository.currentUserDto) + Assert.assertEquals(serverUserConfig, serverRepository.currentUserDto) val appPreferences = dataStore.data.first() Assert.assertEquals(serverId.toServerString(), appPreferences.currentServerId) diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/StreamChoiceServiceTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/StreamChoiceServiceTest.kt index 37671f8d..b300e09a 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/StreamChoiceServiceTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/StreamChoiceServiceTest.kt @@ -5,6 +5,7 @@ import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.ItemPlayback import com.github.damontecres.wholphin.data.model.JellyfinUserPreferences import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.data.model.TrackIndex import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration @@ -17,7 +18,6 @@ import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.MediaStream import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.SubtitlePlaybackMode -import org.jellyfin.sdk.model.api.UserDto import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith @@ -902,11 +902,8 @@ private fun serverRepo( ): ServerRepository { val mocked = mockk() every { mocked.currentUserDto } returns - UserDto( + ServerUserConfig( id = UUID.randomUUID(), - hasPassword = true, - hasConfiguredPassword = true, - hasConfiguredEasyPassword = true, configuration = DefaultUserConfiguration.copy( audioLanguagePreference = audioLang, diff --git a/app/src/test/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModelTests.kt b/app/src/test/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModelTests.kt index cf7ee59f..21c6179b 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModelTests.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModelTests.kt @@ -11,6 +11,7 @@ import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.Playlist import com.github.damontecres.wholphin.data.model.PlaylistItem +import com.github.damontecres.wholphin.data.model.ServerUserConfig import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.ShowNextUpWhen @@ -68,7 +69,6 @@ import org.jellyfin.sdk.model.api.MediaSegmentDtoQueryResult import org.jellyfin.sdk.model.api.MediaSegmentType import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.PlaybackInfoResponse -import org.jellyfin.sdk.model.api.UserDto import org.jellyfin.sdk.model.extensions.inWholeTicks import org.jellyfin.sdk.model.extensions.ticks import org.junit.After @@ -152,13 +152,9 @@ class PlaybackViewModelTests { pin = "1234", ) private val userDto = - UserDto( + ServerUserConfig( id = userId, name = "test-user", - serverName = "test server", - hasPassword = true, - hasConfiguredPassword = true, - hasConfiguredEasyPassword = false, ) private val mediaSource =