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
This commit is contained in:
Damontecres 2026-09-11 15:40:11 -04:00 • committed by GitHub
parent 6486bd19ed
commit 616565f671
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 28 deletions

View file

@ -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

View file

@ -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)

View file

@ -273,8 +273,7 @@ class SearchViewModel
}
val sorted =
items.sortedWith(
compareBy<BaseItem> { SearchRelevance.score(it, query) }
.thenBy { it.sortName },
compareBy<BaseItem> { 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<BaseItem> { SearchRelevance.score(it, query) }
.thenBy { it.name ?: "" },
compareBy<BaseItem> { SearchRelevance.score(it, query) },
)
Timber.v("searchCombined complete %s results", sorted.size)
_state.update { it.copy(combinedResults = SearchResult.Success(sorted)) }

View file

@ -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)
}
},
)

View file

@ -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<String?> =
fun trySwitchUser(user: JellyfinUser): Deferred<SwitchUserResult> =
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
}

View file

@ -923,4 +923,6 @@
<string name="photo_albums">Photo albums</string>
<string name="no_favorites_found">No favorites found!</string>
<string name="remove_from_playlist">Remove from playlist</string>
<string name="login_credentials_expired">Credentials expired, please login in again</string>
<string name="login_not_authorized">You are not authorized to access the server at this time</string>
</resources>