git.iko.soy/opencode-android
on master help sign in
Update all dependencies to latest: AGP 9.2, Kotlin 2.4, Gradle 9.6.1, compileSdk 37, Coil 3, Compose BOM 2026.06, markdown-renderer 0.43
2781a1aiko
AGENTS.md+5−5
@@ -12,12 +12,12 @@ an adaptive single-pane / two-pane Material 3 UI.
12
12
13- Application id / namespace: `soy.iko.opencode`
13- Application id / namespace: `soy.iko.opencode`
14- Package root: `app/src/main/java/soy/iko/opencode`
14- Package root: `app/src/main/java/soy/iko/opencode`
15−- minSdk 26, compileSdk/targetSdk 35, JDK 17, Kotlin 2.0.x, AGP 8.7.x
15+- minSdk 26, compileSdk/targetSdk 37, JDK 17, Kotlin 2.2.x, AGP 9.2.x
16- Single Gradle module (`:app`); no dynamic feature modules.
16- Single Gradle module (`:app`); no dynamic feature modules.
17
17
18## Build & environment
18## Build & environment
19
19
20−A Nix flake provides the exact toolchain (JDK 17, Gradle, Android SDK 35, and a
20+A Nix flake provides the exact toolchain (JDK 17, Gradle, Android SDK 37, and a
21Nix-built `aapt2` override because Gradle's bundled aapt2 segfaults on NixOS).
21Nix-built `aapt2` override because Gradle's bundled aapt2 segfaults on NixOS).
22
22
23```bash
23```bash
@@ -26,8 +26,8 @@ nix develop # enter the dev shell (sets ANDROID_HOME, JAVA_HO
26./gradlew assembleRelease # R8-minified + resource-shrunk; signed only when OPENCODE_STORE_* env vars are set
26./gradlew assembleRelease # R8-minified + resource-shrunk; signed only when OPENCODE_STORE_* env vars are set
27```
27```
28
28
29−Outside Nix, you need JDK 17 and the Android SDK (platform 35, build-tools
29+Outside Nix, you need JDK 17 and the Android SDK (platform 37, build-tools
30−35.0.0) installed manually; `ANDROID_HOME` / `ANDROID_SDK_ROOT` must point at it.
30+37.0.0) installed manually; `ANDROID_HOME` / `ANDROID_SDK_ROOT` must point at it.
31
31
32Install on a connected device/emulator:
32Install on a connected device/emulator:
33
33
@@ -194,4 +194,4 @@ util Coroutines.kt (runCatchingCancellable, safeExceptionSummary)
194
194
195`renovate.json` keeps dependencies current: Kotlin+AGP and Compose are grouped;
195`renovate.json` keeps dependencies current: Kotlin+AGP and Compose are grouped;
196Ktor is grouped; AndroidX majors are held for manual review (pinned to
196Ktor is grouped; AndroidX majors are held for manual review (pinned to
197−compileSdk 35). Dependency upgrades land via Renovate PRs, not manual bumps.
197+compileSdk 37). Dependency upgrades land via Renovate PRs, not manual bumps.
README.md+1−1
@@ -73,7 +73,7 @@ Source map: `data/network` (Ktor, SSE, JSON), `data/model` (wire models), `data/
73## Build
73## Build
74
74
75```bash
75```bash
76−nix develop # JDK 17, Gradle 8.14.4, Android SDK 35, aapt2 override
76+nix develop # JDK 17, Gradle 9.6.1, Android SDK 37, aapt2 override
77./gradlew assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
77./gradlew assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
78./gradlew assembleRelease # R8-minified + resource-shrunk release APK
78./gradlew assembleRelease # R8-minified + resource-shrunk release APK
79./gradlew testDebugUnitTest # JVM tests for the JSON/serialization layer
79./gradlew testDebugUnitTest # JVM tests for the JSON/serialization layer
app/build.gradle.kts+16−5
@@ -1,6 +1,5 @@
1plugins {
1plugins {
2 alias(libs.plugins.android.application)
2 alias(libs.plugins.android.application)
3− alias(libs.plugins.kotlin.android)
4 alias(libs.plugins.kotlin.serialization)
3 alias(libs.plugins.kotlin.serialization)
5 alias(libs.plugins.kotlin.compose)
4 alias(libs.plugins.kotlin.compose)
6 alias(libs.plugins.detekt)
5 alias(libs.plugins.detekt)
@@ -24,13 +23,13 @@ val versionNameOverride: String? = providers.gradleProperty("versionName").orNul
24
23
25android {
24android {
26 namespace = "soy.iko.opencode"
25 namespace = "soy.iko.opencode"
27− compileSdk = 35
26+ compileSdk = 37
28− buildToolsVersion = "35.0.0"
27+ buildToolsVersion = "37.0.0"
29
28
30 defaultConfig {
29 defaultConfig {
31 applicationId = "soy.iko.opencode"
30 applicationId = "soy.iko.opencode"
32 minSdk = 26
31 minSdk = 26
33− targetSdk = 35
32+ targetSdk = 37
34 // Monotonically increasing across builds: derived from the git commit count so
33 // Monotonically increasing across builds: derived from the git commit count so
35 // each release ships a strictly higher versionCode without manual bookkeeping
34 // each release ships a strictly higher versionCode without manual bookkeeping
36 // (and never triggers a "downgrade" install block). Falls back to 1 when the
35 // (and never triggers a "downgrade" install block). Falls back to 1 when the
@@ -90,7 +89,18 @@ android {
90 // ProduceStateDoesNotAssignValue false-positives on valid produceState calls that
89 // ProduceStateDoesNotAssignValue false-positives on valid produceState calls that
91 // assign `value` via a conditional/`withContext` expression (see FileViewScreen's
90 // assign `value` via a conditional/`withContext` expression (see FileViewScreen's
92 // match-index scan), so it's disabled rather than worked around at each call site.
91 // match-index scan), so it's disabled rather than worked around at each call site.
92+ // InvalidFragmentVersionForActivityResult false-positives on FragmentActivity
93+ // (the app doesn't use fragments, but BiometricPrompt requires FragmentActivity).
94+ // LocalContextGetResourceValueCall flags context.getString() inside LaunchedEffect /
95+ // remember lambdas where stringResource() can't be called (not a @Composable scope);
96+ // the check is overly aggressive for valid non-composable coroutine contexts.
97+ disable += setOf(
98+ "GradleDependency",
99+ "AndroidGradlePluginVersion",
93− disable += setOf("GradleDependency", "AndroidGradlePluginVersion", "ProduceStateDoesNotAssignValue")
100+ "ProduceStateDoesNotAssignValue",
101+ "InvalidFragmentVersionForActivityResult",
102+ "LocalContextGetResourceValueCall",
103+ )
94 }
104 }
95
105
96 packaging {
106 packaging {
@@ -140,6 +150,7 @@ dependencies {
140 implementation(libs.androidx.security.crypto)
150 implementation(libs.androidx.security.crypto)
141 implementation(libs.androidx.biometric)
151 implementation(libs.androidx.biometric)
142 implementation(libs.coil.compose)
152 implementation(libs.coil.compose)
153+ implementation(libs.coil.network.okhttp)
143 implementation(libs.markdown.renderer)
154 implementation(libs.markdown.renderer)
144 implementation(libs.markdown.renderer.m3)
155 implementation(libs.markdown.renderer.m3)
145 implementation(libs.zxing.core)
156 implementation(libs.zxing.core)
app/src/main/java/soy/iko/opencode/OpencodeApp.kt+20−10
@@ -1,22 +1,25 @@
1package soy.iko.opencode
1package soy.iko.opencode
2
2
3import android.app.Application
3import android.app.Application
4+import android.content.Context
4−import coil.ImageLoader
5+import coil3.ImageLoader
5−import coil.ImageLoaderFactory
6+import coil3.SingletonImageLoader
6−import coil.disk.DiskCache
7+import coil3.disk.DiskCache
7−import coil.memory.MemoryCache
8+import coil3.memory.MemoryCache
9+import coil3.network.okhttp.OkHttpNetworkFetcherFactory
8import okhttp3.Call
10import okhttp3.Call
9import okhttp3.CertificatePinner
11import okhttp3.CertificatePinner
10import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
12import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
11import okhttp3.OkHttpClient
13import okhttp3.OkHttpClient
12import okhttp3.Request
14import okhttp3.Request
15+import okio.Path.Companion.toOkioPath
13import soy.iko.opencode.data.model.ServerProfile
16import soy.iko.opencode.data.model.ServerProfile
14import soy.iko.opencode.data.network.CertPinUtil
17import soy.iko.opencode.data.network.CertPinUtil
15import soy.iko.opencode.data.repo.CrashLogger
18import soy.iko.opencode.data.repo.CrashLogger
16import soy.iko.opencode.di.AppContainer
19import soy.iko.opencode.di.AppContainer
17import java.util.concurrent.atomic.AtomicBoolean
20import java.util.concurrent.atomic.AtomicBoolean
18
21
19−class OpencodeApp : Application(), ImageLoaderFactory {
22+class OpencodeApp : Application(), SingletonImageLoader.Factory {
20 lateinit var container: AppContainer
23 lateinit var container: AppContainer
21 private set
24 private set
22
25
@@ -43,19 +46,26 @@ class OpencodeApp : Application(), ImageLoaderFactory {
43 // pinning as the REST/SSE client (see HttpClientFactory). Server-hosted images carry the
46 // pinning as the REST/SSE client (see HttpClientFactory). Server-hosted images carry the
44 // server's HTTP Basic auth header (see RemoteImage), so without this they would travel over
47 // server's HTTP Basic auth header (see RemoteImage), so without this they would travel over
45 // an UNPINNED TLS connection, silently defeating the pin the user opted into for that host.
48 // an UNPINNED TLS connection, silently defeating the pin the user opted into for that host.
46− override fun newImageLoader(): ImageLoader = ImageLoader.Builder(this)
49+ override fun newImageLoader(context: Context): ImageLoader = ImageLoader.Builder(context)
47 .memoryCache {
50 .memoryCache {
48− MemoryCache.Builder(this)
51+ MemoryCache.Builder()
49− .maxSizePercent(0.25)
52+ .maxSizePercent(context, 0.25)
50 .build()
53 .build()
51 }
54 }
52 .diskCache {
55 .diskCache {
53 DiskCache.Builder()
56 DiskCache.Builder()
54− .directory(cacheDir.resolve("image_cache"))
57+ .directory(cacheDir.resolve("image_cache").toOkioPath())
55 .maxSizeBytes(50L * 1024 * 1024)
58 .maxSizeBytes(50L * 1024 * 1024)
56 .build()
59 .build()
57 }
60 }
61+ .components {
62+ // Route fetches through a Call.Factory that applies the SAME per-profile certificate
63+ // pinning as the REST/SSE client (see HttpClientFactory). Server-hosted images carry
64+ // the server's HTTP Basic auth header (see RemoteImage), so without this they would
65+ // travel over an UNPINNED TLS connection, silently defeating the pin the user opted
66+ // into for that host.
58− .callFactory { PinnedImageCallFactory(container) }
67+ add(OkHttpNetworkFetcherFactory(callFactory = { PinnedImageCallFactory(container) }))
68+ }
59 .build()
69 .build()
60
70
61 override fun onTerminate() {
71 override fun onTerminate() {
app/src/main/java/soy/iko/opencode/ui/chat/ChatAttachments.kt+1−1
@@ -38,7 +38,7 @@ import androidx.compose.ui.text.style.TextOverflow
38import androidx.compose.ui.unit.dp
38import androidx.compose.ui.unit.dp
39import androidx.compose.ui.window.Dialog
39import androidx.compose.ui.window.Dialog
40import androidx.compose.ui.window.DialogProperties
40import androidx.compose.ui.window.DialogProperties
41−import coil.compose.AsyncImage
41+import coil3.compose.AsyncImage
42import soy.iko.opencode.R
42import soy.iko.opencode.R
43import soy.iko.opencode.data.network.NetworkConfig
43import soy.iko.opencode.data.network.NetworkConfig
44import java.util.Locale
44import java.util.Locale
app/src/main/java/soy/iko/opencode/ui/components/MarkdownText.kt+24−10
@@ -72,6 +72,9 @@ import androidx.compose.ui.text.font.FontFamily
72import androidx.compose.ui.unit.dp
72import androidx.compose.ui.unit.dp
73import kotlinx.coroutines.flow.conflate
73import kotlinx.coroutines.flow.conflate
74import kotlinx.coroutines.delay
74import kotlinx.coroutines.delay
75+import com.mikepenz.markdown.annotator.AnnotatorSettings
76+import com.mikepenz.markdown.annotator.DefaultAnnotatorSettings
77+import com.mikepenz.markdown.annotator.buildMarkdownAnnotatedString
75import com.mikepenz.markdown.compose.LocalMarkdownAnnotator
78import com.mikepenz.markdown.compose.LocalMarkdownAnnotator
76import com.mikepenz.markdown.compose.LocalMarkdownColors
79import com.mikepenz.markdown.compose.LocalMarkdownColors
77import com.mikepenz.markdown.compose.LocalMarkdownTypography
80import com.mikepenz.markdown.compose.LocalMarkdownTypography
@@ -80,7 +83,7 @@ import com.mikepenz.markdown.compose.components.markdownComponents
80import com.mikepenz.markdown.compose.elements.MarkdownText as LibraryMarkdownText
83import com.mikepenz.markdown.compose.elements.MarkdownText as LibraryMarkdownText
81import com.mikepenz.markdown.m3.Markdown
84import com.mikepenz.markdown.m3.Markdown
82import com.mikepenz.markdown.model.DefaultMarkdownAnnotator
85import com.mikepenz.markdown.model.DefaultMarkdownAnnotator
83−import com.mikepenz.markdown.utils.buildMarkdownAnnotatedString
86+import com.mikepenz.markdown.model.DefaultMarkdownAnnotatorConfig
84import org.intellij.markdown.MarkdownElementTypes
87import org.intellij.markdown.MarkdownElementTypes
85import org.intellij.markdown.ast.ASTNode
88import org.intellij.markdown.ast.ASTNode
86import org.intellij.markdown.ast.getTextInNode
89import org.intellij.markdown.ast.getTextInNode
@@ -217,7 +220,7 @@ private fun MarkdownBody(
217 // rather than extracted to a helper to keep this file under detekt's TooManyFunctions threshold.
220 // rather than extracted to a helper to keep this file under detekt's TooManyFunctions threshold.
218 val inlineCodeAnnotator = remember(baseAnnotator, inlineCodeTypography, inlineCodeColors) {
221 val inlineCodeAnnotator = remember(baseAnnotator, inlineCodeTypography, inlineCodeColors) {
219 val codeStyle = inlineCodeTypography.inlineCode.copy(
222 val codeStyle = inlineCodeTypography.inlineCode.copy(
220− color = inlineCodeColors.inlineCodeText,
223+ color = inlineCodeTypography.inlineCode.color,
221 background = inlineCodeColors.inlineCodeBackground,
224 background = inlineCodeColors.inlineCodeBackground,
222 ).toSpanStyle()
225 ).toSpanStyle()
223 DefaultMarkdownAnnotator(
226 DefaultMarkdownAnnotator(
@@ -247,6 +250,7 @@ private fun MarkdownBody(
247 baseAnnotator.annotate?.invoke(this, content, node) ?: false
250 baseAnnotator.annotate?.invoke(this, content, node) ?: false
248 }
251 }
249 },
252 },
253+ config = DefaultMarkdownAnnotatorConfig(),
250 )
254 )
251 }
255 }
252 // Compose the inline-code tagger with the search-highlight wrapper when a query is active.
256 // Compose the inline-code tagger with the search-highlight wrapper when a query is active.
@@ -271,6 +275,7 @@ private fun MarkdownBody(
271 }
275 }
272 handled
276 handled
273 },
277 },
278+ config = DefaultMarkdownAnnotatorConfig(),
274 )
279 )
275 }
280 }
276 } else {
281 } else {
@@ -317,12 +322,12 @@ private fun MarkdownBody(
317 // interrupting on every token; the reader hears the reply grow in measured chunks.
322 // interrupting on every token; the reader hears the reply grow in measured chunks.
318 CompositionLocalProvider(
323 CompositionLocalProvider(
319 LocalUriHandler provides safeUriHandler,
324 LocalUriHandler provides safeUriHandler,
320− LocalMarkdownAnnotator provides effectiveAnnotator,
321 ) {
325 ) {
322 Markdown(
326 Markdown(
323 content = renderedContent,
327 content = renderedContent,
324 modifier = modifier.semantics { liveRegion = LiveRegionMode.Polite },
328 modifier = modifier.semantics { liveRegion = LiveRegionMode.Polite },
325 components = components,
329 components = components,
330+ annotator = effectiveAnnotator,
326 )
331 )
327 }
332 }
328 // A blinking caret at the end of a streaming reply gives a "live" typewriter feel.
333 // A blinking caret at the end of a streaming reply gives a "live" typewriter feel.
@@ -336,12 +341,12 @@ private fun MarkdownBody(
336 content = markdown,
341 content = markdown,
337 modifier = modifier.semantics { liveRegion = LiveRegionMode.Polite },
342 modifier = modifier.semantics { liveRegion = LiveRegionMode.Polite },
338 components = components,
343 components = components,
344+ annotator = effectiveAnnotator,
339 )
345 )
340 }
346 }
341 }
347 }
342 CompositionLocalProvider(
348 CompositionLocalProvider(
343 LocalUriHandler provides safeUriHandler,
349 LocalUriHandler provides safeUriHandler,
344− LocalMarkdownAnnotator provides effectiveAnnotator,
345 ) {
350 ) {
346 content()
351 content()
347 }
352 }
@@ -465,12 +470,20 @@ private fun ParagraphWithInlineCodeCopy(model: MarkdownComponentModel) {
465 val context = LocalContext.current
470 val context = LocalContext.current
466 val haptics = LocalHapticFeedback.current
471 val haptics = LocalHapticFeedback.current
467 val style = model.typography.paragraph
472 val style = model.typography.paragraph
468− // buildMarkdownAnnotatedString is @Composable (it reads LocalMarkdownAnnotator to run the
473+ val typography = LocalMarkdownTypography.current
474+ val annotator = LocalMarkdownAnnotator.current
475+ // buildMarkdownAnnotatedString needs an explicit AnnotatorSettings in 0.43+ (the old
476+ // overload that read LocalMarkdownAnnotator internally was removed). Build one from the
469− // inline-code tagger installed in MarkdownBody), so the annotated string can't be built in
477+ // current composition locals so the inline-code tagger installed in MarkdownBody still
470− // a `remember` block. The library's own MarkdownParagraph does the same.
478+ // runs. The library's own MarkdownParagraph does the same.
479+ val settings = DefaultAnnotatorSettings(
480+ linkTextSpanStyle = typography.textLink,
481+ codeSpanStyle = typography.inlineCode.toSpanStyle(),
482+ annotator = annotator,
483+ )
471 val styledText = buildAnnotatedString {
484 val styledText = buildAnnotatedString {
472 pushStyle(style.toSpanStyle())
485 pushStyle(style.toSpanStyle())
473− buildMarkdownAnnotatedString(model.content, model.node)
486+ buildMarkdownAnnotatedString(model.content, model.node, settings)
474 pop()
487 pop()
475 }
488 }
476 // Capture the layout result so a long-press position can be mapped to a character offset,
489 // Capture the layout result so a long-press position can be mapped to a character offset,
@@ -478,7 +491,7 @@ private fun ParagraphWithInlineCodeCopy(model: MarkdownComponentModel) {
478 var layoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
491 var layoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
479 LibraryMarkdownText(
492 LibraryMarkdownText(
480 content = styledText,
493 content = styledText,
481− style = style,
494+ node = model.node,
482 modifier = Modifier.pointerInput(styledText) {
495 modifier = Modifier.pointerInput(styledText) {
483 detectTapGestures(
496 detectTapGestures(
484 onLongPress = { pos ->
497 onLongPress = { pos ->
@@ -493,7 +506,8 @@ private fun ParagraphWithInlineCodeCopy(model: MarkdownComponentModel) {
493 },
506 },
494 )
507 )
495 },
508 },
509+ style = style,
496− onTextLayout = { layoutResult = it },
510+ onTextLayout = { result, _ -> layoutResult = result },
497 )
511 )
498}
512}
499
513
app/src/main/java/soy/iko/opencode/ui/components/RemoteImage.kt+11−5
@@ -46,8 +46,12 @@ import androidx.compose.ui.text.style.TextOverflow
46import androidx.compose.ui.unit.dp
46import androidx.compose.ui.unit.dp
47import androidx.compose.ui.window.Dialog
47import androidx.compose.ui.window.Dialog
48import androidx.compose.ui.window.DialogProperties
48import androidx.compose.ui.window.DialogProperties
49−import coil.compose.SubcomposeAsyncImage
49+import coil3.compose.SubcomposeAsyncImage
50+import coil3.network.NetworkHeaders
51+import coil3.network.httpHeaders
50−import coil.request.ImageRequest
52+import coil3.request.ImageRequest
53+import coil3.request.crossfade
54+import coil3.toBitmap
51import kotlinx.coroutines.Dispatchers
55import kotlinx.coroutines.Dispatchers
52import kotlinx.coroutines.launch
56import kotlinx.coroutines.launch
53import kotlin.math.absoluteValue
57import kotlin.math.absoluteValue
@@ -189,7 +193,9 @@ fun RemoteImage(part: FilePart, ctx: ImageLoadContext, modifier: Modifier = Modi
189 // resolveModel already verified same-origin, so a String model can only point at
193 // resolveModel already verified same-origin, so a String model can only point at
190 // the user's own server — attaching Basic auth over http (not just https) is safe.
194 // the user's own server — attaching Basic auth over http (not just https) is safe.
191 // A data-URI model is a ByteArray, so gate on the model being a URL String.
195 // A data-URI model is a ByteArray, so gate on the model being a URL String.
192− if (model is String) ctx.basicAuthHeader?.let { addHeader("Authorization", it) }
196+ if (model is String) ctx.basicAuthHeader?.let {
197+ httpHeaders(NetworkHeaders.Builder().add("Authorization", it).build())
198+ }
193 }
199 }
194 .crossfade(true)
200 .crossfade(true)
195 .build()
201 .build()
@@ -542,9 +548,9 @@ private suspend fun saveImageToGallery(
542 * failure. Suspending so callers run it on a background dispatcher. */
548 * failure. Suspending so callers run it on a background dispatcher. */
543private suspend fun decodeRequestBitmap(context: android.content.Context, request: ImageRequest): android.graphics.Bitmap? {
549private suspend fun decodeRequestBitmap(context: android.content.Context, request: ImageRequest): android.graphics.Bitmap? {
544 return soy.iko.opencode.util.runCatchingCancellableSuspend {
550 return soy.iko.opencode.util.runCatchingCancellableSuspend {
545− val loader = coil.Coil.imageLoader(context)
551+ val loader = coil3.SingletonImageLoader.get(context)
546 val result = loader.execute(request)
552 val result = loader.execute(request)
547− (result.drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
553+ result.image?.toBitmap()
548 }.getOrNull()
554 }.getOrNull()
549}
555}
550
556
app/src/main/java/soy/iko/opencode/ui/file/FileViewScreen.kt+3−2
@@ -96,8 +96,9 @@ import androidx.compose.ui.unit.dp
96import androidx.compose.ui.res.stringResource
96import androidx.compose.ui.res.stringResource
97import androidx.lifecycle.compose.collectAsStateWithLifecycle
97import androidx.lifecycle.compose.collectAsStateWithLifecycle
98import androidx.lifecycle.viewmodel.compose.viewModel
98import androidx.lifecycle.viewmodel.compose.viewModel
99−import coil.compose.SubcomposeAsyncImage
99+import coil3.compose.SubcomposeAsyncImage
100−import coil.request.ImageRequest
100+import coil3.request.ImageRequest
101+import coil3.request.crossfade
101import soy.iko.opencode.di.AppContainer
102import soy.iko.opencode.di.AppContainer
102import soy.iko.opencode.R
103import soy.iko.opencode.R
103import soy.iko.opencode.ui.components.ConnectionBannerFor
104import soy.iko.opencode.ui.components.ConnectionBannerFor
build.gradle.kts−1
@@ -1,6 +1,5 @@
1plugins {
1plugins {
2 alias(libs.plugins.android.application) apply false
2 alias(libs.plugins.android.application) apply false
3− alias(libs.plugins.kotlin.android) apply false
4 alias(libs.plugins.kotlin.serialization) apply false
3 alias(libs.plugins.kotlin.serialization) apply false
5 alias(libs.plugins.kotlin.compose) apply false
4 alias(libs.plugins.kotlin.compose) apply false
6}
5}
flake.nix+2−2
@@ -10,8 +10,8 @@
10 flake-utils.lib.eachDefaultSystem (system:
10 flake-utils.lib.eachDefaultSystem (system:
11 let
11 let
12 # Versions kept in sync with the Gradle project (compileSdk / build-tools).
12 # Versions kept in sync with the Gradle project (compileSdk / build-tools).
13− platformVersion = "35";
13+ platformVersion = "37";
14− buildToolsVersion = "35.0.0";
14+ buildToolsVersion = "37.0.0";
15
15
16 pkgs = import nixpkgs {
16 pkgs = import nixpkgs {
17 inherit system;
17 inherit system;
gradle/libs.versions.toml+22−22
@@ -1,28 +1,28 @@
1[versions]
1[versions]
2−agp = "8.7.3"
2+agp = "9.2.0"
3−kotlin = "2.0.21"
3+kotlin = "2.4.0"
4−ktor = "3.0.3"
4+ktor = "3.1.3"
5−kotlinxSerialization = "1.7.3"
5+kotlinxSerialization = "1.9.0"
6−kotlinxCoroutines = "1.9.0"
6+kotlinxCoroutines = "1.10.2"
7−composeBom = "2024.12.01"
7+composeBom = "2026.06.01"
8−activityCompose = "1.9.3"
8+activityCompose = "1.13.0"
9−lifecycle = "2.8.7"
9+lifecycle = "2.11.0"
10−navigationCompose = "2.8.5"
10+navigationCompose = "2.9.8"
11−datastore = "1.1.1"
11+datastore = "1.2.1"
12securityCrypto = "1.1.0"
12securityCrypto = "1.1.0"
13−coil = "2.7.0"
13+coil = "3.5.0"
14biometric = "1.1.0"
14biometric = "1.1.0"
15−markdownRenderer = "0.27.0"
15+markdownRenderer = "0.43.0"
16−zxingCore = "3.5.3"
16+zxingCore = "3.5.4"
17−coreKtx = "1.13.1"
17+coreKtx = "1.19.0"
18−splashscreen = "1.0.1"
18+splashscreen = "1.2.0"
19−window = "1.3.0"
19+window = "1.5.1"
20junit = "4.13.2"
20junit = "4.13.2"
21detekt = "1.23.8"
21detekt = "1.23.8"
22−androidxTestExt = "1.2.1"
22+androidxTestExt = "1.3.0"
23−androidxTestRunner = "1.6.2"
23+androidxTestRunner = "1.7.0"
24−androidxTestCore = "1.6.1"
24+androidxTestCore = "1.7.0"
25−espresso = "3.6.1"
25+espresso = "3.7.0"
26
26
27[libraries]
27[libraries]
28androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
28androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -55,7 +55,8 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
55
55
56androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
56androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
57androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" }
57androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" }
58−coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
58+coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
59+coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
59androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
60androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
60markdown-renderer-m3 = { group = "com.mikepenz", name = "multiplatform-markdown-renderer-m3", version.ref = "markdownRenderer" }
61markdown-renderer-m3 = { group = "com.mikepenz", name = "multiplatform-markdown-renderer-m3", version.ref = "markdownRenderer" }
61markdown-renderer = { group = "com.mikepenz", name = "multiplatform-markdown-renderer", version.ref = "markdownRenderer" }
62markdown-renderer = { group = "com.mikepenz", name = "multiplatform-markdown-renderer", version.ref = "markdownRenderer" }
@@ -69,7 +70,6 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co
69
70
70[plugins]
71[plugins]
71android-application = { id = "com.android.application", version.ref = "agp" }
72android-application = { id = "com.android.application", version.ref = "agp" }
72−kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
73kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
73kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
74kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
74kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
75detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
75detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
gradle/wrapper/gradle-wrapper.properties+1−1
@@ -1,6 +1,6 @@
1distributionBase=GRADLE_USER_HOME
1distributionBase=GRADLE_USER_HOME
2distributionPath=wrapper/dists
2distributionPath=wrapper/dists
3−distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
3+distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
4networkTimeout=10000
4networkTimeout=10000
5validateDistributionUrl=true
5validateDistributionUrl=true
6zipStoreBase=GRADLE_USER_HOME
6zipStoreBase=GRADLE_USER_HOME
renovate.json+1−1
@@ -24,7 +24,7 @@
24 "matchPackagePatterns": ["^io.ktor"]
24 "matchPackagePatterns": ["^io.ktor"]
25 },
25 },
26 {
26 {
27− "description": "Pin AndroidX to the set the app targets (compileSdk 35); review major bumps manually.",
27+ "description": "Pin AndroidX to the set the app targets (compileSdk 37); review major bumps manually.",
28 "matchPackagePatterns": ["^androidx."],
28 "matchPackagePatterns": ["^androidx."],
29 "major": { "enabled": false }
29 "major": { "enabled": false }
30 }
30 }