From a4ad56216b124fd9b726cdc7891f54269700f68b Mon Sep 17 00:00:00 2001 From: Jonas Schneider Date: Sun, 12 Apr 2026 22:55:16 +0200 Subject: [PATCH 1/3] feat: use semver versioning for compatibility computation --- .../TandoorServerVersionCompatibility.kt | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt b/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt index 47c7df34..3cbff62e 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt @@ -27,6 +27,7 @@ enum class TandoorServerVersionCompatibilityState( val description: StringResource, val icon: ImageVector, val iconTint: @Composable () -> Color, + val tint: @Composable () -> Color, val hideCompatibleVersionsList: Boolean = false, val disableDismiss: Boolean = false ) { @@ -35,32 +36,37 @@ enum class TandoorServerVersionCompatibilityState( description = Res.string.tandoor_compatibility_incompatible_description, icon = Icons.Rounded.Block, iconTint = { MaterialTheme.colorScheme.error }, + tint = { MaterialTheme.colorScheme.error }, disableDismiss = true ), + UNKNOWN( + label = Res.string.tandoor_compatibility_unknown_label, + description = Res.string.tandoor_compatibility_unknown_description, + icon = Icons.Rounded.QuestionMark, + iconTint = { Color.Gray }, + tint = { Color.Gray } + ), MIXED_COMPATIBILITY( label = Res.string.tandoor_compatibility_mixed_compatibility_label, description = Res.string.tandoor_compatibility_mixed_compatibility_description, icon = Icons.Rounded.WarningAmber, - iconTint = { Color.Yellow } + iconTint = { Color(0xFFBA8E23) }, // darker yellow + tint = { Color.Yellow } ), FULL_COMPATIBILITY( label = Res.string.tandoor_compatibility_full_compatibility_label, description = Res.string.tandoor_compatibility_full_compatibility_description, icon = Icons.Rounded.Check, - iconTint = { Color.Green }, + iconTint = { Color(0xFF06402B) }, // darker green + tint = { Color.Green }, hideCompatibleVersionsList = true ), - UNKNOWN( - label = Res.string.tandoor_compatibility_unknown_label, - description = Res.string.tandoor_compatibility_unknown_description, - icon = Icons.Rounded.QuestionMark, - iconTint = { Color.Gray } - ), NOT_CHECKABLE( label = Res.string.tandoor_compatibility_not_checkable_label, description = Res.string.tandoor_compatibility_not_checkable_description, icon = Icons.Rounded.QuestionMark, - iconTint = { Color.Gray } + iconTint = { Color.Gray }, + tint = { Color.Gray } ) } @@ -112,11 +118,40 @@ enum class TandoorServerVersionCompatibility( } fun getCompatibilityStateOfVersion(version: String): TandoorServerVersionCompatibilityState { - return try { - parseVersion(version).state - } catch(e: NullPointerException) { - TandoorServerVersionCompatibilityState.UNKNOWN + // versions precedence: + // 1. Version found in version matrix -> use that + // 2. Go down bux-fix until 0 -> use that. + // 3. Go down minor-fix until 0 (also include bug fix version) -> MIXED_COMPATIBILITY + // Else Unknown + + val parts = version.split(".").map { it.toIntOrNull() ?: return TandoorServerVersionCompatibilityState.UNKNOWN } + if (parts.size != 3) return TandoorServerVersionCompatibilityState.UNKNOWN + + val (major, minor, patch) = parts + + try { return parseVersion(version).state } catch (_: NullPointerException) {} + + // walk down bugfix and use that + for (p in (patch - 1) downTo 0){ + try { return parseVersion("$major.$minor.$p").state } catch (_: NullPointerException) {} } + + // walk down minor and if found anything return at least MIXED or the more severe state + for (m in (minor - 1) downTo 0) { + for (p in (patch - 1) downTo 0){ + try { + val state = parseVersion("$major.$m.$p").state + + return if (state == TandoorServerVersionCompatibilityState.FULL_COMPATIBILITY) { + TandoorServerVersionCompatibilityState.MIXED_COMPATIBILITY + } else { + state + } + } catch (_: NullPointerException) {} + } + } + + return TandoorServerVersionCompatibilityState.UNKNOWN } } } \ No newline at end of file From f1842544f93962a0a7c09c193051168613da6352 Mon Sep 17 00:00:00 2001 From: Jonas Schneider Date: Sun, 12 Apr 2026 22:56:02 +0200 Subject: [PATCH 2/3] feat: change version setting appearance for compatibilities --- .../ui/component/settings/SettingsListItem.kt | 26 +++++++++++-------- .../kitshn/ui/view/settings/SettingsAbout.kt | 3 ++- .../kitshn/ui/view/settings/SettingsServer.kt | 24 ++++++++++++++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/settings/SettingsListItem.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/settings/SettingsListItem.kt index 5aaed704..ca6518e8 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/settings/SettingsListItem.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/settings/SettingsListItem.kt @@ -2,6 +2,7 @@ package de.kitshn.ui.component.settings import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -12,6 +13,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip @@ -40,15 +42,17 @@ fun SettingsListItem( trailingContent: @Composable () -> Unit = {}, alternativeColors: Boolean = false, selected: Boolean = false, + containerColor: Color? = null, onClick: () -> Unit = {} ) { - val containerColor = if(selected) { - MaterialTheme.colorScheme.primaryContainer - } else if(alternativeColors) { - MaterialTheme.colorScheme.surfaceContainerLow - } else { - MaterialTheme.colorScheme.surfaceContainer - } + val finalContainerColor = containerColor + ?: if(selected) { + MaterialTheme.colorScheme.primaryContainer + } else if(alternativeColors) { + MaterialTheme.colorScheme.surfaceContainerLow + } else { + MaterialTheme.colorScheme.surfaceContainer + } ListItem( modifier = modifier @@ -106,10 +110,10 @@ fun SettingsListItem( .alpha(if(enabled) 1f else 0.5f) .clickable { if(enabled) onClick() }, colors = ListItemDefaults.colors( - containerColor = containerColor, - leadingIconColor = MaterialTheme.colorScheme.contentColorFor(containerColor), - headlineColor = MaterialTheme.colorScheme.contentColorFor(containerColor), - supportingColor = MaterialTheme.colorScheme.contentColorFor(containerColor) + containerColor = finalContainerColor, + leadingIconColor = MaterialTheme.colorScheme.contentColorFor(finalContainerColor), + headlineColor = MaterialTheme.colorScheme.contentColorFor(finalContainerColor), + supportingColor = MaterialTheme.colorScheme.contentColorFor(finalContainerColor) .copy(alpha = 0.8f) ), overlineContent = overlineContent, diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsAbout.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsAbout.kt index b5cee010..68180ded 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsAbout.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsAbout.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp +import com.mikepenz.aboutlibraries.ui.compose.produceLibraries import com.mikepenz.aboutlibraries.ui.compose.rememberLibraries import com.mikepenz.aboutlibraries.ui.compose.util.author import de.kitshn.launchWebsiteHandler @@ -63,7 +64,7 @@ fun ViewSettingsAbout( val launchWebsite = launchWebsiteHandler() val uriHandler = LocalUriHandler.current - val libs by rememberLibraries { + val libs by produceLibraries { Res.readBytes("files/aboutlibraries.json").decodeToString() } diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsServer.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsServer.kt index bb187fbe..a77ef8d4 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsServer.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/settings/SettingsServer.kt @@ -1,7 +1,9 @@ package de.kitshn.ui.view.settings +import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Logout import androidx.compose.material.icons.rounded.AccountCircle @@ -34,6 +36,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp import de.kitshn.api.tandoor.TandoorRequestState import de.kitshn.closeAppHandler import de.kitshn.launchWebsiteHandler @@ -42,6 +45,7 @@ import de.kitshn.ui.component.settings.SettingsListItem import de.kitshn.ui.component.settings.SettingsListItemPosition import de.kitshn.ui.dialog.version.TandoorServerVersionCompatibilityDialog import de.kitshn.ui.view.ViewParameters +import de.kitshn.version.TandoorServerVersionCompatibility import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_sign_out import kitshn.composeapp.generated.resources.action_sign_out_description @@ -70,6 +74,9 @@ fun ViewSettingsServer( val launchWebsiteHandler = launchWebsiteHandler() val closeAppHandler = closeAppHandler() + val serverVersion = p.vm.tandoorClient?.container?.serverSettings?.version + val compatibilityState = TandoorServerVersionCompatibility.getCompatibilityStateOfVersion(serverVersion ?: "") + var showVersionCompatibilityBottomSheet by remember { mutableStateOf(false) } var showDataManagementDialog by remember { mutableStateOf(false) } @@ -129,13 +136,22 @@ fun ViewSettingsServer( label = { Text(stringResource(Res.string.common_version)) }, description = { Text( - p.vm.tandoorClient?.container?.serverSettings?.version - ?: stringResource(Res.string.common_unknown) + serverVersion ?: stringResource(Res.string.common_unknown) ) }, icon = Icons.Rounded.Numbers, - enabled = p.vm.tandoorClient?.container?.serverSettings?.version != null, - contentDescription = stringResource(Res.string.common_version) + trailingContent = { + Icon( + modifier = Modifier + .padding(4.dp), + imageVector = compatibilityState.icon, + contentDescription = compatibilityState.label.toString(), + tint = compatibilityState.iconTint() + ) + }, + containerColor = compatibilityState.tint().copy(alpha = 0.1f), + enabled = serverVersion != null, + contentDescription = stringResource(Res.string.common_version), ) { coroutineScope.launch { showVersionCompatibilityBottomSheet = true From aef30070bd049a88c323b8517f24a72f823e566c Mon Sep 17 00:00:00 2001 From: Jonas Schneider Date: Sun, 12 Apr 2026 23:01:00 +0200 Subject: [PATCH 3/3] fix: clear up comment on versioning --- .../de/kitshn/version/TandoorServerVersionCompatibility.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt b/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt index 3cbff62e..64070f11 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/version/TandoorServerVersionCompatibility.kt @@ -120,9 +120,9 @@ enum class TandoorServerVersionCompatibility( fun getCompatibilityStateOfVersion(version: String): TandoorServerVersionCompatibilityState { // versions precedence: // 1. Version found in version matrix -> use that - // 2. Go down bux-fix until 0 -> use that. - // 3. Go down minor-fix until 0 (also include bug fix version) -> MIXED_COMPATIBILITY - // Else Unknown + // 2. walk patch to 0 -> if found something use that + // 3. walk minor to 0 (including patches) -> at least MIXED_COMPATIBILITY or worse + // else Unknown val parts = version.split(".").map { it.toIntOrNull() ?: return TandoorServerVersionCompatibilityState.UNKNOWN } if (parts.size != 3) return TandoorServerVersionCompatibilityState.UNKNOWN