diff --git a/app/src/main/java/com/github/damontecres/wholphin/WholphinApplication.kt b/app/src/main/java/com/github/damontecres/wholphin/WholphinApplication.kt index f027bbae..6ec9d4b4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/WholphinApplication.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/WholphinApplication.kt @@ -122,5 +122,7 @@ class WholphinApplication : private set val minimumServerVersion: ServerVersion = Jellyfin.minimumVersion + + val newMinimumServerVersion: ServerVersion = ServerVersion(12, 0, 0) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt index 282c161e..4293c422 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt @@ -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(), + ) + } + } + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index 6c0bb088..aeb6ba22 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -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( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt index 200bac4e..2e0ce392 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt @@ -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>(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 = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt index 81cc7c2a..bd14c38c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt @@ -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( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt index c25fe776..c5ac22b3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt @@ -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), + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt index 53190665..00beccb3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt @@ -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, } 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 86cf718d..cfe70890 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 @@ -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) } } } 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 e1a6dd96..b11b0b19 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 @@ -300,16 +300,18 @@ class SwitchUserViewModel knownUsers + publicUsers } - private suspend fun checkServerVersion(): Pair { + private suspend fun checkServerVersion(): Pair { 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, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca023d9c..3ffe79fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -932,6 +932,8 @@ Books Audio books Server version not supported + Server version will not be supported soon + Required server version: %1$s Appears on Dim screen after delay Dim percentage