diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3d5209a..6b4ff4c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,8 +32,8 @@ android { applicationId = "cz.bugsy.karemote" minSdk = 33 targetSdk = 36 - versionCode = 1 - versionName = "1.0" + versionCode = 2 + versionName = "1.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainViewModel.kt b/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainViewModel.kt index a59532a..f68479f 100644 --- a/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainViewModel.kt @@ -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 + ) + } } } }