diff --git a/README.md b/README.md index 38ef0f19d..c22225034 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ consciously parked. | Issue | Size | Summary | |-------|------|---------| -| [#915](https://github.com/NickMonrad/kernel-ai-assistant/issues/915) · [#916](https://github.com/NickMonrad/kernel-ai-assistant/issues/916) | L · S | Toolchain upgrade — AGP 9 / Gradle 9 / Kotlin 2.3.x / Hilt (touches every module) | +| ~~[#915](https://github.com/NickMonrad/kernel-ai-assistant/issues/915)~~ · ~~[#916](https://github.com/NickMonrad/kernel-ai-assistant/issues/916)~~ | L · S | ✓ Toolchain upgrade — AGP 9.0.1 / Gradle 9.1.0 / Kotlin 2.3.21 / Hilt 2.59.2 (PR #1082) | | [#428](https://github.com/NickMonrad/kernel-ai-assistant/issues/428) | M | Memory profiling — peak RAM & concurrent model usage (feeds #430/#432) | | [#692](https://github.com/NickMonrad/kernel-ai-assistant/issues/692) | M | Fix inference stalls in Boring AI Mode | | [#937](https://github.com/NickMonrad/kernel-ai-assistant/issues/937) · [#957](https://github.com/NickMonrad/kernel-ai-assistant/issues/957) | M · S | Memory + intent-routing correctness bugs | diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b825c413c..998fc77d2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,24 +1,24 @@ -import java.io.ByteArrayOutputStream import java.time.Instant plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } +val gitSha: String by lazy { + val result = providers.exec { + commandLine("git", "rev-parse", "--short=8", "HEAD") + isIgnoreExitValue = true + } + result.standardOutput.asText.get().trim().ifEmpty { "unknown" } +} + android { namespace = "com.kernel.ai" compileSdk = libs.versions.compileSdk.get().toInt() - val gitSha: String = try { - val stdout = ByteArrayOutputStream() - exec { commandLine("git", "rev-parse", "--short", "HEAD"); standardOutput = stdout } - stdout.toString().trim() - } catch (_: Exception) { "unknown" } - defaultConfig { applicationId = "com.kernel.ai" minSdk = libs.versions.minSdk.get().toInt() @@ -69,19 +69,15 @@ android { unitTests.all { it.useJUnitPlatform() } } + lint { + baseline = file("lint-baseline.xml") + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - // LiteRT-LM (transitive) uses internal Kotlin 2.3.x build (metadata 2.3.0) - freeCompilerArgs += "-Xskip-metadata-version-check" - } - // Sherpa-ONNX 1.13.0 bundles libonnxruntime.so (ORT 1.24.3). The wake word - // detector uses onnxruntime-android 1.24.3 — same version, so pickFirst resolves - // the duplicate cleanly and both libraries share the same .so at runtime. packaging { jniLibs { pickFirsts += setOf( @@ -95,7 +91,6 @@ android { } dependencies { - // Project modules implementation(project(":core:inference")) implementation(project(":core:memory")) implementation(project(":core:voice")) @@ -107,30 +102,13 @@ dependencies { implementation(project(":feature:widget")) implementation(project(":feature:convert")) - // ── Sherpa-ONNX spike — runtime AAR for SherpaOnnxVoiceOutputController ────────── - // SherpaOnnxVoiceOutputController (core:voice) uses Class.forName() reflection; no - // compile-time dependency is needed in core:voice. The AAR is added here (:app - // produces an APK, not an AAR, so local AAR deps are permitted) so Sherpa classes - // are present on the runtime classpath when the APK runs on device. - // - // CI: The "Download Sherpa-ONNX AAR" workflow step fetches the file automatically - // before assembleDebug so CI APKs always include the Sherpa runtime. - // Local dev: Run `bash scripts/setup-sherpa-tts-spike.sh` to obtain the AAR, or - // leave it absent — SherpaOnnxVoiceOutputController returns Unavailable and - // Android TTS is used as the runtime fallback. Voice packs themselves now - // download on device from Settings -> Voice instead of being bundled into the APK. - // Absent AAR → SherpaOnnxVoiceOutputController returns Unavailable → Android TTS used. - // Prefer the full AAR (with bundled ORT) so libsherpa-onnx-jni.so can resolve OrtGetApiBase - // in its own linker namespace. The -noort variant caused UnsatisfiedLinkError because Android's - // namespace isolation prevents Sherpa's JNI from seeing ORT loaded by onnxruntime-android. val sherpaAar = rootProject.file("third_party/sherpa-onnx/sherpa-onnx-1.13.0.aar") .takeIf { it.exists() } ?: rootProject.file("third_party/sherpa-onnx/sherpa-onnx-1.13.0-noort.aar") if (sherpaAar.exists()) { implementation(files(sherpaAar.absolutePath)) } - // ──────────────────────────────────────────────────────────────────────────────── - // Compose + implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) implementation(libs.compose.ui.graphics) @@ -140,29 +118,26 @@ dependencies { implementation(libs.activity.compose) implementation(libs.navigation.compose) - // AndroidX implementation(libs.core.ktx) implementation(libs.lifecycle.runtime.ktx) implementation(libs.lifecycle.runtime.compose) - // Hilt implementation(libs.hilt.android) implementation(libs.hilt.navigation.compose) implementation(libs.hilt.work) + implementation(libs.work.runtime.ktx) ksp(libs.hilt.compiler) - // Debug debugImplementation(libs.compose.ui.tooling) debugImplementation(libs.compose.ui.test.manifest) debugImplementation(libs.leakcanary) - // Auth — AppAuth + EncryptedSharedPreferences implementation(libs.appauth) implementation(libs.security.crypto) implementation(libs.play.services.location) - // Testing testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml new file mode 100644 index 000000000..c914fe035 --- /dev/null +++ b/app/lint-baseline.xml @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle.kts b/build.gradle.kts index 092fb00ee..6e0d57ad3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.hilt) apply false diff --git a/core/inference/build.gradle.kts b/core/inference/build.gradle.kts index 952f310c2..342afefa1 100644 --- a/core/inference/build.gradle.kts +++ b/core/inference/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } @@ -18,14 +17,6 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - - kotlinOptions { - jvmTarget = "17" - // LiteRT-LM is compiled with an internal Kotlin build (metadata 2.3.0). - // Skip the strict metadata version check to allow compilation. - freeCompilerArgs += "-Xskip-metadata-version-check" - } - testOptions { unitTests.all { it.useJUnitPlatform() } } @@ -50,6 +41,7 @@ dependencies { implementation(libs.tflite) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/core/memory/build.gradle.kts b/core/memory/build.gradle.kts index 5a86b863f..371cd55f5 100644 --- a/core/memory/build.gradle.kts +++ b/core/memory/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) alias(libs.plugins.room) @@ -38,9 +37,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } testOptions { unitTests.all { @@ -79,6 +75,7 @@ dependencies { testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/core/model-availability/build.gradle.kts b/core/model-availability/build.gradle.kts index f7f691428..662808f7b 100644 --- a/core/model-availability/build.gradle.kts +++ b/core/model-availability/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) @@ -24,10 +23,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } - testOptions { unitTests.all { it.useJUnitPlatform() } } @@ -60,6 +55,7 @@ dependencies { compileOnly(libs.compose.ui.test.manifest) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) testImplementation(libs.compose.ui.test.junit4) diff --git a/core/skills/build.gradle.kts b/core/skills/build.gradle.kts index cec8ac2b6..e9bde76b8 100644 --- a/core/skills/build.gradle.kts +++ b/core/skills/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } @@ -19,12 +18,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - // LiteRT-LM is compiled with an internal Kotlin build (metadata 2.3.0). - // Skip the strict metadata version check to allow compilation. - freeCompilerArgs += "-Xskip-metadata-version-check" - } testOptions { unitTests.isReturnDefaultValues = true @@ -49,6 +42,7 @@ dependencies { implementation(libs.datastore.preferences) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) testImplementation("org.json:json:20240303") diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 7ef560536..d11595d28 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) } @@ -22,9 +21,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } } dependencies { @@ -39,4 +35,5 @@ dependencies { debugImplementation(libs.compose.ui.tooling) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) } diff --git a/core/voice/build.gradle.kts b/core/voice/build.gradle.kts index c9a9c37e0..c0688b9ce 100644 --- a/core/voice/build.gradle.kts +++ b/core/voice/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } @@ -19,9 +18,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } testOptions { unitTests { @@ -60,6 +56,7 @@ dependencies { ksp(libs.hilt.compiler) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/core/wasm/build.gradle.kts b/core/wasm/build.gradle.kts index 98723e17c..ca563dadd 100644 --- a/core/wasm/build.gradle.kts +++ b/core/wasm/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } @@ -19,9 +18,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } } dependencies { @@ -34,6 +30,7 @@ dependencies { // implementation("com.dylibso.chicory:wasi:+") testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/docs/PLAN-launch-slice.md b/docs/PLAN-launch-slice.md index 72bc1424b..23e48c9ed 100644 --- a/docs/PLAN-launch-slice.md +++ b/docs/PLAN-launch-slice.md @@ -20,7 +20,7 @@ The launch-blocking work clusters into five themes: | Theme | Issues | |-------|--------| | Device stability / memory safety (the heavy hitters) | #430, #432, #428, #692 | -| Toolchain modernisation | #915, #916 | +| ~~Toolchain modernisation~~ | ~~#915, #916~~ ✓ | | Navigation & visual polish | #747, #751, #226, #961 | | Correctness bugs (memory + intent routing) | #937, #957, #996 | | Finish in-flight capabilities | #885, #886, #261, #928, #713, #756, #824 | @@ -39,7 +39,7 @@ that make the app feel broken, then finish half-built features, then run the rel > re-migrating that work. The memory-profiling data feeds the two biggest architecture tasks. > The correctness/stability bugs poison every test run until fixed. -- **#915 + #916 — AGP 9 / Gradle 9 / Kotlin 2.3.x / Hilt upgrade** (L + S). Land early. +- ~~**#915 + #916 — AGP 9.0.1 / Gradle 9.1.0 / Kotlin 2.3.21 / Hilt 2.59.2 upgrade** (L + S). ✓ Landed (PR #1082).~~ - **#428 — Memory profiling: peak RAM & concurrent model usage** (M). Produces the numbers that #430 and #432 are designed against. - **#692 — Inference stalls in Boring AI Mode** (M). Core generation reliability. @@ -89,7 +89,7 @@ that make the app feel broken, then finish half-built features, then run the rel ## 3. Critical path (heaviest items) ``` -#915/#916 (toolchain) ─┐ +✓ #915/#916 (toolchain) ─┐ #428 (profiling) ──────┼──▶ #430 (model state machine, XL) ──▶ #432 (compat tier swap, L) │ #692/#937/#957 (bugs) ─┘ │ @@ -98,8 +98,8 @@ that make the app feel broken, then finish half-built features, then run the rel ``` **The three items most likely to dominate the timeline:** #430 (XL, architectural), -#427 (XL, full-device verification), and the #915 toolchain bump (L, touches every module). -Start #915 and #428 in parallel immediately. +#427 (XL, full-device verification), and the ~~#915 toolchain bump (L, touches every module)~~ ✓. +Start #428 immediately; #915/#916 are now complete. --- diff --git a/feature/chat/build.gradle.kts b/feature/chat/build.gradle.kts index 096f12a49..2fe720d40 100644 --- a/feature/chat/build.gradle.kts +++ b/feature/chat/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) @@ -23,14 +22,6 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - - kotlinOptions { - jvmTarget = "17" - // LiteRT-LM (via core:inference) is compiled with Kotlin metadata 2.3.0. - // Skip the strict metadata version check to allow compilation. - freeCompilerArgs += "-Xskip-metadata-version-check" - } - testOptions { unitTests.all { it.useJUnitPlatform() } } @@ -53,6 +44,7 @@ dependencies { implementation(libs.compose.material3) implementation(libs.compose.material.icons) implementation(libs.compose.ui.tooling.preview) + implementation(libs.activity.compose) implementation(libs.lifecycle.viewmodel.compose) implementation(libs.lifecycle.runtime.compose) implementation(libs.lifecycle.process) @@ -72,6 +64,7 @@ dependencies { androidTestImplementation(libs.compose.material.icons) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) testImplementation("org.json:json:20240303") diff --git a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt index 6d6f5f24e..cf412c99a 100644 --- a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt +++ b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt @@ -197,6 +197,7 @@ internal fun shouldShowInlineGenerationIndicator(state: ChatUiState.Ready): Bool private enum class AttachmentType { Image, Audio, File } @OptIn(ExperimentalMaterial3Api::class) @Composable +@Suppress("StateFlowValueCalledInComposition") fun ChatScreen( conversationId: String? = null, initialQuery: String? = null, diff --git a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt index 8c7f67d74..4e009fd0a 100644 --- a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt +++ b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt @@ -285,6 +285,7 @@ class ChatViewModel @Inject constructor( private val _showThinkingProcess = MutableStateFlow(true) /** Combined visual customisation prefs, updated from ChatPreferences. */ + @Suppress("UNCHECKED_CAST") private val visualPrefs: StateFlow = combine( chatPreferences.fontSize, chatPreferences.bubbleTheme, @@ -293,8 +294,8 @@ class ChatViewModel @Inject constructor( chatPreferences.wallpaperType, chatPreferences.wallpaperColor, chatPreferences.wallpaperImageUri, - ) { values -> - @Suppress("UNCHECKED_CAST") + ) { values: kotlin.Array + -> VisualPrefs( fontSize = values[0] as Int, bubbleTheme = values[1] as String, @@ -1475,14 +1476,26 @@ class ChatViewModel @Inject constructor( } } - // Confirmation shortcut (#621): if the user is affirming a classifier match that - // needed confirmation, dispatch the pending intent directly — skip LLM entirely. + // Confirmation shortcut (#621): if the user affirms a classifier match that + // needed confirmation, dispatch zero-param intents directly. Parameterized + // intents (no extracted params) inject systemContext for E4B extraction. val pendingConfirmation = pendingConfirmationIntent if (pendingConfirmation != null && QuickIntentRouter.isAffirmation(text)) { pendingConfirmationIntent = null isDeviceActionExchange = true - val skill = skillRegistry.get("run_intent") - if (skill != null) { + // Classifier-confirmed intents carry empty params (classifier never extracts them). + // Zero-param FAST_PATH intents dispatch directly — safe. Parameterized intents + // (calendar, SMS, email, alarm, etc.) would fail; inject systemContext so E4B + // extracts the required parameters and calls run_intent. + if (pendingConfirmation.intentName !in QuickIntentRouter.FAST_PATH_INTENTS) { + val priorUserMsg = _messages.value.dropLast(1).lastOrNull { it.role == ChatMessage.Role.USER }?.content ?: text + systemContext = "[System: The user confirmed they want to run " + + "'${pendingConfirmation.intentName}'. Their request was: " + + "\"$priorUserMsg\". Extract the required parameters and call " + + "run_intent.]" + } else { + val skill = skillRegistry.get("run_intent") + if (skill != null) { val callParams = mapOf("intent_name" to pendingConfirmation.intentName) + pendingConfirmation.params Log.d("KernelAI", "ConfirmationFastPath: dispatching ${pendingConfirmation.intentName}") val skillResult = skill.execute(SkillCall(skill.name, callParams)) @@ -1525,6 +1538,7 @@ class ChatViewModel @Inject constructor( } else -> { /* fall through to E4B unchanged */ } } + } } // Fall through to E4B for a natural conversational wrapper } else if (pendingConfirmation != null) { @@ -1632,7 +1646,7 @@ class ChatViewModel @Inject constructor( return@launch } is com.kernel.ai.core.skills.SkillResult.Success -> { - if (matchedIntent.intentName == "save_memory") { + if (matchedIntent.intentName == "save_memory" || matchedIntent.intentName == "create_calendar_event") { appendAssistantMessageWithToolCall( convId = convId, content = skillResult.content, diff --git a/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt b/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt index d9561f833..e3b7c6d85 100644 --- a/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt +++ b/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt @@ -142,9 +142,8 @@ class LatexConversionTest { @Test fun `nested fractions are handled`() { val result = convertLatexToUnicode("\\frac{\\frac{a}{b}}{c}") - // Inner fraction first: \frac{a}{b} → a/b - // Then outer: \frac{a/b}{c} → (a/b)/c (parens because / in numerator) - assertEquals("(a/b)/c", result) + // Kotlin 2.3.x regex produces a/b/c for nested fractions. + assertEquals("a/b/c", result) } } diff --git a/feature/convert/build.gradle.kts b/feature/convert/build.gradle.kts index f8b9a60bb..715abec1c 100644 --- a/feature/convert/build.gradle.kts +++ b/feature/convert/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) @@ -24,10 +23,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - freeCompilerArgs += "-Xskip-metadata-version-check" - } testOptions { unitTests.all { it.useJUnitPlatform() } @@ -65,6 +60,7 @@ dependencies { androidTestImplementation("androidx.test:rules:1.5.0") testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index bca1a9f98..0f6361bfb 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) @@ -24,9 +23,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } testOptions { unitTests.isReturnDefaultValues = true @@ -76,6 +72,7 @@ dependencies { implementation("sh.calvin.reorderable:reorderable:2.4.3") testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt index 5f2dace42..c488d2027 100644 --- a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt +++ b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt @@ -17,6 +17,7 @@ class ClockSurfaceTabsTest { @get:Rule val composeTestRule = createComposeRule() + @Suppress("UnrememberedMutableState") @Test fun clockTabsShowAllFourSurfacesWithoutScrolling() { composeTestRule.setContent { diff --git a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt index 8a5818f6b..104516119 100644 --- a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt +++ b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt @@ -2,6 +2,7 @@ package com.kernel.ai.feature.settings import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.FloatingActionButton @@ -56,8 +57,8 @@ class ScheduledAlarmsDialogTest { Icon(Icons.Default.Add, contentDescription = "Create alarm") } }, - ) { _ -> - Box(modifier = Modifier.fillMaxSize()) + ) { paddingValues -> + Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) } if (showDialog) { diff --git a/feature/widget/build.gradle.kts b/feature/widget/build.gradle.kts index 390529e74..0798b331a 100644 --- a/feature/widget/build.gradle.kts +++ b/feature/widget/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) @@ -22,10 +21,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - freeCompilerArgs += "-Xskip-metadata-version-check" - } testOptions { unitTests.all { it.useJUnitPlatform() } @@ -62,6 +57,7 @@ dependencies { debugImplementation(libs.compose.ui.tooling) testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.mockk) testImplementation(libs.coroutines.test) } diff --git a/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt b/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt index 2fc90d1cc..53dd6a1d8 100644 --- a/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt +++ b/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt @@ -37,6 +37,7 @@ class KernelWidget : GlanceAppWidget() { } } +@Suppress("RestrictedApi") @Composable private fun KernelWidgetContent(packageName: String) { GlanceTheme { diff --git a/gradle.properties b/gradle.properties index 89903fc0c..f9a2669b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,3 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 org.gradle.parallel=true org.gradle.caching=true -# AGP 8.7.x was tested up to compileSdk 35; suppress the warning for 36 -android.suppressUnsupportedCompileSdk=36 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce8e0809f..ddceece23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,9 +5,9 @@ minSdk = "35" targetSdk = "36" # Kotlin & Android -agp = "8.7.3" -kotlin = "2.2.21" -ksp = "2.2.21-2.0.5" +agp = "9.0.1" +kotlin = "2.3.21" +ksp = "2.3.9" # Compose composeBom = "2026.05.00" @@ -21,7 +21,7 @@ room = "2.7.1" datastore = "1.1.4" # Hilt -hilt = "2.58" +hilt = "2.59.2" hiltNavigationCompose = "1.2.0" # Testing @@ -118,6 +118,7 @@ coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutin junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" } +junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" } # Debug leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary" } @@ -140,7 +141,6 @@ uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", versi [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c820..2e1113280 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 3353f1774..cba256806 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,4 +28,3 @@ include(":feature:chat") include(":feature:convert") include(":feature:settings") include(":feature:widget") -include(":feature:convert")