mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Better Jellyseerr connection error handling (#933)
## Description Show Jellyseerr connection errors in settings. If an error occurred trying to login, in setting it will show "Server error" and clicking gives more error info and option to remove the server. ### Related issues Fixes #907 ### Testing Emulator with simulated errors ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
dfd9acf561
commit
4f8f69c438
6 changed files with 120 additions and 63 deletions
|
|
@ -44,18 +44,33 @@ class SeerrServerRepository
|
|||
private val serverRepository: ServerRepository,
|
||||
@param:StandardOkHttpClient private val okHttpClient: OkHttpClient,
|
||||
) {
|
||||
private val _current = MutableStateFlow<CurrentSeerr?>(null)
|
||||
val current: StateFlow<CurrentSeerr?> = _current
|
||||
val currentServer: Flow<SeerrServer?> = current.map { it?.server }
|
||||
val currentUser: Flow<SeerrUser?> = current.map { it?.user }
|
||||
private val _connection =
|
||||
MutableStateFlow<SeerrConnectionStatus>(SeerrConnectionStatus.NotConfigured)
|
||||
val connection: StateFlow<SeerrConnectionStatus> = _connection
|
||||
|
||||
val current: Flow<CurrentSeerr?> =
|
||||
_connection.map { (it as? SeerrConnectionStatus.Success)?.current }
|
||||
val currentServer: Flow<SeerrServer?> =
|
||||
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.server }
|
||||
val currentUser: Flow<SeerrUser?> =
|
||||
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.user }
|
||||
|
||||
/**
|
||||
* Whether Seerr integration is currently active of not
|
||||
*/
|
||||
val active: Flow<Boolean> = current.map { it != null && seerrApi.active }
|
||||
val active: Flow<Boolean> =
|
||||
connection.map { it is SeerrConnectionStatus.Success && seerrApi.active }
|
||||
|
||||
fun clear() {
|
||||
_current.update { null }
|
||||
_connection.update { SeerrConnectionStatus.NotConfigured }
|
||||
seerrApi.update("", null)
|
||||
}
|
||||
|
||||
fun error(
|
||||
serverUrl: String,
|
||||
exception: Exception,
|
||||
) {
|
||||
_connection.update { SeerrConnectionStatus.Error(serverUrl, exception) }
|
||||
seerrApi.update("", null)
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +80,10 @@ class SeerrServerRepository
|
|||
userConfig: SeerrUserConfig,
|
||||
) {
|
||||
val publicSettings = seerrApi.api.settingsApi.settingsPublicGet()
|
||||
_current.update {
|
||||
CurrentSeerr(server, user, userConfig, publicSettings)
|
||||
_connection.update {
|
||||
SeerrConnectionStatus.Success(
|
||||
CurrentSeerr(server, user, userConfig, publicSettings),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +171,7 @@ class SeerrServerRepository
|
|||
}
|
||||
|
||||
suspend fun removeServer() {
|
||||
val current = _current.value ?: return
|
||||
val current = (_connection.value as? SeerrConnectionStatus.Success)?.current ?: return
|
||||
seerrServerDao.deleteUser(current.server.id, current.user.jellyfinUserRowId)
|
||||
clear()
|
||||
}
|
||||
|
|
@ -165,6 +182,19 @@ class SeerrServerRepository
|
|||
*/
|
||||
typealias SeerrUserConfig = User
|
||||
|
||||
sealed interface SeerrConnectionStatus {
|
||||
data object NotConfigured : SeerrConnectionStatus
|
||||
|
||||
data class Error(
|
||||
val serverUrl: String,
|
||||
val ex: Exception,
|
||||
) : SeerrConnectionStatus
|
||||
|
||||
data class Success(
|
||||
val current: CurrentSeerr,
|
||||
) : SeerrConnectionStatus
|
||||
}
|
||||
|
||||
data class CurrentSeerr(
|
||||
val server: SeerrServer,
|
||||
val user: SeerrUser,
|
||||
|
|
@ -244,45 +274,34 @@ class UserSwitchListener
|
|||
launchIO {
|
||||
seerrServerDao
|
||||
.getUsersByJellyfinUser(user.rowId)
|
||||
.firstOrNull()
|
||||
.lastOrNull()
|
||||
?.let { seerrUser ->
|
||||
val server =
|
||||
seerrServerDao.getServer(seerrUser.serverId)?.server
|
||||
if (server != null) {
|
||||
Timber.i("Found a seerr user & server")
|
||||
seerrApi.update(server.url, seerrUser.credential)
|
||||
val userConfig =
|
||||
if (seerrUser.authMethod != SeerrAuthMethod.API_KEY) {
|
||||
try {
|
||||
try {
|
||||
seerrApi.update(server.url, seerrUser.credential)
|
||||
val userConfig =
|
||||
if (seerrUser.authMethod != SeerrAuthMethod.API_KEY) {
|
||||
login(
|
||||
seerrApi.api,
|
||||
seerrUser.authMethod,
|
||||
seerrUser.username,
|
||||
seerrUser.password,
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(
|
||||
ex,
|
||||
"Error logging into %s",
|
||||
server.url,
|
||||
)
|
||||
seerrServerRepository.clear()
|
||||
return@let
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
} else {
|
||||
seerrApi.api.usersApi.authMeGet()
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(
|
||||
ex,
|
||||
"Error logging into %s",
|
||||
server.url,
|
||||
)
|
||||
seerrServerRepository.clear()
|
||||
return@let
|
||||
}
|
||||
}
|
||||
seerrServerRepository.set(server, seerrUser, userConfig)
|
||||
seerrServerRepository.set(server, seerrUser, userConfig)
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(
|
||||
ex,
|
||||
"Error logging into %s",
|
||||
server.url,
|
||||
)
|
||||
seerrServerRepository.error(server.url, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -408,12 +408,13 @@ fun ConfirmDialog(
|
|||
onConfirm: () -> Unit,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
elevation: Dp = 8.dp,
|
||||
bodyColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) = BasicDialog(
|
||||
onDismissRequest = onCancel,
|
||||
properties = properties,
|
||||
elevation = elevation,
|
||||
content = {
|
||||
ConfirmDialogContent(title, body, onCancel, onConfirm, Modifier)
|
||||
ConfirmDialogContent(title, body, onCancel, onConfirm, Modifier, bodyColor)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -427,6 +428,7 @@ fun ConfirmDialogContent(
|
|||
onCancel: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
bodyColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
|
@ -446,7 +448,7 @@ fun ConfirmDialogContent(
|
|||
item {
|
||||
Text(
|
||||
text = body,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = bodyColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ class DiscoverMovieViewModel
|
|||
val canCancelRequest = MutableStateFlow(false)
|
||||
|
||||
val userConfig = seerrServerRepository.current.map { it?.config }
|
||||
val request4kEnabled = seerrServerRepository.current.map { it?.request4kMovieEnabled ?: false }
|
||||
val request4kEnabled =
|
||||
seerrServerRepository.current.map { it?.request4kMovieEnabled ?: false }
|
||||
|
||||
init {
|
||||
init()
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class SeerrRequestsViewModel
|
|||
viewModelScope.launchIO {
|
||||
backdropService.clearBackdrop()
|
||||
}
|
||||
seerrServerRepository.current
|
||||
seerrServerRepository.connection
|
||||
.onEach { user ->
|
||||
state.update { it.copy(requests = DataLoadingState.Loading) }
|
||||
if (user != null) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import com.github.damontecres.wholphin.preferences.PlayerBackend
|
|||
import com.github.damontecres.wholphin.preferences.advancedPreferences
|
||||
import com.github.damontecres.wholphin.preferences.basicPreferences
|
||||
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
|
||||
import com.github.damontecres.wholphin.services.SeerrConnectionStatus
|
||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
|
|
@ -90,7 +91,7 @@ fun PreferencesContent(
|
|||
var showPinFlow by remember { mutableStateOf(false) }
|
||||
|
||||
var cacheUsage by remember { mutableStateOf(CacheUsage(0, 0, 0)) }
|
||||
val seerrIntegrationEnabled by viewModel.seerrEnabled.collectAsState(false)
|
||||
val seerrConnection by viewModel.seerrConnection.collectAsState()
|
||||
var seerrDialogMode by remember { mutableStateOf<SeerrDialogMode>(SeerrDialogMode.None) }
|
||||
var showQuickConnectDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -385,19 +386,29 @@ fun PreferencesContent(
|
|||
ClickPreference(
|
||||
title = stringResource(pref.title),
|
||||
onClick = {
|
||||
if (seerrIntegrationEnabled) {
|
||||
seerrDialogMode = SeerrDialogMode.Remove
|
||||
} else {
|
||||
seerrVm.resetStatus()
|
||||
seerrDialogMode = SeerrDialogMode.Add
|
||||
}
|
||||
seerrDialogMode =
|
||||
when (val conn = seerrConnection) {
|
||||
is SeerrConnectionStatus.Error -> {
|
||||
SeerrDialogMode.Error(conn.serverUrl, conn.ex)
|
||||
}
|
||||
|
||||
SeerrConnectionStatus.NotConfigured -> {
|
||||
SeerrDialogMode.Add
|
||||
}
|
||||
|
||||
is SeerrConnectionStatus.Success -> {
|
||||
SeerrDialogMode.Remove(
|
||||
conn.current.server.url,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier,
|
||||
summary =
|
||||
if (seerrIntegrationEnabled) {
|
||||
stringResource(R.string.enabled)
|
||||
} else {
|
||||
null
|
||||
when (seerrConnection) {
|
||||
is SeerrConnectionStatus.Error -> stringResource(R.string.voice_error_server)
|
||||
SeerrConnectionStatus.NotConfigured -> stringResource(R.string.add_server)
|
||||
is SeerrConnectionStatus.Success -> stringResource(R.string.enabled)
|
||||
},
|
||||
onLongClick = {},
|
||||
interactionSource = interactionSource,
|
||||
|
|
@ -479,11 +490,11 @@ fun PreferencesContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
when (seerrDialogMode) {
|
||||
SeerrDialogMode.Remove -> {
|
||||
when (val mode = seerrDialogMode) {
|
||||
is SeerrDialogMode.Remove -> {
|
||||
ConfirmDialog(
|
||||
title = stringResource(R.string.remove_seerr_server),
|
||||
body = currentServer?.url ?: "",
|
||||
body = mode.serverUrl,
|
||||
onCancel = { seerrDialogMode = SeerrDialogMode.None },
|
||||
onConfirm = {
|
||||
seerrVm.removeServer()
|
||||
|
|
@ -510,6 +521,28 @@ fun PreferencesContent(
|
|||
)
|
||||
}
|
||||
|
||||
is SeerrDialogMode.Error -> {
|
||||
val errorStr = stringResource(R.string.voice_error_server)
|
||||
val body =
|
||||
remember(mode) {
|
||||
"""
|
||||
${mode.serverUrl}
|
||||
|
||||
$errorStr: ${mode.ex.localizedMessage}
|
||||
""".trimIndent()
|
||||
}
|
||||
ConfirmDialog(
|
||||
title = stringResource(R.string.remove_seerr_server),
|
||||
body = body,
|
||||
onCancel = { seerrDialogMode = SeerrDialogMode.None },
|
||||
onConfirm = {
|
||||
seerrVm.removeServer()
|
||||
seerrDialogMode = SeerrDialogMode.None
|
||||
},
|
||||
bodyColor = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
SeerrDialogMode.None -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -580,10 +613,17 @@ data class CacheUsage(
|
|||
val imageDiskUsed: Long,
|
||||
)
|
||||
|
||||
private sealed class SeerrDialogMode {
|
||||
data object None : SeerrDialogMode()
|
||||
private sealed interface SeerrDialogMode {
|
||||
data object None : SeerrDialogMode
|
||||
|
||||
data object Add : SeerrDialogMode()
|
||||
data object Add : SeerrDialogMode
|
||||
|
||||
data object Remove : SeerrDialogMode()
|
||||
data class Remove(
|
||||
val serverUrl: String,
|
||||
) : SeerrDialogMode
|
||||
|
||||
data class Error(
|
||||
val serverUrl: String,
|
||||
val ex: Exception,
|
||||
) : SeerrDialogMode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.github.damontecres.wholphin.ui.preferences
|
|||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
|
|
@ -22,7 +21,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.ClientInfo
|
||||
import org.jellyfin.sdk.model.DeviceInfo
|
||||
|
|
@ -46,10 +44,7 @@ class PreferencesViewModel
|
|||
RememberTabManager by rememberTabManager {
|
||||
val currentUser get() = serverRepository.currentUser
|
||||
|
||||
val seerrEnabled =
|
||||
seerrServerRepository.currentUser.combine(currentUser.asFlow()) { seerrUser, jellyfinUser ->
|
||||
seerrUser != null && jellyfinUser != null && seerrUser.jellyfinUserRowId == jellyfinUser.rowId
|
||||
}
|
||||
val seerrConnection = seerrServerRepository.connection
|
||||
|
||||
private val _quickConnectStatus = MutableStateFlow<LoadingState>(LoadingState.Pending)
|
||||
val quickConnectStatus: StateFlow<LoadingState> = _quickConnectStatus
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue