Compare commits

..

4 Commits

Author SHA1 Message Date
vesp
cbe363066c Use BuildConfig for version display in About section
Replace hardcoded version string with BuildConfig.VERSION_NAME so
the About screen always reflects the version from build.gradle.kts.
2026-02-16 10:55:29 +01:00
vesp
974c529830 Fix connection state flapping and bump version to 1.1
Add consecutive failure threshold (3 polls) before marking as
disconnected, preventing UI flapping caused by intermittent ESP32
errors. Bump versionCode to 2 and versionName to 1.1.
2026-02-16 10:42:19 +01:00
vesp
b45d4ab473 Update CLAUDE.md with correct JDK path for release builds 2026-02-16 10:30:20 +01:00
vesp
2f64d509d0 Add release signing config for Google Play upload
Read signing credentials from keystore.properties (gitignored) and
configure the release build type to use the upload keystore.
2026-02-16 10:29:44 +01:00
5 changed files with 42 additions and 12 deletions

2
.gitignore vendored
View File

@ -10,3 +10,5 @@
.externalNativeBuild
.cxx
local.properties
keystore.properties
*.jks

View File

@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
JAVA_HOME=/path/to/jdk-17 ./gradlew assembleDebug
# Build release APK
JAVA_HOME=/path/to/jdk-17 ./gradlew assembleRelease
JAVA_HOME=/home/pavelb/Stažené/jdk-17.0.15+6 ./gradlew assembleRelease
# Run unit tests
./gradlew test

View File

@ -7,16 +7,33 @@ plugins {
alias(libs.plugins.ksp)
}
import java.util.Properties
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(keystorePropertiesFile.inputStream())
}
android {
namespace = "cz.bugsy.karemote"
compileSdk = 36
signingConfigs {
create("release") {
storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
}
}
defaultConfig {
applicationId = "cz.bugsy.karemote"
minSdk = 33
targetSdk = 36
versionCode = 1
versionName = "1.0"
versionCode = 2
versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -31,6 +48,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
}
}
compileOptions {
@ -42,6 +60,7 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
packaging {
resources {

View File

@ -65,6 +65,7 @@ class MainViewModel @Inject constructor(
private var pollingJob: Job? = null
private var lastStationName: String? = null
private var connectionFailedSince: Long? = null
private var consecutiveFailures: Int = 0
private var volumeJob: Job? = null
private var lastVolumeSentTime: Long = 0
private var pendingVolume: Int? = null
@ -72,6 +73,7 @@ class MainViewModel @Inject constructor(
companion object {
private const val ERROR_DISPLAY_DELAY_MS = 10_000L // 10 seconds
private const val VOLUME_THROTTLE_MS = 300L // Throttle volume commands
private const val DISCONNECT_THRESHOLD = 3 // Consecutive failures before marking disconnected
}
init {
@ -81,6 +83,7 @@ class MainViewModel @Inject constructor(
if (settings.serverAddress.isNotBlank()) {
// Reset failure tracking when server address changes
connectionFailedSince = null
consecutiveFailures = 0
_uiState.update { it.copy(isConnecting = true, errorMessage = null) }
startPolling()
} else {
@ -115,6 +118,7 @@ class MainViewModel @Inject constructor(
.onSuccess { status ->
// Connection successful - reset failure tracking
connectionFailedSince = null
consecutiveFailures = 0
_uiState.update {
it.copy(
isConnected = true,
@ -131,6 +135,7 @@ class MainViewModel @Inject constructor(
}
}
.onFailure { error ->
consecutiveFailures++
val now = System.currentTimeMillis()
// Start tracking failure time if this is the first failure
@ -141,14 +146,18 @@ class MainViewModel @Inject constructor(
val failureDuration = now - (connectionFailedSince ?: now)
val shouldShowError = failureDuration >= ERROR_DISPLAY_DELAY_MS
_uiState.update {
it.copy(
isConnected = false,
isConnecting = !shouldShowError,
errorMessage = if (shouldShowError) {
"Connection failed: ${error.localizedMessage}"
} else null
)
// Only mark as disconnected after multiple consecutive failures
// to avoid flapping when ESP32 has intermittent errors
if (consecutiveFailures >= DISCONNECT_THRESHOLD) {
_uiState.update {
it.copy(
isConnected = false,
isConnecting = !shouldShowError,
errorMessage = if (shouldShowError) {
"Connection failed: ${error.localizedMessage}"
} else null
)
}
}
}
}

View File

@ -206,7 +206,7 @@ fun SettingsScreen(
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Version 1.0",
text = "Version ${cz.bugsy.karemote.BuildConfig.VERSION_NAME}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)