Run the linter the app build already had, and fix what it found

`./gradlew :androidApp:lintDebug` had apparently never been run. It
reported 11 errors, 8 warnings and 3 hints, and one of the errors was a
crash: UsageScreen formats its reset countdown with java.time --
OffsetDateTime and Duration, both API 26 -- while minSdk is 24 and core
library desugaring was off. On 24 and 25 that is a NoClassDefFoundError,
and the `catch (_: Exception)` around the code does not stop an Error, so
the usage screen would have died rather than degraded.

Core library desugaring is now on, with desugar_jdk_libs 2.1.5. Verified
by looking in the built APK rather than trusting the flag: it now carries
Lj$/time/Duration and Lj$/time/OffsetDateTime, the backported classes the
call sites are rewritten against.

The rest: the two KTX suggestions taken (SharedPreferences.edit's block
form, which cannot forget its apply(), and String.toUri), the three
autoboxing hints taken (mutableIntStateOf/mutableLongStateOf), and
androidx.core:core-ktx declared at 1.19.0 rather than inherited through
activity-compose, since this code now calls its extensions directly.

Two are suppressed with their reasons, both scoped to the one element.
DiscouragedApi on the scanner's screenOrientation, which is not a pin but
the removal of the library's landscape lock. MissingApplicationIcon,
because there is no icon yet and that is a decision to make later, not an
oversight -- an app with no icon is obvious to anyone who opens a
launcher, so the warning tells nobody here anything.

0 errors now. The 4 warnings left are one thing: Compose Multiplatform
1.12.0 is out and this is on 1.11.1. That is an upgrade to decide on, not
a defect, so it is left for its own change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-28 04:00:57 -04:00
1 parent 78c054918f
commit dde5042b12
8 files changed
+54 -13

No files matched your search

@@ -3,6 +3,7 @@ package com.example.aiapp
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -32,7 +33,7 @@ fun AppRoot(settingsVersion: Int) {
var screen by remember { mutableStateOf<Screen>(Screen.SessionList) }
// Bumped whenever another screen changes something the list shows, so
// returning to it refetches instead of showing a stale list.
var reloadToken by remember { mutableStateOf(0) }
var reloadToken by remember { mutableIntStateOf(0) }
val current = settings
if (current == null) {
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -24,7 +25,7 @@ import androidx.core.view.WindowCompat
class MainActivity : ComponentActivity() {
// Bumped whenever enrollment lands via an aiapp:// intent so the
// composition below re-reads the stored settings.
private var settingsVersion by mutableStateOf(0)
private var settingsVersion by mutableIntStateOf(0)
// Registered up front since permission launchers must be registered
// before the activity reaches STARTED.
@@ -2,6 +2,7 @@ package com.example.aiapp
import android.content.Context
import android.net.Uri
import androidx.core.content.edit
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
@@ -37,12 +38,11 @@ fun loadServerSettings(context: Context): ServerSettings? {
}
fun saveServerSettings(context: Context, settings: ServerSettings) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putString(KEY_HOST, settings.host)
.putInt(KEY_PORT, settings.port)
.putString(KEY_TOKEN, seal(settings.token))
.apply()
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
putString(KEY_HOST, settings.host)
putInt(KEY_PORT, settings.port)
putString(KEY_TOKEN, seal(settings.token))
}
}
/**
@@ -33,6 +33,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -124,7 +125,7 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
val scope = rememberCoroutineScope()
var items by remember { mutableStateOf(listOf<TranscriptItem>()) }
var status by remember { mutableStateOf(summary.status) }
var totalTokens by remember { mutableStateOf(0L) }
var totalTokens by remember { mutableLongStateOf(0L) }
var streamError by remember { mutableStateOf<String?>(null) }
var actionError by remember { mutableStateOf<String?>(null) }
var input by remember { mutableStateOf("") }
@@ -25,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import com.google.zxing.client.android.Intents
@@ -56,7 +57,7 @@ fun SettingsScreen(
// Null contents means the user backed out of the scanner -- not an
// error, so nothing to report.
val contents = result.contents ?: return@rememberLauncherForActivityResult
val settings = parseEnrollmentUri(Uri.parse(contents))
val settings = parseEnrollmentUri(contents.toUri())
if (settings == null) {
error = "Not a valid enrollment code"
} else {