Files
ReSearch/app/build.gradle.kts
OneWay a5f3af83a4 Fix dictionary update flow and rebuild the search field on Material 3
Fixed the update card reappearing for a moment after tapping it: the tap
re-ran the status check and republished NeedsUpdate before the download
service reported Loading, so the card flashed back in that gap.

Fixed the update card never returning after cancelling a download: both the
Cancelled branch and cancelDownload() forced the status to UpToDate, and the
card's tap is the only thing that re-evaluates it. The status is now
re-derived on the Cancelled to Idle transition, once the partially downloaded
archive - which carries today's date and would otherwise be read as a current
dictionary - has been cleaned up.

Fixed the download progress bar staying at 0% far too long: progress was
published in 3-percentage-point buckets and then scaled by the download stage
weight, so the bar only moved after about 7% of the archive had transferred.

Rebuilt the search field on the Material 3 TextField instead of a hand-rolled
BasicTextField inside a Surface, so it picks up the standard container, cursor
and icon treatment. Dictionary entry titles in the search field, the results
list and the article bar now share a dictionaryTitleLarge style that keeps the
font padding, so their line height lines up with the Material 3 text field.
2026-07-13 19:20:25 +08:00

199 lines
5.5 KiB
Kotlin

@file:Suppress("UnstableApiUsage")
import java.util.regex.Pattern
fun catalogLibraryIds(file: File): List<String> {
var insideLibraries = false
val tomlString = "\"([^\"]+)\""
return file.readLines()
.asSequence()
.map { it.substringBefore("#").trim() }
.onEach { line ->
if (line.startsWith("[") && line.endsWith("]")) {
insideLibraries = line == "[libraries]"
}
}
.filter { insideLibraries && it.contains("=") && it.contains("{") }
.mapNotNull { line ->
val definition = line.substringAfter("{").substringBeforeLast("}")
val module = Regex("""module\s*=\s*$tomlString""")
.find(definition)
?.groupValues
?.get(1)
if (module != null) {
module
} else {
val group = Regex("""group\s*=\s*$tomlString""")
.find(definition)
?.groupValues
?.get(1)
val name = Regex("""name\s*=\s*$tomlString""")
.find(definition)
?.groupValues
?.get(1)
if (group != null && name != null) "$group:$name" else null
}
}
.distinct()
.toList()
}
fun catalogAllowlistPattern(libraryIds: List<String>): Pattern {
val allowedIds = libraryIds
.flatMap { id ->
val group = id.substringBefore(":")
val name = id.substringAfter(":")
if (group == "com.mikepenz" && name.startsWith("aboutlibraries-")) {
listOf("$group:$name-android")
} else {
listOf(
"$group:$name",
"$group:$name-android",
"$group:$name-jvm",
"$group:$name-android-debug",
"$group:$name-jvm-debug",
)
}
}
.distinct()
.joinToString("|") { Pattern.quote(it) }
return Pattern.compile("^(?!(?:$allowedIds)$).*")
}
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.aboutlibraries)
}
kotlin {
jvmToolchain(21)
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_4)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_4)
progressiveMode.set(true)
freeCompilerArgs.addAll(
"-Xreturn-value-checker=check"
)
}
}
android {
namespace = "com.example.research"
compileSdk = project.property("compileSdk").toString().toInt()
defaultConfig {
applicationId = "com.example.research"
minSdk = project.property("minSdk").toString().toInt()
targetSdk = project.property("targetSdk").toString().toInt()
versionCode = 3
versionName = "1.1.1"
val isFdroid = project.hasProperty("fdroid")
if (isFdroid) {
applicationIdSuffix = ".fdroid"
versionNameSuffix = "-fdroid"
}
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
debug {
ndk {
abiFilters.clear()
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
release {
ndk {
abiFilters.clear()
abiFilters += "arm64-v8a"
}
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
buildFeatures {
compose = true
buildConfig = true
}
bundle {
language {
enableSplit = false
}
}
androidResources {
localeFilters += listOf("en", "ru")
}
}
aboutLibraries {
collect {
fetchRemoteLicense = false
fetchRemoteFunding = false
configPath = file("config")
}
library {
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.SIMPLE
exclusionPatterns = listOf(
catalogAllowlistPattern(catalogLibraryIds(rootProject.file("gradle/libs.versions.toml")))
)
}
}
dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.documentfile)
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
implementation(libs.bundles.compose)
implementation(libs.androidx.compose.material3.adaptive.layout)
debugImplementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.profileinstaller)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.datastore.preferences)
implementation(libs.okhttp)
// Coroutines with Android-optimized dispatchers
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)
}