From 616565f671a16e4305e3f3915e1de0c375c5d986 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 11 Sep 2026 15:40:11 -0400 Subject: [PATCH] Fixes to `v1.0.7` (#1953) ## Description Collection of minor bug fixes for `v1.0.7` - Fixes a possible race condition crash when navigating to the search page - Uses friendlier error message when login fails due to parental restrictions (based on [`jellyfin-web`](https://github.com/jellyfin/jellyfin-web/blob/v10.11.11/src/controllers/session/login/index.js#L38-L41)) - Don't re-sort search results by name, just by relevance scoring - Consume the key event when dim is active (ie if the screen is dimmed, pressing a key will un-dim and not perform another action) ### Related issues Fixes #1922 Fixes #1951 Related to #1848 (search sorting) ### Testing Emulator & nvidia shield ## Screenshots N/A ## AI or LLM usage None --- .../damontecres/wholphin/MainActivity.kt | 2 +- .../wholphin/ui/search/SearchPage.kt | 6 ++- .../wholphin/ui/search/SearchViewModel.kt | 6 +-- .../wholphin/ui/setup/SwitchUserContent.kt | 35 ++++++++------ .../wholphin/ui/setup/SwitchUserViewModel.kt | 47 ++++++++++++++++--- app/src/main/res/values/strings.xml | 2 + 6 files changed, 70 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index f7d995ee..62f0e39b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -288,7 +288,7 @@ class MainActivity : AppCompatActivity() { } override fun dispatchKeyEvent(event: KeyEvent): Boolean { - if (screensaverService.state.value.show) { + if (screensaverService.state.value.run { show || showDim }) { screensaverService.stop(false) screensaverService.pulse() return true diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt index 1bde1c4c..e353287d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt @@ -418,12 +418,16 @@ fun SearchPage( itemsIndexed(state.includedSearchableTypes) { index, type -> val rowIndex = RESULTS_START + index val result = state.results.getOrDefault(type, SearchResult.Searching) + val focusRequester = + remember(state.includedSearchableTypes.size) { + focusRequesters.getOrNull(rowIndex) ?: FocusRequester() + } SearchRowResult( title = type.titleStringRes, result = result, rowIndex = rowIndex, position = position, - focusRequester = focusRequesters[rowIndex], + focusRequester = focusRequester, onClickItem = onClickItem, onLongClickItem = { index, item -> onLongClickItem(rowIndex, index, item) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt index 60ba2199..81dfa95f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt @@ -273,8 +273,7 @@ class SearchViewModel } val sorted = items.sortedWith( - compareBy { SearchRelevance.score(it, query) } - .thenBy { it.sortName }, + compareBy { SearchRelevance.score(it, query) }, ) Timber.v("Search finished for %s, %s results", type, sorted.size) _state.value.results[type] = SearchResult.Success(sorted) @@ -307,8 +306,7 @@ class SearchViewModel } val sorted = items.sortedWith( - compareBy { SearchRelevance.score(it, query) } - .thenBy { it.name ?: "" }, + compareBy { SearchRelevance.score(it, query) }, ) Timber.v("searchCombined complete %s results", sorted.size) _state.update { it.copy(combinedResults = SearchResult.Success(sorted)) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt index 22b5a594..86cf718d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt @@ -86,6 +86,24 @@ fun SwitchUserContent( showAddUser = false } + fun trySwitchUser(user: JellyfinUser) { + val result = viewModel.trySwitchUser(user) + scope.launch { + when (val r = result.await()) { + is SwitchUserResult.Error -> { + Toast.makeText(context, r.errorMessage, Toast.LENGTH_LONG).show() + if (r.showLogin) { + showAddUserDialog(user) + } + } + + SwitchUserResult.Success -> { + // no-op, view model will navigate + } + } + } + } + LaunchedEffect(state.switchUserState) { if (!showAddUser) { when (val s = state.switchUserState) { @@ -148,13 +166,7 @@ fun SwitchUserContent( } else if (user.hasPin) { switchUserWithPin = user } else { - val result = viewModel.trySwitchUser(user) - scope.launch { - result.await()?.let { - Toast.makeText(context, it, Toast.LENGTH_LONG).show() - showAddUserDialog(user) - } - } + trySwitchUser(user) } }, onAddUser = { @@ -396,14 +408,7 @@ fun SwitchUserContent( }, onTextChange = { if (it == user.pin) { - val result = viewModel.trySwitchUser(user) - scope.launch { - result.await()?.let { - Toast.makeText(context, it, Toast.LENGTH_LONG).show() - showAddUserDialog(user) - switchUserWithPin = null - } - } + trySwitchUser(user) } }, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index a282eb15..c47b86c5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -1,7 +1,9 @@ package com.github.damontecres.wholphin.ui.setup +import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.WholphinApplication import com.github.damontecres.wholphin.data.JellyfinServerDao import com.github.damontecres.wholphin.data.ServerRepository @@ -19,6 +21,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job @@ -46,6 +49,7 @@ import kotlin.time.Duration.Companion.seconds class SwitchUserViewModel @AssistedInject constructor( + @param:ApplicationContext private val context: Context, val jellyfin: Jellyfin, val serverRepository: ServerRepository, val serverDao: JellyfinServerDao, @@ -124,21 +128,38 @@ class SwitchUserViewModel } } - fun trySwitchUser(user: JellyfinUser): Deferred = + fun trySwitchUser(user: JellyfinUser): Deferred = viewModelScope.async(WholphinDispatchers.IO) { try { val current = serverRepository.changeUser(server, user) setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) - null + SwitchUserResult.Success } catch (ex: InvalidStatusException) { - if (ex.status == 401) { - "Credentials expired, please login in again" - } else { - ex.localizedMessage + when (ex.status) { + 401 -> { + Timber.w(ex, "Error switching user 401") + SwitchUserResult.Error( + context.getString(R.string.login_credentials_expired), + true, + ) + } + + 403 -> { + Timber.w(ex, "Error switching user 403") + SwitchUserResult.Error( + context.getString(R.string.login_not_authorized), + false, + ) + } + + else -> { + Timber.e(ex, "Error switching user") + SwitchUserResult.Error(ex.localizedMessage, true) + } } } catch (ex: Exception) { Timber.e(ex, "Error switching user") - ex.localizedMessage + SwitchUserResult.Error(ex.localizedMessage, true) } } @@ -322,3 +343,15 @@ data class SwitchUserState( val serverVersion: String? = null, val serverVersionSupported: ServerVersionSupported = ServerVersionSupported.UNKNOWN, ) + +/** + * Result of trying to switch users via [SwitchUserViewModel.trySwitchUser] + */ +sealed interface SwitchUserResult { + data object Success : SwitchUserResult + + data class Error( + val errorMessage: String?, + val showLogin: Boolean, + ) : SwitchUserResult +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a233096f..cff98add 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -923,4 +923,6 @@ Photo albums No favorites found! Remove from playlist + Credentials expired, please login in again + You are not authorized to access the server at this time