Better restoration for auto sign in (#566)

## Description
Improves how the app restores state when auto sign in is enabled. If
auto sign in is enabled, the app will restore back to the last page.

#538 altered the app start up logic. This caused the app to reset state
more often when it goes into the background.

### Related issues
Related to #552
This commit is contained in:
Ray 2025-12-24 16:44:41 -05:00 committed by GitHub
parent bcc7e660ea
commit b671e14826
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 61 additions and 23 deletions

View file

@ -32,7 +32,6 @@ import androidx.tv.material3.Surface
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.AppUpgradeHandler import com.github.damontecres.wholphin.services.AppUpgradeHandler
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
@ -102,7 +101,7 @@ class MainActivity : AppCompatActivity() {
@OptIn(ExperimentalTvMaterial3Api::class) @OptIn(ExperimentalTvMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Timber.i("MainActivity.onCreate") Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
lifecycle.addObserver(playbackLifecycleObserver) lifecycle.addObserver(playbackLifecycleObserver)
if (savedInstanceState == null) { if (savedInstanceState == null) {
appUpgradeHandler.copySubfont(false) appUpgradeHandler.copySubfont(false)
@ -209,12 +208,8 @@ class MainActivity : AppCompatActivity() {
appPreferences, appPreferences,
) )
val preferences = val preferences =
remember(appPreferences, current) { remember(appPreferences) {
UserPreferences( UserPreferences(appPreferences)
appPreferences,
current.userDto.configuration
?: DefaultUserConfiguration,
)
} }
ApplicationContent( ApplicationContent(
user = current.user, user = current.user,
@ -238,6 +233,7 @@ class MainActivity : AppCompatActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Timber.i("onResume")
lifecycleScope.launchIO { lifecycleScope.launchIO {
appUpgradeHandler.run() appUpgradeHandler.run()
} }
@ -256,6 +252,36 @@ class MainActivity : AppCompatActivity() {
// serverRepository.closeSession() // serverRepository.closeSession()
// } // }
} }
// override fun onStop() {
// super.onStop()
// Timber.i("onStop")
// }
//
// override fun onPause() {
// super.onPause()
// Timber.i("onPause")
// }
//
// override fun onStart() {
// super.onStart()
// Timber.i("onStart")
// }
//
// override fun onSaveInstanceState(outState: Bundle) {
// super.onSaveInstanceState(outState)
// Timber.i("onSaveInstanceState")
// }
//
// override fun onRestoreInstanceState(savedInstanceState: Bundle) {
// super.onRestoreInstanceState(savedInstanceState)
// Timber.i("onRestoreInstanceState")
// }
//
// override fun onDestroy() {
// super.onDestroy()
// Timber.i("onDestroy")
// }
} }
@HiltViewModel @HiltViewModel

View file

@ -46,9 +46,11 @@ class ServerRepository
private var _current = EqualityMutableLiveData<CurrentUser?>(null) private var _current = EqualityMutableLiveData<CurrentUser?>(null)
val current: LiveData<CurrentUser?> = _current val current: LiveData<CurrentUser?> = _current
private var _currentUserDto = EqualityMutableLiveData<UserDto?>(null)
val currentUserDto: LiveData<UserDto?> = _currentUserDto
val currentServer: LiveData<JellyfinServer?> get() = _current.map { it?.server } val currentServer: LiveData<JellyfinServer?> get() = _current.map { it?.server }
val currentUser: LiveData<JellyfinUser?> get() = _current.map { it?.user } val currentUser: LiveData<JellyfinUser?> get() = _current.map { it?.user }
val currentUserDto: LiveData<UserDto?> get() = _current.map { it?.userDto }
/** /**
* Adds a server to the app database and updated the [ApiClient] to the server's URL * Adds a server to the app database and updated the [ApiClient] to the server's URL
@ -101,7 +103,8 @@ class ServerRepository
}.build() }.build()
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_current.value = CurrentUser(updatedServer, updatedUser, userDto) _current.value = CurrentUser(updatedServer, updatedUser)
_currentUserDto.value = userDto
} }
sharedPreferences.edit(true) { sharedPreferences.edit(true) {
putString(SERVER_URL_KEY, updatedServer.url) putString(SERVER_URL_KEY, updatedServer.url)
@ -128,6 +131,15 @@ class ServerRepository
serverDao.getServer(serverId) serverDao.getServer(serverId)
} }
if (serverAndUsers != null) { if (serverAndUsers != null) {
val current = _current.value
if (current != null && current.server.id == serverId && current.user.id == userId) {
Timber.v("Restoring session for current user, so shortcut")
apiClient.update(
baseUrl = current.server.url,
accessToken = current.user.accessToken,
)
return current
} else {
val user = serverAndUsers.users.firstOrNull { it.id == userId } val user = serverAndUsers.users.firstOrNull { it.id == userId }
if (user != null) { if (user != null) {
// TODO pin-related // TODO pin-related
@ -135,6 +147,7 @@ class ServerRepository
return changeUser(serverAndUsers.server, user) return changeUser(serverAndUsers.server, user)
} }
} }
}
return null return null
} }
@ -271,5 +284,4 @@ class ServerRepository
data class CurrentUser( data class CurrentUser(
val server: JellyfinServer, val server: JellyfinServer,
val user: JellyfinUser, val user: JellyfinUser,
val userDto: UserDto,
) )

View file

@ -8,7 +8,6 @@ import org.jellyfin.sdk.model.api.UserConfiguration
*/ */
data class UserPreferences( data class UserPreferences(
val appPreferences: AppPreferences, val appPreferences: AppPreferences,
val userConfig: UserConfiguration,
) )
val DefaultUserConfiguration = val DefaultUserConfiguration =

View file

@ -13,6 +13,7 @@ import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaStream import org.jellyfin.sdk.model.api.MediaStream
import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.MediaStreamType
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
import org.jellyfin.sdk.model.api.UserConfiguration
import org.jellyfin.sdk.model.serializer.toUUIDOrNull import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
@ -26,6 +27,8 @@ class StreamChoiceService
private val serverRepository: ServerRepository, private val serverRepository: ServerRepository,
private val playbackLanguageChoiceDao: PlaybackLanguageChoiceDao, private val playbackLanguageChoiceDao: PlaybackLanguageChoiceDao,
) { ) {
private val userConfig: UserConfiguration? get() = serverRepository.currentUserDto.value?.configuration
suspend fun updateAudio( suspend fun updateAudio(
dto: BaseItemDto, dto: BaseItemDto,
audioLang: String, audioLang: String,
@ -117,7 +120,7 @@ class StreamChoiceService
val seriesLang = val seriesLang =
playbackLanguageChoice?.audioLanguage?.takeIf { it.isNotNullOrBlank() } playbackLanguageChoice?.audioLanguage?.takeIf { it.isNotNullOrBlank() }
// If the user has chosen a different language for the series, prefer that // If the user has chosen a different language for the series, prefer that
val audioLanguage = seriesLang ?: prefs.userConfig.audioLanguagePreference val audioLanguage = seriesLang ?: userConfig?.audioLanguagePreference
if (audioLanguage.isNotNullOrBlank()) { if (audioLanguage.isNotNullOrBlank()) {
val sorted = val sorted =
@ -174,7 +177,7 @@ class StreamChoiceService
val seriesLang = val seriesLang =
playbackLanguageChoice?.subtitleLanguage?.takeIf { it.isNotNullOrBlank() } playbackLanguageChoice?.subtitleLanguage?.takeIf { it.isNotNullOrBlank() }
val subtitleLanguage = val subtitleLanguage =
(seriesLang ?: prefs.userConfig.subtitleLanguagePreference) (seriesLang ?: userConfig?.subtitleLanguagePreference)
?.takeIf { it.isNotNullOrBlank() } ?.takeIf { it.isNotNullOrBlank() }
val subtitleMode = val subtitleMode =
@ -192,7 +195,7 @@ class StreamChoiceService
else -> { else -> {
// Fallback to the user's preference // Fallback to the user's preference
prefs.userConfig.subtitleMode userConfig?.subtitleMode ?: SubtitlePlaybackMode.DEFAULT
} }
} }
return when (subtitleMode) { return when (subtitleMode) {
@ -213,7 +216,7 @@ class StreamChoiceService
} }
SubtitlePlaybackMode.SMART -> { SubtitlePlaybackMode.SMART -> {
val audioLanguage = prefs.userConfig.audioLanguagePreference val audioLanguage = userConfig?.audioLanguagePreference
val audioStreamLang = audioStream?.language val audioStreamLang = audioStream?.language
if (audioLanguage.isNotNullOrBlank() && audioStreamLang.isNotNullOrBlank() && audioLanguage != audioStreamLang) { if (audioLanguage.isNotNullOrBlank() && audioStreamLang.isNotNullOrBlank() && audioLanguage != audioStreamLang) {
candidates.firstOrNull { it.language == subtitleLanguage } candidates.firstOrNull { it.language == subtitleLanguage }

View file

@ -3,7 +3,6 @@ package com.github.damontecres.wholphin.services
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject import javax.inject.Inject
@ -21,7 +20,6 @@ class UserPreferencesService
val appPrefs = preferencesDataStore.data.firstOrNull() ?: AppPreferences.getDefaultInstance() val appPrefs = preferencesDataStore.data.firstOrNull() ?: AppPreferences.getDefaultInstance()
UserPreferences( UserPreferences(
appPrefs, appPrefs,
userConfig ?: DefaultUserConfiguration,
) )
} }
} }

View file

@ -302,7 +302,7 @@ fun DebugPage(
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
Text( Text(
text = "User server settings: ${preferences.userConfig}", text = "User server settings: ${viewModel.serverRepository.currentUserDto.value?.configuration}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )