Show warning for future unsupported server version (#2115)
## Description Now that Jellyfin 12 has been out for over six weeks, it's time to start considering dropping support for Jellyfin 10 & 11 as part of #1640. This PR adds a warning message on the switch server, switch user, & preferences pages if the current server version is <12. ### Related issues See https://github.com/damontecres/Wholphin/discussions/1640 for further discussion! ### Testing Emulator w/ faked data ## Screenshots N/A, reuses the same error message space ## AI or LLM usage None
This commit is contained in:
parent
9218940a69
commit
6cde8ce8a6
10 changed files with 164 additions and 67 deletions
|
|
@ -122,5 +122,7 @@ class WholphinApplication :
|
|||
private set
|
||||
|
||||
val minimumServerVersion: ServerVersion = Jellyfin.minimumVersion
|
||||
|
||||
val newMinimumServerVersion: ServerVersion = ServerVersion(12, 0, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import androidx.compose.ui.text.AnnotatedString
|
|||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.WholphinApplication
|
||||
import com.github.damontecres.wholphin.ui.setup.ServerVersionSupported
|
||||
import org.jellyfin.sdk.model.ServerVersion
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
|
|
@ -319,3 +321,55 @@ fun Resources.formatDuration(duration: Duration): String =
|
|||
else -> getString(R.string.duration_seconds, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
fun Resources.serverSupportedMessage(
|
||||
serverVersionSupported: ServerVersionSupported,
|
||||
serverVersion: ServerVersion?,
|
||||
): String? {
|
||||
val errorMsg =
|
||||
when (serverVersionSupported) {
|
||||
ServerVersionSupported.SUPPORTED -> {
|
||||
null
|
||||
}
|
||||
|
||||
ServerVersionSupported.NOT_SUPPORTED,
|
||||
ServerVersionSupported.UNKNOWN,
|
||||
-> {
|
||||
getString(R.string.server_version_not_supported)
|
||||
}
|
||||
|
||||
ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE -> {
|
||||
getString(R.string.server_version_not_supported_future)
|
||||
}
|
||||
}
|
||||
return errorMsg?.let {
|
||||
val msg =
|
||||
if (serverVersion != null) {
|
||||
"$it: $serverVersion"
|
||||
} else {
|
||||
it + ": " + getString(R.string.unknown)
|
||||
}
|
||||
msg + "\n" +
|
||||
when (serverVersionSupported) {
|
||||
ServerVersionSupported.SUPPORTED -> {
|
||||
""
|
||||
}
|
||||
|
||||
ServerVersionSupported.NOT_SUPPORTED,
|
||||
ServerVersionSupported.UNKNOWN,
|
||||
-> {
|
||||
getString(
|
||||
R.string.server_version_required,
|
||||
WholphinApplication.minimumServerVersion.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE -> {
|
||||
getString(
|
||||
R.string.server_version_required,
|
||||
WholphinApplication.newMinimumServerVersion.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.tv.material3.ListItem
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
|
|
@ -78,6 +80,7 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
|||
import com.github.damontecres.wholphin.ui.playOnClickSound
|
||||
import com.github.damontecres.wholphin.ui.playSoundOnFocus
|
||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
||||
import com.github.damontecres.wholphin.ui.serverSupportedMessage
|
||||
import com.github.damontecres.wholphin.ui.setup.ReleaseNotes
|
||||
import com.github.damontecres.wholphin.ui.setup.UpdateViewModel
|
||||
import com.github.damontecres.wholphin.ui.setup.seerr.AddSeerServerDialog
|
||||
|
|
@ -102,6 +105,7 @@ fun PreferencesContent(
|
|||
onFocus: (Int, Int) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val firstFocusRequester = remember { FocusRequester() }
|
||||
|
|
@ -114,6 +118,11 @@ fun PreferencesContent(
|
|||
var showPinFlow by remember { mutableStateOf(false) }
|
||||
var showVersionDialog by remember { mutableStateOf(false) }
|
||||
val players by viewModel.externalPlayers.collectAsState()
|
||||
val serverSupported by viewModel.serverSupported.collectAsState()
|
||||
val serverVersionMessage =
|
||||
remember(resources, serverSupported) {
|
||||
resources.serverSupportedMessage(serverSupported.second, serverSupported.first)
|
||||
}
|
||||
|
||||
var cacheUsage by remember { mutableStateOf(CacheUsage(0, 0, 0)) }
|
||||
val seerrConnection by viewModel.seerrConnection.collectAsState()
|
||||
|
|
@ -230,6 +239,19 @@ fun PreferencesContent(
|
|||
contentPadding = PaddingValues(16.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
serverVersionMessage?.let { message ->
|
||||
item {
|
||||
ListItem(
|
||||
selected = false,
|
||||
enabled = true,
|
||||
onClick = {},
|
||||
headlineContent = {
|
||||
PreferenceTitle(message, color = MaterialTheme.colorScheme.error)
|
||||
},
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showUpdate) {
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
|
@ -265,6 +287,7 @@ fun PreferencesContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
prefList.forEachIndexed { groupIndex, group ->
|
||||
item {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.datastore.core.DataStore
|
|||
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.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
|
|
@ -25,6 +26,7 @@ import com.github.damontecres.wholphin.services.SeerrServerRepository
|
|||
import com.github.damontecres.wholphin.services.ServerReportService
|
||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.setup.ServerVersionSupported
|
||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
@ -33,6 +35,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -76,6 +79,23 @@ class PreferencesViewModel
|
|||
|
||||
val externalPlayers = MutableStateFlow<List<ExternalPlayerApp>>(emptyList())
|
||||
|
||||
val serverSupported =
|
||||
serverRepository.currentServerFlow
|
||||
.map {
|
||||
val serverVersion = it?.serverVersion
|
||||
serverVersion to
|
||||
when {
|
||||
serverVersion == null -> ServerVersionSupported.NOT_SUPPORTED
|
||||
serverVersion < WholphinApplication.minimumServerVersion -> ServerVersionSupported.NOT_SUPPORTED
|
||||
serverVersion < WholphinApplication.newMinimumServerVersion -> ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE
|
||||
else -> ServerVersionSupported.SUPPORTED
|
||||
}
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
null to ServerVersionSupported.SUPPORTED,
|
||||
)
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
val fakeIntent =
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import androidx.tv.material3.Surface
|
|||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import org.jellyfin.sdk.model.api.PublicSystemInfo
|
||||
|
|
@ -177,7 +178,12 @@ fun ServerIconCard(
|
|||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = (connectionStatus as? ServerConnectionStatus.Error)?.message,
|
||||
tint = MaterialTheme.colorScheme.errorContainer,
|
||||
tint =
|
||||
if (serverVersionSupported == ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE) {
|
||||
AppColors.GoldenYellow
|
||||
} else {
|
||||
MaterialTheme.colorScheme.errorContainer
|
||||
},
|
||||
modifier = Modifier.size(cardSize * 0.3f),
|
||||
)
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.animation.slideOutVertically
|
|||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -69,8 +70,10 @@ import com.github.damontecres.wholphin.ui.dimAndBlur
|
|||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.serverSupportedMessage
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import org.jellyfin.sdk.model.ServerVersion
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
|
|
@ -235,20 +238,7 @@ private fun SwitchServerContentInternal(
|
|||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
) { message ->
|
||||
|
||||
Text(
|
||||
text = message ?: "",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 32.dp, end = 32.dp, bottom = 32.dp)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
SwitchErrorMessage(message ?: "")
|
||||
}
|
||||
|
||||
// Delete server dialog
|
||||
|
|
@ -491,21 +481,38 @@ fun serverErrorMessage(
|
|||
server: ServerState?,
|
||||
resources: Resources,
|
||||
): String? =
|
||||
when {
|
||||
server?.status is ServerConnectionStatus.Error -> {
|
||||
server.status.message
|
||||
}
|
||||
|
||||
server?.status is ServerConnectionStatus.Success &&
|
||||
server.versionSupported == ServerVersionSupported.NOT_SUPPORTED -> {
|
||||
resources.getString(R.string.server_version_not_supported) + ": ${server.status.systemInfo.version}"
|
||||
}
|
||||
|
||||
server?.versionSupported == ServerVersionSupported.NOT_SUPPORTED -> {
|
||||
resources.getString(R.string.server_version_not_supported)
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
if (server == null || server.status == ServerConnectionStatus.Pending) {
|
||||
null
|
||||
} else if (server.status is ServerConnectionStatus.Success) {
|
||||
resources.serverSupportedMessage(
|
||||
server.versionSupported,
|
||||
server.status.systemInfo.version
|
||||
?.let { ServerVersion.fromString(it) },
|
||||
)
|
||||
} else if (server.status is ServerConnectionStatus.Error) {
|
||||
server.status.message
|
||||
} else {
|
||||
resources.serverSupportedMessage(
|
||||
server.versionSupported,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BoxScope.SwitchErrorMessage(message: String?) {
|
||||
message?.let {
|
||||
Text(
|
||||
text = message,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 32.dp, end = 32.dp, bottom = 32.dp)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ class SwitchServerViewModel
|
|||
result.systemInfo.version?.let { ServerVersion.fromString(it) }
|
||||
if (serverVersion == null || serverVersion < WholphinApplication.minimumServerVersion) {
|
||||
ServerVersionSupported.NOT_SUPPORTED
|
||||
} else if (serverVersion < WholphinApplication.newMinimumServerVersion) {
|
||||
ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE
|
||||
} else {
|
||||
ServerVersionSupported.SUPPORTED
|
||||
}
|
||||
|
|
@ -307,4 +309,5 @@ enum class ServerVersionSupported {
|
|||
SUPPORTED,
|
||||
NOT_SUPPORTED,
|
||||
UNKNOWN,
|
||||
NOT_SUPPORTED_IN_FUTURE,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -50,6 +49,7 @@ import com.github.damontecres.wholphin.ui.components.TextButton
|
|||
import com.github.damontecres.wholphin.ui.dimAndBlur
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.serverSupportedMessage
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -183,37 +183,15 @@ fun SwitchUserContent(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
when (state.serverVersionSupported) {
|
||||
ServerVersionSupported.SUPPORTED -> {}
|
||||
|
||||
ServerVersionSupported.NOT_SUPPORTED,
|
||||
ServerVersionSupported.UNKNOWN,
|
||||
-> {
|
||||
val resources = LocalResources.current
|
||||
val message =
|
||||
remember(resources) {
|
||||
if (state.serverVersion.isNotNullOrBlank()) {
|
||||
resources.getString(R.string.server_version_not_supported) + ": ${state.serverVersion}"
|
||||
} else {
|
||||
resources.getString(R.string.server_version_not_supported) + ": " +
|
||||
resources.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = message,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 32.dp, end = 32.dp, bottom = 32.dp)
|
||||
.align(Alignment.BottomCenter),
|
||||
val resources = LocalResources.current
|
||||
val message =
|
||||
remember(resources, state.serverVersionSupported) {
|
||||
resources.serverSupportedMessage(
|
||||
state.serverVersionSupported,
|
||||
state.serverVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
SwitchErrorMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,16 +300,18 @@ class SwitchUserViewModel
|
|||
knownUsers + publicUsers
|
||||
}
|
||||
|
||||
private suspend fun checkServerVersion(): Pair<String?, ServerVersionSupported> {
|
||||
private suspend fun checkServerVersion(): Pair<ServerVersion?, ServerVersionSupported> {
|
||||
val api = jellyfin.createApi(server.url)
|
||||
val systemInfo by api.systemApi.getPublicSystemInfo()
|
||||
val serverVersion = systemInfo.version?.let { ServerVersion.fromString(it) }
|
||||
return if (serverVersion == null) {
|
||||
systemInfo.version to ServerVersionSupported.UNKNOWN
|
||||
serverVersion to ServerVersionSupported.UNKNOWN
|
||||
} else if (serverVersion < WholphinApplication.minimumServerVersion) {
|
||||
systemInfo.version to ServerVersionSupported.NOT_SUPPORTED
|
||||
serverVersion to ServerVersionSupported.NOT_SUPPORTED
|
||||
} else if (serverVersion < WholphinApplication.newMinimumServerVersion) {
|
||||
serverVersion to ServerVersionSupported.NOT_SUPPORTED_IN_FUTURE
|
||||
} else {
|
||||
systemInfo.version to ServerVersionSupported.SUPPORTED
|
||||
serverVersion to ServerVersionSupported.SUPPORTED
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +343,7 @@ data class SwitchUserState(
|
|||
// LoadingState for while adding/switching users
|
||||
val switchUserState: LoadingState = LoadingState.Pending,
|
||||
val loginAttempts: Int = 0,
|
||||
val serverVersion: String? = null,
|
||||
val serverVersion: ServerVersion? = null,
|
||||
val serverVersionSupported: ServerVersionSupported = ServerVersionSupported.UNKNOWN,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -932,6 +932,8 @@
|
|||
<string name="books">Books</string>
|
||||
<string name="audio_books">Audio books</string>
|
||||
<string name="server_version_not_supported">Server version not supported</string>
|
||||
<string name="server_version_not_supported_future">Server version will not be supported soon</string>
|
||||
<string name="server_version_required">Required server version: %1$s</string>
|
||||
<string name="appears_on">Appears on</string>
|
||||
<string name="dim_after_delay">Dim screen after delay</string>
|
||||
<string name="dim_percentage">Dim percentage</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue