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:
1 parent
78c054918f
commit
dde5042b12
8 files changed
+54
-13
No files matched your search
@@ -106,6 +106,11 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
// minSdk is 24 and UsageScreen formats its countdown with
|
||||
// java.time, which the platform only has from 26. Without this it
|
||||
// is a NoClassDefFoundError on 24 and 25 -- an Error, so the
|
||||
// catch around that code does not stop it.
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +126,15 @@ androidComponents {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Not a library this code calls: it is what `isCoreLibraryDesugaring
|
||||
// Enabled` above rewrites java.time against, so API 24 and 25 have it.
|
||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||
|
||||
implementation(libs.compose.runtime)
|
||||
implementation(libs.compose.foundation)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.zxing.embedded)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Android 17 (API 37) made Local Network Protection mandatory: an app
|
||||
targeting 37+ needs this runtime permission to reach *any* local
|
||||
@@ -9,10 +10,17 @@
|
||||
dev-updater's manifest for the full story. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
|
||||
|
||||
<!-- tools:ignore MissingApplicationIcon: there is no icon yet, and
|
||||
that is a decision rather than an oversight. An app with no icon
|
||||
of its own is obvious to anyone who opens a launcher, so the
|
||||
warning tells nobody here anything they cannot already see, and
|
||||
the fix is a judgement about how this app should look. Drop this
|
||||
suppression when a real icon lands. -->
|
||||
<application
|
||||
android:label="AI Sessions"
|
||||
android:allowBackup="true"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar"
|
||||
tools:ignore="MissingApplicationIcon">
|
||||
<!-- adjustResize (not the system's default pan): the layout handles
|
||||
the keyboard itself via imePadding(), so the window must resize
|
||||
rather than slide the top bar off screen. -->
|
||||
@@ -46,12 +54,21 @@
|
||||
code being scanned is usually on a monitor in front of someone
|
||||
holding the phone upright. zxing_CaptureTheme is the library's
|
||||
own fullscreen theme, which is all the activity needs. -->
|
||||
<!-- tools:ignore DiscouragedApi: lint flags every fixed
|
||||
screenOrientation, because Android 16 ignores most of them.
|
||||
This one is not a pin but its removal. fullSensor is what
|
||||
drops the library's landscape lock, so the activity follows
|
||||
the phone rather than asking anyone to turn it, and where the
|
||||
platform ignores the attribute the behaviour is the one this
|
||||
asked for anyway. Scoped to this activity, so a genuine pin
|
||||
elsewhere would still be reported. -->
|
||||
<activity
|
||||
android:name=".EnrollmentScanActivity"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:stateNotNeeded="true"
|
||||
android:theme="@style/zxing_CaptureTheme"
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,15 +8,25 @@ compose-multiplatform = "1.11.1"
|
||||
# material3 ships on its own release train, separate from the CMP version.
|
||||
compose-material3 = "1.9.0"
|
||||
androidx-activityCompose = "1.13.0"
|
||||
# Declared rather than inherited because this
|
||||
# code now calls its extensions directly (SharedPreferences.edit, String
|
||||
# .toUri), and a transitive it merely inherited could change under it.
|
||||
androidx-core-ktx = "1.19.0"
|
||||
zxing-embedded = "4.3.0"
|
||||
# Backports java.time (and more) to API 24, which UsageScreen needs: its
|
||||
# reset countdown is OffsetDateTime/Duration, both API 26. Checked
|
||||
# 2026-08-28 against Google Maven.
|
||||
desugar-jdk-libs = "2.1.5"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }
|
||||
# In-app QR scanner: a ready-made scanning Activity (camera preview, runtime
|
||||
# permission prompt, flashlight toggle) reached through the AndroidX Activity
|
||||
# Result API (ScanContract, added in 4.3.0). Fully offline -- no Play
|
||||
# Services / ML Kit model download involved.
|
||||
zxing-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxing-embedded" }
|
||||
desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-jdk-libs" }
|
||||
# Declared directly rather than through the plugin's `compose.*` accessors,
|
||||
# which are deprecated as of CMP 1.11.
|
||||
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "compose-multiplatform" }
|
||||
|
||||
Reference in new issue
Block a user