From 1f8c9ccdfda5b04a30d1ec2c84893e11e81bde9d Mon Sep 17 00:00:00 2001 From: Secozzi Date: Mon, 27 Apr 2026 22:41:58 +0200 Subject: [PATCH 01/59] Merge and cleanup version catalogs Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 90 +++--- build.gradle.kts | 4 +- buildSrc/build.gradle.kts | 9 +- buildSrc/settings.gradle.kts | 9 - .../main/kotlin/mihon.code.lint.gradle.kts | 2 +- .../mihon/buildlogic/ProjectExtensions.kt | 12 +- core-metadata/build.gradle.kts | 2 +- core/common/build.gradle.kts | 16 +- data/build.gradle.kts | 2 +- domain/build.gradle.kts | 11 +- gradle/androidx.versions.toml | 40 --- gradle/compose.versions.toml | 19 -- gradle/kotlinx.versions.toml | 33 -- gradle/libs.versions.toml | 288 +++++++++++------- i18n-animiru/build.gradle.kts | 4 +- i18n-aniyomi/build.gradle.kts | 4 +- i18n/build.gradle.kts | 4 +- macrobenchmark/build.gradle.kts | 8 +- presentation-core/build.gradle.kts | 24 +- presentation-widget/build.gradle.kts | 5 +- settings.gradle.kts | 9 - source-api/build.gradle.kts | 13 +- source-local/build.gradle.kts | 2 +- 23 files changed, 285 insertions(+), 325 deletions(-) delete mode 100644 gradle/androidx.versions.toml delete mode 100644 gradle/compose.versions.toml delete mode 100644 gradle/kotlinx.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13e8a7bb16..58b9dc2fca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -189,81 +189,79 @@ dependencies { implementation(projects.presentationWidget) // Compose - implementation(compose.activity) - implementation(compose.foundation) - implementation(compose.material3.core) - implementation(compose.material.icons) - implementation(compose.animation) - implementation(compose.animation.graphics) - debugImplementation(compose.ui.tooling) - implementation(compose.ui.tooling.preview) - implementation(compose.ui.util) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.materialIcons) + implementation(libs.androidx.compose.animation) + implementation(libs.androidx.compose.animationGraphics) + debugImplementation(libs.androidx.compose.uiTooling) + implementation(libs.androidx.compose.uiToolingPreview) + implementation(libs.androidx.compose.uiUtil) - implementation(androidx.interpolator) + implementation(libs.androidx.interpolator) - implementation(androidx.paging.runtime) - implementation(androidx.paging.compose) + implementation(libs.androidx.paging.runtime) + implementation(libs.androidx.paging.compose) - implementation(androidx.sqlite.bundled) + implementation(libs.androidx.sqlite.bundled) - implementation(kotlinx.reflect) - implementation(kotlinx.immutables) + implementation(libs.kotlin.reflect) + implementation(libs.kotlinx.collections.immutable) - implementation(platform(kotlinx.coroutines.bom)) - implementation(kotlinx.bundles.coroutines) + implementation(libs.bundles.kotlinx.coroutines) // AndroidX libraries - implementation(androidx.annotation) - implementation(androidx.appcompat) - implementation(androidx.biometricktx) - implementation(androidx.constraintlayout) + implementation(libs.androidx.annotation) + implementation(libs.androidx.appCompat) + implementation(libs.androidx.biometric) + implementation(libs.androidx.constraintLayout) // AY --> implementation(aniyomilibs.compose.constraintlayout) // <-- AY - implementation(androidx.corektx) - implementation(androidx.splashscreen) - implementation(androidx.recyclerview) + implementation(libs.androidx.core) + implementation(libs.androidx.coreSplashScreen) + implementation(libs.androidx.recyclerView) // AM (REMOVE_LIBRARIES) --> - // implementation(androidx.viewpager) + // implementation(libs.androidx.viewPager) // <-- AM (REMOVE_LIBRARIES) - implementation(androidx.profileinstaller) + implementation(libs.androidx.profileInstaller) // AY --> implementation(aniyomilibs.mediasession) // <-- AY - implementation(androidx.bundles.lifecycle) + implementation(libs.bundles.androidx.lifecycle) // Job scheduling - implementation(androidx.workmanager) + implementation(libs.androidx.work) // RxJava - implementation(libs.rxjava) + implementation(libs.rxJava) // Networking implementation(libs.bundles.okhttp) implementation(libs.okio) - implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10 + implementation(libs.conscrypt) // TLS 1.3 support for Android < 10 // Data serialization (JSON, protobuf, xml) - implementation(kotlinx.bundles.serialization) + implementation(libs.bundles.serialization) // HTML parser implementation(libs.jsoup) // Disk - implementation(libs.disklrucache) + implementation(libs.diskLruCache) implementation(libs.unifile) // Preferences - implementation(libs.preferencektx) + implementation(libs.androidx.preference) // Dependency injection implementation(libs.injekt) // Image loading - implementation(platform(libs.coil.bom)) implementation(libs.bundles.coil) - implementation(libs.subsamplingscaleimageview) { + implementation(libs.subsamplingScaleImageView) { exclude(module = "image-decoder") } // AM (REMOVE_LIBRARIES) --> @@ -272,20 +270,20 @@ dependencies { // UI libraries implementation(libs.material) - implementation(libs.flexible.adapter.core) + implementation(libs.flexibleAdapter) // AM (REMOVE_LIBRARIES) --> - // implementation(libs.photoview) - // implementation(libs.directionalviewpager) { + // implementation(libs.photoView) + // implementation(libs.directionalViewPager) { // exclude(group = "androidx.viewpager", module = "viewpager") // } // <-- AM (REMOVE_LIBRARIES) - implementation(libs.richeditor.compose) + implementation(libs.composeRichEditor) implementation(libs.aboutLibraries.compose) implementation(libs.bundles.voyager) - implementation(libs.compose.materialmotion) + implementation(libs.composeMaterialMotion) implementation(libs.swipe) - implementation(libs.compose.webview) - implementation(libs.compose.grid) + implementation(libs.composeWebview) + implementation(libs.composeGrid) implementation(libs.reorderable) implementation(libs.bundles.markdown) implementation(libs.materialKolor) @@ -304,9 +302,9 @@ dependencies { testRuntimeOnly(libs.junit.platform.launcher) // For detecting memory leaks; see https://square.github.io/leakcanary/ - // debugImplementation(libs.leakcanary.android) + // debugImplementation(libs.leakCanary.android) // AM (REMOVE_LIBRARIES) --> - // implementation(libs.leakcanary.plumber) + // implementation(libs.leakCanary.plumber) // <-- AM (REMOVE_LIBRARIES) // AY --> @@ -329,7 +327,7 @@ dependencies { implementation(aniyomilibs.google.api.client.oauth) // <-- AM (SYNC_DRIVE) - testImplementation(kotlinx.coroutines.test) + testImplementation(libs.kotlinx.coroutines.test) } androidComponents { @@ -342,6 +340,6 @@ androidComponents { buildscript { dependencies { - classpath(kotlinx.gradle) + classpath(libs.kotlin.gradle) } } diff --git a/build.gradle.kts b/build.gradle.kts index a13d3fb00d..9f5e41bf0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,9 +5,9 @@ buildscript { } plugins { - alias(kotlinx.plugins.serialization) apply false + alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.aboutLibraries) apply false - alias(libs.plugins.moko) apply false + alias(libs.plugins.moko.resources) apply false alias(libs.plugins.sqldelight) apply false } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 5ed94c26ba..9b565a5d4e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,16 +3,13 @@ plugins { } dependencies { - implementation(androidx.gradle) - implementation(kotlinx.gradle) - implementation(kotlinx.compose.compiler.gradle) + implementation(libs.android.gradle) + implementation(libs.kotlin.gradle) + implementation(libs.kotlin.compose.compiler.gradle) implementation(libs.spotless.gradle) implementation(gradleApi()) implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(androidx.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(compose.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(kotlinx.javaClass.superclass.protectionDomain.codeSource.location)) } repositories { diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index 0e494efad9..952faa1848 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -3,15 +3,6 @@ dependencyResolutionManagement { create("libs") { from(files("../gradle/libs.versions.toml")) } - create("androidx") { - from(files("../gradle/androidx.versions.toml")) - } - create("compose") { - from(files("../gradle/compose.versions.toml")) - } - create("kotlinx") { - from(files("../gradle/kotlinx.versions.toml")) - } // AY --> create("aniyomilibs") { from(files("../gradle/aniyomi.versions.toml")) diff --git a/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts b/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts index 99c3e99a16..ad36c03345 100644 --- a/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts +++ b/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts @@ -22,7 +22,7 @@ spotless { kotlin { target("**/*.kt", "**/*.kts") targetExclude("**/build/**/*.kt") - ktlint(libs.ktlint.core.get().version) + ktlint(libs.ktlint.bom.get().version) trimTrailingWhitespace() endWithNewline() } diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt index 7df0c772a7..069544e21c 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt @@ -1,9 +1,6 @@ package mihon.buildlogic import com.android.build.api.dsl.CommonExtension -import org.gradle.accessors.dm.LibrariesForAndroidx -import org.gradle.accessors.dm.LibrariesForCompose -import org.gradle.accessors.dm.LibrariesForKotlinx import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.tasks.testing.Test @@ -17,9 +14,6 @@ import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginE import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File -val Project.androidx get() = the() -val Project.compose get() = the() -val Project.kotlinx get() = the() val Project.libs get() = the() internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { @@ -57,12 +51,12 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, } dependencies { - "coreLibraryDesugaring"(libs.desugar) + "coreLibraryDesugaring"(libs.android.desugar) } } internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, *, *, *>) { - pluginManager.apply(kotlinx.plugins.compose.compiler.get().pluginId) + pluginManager.apply(libs.plugins.kotlin.compose.compiler.get().pluginId) commonExtension.apply { buildFeatures { @@ -70,7 +64,7 @@ internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, } dependencies { - "implementation"(platform(compose.bom)) + "implementation"(platform(libs.androidx.compose.bom)) } } diff --git a/core-metadata/build.gradle.kts b/core-metadata/build.gradle.kts index dfb3c1943f..358d4f6d04 100644 --- a/core-metadata/build.gradle.kts +++ b/core-metadata/build.gradle.kts @@ -16,5 +16,5 @@ android { dependencies { implementation(projects.sourceApi) - implementation(kotlinx.bundles.serialization) + implementation(libs.bundles.serialization) } diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index e99c841740..d327170438 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -22,12 +22,12 @@ dependencies { api(libs.logcat) - api(libs.rxjava) + api(libs.rxJava) api(libs.okhttp.core) api(libs.okhttp.logging) api(libs.okhttp.brotli) - api(libs.okhttp.dnsoverhttps) + api(libs.okhttp.dnsOverHttps) api(libs.okio) // AM (REMOVE_LIBRARIES) --> @@ -35,13 +35,13 @@ dependencies { // <-- AM (REMOVE_LIBRARIES) implementation(libs.unifile) - implementation(libs.libarchive) + implementation(libs.archive) - api(kotlinx.coroutines.core) - api(kotlinx.serialization.json) - api(kotlinx.serialization.json.okio) + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.serialization.json) + api(libs.kotlinx.serialization.jsonOkio) - api(libs.preferencektx) + api(libs.androidx.preference) implementation(libs.jsoup) @@ -49,7 +49,7 @@ dependencies { implementation(libs.natural.comparator) // JavaScript engine - implementation(libs.bundles.js.engine) + implementation(libs.quickJs) // AY --> // FFmpeg-kit diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 552bae9b3a..3e4abe5f8d 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -16,7 +16,7 @@ android { databases { create("Database") { packageName.set("tachiyomi.data") - dialect(libs.sqldelight.dialects.sql) + dialect(libs.sqldelight.sqliteDialect338) schemaOutputDirectory.set(project.file("./src/main/sqldelight")) } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 89bf001213..f281569236 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -27,17 +27,16 @@ dependencies { implementation(projects.i18nAnimiru) // <-- AM (GROUPING) - implementation(platform(kotlinx.coroutines.bom)) - implementation(kotlinx.bundles.coroutines) - implementation(kotlinx.bundles.serialization) + implementation(libs.bundles.kotlinx.coroutines) + implementation(libs.bundles.serialization) implementation(libs.unifile) - api(libs.sqldelight.android.paging) + api(libs.sqldelight.androidxPaging) - compileOnly(compose.runtime.annotation) + compileOnly(libs.androidx.compose.runtimeAnnotation) testImplementation(libs.bundles.test) - testImplementation(kotlinx.coroutines.test) + testImplementation(libs.kotlinx.coroutines.test) testRuntimeOnly(libs.junit.platform.launcher) } diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml deleted file mode 100644 index 43296b6476..0000000000 --- a/gradle/androidx.versions.toml +++ /dev/null @@ -1,40 +0,0 @@ -[versions] -agp_version = "8.13.2" -lifecycle_version = "2.10.0" -paging_version = "3.4.2" -interpolator_version = "1.0.0" -sqlite = "2.6.2" - -[libraries] -gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } - -annotation = "androidx.annotation:annotation:1.9.1" -appcompat = "androidx.appcompat:appcompat:1.7.1" -biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" -constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.1" -corektx = "androidx.core:core-ktx:1.18.0" -splashscreen = "androidx.core:core-splashscreen:1.2.0" -recyclerview = "androidx.recyclerview:recyclerview:1.4.0" -viewpager = "androidx.viewpager:viewpager:1.1.0" -profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1" - -lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" } -lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" } -lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" } - -workmanager = "androidx.work:work-runtime:2.11.1" - -paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } -paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } - -interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator_version" } - -benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.4.1" -test-ext = "androidx.test.ext:junit-ktx:1.3.0" -test-espresso-core = "androidx.test.espresso:espresso-core:3.7.0" -test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0" - -sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } - -[bundles] -lifecycle = ["lifecycle-common", "lifecycle-process", "lifecycle-runtimektx"] diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml deleted file mode 100644 index 03f7285aa9..0000000000 --- a/gradle/compose.versions.toml +++ /dev/null @@ -1,19 +0,0 @@ -[versions] -compose-bom = "2026.03.00" - -[libraries] -activity = "androidx.activity:activity-compose:1.13.0" -bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } -foundation = { module = "androidx.compose.foundation:foundation" } -animation = { module = "androidx.compose.animation:animation" } -animation-graphics = { module = "androidx.compose.animation:animation-graphics" } -runtime = { module = "androidx.compose.runtime:runtime" } -runtime-annotation = { module = "androidx.compose.runtime:runtime-annotation" } -ui-tooling = { module = "androidx.compose.ui:ui-tooling" } -ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } -ui-util = { module = "androidx.compose.ui:ui-util" } - -material3-core = { module = "androidx.compose.material3:material3" } -material-icons = { module = "androidx.compose.material:material-icons-extended" } - -glance = "androidx.glance:glance-appwidget:1.1.1" diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml deleted file mode 100644 index 83ea539e84..0000000000 --- a/gradle/kotlinx.versions.toml +++ /dev/null @@ -1,33 +0,0 @@ -[versions] -kotlin_version = "2.3.20" -serialization_version = "1.10.0" -xml_serialization_version = "0.91.3" - -[libraries] -reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" } -gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } -compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin_version" } - -immutables = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" } - -coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.10.2" } -coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } -coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } -coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava" } -coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } - -serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } -serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization_version" } -serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" } - -serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xml_serialization_version" } -serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xml_serialization_version" } - -[bundles] -coroutines = ["coroutines-core", "coroutines-android", "coroutines-guava"] -serialization = ["serialization-json", "serialization-json-okio", "serialization-protobuf", "serialization-xml-core", "serialization-xml"] - -[plugins] -android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin_version" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin_version" } -serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin_version" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c85d8d87b5..412629b094 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,122 +1,206 @@ [versions] -aboutlib_version = "13.2.1" -leakcanary = "2.14" -moko = "0.26.1" -okhttp_version = "5.3.2" -shizuku_version = "13.1.5" -sqldelight = "2.3.2" -voyager = "1.1.0-beta03" -spotless = "8.4.0" -ktlint-core = "1.8.0" +aboutLibraries = "13.2.1" +android-desugar = "2.1.5" +android-gradle = "8.13.2" +android-shortcut = "0.1.2" +androidx-activity-compose = "1.13.0" +androidx-annotation = "1.9.1" +androidx-appCompat = "1.7.1" +androidx-benchmark = "1.4.1" +androidx-biometric = "1.2.0-alpha05" +androidx-compose-bom = "2026.03.00" +androidx-constraintLayout = "2.2.1" +androidx-core = "1.18.0" +androidx-coreSplashScreen = "1.2.0" +androidx-glance = "1.1.1" +androidx-interpolator = "1.0.0" +androidx-lifecycle = "2.10.0" +androidx-paging = "3.4.2" +androidx-preference = "1.2.1" +androidx-profileInstaller = "1.4.1" +androidx-recyclerView = "1.4.0" +androidx-sqlite = "2.6.2" +androidx-test-espresso = "3.7.0" +androidx-test-junit = "1.3.0" +androidx-test-uiautomator = "2.3.0" +androidx-viewPager = "1.1.0" +androidx-work = "2.11.1" +archive = "1.1.6" +coil = "3.4.0" +composeGrid = "1.2.2" +composeMaterialMotion = "2.0.1" +composeRichEditor = "1.0.0-rc13" +composeWebview = "0.33.6" +conscrypt = "2.5.3" +directionalViewPager = "1.0.0" +diskLruCache = "2.0.2" firebase-bom = "34.11.0" -markdown = "0.39.2" +firebase-crashlytics = "3.0.6" +flexibleAdapter = "c8013533" +google-services = "4.4.4" +image-decoder = "41c059e540" +injekt = "91edab2317" +jsoup = "1.22.1" junit = "6.0.3" +kotest-assertions = "6.1.7" +kotlin-gradle = "2.3.20" +kotlinx-collections-immutable = "0.4.0" +kotlinx-coroutines = "1.10.2" +kotlinx-serialization = "1.10.0" +ktlint = "1.8.0" +leakCanary = "2.14" +logcat = "0.4" +markdown = "0.39.2" +material = "1.12.0" materialKolor = "5.0.0-alpha07" +mockk = "1.14.9" +moko-resources = "0.26.1" +okhttp = "5.3.2" +okio = "3.17.0" +photoView = "2.3.0" +quickJs = "547f5b1597" +reorderable = "3.0.0" +rxJava = "1.3.8" +shizuku = "13.1.5" +spotless = "8.4.0" +sqldelight = "2.3.2" +sqldelight-androidx-driver = "0.0.17" +stringSimilarity = "0.1.0" +subsamplingScaleImageView = "66e0db195d" +swipe = "1.3.0" +unifile = "e0def6b3dc" +voyager = "1.1.0-beta03" +xmlutil = "0.91.3" [libraries] -desugar = "com.android.tools:desugar_jdk_libs:2.1.5" -android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" - -rxjava = "io.reactivex:rxjava:1.3.8" - -okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" } -okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" } -okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp_version" } -okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" } -okio = "com.squareup.okio:okio:3.17.0" - -conscrypt-android = "org.conscrypt:conscrypt-android:2.5.3" - -quickjs-android = { group = "com.github.zhanghai.quickjs-java", name = "quickjs-android", version = "547f5b1597" } - -jsoup = "org.jsoup:jsoup:1.22.1" - -disklrucache = "com.jakewharton:disklrucache:2.0.2" -unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" -libarchive = "me.zhanghai.android.libarchive:library:1.1.6" - -preferencektx = "androidx.preference:preference-ktx:1.2.1" - -injekt = "com.github.mihonapp:injekt:91edab2317" - -coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.4.0" } -coil-core = { module = "io.coil-kt.coil3:coil" } -coil-gif = { module = "io.coil-kt.coil3:coil-gif" } -coil-compose = { module = "io.coil-kt.coil3:coil-compose" } -coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } - -subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:66e0db195d" -image-decoder = "com.github.tachiyomiorg:image-decoder:41c059e540" - -natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" - -richeditor-compose = "com.mohamedrejeb.richeditor:richeditor-compose:1.0.0-rc13" - -material = "com.google.android.material:material:1.12.0" -flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533" -photoview = "com.github.chrisbanes:PhotoView:2.3.0" -directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" -compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.1" -compose-webview = "io.github.kevinnzou:compose-webview:0.33.6" -compose-grid = "io.woong.compose.grid:grid:1.2.2" -reorderable = { module = "sh.calvin.reorderable:reorderable", version = "3.0.0" } - -swipe = "me.saket.swipe:swipe:1.3.0" - -moko-core = { module = "dev.icerock.moko:resources", version.ref = "moko" } - -logcat = "com.squareup.logcat:logcat:0.4" - -firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } +aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutLibraries" } +android-desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugar" } +android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } +android-shortcut-gradle = { module = "com.github.zellius:android-shortcut-gradle-plugin", version.ref = "android-shortcut" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } +androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } +androidx-appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appCompat" } +androidx-benchmark-macroJunit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" } +androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" } +androidx-compose-animation = { module = "androidx.compose.animation:animation" } +androidx-compose-animationGraphics = { module = "androidx.compose.animation:animation-graphics" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-beta", version.ref = "androidx-compose-bom" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3" } +androidx-compose-materialIcons = { module = "androidx.compose.material:material-icons-extended" } +androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } +androidx-compose-runtimeAnnotation = { module = "androidx.compose.runtime:runtime-annotation" } +androidx-compose-uiTooling = { module = "androidx.compose.ui:ui-tooling" } +androidx-compose-uiToolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-compose-uiUtil = { module = "androidx.compose.ui:ui-util" } +androidx-constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintLayout" } +androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-coreSplashScreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-coreSplashScreen" } +androidx-glance-appWidget = { module = "androidx.glance:glance-appwidget", version.ref = "androidx-glance" } +androidx-interpolator = { module = "androidx.interpolator:interpolator", version.ref = "androidx-interpolator" } +androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "androidx-lifecycle" } +androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } +androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } +androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" } +androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" } +androidx-profileInstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "androidx-profileInstaller" } +androidx-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "androidx-recyclerView" } +androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidx-sqlite" } +androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } +androidx-test-junit = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-junit" } +androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidx-test-uiautomator" } +androidx-viewPager = { module = "androidx.viewpager:viewpager", version.ref = "androidx-viewPager" } +androidx-work = { module = "androidx.work:work-runtime", version.ref = "androidx-work" } +archive = { module = "me.zhanghai.android.libarchive:library", version.ref = "archive" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } +coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } +coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } +composeGrid = { module = "io.woong.compose.grid:grid", version.ref = "composeGrid" } +composeMaterialMotion = { module = "io.github.fornewid:material-motion-compose-core", version.ref = "composeMaterialMotion" } +composeRichEditor = { module = "com.mohamedrejeb.richeditor:richeditor-compose", version.ref = "composeRichEditor" } +composeWebview = { module = "io.github.kevinnzou:compose-webview", version.ref = "composeWebview" } +conscrypt = { module = "org.conscrypt:conscrypt-android", version.ref = "conscrypt" } +directionalViewPager = { module = "com.github.tachiyomiorg:DirectionalViewPager", version.ref = "directionalViewPager" } +diskLruCache = { module = "com.jakewharton:disklrucache", version.ref = "diskLruCache" } firebase-analytics = { module = "com.google.firebase:firebase-analytics" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } - -aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlib_version" } - -shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku_version" } -shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku_version" } - -leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } -leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" } - -sqldelight-androidx-driver = { module = "com.eygraber:sqldelight-androidx-driver", version = "0.0.17" } -sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } -sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } -sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } - +flexibleAdapter = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter", version.ref = "flexibleAdapter" } +image-decoder = { module = "com.github.tachiyomiorg:image-decoder", version.ref = "image-decoder" } +injekt = { module = "com.github.mihonapp:injekt", version.ref = "injekt" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -kotest-assertions = "io.kotest:kotest-assertions-core:6.1.7" -mockk = "io.mockk:mockk:1.14.9" - -voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } -voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } -voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } -voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } - -spotless-gradle = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" } -ktlint-core = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint-core" } - -markdown-core = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "markdown" } +kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest-assertions" } +kotlin-compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin-gradle" } +kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-gradle" } +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-gradle" } +kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } +kotlinx-serialization-jsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" } +kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" } +ktlint-bom = { module = "com.pinterest.ktlint:ktlint-bom", version.ref = "ktlint" } +leakCanary-core = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanary" } +leakCanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakCanary" } +logcat = { module = "com.squareup.logcat:logcat", version.ref = "logcat" } markdown-coil = { module = "com.mikepenz:multiplatform-markdown-renderer-coil3", version.ref = "markdown" } - -stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version = "0.1.0" } - +markdown-core = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "markdown" } +material = { module = "com.google.android.material:material", version.ref = "material" } materialKolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko-resources" } +natural-comparator = { module = "com.github.gpanther:java-nat-sort", version = "natural-comparator-1.1" } +okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } +okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-dnsOverHttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } +okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +photoView = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoView" } +quickJs = { module = "com.github.zhanghai.quickjs-java:quickjs-android", version.ref = "quickJs" } +reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" } +rxJava = { module = "io.reactivex:rxjava", version.ref = "rxJava" } +shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } +shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" } +spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } +sqldelight-androidxDriver = { module = "com.eygraber:sqldelight-androidx-driver", version.ref = "sqldelight-androidx-driver" } +sqldelight-androidxPaging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } +sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } +sqldelight-sqliteDialect338 = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } +stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version.ref = "stringSimilarity" } +subsamplingScaleImageView = { module = "com.github.tachiyomiorg:subsampling-scale-image-view", version.ref = "subsamplingScaleImageView" } +swipe = { module = "me.saket.swipe:swipe", version.ref = "swipe" } +unifile = { module = "com.github.tachiyomiorg:unifile", version.ref = "unifile" } +voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } +voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } +voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } +voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } +xmlutil-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xmlutil" } +xmlutil-serialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xmlutil" } [plugins] -google-services = { id = "com.google.gms.google-services", version = "4.4.4" } -aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutlib_version" } +aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } +google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle" } +kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-gradle" } +moko-resources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko-resources" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } -moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" } -firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.6" } [bundles] -okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"] -js-engine = ["quickjs-android"] +androidx-lifecycle = ["androidx-lifecycle-common", "androidx-lifecycle-process", "androidx-lifecycle-runtime"] coil = ["coil-core", "coil-gif", "coil-compose", "coil-network-okhttp"] +kotlinx-coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-coroutines-guava"] +markdown = ["markdown-core", "markdown-coil"] +okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsOverHttps"] +serialization = ["kotlinx-serialization-json", "kotlinx-serialization-jsonOkio", "kotlinx-serialization-protobuf", "xmlutil-core", "xmlutil-serialization"] shizuku = ["shizuku-api", "shizuku-provider"] -sqldelight = ["sqldelight-androidx-driver", "sqldelight-coroutines", "sqldelight-android-paging"] -voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-transitions"] +sqldelight = ["sqldelight-androidxDriver", "sqldelight-coroutines", "sqldelight-androidxPaging"] test = ["junit-jupiter", "kotest-assertions", "mockk"] -markdown = ["markdown-core", "markdown-coil"] +voyager = ["voyager-navigator", "voyager-screenModel", "voyager-tabNavigator", "voyager-transitions"] diff --git a/i18n-animiru/build.gradle.kts b/i18n-animiru/build.gradle.kts index 298b3cb909..ef86676b3f 100644 --- a/i18n-animiru/build.gradle.kts +++ b/i18n-animiru/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { id("mihon.library") kotlin("multiplatform") - alias(libs.plugins.moko) + alias(libs.plugins.moko.resources) } kotlin { @@ -16,7 +16,7 @@ kotlin { sourceSets { commonMain { dependencies { - api(libs.moko.core) + api(libs.moko.resources) } } } diff --git a/i18n-aniyomi/build.gradle.kts b/i18n-aniyomi/build.gradle.kts index 56736f6a12..2533f0aa14 100644 --- a/i18n-aniyomi/build.gradle.kts +++ b/i18n-aniyomi/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { id("mihon.library") kotlin("multiplatform") - alias(libs.plugins.moko) + alias(libs.plugins.moko.resources) } kotlin { @@ -16,7 +16,7 @@ kotlin { sourceSets { commonMain { dependencies { - api(libs.moko.core) + api(libs.moko.resources) } } } diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index 7259e46fdb..1f449ae0ca 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { id("mihon.library") kotlin("multiplatform") - alias(libs.plugins.moko) + alias(libs.plugins.moko.resources) } kotlin { @@ -16,7 +16,7 @@ kotlin { sourceSets { commonMain { dependencies { - api(libs.moko.core) + api(libs.moko.resources) } } } diff --git a/macrobenchmark/build.gradle.kts b/macrobenchmark/build.gradle.kts index ec2d049a5d..b74c26bdd8 100644 --- a/macrobenchmark/build.gradle.kts +++ b/macrobenchmark/build.gradle.kts @@ -26,10 +26,10 @@ android { } dependencies { - implementation(androidx.test.ext) - implementation(androidx.test.espresso.core) - implementation(androidx.test.uiautomator) - implementation(androidx.benchmark.macro) + implementation(libs.androidx.test.junit) + implementation(libs.androidx.test.espresso.core) + implementation(libs.androidx.test.uiautomator) + implementation(libs.androidx.benchmark.macroJunit4) } androidComponents { diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index 332c1fffea..4a3f632ece 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -32,17 +32,17 @@ dependencies { api(projects.i18n) // Compose - implementation(compose.activity) - implementation(compose.foundation) - implementation(compose.material3.core) - implementation(compose.material.icons) - implementation(compose.animation) - implementation(compose.animation.graphics) - debugImplementation(compose.ui.tooling) - implementation(compose.ui.tooling.preview) - implementation(compose.ui.util) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.materialIcons) + implementation(libs.androidx.compose.animation) + implementation(libs.androidx.compose.animationGraphics) + debugImplementation(libs.androidx.compose.uiTooling) + implementation(libs.androidx.compose.uiToolingPreview) + implementation(libs.androidx.compose.uiUtil) - implementation(androidx.paging.runtime) - implementation(androidx.paging.compose) - implementation(kotlinx.immutables) + implementation(libs.androidx.paging.runtime) + implementation(libs.androidx.paging.compose) + implementation(libs.kotlinx.collections.immutable) } diff --git a/presentation-widget/build.gradle.kts b/presentation-widget/build.gradle.kts index 09faa96394..e884be0699 100644 --- a/presentation-widget/build.gradle.kts +++ b/presentation-widget/build.gradle.kts @@ -19,12 +19,11 @@ dependencies { implementation(projects.presentationCore) api(projects.i18n) - implementation(compose.glance) + implementation(libs.androidx.glance.appWidget) implementation(libs.material) - implementation(kotlinx.immutables) + implementation(libs.kotlinx.collections.immutable) - implementation(platform(libs.coil.bom)) implementation(libs.coil.core) api(libs.injekt) diff --git a/settings.gradle.kts b/settings.gradle.kts index 39f414650b..f48ef0ee31 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,15 +17,6 @@ pluginManagement { dependencyResolutionManagement { versionCatalogs { - create("kotlinx") { - from(files("gradle/kotlinx.versions.toml")) - } - create("androidx") { - from(files("gradle/androidx.versions.toml")) - } - create("compose") { - from(files("gradle/compose.versions.toml")) - } // AY --> create("aniyomilibs") { from(files("gradle/aniyomi.versions.toml")) diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index 330e5f29d5..ea6b4c4b2b 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -11,23 +11,22 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - api(kotlinx.serialization.json) + api(libs.kotlinx.serialization.json) api(libs.injekt) - api(libs.rxjava) + api(libs.rxJava) api(libs.jsoup) - implementation(project.dependencies.platform(compose.bom)) - implementation(compose.runtime) + implementation(project.dependencies.platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.runtime) } } val androidMain by getting { dependencies { implementation(projects.core.common) - api(libs.preferencektx) + api(libs.androidx.preference) // Workaround for https://youtrack.jetbrains.com/issue/KT-57605 - implementation(kotlinx.coroutines.android) - implementation(project.dependencies.platform(kotlinx.coroutines.bom)) + implementation(libs.kotlinx.coroutines.android) } } } diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index 48cddc95f3..28abcedeb5 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -25,7 +25,7 @@ kotlin { // Move ChapterRecognition to separate module? implementation(projects.domain) - implementation(kotlinx.bundles.serialization) + implementation(libs.bundles.serialization) } } } From cdcb86a8fb85675e0be9066b7e8d7bd135206ffe Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 21 Mar 2026 19:39:16 +0100 Subject: [PATCH 02/59] Translations update from Hosted Weblate (#3003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/be/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fi/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/be/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lv/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nl/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ta/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals Co-authored-by: A Co-authored-by: Infy's Tagalog Translations Co-authored-by: Kehribar <103407696+dpentx@users.noreply.github.com> Co-authored-by: Lucas Correia Co-authored-by: ScratchBuild Co-authored-by: Siebrenvde Co-authored-by: lilp Co-authored-by: nadevko Co-authored-by: தமிழ்நேரம் Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) Co-authored-by: 안세훈 (cherry picked from commit 2366fbb4b3ae97093d282354c03a96a2f424be04) --- .../commonMain/moko-resources/be/plurals.xml | 124 ++- .../commonMain/moko-resources/be/strings.xml | 966 ++++++++++++++---- .../commonMain/moko-resources/cs/strings.xml | 1 - .../commonMain/moko-resources/de/strings.xml | 1 - .../commonMain/moko-resources/el/strings.xml | 1 - .../commonMain/moko-resources/eo/strings.xml | 1 - .../commonMain/moko-resources/es/strings.xml | 1 - .../commonMain/moko-resources/fi/plurals.xml | 12 + .../commonMain/moko-resources/fi/strings.xml | 1 + .../commonMain/moko-resources/fil/strings.xml | 14 +- .../commonMain/moko-resources/hr/strings.xml | 1 - .../commonMain/moko-resources/ja/strings.xml | 2 +- .../commonMain/moko-resources/ko/strings.xml | 2 +- .../commonMain/moko-resources/lv/strings.xml | 2 +- .../commonMain/moko-resources/nl/strings.xml | 36 +- .../moko-resources/pt-rBR/plurals.xml | 4 +- .../moko-resources/pt-rBR/strings.xml | 2 + .../commonMain/moko-resources/ru/strings.xml | 1 - .../commonMain/moko-resources/sk/strings.xml | 1 - .../commonMain/moko-resources/ta/strings.xml | 20 +- .../commonMain/moko-resources/tr/plurals.xml | 4 +- .../commonMain/moko-resources/tr/strings.xml | 1 - .../commonMain/moko-resources/vi/strings.xml | 1 - .../moko-resources/zh-rCN/strings.xml | 1 - .../moko-resources/zh-rTW/strings.xml | 1 - 25 files changed, 944 insertions(+), 257 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/be/plurals.xml b/i18n/src/commonMain/moko-resources/be/plurals.xml index f326e86dcd..93afb4ca11 100644 --- a/i18n/src/commonMain/moko-resources/be/plurals.xml +++ b/i18n/src/commonMain/moko-resources/be/plurals.xml @@ -7,10 +7,10 @@ %d катэгорый - Пасля %1$s хвіліны - Пасля %1$s хвілін - Пасля %1$s хвілін - Пасля %1$s хвілін + Праз %1$s хвіліну + Праз %1$s хвіліны + Праз %1$s хвілін + Праз %1$s хвілін Учора @@ -19,9 +19,117 @@ %1$d дзён таму - Наступная непрачытаная глава - Наступныя %d непрачытаныя главы - Наступныя %d непрачытаных глав - Наступныя %d непрачытаных глав + Наступны непрачытаная раздзел + Наступныя %d непрачытаныя раздзелы + Наступныя %d непрачытаных раздзелаў + Наступныя %d непрачытаных раздзелаў + + + Заўтра + Праз %1$d дні + Праз %1$d дзён + Праз %1$d дзён + + + Выканана за %1$s з %2$s памылкай + Выканана за %1$s з %2$s памылкамі + Выканана за %1$s з %2$s памылкамі + Выканана за %1$s з %2$s памылкамі + + + Застаўся %1$s + Засталося %1$s + Засталося %1$s + Засталося %1$s + + + %d дзень + %d дні + %d дзён + %d дзён + + + %1$s старонка + %1$s старонкі + %1$s старонак + %1$s старонак + + + Адсутнічае %1$s раздзел + Адсутнічаюць %1$s раздзелы + Адсутнічаюць %1$s раздзелаў + Адсутнічаюць %1$s раздзелаў + + + %1$s раздзел + %1$s раздзелы + %1$s раздзелаў + %1$s раздзелаў + + + Наступны раздзел + Наступныя %d раздзелы + Наступныя %d раздзелаў + Наступныя %d раздзелаў + + + %d трэкер + %d трэкеры + %d трэкераў + %d трэкераў + + + Прапускаецца %d раздзел: адсутнічае ў крыніцы або адфільтраваны + Прапускаюцца %d раздзелы: адсутнічаюць у крыніцы або адфільтраваныя + Прапускаецца %d раздзелаў: адсутнічаюць у крыніцы або адфільтраваныя + Прапускаецца %d раздзелаў: адсутнічаюць у крыніцы або адфільтраваныя + + + Для %d твора + Для %d твораў + Для %d твораў + Для %d твораў + + + %1$d новы раздзел + %1$d новыя раздзелы + %1$d новых раздзелаў + %1$d новых раздзелаў + + + %1$d твор быў прапушчаны + %1$d творы былі прапушчаны + %1$d твораў было прапушчана + %1$d твораў было прапушчана + + + Раздзелы %1$s і яшчэ адзін + Раздзелы %1$s і яшчэ %2$d + Раздзелы %1$s і яшчэ %2$d + Раздзелы %1$s і яшчэ %2$d + + + Даступна абнаўленне пашырэння + Даступна %d абнаўленні пашырэнняў + Даступна %d абнаўленняў пашырэнняў + Даступна %d абнаўленняў пашырэнняў + + + %d рэпазіторый + %d рэпазіторыі + %d рэпазіторыяў + %d рэпазіторыяў + + + Перанесці %1$d твор? + Перанесці %1$d творы? + Перанесці %1$d твораў? + Перанесці %1$d твораў? + + + Скапіяваць %1$d твор? + Скапіяваць %1$d творы? + Скапіяваць %1$d твораў? + Скапіяваць %1$d твораў? diff --git a/i18n/src/commonMain/moko-resources/be/strings.xml b/i18n/src/commonMain/moko-resources/be/strings.xml index be98fcffac..08aa5b65f9 100644 --- a/i18n/src/commonMain/moko-resources/be/strings.xml +++ b/i18n/src/commonMain/moko-resources/be/strings.xml @@ -1,355 +1,905 @@ - Перайсці да пачатку - Сыходны - Узыходзячы + Да пачатку + Па спаданні + Па ўзрастанні Найстарэйшы Найноўшы - Па нумару часткi - Па даце загрузкі + Па нумары раздзела + Па даце запампоўкі Сартаваць - Адмяніце ўсе для гэтай серыі - Адмяніць усе - Адмена + Скасаваць усе для гэтай серыі + Скасаваць усе + Скасаваць Адмацаваць Замацаваць Адключыць - Паказаць колькасць прадметаў - Паказаць укладкі катэгорый - Значкі лакальных - Значкі загружаных + Паказаць колькасць элементаў + Паказваць укладкі катэгорый + Лакальная крыніца + Спампаваныя раздзелы Спіс Зручная сетка Кампактная сетка - Выгляд + Адлюстраванне Рэжым адлюстравання Міграваць Адкрыць у WebView Адкрыць у браўзэры - Прадоўжыць + Узнавіць Пачаць - Выдаляць + Прыбраць Паўтарыць - Наступная частка - Папярэдняя частка + Наступны раздзел + Папярэдні раздзел Паўза - Прагляд частак - Рэдагаваць вокладку - Дадаць у катэгорыі + Праглядзець раздзелы + Змяніць вокладку + Змяніць катэгорыі Перайменаваць катэгорыю Рэдагаваць катэгорыі Дадаць катэгорыю Дадаць Рэдагаваць Адключыць усе - Ўключыць усе + Уключыць усе Абнавіць бібліятэку Выдаліць - Зняць закладку з часткi - Закладка часткi - Скачаць + Зняць закладку + Дадаць у закладкі + Спампаваць Пазначыць папярэдняе як прачытанае Пазначыць як непрачытанае - Адзначыць як прачытанае - Выбраць наадварот + Пазначыць як прачытанае + Інвертаваць выбар Выбраць усе Глабальны пошук Пошук налад Пошук Дата дадання Дата атрымання - Апошняя частка - Апошняе чытанне - Усяго частак - У алфавітным парадку + Апошні раздзел + Апошняе прачытанае + Усяго раздзелаў + Па алфавіту Зняць фільтр - Непрачытаны + Непрачытана Адсочваецца - Паведамленні ў закладкі + У закладках Фільтр Меню Налады Дапамога - Інфармацыя аб пашырэнні + Зведкі пра пашырэнні Назва - Пашырэння - перанесці дадзеныя - Рэзервовае капіяванне і аднаўленне + Пашырэнні + Міграваць + Рэзерваванне і аднаўленне Крыніцы Гісторыя Абнаўленні Бібліятэка - Чарга загрузак + Чарга спамповак Налады Яшчэ Гісторыя Адсочванне - Часткi - Манга + Раздзелы + Творы бібліятэкі Катэгорыі - Выдаліць - Ненадзейны - Надзейны - Усталяваны - Устаноўка - Загрузка - Чаканне + Дэінсталяваць + Ненадзейнае + Надзейнае + Усталявана + Усталёўваецца + Пампуецца + Чакаецца Усталяваць - Састарэла - Абнаўленне + Састарэлае + Абнавіць Даступны абнаўлення - Выключыць: %s + Выключаць: %s Уключаць: %s - Ніхто + Няма Усе - Манга ў выключаных катэгорыях не будзе абнаўляцца, нават калі яны таксама знаходзяцца ў уключаных катэгорыях. - Налады для кожнай катэгорыі для сартавання і адлюстравання - Заўсёды пытацца + Творы ў выключаных катэгорыях не будуць абнаўляцца, нават калі яны таксама знаходзяцца ва ўключаных. + Асобныя налады сартавання для кожнай катэгорыі + Запытваць Катэгорыя па змаўчанні Правераць наяўнасць новай вокладкі і дэталяў пры абнаўленні бібліятэкі - Аўтаматычнае абнаўленне метададзеных - Абнаўляць толькі незавершаную мангу + Аўтаабнаўленне метададзеных + Прапускаць завершаныя творы Абмежаванні: %s - Зарадка - Абмежаванні на абнаўленне + Падчас зарадкі + Абмежаванні аўтаабнаўленняў Штотыдзень Кожныя 2 дні Штодня Кожныя 12 гадзін Кожныя 6 гадзін - Уручную - Частата абнаўлення + Выключаны + Аўтаабнаўленні Глабальнае абнаўленне - Гарызантальная - Партрэтная - Элементы ў радку + Альбомная + Кніжная + Элементаў у радку Адлюстраванне - Гэта не перашкаджае таму, каб неафіцыйныя або патэнцыйна няправільна пазначаныя пашырэння адлюстроўвалі змесціва NSFW (18+) у дадатку. - Паказаць у спісе крыніц - Крыніцы NSFW (18+) + Гэта не перашкаджае неафіцыйным або патэнцыйна няправільна пазначаным пашырэнням паказваць кантэнт 18+ (NSFW) у праграме. + Паказваць у спісе крыніц і пашырэнняў + Крыніцы 18+ (NSFW) Схаваць змесціва апавяшчэнняў - Схаваць змесціва прыкладання пры пераключэнні на іншыя прыкладання і блакаваць скрыншоты + Абарона экрана хавае змесціва праграмы пры пераключэнні паміж праграмамі і стварэнні скрыншотаў Абарона экрана Ніколі Заўсёды Блакаванне пры бяздзейнасці Патрабаваць разблакоўку - Бяспека + Бяспека і прыватнасць Кіраванне апавяшчэннямі Фармат даты - Чысты чорны цёмны рэжым - Ёцуба + Чорная + Yotsuba Інь і Ян - Тако + Tako Клубнічны дайкiры Паўночнае змярканне Зялёны яблык Дынамічная - Тэма прыкладання - Уключаны - Выключаны - У адпаведнасці з сістэмнай тэмай + Тэма праграмы + Цёмная + Светлая + Сістэмная Тэма - Інфармацыя + Пра праграму Дадаткова Адсочванне - Загрузкi - Чыталка + Пампоўкі + Чытанка Бібліятэка Агульныя - Дадатак недаступны - Пагрузка… - Пачаць загрузку прама зараз - Асвяжыць + Праграма недаступная + Загрузка… + Пачаць спампоўку зараз + Абнавіць Наперад Назад Аднавіць - Паказваць памылкі - Адкрыць часопіс + Націсніце для падрабязнасцей + Адкрыць журнал Адмяніць - Скід + Скінуць Захаваць Падзяліцца Усталяваць - Перайсцi на дно - Ніякіх апошніх абнаўленняў - Няма загрузак - Анімацыя пераходаў паміж старонкамі - Паказваць змесціва ў галіне выраза - Калі размяшчэнне падзелу на дзве старонкі не адпавядае кірунку чытання - Інвертаваць размяшчэнне з падзелам на дзве старонкі - Падзел на дзве старонкі - Паказваць зоны дотыку калі чыталка адкрыта - Паказваць накладанне схемы навігацыі - Поўнаэкранны рэжым - Можа ўтрымліваць кантэнт NSFW (18+) + У канец + Няма нядаўніх абнаўленняў + Няма спамповак + Аніміраваць пераходы старонак + Паказваць змесціва ў вобласці выраза + Калі размяшчэнне палоў падзеленай старонкі не адпавядае напрамку чытання + Інвертаваць размяшчэнне палоў падзеленай старонкі + Падзяляць шырокія старонкі напалову + Кароткачасова паказваць пры адкрыцці чытанкі + Паказваць накладанне зон дотыку + На ўвесь экран + Крыніцы з гэтага пашырэння могуць утрымліваць 18+ кантэнт (NSFW) 18+ - Гэта пашырэнне больш недаступна. - Гэта пашырэнне было падпісана ненадзейным сертыфікатам і не было актывавана. -\n -\nШкоднаснае пашырэнне можа счытваць любыя ўліковыя дадзеныя для ўваходу, якія захоўваюцца ў Mihon, або выконваць адвольны код. -\n -\nДавяраючы гэтаму сертыфікату, вы прымаеце на сябе гэтыя рызыкі. + Гэта пашырэнне больш недаступнае. Яно можа працаваць некарэктна і выклікаць праблемы ў праграме. Рэкамендуецца яго дэінсталяваць. + Шкодныя пашырэнні могуць счытваць любыя захаваныя ўліковыя даныя або выконваць адвольны код.\n\nДавяраючы гэтаму пашырэнню, вы прымаеце гэтыя рызыкі. Ненадзейнае пашырэнне - Схема навігацыі + Зоны дотыку Пастаронкава - Бесперапынна вертыкальна - Вэб-комікс - Вертыкальна - Справа налева - Злева направа + Рулон з прамежкамі + Рулон + Пастаронкава (вертыкальна) + Пастаронкава (справа налева) + Пастаронкава (злева направа) Справа Злева Наступная Папярэдняя Справа і злева - Край - Kindle-падобная - L-вобразная форма - Рэжым чытання па змаўчанні - Аўтаматычна + Краі + Kindle-падобны + L-падобны + Прадвызначаны рэжым чытання + Аўта Чорны Шэры Белы Колер фону - Ствараць тэчкі ў адпаведнасці з назвай мангі + Ствараць тэчкі ў адпаведнасці з назвай твора Захоўваць старонкі ў асобныя тэчкі - Паказаць пры доўгім націску - Дзеяння - Абодва + Паказваць дзеянні пры доўгім дотыку + Дзеянні + Па абедзвум Вертыкальна Гарызантальна - Не - Націск наадварот - Інвертаваць клавішы рэгулявання гучнасці - Клавішы рэгулявання гучнасці + Няма + Інвертаваць зоны дотыку + Інвертаваць клавішы гучнасці + Клавішы гучнасці Навігацыя - Прапусціць адфільтраваныя часткi - Прапусціць часткi, адзначаныя як прачытаныя - Трымаць экран уключаным - Зацямненьне + Пропускаць адфільтраваныя раздзелы + Пропускаць прачытаныя раздзелы + Не выключаць экран + Зацямненне Асвятленне Экран Множанне - Перакрыцце - Рэжым накладання каляровага фільтра - Карыстацкі каляровы фільтр + Накладанне + Рэжым змешвання колернага фільтра + Уласны колерны фільтр Адценні шэрага - Карыстацкая яркасць - Выключаны - Уключаны - Абрэзаць поля - Ненадоўга адлюстраваць бягучы рэжым пры адкрыцці чыталкі - Паказаць рэжым чытання - Паказаць нумар старонкі - Хуткасць анімацыі падвойнага націску + Уласная яркасць + Выключана + Уключана + Абразаць палі + Ненадоўга паказваць бягучы рэжым пры адкрыцці чытанкі + Паказваць рэжым чытання + Паказваць нумар старонкі + Хуткасць анімацыі пры падвойным дотыку Хуткая Звычайная - Ніякай анімацыі - Цэнтральная + Без анімацыі + Па цэнтры Справа Злева - Аўтаматычная - Стартавая пазіцыя павелічэння - Разумны - Зыходны памер + Аўтаматычна + Пачатковая пазіцыя павелічэння + Разумнае ўпісванне + Арыгінальны памер Па вышыні Па шырыні Расцягнуць - Змясціць у экран + Упісаць у экран Маштабаванне - Колеры наадварот + Інвертаваныя Сёння - Бірузовы - Знешнасць - Аўтэнтыфікацыя для пацверджання змяненняў + Бірузовая + Выгляд + Аўтэнтыфікуйцеся, каб падтвердзіць змену Па змаўчанні - У вас няма катэгорый. Націсніце кнопку \"Плюс\", каб стварыць яе для арганізацыі вашай бібліятэкі. + У вас няма катэгорый. Націсніце «плюс», каб стварыць іх і ўпарадкаваць бібліятэку. Ваша бібліятэка пустая - У апошні час нічога не прачытана + Нядаўна прачытанага няма Адсочваць - Службы, якія прадстаўляюць пашыраныя функцыі для пэўных крыніц. Манга аўтаматычна адсочваецца пры даданні ў вашу бібліятэку. - Пашыраныя паслугі - Аднабаковая сінхранізацыя для абнаўлення прагрэсу частак у службах адсочвання. Наладзьце адсочванне асобных запісаў мангі з дапамогай кнопкі адсочвання. - Паслугi + Дае пашыраныя магчымасці для пэўных крыніц. Творы будуць аўтаматычна адсочвацца пры даданні ў бібліятэку. + Пашыраныя трэкеры + Аднабаковая сінхранізацыя прагрэсу з вонкавымі трэкерамі. Наладзьце адсочванне твораў з дапамогай кнопкі «Адсочванне». + Трэкеры Абнаўляць прагрэс пасля чытання - Кіраўніцтва па адсочванні - Манга ў выключаных катэгорыях не будзе загружана, нават калі яны таксама знаходзяцца ў уключаных катэгорыях. - Спампаваць новыя часткi - Аўтаматычная загрузка - Пятая ад прачытанай часткi - Чацвёртая ад прачытанай часткi - Трэцяя ад прачытанай часткі - Перадапошняя частка - Апошняя прачытаная частка + Дапаможнік па адсочванні + Творы з выключаных катэгорый не будуць спампоўвацца, нават калі яны ёсць у іншых катэгорыях. + Спампоўваць новыя раздзелы + Аўтаспампоўка + 5 ад канца прачытаны раздзел + 4 ад канца прачытаны раздзел + 3 ад канца прачытаны раздзел + Перадапошні прачытаны раздзел + Апошні прачытаны раздзел Адключана - Дазволіць выдаленне частак з закладкамі - Аўтаматычна пасля чытання - Пасля таго, як пазначана як \"Прачытанае\" - Выдаліць часткі - Найнізкая + Дазволіць выдаленне раздзелаў з закладкамі + Аўтавыдаленне пасля чытання + Пасля ручнога пазначэння прачытаным + Выдаліць раздзелы + Найніжэйшая Нізкая Высокая Найвышэйшая - Аўтаматычнае утойванне меню пры адчувальнасці да прагортцы - Бакавой водступ - Чытаньне + Адчувальнасць хавання меню пры пракрутцы + Бакавы водступ + Чытанне Рэжым чытання - Заўсёды паказваць пераход паміж часткамі - A - B - G - R + Заўсёды паказваць пераход раздзелаў + Празрыстасць + Сіні + Зялёны + Чырвоны Заблакаваная альбомная Свабодная - Партрэтная + Кніжная Альбомная - Заблакаваная партрэтная - Тып арыентацыі - Тып арыентацыі па змаўчанні - папярэджанне + Заблакаваная кніжная + Арыентацыя + Прадвызначаная арыентацыя + Папярэджанне Выдаліць катэгорыю - Вы хочаце выдаліць катэгорыю \"%s\"? - InternalError: Праверце журнал збояў для атрымання дадатковай інфармацыі + Вы хочаце выдаліць катэгорыю «%s»? + InternalError: Праверце журнал збояў для дадатковых зведак Абнавіць усё - Узроставае абмежаванне - Не пачата - Паказаць мангу + Узроставы цэнз + Прапускаць непачатыя творы + Паказаць твор Версія - Апошняе абнаўленне мангі + Апошняя праверка абнаўлення Колькасць непрачытаных - Не ўдалося атрымаць спіс пашырэнняў - Сетка без назвы - Аб дадатку - Колькасць мангі + Не ўдалося атрымаць даступныя пашырэнні + Сетка вокладак + Звесткі пра праграму + Усяго твораў Мова - Толькі ў безлемітнай сетцы + Толькі ў безлімітнай сетцы Мова Лаванда - Мова дадатку - Ёсць непрачытаныя главы - Shizuku не працуе + Мова праграмы + Прапускаць з непрачытаным(і) раздзелам(і) + Shizuku не запушчаны Закрыць - Устаноўка пашырэнняў… - Перамясціць у пачатак спісу + Усталёўка пашырэння… + Перамясціць серыю ў пачатак Усталёўнік - па змаўчанні - Выдаліць усё + Састарэлы + Прыбраць усё Кожныя 3 дні - Толькі па Wi-Fi + Толькі праз Wi-Fi Не выбрана Перайсці ўверх - Разблакіраваць %s - Пачата + Разблакаваць %s + Пачатае Выбрана - Другія опцыі - Выдаліць загружанае - Даныя і хаванне + Дадатковыя параметры + Выдаліць спампаванае + Даныя і сховішча Статыстыка - На ўстройстве + Лакальна Задаць інтэрвал - Наладжвальная частата абнаўлення + Наладжваемая частата абнаўлення Наступнае - Перакладчык - Загружана + Сканлейтар + Спампована + Падтрымаць + Аўта + Наступнае чаканае абнаўленне + Ацэнка трэкера + Выпадкова + Шукаць… + Абнавіць катэгорыю + Адкрыць выпадковы твор + Скапіяваць у буфер абмену + Скапіяваць спасылку + Непрачытаныя раздзелы + Кнопка «Працягнуць чытанне» + Ужыць + ОК + Нататкі + Перамясціць серыю ў канец + Рэдагаваць нататкі + Скінуць налады + Закрыць укладку + Не зараз + Усё адно дадаць + Першыя крокі + Вітаем! + Давайце спачатку ўсё наладзім. Вы заўсёды зможаце змяніць гэта пазней у наладах. + Далей + Пачнем + Прапусціць + Абярыце тэчку, дзе %1$s будзе захоўваць спампаваныя раздзелы, рэзервовыя копіі ды іншае.\n\nРэкамендуецца абраць асобную тэчку.\n\nВыбраная тэчка: %2$s + Абраць тэчку + Неабходна абраць тэчку + Абнаўляецеся са старой версіі і не ведаеце, што выбраць? Звярніцеся да дапаможніка па сховішчы для дадатковых зведак. + Дапаможнік па сховішчы + Дазвол на ўсталёўку праграм + Для ўсталёўкі пашырэнняў крыніц. + Дазвол на апавяшчэнні + Атрымліваць апавяшчэнні пра абнаўленні бібліятэкі ды іншае. + Выкарыстанне батарэі ў фоне + Дазволіць пазбегнуць абрываў доўгатэрміновых абнаўленняў бібліятэкі, памповак ды аднаўленняў рэзервоўкі. + Даслаць журнал збояў + Ананімна даслаць журнал збояў распрацоўнікам. + Дазволіць аналітыку + Ананімна дасылаць даныя выкарыстання праграмы для паляпшэння яе функцый. + Надаць + Упершыню ў %s? Рэкамендуем азнаёміцца з дапаможнікам пачаткоўца. + Пераўсталёўваеце %s? + Тэма, фармат даты і часу + Катэгорыі, глабальнае абнаўленне, гартанне раздзелаў + Рэжым чытання, адлюстраванне, навігацыя + Аўтаматычнае спампоўванне, спампоўванне наперад + Аднабаковая сінхранізацыя прагрэсу, пашыраная сінхранізацыя + Крыніцы, пашырэнні, глабальны пошук + Ручныя і аўтаматычныя рэзервоўкі, сховішча + Блакіроўка праграмы, абарона экрана + Выгрузка журнала збояў, аптымізацыя батарэі + Catppuccin + Манахром + Nord + Tidal Wave + Адносныя пазнакі часу + «%1$s» замест « %2$s» + Адлюстроўваць выявы ў апісаннях твораў + Бяспека + Аналітыка і журналы збояў + Адпраўка журналаў збояў і аналітыкі дазволіць нам выяўляць і выпраўляць памылкі, паляпшаць прадукцыйнасць і развіваць праграму з улікам вашых патрэб + Разумнае абнаўленне + Прадказваць выхад новых раздзелаў + Паказваць колькасць непрачытаных на значку «Абнаўленні» + Паводзіны + Раздзел пры змахванні ўправа + Раздзел пры змахванні ўлева + Пазначаць прачытаны дублікат раздзела як «прачытаны» + Пасля прачытання раздзела + Пасля атрымання новага раздзела + Схаваць індыкатары адсутных раздзелаў + Забараніць не-ASCII назвы файлаў + Забяспечвае сумяшчальнасць з носьбітамі без падтрымкі Unicode. Патрабуе ручнога перайменавання тэчак крыніц і серый: замяніце не-ASCII сімвалы на іх шаснаццатковыя коды UTF-8 (ніжні рэгістр). Файлы раздзелаў пераймяноўваць не трэба. + Шматмоўныя + Прыбраць + Прыбраць пашырэнне? + Вы сапраўды хочаце прыбраць пашырэнне «%s»? + Для ўсталёўкі пашырэнняў патрабуюцца дазволы. Націсніце сюды, каб даць іх. + Усталюйце і запусціце Shizuku, каб выкарыстоўваць яго як усталёўнік пашырэнняў. + Адклікаць давер да ненадзейных пашырэнняў + Рэпазітар пашырэнняў + У вас няма рэпазітароў. + Дадаць рэпазітар + URL-адрас рэпазітара + Дадайце дадатковыя рэпазітары у %s. Іхнія URL-адрасы павінны заканчвацца на «index.min.json». + Гэты рэпазітар ужо дададзены! + Выдаліць рэпазітар + Недапушчальны URL рэпазітара + Вы сапраўды хочаце выдаліць рэпазітар «%s»? + Ці хочаце дадаць рэпазітар «%s»? + Адкрыць рэпазітар крыніцы + Замяніць + Адбітак ключа подпісу ўжо існуе + Рэпазітар %1$s мае такі ж самы адбітак ключа подпісу, як і %2$s.\nКалі гэта чакана, %2$s будзе заменены, у адваротным выпадку звяжыцеся з суправаджальнікам вашага рэпазітара. + Паварочваць шырокія старонкі пад памер экрана + Адлюстроўваць арыентацыю павернутых шырокіх старонак + Падвойны дотык для павелічэння + Мігаць пры змене старонкі + Змяншае дваенне выявы на экранах e-ink + Час мігацення + %1$s мс + Мігаць кожную + Мігаць з + Чорным + Белым + Белым і чорным + Уласная мяжа апаратнага растравання + Па змаўчанні (%d) + Калі чытанка загружае пустую выяву, паступова змяншаць парог.\nВыбрана: %s + Выкарыстоўваць састарэлы дэкодэр у чытанцы рулонаў + Уплывае на прадукцыйнасць. Уключайце толькі калі памяншэнне парога растра не вырашае праблему з пустымі выявамі + Уласны профіль адлюстравання + Пропускаць дубляваныя раздзелы + Адключана + Панарамаванне шырокіх выяў + Аўтаматычна павялічваць шырокія выявы + Зваротная кніжная + Адключыць памяншэнне маштабу + Выключаныя катэгорыі + Не вызначана месца сховішча + Недапушчальнае месца: %s + Не ўдалося стварыць тэчку спамповак + Не ўдалося стварыць тэчку: %s + Прапускаць спампоўку дублікатаў прачытаных раздзелаў + Спампоўваць наперад + Аўтаспампоўка падчас чытання + Працуе, толькі калі бягучы і наступны раздзелы ўжо спампаваны. + Захаваць як CBZ-архіў + Раздзяляць доўгія выявы + Паляпшае прадукцыйнасць чытанкі + Паралельныя спампоўкі з крыніц + Паралельныя спампоўкі старонак + Адначасовых спамповак старонак на крыніцу + Абнаўляць прагрэс пасля пазначэння прачытаным + Даступна, але крыніца не ўсталявана: %s + Уваход у трэкер + Хаваць творы, якія ўжо ёсць у бібліятэцы + Месца сховішча + Для аўтаматычных рэзервовак, спампоўвання раздзелаў і лакальных крыніц. + Зарэзерваваць + можна выкарыстаць каб аднавіць бягучую бібліятэку + Аднавіць з рэзервоўкі + Аднавіць бібліятэку з файла рэзервоўкі + Частата аўтаматычнага рэзервавання + Стварыць + Рэзервоўка створана + Няправільны файл рэзервоўкі: + Поўная памылка: + Рэзервоўка не ўтрымлівае твораў. + JSON-фармат рэзервовак не падтрымліваецца + Файл рэзервоўкі пашкоджаны + Адсутныя крыніцы: + Трэкеры без аўтарызацыі: + Вам можа спатрэбіцца ўсталяваць адсутныя пашырэнні і ўвайсці ў трэкеры для іх выкарыстання. + Аднаўленне завершана + %1$02d хв %2$02d сек + Рэзерваванне ўжо выконваецца + Што вы хочаце зарэзерваваць? + Налады праграмы + Налады крыніцы + Рэпазітар пашырэнняў + Уключаць прыватныя налады (напр., токены трэкераў) + Усе прачытаныя творы + Зарэзерваванне + Памылка рэзервавання + Не нададзены дазволы на захаванне даных + У бібліятэцы няма твораў для рэзервавання + Не ўдалося стварыць файл рэзервоўкі + Рэзервоўка і аднаўленне могуць працаваць неналежным чынам, калі адключана «Аптымізацыя MIUI». + Аднаўленне ўжо выконваецца + Аднаўленне з рэзервоўкі + Памылка аднаўлення з рэзервоўкі + Аднаўленне адменена + Варта захоўваць копіі рэзервовак таксама і ў іншых месцах. Рэзервоўкі могуць утрымліваць канфідэнцыйныя даныя, у тым ліку захаваныя паролі; будзьце асцярожныя, калі дзеліцеся імі. + Апошняе аўтаматычнае рэзерваванне: %s + Даныя + Выкарыстанне сховішча + Даступна: %1$s / Усяго: %2$s + Ачысціць кэш раздзелаў + Выкарыстана %1$s + Кэш ачышчаны, выдалена файлаў: %1$d + Адбылася памылка падчас ачысткі + Ачышчаць кэш раздзелаў пры запуску праграмы + Экспартаваць + Спіс бібліятэкі + Бібліятэка экспартавана + Сінхранізацыя бібліятэкі + Сінхранізацыя бібліятэкі завершана + Сетка + Ачысціць кукі + DNS праз HTTPS (DoH) + Прадвызначаны user agent + User agent не можа быць пустым + Недапушчальны user agent + Аднавіць user agent па змаўчанні + Патрабуецца перазапуск праграмы, каб змены ўступілі ў сілу + Кукі ачышчаны + Пераіндэксаваць спампоўкі + Прымусова пераправерыць спампаваныя раздзелы + Перастварэнне індэкса спамповак + Ачысціць базу даных + Выдаліць гісторыю для твораў, якія не захаваны ў вашай бібліятэцы + У базе даных %1$d пазабібліятэчных твораў + Вы збіраецеся прыбраць творы з базы даных + Прачытаныя раздзелы і прагрэс пазабібліятэчных твораў будуць страчаны + Пакідаць творы з прачытанымі раздзеламі + Творы выдалены + Няма чаго ачышчаць + Ачысціць даныя WebView + Даныя WebView ачышчаны + Абнавіць вокладкі бібліятэкі + Скінуць налады чытанкі для кожнай серыі + Скідае рэжым чытання і арыентацыю ўсіх серый + Усе налады чытанкі скінуты + Не ўдалося скінуць параметры чытанкі + Падзяліцца журналам збояў + Захоўвае журналы збояў у файл для адпраўкі распрацоўнікам + Фонавая актыўнасць + Адключыць аптымізацыю батарэі + Дапамагае з абнаўленнем бібліятэкі і рэзерваваннем ў фоне + Аптымізацыя батарэі ўжо адключана + Не ўдалося адкрыць налады прылады + Некаторыя вытворцы маюць дадатковыя абмежаванні для праграм, якія забіваюць фонавыя службы. Тут ёсць зведкі, як гэта выправіць. + Планшэтны інтэрфейс + Падрабязнае журналяванне + Запісваць падрабязнасці у сістэмны журнал (зніжае прадукцыйнасць праграмы) + Зведкі адладкі + Абнаўляць назвы твораў бібліятэцы пры неадпаведнасці крыніцы + Увага: калі твор быў перайменаваны, ён будзе выдалены з чаргі спампоўкі (калі там быў). + Сайт + Версія + Што новага + Дапамагчы з перакладам + Ліцэнзіі адкрытага кода + Палітыка прыватнасці + Праверыць абнаўленні + Абноўлена да %1$s + Адпраўляць справаздачы пра збоі + Дапамагае выпраўляць памылкі. Ніякія асабістыя даныя не будуць адпраўлены + Зборкі F-Droid афіцыйна не падтрымліваюцца.\nНацісніце, каб даведацца больш. + Толькі спампаваныя + Рэжым інкогніта + Прыпыняе гісторыю чытання + Прыпыніць гісторыю чытання для пашырэння + Адключыць рэжым інкогніта + Фільтруе ўсе творы ў вашай бібліятэцы + Увайсці ў %1$s + Імя карыстальніка + Адрас эл. пошты + Пароль + Логін + Уваход… + Увайшлі + Выйсці з %1$s? + Выйсці + Вы паспешна выйшлі + Невядомая памылка + Абнаўленне катэгорыі + З бібліятэкі + Спампаваныя раздзелы + Інтэрвалы + Накладанне + Укладкі + Больш няма вынікаў + Нічога не знойдзена + Лакальная крыніца + Іншае + Апошнія выкарыстаныя + Замацавана + Глабальны пошук… + Шукаць «%1$s» глабальна + Апошнія + Папулярнае + Агляд + Ёсць вынікі + Дапаможнік па лакальнай крыніцы + У вас няма замацаваных крыніц + Раздзел не знойдзены + Недапушчальны фармат раздзела + Сартаваць па + Дата + Выходзіць + Невядома + Невядомы аўтар + Аўтар + Мастак + Невядомы статус + Ліцэнзавана + Публікацыя завершана + Адменена + Замарожана + Дадаць у бібліятэку + У бібліятэцы + Прыбраць з бібліятэкі + Невядомая назва + Магчымыя дублікаты + У вашай бібліятэцы ёсць творы з падобнай назвай.\n\nАбярыце твор для пераносу або проста дадайце яго. + Дададзена ў бібліятэку + Прыбрана з бібліятэкі + Больш + Менш + Выдаліць спампаваныя раздзелы? + Скапіявана ў буфер абмену + Скапіявана ў буфер абмену:\n%1$s + Не ўдалося скапіяваць у буфер абмену + Крыніца не ўсталявана: %1$s + Дадаць у бібліятэку? + Няма апісання + Раздзел %1$s + Ацэньваць кожныя + Абнаўляць кожныя + Выхад новых раздзелаў прагназуецца прыкладна праз %1$s, праверка кожныя %2$s. + Гэты твор альбо завершаны, альбо дата яго выхаду невядомая. + Хутка + Наладжаная частата абнаўлення: + Спампоўваецца (%1$d/%2$d) + Памылка + Прыпынена + Назва крыніцы + Нумар раздзела + Па крыніцы + Па нумары раздзела + Па даце запампоўкі + Спампаваць + Непрачытаныя + У закладках + Уласная вокладка + Вокладка + Вокладка захавана + Памылка пры захаванні вокладкі + Памылка адпраўлення вокладкі + Дакладна выдаліць выбраныя раздзелы? + Налады раздзела + Дакладна захаваць гэтыя налады як прадвызначаныя? + Таксама ўжыць для ўсёй маёй бібліятэкі + Як па змаўчанні + Раздзелы не знойдзены + Вы ўпэўнены? + Выключыць сканлэйтараў + Сканлэйтары не знойдзены + Аднавіць трэкеры да раздзела %d? + Трэкеры адноўлены да раздзела %d + Адсочванне + Дадаць адсочванне + Непрачытанае + Чытаю + Скончана + Пакінута + Адкладзена + Прыпынена + Запланавана + Перачытваю + Спіс чытання + Спіс пажаданняў + Спіс завершаных + Спіс адкладзеных + Спіс незакончаных + Ацэнка + Загаловак + Статус + Статус + Дата пачатку + Дата заканчэння + Адсочваецца прыватна + Адсочваць прыватна + Адсочваць публічна + Тып + Калі ласка, увайдзіце ў MAL зноў + Крыніца не падтрымліваецца + Супадзенняў не знойдзена + %1$s памылка: %2$s + Прыбраць дату? + Гэта прыбярэ выбраную раней дату пачатку з %s + Гэта прыбярэ абраную раней дату заканчэння з %s + Прыбраць адсочванне %s? + Гэта прыбярэ адсочванне лакальна. + Таксама прыбраць з %s + Катэгoрыя з такой назвай ужо існуе! + Катэгорыі выдалены + Гэта прыбярэ дату прачытання раздзела. Вы ўпэўнены? + Скінуць усе раздзелы для гэтага твора + Выява захавана + Памылка захавання выявы + Уласны фільтр + Усталяваць як вокладку + Вокладка адноўлена + %1$s: %2$s, старонка %3$d + Старонка: %1$d + Наступны раздзел не знойдзены + Не ўдалося загрузіць выяву + Выкарыстаць як вокладку? + Для гэтай серыі + Рэжым чытання + Скончана: + Бягучая: + Наступная: + Папярэдняя: + Наступнага раздзела няма + Папярэдняга раздзела няма + Загрузка старонак… + Не ўдалося загрузіць старонкі: %1$s + Старонкі не знойдзены + Крыніца не знойдзена + Абнаўленне бібліятэкі + Абнаўленне ўжо запушчана + Не ўдалося адкрыць апошні прачытаны раздзел + Апошняе абноўленне: %s + Толькі што + Ніколі + Прагляд будучых абнаўленняў + Фільтраваць выключаных сканлэйтараў + Будучы дапаможнік + Наступны месяц + Папярэдні месяц + Раздз. %1$s ‐ %2$s + Ачысціць гісторыю + Гісторыя выдалена + Вы ўпэўнены? Уся гісторыя будзе выдалена. + Крыніц не знойдзена + Усталяваных крыніц не знойдзена + Дапаможнік па пераносе крыніц + Абярыце даныя для ўключэння + Скасаваць + Скасаваць + Скасаваць + Дапаможнік пачаткоўца + Абярыце мігруючую крыніцу + Міграваць + Скапіяваць + Вой, нейкая няўвязка + Не ўсталявана + Вой! + У %s адбылася непрадбачаная памылка. Мы рэкамендуем падзяліцца журналам збояў у нашым канале падтрымкі ў Discord. + Перазапусціць праграму + Агляд + Завершаныя творы + Працягласць чытання + Творы + У глабальным абнаўленні + Усяго + Прачытана + Адсочванне + Адсочваемыя творы + Сярэдняя ацэнка + Выкарыстана + Н/Д + %dд + %dг + %dхв + %dс + Не ўдалося спампаваць раздзелы. Паспрабуйце яшчэ раз у раздзеле спамповак + Не ўдалося спампаваць праз недахоп месца ў сховішчы + Папярэджанне: вялікая колькасць спамповак можа прывесці да запавольвання працы крыніц і/або блакіроўкі %s. Націсніце, каб даведацца больш. + Абнаўленне бібліятэкі… (%s) + Вялікія абнаўленні шкодзяць крыніцам і могуць прывесці да іх запавольвання і павялічанага расходу батарэі. Націсніце, каб даведацца больш. + Знойдзены новыя раздзелы + Раздзел %1$s + Раздзел %1$s ды яшчэ %2$d + Раздзелы %1$s + Не ўдалося %1$d абнаўленне(яў) + Націсніце, каб даведацца больш + Не ўдалося абнавіць вокладку + Калі ласка, дадайце твор у бібліятэку перад гэтым дзеяннем + Для дапамогі ў выпраўленні памылак абнаўлення бібліятэкі глядзіце %1$s + Прапушчана, бо серыя завершана + Прапушчана, бо ёсць непрачытаныя раздзелы + Прапушчана, бо няма прачытаных раздзелаў + Прапушчана, бо твор не патрабуе абнаўленняў + Прапушчана, бо сёння не чакаецца выхаду раздзелаў + Абраць вокладку + Абраць файл рэзервоўкі + Праграма для выбару файлаў не знойдзена + Не ўдалося атрымаць пастаянны доступ да тэчкі. Праграма можа паводзіць сябе непрадбачана. + Файл не выбраны + Спампаваць + Адкрыць на GitHub + Гэтая версія Android больш не падтрымліваецца + Няма новых абнаўленняў + Спампоўванне… + Націсніце, каб усталяваць абнаўленне + Памылка спампоўвання + Даступная новая версія! + Катэгорыя пустая + У гэтай катэгорыі твораў не знойдзена + У вас яшчэ няма катэгорый. + Не ўдалося прайсці Cloudflare + Націсніце тут, каб атрымаць дапамогу з Cloudflare + *абавязкова + WebView неабходны для працы праграмы + Калі ласка, абнавіце WebView для лепшай сумяшчальнасці + Стандартныя параметры раздзелаў абноўлены + Пампоўшчык + Памылка + Не ўдалося спампаваць раздзел праз непрадбачаную памылку + Няма Wi-Fi злучэння + Няма злучэння з сеткай + Спампоўванне прыпынена + Старонка %d не знойдзена падчас падзелу + Шлях да файлу старонкі %d не знойдзены + Праверка спамповак + Агульнае + Прагрэс + Завершэнні + Памылкі + Абнаўленні раздзелаў + Абнаўленні праграмы + Абнаўленні пашырэнняў + Папярэдняя старонка + Наступная старонка + Праглядзець нядаўна абноўленыя ў бібліятэцы + Віджэт недаступны, калі ўключана блакіроўка праграмы + Вы збіраецеся прыбраць «%s» са сваёй бібліятэкі + HTTP %d: праверце сайт у WebView + Няма інтэрнэт-злучэння + Не ўдалося далучыцца да %s + Спадабалася частка, дзе… + Выбрана + Даступна + Выбраць усё + Нічога не выбіраць + Выбраць уключаныя крыніцы + Выбраць замацаваныя крыніцы + Працягнуць + Даныя для мігравання + Выдаліць спампоўкі бягучага твора пасля міграцыі + Дадатковыя ключавыя словы (неабавязкова) + Дапамагае звузіць пошук праз дадатковыя ключавыя словы + Хаваць творы без супадзенняў + Хаваць творы без новых раздзелаў + Паказваць толькі пры наяўнасці дадатковых раздзелаў + Гэтыя параметры марудныя, небяспечныя і могуць прывесці да блакіровак + Пашыраны глыбокі пошук + Разбівае назву на ключавыя словы для пашырэння пошуку + Супадзенне па колькасці раздзелаў + Выбірае супадзенне з найбольшай колькасцю раздзелаў. Інакш — па прыярытэце крыніцы. + Міграцыя + Міграцыя (%1$d/%2$d) + Скапіяваць + Міграваць + Альтэрнаціў не знойдзена + Апошні: %1$s + Невядома + Шукаць уручную + Не міграваць + Міграваць зараз + Скапіяваць зараз + Спыніць міграванне? + Спыніць + Скапіяваць + Міграваць + Няма раздзелаў, твор не падыходзіць для міграцыі diff --git a/i18n/src/commonMain/moko-resources/cs/strings.xml b/i18n/src/commonMain/moko-resources/cs/strings.xml index b16b0e822a..69941b3869 100644 --- a/i18n/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n/src/commonMain/moko-resources/cs/strings.xml @@ -868,7 +868,6 @@ Souběžné stahování zdrojů Souběžné stahování stránek Zavřít okno - Vypočítávání… Souběžně stahované stránky na zdroj Vybrat vše Vybrat žádné diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index 36b77d18be..03d25b4eea 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -910,6 +910,5 @@ Pro Quelle gleichzeitig heruntergeladene Seiten Tab schließen Ausgeschlossene Scanlationgruppen herausfiltern - Wird berechnet… Mit Lesezeichen versehen diff --git a/i18n/src/commonMain/moko-resources/el/strings.xml b/i18n/src/commonMain/moko-resources/el/strings.xml index 5860a3eb9b..917c3a9619 100644 --- a/i18n/src/commonMain/moko-resources/el/strings.xml +++ b/i18n/src/commonMain/moko-resources/el/strings.xml @@ -909,7 +909,6 @@ Ταυτόχρονες λήψεις πηγών Ταυτόχρονες λήψεις σελίδων Σελίδες που λήφθηκαν ταυτόχρονα ανά πηγή - Υπολογισμός… Προστέθηκε στους σελιδοδείκτες Φιλτράρισμα εξαιρούμενων scanlator diff --git a/i18n/src/commonMain/moko-resources/eo/strings.xml b/i18n/src/commonMain/moko-resources/eo/strings.xml index 0fe38264c8..09ccc743be 100644 --- a/i18n/src/commonMain/moko-resources/eo/strings.xml +++ b/i18n/src/commonMain/moko-resources/eo/strings.xml @@ -901,6 +901,5 @@ Kunrulaj elŝutoj de paĝoj Paĝoj elŝutitaj sammomente por fonto Filtri ekskluzivitajn skantradukistojn - Kalkulado… Legosignita diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 76a50fc2c4..ebb7c19eb0 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -903,6 +903,5 @@ Cantidad de páginas a descargar simultáneamente desde cada fuente remota Cerrar pestaña Ocultar equipos de traducción excluidos - Calculando… Favoritos diff --git a/i18n/src/commonMain/moko-resources/fi/plurals.xml b/i18n/src/commonMain/moko-resources/fi/plurals.xml index 7fa9a44aea..93467b5042 100644 --- a/i18n/src/commonMain/moko-resources/fi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fi/plurals.xml @@ -52,4 +52,16 @@ Seuraava lukematon luku Seuraavat %d lukematonta lukua + + Huomenna + %1$d:n päivän päästä + + + 1 päivä + %d päivää + + + 1 sivu + %1$s sivua + diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index 1e6e4acd60..dd11f147de 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -668,4 +668,5 @@ Automaattinen lataus, ennakkolataus Yksisuuntainen synkronointi, paranneltu synkronointi Kategoriat, globaalit päivitykset, luvun pyyhkäisy + Navigoi ylös diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index c0b0b4249e..4fde8e327e 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -225,7 +225,7 @@ I-restore ang backup Magagamit para ma-restore ang kasalukuyang aklatan Gumawa ng backup - Isahang pag-sync para mai-update ang progress sa mga kabanata sa mga external na tracking service. Ayusin ang pagta-track para sa mga indibidwal na entry gamit ang button na Pagta-track. + Isahang pag-sync para mai-update ang progress ng kabanata sa mga external na tracking service. Ayusin ang pagta-track para sa mga indibidwal na entry gamit ang button na Pagta-track. Mga Tracker I-update ang progress pagkatapos basahin I-download ang mga bagong kabanata @@ -650,7 +650,7 @@ Tatanggalin nito ang huling petsa na napili mo mula sa %s Aalisin nito ang dati mong napiling petsa ng pagtatapos mula sa %s Muling i-index ang mga download - Pilitin ang app na tingnan kung may naka-download + Pilitin ang app na tingnan kung may naka-download na kabanata Mga natapos na entry Nasimulan Lokal @@ -857,10 +857,10 @@ Panatilihin ang mga entry na may mga nabasang kabanata Mga nabasang kabanata at ang progress ng mga entry na wala sa aklatan ay mawawala Nagla-log in… - Nabigong lumikha ng direktory para sa pag-download - Nabigong lumikha ng direktory: %s - I-update ang mga pamagat ng manga ng aklatan upang tumugma sa source - Babala: kung ang isang manga ay pinalitan ng pangalan, ito ay aalisin sa pila ng mga download (kung mayroon). + Nabigong lumikha ng directory para sa pag-download + Nabigong lumikha ng directory: %s + I-update ang mga pamagat ng manga upang tumugma sa source + Babala: kung ang manga ay pinalitan ng pangalan, ito ay aalisin sa pila ng mga download (kung mayroon). Napili Piliin lahat Pumili ng wala @@ -910,5 +910,5 @@ Catppuccin Maaaring Magamit I-filter ang na-ibukod na mga scanlator - Kinakalkula… + Na-bookmark diff --git a/i18n/src/commonMain/moko-resources/hr/strings.xml b/i18n/src/commonMain/moko-resources/hr/strings.xml index b43baa934e..3046ce516f 100644 --- a/i18n/src/commonMain/moko-resources/hr/strings.xml +++ b/i18n/src/commonMain/moko-resources/hr/strings.xml @@ -909,7 +909,6 @@ Istovremena preuzimanja stranica Istovremeno preuzete stranice po izvoru Zatvori karticu - Izračunavanje … Filtriraj isključene amaterske prevoditelje Zabilježeno diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index ec568159ed..d0e36e0bfd 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -853,7 +853,7 @@ 読み終えた章を含むエントリーを保持する 重複の可能性 寄付する - 自動車 + 自動 キャットプッチン モノクロ マンガの描写で画像をレンダリングする diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index 11e84cf8ea..f70d8930c7 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -909,5 +909,5 @@ 취소 챕터를 찾을 수 없어 이 항목은 마이그레이션에 사용할 수 없습니다 스캔레이터를 제외한 필터 - 계산 중… + 북마크됨 diff --git a/i18n/src/commonMain/moko-resources/lv/strings.xml b/i18n/src/commonMain/moko-resources/lv/strings.xml index 3af102ee96..4745fd8964 100644 --- a/i18n/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n/src/commonMain/moko-resources/lv/strings.xml @@ -6,7 +6,7 @@ Izsekošana Vēsture Iestatījumi - Lejupieladēšanas rinda + Lejupielādes rinda Bibliotēka Vēsture Atjauninājumi diff --git a/i18n/src/commonMain/moko-resources/nl/strings.xml b/i18n/src/commonMain/moko-resources/nl/strings.xml index 05639387a0..eb32161ecf 100644 --- a/i18n/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n/src/commonMain/moko-resources/nl/strings.xml @@ -163,7 +163,7 @@ Selecteer back-upbestand Download Geen nieuwe updates beschikbaar - Download voltooid + Tik om update te installeren Nieuwe versie beschikbaar! Geen downloads Geen recente updates @@ -430,7 +430,7 @@ Geen bestandskiezer-app gevonden Log alstublieft opnieuw in op MAL Weergeven in bronnen- en extensielijst - Navigatie-indeling + Tikzones Rand Kindle-achtig L-vormig @@ -463,7 +463,7 @@ Items in uitgesloten categorieën worden niet bijgewerkt, zelfs niet als ze onder opgenomen categorieën vallen. Datum opgehaald Geen - Klik om details te weergeven + Tik om details weer te geven Deze Android versie wordt niet meer ondersteund Mislukt om naar het klemboord te kopiëren Incognitomodus uitschakelen @@ -555,7 +555,7 @@ Geannuleerd Gepauzeerd %1$d update(s) mislukt - Kom meer te weten + Tik om meer te leren Overgeslagen want er zijn geen ongelezen hoofdstukken Overgeslagen want er zijn geen gelezen hoofdstukken Zoom liggende afbeelding @@ -594,7 +594,7 @@ Werkt alleen als het huidige hoofdstuk en het volgende alreeds gedownload zijn. Standaard \'user-agent-string\' Zoek… - Opslag bevoegdheden niet verleent + Opslagrechten niet verleend Knop om lezen te hervatten weergeven Statistieken Gestart @@ -613,8 +613,7 @@ Kon leesinstellingen niet terugzetten naar standaardwaarden Populair Downloads worden gecontroleerd - F-Droid versies worden niet officieel ondersteund. -\nTik hier voor meer informatie. + F-Droid versies worden niet officieel ondersteund. \nTik om meer te leren. Dubbele hoofdstukken overslaan Weet je het zeker? Leeslijst @@ -676,7 +675,7 @@ Leesduur Schat elke Herstart de applicatie - Download Index geïnvalideerd + Downloadindex hermaken Herindexeer downloads OK Hoofdstuk bij links vegen @@ -720,7 +719,7 @@ Opslaggebruik Bibliotheek bijwerken... (%s) Data en opslag - Bestandskiezer gaf geen bestand terug aan de app + Geen bestand geselecteerd Bron-instellingen App-instellingen Relatieve tijdstempels @@ -732,7 +731,7 @@ Scanlator Toepassen Naar standaard terugkeren - Onboarding gids + Onboardinggids Welkom! Link kopiëren Vertrouwde onbekende extensies intrekken @@ -887,4 +886,21 @@ Annuleer Annuleer Geen hoofdstukken gevonden. Dit item kan niet worden gebruikt voor migratie + Tabblad sluiten + Gelijktijdige brondownloads + Gelijktijdige paginadownloads + Gelijktijdig gedownloade pagina\'s per bron + Met bladwijzer + Filter uitgesloten scanlators + Catppuccin + Selecteer een map waarin %1$s hoofdstukdownloads, back-ups en meer zal opslaan.\n\nEen specifiek hiervoor bestemde map is aangeraden.\n\nGeselecteerde map: %2$s + App-installatierecht + Meldingsrecht + Achtergrondbatterijgebruik + Het verzenden van crashlogs en analytics zal ons toestaan om problemen te identificeren en op te lossen, performance te verbeteren en om toekomstige updates relevanter te maken voor jouw noden + Ondertekeningssleutelvingerafdruk bestaat al + Repo %1$s heeft dezelfde ondertekeningssleutelvingerafdruk als %2$s.\nAls dit verwacht is zal %2$s vervangen worden. Zo niet, neem dan contact op met de beheerder van uw repo. + Toekomstige Updatesgids + Kon geen permanente toegang tot map vergijken. De app kan zich onverwacht gedragen. + Beginnen diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml index e55abba583..dae2e4e4c7 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -3,7 +3,7 @@ Após %1$s minuto Após %1$s minutos - Após %1$s minuto + Após %1$s minutos %1$d capítulo novo @@ -108,6 +108,6 @@ 1 página %1$s páginas - + diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 9bb51d4df3..6cb2271ee0 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -904,4 +904,6 @@ Doar Fechar aba Renderizar imagens em descrições de mangá + Não permitir nomes de arquivos não ASCII + Garante a compatibilidade com determinadas mídias de armazenamento que não suportam Unicode. Quando esta opção estiver ativada, você precisará renomear manualmente as pastas de origem e de mangá, substituindo os caracteres não ASCII por suas representações hexadecimais UTF-8 em minúsculas. Os arquivos de capítulos não precisam ser renomeados. diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 3e5e7427de..1f2f3cc16e 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -910,6 +910,5 @@ Параллельные загрузки страниц Число одновременных загрузок страниц с источника Фильтровать исключенных переводчиков - Вычисление… В закладках diff --git a/i18n/src/commonMain/moko-resources/sk/strings.xml b/i18n/src/commonMain/moko-resources/sk/strings.xml index c5719f8aff..72384b6b5e 100644 --- a/i18n/src/commonMain/moko-resources/sk/strings.xml +++ b/i18n/src/commonMain/moko-resources/sk/strings.xml @@ -675,7 +675,6 @@ Zatvoriť kartu Nie teraz Ajtak pridať - Vypočítavam… Príručka na úvod Poďme najprv nastaviť nejaké veci. Vždy ich môžete neskôr zmeniť. Vyberte priečinok, kde %1$s bude ukladať stiahnuté kapitoly, zálohy a ďalšie.\n\nOdporúčame použiť samostatný priečinok špecificky pre túto aplikáciu.\n\nVybraný priečinok: %2$s diff --git a/i18n/src/commonMain/moko-resources/ta/strings.xml b/i18n/src/commonMain/moko-resources/ta/strings.xml index b4ae4cdc2c..1542ca832d 100644 --- a/i18n/src/commonMain/moko-resources/ta/strings.xml +++ b/i18n/src/commonMain/moko-resources/ta/strings.xml @@ -348,7 +348,7 @@ ரெப்போ சேர்க்கவும் முகவரி களஞ்சி தீங்கிழைக்கும் நீட்டிப்புகள் சேமிக்கப்பட்ட உள்நுழைவு நற்சான்றிதழ்களைப் படிக்கலாம் அல்லது தன்னிச்சையான குறியீட்டை இயக்கலாம்.\n\n இந்த நீட்டிப்பை நம்புவதன் மூலம், இந்த அபாயங்களை நீங்கள் ஏற்றுக்கொள்கிறீர்கள். - மிஓனில் கூடுதல் களஞ்சியங்களைச் சேர்க்கவும். இது \"index.min.json\" உடன் முடிவடையும் முகவரி ஆக இருக்க வேண்டும். + %s இல் கூடுதல் களஞ்சியங்களைச் சேர்க்கவும். இது \"index.min.json\" என்று முடிவடையும் முகவரி ஆக இருக்க வேண்டும். இந்த ரெப்போ ஏற்கனவே உள்ளது! ரெப்போவை நீக்கு தவறான ரெப்போ முகவரி @@ -372,7 +372,7 @@ முழு திரை குழாய் மண்டலங்கள் மேலடுக்கைக் காட்டு வாசகர் திறக்கப்படும்போது சுருக்கமாகக் காட்டுங்கள் - %1$s MS + %1$s மிநொ ஒவ்வொரு ஃபிளாச் உடன் ஃபிளாச் செயல்திறனை பாதிக்கிறது. பிட்மேப் வாசலைக் குறைப்பது வெற்று பட சிக்கல்களை சரிசெய்யவில்லை என்றால் மட்டுமே இயக்கவும் @@ -547,9 +547,9 @@ நூலகம் ஒத்திசைத்தல் நூலக ஒத்திசைவு முடிந்தது நெட்வொர்க்கிங் - REINDEX பதிவிறக்கங்கள் + மறுகுறியீடு பதிவிறக்கங்கள் பதிவிறக்கம் செய்யப்பட்ட அத்தியாயங்களை மறுபரிசீலனை செய்ய பயன்பாடு - பதிவிறக்கங்கள் அட்டவணை செல்லாதது + பதிவிறக்க அட்டவணையை மீண்டும் உருவாக்குகிறது தரவுத்தளத்தை அழிக்கவும் உங்கள் நூலகத்தில் சேமிக்கப்படாத உள்ளீடுகளுக்கான வரலாற்றை நீக்கவும் தரவுத்தளத்தில் %1$d நூலகமற்ற உள்ளீடுகள் @@ -753,7 +753,7 @@ சராசரி மதிப்பெண் பயன்படுத்தப்பட்டது குறைந்த சேமிப்பு இடம் காரணமாக அத்தியாயங்களை பதிவிறக்கம் செய்ய முடியவில்லை - எச்சரிக்கை: பெரிய மொத்த பதிவிறக்கங்கள் ஆதாரங்கள் மெதுவாகவும்/அல்லது மிஓனைத் தடுக்கவும் வழிவகுக்கும். மேலும் அறிய தட்டவும். + எச்சரிக்கை: பெரிய மொத்தப் பதிவிறக்கங்கள் மூலங்கள் மெதுவாக மாறுவதற்கும்/அல்லது %sஐத் தடுப்பதற்கும் வழிவகுக்கும். மேலும் அறிய தட்டவும். இதற்கில்லை %d.டி. நூலகத்தைப் புதுப்பித்தல்… (%s) @@ -892,4 +892,14 @@ நூலாசிரியர் கோப்பகத்தை உருவாக்குவதில் தோல்வி: %s பதிவிறக்க கோப்பகத்தை உருவாக்குவதில் தோல்வி + நன்கொடை + தாவலை மூடு + மங்கா விளக்கங்களில் படங்களை வழங்கவும் + ASCII அல்லாத கோப்பு பெயர்களை அனுமதிக்க வேண்டாம் + யூனிகோடை ஆதரிக்காத சில சேமிப்பக மீடியாவுடன் இணக்கத்தன்மையை உறுதி செய்கிறது. இது இயக்கப்பட்டால், ASCII அல்லாத எழுத்துக்களை அவற்றின் சிறிய UTF-8 எக்சாடெசிமல் பிரதிநிதித்துவங்களுடன் மாற்றுவதன் மூலம், நீங்கள் மூல மற்றும் மங்கா கோப்புறைகளை கைமுறையாக மறுபெயரிட வேண்டும். அத்தியாய கோப்புகளை மறுபெயரிட தேவையில்லை. + ஒரே நேரத்தில் மூல பதிவிறக்கங்கள் + ஒரே நேரத்தில் பக்க பதிவிறக்கங்கள் + ஒவ்வொரு மூலத்திற்கும் ஒரே நேரத்தில் பதிவிறக்கம் செய்யப்பட்ட பக்கங்கள் + புத்தகக்குறி செய்யப்பட்டது + நீக்கப்பட்ட ச்கேலேட்டர்களை வடிகட்டி diff --git a/i18n/src/commonMain/moko-resources/tr/plurals.xml b/i18n/src/commonMain/moko-resources/tr/plurals.xml index e4222dc4ce..4dc2a29a41 100644 --- a/i18n/src/commonMain/moko-resources/tr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/tr/plurals.xml @@ -2,7 +2,7 @@ %1$s dakika sonra - %1$s dakika sonra + %1$s dakika sonrası %1$d yeni bölüm @@ -70,7 +70,7 @@ Yarın - %1$d gün içinde + %1$d günleri içinde 1 sayfa diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 6c264329b1..9af7066614 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -910,6 +910,5 @@ Tek seferde indirilen kaynak sayısı Sekmeyi kapat Hariç tutulan Çevirenleri filtrele - Hesaplanıyor… Yer imli diff --git a/i18n/src/commonMain/moko-resources/vi/strings.xml b/i18n/src/commonMain/moko-resources/vi/strings.xml index a9c96b430b..eef64e56f6 100644 --- a/i18n/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n/src/commonMain/moko-resources/vi/strings.xml @@ -910,6 +910,5 @@ Các trang được tải xuống đồng thời theo từng nguồn Đóng thẻ Lọc nhóm dịch bị loại trừ - Đang tính… Đã đánh dấu diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 8fd311a76c..46a2e7c593 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -910,6 +910,5 @@ 每个图源同时下载的页数 关闭标签 筛选排除翻译组 - 计算中… 已添加书签 diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml index 028c2e7a09..c7a51d9fd9 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -901,6 +901,5 @@ 每個來源同時下載的頁數 關閉頁籤 篩選排除掃譯者 - 計算中… 已新增書籤 From e62d5bf3beb4ae533ae244be2c51e431cd9812dc Mon Sep 17 00:00:00 2001 From: Secozzi Date: Mon, 27 Apr 2026 23:25:46 +0200 Subject: [PATCH 03/59] Cleanup and rework build logic Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 45 ++++++--- app/{ => src/main}/shortcuts.xml | 12 ++- build.gradle.kts | 23 +++-- buildSrc/build.gradle.kts | 19 ---- buildSrc/settings.gradle.kts | 14 --- ...hon.android.application.compose.gradle.kts | 12 --- .../mihon.android.application.gradle.kts | 18 ---- .../main/kotlin/mihon.benchmark.gradle.kts | 14 --- .../main/kotlin/mihon.code.lint.gradle.kts | 35 ------- .../kotlin/mihon.library.compose.gradle.kts | 11 --- .../src/main/kotlin/mihon.library.gradle.kts | 13 --- .../kotlin/mihon/buildlogic/AndroidConfig.kt | 14 --- .../mihon/buildlogic/ProjectExtensions.kt | 98 ------------------- .../buildlogic/tasks/LocalesConfigTask.kt | 36 ------- core-metadata/build.gradle.kts | 12 +-- core-metadata/consumer-rules.pro | 0 core-metadata/proguard-rules.pro | 21 ---- core/archive/build.gradle.kts | 7 +- core/common/build.gradle.kts | 7 +- data/build.gradle.kts | 11 +-- data/consumer-rules.pro | 0 data/proguard-rules.pro | 21 ---- domain/build.gradle.kts | 12 +-- domain/consumer-rules.pro | 0 domain/proguard-rules.pro | 21 ---- gradle/build-logic/build.gradle.kts | 78 +++++++++++++++ gradle/build-logic/settings.gradle.kts | 23 +++++ .../main/kotlin/PluginAndroidApplication.kt | 16 +++ .../src/main/kotlin/PluginAndroidBase.kt | 21 ++++ .../src/main/kotlin/PluginAndroidLibrary.kt | 16 +++ .../src/main/kotlin/PluginAndroidTest.kt | 16 +++ .../src/main/kotlin/PluginComposeAndroid.kt | 33 +++++++ .../main/kotlin/PluginKotlinMultiplatform.kt | 36 +++++++ .../src/main/kotlin/PluginSpotless.kt | 44 +++++++++ .../main/kotlin/mihon/gradle}/BuildConfig.kt | 2 +- .../src/main/kotlin/mihon/gradle}/Commands.kt | 20 ++-- .../mihon/gradle/configurations/Android.kt | 32 ++++++ .../mihon/gradle/configurations/Kotlin.kt | 27 +++++ .../extensions/DependencyHandlerScope.kt | 32 ++++++ .../mihon/gradle/extensions/PluginManager.kt | 9 ++ .../kotlin/mihon/gradle/extensions/Project.kt | 40 ++++++++ .../gradle/tasks/GenerateLocalesConfigTask.kt | 46 +++++++++ .../tasks/ReplaceShortcutsPlaceholderTask.kt | 34 +++++++ gradle/libs.versions.toml | 11 ++- gradle/mihon.versions.toml | 16 +++ i18n-animiru/build.gradle.kts | 51 ++++------ i18n-aniyomi/build.gradle.kts | 51 ++++------ .../commonMain/moko-resources/am/strings.xml | 2 +- .../commonMain/moko-resources/ar/plurals.xml | 2 +- .../commonMain/moko-resources/be/strings.xml | 2 +- .../commonMain/moko-resources/bg/strings.xml | 2 +- .../commonMain/moko-resources/ca/plurals.xml | 2 +- .../commonMain/moko-resources/ceb/strings.xml | 2 +- .../commonMain/moko-resources/cs/plurals.xml | 2 +- .../commonMain/moko-resources/cv/strings.xml | 2 +- .../commonMain/moko-resources/da/strings.xml | 2 +- .../commonMain/moko-resources/de/plurals.xml | 2 +- .../commonMain/moko-resources/el/strings.xml | 2 +- .../commonMain/moko-resources/eo/strings.xml | 2 +- .../commonMain/moko-resources/es/plurals.xml | 2 +- .../commonMain/moko-resources/eu/strings.xml | 2 +- .../commonMain/moko-resources/fa/plurals.xml | 2 +- .../commonMain/moko-resources/fi/plurals.xml | 2 +- .../commonMain/moko-resources/fil/plurals.xml | 2 +- .../commonMain/moko-resources/fr/plurals.xml | 2 +- .../commonMain/moko-resources/gl/strings.xml | 2 +- .../commonMain/moko-resources/he/plurals.xml | 2 +- .../commonMain/moko-resources/hr/plurals.xml | 2 +- .../commonMain/moko-resources/hu/strings.xml | 2 +- .../commonMain/moko-resources/in/plurals.xml | 2 +- .../commonMain/moko-resources/it/plurals.xml | 2 +- .../commonMain/moko-resources/jv/strings.xml | 2 +- .../moko-resources/ka-rGE/strings.xml | 2 +- .../commonMain/moko-resources/km/strings.xml | 2 +- .../commonMain/moko-resources/kn/strings.xml | 2 +- .../commonMain/moko-resources/ko/plurals.xml | 2 +- .../commonMain/moko-resources/lv/strings.xml | 2 +- .../commonMain/moko-resources/mr/strings.xml | 2 +- .../commonMain/moko-resources/my/strings.xml | 2 +- .../moko-resources/nb-rNO/strings.xml | 2 +- .../commonMain/moko-resources/ne/strings.xml | 2 +- .../commonMain/moko-resources/pl/plurals.xml | 2 +- .../moko-resources/pt-rBR/plurals.xml | 2 +- .../commonMain/moko-resources/pt/plurals.xml | 2 +- .../commonMain/moko-resources/ro/strings.xml | 2 +- .../commonMain/moko-resources/sa/strings.xml | 2 +- .../commonMain/moko-resources/sah/strings.xml | 2 +- .../commonMain/moko-resources/sc/strings.xml | 2 +- .../commonMain/moko-resources/sdh/strings.xml | 2 +- .../commonMain/moko-resources/sq/strings.xml | 2 +- .../commonMain/moko-resources/te/strings.xml | 2 +- .../commonMain/moko-resources/th/strings.xml | 2 +- .../commonMain/moko-resources/tr/plurals.xml | 2 +- .../commonMain/moko-resources/uk/plurals.xml | 2 +- .../commonMain/moko-resources/ur/strings.xml | 2 +- .../commonMain/moko-resources/uz/strings.xml | 2 +- .../moko-resources/zh-rCN/plurals.xml | 2 +- i18n/build.gradle.kts | 51 ++++------ .../commonMain/moko-resources/he/plurals.xml | 2 +- .../commonMain/moko-resources/kk/plurals.xml | 2 +- .../commonMain/moko-resources/pl/plurals.xml | 2 +- .../commonMain/moko-resources/sa/plurals.xml | 2 +- .../commonMain/moko-resources/sc/plurals.xml | 2 +- .../commonMain/moko-resources/tt/strings.xml | 2 +- macrobenchmark/build.gradle.kts | 2 +- presentation-core/build.gradle.kts | 12 +-- presentation-core/consumer-rules.pro | 0 presentation-core/proguard-rules.pro | 21 ---- presentation-widget/build.gradle.kts | 12 +-- presentation-widget/consumer-rules.pro | 0 presentation-widget/proguard-rules.pro | 21 ---- settings.gradle.kts | 16 ++- source-api/build.gradle.kts | 50 +++++----- source-local/build.gradle.kts | 49 ++++------ source-local/consumer-rules.pro | 0 source-local/proguard-rules.pro | 21 ---- 116 files changed, 777 insertions(+), 720 deletions(-) rename app/{ => src/main}/shortcuts.xml (88%) delete mode 100644 buildSrc/build.gradle.kts delete mode 100644 buildSrc/settings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.android.application.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.benchmark.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.library.compose.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon.library.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt delete mode 100644 buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt delete mode 100644 buildSrc/src/main/kotlin/mihon/buildlogic/tasks/LocalesConfigTask.kt delete mode 100644 core-metadata/consumer-rules.pro delete mode 100644 core-metadata/proguard-rules.pro delete mode 100644 data/consumer-rules.pro delete mode 100644 data/proguard-rules.pro delete mode 100644 domain/consumer-rules.pro delete mode 100644 domain/proguard-rules.pro create mode 100644 gradle/build-logic/build.gradle.kts create mode 100644 gradle/build-logic/settings.gradle.kts create mode 100644 gradle/build-logic/src/main/kotlin/PluginAndroidApplication.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginAndroidLibrary.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginAndroidTest.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt create mode 100644 gradle/build-logic/src/main/kotlin/PluginSpotless.kt rename {buildSrc/src/main/kotlin/mihon/buildlogic => gradle/build-logic/src/main/kotlin/mihon/gradle}/BuildConfig.kt (95%) rename {buildSrc/src/main/kotlin/mihon/buildlogic => gradle/build-logic/src/main/kotlin/mihon/gradle}/Commands.kt (64%) create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Kotlin.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/DependencyHandlerScope.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/PluginManager.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/ReplaceShortcutsPlaceholderTask.kt create mode 100644 gradle/mihon.versions.toml delete mode 100644 presentation-core/consumer-rules.pro delete mode 100644 presentation-core/proguard-rules.pro delete mode 100644 presentation-widget/consumer-rules.pro delete mode 100644 presentation-widget/proguard-rules.pro delete mode 100644 source-local/consumer-rules.pro delete mode 100644 source-local/proguard-rules.pro diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 58b9dc2fca..837816b846 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,18 +1,18 @@ -import mihon.buildlogic.Config -import mihon.buildlogic.getBuildTime -import mihon.buildlogic.getCommitCount -import mihon.buildlogic.getGitSha +import mihon.gradle.Config +import mihon.gradle.getBuildTime +import mihon.gradle.getLatestCommitCount +import mihon.gradle.getLatestCommitSha +import mihon.gradle.tasks.ReplaceShortcutsPlaceholderTask plugins { - id("mihon.android.application") - id("mihon.android.application.compose") - id("com.github.zellius.shortcut-helper") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.application) + alias(mihonx.plugins.compose) + alias(mihonx.plugins.spotless) + alias(libs.plugins.aboutLibraries) + alias(libs.plugins.kotlin.serialization) } -shortcutHelper.setFilePath("./shortcuts.xml") - android { namespace = "eu.kanade.tachiyomi" @@ -22,9 +22,9 @@ android { versionCode = 140 versionName = "0.19.7.3" - buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") - buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") - buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"") + buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"") + buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"") + buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"") buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -33,7 +33,7 @@ android { buildTypes { val debug by getting { applicationIdSuffix = ".dev" - versionNameSuffix = "-${getCommitCount()}" + versionNameSuffix = "-${getLatestCommitCount()}" isPseudoLocalesEnabled = true } val release by getting { @@ -42,7 +42,7 @@ android { proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro") - buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"") + buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = true)}\"") } val commonMatchingFallbacks = listOf(release.name) @@ -57,7 +57,7 @@ android { matchingFallbacks.addAll(commonMatchingFallbacks) - buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"") + buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"") } create("benchmark") { initWith(release) @@ -331,6 +331,19 @@ dependencies { } androidComponents { + onVariants { variant -> + val resSource = variant.sources.res ?: return@onVariants + + val variantName = variant.name.replaceFirstChar { it.uppercase() } + val replaceShortcutsPlaceholderTask = tasks.register( + "replace${variantName}ShortcutPlaceholder", + ) { + applicationId.set(variant.applicationId) + shortcutsFile.set(projectDir.resolve("src/main/shortcuts.xml")) + } + resSource.addGeneratedSourceDirectory(replaceShortcutsPlaceholderTask) { it.outputDir } + } + onVariants(selector().withFlavor("default" to "standard")) { // Only excluding in standard flavor because this breaks // Layout Inspector's Compose tree diff --git a/app/shortcuts.xml b/app/src/main/shortcuts.xml similarity index 88% rename from app/shortcuts.xml rename to app/src/main/shortcuts.xml index 48936aafe6..cf3c196aec 100644 --- a/app/shortcuts.xml +++ b/app/src/main/shortcuts.xml @@ -8,7 +8,8 @@ android:shortcutShortLabel="@string/label_library"> + android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" + android:targetPackage="${applicationId}" /> + android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" + android:targetPackage="${applicationId}" /> + android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" + android:targetPackage="${applicationId}" /> + android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" + android:targetPackage="${applicationId}" /> diff --git a/build.gradle.kts b/build.gradle.kts index 9f5e41bf0d..bfa5d8e0ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,16 +1,21 @@ -buildscript { - dependencies { - classpath(libs.android.shortcut.gradle) - } -} - plugins { - alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.aboutLibraries) apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.android.test) apply false + alias(libs.plugins.kotlin.compose.compiler) apply false + alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.moko.resources) apply false alias(libs.plugins.sqldelight) apply false + + alias(mihonx.plugins.spotless) } -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) +val buildLogic: IncludedBuild = gradle.includedBuild("build-logic") +tasks { + listOf("clean", "spotlessApply", "spotlessCheck").forEach { task -> + named(task) { + dependsOn(buildLogic.task(":$task")) + } + } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 9b565a5d4e..0000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - `kotlin-dsl` -} - -dependencies { - implementation(libs.android.gradle) - implementation(libs.kotlin.gradle) - implementation(libs.kotlin.compose.compiler.gradle) - implementation(libs.spotless.gradle) - implementation(gradleApi()) - - implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) -} - -repositories { - gradlePluginPortal() - mavenCentral() - google() -} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index 952faa1848..0000000000 --- a/buildSrc/settings.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - // AY --> - create("aniyomilibs") { - from(files("../gradle/aniyomi.versions.toml")) - } - // <-- AY - } -} - -rootProject.name = "mihon-buildSrc" diff --git a/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts b/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts deleted file mode 100644 index 767f758cbe..0000000000 --- a/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -import mihon.buildlogic.configureCompose - -plugins { - id("com.android.application") - kotlin("android") - - id("mihon.code.lint") -} - -android { - configureCompose(this) -} diff --git a/buildSrc/src/main/kotlin/mihon.android.application.gradle.kts b/buildSrc/src/main/kotlin/mihon.android.application.gradle.kts deleted file mode 100644 index 080f9ca643..0000000000 --- a/buildSrc/src/main/kotlin/mihon.android.application.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -import mihon.buildlogic.AndroidConfig -import mihon.buildlogic.configureAndroid -import mihon.buildlogic.configureTest - -plugins { - id("com.android.application") - kotlin("android") - - id("mihon.code.lint") -} - -android { - defaultConfig { - targetSdk = AndroidConfig.TARGET_SDK - } - configureAndroid(this) - configureTest() -} diff --git a/buildSrc/src/main/kotlin/mihon.benchmark.gradle.kts b/buildSrc/src/main/kotlin/mihon.benchmark.gradle.kts deleted file mode 100644 index 50c47f2801..0000000000 --- a/buildSrc/src/main/kotlin/mihon.benchmark.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -import mihon.buildlogic.configureAndroid -import mihon.buildlogic.configureTest - -plugins { - id("com.android.test") - kotlin("android") - - id("mihon.code.lint") -} - -android { - configureAndroid(this) - configureTest() -} diff --git a/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts b/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts deleted file mode 100644 index ad36c03345..0000000000 --- a/buildSrc/src/main/kotlin/mihon.code.lint.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -import org.gradle.accessors.dm.LibrariesForLibs - -plugins { - id("com.diffplug.spotless") -} - -val libs = the() - -val xmlFormatExclude = buildList(2) { - add("**/build/**/*.xml") - - projectDir - .resolve("src/commonMain/moko-resources") - .takeIf { it.isDirectory } - ?.let(::fileTree) - ?.matching { exclude("/base/**") } - ?.let(::add) -} - .toTypedArray() - -spotless { - kotlin { - target("**/*.kt", "**/*.kts") - targetExclude("**/build/**/*.kt") - ktlint(libs.ktlint.bom.get().version) - trimTrailingWhitespace() - endWithNewline() - } - format("xml") { - target("**/*.xml") - targetExclude(*xmlFormatExclude) - trimTrailingWhitespace() - endWithNewline() - } -} diff --git a/buildSrc/src/main/kotlin/mihon.library.compose.gradle.kts b/buildSrc/src/main/kotlin/mihon.library.compose.gradle.kts deleted file mode 100644 index 7b377ce4ce..0000000000 --- a/buildSrc/src/main/kotlin/mihon.library.compose.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -import mihon.buildlogic.configureCompose - -plugins { - id("com.android.library") - - id("mihon.code.lint") -} - -android { - configureCompose(this) -} diff --git a/buildSrc/src/main/kotlin/mihon.library.gradle.kts b/buildSrc/src/main/kotlin/mihon.library.gradle.kts deleted file mode 100644 index 4cf5f6efc7..0000000000 --- a/buildSrc/src/main/kotlin/mihon.library.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -import mihon.buildlogic.configureAndroid -import mihon.buildlogic.configureTest - -plugins { - id("com.android.library") - - id("mihon.code.lint") -} - -android { - configureAndroid(this) - configureTest() -} diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt deleted file mode 100644 index f97970c4c8..0000000000 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt +++ /dev/null @@ -1,14 +0,0 @@ -package mihon.buildlogic - -import org.gradle.api.JavaVersion as GradleJavaVersion -import org.jetbrains.kotlin.gradle.dsl.JvmTarget as KotlinJvmTarget - -object AndroidConfig { - const val COMPILE_SDK = 36 - const val TARGET_SDK = 36 - const val MIN_SDK = 26 - - // https://youtrack.jetbrains.com/issue/KT-66995/JvmTarget-and-JavaVersion-compatibility-for-easier-JVM-version-setup - val JavaVersion = GradleJavaVersion.VERSION_17 - val JvmTarget = KotlinJvmTarget.JVM_17 -} diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt deleted file mode 100644 index 069544e21c..0000000000 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt +++ /dev/null @@ -1,98 +0,0 @@ -package mihon.buildlogic - -import com.android.build.api.dsl.CommonExtension -import org.gradle.accessors.dm.LibrariesForLibs -import org.gradle.api.Project -import org.gradle.api.tasks.testing.Test -import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.provideDelegate -import org.gradle.kotlin.dsl.the -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.File - -val Project.libs get() = the() - -internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { - commonExtension.apply { - compileSdk = AndroidConfig.COMPILE_SDK - - defaultConfig { - minSdk = AndroidConfig.MIN_SDK - } - - compileOptions { - sourceCompatibility = AndroidConfig.JavaVersion - targetCompatibility = AndroidConfig.JavaVersion - isCoreLibraryDesugaringEnabled = true - } - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(AndroidConfig.JvmTarget) - freeCompilerArgs.addAll( - // AM --> - "-Xwhen-guards", - // <-- AM - "-Xcontext-parameters", - "-opt-in=kotlin.RequiresOptIn", - ) - - // Treat all Kotlin warnings as errors (disabled by default) - // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties - val warningsAsErrors: String? by project - allWarningsAsErrors.set(warningsAsErrors.toBoolean()) - - } - } - - dependencies { - "coreLibraryDesugaring"(libs.android.desugar) - } -} - -internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, *, *, *>) { - pluginManager.apply(libs.plugins.kotlin.compose.compiler.get().pluginId) - - commonExtension.apply { - buildFeatures { - compose = true - } - - dependencies { - "implementation"(platform(libs.androidx.compose.bom)) - } - } - - extensions.configure { - val enableMetrics = project.providers.gradleProperty("enableComposeCompilerMetrics").orNull.toBoolean() - val enableReports = project.providers.gradleProperty("enableComposeCompilerReports").orNull.toBoolean() - - val rootBuildDir = rootProject.layout.buildDirectory.asFile.get() - val relativePath = projectDir.relativeTo(rootDir) - - if (enableMetrics) { - rootBuildDir.resolve("compose-metrics").resolve(relativePath).let(metricsDestination::set) - } - - if (enableReports) { - rootBuildDir.resolve("compose-reports").resolve(relativePath).let(reportsDestination::set) - } - } - -} - -internal fun Project.configureTest() { - tasks.withType { - useJUnitPlatform() - testLogging { - events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - } - } -} - -val Project.generatedBuildDir: File get() = project.layout.buildDirectory.asFile.get().resolve("generated/mihon") diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/tasks/LocalesConfigTask.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/tasks/LocalesConfigTask.kt deleted file mode 100644 index 329e4a769d..0000000000 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/tasks/LocalesConfigTask.kt +++ /dev/null @@ -1,36 +0,0 @@ -package mihon.buildlogic.tasks - -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.TaskProvider -import java.io.File - -private val emptyResourcesElement = "\\s*|".toRegex() - -fun Project.getLocalesConfigTask(outputResourceDir: File): TaskProvider { - return tasks.register("generateLocalesConfig") { - val locales = fileTree("$projectDir/src/commonMain/moko-resources/") - .matching { include("**/strings.xml") } - .filterNot { it.readText().contains(emptyResourcesElement) } - .map { - it.parentFile.name - .replace("base", "en") - .replace("-r", "-") - .replace("+", "-") - } - .sorted() - .joinToString("\n") { "| " } - - val content = """ - | - | - $locales - | - """.trimMargin() - - outputResourceDir.resolve("xml/locales_config.xml").apply { - parentFile.mkdirs() - writeText(content) - } - } -} diff --git a/core-metadata/build.gradle.kts b/core-metadata/build.gradle.kts index 358d4f6d04..2c528e5a5e 100644 --- a/core-metadata/build.gradle.kts +++ b/core-metadata/build.gradle.kts @@ -1,16 +1,12 @@ plugins { - id("mihon.library") - kotlin("android") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) } android { namespace = "tachiyomi.core.metadata" - - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } } dependencies { diff --git a/core-metadata/consumer-rules.pro b/core-metadata/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-metadata/proguard-rules.pro b/core-metadata/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/core-metadata/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/archive/build.gradle.kts b/core/archive/build.gradle.kts index c2208a695a..64f1ff7961 100644 --- a/core/archive/build.gradle.kts +++ b/core/archive/build.gradle.kts @@ -1,7 +1,8 @@ plugins { - id("mihon.library") - kotlin("android") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) } android { diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index d327170438..42a3f9df51 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,7 +1,8 @@ plugins { - id("mihon.library") - kotlin("android") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) } android { diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 3e4abe5f8d..38386f7c3d 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - id("mihon.library") - kotlin("android") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.sqldelight) } android { namespace = "tachiyomi.data" - defaultConfig { - consumerProguardFiles("consumer-rules.pro") - } - sqldelight { databases { create("Database") { diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/data/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index f281569236..ac3997c268 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,16 +1,12 @@ plugins { - id("mihon.library") - kotlin("android") - kotlin("plugin.serialization") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) } android { namespace = "tachiyomi.domain" - - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } } kotlin { diff --git a/domain/consumer-rules.pro b/domain/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/domain/proguard-rules.pro b/domain/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/domain/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/gradle/build-logic/build.gradle.kts b/gradle/build-logic/build.gradle.kts new file mode 100644 index 0000000000..cb4968fdee --- /dev/null +++ b/gradle/build-logic/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.samWithReceiver) + alias(libs.plugins.spotless) + `java-gradle-plugin` +} + +// Configuration should be synced with [/gradle/build-logic/src/main/kotlin/PluginSpotless.kt] +val ktlintVersion = libs.ktlint.bom.get().version +val editorConfigFile = rootProject.file("../../.editorconfig") +spotless { + kotlin { + target("src/**/*.kt") + ktlint(ktlintVersion).setEditorConfigPath(editorConfigFile) + trimTrailingWhitespace() + endWithNewline() + } + + kotlinGradle { + target("*.kts") + ktlint(ktlintVersion).setEditorConfigPath(editorConfigFile) + trimTrailingWhitespace() + endWithNewline() + } +} + +dependencies { + compileOnly(gradleKotlinDsl()) + compileOnly(libs.android.gradle) + compileOnly(libs.kotlin.compose.compiler.gradle) + compileOnly(libs.kotlin.gradle) + implementation(libs.spotless.gradle) + implementation(libs.tapmoc.gradle) + + // These allow us to reference the dependency catalog inside our compiled plugins + compileOnly(files(libs::class.java.superclass.protectionDomain.codeSource.location)) + compileOnly(files(mihonx::class.java.superclass.protectionDomain.codeSource.location)) + // AM --> + compileOnly(files(aniyomilibs::class.java.superclass.protectionDomain.codeSource.location)) + // <-- AM +} + +samWithReceiver { + annotation("org.gradle.api.HasImplicitReceiver") +} + +gradlePlugin { + plugins { + register("android-application") { + id = mihonx.plugins.android.application.get().pluginId + implementationClass = "PluginAndroidApplication" + } + register("android-base") { + id = mihonx.plugins.android.base.get().pluginId + implementationClass = "PluginAndroidBase" + } + register("android-library") { + id = mihonx.plugins.android.library.get().pluginId + implementationClass = "PluginAndroidLibrary" + } + register("android-test") { + id = mihonx.plugins.android.test.get().pluginId + implementationClass = "PluginAndroidTest" + } + register("compose-android") { + id = mihonx.plugins.compose.get().pluginId + implementationClass = "PluginComposeAndroid" + } + register("kotlin-multiplatform") { + id = mihonx.plugins.kotlin.multiplatform.get().pluginId + implementationClass = "PluginKotlinMultiplatform" + } + register("spotless") { + id = mihonx.plugins.spotless.get().pluginId + implementationClass = "PluginSpotless" + } + } +} diff --git a/gradle/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts new file mode 100644 index 0000000000..fc1cffc513 --- /dev/null +++ b/gradle/build-logic/settings.gradle.kts @@ -0,0 +1,23 @@ +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + create("mihonx") { + from(files("../mihon.versions.toml")) + } + // AM --> + create("aniyomilibs") { + from(files("../aniyomi.versions.toml")) + } + // <-- AM + } +} + +rootProject.name = "build-logic" diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidApplication.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidApplication.kt new file mode 100644 index 0000000000..1348f92cc1 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidApplication.kt @@ -0,0 +1,16 @@ +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project + +@Suppress("UNUSED") +class PluginAndroidApplication : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.android.application) + alias(mihonx.plugins.android.base) + } + } +} diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt new file mode 100644 index 0000000000..9f2022dae8 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt @@ -0,0 +1,21 @@ +import mihon.gradle.configurations.configureAndroid +import mihon.gradle.configurations.configureKotlin +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project + +@Suppress("UNUSED") +class PluginAndroidBase : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.kotlin.android) + } + + configureKotlin() + configureTest() + configureAndroid() + } +} diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidLibrary.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidLibrary.kt new file mode 100644 index 0000000000..044301f4e6 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidLibrary.kt @@ -0,0 +1,16 @@ +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project + +@Suppress("UNUSED") +class PluginAndroidLibrary : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.android.library) + alias(mihonx.plugins.android.base) + } + } +} diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidTest.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidTest.kt new file mode 100644 index 0000000000..91c2027fc1 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidTest.kt @@ -0,0 +1,16 @@ +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project + +@Suppress("UNUSED") +class PluginAndroidTest : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.android.test) + alias(mihonx.plugins.android.base) + } + } +} diff --git a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt new file mode 100644 index 0000000000..24580206a4 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt @@ -0,0 +1,33 @@ +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.android +import mihon.gradle.extensions.api +import mihon.gradle.extensions.debugApi +import mihon.gradle.extensions.implementation +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +@Suppress("UNUSED") +class PluginComposeAndroid : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.kotlin.compose.compiler) + } + + android { + buildFeatures { + compose = true + } + } + + dependencies { + implementation(platform(libs.androidx.compose.bom)) + + // Compose @Preview tooling + api(libs.androidx.compose.uiToolingPreview) + debugApi(libs.androidx.compose.uiTooling) + } + } +} diff --git a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt new file mode 100644 index 0000000000..7f37f863c7 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt @@ -0,0 +1,36 @@ +import mihon.gradle.configurations.configureAndroid +import mihon.gradle.configurations.configureKotlin +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +@Suppress("UNUSED") +class PluginKotlinMultiplatform : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + } + + configureKotlin() + configureTest() + configureAndroid() + + kotlin { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate() + + androidTarget() + } + } +} + +private fun Project.kotlin(block: KotlinMultiplatformExtension.() -> Unit) { + extensions.configure(block) +} diff --git a/gradle/build-logic/src/main/kotlin/PluginSpotless.kt b/gradle/build-logic/src/main/kotlin/PluginSpotless.kt new file mode 100644 index 0000000000..6584c9f86d --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/PluginSpotless.kt @@ -0,0 +1,44 @@ +import com.diffplug.gradle.spotless.SpotlessExtension +import mihon.gradle.extensions.alias +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.plugins +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +@Suppress("UNUSED") +class PluginSpotless : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins { + alias(libs.plugins.spotless) + } + + // Configuration should be synced with [/gradle/build-logic/build.gradle.kts] + val ktlintVersion = libs.ktlint.bom.get().version + spotless { + kotlin { + target("src/**/*.kt") + ktlint(ktlintVersion) + trimTrailingWhitespace() + endWithNewline() + } + + kotlinGradle { + target("*.kts") + ktlint(ktlintVersion) + trimTrailingWhitespace() + endWithNewline() + } + + format("xml") { + target("src/**/*.xml") + trimTrailingWhitespace() + endWithNewline() + } + } + } +} + +private fun Project.spotless(block: SpotlessExtension.() -> Unit) { + extensions.configure(block) +} diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/BuildConfig.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/BuildConfig.kt similarity index 95% rename from buildSrc/src/main/kotlin/mihon/buildlogic/BuildConfig.kt rename to gradle/build-logic/src/main/kotlin/mihon/gradle/BuildConfig.kt index c838003bf2..e150dfe2de 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/BuildConfig.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/BuildConfig.kt @@ -1,4 +1,4 @@ -package mihon.buildlogic +package mihon.gradle import org.gradle.api.Project diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/Commands.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/Commands.kt similarity index 64% rename from buildSrc/src/main/kotlin/mihon/buildlogic/Commands.kt rename to gradle/build-logic/src/main/kotlin/mihon/gradle/Commands.kt index f3470f957e..c56a3e2693 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/Commands.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/Commands.kt @@ -1,4 +1,4 @@ -package mihon.buildlogic +package mihon.gradle import org.gradle.api.Project import java.time.Instant @@ -8,33 +8,33 @@ import java.time.format.DateTimeFormatter // Git is needed in your system PATH for these commands to work. // If it's not installed, you can return a random value as a workaround -fun Project.getCommitCount(): String { - return runCommand("git rev-list --count HEAD") +fun Project.getLatestCommitCount(): String { + return exec("git rev-list --count HEAD") // return "1" } -fun Project.getGitSha(): String { - return runCommand("git rev-parse --short HEAD") +fun Project.getLatestCommitSha(): String { + return exec("git rev-parse --short HEAD") // return "1" } private val BUILD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") /** - * @param useLastCommitTime If `true`, the build time is based on the timestamp of the last Git commit; + * @param useLatestCommitTime If `true`, the build time is based on the timestamp of the last Git commit; * otherwise, the current time is used. Both are in UTC. * @return A formatted string representing the build time. The format used is defined by [BUILD_TIME_FORMATTER]. */ -fun Project.getBuildTime(useLastCommitTime: Boolean): String { - return if (useLastCommitTime) { - val epoch = runCommand("git log -1 --format=%ct").toLong() +fun Project.getBuildTime(useLatestCommitTime: Boolean): String { + return if (useLatestCommitTime) { + val epoch = exec("git log -1 --format=%ct").toLong() Instant.ofEpochSecond(epoch).atOffset(ZoneOffset.UTC).format(BUILD_TIME_FORMATTER) } else { LocalDateTime.now(ZoneOffset.UTC).format(BUILD_TIME_FORMATTER) } } -private fun Project.runCommand(command: String): String { +fun Project.exec(command: String): String { return providers.exec { commandLine = command.split(" ") } diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt new file mode 100644 index 0000000000..2fb978311e --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt @@ -0,0 +1,32 @@ +package mihon.gradle.configurations + +import com.android.build.api.dsl.ApplicationDefaultConfig +import mihon.gradle.extensions.android +import mihon.gradle.extensions.coreLibraryDesugaring +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +fun Project.configureAndroid() { + android { + defaultConfig { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + if (this is ApplicationDefaultConfig) { + targetSdk = mihonx.versions.android.sdk.target.get().toInt() + } + + ndkVersion = mihonx.versions.android.ndk.get() + } + + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + + compileOptions { + isCoreLibraryDesugaringEnabled = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) + } +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Kotlin.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Kotlin.kt new file mode 100644 index 0000000000..aea762db69 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Kotlin.kt @@ -0,0 +1,27 @@ +package mihon.gradle.configurations + +import mihon.gradle.extensions.mihonx +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.HasConfigurableKotlinCompilerOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions +import tapmoc.configureJavaCompatibility + +fun Project.configureKotlin() { + configureJavaCompatibility(mihonx.versions.java.get().toInt()) + + kotlin { + compilerOptions { + freeCompilerArgs.add("-Xcontext-parameters") + } + } +} + +private fun Project.kotlin(block: KotlinBaseExtension.() -> Unit) { + extensions.configure(block) +} + +private fun KotlinBaseExtension.compilerOptions(block: KotlinCommonCompilerOptions.() -> Unit) { + if (this is HasConfigurableKotlinCompilerOptions<*>) compilerOptions(block) +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/DependencyHandlerScope.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/DependencyHandlerScope.kt new file mode 100644 index 0000000000..9627b2145a --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/DependencyHandlerScope.kt @@ -0,0 +1,32 @@ +package mihon.gradle.extensions + +import org.gradle.api.Project +import org.gradle.api.artifacts.ExternalModuleDependencyBundle +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.DependencyHandlerScope + +fun DependencyHandlerScope.api(dependencyNotation: Provider) { + add("api", dependencyNotation) +} + +fun DependencyHandlerScope.coreLibraryDesugaring(dependencyNotation: Provider) { + add("coreLibraryDesugaring", dependencyNotation) +} + +fun DependencyHandlerScope.debugApi(dependencyNotation: Provider) { + add("debugApi", dependencyNotation) +} + +@JvmName("implementationBundle") +fun DependencyHandlerScope.implementation(dependencyNotation: Provider) { + add("implementation", dependencyNotation) +} + +fun DependencyHandlerScope.implementation(dependencyNotation: Provider) { + add("implementation", dependencyNotation) +} + +fun DependencyHandlerScope.implementation(dependencyNotation: Project) { + add("implementation", dependencyNotation) +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/PluginManager.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/PluginManager.kt new file mode 100644 index 0000000000..5ffc17134d --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/PluginManager.kt @@ -0,0 +1,9 @@ +package mihon.gradle.extensions + +import org.gradle.api.plugins.PluginManager +import org.gradle.api.provider.Provider +import org.gradle.plugin.use.PluginDependency + +fun PluginManager.alias(notation: Provider) { + apply(notation.get().pluginId) +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt new file mode 100644 index 0000000000..f3621c61d3 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt @@ -0,0 +1,40 @@ +package mihon.gradle.extensions + +import com.android.build.api.dsl.CommonExtension +import com.android.build.gradle.BaseExtension +import org.gradle.accessors.dm.LibrariesForAniyomilibs +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.accessors.dm.LibrariesForMihonx +import org.gradle.api.Project +import org.gradle.api.plugins.PluginManager +import org.gradle.api.tasks.testing.Test +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType + +internal val Project.libs get() = the() +internal val Project.mihonx get() = the() + +// AM --> +internal val Project.aniyomilibs get() = the() +// <-- AM + +internal fun Project.plugins(block: PluginManager.() -> Unit) { + pluginManager.apply(block) +} + +internal fun Project.android(block: CommonExtension<*, *, *, *, *, *>.() -> Unit) { + extensions.configure { + if (this is CommonExtension<*, *, *, *, *, *>) apply(block) + } +} + +fun Project.configureTest() { + tasks.withType { + useJUnitPlatform() + testLogging { + events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt new file mode 100644 index 0000000000..d963dac0f6 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt @@ -0,0 +1,46 @@ +package mihon.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +abstract class GenerateLocalesConfigTask : DefaultTask() { + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @TaskAction + fun action() { + val locales = project.fileTree("src/commonMain/moko-resources") { + matching { include("**/strings.xml") } + } + .asSequence() + .filterNot { it.readText().contains(emptyResourcesElement) } + .map { + it.parentFile.name + .replace("base", "en") + .replace("-r", "-") + .replace("+", "-") + } + .distinct() + .sorted() + .joinToString("\n") { "| " } + + val content = """ + | + | + $locales + | + """.trimMargin() + + outputDir.get().file("xml/locales_config.xml").asFile.apply { + parentFile.mkdirs() + writeText(content) + } + } + + companion object { + private val emptyResourcesElement = "\\s*|".toRegex() + } +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/ReplaceShortcutsPlaceholderTask.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/ReplaceShortcutsPlaceholderTask.kt new file mode 100644 index 0000000000..1cde0423c6 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/ReplaceShortcutsPlaceholderTask.kt @@ -0,0 +1,34 @@ +package mihon.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +abstract class ReplaceShortcutsPlaceholderTask : DefaultTask() { + + @get:Input + abstract val applicationId: Property + + @get:InputFile + abstract val shortcutsFile: RegularFileProperty + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @TaskAction + fun action() { + val content = shortcutsFile.asFile.get() + .readText() + .replace($$"${applicationId}", applicationId.get()) + + outputDir.get().file("xml/shortcuts.xml").asFile.apply { + parentFile.mkdirs() + writeText(content) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 412629b094..ab7e57f923 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,6 @@ aboutLibraries = "13.2.1" android-desugar = "2.1.5" android-gradle = "8.13.2" -android-shortcut = "0.1.2" androidx-activity-compose = "1.13.0" androidx-annotation = "1.9.1" androidx-appCompat = "1.7.1" @@ -68,6 +67,7 @@ sqldelight-androidx-driver = "0.0.17" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" +tapmoc = "0.4.0" unifile = "e0def6b3dc" voyager = "1.1.0-beta03" xmlutil = "0.91.3" @@ -76,7 +76,6 @@ xmlutil = "0.91.3" aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutLibraries" } android-desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugar" } android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } -android-shortcut-gradle = { module = "com.github.zellius:android-shortcut-gradle-plugin", version.ref = "android-shortcut" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } androidx-appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appCompat" } @@ -175,6 +174,7 @@ sqldelight-sqliteDialect338 = { module = "app.cash.sqldelight:sqlite-3-38-dialec stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version.ref = "stringSimilarity" } subsamplingScaleImageView = { module = "com.github.tachiyomiorg:subsampling-scale-image-view", version.ref = "subsamplingScaleImageView" } swipe = { module = "me.saket.swipe:swipe", version.ref = "swipe" } +tapmoc-gradle = { module = "com.gradleup.tapmoc:tapmoc-gradle-plugin", version.ref = "tapmoc" } unifile = { module = "com.github.tachiyomiorg:unifile", version.ref = "unifile" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } @@ -185,12 +185,19 @@ xmlutil-serialization = { module = "io.github.pdvrieze.xmlutil:serialization", v [plugins] aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } +android-application = { id = "com.android.application", version.ref = "android-gradle" } +android-library = { id = "com.android.library", version.ref = "android-gradle" } +android-test = { id = "com.android.test", version.ref = "android-gradle" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-gradle" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-gradle" } +kotlin-samWithReceiver = { id = "org.jetbrains.kotlin.plugin.sam.with.receiver", version.ref = "kotlin-gradle" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-gradle" } moko-resources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko-resources" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } [bundles] diff --git a/gradle/mihon.versions.toml b/gradle/mihon.versions.toml new file mode 100644 index 0000000000..ac68cd4e82 --- /dev/null +++ b/gradle/mihon.versions.toml @@ -0,0 +1,16 @@ +[versions] +# Used in [/gradle/build-config/src/main/kotlin/PluginKotlinMultiplatform.kt] +android-sdk-min = "26" +android-sdk-compile = "36" +android-sdk-target = "36" +android-ndk = "29.0.14206865" +java = "17" + +[plugins] +android-application = { id = "mihon.plugins.android.application" } +android-base = { id = "mihon.plugins.android.base" } +android-library = { id = "mihon.plugins.android.library" } +android-test = { id = "mihon.plugins.android.test" } +compose = { id = "mihon.plugins.compose.android" } +kotlin-multiplatform = { id = "mihon.plugins.kotlin.multiplatform" } +spotless = { id = "mihon.plugins.spotless" } diff --git a/i18n-animiru/build.gradle.kts b/i18n-animiru/build.gradle.kts index ef86676b3f..c84ed9423b 100644 --- a/i18n-animiru/build.gradle.kts +++ b/i18n-animiru/build.gradle.kts @@ -1,24 +1,22 @@ -import mihon.buildlogic.generatedBuildDir -import mihon.buildlogic.tasks.getLocalesConfigTask +import mihon.gradle.tasks.GenerateLocalesConfigTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { - id("mihon.library") - kotlin("multiplatform") + alias(mihonx.plugins.kotlin.multiplatform) + alias(mihonx.plugins.spotless) + alias(libs.plugins.moko.resources) } kotlin { - androidTarget() - - applyDefaultHierarchyTemplate() + android { + namespace = "tachiyomi.i18n.animiru" + } - sourceSets { - commonMain { - dependencies { - api(libs.moko.resources) - } - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + @Suppress("UnstableApiUsage") + dependencies { + api(libs.moko.resources) } @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -27,21 +25,13 @@ kotlin { } } -val generatedAndroidResourceDir = generatedBuildDir.resolve("android/res") - -android { - namespace = "tachiyomi.i18n.animiru" +androidComponents { + onVariants { variant -> + val resSource = variant.sources.res ?: return@onVariants - sourceSets { - val main by getting - main.res.srcDirs( - "src/commonMain/resources", - generatedAndroidResourceDir, - ) - } - - lint { - disable.addAll(listOf("MissingTranslation", "ExtraTranslation")) + val variantName = variant.name.replaceFirstChar { it.uppercase() } + val task = tasks.register("generate${variantName}LocalesConfig") + resSource.addGeneratedSourceDirectory(task) { it.outputDir } } } @@ -49,10 +39,3 @@ multiplatformResources { resourcesClassName.set("AMMR") resourcesPackage.set("tachiyomi.i18n.animiru") } - -tasks { - val localesConfigTask = project.getLocalesConfigTask(generatedAndroidResourceDir) - preBuild { - dependsOn(localesConfigTask) - } -} diff --git a/i18n-aniyomi/build.gradle.kts b/i18n-aniyomi/build.gradle.kts index 2533f0aa14..40ee0601db 100644 --- a/i18n-aniyomi/build.gradle.kts +++ b/i18n-aniyomi/build.gradle.kts @@ -1,24 +1,22 @@ -import mihon.buildlogic.generatedBuildDir -import mihon.buildlogic.tasks.getLocalesConfigTask +import mihon.gradle.tasks.GenerateLocalesConfigTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { - id("mihon.library") - kotlin("multiplatform") + alias(mihonx.plugins.kotlin.multiplatform) + alias(mihonx.plugins.spotless) + alias(libs.plugins.moko.resources) } kotlin { - androidTarget() - - applyDefaultHierarchyTemplate() + android { + namespace = "tachiyomi.i18n.aniyomi" + } - sourceSets { - commonMain { - dependencies { - api(libs.moko.resources) - } - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + @Suppress("UnstableApiUsage") + dependencies { + api(libs.moko.resources) } @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -27,21 +25,13 @@ kotlin { } } -val generatedAndroidResourceDir = generatedBuildDir.resolve("android/res") - -android { - namespace = "tachiyomi.i18n.aniyomi" +androidComponents { + onVariants { variant -> + val resSource = variant.sources.res ?: return@onVariants - sourceSets { - val main by getting - main.res.srcDirs( - "src/commonMain/resources", - generatedAndroidResourceDir, - ) - } - - lint { - disable.addAll(listOf("MissingTranslation", "ExtraTranslation")) + val variantName = variant.name.replaceFirstChar { it.uppercase() } + val task = tasks.register("generate${variantName}LocalesConfig") + resSource.addGeneratedSourceDirectory(task) { it.outputDir } } } @@ -49,10 +39,3 @@ multiplatformResources { resourcesClassName.set("AYMR") resourcesPackage.set("tachiyomi.i18n.aniyomi") } - -tasks { - val localesConfigTask = project.getLocalesConfigTask(generatedAndroidResourceDir) - preBuild { - dependsOn(localesConfigTask) - } -} diff --git a/i18n-aniyomi/src/commonMain/moko-resources/am/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/am/strings.xml index 1febe1df6c..0e5329cae3 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/am/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/am/strings.xml @@ -30,4 +30,4 @@ ነባሪ ምድብ ማውረድ ለአፍታ ቆሟል የምዕራፍ ዝመናዎች - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ar/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/ar/plurals.xml index 1108b04d95..bdbc88ca9c 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ar/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ar/plurals.xml @@ -56,4 +56,4 @@ الحلقات %1$s و %2$d أخرى الحلقات %1$s و %2$d أخرى - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/be/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/be/strings.xml index 18ee625206..469bcd2803 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/be/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/be/strings.xml @@ -5,4 +5,4 @@ Значкі лакальных Катэгорыя па змаўчанні Манга ў выключаных катэгорыях не будзе абнаўляцца, нават калі яны таксама знаходзяцца ў уключаных катэгорыях. - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/bg/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/bg/strings.xml index b415e10371..bcaa58dc69 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/bg/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/bg/strings.xml @@ -14,4 +14,4 @@ На пауза Изтеглянето спряно Нови глави - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ca/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/ca/plurals.xml index d9dc187b8a..56e9393ad4 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ca/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ca/plurals.xml @@ -24,4 +24,4 @@ Episodis %1$s i 1 més Episodis %1$s i %2$d més - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ceb/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/ceb/strings.xml index b077e8ee81..25db126cbf 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ceb/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ceb/strings.xml @@ -7,4 +7,4 @@ Wala iapil nga mga kategorya Klaro nga database I-delete ang history para sa manga nga wala ma-save sa imong library - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/cs/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/cs/plurals.xml index cb27314463..7ab5b8bc75 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/cs/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/cs/plurals.xml @@ -30,4 +30,4 @@ Epizody %1$s a další %2$d Epizody %1$s a dalších %2$d - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/cv/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/cv/strings.xml index bdb8da5906..3e45e79173 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/cv/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/cv/strings.xml @@ -11,4 +11,4 @@ Вӑхӑтлӑха чарнӑ Тиеве вӑхӑтлӑха чарнӑ Сыпӑксен ҫӗнетӗвӗсем - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/da/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/da/strings.xml index a071022186..aad446c887 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/da/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/da/strings.xml @@ -4,4 +4,4 @@ Lokale manga Standard kategori Manga i ekskluderede kategorier vil ikke blive opdateret selv hvis de også er i inkluderede kategorier. - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/de/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/de/plurals.xml index 3f46e2c45e..12bffd0305 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/de/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/de/plurals.xml @@ -28,4 +28,4 @@ Folgen %1$s und 1 mehr Folgen %1$s und %2$d mehr - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/el/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/el/strings.xml index 4f197a7abf..91bf454d7e 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/el/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/el/strings.xml @@ -14,4 +14,4 @@ Παύση Λήψη σε παύση Ενημερώσεις κεφαλαίων - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/eo/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/eo/strings.xml index 3658e3c37f..058f1ea7fd 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/eo/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/eo/strings.xml @@ -9,4 +9,4 @@ Paŭzigita Elŝutito paŭzigita Ĉapitraj ĝisdatigoj - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/es/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/es/plurals.xml index 9b71f4070b..78d349f490 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/es/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/es/plurals.xml @@ -35,4 +35,4 @@ Episodios %1$s y %2$d más Episodios %1$s y %2$d más - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/eu/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/eu/strings.xml index 14312a142d..a229f23791 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/eu/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/eu/strings.xml @@ -13,4 +13,4 @@ Gelditurik Deskarga pausatu da Kapituluaren eguneraketak - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/fa/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/fa/plurals.xml index 5e98c904e4..4c71e067a2 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/fa/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/fa/plurals.xml @@ -28,4 +28,4 @@ قسمت‌های %1$s و 1 قسمت دیگر قسمت‌های %1$s و %2$d قسمت دیگر - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/fi/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/fi/plurals.xml index 5f3010623c..7dfbf3f4c3 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/fi/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/fi/plurals.xml @@ -12,4 +12,4 @@ %d sekunti %d sekuntia - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/fil/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/fil/plurals.xml index e3da54fba4..de40f5ca2a 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/fil/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/fil/plurals.xml @@ -28,4 +28,4 @@ Mga Episode %1$s at 1 pa Mga Episode %1$s at %2$d pa - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/fr/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/fr/plurals.xml index 0728ce4ebf..e1563ba26d 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/fr/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/fr/plurals.xml @@ -30,4 +30,4 @@ Épisodes %1$s et %2$d autres Épisodes %1$s et %2$d autres - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/gl/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/gl/strings.xml index c05b3c81f9..d9405dd43b 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/gl/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/gl/strings.xml @@ -3,4 +3,4 @@ Categorías, actualización global, accións ao arrastrar Categorías Categoría por defecto - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/he/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/he/plurals.xml index e477b49508..788497193a 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/he/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/he/plurals.xml @@ -42,4 +42,4 @@ פרקים %1$s ו-%2$d נוספים - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/hr/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/hr/plurals.xml index a0cf6d3a7b..80579d14e2 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/hr/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/hr/plurals.xml @@ -35,4 +35,4 @@ Epizode %1$s i još %2$d Epizode %1$s i još %2$d - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/hu/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/hu/strings.xml index 6bc326fee9..e9b4522656 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/hu/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/hu/strings.xml @@ -14,4 +14,4 @@ Szünetel Letöltés szüneteltetve Fejezetfrissítések - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/in/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/in/plurals.xml index 291ce0553d..94358e041c 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/in/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/in/plurals.xml @@ -21,4 +21,4 @@ Episode %1$s dan %2$d lainnya - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/it/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/it/plurals.xml index c57a4e20e6..c301b7d9e0 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/it/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/it/plurals.xml @@ -35,4 +35,4 @@ Episodi %1$s e altri %2$d Episodi %1$s e altri %2$d - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/jv/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/jv/strings.xml index 05c62a6879..9ccf48e27f 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/jv/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/jv/strings.xml @@ -8,4 +8,4 @@ kategori gawan Manga ing kategori sing ora kalebu ora bakal dianyari sanajan uga ana ing kategori sing kalebu. Donlotan mandheg - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ka-rGE/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/ka-rGE/strings.xml index b03487442d..ff3737ad9e 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ka-rGE/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ka-rGE/strings.xml @@ -9,4 +9,4 @@ დაპაუზებულია გადმოწერა დაპაუზებულია თავების განახლებები - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/km/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/km/strings.xml index 364f8b0abb..3039d8111f 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/km/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/km/strings.xml @@ -3,4 +3,4 @@ ការតម្រៀបថ្នាក់ ម៊េងហ្គាដែលមានស្រាប់ក្នុងឧបករណ៍ ថ្នាក់ធម្មតា - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/kn/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/kn/strings.xml index 80d49685dc..c108db7f89 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/kn/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/kn/strings.xml @@ -11,4 +11,4 @@ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ ಅಧ್ಯಾಯ ನವೀಕರಣಗಳು - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ko/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/ko/plurals.xml index fe09ef8cc3..2cb28d2e51 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ko/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ko/plurals.xml @@ -21,4 +21,4 @@ 에피소드 %1$s와 그 외 %2$d - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/lv/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/lv/strings.xml index aa1a1f05d6..2fe2bd7d4c 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/lv/strings.xml @@ -18,4 +18,4 @@ Pievienot manga bibliotēkai? Lejuplāde pauzēta Nodaļu atjauninājumi - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/mr/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/mr/strings.xml index 82ee3fe101..4e6b295044 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/mr/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/mr/strings.xml @@ -3,4 +3,4 @@ चालु केले श्रेण्या मूलभूत श्रेणी - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/my/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/my/strings.xml index 326a328ce6..c20d1f28db 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/my/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/my/strings.xml @@ -2,4 +2,4 @@ စာကြည့်တိုက်အနှစ်ချုပ် အိုင်ကွန်ပေါ်တွင် မဖတ်ရသေးတဲ့ အရေအတွက်ကို ပြသပါ - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/nb-rNO/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/nb-rNO/strings.xml index 04764cf06d..0e2b64f44f 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/nb-rNO/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/nb-rNO/strings.xml @@ -14,4 +14,4 @@ Pauset Nedlasting pauset Kapitteloppdateringer - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ne/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/ne/strings.xml index a98fffd8ac..2224a34d31 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ne/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ne/strings.xml @@ -14,4 +14,4 @@ रोकिएको डाउनलोडहरु रोकियो अध्याय अपडेटहरू - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/pl/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/pl/plurals.xml index 82fce5858c..3f058c1364 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/pl/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/pl/plurals.xml @@ -42,4 +42,4 @@ Odcinki %1$s i %2$d więcej Odcinki %1$s i %2$d więcej - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/pt-rBR/plurals.xml index 3ddd82f513..a42a1c9dd5 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -35,4 +35,4 @@ Episódios %1$s e %2$d mais Episódios %1$s e %2$d mais - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/pt/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/pt/plurals.xml index 9ed43cd31c..b41222c75c 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/pt/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/pt/plurals.xml @@ -35,4 +35,4 @@ Episódios %1$s e %2$d mais Episódios %1$s e %2$d mais - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ro/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/ro/strings.xml index f6ee4b2cf5..82dc38aff0 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ro/strings.xml @@ -14,4 +14,4 @@ Întrerupt Descărcare întreruptă Actualizări de capitole - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/sa/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/sa/strings.xml index 81b98f87d6..ed2e641f48 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/sa/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/sa/strings.xml @@ -13,4 +13,4 @@ विरमितम् अवारोपणं विरमितम् अध्यायनवीकरणानि - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/sah/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/sah/strings.xml index c5b2883276..9bb556dad5 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/sah/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/sah/strings.xml @@ -8,4 +8,4 @@ Маанганы Бэбэлэтиэкэҕэ киллэрэбит дуу? Сыыһааһын Тохтотулунна - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/sc/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/sc/strings.xml index 684bc385fd..01faf015a4 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/sc/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/sc/strings.xml @@ -55,4 +55,4 @@ In pàusa Iscarrigamentu in pàusa Agiornamentos de sos capìtulos - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/sdh/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/sdh/strings.xml index d3d86b3319..152730f33d 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/sdh/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/sdh/strings.xml @@ -3,4 +3,4 @@ کاتیگۆرییەکان مانگای ناوخۆیی زیادکردنی مانگا بۆ کۆکراوەکان؟ - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/sq/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/sq/strings.xml index 0bfdc588ae..f2467881dd 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/sq/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/sq/strings.xml @@ -1,4 +1,4 @@ Kategoritë, përditësimi global - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/te/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/te/strings.xml index 66f3c1a08f..3e3f97e323 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/te/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/te/strings.xml @@ -1,4 +1,4 @@ విభాగములు - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/th/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/th/strings.xml index 2a0f5e049a..b351a27bd7 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/th/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/th/strings.xml @@ -14,4 +14,4 @@ หยุดชั่วคราว การดาวน์โหลดหยุดชั่วคราว อัปเดตตอน - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/tr/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/tr/plurals.xml index d9b31be478..5cfa767b16 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/tr/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/tr/plurals.xml @@ -28,4 +28,4 @@ %1$s bölüm ve 1 tane daha %1$s bölüm ve %2$d tane daha - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/uk/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/uk/plurals.xml index b35608e7f5..08475baf06 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/uk/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/uk/plurals.xml @@ -42,4 +42,4 @@ Епізоди %1$s та інші %2$d Епізоди %1$s та інші %2$d - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/ur/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/ur/strings.xml index 55344e5192..045e125f3d 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/ur/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/ur/strings.xml @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/uz/strings.xml b/i18n-aniyomi/src/commonMain/moko-resources/uz/strings.xml index e4fc68ea81..5da3c9f336 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/uz/strings.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/uz/strings.xml @@ -2,4 +2,4 @@ Toifalar, global yangilanish Kategoriyalar - \ No newline at end of file + diff --git a/i18n-aniyomi/src/commonMain/moko-resources/zh-rCN/plurals.xml b/i18n-aniyomi/src/commonMain/moko-resources/zh-rCN/plurals.xml index 7f2ac66c62..08ab10fb67 100644 --- a/i18n-aniyomi/src/commonMain/moko-resources/zh-rCN/plurals.xml +++ b/i18n-aniyomi/src/commonMain/moko-resources/zh-rCN/plurals.xml @@ -9,4 +9,4 @@ %d秒 - \ No newline at end of file + diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index 1f449ae0ca..aba2cab73b 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -1,24 +1,22 @@ -import mihon.buildlogic.generatedBuildDir -import mihon.buildlogic.tasks.getLocalesConfigTask +import mihon.gradle.tasks.GenerateLocalesConfigTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { - id("mihon.library") - kotlin("multiplatform") + alias(mihonx.plugins.kotlin.multiplatform) + alias(mihonx.plugins.spotless) + alias(libs.plugins.moko.resources) } kotlin { - androidTarget() - - applyDefaultHierarchyTemplate() + android { + namespace = "tachiyomi.i18n" + } - sourceSets { - commonMain { - dependencies { - api(libs.moko.resources) - } - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + @Suppress("UnstableApiUsage") + dependencies { + api(libs.moko.resources) } @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -27,31 +25,16 @@ kotlin { } } -val generatedAndroidResourceDir = generatedBuildDir.resolve("android/res") - -android { - namespace = "tachiyomi.i18n" +androidComponents { + onVariants { variant -> + val resSource = variant.sources.res ?: return@onVariants - sourceSets { - val main by getting - main.res.srcDirs( - "src/commonMain/resources", - generatedAndroidResourceDir, - ) - } - - lint { - disable.addAll(listOf("MissingTranslation", "ExtraTranslation")) + val variantName = variant.name.replaceFirstChar { it.uppercase() } + val task = tasks.register("generate${variantName}LocalesConfig") + resSource.addGeneratedSourceDirectory(task) { it.outputDir } } } multiplatformResources { resourcesPackage.set("tachiyomi.i18n") } - -tasks { - val localesConfigTask = project.getLocalesConfigTask(generatedAndroidResourceDir) - preBuild { - dependsOn(localesConfigTask) - } -} diff --git a/i18n/src/commonMain/moko-resources/he/plurals.xml b/i18n/src/commonMain/moko-resources/he/plurals.xml index 03bd26a33d..ce9d6f171b 100644 --- a/i18n/src/commonMain/moko-resources/he/plurals.xml +++ b/i18n/src/commonMain/moko-resources/he/plurals.xml @@ -101,4 +101,4 @@ מחרותיים בעוד %1$d ימים - \ No newline at end of file + diff --git a/i18n/src/commonMain/moko-resources/kk/plurals.xml b/i18n/src/commonMain/moko-resources/kk/plurals.xml index 88f5ca8073..926ab35fbc 100644 --- a/i18n/src/commonMain/moko-resources/kk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/kk/plurals.xml @@ -64,4 +64,4 @@ Ертең %1$d күнде - \ No newline at end of file + diff --git a/i18n/src/commonMain/moko-resources/pl/plurals.xml b/i18n/src/commonMain/moko-resources/pl/plurals.xml index 2a86f3d90e..920ab950c6 100644 --- a/i18n/src/commonMain/moko-resources/pl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pl/plurals.xml @@ -114,4 +114,4 @@ %1$s stron %1$s stron - \ No newline at end of file + diff --git a/i18n/src/commonMain/moko-resources/sa/plurals.xml b/i18n/src/commonMain/moko-resources/sa/plurals.xml index 4c1eec2956..1cddee9120 100644 --- a/i18n/src/commonMain/moko-resources/sa/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sa/plurals.xml @@ -76,4 +76,4 @@ %1$s अध्यायः नास्ति %1$s अध्यायाः न सन्ति - \ No newline at end of file + diff --git a/i18n/src/commonMain/moko-resources/sc/plurals.xml b/i18n/src/commonMain/moko-resources/sc/plurals.xml index dfe3faee05..903973414a 100644 --- a/i18n/src/commonMain/moko-resources/sc/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sc/plurals.xml @@ -76,4 +76,4 @@ 1 pàgina %1$s pàginas - \ No newline at end of file + diff --git a/i18n/src/commonMain/moko-resources/tt/strings.xml b/i18n/src/commonMain/moko-resources/tt/strings.xml index 55344e5192..045e125f3d 100644 --- a/i18n/src/commonMain/moko-resources/tt/strings.xml +++ b/i18n/src/commonMain/moko-resources/tt/strings.xml @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/macrobenchmark/build.gradle.kts b/macrobenchmark/build.gradle.kts index b74c26bdd8..c7b006979b 100644 --- a/macrobenchmark/build.gradle.kts +++ b/macrobenchmark/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("mihon.benchmark") + alias(mihonx.plugins.android.test) } android { diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index 4a3f632ece..79fd1b4ef6 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -1,16 +1,12 @@ plugins { - id("mihon.library") - id("mihon.library.compose") - kotlin("android") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.compose) + + alias(mihonx.plugins.spotless) } android { namespace = "tachiyomi.presentation.core" - - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } } kotlin { diff --git a/presentation-core/consumer-rules.pro b/presentation-core/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/presentation-core/proguard-rules.pro b/presentation-core/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/presentation-core/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/presentation-widget/build.gradle.kts b/presentation-widget/build.gradle.kts index e884be0699..1018b54e16 100644 --- a/presentation-widget/build.gradle.kts +++ b/presentation-widget/build.gradle.kts @@ -1,16 +1,12 @@ plugins { - id("mihon.library") - id("mihon.library.compose") - kotlin("android") + alias(mihonx.plugins.android.library) + alias(mihonx.plugins.compose) + + alias(mihonx.plugins.spotless) } android { namespace = "tachiyomi.presentation.widget" - - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } } dependencies { diff --git a/presentation-widget/consumer-rules.pro b/presentation-widget/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/presentation-widget/proguard-rules.pro b/presentation-widget/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/presentation-widget/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f48ef0ee31..2ae6920ad7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,22 +1,18 @@ pluginManagement { - resolutionStrategy { - eachPlugin { - val regex = "com.android.(library|application)".toRegex() - if (regex matches requested.id.id) { - useModule("com.android.tools.build:gradle:${requested.version}") - } - } - } + includeBuild("gradle/build-logic") repositories { - gradlePluginPortal() google() mavenCentral() + gradlePluginPortal() maven(url = "https://www.jitpack.io") } } dependencyResolutionManagement { versionCatalogs { + create("mihonx") { + from(files("gradle/mihon.versions.toml")) + } // AY --> create("aniyomilibs") { from(files("gradle/aniyomi.versions.toml")) @@ -25,8 +21,8 @@ dependencyResolutionManagement { } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - mavenCentral() google() + mavenCentral() maven(url = "https://www.jitpack.io") } } diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index ea6b4c4b2b..3d156f3e87 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -1,32 +1,38 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { - id("mihon.library") - kotlin("multiplatform") - kotlin("plugin.serialization") + alias(mihonx.plugins.kotlin.multiplatform) + alias(mihonx.plugins.spotless) + + alias(libs.plugins.kotlin.serialization) } kotlin { - androidTarget() - sourceSets { - val commonMain by getting { - dependencies { - api(libs.kotlinx.serialization.json) - api(libs.injekt) - api(libs.rxJava) - api(libs.jsoup) + android { + namespace = "eu.kanade.tachiyomi.source" - implementation(project.dependencies.platform(libs.androidx.compose.bom)) - implementation(libs.androidx.compose.runtime) - } + defaultConfig { + consumerProguardFile("consumer-proguard.pro") } - val androidMain by getting { + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + @Suppress("UnstableApiUsage") + dependencies { + api(libs.kotlinx.serialization.json) + api(libs.injekt) + api(libs.rxJava) + api(libs.jsoup) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.runtime) + } + + sourceSets { + androidMain { dependencies { implementation(projects.core.common) api(libs.androidx.preference) - - // Workaround for https://youtrack.jetbrains.com/issue/KT-57605 - implementation(libs.kotlinx.coroutines.android) } } } @@ -36,11 +42,3 @@ kotlin { freeCompilerArgs.add("-Xexpect-actual-classes") } } - -android { - namespace = "eu.kanade.tachiyomi.source" - - defaultConfig { - consumerProguardFile("consumer-proguard.pro") - } -} diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index 28abcedeb5..c0e2841468 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -1,22 +1,26 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { - id("mihon.library") - kotlin("multiplatform") + alias(mihonx.plugins.kotlin.multiplatform) + alias(mihonx.plugins.spotless) } kotlin { - androidTarget() - sourceSets { - val commonMain by getting { - dependencies { - implementation(projects.sourceApi) - api(projects.i18n) + android { + namespace = "tachiyomi.source.local" + } - implementation(libs.unifile) - } - } - val androidMain by getting { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + @Suppress("UnstableApiUsage") + dependencies { + implementation(projects.sourceApi) + api(projects.i18n) + + implementation(libs.unifile) + } + + sourceSets { + androidMain { dependencies { implementation(projects.core.archive) implementation(projects.core.common) @@ -26,6 +30,11 @@ kotlin { implementation(projects.domain) implementation(libs.bundles.serialization) + + // AM --> + // FFmpeg-kit + implementation(aniyomilibs.ffmpeg.kit) + // <-- AM } } } @@ -38,19 +47,3 @@ kotlin { ) } } - -android { - namespace = "tachiyomi.source.local" - - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - // AY --> - dependencies { - // FFmpeg-kit - implementation(aniyomilibs.ffmpeg.kit) - } - // <-- AY -} diff --git a/source-local/consumer-rules.pro b/source-local/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/source-local/proguard-rules.pro b/source-local/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/source-local/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file From ad72dbabf3da397650bdb9594042cc6d6637eb58 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 21 Mar 2026 19:49:57 +0000 Subject: [PATCH 04/59] Update Gradle to v9 (#2333) (cherry picked from commit c95269afd7b73371bc3adc3990e6771869053df1) --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 ++----- gradlew.bat | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 40557 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 Kt*`#|wEG{3k;I+= delta 35566 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

%6K4SwC7IQze=vR`C9urpeXS*~OW{ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aaaabb3cb9..c61a118f7d 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.14.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9367..739907dfd1 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac207..c4bdd3ab8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 7b98290ade67be48e7d24751e326b04190b44745 Mon Sep 17 00:00:00 2001 From: MajorTanya <39014446+MajorTanya@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:17:35 +0100 Subject: [PATCH 05/59] Address CancellableContinuation.resume deprecation (#3115) According to the source docs, this has been a warning since kotlinx.coroutines 1.9.0. (cherry picked from commit bc3230832704116b7eb0e5c3ad3edf4656d998fd) --- .../main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 0a860d0697..072c50c81e 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -77,7 +77,7 @@ private suspend fun Call.await(callStack: Array): Response { val callback = object : Callback { override fun onResponse(call: Call, response: Response) { - continuation.resume(response) { + continuation.resume(response) { _, _, _ -> response.body.close() } } From 90b6dd8a1bdde6b985782025135c6c94a28fb9fa Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sun, 22 Mar 2026 14:02:55 +0100 Subject: [PATCH 06/59] Update android.gradle to v9 (major) (#3105) Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit 34afdabcfe175bf04e07eb7c567f998c8c173cb6) --- .github/workflows/build.yml | 4 +- app/build.gradle.kts | 8 +--- build.gradle.kts | 7 +++ domain/build.gradle.kts | 1 + gradle.properties | 2 - .../src/main/kotlin/PluginAndroidBase.kt | 45 +++++++++++++++---- .../src/main/kotlin/PluginComposeAndroid.kt | 6 +++ .../main/kotlin/PluginKotlinMultiplatform.kt | 24 ++++++++-- .../mihon/gradle/configurations/Android.kt | 32 ------------- .../kotlin/mihon/gradle/extensions/Project.kt | 6 +-- gradle/libs.versions.toml | 10 ++--- i18n/build.gradle.kts | 3 ++ source-api/build.gradle.kts | 9 ++-- source-local/build.gradle.kts | 3 ++ 14 files changed, 94 insertions(+), 66 deletions(-) delete mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 800e667cbb..2bb62a6e91 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,14 +57,14 @@ jobs: - name: Run unit tests id: unit_tests - run: ./gradlew testReleaseUnitTest + run: ./gradlew testDebugUnitTest - name: Upload test report if: steps.unit_tests.outcome == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-report-${{ github.sha }} - path: app/build/reports/tests/testReleaseUnitTest + path: app/build/reports/tests/testDebugUnitTest - name: Upload APK uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 837816b846..09bc955474 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,8 +74,8 @@ android { } sourceSets { - getByName("preview").res.srcDirs("src/debug/res") - getByName("benchmark").res.srcDirs("src/debug/res") + getByName("preview").res.directories.add("src/debug/res") + getByName("benchmark").res.directories.add("src/debug/res") } splits { @@ -138,10 +138,6 @@ android { viewBinding = true buildConfig = true aidl = true - - // Disable some unused things - renderScript = false - shaders = false } lint { diff --git a/build.gradle.kts b/build.gradle.kts index bfa5d8e0ab..253ff367fd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,13 @@ +buildscript { + dependencies { + classpath(libs.kotlin.gradle) + } +} + plugins { alias(libs.plugins.aboutLibraries) apply false alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.kmp.library) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.android.test) apply false alias(libs.plugins.kotlin.compose.compiler) apply false diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index ac3997c268..467dccef44 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { api(libs.sqldelight.androidxPaging) + compileOnly(platform(libs.androidx.compose.bom)) compileOnly(libs.androidx.compose.runtimeAnnotation) testImplementation(libs.bundles.test) diff --git a/gradle.properties b/gradle.properties index 22e7dfed58..b88c41e9f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,6 @@ android.nonTransitiveRClass=false -android.useAndroidX=true kotlin.code.style=official -kotlin.mpp.androidSourceSetLayoutVersion=2 org.gradle.caching=true org.gradle.configureondemand=true diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt index 9f2022dae8..70e553ea32 100644 --- a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt @@ -1,21 +1,50 @@ -import mihon.gradle.configurations.configureAndroid +import com.android.build.api.dsl.ApplicationDefaultConfig +import com.android.build.api.dsl.CommonExtension +import com.android.build.api.dsl.CompileOptions +import com.android.build.api.dsl.DefaultConfig import mihon.gradle.configurations.configureKotlin -import mihon.gradle.extensions.alias +import mihon.gradle.extensions.android import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs -import mihon.gradle.extensions.plugins +import mihon.gradle.extensions.mihonx import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies @Suppress("UNUSED") class PluginAndroidBase : Plugin { override fun apply(target: Project): Unit = with(target) { - plugins { - alias(libs.plugins.kotlin.android) - } - configureKotlin() configureTest() - configureAndroid() + + android { + defaultConfig { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + if (this is ApplicationDefaultConfig) { + targetSdk = mihonx.versions.android.sdk.target.get().toInt() + } + + ndkVersion = mihonx.versions.android.ndk.get() + } + + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + + compileOptions { + isCoreLibraryDesugaringEnabled = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) + } } } + +private fun CommonExtension.defaultConfig(block: DefaultConfig.() -> Unit) { + defaultConfig.apply(block) +} + +private fun CommonExtension.compileOptions(block: CompileOptions.() -> Unit) { + compileOptions.apply(block) +} diff --git a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt index 24580206a4..8f961e06f3 100644 --- a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt +++ b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt @@ -1,3 +1,5 @@ +import com.android.build.api.dsl.BuildFeatures +import com.android.build.api.dsl.CommonExtension import mihon.gradle.extensions.alias import mihon.gradle.extensions.android import mihon.gradle.extensions.api @@ -31,3 +33,7 @@ class PluginComposeAndroid : Plugin { } } } + +private fun CommonExtension.buildFeatures(block: BuildFeatures.() -> Unit) { + buildFeatures.apply(block) +} diff --git a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt index 7f37f863c7..5839156449 100644 --- a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt +++ b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt @@ -1,32 +1,44 @@ -import mihon.gradle.configurations.configureAndroid +import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget import mihon.gradle.configurations.configureKotlin import mihon.gradle.extensions.alias import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx import mihon.gradle.extensions.plugins import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import kotlin.text.toInt @Suppress("UNUSED") class PluginKotlinMultiplatform : Plugin { override fun apply(target: Project): Unit = with(target) { plugins { - alias(libs.plugins.android.library) + alias(libs.plugins.android.kmp.library) alias(libs.plugins.kotlin.multiplatform) } configureKotlin() configureTest() - configureAndroid() kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate() - androidTarget() + android { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + enableCoreLibraryDesugaring = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) } } } @@ -34,3 +46,7 @@ class PluginKotlinMultiplatform : Plugin { private fun Project.kotlin(block: KotlinMultiplatformExtension.() -> Unit) { extensions.configure(block) } + +private fun KotlinMultiplatformExtension.android(block: KotlinMultiplatformAndroidLibraryTarget.() -> Unit) { + targets.withType().configureEach(block) +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt deleted file mode 100644 index 2fb978311e..0000000000 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt +++ /dev/null @@ -1,32 +0,0 @@ -package mihon.gradle.configurations - -import com.android.build.api.dsl.ApplicationDefaultConfig -import mihon.gradle.extensions.android -import mihon.gradle.extensions.coreLibraryDesugaring -import mihon.gradle.extensions.libs -import mihon.gradle.extensions.mihonx -import org.gradle.api.Project -import org.gradle.kotlin.dsl.dependencies - -fun Project.configureAndroid() { - android { - defaultConfig { - minSdk = mihonx.versions.android.sdk.min.get().toInt() - if (this is ApplicationDefaultConfig) { - targetSdk = mihonx.versions.android.sdk.target.get().toInt() - } - - ndkVersion = mihonx.versions.android.ndk.get() - } - - compileSdk = mihonx.versions.android.sdk.compile.get().toInt() - - compileOptions { - isCoreLibraryDesugaringEnabled = true - } - } - - dependencies { - coreLibraryDesugaring(libs.android.desugar) - } -} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt index f3621c61d3..e22ccf04b4 100644 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt @@ -24,10 +24,8 @@ internal fun Project.plugins(block: PluginManager.() -> Unit) { pluginManager.apply(block) } -internal fun Project.android(block: CommonExtension<*, *, *, *, *, *>.() -> Unit) { - extensions.configure { - if (this is CommonExtension<*, *, *, *, *, *>) apply(block) - } +internal fun Project.android(block: CommonExtension.() -> Unit) { + extensions.configure(block) } fun Project.configureTest() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab7e57f923..b217d5b514 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -aboutLibraries = "13.2.1" +aboutLibraries = "14.0.0-b03" android-desugar = "2.1.5" -android-gradle = "8.13.2" +android-gradle = "9.1.0" androidx-activity-compose = "1.13.0" androidx-annotation = "1.9.1" androidx-appCompat = "1.7.1" @@ -83,7 +83,7 @@ androidx-benchmark-macroJunit4 = { module = "androidx.benchmark:benchmark-macro- androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" } androidx-compose-animation = { module = "androidx.compose.animation:animation" } androidx-compose-animationGraphics = { module = "androidx.compose.animation:animation-graphics" } -androidx-compose-bom = { module = "androidx.compose:compose-bom-beta", version.ref = "androidx-compose-bom" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "androidx-compose-bom" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-materialIcons = { module = "androidx.compose.material:material-icons-extended" } @@ -186,11 +186,11 @@ xmlutil-serialization = { module = "io.github.pdvrieze.xmlutil:serialization", v [plugins] aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } android-application = { id = "com.android.application", version.ref = "android-gradle" } -android-library = { id = "com.android.library", version.ref = "android-gradle" } android-test = { id = "com.android.test", version.ref = "android-gradle" } +android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "android-gradle" } +android-library = { id = "com.android.library", version.ref = "android-gradle" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-gradle" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-gradle" } diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index aba2cab73b..f1a589d496 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -11,6 +11,9 @@ plugins { kotlin { android { namespace = "tachiyomi.i18n" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index 3d156f3e87..92ea71e4e5 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -8,12 +8,15 @@ plugins { } kotlin { + @Suppress("UnstableApiUsage") android { namespace = "eu.kanade.tachiyomi.source" - - defaultConfig { - consumerProguardFile("consumer-proguard.pro") + optimization { + consumerKeepRules.file("consumer-proguard.pro") } + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index c0e2841468..f34e7d0b42 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -8,6 +8,9 @@ plugins { kotlin { android { namespace = "tachiyomi.source.local" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) From 68b3346b619fe0c67eff6dce461f02a1635dcba0 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:37:04 +0600 Subject: [PATCH 07/59] Fix wrong exception being caught after 8c480c6355 migration `android.database.sqlite.SQLiteException` instead of `android.database.SQLException` (cherry picked from commit cb0e3297160aab7a2d1486aeeecdd92394eb220e) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ac2e9a5d..e136af5640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Added - Added system font fallback ([@Secozzi](https://github.com/Secozzi)) ([#156](https://github.com/quickdesh/Animiru/pull/156)) +### Fixed +- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) + ## [v0.19.7.3] - 2026-04-17 ### Improved - Allow options in mpv.conf to override options set by `AniyomiMPVView` ([@Secozzi](https://github.com/Secozzi)) ([#151](https://github.com/quickdesh/Animiru/pull/151)) From 22353ff1334fa3f389a0f3da5b18a7fe2eaca984 Mon Sep 17 00:00:00 2001 From: MajorTanya <39014446+MajorTanya@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:22:53 +0100 Subject: [PATCH 08/59] Address Gradle warning about `Task.project` (#3118) Apparently, this is all that's needed to replace the forbidden `Task.project` accessor, which would be an error in Gradle v10 (it's been deprecated for a while) This will also allow us to use the Configuration Cache if we wanted to. (cherry picked from commit 8b9d0a89c88b427e2d6569db914d123e8063fba4) --- .../mihon/gradle/tasks/GenerateLocalesConfigTask.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt index d963dac0f6..736cb7f91c 100644 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/tasks/GenerateLocalesConfigTask.kt @@ -2,19 +2,24 @@ package mihon.gradle.tasks import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import javax.inject.Inject abstract class GenerateLocalesConfigTask : DefaultTask() { + @get:Inject + abstract val objectFactory: ObjectFactory + @get:OutputDirectory abstract val outputDir: DirectoryProperty @TaskAction fun action() { - val locales = project.fileTree("src/commonMain/moko-resources") { - matching { include("**/strings.xml") } - } + val locales = objectFactory.fileTree() + .from("src/commonMain/moko-resources") + .matching { include("**/strings.xml") } .asSequence() .filterNot { it.readText().contains(emptyResourcesElement) } .map { From c1068dfc58acd3108d67665eb7c3f7277c42d60f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 24 Mar 2026 08:10:52 +0000 Subject: [PATCH 09/59] Update dependency io.kotest:kotest-assertions-core to v6.1.8 (#3132) (cherry picked from commit d75a125b2691f334c04ddc3a6fce7cb13a412126) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b217d5b514..001e06986e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ image-decoder = "41c059e540" injekt = "91edab2317" jsoup = "1.22.1" junit = "6.0.3" -kotest-assertions = "6.1.7" +kotest-assertions = "6.1.8" kotlin-gradle = "2.3.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" From 88691d8000e36256f92f21a7df3b6bbd6d03e10d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 25 Mar 2026 09:43:04 +0000 Subject: [PATCH 10/59] Update dependency io.kotest:kotest-assertions-core to v6.1.9 (#3137) (cherry picked from commit 7c5034275fe1c2307e66fb59bf2258c09adb1bad) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 001e06986e..d406944734 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ image-decoder = "41c059e540" injekt = "91edab2317" jsoup = "1.22.1" junit = "6.0.3" -kotest-assertions = "6.1.8" +kotest-assertions = "6.1.9" kotlin-gradle = "2.3.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" From 6508b4e90042c78afc6c837f666741537b4a3fb7 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+antsylich@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:57:35 +0100 Subject: [PATCH 11/59] Revert "Update android.gradle to v9 (major) (#3105)" This reverts commit 34afdabcfe175bf04e07eb7c567f998c8c173cb6. (cherry picked from commit eb5b76251576559fca25e30f96ac87bdb3e9d6ad) --- .github/workflows/build.yml | 4 +- app/build.gradle.kts | 8 +++- build.gradle.kts | 7 --- domain/build.gradle.kts | 1 - gradle.properties | 2 + .../src/main/kotlin/PluginAndroidBase.kt | 45 ++++--------------- .../src/main/kotlin/PluginComposeAndroid.kt | 6 --- .../main/kotlin/PluginKotlinMultiplatform.kt | 24 ++-------- .../mihon/gradle/configurations/Android.kt | 32 +++++++++++++ .../kotlin/mihon/gradle/extensions/Project.kt | 6 ++- gradle/libs.versions.toml | 10 ++--- i18n/build.gradle.kts | 3 -- source-api/build.gradle.kts | 9 ++-- source-local/build.gradle.kts | 3 -- 14 files changed, 66 insertions(+), 94 deletions(-) create mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bb62a6e91..800e667cbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,14 +57,14 @@ jobs: - name: Run unit tests id: unit_tests - run: ./gradlew testDebugUnitTest + run: ./gradlew testReleaseUnitTest - name: Upload test report if: steps.unit_tests.outcome == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-report-${{ github.sha }} - path: app/build/reports/tests/testDebugUnitTest + path: app/build/reports/tests/testReleaseUnitTest - name: Upload APK uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09bc955474..837816b846 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,8 +74,8 @@ android { } sourceSets { - getByName("preview").res.directories.add("src/debug/res") - getByName("benchmark").res.directories.add("src/debug/res") + getByName("preview").res.srcDirs("src/debug/res") + getByName("benchmark").res.srcDirs("src/debug/res") } splits { @@ -138,6 +138,10 @@ android { viewBinding = true buildConfig = true aidl = true + + // Disable some unused things + renderScript = false + shaders = false } lint { diff --git a/build.gradle.kts b/build.gradle.kts index 253ff367fd..bfa5d8e0ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,6 @@ -buildscript { - dependencies { - classpath(libs.kotlin.gradle) - } -} - plugins { alias(libs.plugins.aboutLibraries) apply false alias(libs.plugins.android.application) apply false - alias(libs.plugins.android.kmp.library) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.android.test) apply false alias(libs.plugins.kotlin.compose.compiler) apply false diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 467dccef44..ac3997c268 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { api(libs.sqldelight.androidxPaging) - compileOnly(platform(libs.androidx.compose.bom)) compileOnly(libs.androidx.compose.runtimeAnnotation) testImplementation(libs.bundles.test) diff --git a/gradle.properties b/gradle.properties index b88c41e9f5..22e7dfed58 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,8 @@ android.nonTransitiveRClass=false +android.useAndroidX=true kotlin.code.style=official +kotlin.mpp.androidSourceSetLayoutVersion=2 org.gradle.caching=true org.gradle.configureondemand=true diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt index 70e553ea32..9f2022dae8 100644 --- a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt @@ -1,50 +1,21 @@ -import com.android.build.api.dsl.ApplicationDefaultConfig -import com.android.build.api.dsl.CommonExtension -import com.android.build.api.dsl.CompileOptions -import com.android.build.api.dsl.DefaultConfig +import mihon.gradle.configurations.configureAndroid import mihon.gradle.configurations.configureKotlin -import mihon.gradle.extensions.android +import mihon.gradle.extensions.alias import mihon.gradle.extensions.configureTest -import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs -import mihon.gradle.extensions.mihonx +import mihon.gradle.extensions.plugins import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.kotlin.dsl.dependencies @Suppress("UNUSED") class PluginAndroidBase : Plugin { override fun apply(target: Project): Unit = with(target) { - configureKotlin() - configureTest() - - android { - defaultConfig { - minSdk = mihonx.versions.android.sdk.min.get().toInt() - if (this is ApplicationDefaultConfig) { - targetSdk = mihonx.versions.android.sdk.target.get().toInt() - } - - ndkVersion = mihonx.versions.android.ndk.get() - } - - compileSdk = mihonx.versions.android.sdk.compile.get().toInt() - - compileOptions { - isCoreLibraryDesugaringEnabled = true - } + plugins { + alias(libs.plugins.kotlin.android) } - dependencies { - coreLibraryDesugaring(libs.android.desugar) - } + configureKotlin() + configureTest() + configureAndroid() } } - -private fun CommonExtension.defaultConfig(block: DefaultConfig.() -> Unit) { - defaultConfig.apply(block) -} - -private fun CommonExtension.compileOptions(block: CompileOptions.() -> Unit) { - compileOptions.apply(block) -} diff --git a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt index 8f961e06f3..24580206a4 100644 --- a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt +++ b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt @@ -1,5 +1,3 @@ -import com.android.build.api.dsl.BuildFeatures -import com.android.build.api.dsl.CommonExtension import mihon.gradle.extensions.alias import mihon.gradle.extensions.android import mihon.gradle.extensions.api @@ -33,7 +31,3 @@ class PluginComposeAndroid : Plugin { } } } - -private fun CommonExtension.buildFeatures(block: BuildFeatures.() -> Unit) { - buildFeatures.apply(block) -} diff --git a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt index 5839156449..7f37f863c7 100644 --- a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt +++ b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt @@ -1,44 +1,32 @@ -import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget +import mihon.gradle.configurations.configureAndroid import mihon.gradle.configurations.configureKotlin import mihon.gradle.extensions.alias import mihon.gradle.extensions.configureTest -import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs -import mihon.gradle.extensions.mihonx import mihon.gradle.extensions.plugins import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import kotlin.text.toInt @Suppress("UNUSED") class PluginKotlinMultiplatform : Plugin { override fun apply(target: Project): Unit = with(target) { plugins { - alias(libs.plugins.android.kmp.library) + alias(libs.plugins.android.library) alias(libs.plugins.kotlin.multiplatform) } configureKotlin() configureTest() + configureAndroid() kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate() - android { - minSdk = mihonx.versions.android.sdk.min.get().toInt() - compileSdk = mihonx.versions.android.sdk.compile.get().toInt() - enableCoreLibraryDesugaring = true - } - } - - dependencies { - coreLibraryDesugaring(libs.android.desugar) + androidTarget() } } } @@ -46,7 +34,3 @@ class PluginKotlinMultiplatform : Plugin { private fun Project.kotlin(block: KotlinMultiplatformExtension.() -> Unit) { extensions.configure(block) } - -private fun KotlinMultiplatformExtension.android(block: KotlinMultiplatformAndroidLibraryTarget.() -> Unit) { - targets.withType().configureEach(block) -} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt new file mode 100644 index 0000000000..2fb978311e --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt @@ -0,0 +1,32 @@ +package mihon.gradle.configurations + +import com.android.build.api.dsl.ApplicationDefaultConfig +import mihon.gradle.extensions.android +import mihon.gradle.extensions.coreLibraryDesugaring +import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +fun Project.configureAndroid() { + android { + defaultConfig { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + if (this is ApplicationDefaultConfig) { + targetSdk = mihonx.versions.android.sdk.target.get().toInt() + } + + ndkVersion = mihonx.versions.android.ndk.get() + } + + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + + compileOptions { + isCoreLibraryDesugaringEnabled = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) + } +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt index e22ccf04b4..f3621c61d3 100644 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt @@ -24,8 +24,10 @@ internal fun Project.plugins(block: PluginManager.() -> Unit) { pluginManager.apply(block) } -internal fun Project.android(block: CommonExtension.() -> Unit) { - extensions.configure(block) +internal fun Project.android(block: CommonExtension<*, *, *, *, *, *>.() -> Unit) { + extensions.configure { + if (this is CommonExtension<*, *, *, *, *, *>) apply(block) + } } fun Project.configureTest() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d406944734..d264767b61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -aboutLibraries = "14.0.0-b03" +aboutLibraries = "13.2.1" android-desugar = "2.1.5" -android-gradle = "9.1.0" +android-gradle = "8.13.2" androidx-activity-compose = "1.13.0" androidx-annotation = "1.9.1" androidx-appCompat = "1.7.1" @@ -83,7 +83,7 @@ androidx-benchmark-macroJunit4 = { module = "androidx.benchmark:benchmark-macro- androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" } androidx-compose-animation = { module = "androidx.compose.animation:animation" } androidx-compose-animationGraphics = { module = "androidx.compose.animation:animation-graphics" } -androidx-compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "androidx-compose-bom" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-beta", version.ref = "androidx-compose-bom" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-materialIcons = { module = "androidx.compose.material:material-icons-extended" } @@ -186,11 +186,11 @@ xmlutil-serialization = { module = "io.github.pdvrieze.xmlutil:serialization", v [plugins] aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } android-application = { id = "com.android.application", version.ref = "android-gradle" } -android-test = { id = "com.android.test", version.ref = "android-gradle" } -android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "android-gradle" } android-library = { id = "com.android.library", version.ref = "android-gradle" } +android-test = { id = "com.android.test", version.ref = "android-gradle" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-gradle" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-gradle" } diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index f1a589d496..aba2cab73b 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -11,9 +11,6 @@ plugins { kotlin { android { namespace = "tachiyomi.i18n" - - // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved - withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index 92ea71e4e5..3d156f3e87 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -8,15 +8,12 @@ plugins { } kotlin { - @Suppress("UnstableApiUsage") android { namespace = "eu.kanade.tachiyomi.source" - optimization { - consumerKeepRules.file("consumer-proguard.pro") - } - // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved - withHostTest { } + defaultConfig { + consumerProguardFile("consumer-proguard.pro") + } } @OptIn(ExperimentalKotlinGradlePluginApi::class) diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index f34e7d0b42..c0e2841468 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -8,9 +8,6 @@ plugins { kotlin { android { namespace = "tachiyomi.source.local" - - // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved - withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) From f22ba5bcb410dfaafbd964ce8fa07ed081f5921c Mon Sep 17 00:00:00 2001 From: Christos <42313770+sudo-ezekiel@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:28:11 +0200 Subject: [PATCH 12/59] Use plural forms for update error notification (#3090) * Use plural forms for update error notification Replace the hardcoded 'X update(s) failed' string with Android plural resources so the notification correctly shows '1 update failed' vs '2 updates failed', enabling proper localization. Fixes #3051 * Address review: rename plural key and remove comment Rename plural key to notification_update_errors to avoid potential conflicts with existing translated string keys. Remove leftover comment from strings.xml. * Revert key rename per review (cherry picked from commit 2e635ff2cec72095e4ad93040b52e29dee29d750) --- .../kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt | 2 +- i18n/src/commonMain/moko-resources/base/plurals.xml | 5 +++++ i18n/src/commonMain/moko-resources/base/strings.xml | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index d2b980ea02..1b3386305d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -155,7 +155,7 @@ class LibraryUpdateNotifier( Notifications.ID_LIBRARY_ERROR, Notifications.CHANNEL_LIBRARY_ERROR, ) { - setContentTitle(context.stringResource(MR.strings.notification_update_error, failed)) + setContentTitle(context.pluralStringResource(MR.plurals.notification_update_error, failed, failed)) setContentText(context.stringResource(MR.strings.action_show_errors)) setSmallIcon(R.drawable.ic_ani) diff --git a/i18n/src/commonMain/moko-resources/base/plurals.xml b/i18n/src/commonMain/moko-resources/base/plurals.xml index c257f67648..1f1cb182fe 100644 --- a/i18n/src/commonMain/moko-resources/base/plurals.xml +++ b/i18n/src/commonMain/moko-resources/base/plurals.xml @@ -109,4 +109,9 @@ An entry was skipped %1$d entries were skipped + + + %1$d update failed + %1$d updates failed + diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index bd7005e8be..6ab3463c1b 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -919,7 +919,6 @@ Chapter %1$s Chapter %1$s and %2$d more Chapters %1$s - %1$d update(s) failed Tap to learn more Failed to update cover Please add the entry to your library before doing this From 4c01b3be987a4149aa5dc61b59222afeda3339a9 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 26 Mar 2026 08:34:09 +0000 Subject: [PATCH 13/59] Update dependency androidx.work:work-runtime to v2.11.2 (#3146) (cherry picked from commit a938708e1f3499cbf4e9ba31c624fa3cbfd5964d) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d264767b61..dc821d47fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ androidx-test-espresso = "3.7.0" androidx-test-junit = "1.3.0" androidx-test-uiautomator = "2.3.0" androidx-viewPager = "1.1.0" -androidx-work = "2.11.1" +androidx-work = "2.11.2" archive = "1.1.6" coil = "3.4.0" composeGrid = "1.2.2" From d793ad49b99b8e4bb4ab9e3e2ee0d41473fae23d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 26 Mar 2026 09:34:25 +0100 Subject: [PATCH 14/59] Update r0adkll/sign-android-release digest to a391126 (#3145) (cherry picked from commit c3243882b16d0612c984861139ee789e9fd87dab) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22049ed60e..844d2effd7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,7 @@ jobs: run: ./gradlew assembleRelease -Penable-updater - name: Sign APK - uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478 + uses: r0adkll/sign-android-release@a391126403df92c6a0a3988a829dd47f79b89ed7 with: releaseDirectory: app/build/outputs/apk/release signingKeyBase64: ${{ secrets.SIGNING_KEY }} From 027c46b1ebbf169a0be823973ee872a751e9d80f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 26 Mar 2026 08:36:15 +0000 Subject: [PATCH 15/59] Update dependency androidx.compose:compose-bom-beta to v2026.03.01 (#3147) (cherry picked from commit fa197a4ab229f5c17276942677bdabd3ec12f038) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc821d47fc..bf99cdc0ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-annotation = "1.9.1" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" -androidx-compose-bom = "2026.03.00" +androidx-compose-bom = "2026.03.01" androidx-constraintLayout = "2.2.1" androidx-core = "1.18.0" androidx-coreSplashScreen = "1.2.0" From fe63a53127484f6d3964d550baa5f75ff14701c9 Mon Sep 17 00:00:00 2001 From: MajorTanya <39014446+majortanya@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:22:37 +0100 Subject: [PATCH 16/59] Add informative error for unapproved MAL titles (#3155) MAL has a concept of "titles waiting for approval". These titles cannot be added to user lists, but they do show up on the website and crucially, in search results. However, trying to add such an "unapproved" title will return a 400 error response with the error "invalid_content". Previously, the awaitSuccess() call would mean the generic "HTTP 400" toast would be shown. Now, a dedicated informative error message is shown instead. (cherry picked from commit af4f17efc54a00d9abe20e0d0b701d60c3531369) --- .../data/track/myanimelist/MyAnimeListApi.kt | 21 +++++++++++++++++-- .../myanimelist/MyAnimeListInterceptor.kt | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 0f1b19fe3f..3806e806cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -12,7 +12,9 @@ import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.PkceUtil @@ -121,8 +123,23 @@ class MyAnimeListApi( .put(formBodyBuilder.build()) .build() with(json) { - authClient.newCall(request) - .awaitSuccess() + val response = authClient + .newCall(request) + .await() + + if (!response.isSuccessful) { + if (response.body.string().contains("invalid_content")) { + // MAL returns unapproved titles in search but does not allow adding them to the list + // returns 400 with this body: {"message":"Invalid content","error":"invalid_content"} + // These unapproved titles cannot be filtered out in search and are also returned by the + // endpoint we use for id prefix search + throw MALTitleNotApproved() + } else { + throw HttpException(response.code) + } + } + + response .parseAs() .let { parseAnimeItem(it, track) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index 84eed73021..3a3644c481 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -80,5 +80,6 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor } } +class MALTitleNotApproved : IOException("MAL: This title can't be added because it is waiting for approval.") class MALTokenRefreshFailed : IOException("MAL: Failed to refresh account token") class MALTokenExpired : IOException("MAL: Login has expired") From 3282edebb27f8e54e6a061336bb24e75fb67c1ab Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 30 Mar 2026 23:42:10 +0100 Subject: [PATCH 17/59] Update dependency io.kotest:kotest-assertions-core to v6.1.10 (#3172) (cherry picked from commit 85eb539c4d3774740790b5ab9e3b124cce00ce99) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf99cdc0ca..773761048b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ image-decoder = "41c059e540" injekt = "91edab2317" jsoup = "1.22.1" junit = "6.0.3" -kotest-assertions = "6.1.9" +kotest-assertions = "6.1.10" kotlin-gradle = "2.3.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" From 2e72841cfc39015c87a9e64acaf335904a31b61f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 30 Mar 2026 23:52:48 +0100 Subject: [PATCH 18/59] Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.1 (#3173) (cherry picked from commit 580863e8912b2a6493604051708f3cda55aaab33) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 773761048b..97b56be708 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,7 +67,7 @@ sqldelight-androidx-driver = "0.0.17" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" -tapmoc = "0.4.0" +tapmoc = "0.4.1" unifile = "e0def6b3dc" voyager = "1.1.0-beta03" xmlutil = "0.91.3" From 0b7fc9e01720752af7a7d6dcc8a8509fe0934eff Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sun, 10 May 2026 21:52:33 +0200 Subject: [PATCH 19/59] Run SQLDelight database migration verification task in CI Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 800e667cbb..dd16f2966d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,6 +59,9 @@ jobs: id: unit_tests run: ./gradlew testReleaseUnitTest + - name: Verify SQLDelight migrations + run: ./gradlew verifySqlDelightMigration + - name: Upload test report if: steps.unit_tests.outcome == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 From cfe144150abe83b834dea0c81603b2111f98ff84 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sun, 10 May 2026 21:53:12 +0200 Subject: [PATCH 20/59] Run tests before compiling APK in CI Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd16f2966d..5e15dc686c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,9 +52,6 @@ jobs: - name: Check code format run: ./gradlew spotlessCheck - - name: Build app - run: ./gradlew assembleRelease -Penable-updater - - name: Run unit tests id: unit_tests run: ./gradlew testReleaseUnitTest @@ -69,6 +66,9 @@ jobs: name: test-report-${{ github.sha }} path: app/build/reports/tests/testReleaseUnitTest + - name: Build app + run: ./gradlew assembleRelease -Penable-updater + - name: Upload APK uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: From 6c46a84ca571c4893adf57728147749eed9d2867 Mon Sep 17 00:00:00 2001 From: ArthurKun <16458204+ArthurKun21@users.noreply.github.com> Date: Tue, 31 Mar 2026 07:40:06 +0800 Subject: [PATCH 21/59] Add `gradle.properties` to `build-logic` synced with root `gradle.properties` (#3159) Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit 25d4bf5e2ffdcb84f6469f5e0a81108777a98e85) --- gradle.properties | 1 + gradle/build-logic/gradle.properties | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 gradle/build-logic/gradle.properties diff --git a/gradle.properties b/gradle.properties index 22e7dfed58..5826add1c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ android.useAndroidX=true kotlin.code.style=official kotlin.mpp.androidSourceSetLayoutVersion=2 +# Configuration should be synced with [/gradle/build-logic/gradle.properties] org.gradle.caching=true org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 diff --git a/gradle/build-logic/gradle.properties b/gradle/build-logic/gradle.properties new file mode 100644 index 0000000000..ed607fa33c --- /dev/null +++ b/gradle/build-logic/gradle.properties @@ -0,0 +1,8 @@ +# Configuration should be synced with [/gradle.properties] +# +# Gradle properties are not passed to included builds +# https://github.com/gradle/gradle/issues/2534 +org.gradle.caching=true +org.gradle.configureondemand=true +org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 +org.gradle.parallel=true From fa18503a53bc728654f1e695c1d79e2123a71dc1 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 7 Apr 2026 19:36:33 +0100 Subject: [PATCH 22/59] Update dependency io.kotest:kotest-assertions-core to v6.1.11 (#3185) (cherry picked from commit af04276bedcd35479de7d2716a29cdca9d314583) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97b56be708..cce7979469 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ image-decoder = "41c059e540" injekt = "91edab2317" jsoup = "1.22.1" junit = "6.0.3" -kotest-assertions = "6.1.10" +kotest-assertions = "6.1.11" kotlin-gradle = "2.3.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" From 7532ac5c5a4d05e3d726ff3ce4f54cf7da5ab94c Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 8 Apr 2026 10:41:04 +0100 Subject: [PATCH 23/59] Update moko.resources to v0.26.2 (#3202) (cherry picked from commit 2a1859d7d7000f4f92f13c4ca41996d2d83df857) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cce7979469..2be220a236 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ markdown = "0.39.2" material = "1.12.0" materialKolor = "5.0.0-alpha07" mockk = "1.14.9" -moko-resources = "0.26.1" +moko-resources = "0.26.2" okhttp = "5.3.2" okio = "3.17.0" photoView = "2.3.0" From 82906443d7e6907665a14f53ae81bfb4015cbd03 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 8 Apr 2026 21:38:00 +0100 Subject: [PATCH 24/59] Update dependency androidx.annotation:annotation to v1.10.0 (#3204) (cherry picked from commit ed3b5305f1b62c7ac1b05539d17609ccfddf4ae5) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2be220a236..ff30f75afd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ aboutLibraries = "13.2.1" android-desugar = "2.1.5" android-gradle = "8.13.2" androidx-activity-compose = "1.13.0" -androidx-annotation = "1.9.1" +androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" From 434d17dd9180e01725a6b6d5ddb3abf96413a560 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Apr 2026 05:19:03 +0100 Subject: [PATCH 25/59] Update dependency androidx.compose:compose-bom-beta to v2026.04.00 (#3205) (cherry picked from commit 4b1ae0526b09c30b47eef68fee948ae24afc490f) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff30f75afd..cb7e7c06b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" -androidx-compose-bom = "2026.03.01" +androidx-compose-bom = "2026.04.00" androidx-constraintLayout = "2.2.1" androidx-core = "1.18.0" androidx-coreSplashScreen = "1.2.0" From 5c5915c478386e7b53693a0d30a9ba4d32e507ad Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Apr 2026 05:26:25 +0100 Subject: [PATCH 26/59] Update aboutLibraries to v14 (major) (#3195) Update aboutLibraries to v14 (cherry picked from commit b7cdfb1b30ef6e8f0e1e19892146d34a8b818ae8) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb7e7c06b5..8dabddc314 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutLibraries = "13.2.1" +aboutLibraries = "14.0.0" android-desugar = "2.1.5" android-gradle = "8.13.2" androidx-activity-compose = "1.13.0" From 844ac5700c3c02dffa99e204344994273616c41b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Apr 2026 17:58:44 +0100 Subject: [PATCH 27/59] Update moko.resources to v0.26.3 (#3213) (cherry picked from commit 3521ccfb4f3d5f9fec1a4ea09fcd54a9c2a25ce8) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8dabddc314..af47cc441c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ markdown = "0.39.2" material = "1.12.0" materialKolor = "5.0.0-alpha07" mockk = "1.14.9" -moko-resources = "0.26.2" +moko-resources = "0.26.3" okhttp = "5.3.2" okio = "3.17.0" photoView = "2.3.0" From b58ad27f59c170dfd95c8dde270d3056ea5a1f43 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Apr 2026 21:06:43 +0100 Subject: [PATCH 28/59] Update kotlinx.serialization to v1.11.0 (#3218) (cherry picked from commit 02c64278237c3be3378322530b443090f6d226f7) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af47cc441c..cebd50065a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ kotest-assertions = "6.1.11" kotlin-gradle = "2.3.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" -kotlinx-serialization = "1.10.0" +kotlinx-serialization = "1.11.0" ktlint = "1.8.0" leakCanary = "2.14" logcat = "0.4" From 48cb698ec257e5fefe793c234174c195464dbe91 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 9 Apr 2026 22:28:15 +0200 Subject: [PATCH 29/59] Translations update from Hosted Weblate (#3141) Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/de/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/el/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ko/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nl/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pl/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals Co-authored-by: Dexroneum Co-authored-by: Frosted Co-authored-by: Gino Cicatiello Co-authored-by: Infy's Tagalog Translations Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com> Co-authored-by: MajorTanya Co-authored-by: Pitpe11 Co-authored-by: Ram Co-authored-by: ScratchBuild Co-authored-by: Siebrenvde Co-authored-by: Swyter Co-authored-by: Victoria Niedzielska Co-authored-by: ZenVinny Co-authored-by: ZerOriSama Co-authored-by: on9686 (cherry picked from commit 230dc6fd05a5f0010cceecab6d77ee9be2ca0fa0) --- .../commonMain/moko-resources/ar/strings.xml | 3 --- .../commonMain/moko-resources/as/strings.xml | 2 -- .../commonMain/moko-resources/be/strings.xml | 3 --- .../commonMain/moko-resources/bg/strings.xml | 2 -- .../commonMain/moko-resources/bn/strings.xml | 3 --- .../commonMain/moko-resources/ca/strings.xml | 3 --- .../commonMain/moko-resources/ceb/strings.xml | 1 - .../commonMain/moko-resources/cs/strings.xml | 3 --- .../commonMain/moko-resources/cv/strings.xml | 1 - .../commonMain/moko-resources/de/plurals.xml | 4 ++++ .../commonMain/moko-resources/de/strings.xml | 3 --- .../commonMain/moko-resources/el/plurals.xml | 4 ++++ .../commonMain/moko-resources/el/strings.xml | 4 +--- .../commonMain/moko-resources/eo/strings.xml | 3 --- .../commonMain/moko-resources/es/plurals.xml | 5 +++++ .../commonMain/moko-resources/es/strings.xml | 3 --- .../commonMain/moko-resources/eu/strings.xml | 1 - .../commonMain/moko-resources/fa/strings.xml | 2 -- .../commonMain/moko-resources/fi/strings.xml | 2 -- .../commonMain/moko-resources/fil/plurals.xml | 4 ++++ .../commonMain/moko-resources/fil/strings.xml | 4 +--- .../commonMain/moko-resources/fr/strings.xml | 3 --- .../commonMain/moko-resources/gl/strings.xml | 2 -- .../commonMain/moko-resources/he/strings.xml | 2 -- .../commonMain/moko-resources/hi/strings.xml | 3 --- .../commonMain/moko-resources/hr/strings.xml | 3 --- .../commonMain/moko-resources/hu/strings.xml | 2 -- .../commonMain/moko-resources/in/strings.xml | 3 --- .../commonMain/moko-resources/it/strings.xml | 11 +++++----- .../commonMain/moko-resources/ja/strings.xml | 6 ++---- .../commonMain/moko-resources/jv/strings.xml | 1 - .../moko-resources/ka-rGE/strings.xml | 1 - .../commonMain/moko-resources/kk/strings.xml | 2 -- .../commonMain/moko-resources/kn/strings.xml | 1 - .../commonMain/moko-resources/ko/plurals.xml | 3 +++ .../commonMain/moko-resources/ko/strings.xml | 4 +--- .../commonMain/moko-resources/lt/strings.xml | 2 -- .../commonMain/moko-resources/lv/strings.xml | 2 -- .../commonMain/moko-resources/ml/strings.xml | 13 ++++++++++++ .../commonMain/moko-resources/ms/strings.xml | 3 --- .../commonMain/moko-resources/my/strings.xml | 1 - .../moko-resources/nb-rNO/strings.xml | 2 -- .../commonMain/moko-resources/ne/strings.xml | 3 --- .../commonMain/moko-resources/nl/plurals.xml | 4 ++++ .../commonMain/moko-resources/nl/strings.xml | 3 --- .../commonMain/moko-resources/nn/strings.xml | 1 - .../commonMain/moko-resources/pl/plurals.xml | 6 ++++++ .../commonMain/moko-resources/pl/strings.xml | 6 ++++-- .../moko-resources/pt-rBR/plurals.xml | 5 +++++ .../moko-resources/pt-rBR/strings.xml | 21 ++++++++++--------- .../commonMain/moko-resources/pt/strings.xml | 3 --- .../commonMain/moko-resources/ro/strings.xml | 3 --- .../commonMain/moko-resources/ru/plurals.xml | 14 +++++++++---- .../commonMain/moko-resources/ru/strings.xml | 4 +--- .../commonMain/moko-resources/sa/strings.xml | 2 -- .../commonMain/moko-resources/sah/strings.xml | 1 - .../commonMain/moko-resources/sc/strings.xml | 2 -- .../commonMain/moko-resources/sk/strings.xml | 3 --- .../commonMain/moko-resources/sq/strings.xml | 2 -- .../commonMain/moko-resources/sr/strings.xml | 2 -- .../commonMain/moko-resources/sv/strings.xml | 2 -- .../commonMain/moko-resources/ta/strings.xml | 3 --- .../commonMain/moko-resources/th/strings.xml | 3 --- .../commonMain/moko-resources/tr/plurals.xml | 12 +++++++---- .../commonMain/moko-resources/tr/strings.xml | 3 --- .../commonMain/moko-resources/uk/strings.xml | 2 -- .../commonMain/moko-resources/vi/strings.xml | 3 --- .../moko-resources/zh-rCN/plurals.xml | 3 +++ .../moko-resources/zh-rCN/strings.xml | 4 +--- .../moko-resources/zh-rTW/plurals.xml | 3 +++ .../moko-resources/zh-rTW/strings.xml | 4 +--- 71 files changed, 100 insertions(+), 157 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/ar/strings.xml b/i18n/src/commonMain/moko-resources/ar/strings.xml index 00ad13fe44..81d8ac57b2 100644 --- a/i18n/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n/src/commonMain/moko-resources/ar/strings.xml @@ -409,7 +409,6 @@ مؤلّف غير معروف لا تمتلك أيّة مصادر مثبّتة إبحث عن \"%1$s\" بشكلٍ شامل - حُدِّث إلى الإصدار v%1$s لم يُعثر على فصول %1$s: %2$s, صفحة %3$d إعدادات الفصل الإفتراضية المُحدثة @@ -567,7 +566,6 @@ خطأ أثناء حفظ الصورة معطل رأسي بالعكس - فشل %1$d تحديث أو تحديثات اضغط لمعرفة المزيد نقل سلسلة الفصول للأعلى لا توجد مدخلات في المكتبة لإجراء نسخة احتياطي‮‮ة @@ -852,7 +850,6 @@ التكرارات المحتملة تحديث المتابعات إلى الفصل %d؟ إيقاف قراءة التاريخ مؤقتًا للتمديد - تبرع كاتبوتشين ­تقديم الصور في أوصاف المانجا إخفاء مؤشرات الفصل المفقودة diff --git a/i18n/src/commonMain/moko-resources/as/strings.xml b/i18n/src/commonMain/moko-resources/as/strings.xml index 53d4ee14ab..96df539ea4 100644 --- a/i18n/src/commonMain/moko-resources/as/strings.xml +++ b/i18n/src/commonMain/moko-resources/as/strings.xml @@ -551,7 +551,6 @@ খুলা উৎস লাইচেন্স গোপনীয়তা নীতি আপডেট চাওক - v%1$s ত আপডেট কৰা হৈছে ক্রাশ ৰিপ\'ৰ্ট পঠিয়াব যিকোনো বাগ মেলাবলৈ সহায় কৰে। কোনো সংবেদনশীল তথ্য পঠিয়াব নোৱাৰি F-Droid নিৰ্মাণসমূহৰ আনুষ্ঠানিক সমৰ্থন নাই। @@ -755,7 +754,6 @@ অধ্যায় %1$s অধ্যায় %1$s আৰু %2$d অধিক অধ্যায়সমূহ %1$s - %1$d আপডেট(s) ব্যৰ্থ অধিক জানিবলৈ টেপ কৰক কভাৰ আপডেট কৰিবলৈ ব্যৰ্থ এইটো কৰিবলৈ আগত অনুগ্ৰহ কৰি ৰেকৰ্ডটো আপোনাৰ লাইব্রেৰীত যোগ কৰক diff --git a/i18n/src/commonMain/moko-resources/be/strings.xml b/i18n/src/commonMain/moko-resources/be/strings.xml index 08aa5b65f9..435402f332 100644 --- a/i18n/src/commonMain/moko-resources/be/strings.xml +++ b/i18n/src/commonMain/moko-resources/be/strings.xml @@ -348,7 +348,6 @@ Наступнае Сканлейтар Спампована - Падтрымаць Аўта Наступнае чаканае абнаўленне Ацэнка трэкера @@ -587,7 +586,6 @@ Ліцэнзіі адкрытага кода Палітыка прыватнасці Праверыць абнаўленні - Абноўлена да %1$s Адпраўляць справаздачы пра збоі Дапамагае выпраўляць памылкі. Ніякія асабістыя даныя не будуць адпраўлены Зборкі F-Droid афіцыйна не падтрымліваюцца.\nНацісніце, каб даведацца больш. @@ -810,7 +808,6 @@ Раздзел %1$s Раздзел %1$s ды яшчэ %2$d Раздзелы %1$s - Не ўдалося %1$d абнаўленне(яў) Націсніце, каб даведацца больш Не ўдалося абнавіць вокладку Калі ласка, дадайце твор у бібліятэку перад гэтым дзеяннем diff --git a/i18n/src/commonMain/moko-resources/bg/strings.xml b/i18n/src/commonMain/moko-resources/bg/strings.xml index 3208bc2350..6b23be1222 100644 --- a/i18n/src/commonMain/moko-resources/bg/strings.xml +++ b/i18n/src/commonMain/moko-resources/bg/strings.xml @@ -400,7 +400,6 @@ Неизвестен автор Нямате отметнати източници Търси „%1$s“ глобално - Обновена до v%1$s Какво е новото Не сте влезли като потребител в: Позволи изтриване на отметнати глави @@ -618,7 +617,6 @@ Версия В хиатус Корица по избор - %1$d провалени обновление/я Търсене… Деактивирано Натисни, за да научиш повече diff --git a/i18n/src/commonMain/moko-resources/bn/strings.xml b/i18n/src/commonMain/moko-resources/bn/strings.xml index a297619430..1b1ef81184 100644 --- a/i18n/src/commonMain/moko-resources/bn/strings.xml +++ b/i18n/src/commonMain/moko-resources/bn/strings.xml @@ -410,7 +410,6 @@ পড়ার ইতিহাসে বিরতি দেয় ছদ্মবেশী মোড শুধু ডাউনলোড - v%1$s এ আপডেট হয়েছে সংস্করণ যাচাই ওপেন সোর্স লাইসেন্স নতুন কি @@ -551,7 +550,6 @@ এড়িয়ে যাওয়া হয়েছে কারণ সিরিজটি সম্পূর্ণ বাতিল করা হয়েছে বিরতিতে রয়েছে - %1$d আপডেট(গুলি) ব্যর্থ হয়েছে সম্পূর্ণ প্রকাশিত সর্বশেষ হালনাগাদ চেক আপনি কি “%s” ক্যাটাগরিটি মুছে ফেলতে চান? @@ -687,7 +685,6 @@ সাদা আর কালো যদি রিডার একটি ফাঁকা ছবি লোড করে, ধাপে ধাপে থ্রেশহোল্ড কমিয়ে দিন।\nসিলেক্টেড %s পারফরম্যান্স অ্যাফেক্ট করবে। ফাঁকা ইমেজ সমস্যা থ্রেশহোল্ড কমিয়ে ঠিক না হলে কেবল এনেবল করবে - ডোনেট অটোমেটিক ডাউনলোড, আগাম ডাউনলোড ওয়ান-ওয়ে প্রগ্রেস সিঙ্ক, এনহ্যান্সড সিঙ্ক সোর্স, এক্সটেনশন, গ্লোবাল সার্চ diff --git a/i18n/src/commonMain/moko-resources/ca/strings.xml b/i18n/src/commonMain/moko-resources/ca/strings.xml index a0e4c37bf5..9c903a3251 100644 --- a/i18n/src/commonMain/moko-resources/ca/strings.xml +++ b/i18n/src/commonMain/moko-resources/ca/strings.xml @@ -386,7 +386,6 @@ Estat desconegut Autor desconegut Cerca «%1$s» globalment - S’ha actualitzat a la v%1$s Novetats Cal reiniciar l’aplicació perquè tingui efecte Xarxa @@ -562,7 +561,6 @@ S’ha omès perquè no hi ha cap capítol llegit S’ha omès perquè hi ha capítols no llegits Premeu per a obtenir més informació - Han fallat %1$d actualitzacions Vertical inversa Mou la sèrie a dalt de tot Desactivat @@ -833,7 +831,6 @@ Monocrom Llista de la biblioteca Segueix de manera privada - Dóna Capítols no llegits Notes Edita les notes diff --git a/i18n/src/commonMain/moko-resources/ceb/strings.xml b/i18n/src/commonMain/moko-resources/ceb/strings.xml index 05e96a8894..108386501a 100644 --- a/i18n/src/commonMain/moko-resources/ceb/strings.xml +++ b/i18n/src/commonMain/moko-resources/ceb/strings.xml @@ -419,7 +419,6 @@ Incognito mode Bersyon Susiha ang mga update - Mga update sa v%1$s Ipadala ang mga taho sa pagkahagsa Naka-log out ka na karon Password diff --git a/i18n/src/commonMain/moko-resources/cs/strings.xml b/i18n/src/commonMain/moko-resources/cs/strings.xml index 69941b3869..9dfe09b97a 100644 --- a/i18n/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n/src/commonMain/moko-resources/cs/strings.xml @@ -332,7 +332,6 @@ Pozastaví historii čtení Anonymní režim Pouze staženo - Aktualizováno na verzi v%1$s Zkontrolovat aktualizace Open Source licence Co je nového @@ -558,7 +557,6 @@ Zobrazit položku Mřížka jen s přebaly Přeskočit nerozečtené položky - %1$d aktualizace(í) selhalo Má pauzu Přeskočeno, protože obsahuje nepřečtené kapitoly Přesunout sérii na začátek @@ -860,7 +858,6 @@ Nepodařilo se vytvořit adresář: %s Chystáte se odstranit položky z databáze Ponechat položky s přečtenými kapitolami - Přispěj Catppuccin Skrýt indikátory chybějících kapitol Nepovolovat soubory s non-ASCII znaky diff --git a/i18n/src/commonMain/moko-resources/cv/strings.xml b/i18n/src/commonMain/moko-resources/cv/strings.xml index 20caa2115c..17a4bc4a3d 100644 --- a/i18n/src/commonMain/moko-resources/cv/strings.xml +++ b/i18n/src/commonMain/moko-resources/cv/strings.xml @@ -391,7 +391,6 @@ Вулавӑша йантӑлавран тавӑр Йантӑлав тавӑр Йантӑлав ту - v%1$s верссиччен ҫӗнетнӗ Мӗн ҫӗнни Темӗ Хушни вӑхӑчӗпе diff --git a/i18n/src/commonMain/moko-resources/de/plurals.xml b/i18n/src/commonMain/moko-resources/de/plurals.xml index 619f1feb02..80586f0c58 100644 --- a/i18n/src/commonMain/moko-resources/de/plurals.xml +++ b/i18n/src/commonMain/moko-resources/de/plurals.xml @@ -88,4 +88,8 @@ Ein Eintrag wurde übersprungen %1$d Einträge wurden übersprungen + + %1$d Aktualisierung ist fehlgeschlagen + %1$d Aktualisierungen sind fehlgeschlagen + diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index 03d25b4eea..d2d85394e3 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -393,7 +393,6 @@ Unbekannter Status Unbekannter Autor - Auf v%1$s aktualisiert Was ist neu Herunterladen von Kapiteln aufgrund von zu wenig Speicherplatz nicht möglich Überall nach „%1$s“ suchen @@ -564,7 +563,6 @@ Übersprungen, da die Serie abgeschlossen ist Übersprungen, weil es ungelesene Kapitel gibt Übersprungen, weil keine Kapitel gelesen wurden - %1$d Aktualisierung(en) fehlgeschlagen Antippen, um mehr zu erfahren Umgekehrtes Hochformat Serie nach oben verschieben @@ -901,7 +899,6 @@ Abbrechen Abbrechen Keine Kapitel gefunden, dieser Eintrag konnte nicht für eine Migration verwendet werden - Spenden Bilder in Mangabeschreibungen anzeigen Nicht-ASCII-Dateinamen nicht zulassen Versichert Kompatibilität mit bestimmten Speichermedien, die Unicode nicht unterstützen. Ist diese Option aktiviert, müssen Quellen- und Manga-Ordner manuell umbenannt werden, indem Nicht-ASCII-Zeichen durch ihre UTF-8-Hexadezimaldarstellungen in Kleinbuchstaben ersetzt werden. Kapiteldateien müssen nicht umbenannt werden. diff --git a/i18n/src/commonMain/moko-resources/el/plurals.xml b/i18n/src/commonMain/moko-resources/el/plurals.xml index 3debd74758..c627de2897 100644 --- a/i18n/src/commonMain/moko-resources/el/plurals.xml +++ b/i18n/src/commonMain/moko-resources/el/plurals.xml @@ -88,4 +88,8 @@ Μια καταχώρηση παραλείφθηκε %1$d καταχωρήσεις παραλείφθηκαν + + %1$d ενημέρωση απέτυχε + %1$d ενημερώσεις απέτυχαν + diff --git a/i18n/src/commonMain/moko-resources/el/strings.xml b/i18n/src/commonMain/moko-resources/el/strings.xml index 917c3a9619..5d0b2decc3 100644 --- a/i18n/src/commonMain/moko-resources/el/strings.xml +++ b/i18n/src/commonMain/moko-resources/el/strings.xml @@ -393,7 +393,6 @@ Αντιστροφή ζωνών πατήματος Άγνωστη κατάσταση Άγνωστος συγγραφέας - Ενημερώθηκε σε v%1$s Τι νέο υπάρχει Δεν ήταν δυνατή η λήψη κεφαλαίων λόγω χαμηλού αποθηκευτικού χώρου Αναζήτηση του \"%1$s\" καθολικά @@ -564,7 +563,6 @@ Παραλείφθηκε επειδή η σειρά ολοκληρώθηκε Παραβλέφθηκε επειδή υπάρχουν μη αναγνωσμένα κεφάλαια Παραλείπεται επειδή δεν έχουν διαβαστεί κεφάλαια - %1$d ενημέρωση(ες) απέτυχε(-αν) Πατήστε για να μάθετε περισσότερα Αντίστροφο πορτρέτο Μετακίνηση σειράς προς τα πάνω @@ -901,7 +899,6 @@ Κρατήστε καταχωρήσεις με αναγνωσμένα κεφάλαια Αποτυχία δημιουργίας καταλόγου λήψης Αποτυχία δημιουργίας καταλόγου: %s - Δωρεά Αναπαράσταση εικόνων σε περιγραφές manga Απαγόρευση μη ASCII ονομάτων αρχείων Εξασφαλίζει συμβατότητα με ορισμένα μέσα αποθήκευσης που δεν υποστηρίζουν Unicode. Όταν αυτή η επιλογή είναι ενεργοποιημένη, θα πρέπει να μετονομάσετε χειροκίνητα τους φακέλους πηγής και manga, αντικαθιστώντας τους χαρακτήρες που δεν είναι ASCII με τις μικρές κεφαλαίες δεκαεξαδικές αναπαραστάσεις UTF-8. Τα αρχεία κεφαλαίων δε χρειάζεται να μετονομάζονται. @@ -911,4 +908,5 @@ Σελίδες που λήφθηκαν ταυτόχρονα ανά πηγή Προστέθηκε στους σελιδοδείκτες Φιλτράρισμα εξαιρούμενων scanlator + Υποστηρίξτε μας diff --git a/i18n/src/commonMain/moko-resources/eo/strings.xml b/i18n/src/commonMain/moko-resources/eo/strings.xml index 09ccc743be..53a5659a14 100644 --- a/i18n/src/commonMain/moko-resources/eo/strings.xml +++ b/i18n/src/commonMain/moko-resources/eo/strings.xml @@ -297,7 +297,6 @@ Ensaluti Ensaluti al %1$s Nur elŝutitaj - Ĝisdatigita al v%1$s Malfermitkodaj permesiloj Kio novas Versio @@ -815,7 +814,6 @@ Averto: grandaj amasaj elŝutoj povas igi fontojn fariĝi pli malrapidaj kaj/aŭ bloki %s. Tuŝetu por ekscii pli. Ĝisdatigado de biblioteko… (%s) Grandaj ĝisdatigoj povas damaĝi fontojn kaj konduki al pli malrapidaj ĝisdatigoj kaj pliigan baterian uzadon. Tuŝetu por ekscii pli. - %1$d ĝisdatigo(j) fiaskis Tuŝetu por ekscii pli Bonvolu aldoni la elementon al via biblioteko antaŭ ol fari tion Por helpo pri kiel ripari erarojn de bibliotekaj ĝisdatigoj, vidu %1$s @@ -889,7 +887,6 @@ Nuligi Nuligi Neniu ĉapitro trovita, ĉi tiu elemento ne povas esti uzota por migri - Donacu Katpuĉino Montri bildojn en mangaaj priskriboj Kaŝi mankantajn ĉapitrajn indikilojn diff --git a/i18n/src/commonMain/moko-resources/es/plurals.xml b/i18n/src/commonMain/moko-resources/es/plurals.xml index 3ea0549b14..2ebe657577 100644 --- a/i18n/src/commonMain/moko-resources/es/plurals.xml +++ b/i18n/src/commonMain/moko-resources/es/plurals.xml @@ -110,4 +110,9 @@ ¿Migrar estos %1$d elementos? ¿Migrar estos %1$d elementos? + + No se ha podido actualizar %1$d elemento + No se han podido actualizar %1$d elementos + No se han podido actualizar %1$d elementos + diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index ebb7c19eb0..2e08c726b5 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -391,7 +391,6 @@ No invertir Invertir zonas de toque Autor desconocido - Actualizado a v%1$s Novedades No se pudo descargar ningún capítulo, queda muy poco espacio Buscar por «%1$s» en todas las fuentes @@ -563,7 +562,6 @@ Omitido porque todavía hay capítulos sin leer Omitido porque no hay capítulos leídos Ver más detalles - Actualizaciones fallidas: %1$d En vertical, al revés Mover al primer puesto Desactivado @@ -894,7 +892,6 @@ Migrando (%1$d/%2$d) Migrar ahora Ocultar los indicadores de capítulos que falten - Donar Ver imágenes en las descripciones de manga Prohibir los nombres de archivo que no sean ASCII Permite guardar tus datos en ciertos tipos de almacenamiento que no admitan Unicode. Al activarlo tendrás que renombrar las carpetas de tus fuentes y manga, pasándolas a representaciones UTF-8 en hexadecimal en minúscula. No tienes que hacer lo mismo con las carpetas de capítulos. diff --git a/i18n/src/commonMain/moko-resources/eu/strings.xml b/i18n/src/commonMain/moko-resources/eu/strings.xml index 9951193e9a..5e485f3d92 100644 --- a/i18n/src/commonMain/moko-resources/eu/strings.xml +++ b/i18n/src/commonMain/moko-resources/eu/strings.xml @@ -169,7 +169,6 @@ Itzali Nabigazioa U - v%1$s-ra eguneratua Saioa hasia Itxi saioa %1$s-n? Liburutegira gehitua diff --git a/i18n/src/commonMain/moko-resources/fa/strings.xml b/i18n/src/commonMain/moko-resources/fa/strings.xml index 9baa5b38ba..6b50df251d 100644 --- a/i18n/src/commonMain/moko-resources/fa/strings.xml +++ b/i18n/src/commonMain/moko-resources/fa/strings.xml @@ -399,7 +399,6 @@ تم غیرفعال کردن تاریخ اضافه شدن - به نسخه v%1$s آپدیت شد جست و جوی تنضیمات قسمت های دانلود شده مانگاهای کتابخانه @@ -602,7 +601,6 @@ به دلیل این که این مجموعه نیازی به به روز رسانی نداشت رد شد به دلیل وجود چپتر های خوانده نشده رد شد به دلیل این که هیچ چپتری خوانده نشده بود رد شد - %1$d بروز رسانی ناموفق \"%1$s\" به جای \"%2$s\" صفحات عریض را بچرخان تا جا شوند محبوب diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index dd11f147de..f1a738991e 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -393,7 +393,6 @@ Käännä näpäyttäminen Tuntematon tila Tuntematon tekijä - Päivitetty versioon v%1$s Mitä uutta Lukuja ei voitu ladata vähäisen tallennustilan vuoksi Etsi \"%1$s\" globaalisesti @@ -571,7 +570,6 @@ Virhe tallentaessa kuvaa Kirjastoa ollaan jo päivitämässä Kirjasto päivitetty viimeksi: %s - %1$d päivitys(tä) epäonnistui Ohitettu, koska sarja on päättynyt Sinulla ei vielä ole kategorioita. Tauolla diff --git a/i18n/src/commonMain/moko-resources/fil/plurals.xml b/i18n/src/commonMain/moko-resources/fil/plurals.xml index 5cf2086a3f..a727a509a0 100644 --- a/i18n/src/commonMain/moko-resources/fil/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fil/plurals.xml @@ -88,4 +88,8 @@ Isang entry ang nilaktawan %1$d (na) entry ang nilaktawan + + %1$d na update ay nabigo + %1$d na mga update ay nabigo + diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 4fde8e327e..d8b8095435 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -367,7 +367,6 @@ Mga na-download lang Nakatutulong sa pag-ayos sa mga bug. Walang sensitibong data ang ipapadala Ipadala ang mga ulat ng pag-crash - Na-update sa v%1$s Maghanap ng update Mga lisensiyang open-source Anong bago @@ -565,7 +564,6 @@ Awtomatikong mag-zoom sa mga malalawak na larawan I-pan ang mga malalapad na larawan Matuto pa - Nabigo ang %1$d (na) update Baligtad na patayo Ilagay sa taas ang serye Nakasara @@ -900,7 +898,6 @@ Ihinto Itago ang mga indikasyon ng nawawalang kabanata I-render ang mga imahe sa mga paglalarawan ng manga - Mag-donate Di-payagan ang mga non-ASCII na filename Tinitiyak ang pagiging tugma sa ilang partikular na storage media na hindi sumusuporta sa Unicode. Kapag napagana ito, kakailanganin mong manu-manong palitan ang pangalan ng source at manga folder sa pamamagitan ng pagpapalit ng mga hindi ASCII na character ng kanilang lowercase na UTF-8 hexadecimal na representasyon. Hindi kailangang palitan ng pangalan ang mga file ng kabanata. Kasabay na pag-download ng source @@ -911,4 +908,5 @@ Maaaring Magamit I-filter ang na-ibukod na mga scanlator Na-bookmark + Suportahan Kami diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index ba5a8f33cb..2d6bfa376c 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -394,7 +394,6 @@ Statut inconnu Auteur inconnu Quoi de neuf - Mise à jour de v%1$s Impossible de télécharger les chapitres, l\'espace de stockage est insuffisant Rechercher « %1$s » partout Date ajoutée @@ -565,7 +564,6 @@ Sauté car aucun chapitre n\'est lu Sauté car il y a des chapitres non lus Appuyez pour en savoir plus - Échec de %1$d mise(s) à jour Portrait inversé Déplacer la série vers le haut Désactivé @@ -901,7 +899,6 @@ Migrer Annuler Annuler - Donation Rendre les images dans les descriptions de mangas Fermer l\'onglet Interdire les noms de fichiers non ASCII diff --git a/i18n/src/commonMain/moko-resources/gl/strings.xml b/i18n/src/commonMain/moko-resources/gl/strings.xml index b8ba879553..cd5ab1b9c8 100644 --- a/i18n/src/commonMain/moko-resources/gl/strings.xml +++ b/i18n/src/commonMain/moko-resources/gl/strings.xml @@ -272,7 +272,6 @@ Procurar actualizacións Licenzas de código aberto Política de privacidade - Actualizado a v%1$s Axuda a corrixir os erros. Non se enviará ningunha información sensíbel Desactivar o modo incógnito Iniciar sesión @@ -606,7 +605,6 @@ Rematado: Páxina: %1$d Produciuse un erro ao gardar a imaxe - Actualizacións erradas: %1$d Descargar Actualizacións da aplicación Preme para saber máis diff --git a/i18n/src/commonMain/moko-resources/he/strings.xml b/i18n/src/commonMain/moko-resources/he/strings.xml index c46b983903..1fdbe1fd50 100644 --- a/i18n/src/commonMain/moko-resources/he/strings.xml +++ b/i18n/src/commonMain/moko-resources/he/strings.xml @@ -524,7 +524,6 @@ WebView נצרך ל-Mihon רשת רק של הכריכות פתח את יומן האירועים - %1$d עדכונים נכשלו בצע קירוב לתמונות אופקיות באופן אוטומטי משפר את ביצועי מצב הקריאה דולג בגלל שהסדרה נגמרה @@ -682,7 +681,6 @@ לא מסוגל לפתוח את הפרק שנקרא לאחרונה פרק בהחלקה שמאלה לחיצה כפולה לקירוב (זום) - עודכן לגרסה %1$s בסך הכל שגיאה %1$s: %2$s עובד רק אם הפרק הנוכחי והפרק הבא כבר הורדו. diff --git a/i18n/src/commonMain/moko-resources/hi/strings.xml b/i18n/src/commonMain/moko-resources/hi/strings.xml index c7d5fe38e7..6d8937a4b1 100644 --- a/i18n/src/commonMain/moko-resources/hi/strings.xml +++ b/i18n/src/commonMain/moko-resources/hi/strings.xml @@ -398,7 +398,6 @@ नया क्या है कम संग्रहण स्थान के कारण अध्याय डाउनलोड नहीं कर सके विश्व स्तर पर \"%1$s\" के लिए खोजें - v%1$s तक अद्यतन पढ़न मोड थीम तारीख को जोड़ा गया @@ -567,7 +566,6 @@ केवल कवर ग्रिड अक्षम रिवर्स पोर्ट्रेट - %1$d अपडेट विफल चौड़ी छवियां पैन करें ज़ूम लैंडस्केप इमेज श्रृंखला को शीर्ष पर ले जाएं @@ -700,7 +698,6 @@ आपकी पुस्तकालय में एक समान नाम वाली प्रविष्टियाँ हैं।\n\nस्थानांतरित करने के लिए एक प्रविष्टि चुनें या फिर भी जोड़ें। %1$s अध्याय डाउनलोड, बैकअप और अन्य चीज़ों को संग्रहित करने के लिए एक फ़ोल्डर चुनें।\n\nएक समर्पित फ़ोल्डर उपयुक्त रहेगा।\n\nचयनित फ़ोल्डर: %2$s एक्सटेंशन स्थापित करने के लिए अनुमतियाँ आवश्यक हैं। अनुमति देने के लिए यहाँ टैप करें। - दान करें अनुप्रयोग संस्थापित करने की अनुमति स्रोत विस्तार संस्थापित करने हेतु। अधिसूचना अनुमति diff --git a/i18n/src/commonMain/moko-resources/hr/strings.xml b/i18n/src/commonMain/moko-resources/hr/strings.xml index 3046ce516f..9d711d1a00 100644 --- a/i18n/src/commonMain/moko-resources/hr/strings.xml +++ b/i18n/src/commonMain/moko-resources/hr/strings.xml @@ -393,7 +393,6 @@ Preokreni područja za dodirivanje Nepoznato stanje Nepoznat autor - Aktualizirano na v%1$s Što je novo Neuspjelo preuzimanje poglavlja zbog nedovoljne memorije Traži „%1$s” globalno @@ -564,7 +563,6 @@ Preskočeno, jer je serija kompletna Preskočeno, jer nijedno poglavlje nije pročitano Preskočeno, jer postoje nepročitana poglavlja - Nauspjela aktualiziranja: %1$d Dodirni za daljnje informacije Preokreni uspravni format Premjesti seriju na vrh @@ -901,7 +899,6 @@ Najnovije: %1$s Nepoznato Nije pronađeno nijedno poglavlje. Ovaj se unos ne može koristiti za premještanje - Doniraj Iscrtaj slike u opisima manga Zabrani imena datoteka koji nisu u ASCII formatu Osigurava kompatibilnost s određenim medijima za spremanje podataka koji ne podržavaju Unicode. Kada je ova opcija aktivirana, morat ćeš ručno preimenovati izvor i mape s mangama zamjenjivanjem znakova koji nisu ASCII s njihovim UTF-8 heksadecimalnim vrijednostima (u malim slovima). Datoteke poglavlja se ne moraju preimenovati. diff --git a/i18n/src/commonMain/moko-resources/hu/strings.xml b/i18n/src/commonMain/moko-resources/hu/strings.xml index 0f1f823e9d..22d061306b 100644 --- a/i18n/src/commonMain/moko-resources/hu/strings.xml +++ b/i18n/src/commonMain/moko-resources/hu/strings.xml @@ -510,7 +510,6 @@ Ez az Android verzió már nem támogatott Alkalmazás frissítések Nem sikerült megnyitni az utoljára olvasott fejezetet - Frissítve erre: v%1$s Széles képek pásztázása Mentés CBZ archívumkén Levendula @@ -538,7 +537,6 @@ Jelenlegi: Hát, ez kínos A nagy frissítések kárt okoznak a forrásoknak, és lassabb frissítésekhez, valamint megnövekedett akkumulátorhasználathoz vezethetnek. Koppintson további információért. - %1$d frissítés sikertelen Javítja az olvasó teljesítményét Nem bejelentkezett nyilvántartó: Inkognitó mód diff --git a/i18n/src/commonMain/moko-resources/in/strings.xml b/i18n/src/commonMain/moko-resources/in/strings.xml index b1651faafb..790da8cc24 100644 --- a/i18n/src/commonMain/moko-resources/in/strings.xml +++ b/i18n/src/commonMain/moko-resources/in/strings.xml @@ -391,7 +391,6 @@ Balikkan zona ketuk Status tidak diketahui Penulis tidak diketahui - Diperbarui ke v%1$s Apa yang baru Tidak dapat mengunduh bab karena ruang penyimpanan rendah Pencarian untuk \"%1$s\" secara global @@ -566,7 +565,6 @@ Geser gambar lebar Pelajari lebih lanjut Layar tegak terbalik - %1$d pembaruan gagal Pindahkan seri ke atas Dinonaktifkan Gagal menyimpan gambar @@ -853,7 +851,6 @@ Setelah mengambil bab baru Sembunyikan indikator bab yang hilang Gagal membuat direktori unduhan - Donasi Render gambar dalam deskripsi manga Gagal membuat direktori: %s Anda akan menghapus entri dari database diff --git a/i18n/src/commonMain/moko-resources/it/strings.xml b/i18n/src/commonMain/moko-resources/it/strings.xml index 8c0e44ee9f..f5b45adaef 100644 --- a/i18n/src/commonMain/moko-resources/it/strings.xml +++ b/i18n/src/commonMain/moko-resources/it/strings.xml @@ -387,7 +387,6 @@ Fonte non trovata Stato sconosciuto Autore sconosciuto - Aggiornato alla v%1$s Le novità Richiesto riavvio dell\'app per applicare le modifiche Rete @@ -564,7 +563,6 @@ Saltato perché la serie è completa Saltato perché ci sono capitoli non letti Saltato perché non ci sono capitoli letti - %1$d aggiornamento(i) fallito(i) Tocca per approfondire Verticale inverso Sposta la serie in cima @@ -707,7 +705,7 @@ Sincronizzazione libreria completata Sincronizzazione libreria Tocca qua per assistenza con Cloudflare - Indice dei download invalidato + Ricreando l\'indice dei download Login del trakcer Non è stato possibile creare un file di backup HTTP %d, controlla il sito nella WebView @@ -733,7 +731,7 @@ Altre opzioni Selezionato Non selezionato - Scanlator + Traduttore Salta Prossimo Uso della batteria in secondo piano @@ -850,7 +848,7 @@ Lista libreria Libreria esportata Metti in pausa la cronologia di lettura per l\'estensione - Aggiorna i titoli della libreria perchè coincidano con la fonte + Aggiorna i titoli della libreria perché coincidano con la fonte Attenzione: se un manga viene rinominato, sarà rimosso dalla coda download (se presente). Autenticazione in corso… Possibili duplicati @@ -869,7 +867,6 @@ Creazione della cartella download fallita Creazione della cartella %s fallita Mi è piaciuta la parte dove… - Dona Chiudi scheda Renderizza immagini nella descrizioni dei manga Nascondi gli indicatori dei capitoli mancanti @@ -909,4 +906,6 @@ Annulla Annulla Nessun capitolo trovato, questa voce non può essere utilizzata per la migrazione + Segnato + Filtrare i scanlator esclusi diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index d0e36e0bfd..bdb5c4e199 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -13,7 +13,7 @@ バックアップと復元 設定 絞り込む - お気に入り + ブックマーク済み 未読 フィルタを削除 アルファベット順 @@ -393,7 +393,6 @@ 変更を適用するには再起動してください ネットワーク タップでのページめくりを反転 - v%1$sに更新しました 更新情報 読み方 完了 @@ -565,7 +564,6 @@ 読了した章がない為スキップしました 幅広い画像を左右に移動 タップでもっと詳しく - %1$d件の更新に失敗しました 縦向き (反転) シリーズをトップに移動 無効 @@ -852,7 +850,6 @@ 非図書館エントリの章の読み取りと進捗は失われます 読み終えた章を含むエントリーを保持する 重複の可能性 - 寄付する 自動 キャットプッチン モノクロ @@ -875,4 +872,5 @@ 有効なソースを選択 固定されたソースを選択 移行するデータ + ブックマーク済み diff --git a/i18n/src/commonMain/moko-resources/jv/strings.xml b/i18n/src/commonMain/moko-resources/jv/strings.xml index 8bb0d7e209..d602994fd6 100644 --- a/i18n/src/commonMain/moko-resources/jv/strings.xml +++ b/i18n/src/commonMain/moko-resources/jv/strings.xml @@ -308,7 +308,6 @@ Bakal muncul navigasi mendhuwur Diluru - Sumbangan Otomatis Bikak kunci %s Trapake jeda wayah diff --git a/i18n/src/commonMain/moko-resources/ka-rGE/strings.xml b/i18n/src/commonMain/moko-resources/ka-rGE/strings.xml index a0a451f620..20520817da 100644 --- a/i18n/src/commonMain/moko-resources/ka-rGE/strings.xml +++ b/i18n/src/commonMain/moko-resources/ka-rGE/strings.xml @@ -393,7 +393,6 @@ უცნობი სტატუსი უცნობი ავტორი მოიძებნოს \"%1$s\"-თვის გლობალურად - განახლებულია v%1$s-მდე რა არის ახალი ასამოქმედებლად საჭიროა აპლიკაციის გადატვირთვა ქსელი diff --git a/i18n/src/commonMain/moko-resources/kk/strings.xml b/i18n/src/commonMain/moko-resources/kk/strings.xml index 0b9f99e821..165a0d11cd 100644 --- a/i18n/src/commonMain/moko-resources/kk/strings.xml +++ b/i18n/src/commonMain/moko-resources/kk/strings.xml @@ -403,7 +403,6 @@ Сұрыптау Нұсқа Open source лицензасы - %1$s дейін жаңартылды Жарамсыз тарау пішіні Күн Шығып жатыр @@ -509,7 +508,6 @@ Келесі: Сенімдісіз бе? Бар тарих жоғалады. Дереккөз табылмады - %1$d жаңарту сәтсіз өтті Мұқаба жаңартылмады Жаңа нұсқа қолжетімді! %d бет табылмады, бөлінгенде diff --git a/i18n/src/commonMain/moko-resources/kn/strings.xml b/i18n/src/commonMain/moko-resources/kn/strings.xml index 2527addc2a..a431e69d12 100644 --- a/i18n/src/commonMain/moko-resources/kn/strings.xml +++ b/i18n/src/commonMain/moko-resources/kn/strings.xml @@ -395,7 +395,6 @@ ಅಜ್ಞಾತ ಸ್ಥಿತಿ ಅಜ್ಞಾತ ಲೇಖಕ \"%1$s\" ಅನ್ನು ಗ್ಲೋಬಲ್ ಸರ್ಚ್ ಮಾಡಿ - v%1$s ಗೆ ಅಪ್ಡೇಟ್ ಆಗಿದೆ ಹೊಸತೇನಿದೆ ಅನ್ವಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್ ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ ಅಡ್ವಾನ್ಸ್ಡ್ ಸೆಕ್ಷನ್ diff --git a/i18n/src/commonMain/moko-resources/ko/plurals.xml b/i18n/src/commonMain/moko-resources/ko/plurals.xml index 0d1c7d0bef..31f6cae264 100644 --- a/i18n/src/commonMain/moko-resources/ko/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ko/plurals.xml @@ -66,4 +66,7 @@ %1$d개 항목이 생략되었습니다 + + %1$d개 업데이트 실패 + diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index f70d8930c7..d3ebb3fd9c 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -298,7 +298,6 @@ 시크릿 모드 끄기 다운로드가 완료된 항목만 표시 시크릿 모드 - v%1$s 으로 업데이트 됨 업데이트 확인 오픈 소스 라이선스 디바이스 설정을 열 수 없습니다 @@ -465,7 +464,6 @@ 경고: 대량 다운로드는 원본 속도에 영향을 미치거나 %s을 차단할 수 있습니다. 탭하여 자세히 알아보기. 대규모 업데이트는 소스에 피해를 입히고 업데이트가 느려지고 배터리 사용량이 증가할 수 있습니다. 탭하여 자세히 알아보기. %1$s화 - %1$d개의 업데이트가 실패했습니다 서재 업데이트 오류를 해결하려면 %1$s를 참조하세요 완결된 만화를 건너 뛰었습니다 읽지 않은 만화를 건너 뛰었습니다 @@ -786,7 +784,6 @@ 사용가능: %1$s / 전체: %2$s Nord 곧 공개 - 기부 자동 무작위 링크 복사 @@ -910,4 +907,5 @@ 챕터를 찾을 수 없어 이 항목은 마이그레이션에 사용할 수 없습니다 스캔레이터를 제외한 필터 북마크됨 + 저희를 지원해 주세요 diff --git a/i18n/src/commonMain/moko-resources/lt/strings.xml b/i18n/src/commonMain/moko-resources/lt/strings.xml index 99bd76dd9c..f64dad45af 100644 --- a/i18n/src/commonMain/moko-resources/lt/strings.xml +++ b/i18n/src/commonMain/moko-resources/lt/strings.xml @@ -347,7 +347,6 @@ Priskirti šiai serijai Baigta: Dabartinis: - Nepavyko atnaujinti %1$d Prieš atlikdami šį veiksmą, pridėkite įrašą į savo biblioteką Praleista, nes nėra perskaitytų skyrių Nepavyko apeiti \"Cloudflare\" @@ -536,7 +535,6 @@ Atsisiųsti Data Kategorijos atnaujinimas - Atnaujinta į v%1$s Prisijunkite prie %1$s Rezultatų nerasta Vykdoma diff --git a/i18n/src/commonMain/moko-resources/lv/strings.xml b/i18n/src/commonMain/moko-resources/lv/strings.xml index 4745fd8964..20b508d4bd 100644 --- a/i18n/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n/src/commonMain/moko-resources/lv/strings.xml @@ -402,7 +402,6 @@ Tīmekļa vietne Versija Jaunumi - Atjaunināts uz v%1$s Atvērtā avota licences Pārbaudīt, vai nav atjauninājumu Notīrīt WebView datus @@ -556,7 +555,6 @@ Nodaļa %1$s Sadalīšanas laikā netika atrasta lapa %d Izlaists, jo ir nelasītas nodaļas - %1$d atjauninājums(-i) neizdevās Pieskarieties, lai uzzinātu vairāk Lietotnes atjauninājumi Ir pieejama jauna versija! diff --git a/i18n/src/commonMain/moko-resources/ml/strings.xml b/i18n/src/commonMain/moko-resources/ml/strings.xml index 944334eba3..0428034d0a 100644 --- a/i18n/src/commonMain/moko-resources/ml/strings.xml +++ b/i18n/src/commonMain/moko-resources/ml/strings.xml @@ -207,4 +207,17 @@ ലൈബ്രറി അപ്‌ഡേറ്റുകൾക്കും മറ്റും അറിയിപ്പ് നേടുക. പശ്ചാത്തല ബാറ്ററി ഉപയോഗം ദീർഘകാല ലൈബ്രറി അപ്‌ഡേറ്റുകൾ, ഡൗൺലോഡ്, ബാക്കപ്പ് പുനഃസ്ഥാപിക്കൽ എന്നിവയ്ക്കുള്ള തടസ്സങ്ങൾ ഒഴിവാക്കുക. + നിരസിക്കുക + നിരസിക്കുക + സ്ഥലാന്തരീകരിക്കുക + പകർത്തുക + നിരസിക്കുക + നിർത്തുക + സ്ഥലാന്തരീകരണം നിർത്തണോ? + അജ്ഞാതം + സ്ഥലാന്തരീകരിക്കുക + പകർത്തുക + സ്ഥലാന്തരീകരണം + തുടരുക + ലഭ്യം diff --git a/i18n/src/commonMain/moko-resources/ms/strings.xml b/i18n/src/commonMain/moko-resources/ms/strings.xml index 7d0e328dff..b8abb91efa 100644 --- a/i18n/src/commonMain/moko-resources/ms/strings.xml +++ b/i18n/src/commonMain/moko-resources/ms/strings.xml @@ -393,7 +393,6 @@ Songsangkan zon ketik Status tidak diketahui Pengarang tidak diketahui - Dikemas kini ke v%1$s Apa yang terbaharu Tarikh ditambah Memuat turun tidak berjaya kerana kekurangan ruang storan @@ -564,7 +563,6 @@ Dilangkau kerana tiada bab yang dibaca Zum secara automatik pada imej lebar Panorama imej lebar - %1$d kemas kini gagal Ketik untuk ketahui selebihnya Potret terbalik Alih siri ke atas @@ -792,7 +790,6 @@ Panduan yang akan datang Bulan depan Bulan sebelumnya - Menderma Otomatis Rawak Pautan salin diff --git a/i18n/src/commonMain/moko-resources/my/strings.xml b/i18n/src/commonMain/moko-resources/my/strings.xml index 6b57f782a5..b023054a81 100644 --- a/i18n/src/commonMain/moko-resources/my/strings.xml +++ b/i18n/src/commonMain/moko-resources/my/strings.xml @@ -292,6 +292,5 @@ ကန့်သက်မထားသော ကွန်ရက်ပေါ်တွင်သာ သင့်ဖုန်းမှ ဖယ်ရှားရန် Dynamic - လှူဒါန်း အလိုအလျောက် diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml index a0525b959e..b898840975 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml @@ -409,7 +409,6 @@ Ikke nok lagringsplass til å laste ned kapitler Anvend også for allle oppføringer i biblioteket Søk etter \"%1$s\" globalt - Oppdatert til v%1$s Friprog-lisenser Hva er nytt Hjelper med oppdatering og sikkerhetskopiering av biblioteket @@ -565,7 +564,6 @@ Klarte ikke å hente tilgjengelige utvidelser Omvendt portrett Tøm kapittel hurtigbufferen ved oppstart av appen - %1$d oppdatering(er) mislyktes Trykk for å finne ut mer Hoppet over fordi ingen kapitler er lest Deaktivert diff --git a/i18n/src/commonMain/moko-resources/ne/strings.xml b/i18n/src/commonMain/moko-resources/ne/strings.xml index cf7d967df7..492897f6c0 100644 --- a/i18n/src/commonMain/moko-resources/ne/strings.xml +++ b/i18n/src/commonMain/moko-resources/ne/strings.xml @@ -169,7 +169,6 @@ डाउनलोड गरिएको मात्र कुनै पनि बगहरू ठीक गर्न मद्दत गर्दछ। कुनै संवेदनशील डेटा पठाइने छैन Crash रिपोर्ट पठाउनुहोस् - v%1$s मा अपडेट गरियो अपडेटका लागि जाँच गर्नुहोस् छवि लोड गर्न सकिएन अध्यायहरू डाउनलोड गर्न सकिएन। तपाईं डाउनलोड सूचीमा फेरि प्रयास गर्न सक्नुहुन्छ @@ -564,7 +563,6 @@ सुरु गर्ने गाइड चौडा छविहरू प्यान गर्नुहोस् फराकिलो छविहरूमा स्वचालित रूपमा जूम गर्नुहोस् - %1$d अपडेट(हरू) असफल भयो असक्षम पुस्तकालय अपडेट त्रुटिहरू कसरी समाधान गर्ने भन्ने बारे मद्दतको लागि, %1$s हेर्नुहोस् नपढिएको संख्या @@ -859,7 +857,6 @@ तपाईंको पुस्तकालयमा उस्तै नामका इन्ट्रीहरू छन्।\n\nस्थानान्तरण गर्न वा जे भए पनि थप्नको लागि इन्ट्री चयन गर्नुहोस्। तपाईं डाटाबेसबाट इन्ट्रीहरू हटाउन लाग्दै हुनुहुन्छ डाउनलोड directory सिर्जना गर्न असफल भयो - दान कात्पुचिन mangaको विवरणहरूमा तस्वीर देखाउनुहोस नभएको अध्यायहरु लुकौनुहोस diff --git a/i18n/src/commonMain/moko-resources/nl/plurals.xml b/i18n/src/commonMain/moko-resources/nl/plurals.xml index 44805cca4c..fcadb40b10 100644 --- a/i18n/src/commonMain/moko-resources/nl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/nl/plurals.xml @@ -88,4 +88,8 @@ Een item werd overgeslagen %1$d items werden overgeslagen + + %1$d update mislukt + %1$d updates mislukt + diff --git a/i18n/src/commonMain/moko-resources/nl/strings.xml b/i18n/src/commonMain/moko-resources/nl/strings.xml index eb32161ecf..5e1ee508ad 100644 --- a/i18n/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n/src/commonMain/moko-resources/nl/strings.xml @@ -393,7 +393,6 @@ Onbekende auteur Kon hoofdstukken niet downloaden omdat er te weinig opslagruimte is Overal zoeken naar \"%1$s\" - Bijgewerkt naar v%1$s Wat is er nieuw Leesmodus Thema @@ -554,7 +553,6 @@ Uitgave voltooid Geannuleerd Gepauzeerd - %1$d update(s) mislukt Tik om meer te leren Overgeslagen want er zijn geen ongelezen hoofdstukken Overgeslagen want er zijn geen gelezen hoofdstukken @@ -818,7 +816,6 @@ Duplicaat gelezen hoofdstuk als gelezen markeren Ongelezen hoofdstukken Monochroom - Doneer Geef afbeeldingen weer in mangabeschrijvingen Verberg ontbrekende hoofdstukindicatoren Verbied niet-ASCII-bestandsnamen diff --git a/i18n/src/commonMain/moko-resources/nn/strings.xml b/i18n/src/commonMain/moko-resources/nn/strings.xml index 32ec8d2242..c669f7343e 100644 --- a/i18n/src/commonMain/moko-resources/nn/strings.xml +++ b/i18n/src/commonMain/moko-resources/nn/strings.xml @@ -205,7 +205,6 @@ Hjelp med å omsetje Personvernerklæring Sjå etter oppdateringar - Oppdatert til v%1$s Inkognitomodus Brukarnamn E-postadresse diff --git a/i18n/src/commonMain/moko-resources/pl/plurals.xml b/i18n/src/commonMain/moko-resources/pl/plurals.xml index 920ab950c6..8ff20f65ae 100644 --- a/i18n/src/commonMain/moko-resources/pl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pl/plurals.xml @@ -114,4 +114,10 @@ %1$s stron %1$s stron + + %1$d aktualizacja się nie powiodła + %1$d aktualizacje się nie powiodły + %1$d aktualizacji się nie powiodły + %1$d aktualizacji się nie powiodły + diff --git a/i18n/src/commonMain/moko-resources/pl/strings.xml b/i18n/src/commonMain/moko-resources/pl/strings.xml index c7307186ef..e2f1f04deb 100644 --- a/i18n/src/commonMain/moko-resources/pl/strings.xml +++ b/i18n/src/commonMain/moko-resources/pl/strings.xml @@ -397,7 +397,6 @@ Nieznany status Nieznany autor Szukaj \"%1$s\" globalnie - Zaktualizowano do v%1$s Co nowego Tryb czytania Motyw @@ -568,7 +567,6 @@ Pominięto, ponieważ brak przeczytanych rozdziałów Pominięto, ponieważ seria została ukończona Pominięto, ponieważ istnieją nieprzeczytane rozdziały - %1$d aktualizacja (aktualizacje) nie powiodła się Zamknij Przenieś serię na górę Ilość nieprzeczytanych @@ -843,4 +841,8 @@ Nie udało się utworzyć folderu pobierania Nie udało się utworzyć folderu: %s Eksportuj + Auto + Nieprzeczytane rozdziały + Catppucin + Zachowanie diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml index dae2e4e4c7..b9b311fd63 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -110,4 +110,9 @@ %1$s páginas + + %1$d atualização falhou + %1$d atualizações falharam + %1$d atualizações falharam + diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 6cb2271ee0..573bda8f92 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -116,10 +116,10 @@ Automática Retrato bloqueado Paisagem bloqueado - R - G - B - A + Vermelho + Verde + Azul + Alfa Após marcado como lido manualmente Excluir automaticamente após a leitura Desativado @@ -393,7 +393,6 @@ Inverter as zonas de toque Estado desconhecido Autor desconhecido - Atualizado para v%1$s O que há de novo Não foi possível fazer o download dos capítulos devido ao pouco espaço de armazenamento Data de inclusão @@ -564,7 +563,6 @@ Pulado porque a série está finalizada Pulado porque há capítulos não lidos Pulado porque nenhum capítulo foi lido - %1$d atualização(ões) falhou(aram) Toque para saber mais Retrato invertido Mover série para o começo @@ -578,7 +576,7 @@ Nenhuma fonte instalada foi encontrada Nenhuma fonte encontrada Contagem de não lidos - Ultima checagem de atualização + Última checagem de atualização Melhora o desempenho do leitor Página %d não encontrada durante a divisão Não foi possível encontrar o caminho do arquivo da página %d @@ -649,7 +647,7 @@ Remover data? Isto irá remover sua data de início selecionada anteriormente no %s Isto irá remover sua data de término selecionada anteriormente no %s - Invalidar o índice de downloads + Reindexar downloads Força o aplicativo a checar novamente por capítulos disponíveis offline Local Disponíveis offline @@ -687,7 +685,7 @@ Rotacionar páginas largas para caber Inverter a orientação das páginas largas rotacionadas Informações de depuração - Capitulo ao deslizar para a direita + Capítulo ao deslizar para a direita Ação de deslizar para a esquerda Toque duplo para dar zoom Próxima atualização esperada @@ -901,9 +899,12 @@ Editar anotações Anotações Catppuccin - Doar Fechar aba Renderizar imagens em descrições de mangá Não permitir nomes de arquivos não ASCII Garante a compatibilidade com determinadas mídias de armazenamento que não suportam Unicode. Quando esta opção estiver ativada, você precisará renomear manualmente as pastas de origem e de mangá, substituindo os caracteres não ASCII por suas representações hexadecimais UTF-8 em minúsculas. Os arquivos de capítulos não precisam ser renomeados. + Favoritos + Download simultâneo de páginas + Páginas baixadas simultâneamente por fonte + Filtrar grupo de traduções excluidos diff --git a/i18n/src/commonMain/moko-resources/pt/strings.xml b/i18n/src/commonMain/moko-resources/pt/strings.xml index 50034e17d4..d180f232df 100644 --- a/i18n/src/commonMain/moko-resources/pt/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt/strings.xml @@ -391,7 +391,6 @@ Autor desconhecido Requer reinício da app para ter efeito Rede - Atualizado para v%1$s Novidades Não foi possível transferir capítulos devido a falta de espaço de armazenamento Procurar por \"%1$s\" globalmente @@ -568,7 +567,6 @@ Dados do WebView limpos Vertical invertido Nenhuma fonte instalada encontrada - %1$d actualização(ões) falhada(s) Toque para saber mais Imagens panorâmicas Ampliar imagem horizontal @@ -828,6 +826,5 @@ Artista Autor Equipa de tradução - Doar Capítulos não lidos diff --git a/i18n/src/commonMain/moko-resources/ro/strings.xml b/i18n/src/commonMain/moko-resources/ro/strings.xml index d85b623df9..c1f4d5d4be 100644 --- a/i18n/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n/src/commonMain/moko-resources/ro/strings.xml @@ -393,7 +393,6 @@ Inversați zonele de atingere Stare necunoscută Autor necunoscut - Actualizat la v%1$s Ce este nou Nu s-au putut descărca capitole din cauza spațiului de stocare redus Căutați „%1$s” la nivel global @@ -500,7 +499,6 @@ Format de capitol invalid Atenție : actualizarile mari în masă vor face sursele mai incete și/sau vor bloca %s. Apăsați pentru mai multe informatii. Ghidul de inceput - %1$d actualizari esuate Ignorata deoarece sunt capitole necitite Nu există intrări în bibliotecă pentru a face backupuri Ștergeți categoria @@ -833,7 +831,6 @@ Afectează performanța. Activați numai dacă reducerea pragului bitmap nu rezolvă problemele legate de imaginile goale Automatic Întrerupeți istoricul de lectură pentru extensie - Donează Capitole necitite Notițe Editează notițe diff --git a/i18n/src/commonMain/moko-resources/ru/plurals.xml b/i18n/src/commonMain/moko-resources/ru/plurals.xml index d9ff94cf45..95a9556551 100644 --- a/i18n/src/commonMain/moko-resources/ru/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ru/plurals.xml @@ -61,10 +61,10 @@ %d сервисов отслеживания - Отсутствует %d глава в источнике или была отфильтрована - Отсутствуют %d главы в источнике или были отфильтрованы - Отсутствуют %d глав в источнике или были отфильтрованы - Отсутствуют %d глав в источнике или были отфильтрованы + Была пропущена %d глава, поскольку она отсутствует в источнике или была отфильтрована + Были пропущены %d главы, поскольку они отсутствуют в источнике или были отфильтрованы + Было пропущено %d глав, поскольку они отсутствуют в источнике или были отфильтрованы + Было пропущено %d глав, поскольку они отсутствуют в источнике или были отфильтрованы Вчера @@ -132,4 +132,10 @@ Копировать %1$d серий? Копировать %1$d серий? + + %1$d ошибка обновления + %1$d ошибки обновлений + %1$d ошибок обновлений + %1$d ошибок обновлений + diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 1f2f3cc16e..268c6571e4 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -393,7 +393,6 @@ Зоны касания наоборот Неизвестный статус Неизвестный автор - Обновлено до %1$s Что нового Не удалось загрузить главы из-за малого объема хранилища Искать \"%1$s\" глобально @@ -565,7 +564,6 @@ Пропущено, т.к есть непрочитанные главы Пропущено, т.к серия не начата Нажать для подробностей - %1$d обновление(ий) не удалось Портретная наоборот Переместить серию в начало Отключено @@ -902,7 +900,6 @@ Отменить Скрывать индикаторы отсутствующих глав Отрисововать изображения в описаниях серий - Спонсорство Не позволять наименования файлов в виде non-ASCII Обеспечивает совместимость с некоторыми носителями информации, которые не поддерживают Unicode. Когда этот функция включена, вам нужно будет вручную переименовать папки серий и источников, заменив non-ASCII символы на шестнадцатеричные UTF-8 в нижнем регистре. Файлы глав можно не переименовать. Параллельные загрузки с источников @@ -911,4 +908,5 @@ Число одновременных загрузок страниц с источника Фильтровать исключенных переводчиков В закладках + Поддержите нас diff --git a/i18n/src/commonMain/moko-resources/sa/strings.xml b/i18n/src/commonMain/moko-resources/sa/strings.xml index df87bd6384..eecbd02f05 100644 --- a/i18n/src/commonMain/moko-resources/sa/strings.xml +++ b/i18n/src/commonMain/moko-resources/sa/strings.xml @@ -428,7 +428,6 @@ उपयुक्तम् - %1$s निवेशाः नाशिताः नवीकरणेभ्यः मार्गतु - सं%1$s प्रति नवीकृतम् ईपत्रसङ्केतः अन्तर्गतम् बहिर्गच्छतु @@ -558,7 +557,6 @@ अध्यायानाम् अवारोपणं न अशक्नोत्। त्वया अवारोपितानि नाम्नः विभागे पुनः यतितुं शक्यते यस्मात् स्थानान्तरगमनं करणीयं तत् चिनोतु पूर्वसूचना -- मूलेभ्यः महद्नवीकरणानि हानिकारकाणि भवन्ति तस्मात् नवीकरणानि मन्दानि भवेयुः अपि च विद्युत्कोषोपयोगः अधिकं भवेत् - %1$d नवीकरणानि अनुत्तीर्णानि अधिकं जानातु धारावाहिकम् अग्रस्थानं चालयतु विपरीतं लम्बदर्शनम् diff --git a/i18n/src/commonMain/moko-resources/sah/strings.xml b/i18n/src/commonMain/moko-resources/sah/strings.xml index b6135d89d4..025e598ab2 100644 --- a/i18n/src/commonMain/moko-resources/sah/strings.xml +++ b/i18n/src/commonMain/moko-resources/sah/strings.xml @@ -269,7 +269,6 @@ Биэрсия Үөп сир Аһаҕас төрүттээх лицензиялар - v%1$s дылы саҥарданна Саҥардыы баар буолуутун бэрэбиэркэлээһин Толору көрдөөһүн… Иҥиннэриллибит diff --git a/i18n/src/commonMain/moko-resources/sc/strings.xml b/i18n/src/commonMain/moko-resources/sc/strings.xml index f16505b2e7..f2b1a5609a 100644 --- a/i18n/src/commonMain/moko-resources/sc/strings.xml +++ b/i18n/src/commonMain/moko-resources/sc/strings.xml @@ -391,7 +391,6 @@ Fùrria sas zonas de tocu Istadu disconnotu Autore disconnotu - Agiornadu a v%1$s Ite b\'at de nou Iscarrigamentu de sos capìtulos fallidu pro neghe de su pagu ispàtziu lìberu in su discu Chirca \"%1$s\" globalmente @@ -563,7 +562,6 @@ Brincadu ca bi sunt capìtulos non lèghidos Brincadu ca non bi sunt capìtulos lèghidos Toca pro àteras informatziones - %1$d agiornamentu(os) fallidu(os) Verticale a s\'imbesse Move sa sèrie cara a pitzos Disabilitadu diff --git a/i18n/src/commonMain/moko-resources/sk/strings.xml b/i18n/src/commonMain/moko-resources/sk/strings.xml index 72384b6b5e..9f00838818 100644 --- a/i18n/src/commonMain/moko-resources/sk/strings.xml +++ b/i18n/src/commonMain/moko-resources/sk/strings.xml @@ -535,7 +535,6 @@ Horizontálne Zakázané Vylúčené kategórie - Aktualizované na v%1$s Iba stiahnuté Zoznam prianí Chystáte sa odstrániť \"%s\" zo svojej knižnice @@ -603,7 +602,6 @@ Viac Upozornenie: Veľké hromadné sťahovanie môže viesť k spomaleniu zdrojov a/alebo blokovaniu %s. Klepnutím sa dozviete viac. Veľké aktualizácie poškodzujú zdroje a môžu viesť k pomalším aktualizáciám a tiež k zvýšeniu spotreby batérie. Klepnutím sa dozviete viac. - Zlyhané aktualizácie: %1$d Zálohovanie zlyhalo Sledovať Webová stránka @@ -663,7 +661,6 @@ Nadchádzajúce Nahradiť Navigovať hore - Prispieť finančne Auto Vlastná frekvencia aktualizácie Skóre na sledovači diff --git a/i18n/src/commonMain/moko-resources/sq/strings.xml b/i18n/src/commonMain/moko-resources/sq/strings.xml index 3f8ab926d8..06dd02f223 100644 --- a/i18n/src/commonMain/moko-resources/sq/strings.xml +++ b/i18n/src/commonMain/moko-resources/sq/strings.xml @@ -420,7 +420,6 @@ Mënyra e leximit Tjetra: Pastro historinë - %1$d përditësim(e) dështoi U anashkalua sepse seria ka përfunduar Zgjidhni imazhin e kopertinës Hapni në GitHub @@ -524,7 +523,6 @@ Optimizimi i baterisë është tashmë i çaktivizuar Cilësimet e pajisjes nuk mund të hapeshin Kontrolloni për përditësime - Përditësuar në v%1$s Dërgoni raporte përplasjesh Ndihmon në rregullimin e çdo defekti. Nuk do të dërgohen të dhëna sensitive Ndërtimet e F-Droid nuk mbështeten zyrtarisht. diff --git a/i18n/src/commonMain/moko-resources/sr/strings.xml b/i18n/src/commonMain/moko-resources/sr/strings.xml index 783dee64db..ccc31fa0be 100644 --- a/i18n/src/commonMain/moko-resources/sr/strings.xml +++ b/i18n/src/commonMain/moko-resources/sr/strings.xml @@ -481,7 +481,6 @@ Датум завршетка Аутоматски Лиценце отвореног кода - Ажурирано на v%1$s Ништа није пронађено Раздели широке странице Ништа @@ -561,7 +560,6 @@ Прескочи уносе који нису започети Прикажи наслов Помакни наслов на врх - %1$d ажурирање није успело Додирните да сазнате више За помоћ о томе како да поправите грешке у ажурирању колекције, погледајте %1$s Прескочено јер ниједно поглавље није прочитано diff --git a/i18n/src/commonMain/moko-resources/sv/strings.xml b/i18n/src/commonMain/moko-resources/sv/strings.xml index 3922722e8e..14089b0fd3 100644 --- a/i18n/src/commonMain/moko-resources/sv/strings.xml +++ b/i18n/src/commonMain/moko-resources/sv/strings.xml @@ -395,7 +395,6 @@ Inaktivera Det gick inte att ladda ner kapitelt på grund av lågt lagringsutrymme Sök efter \"%1$s\" globalt - Uppdaterad till v%1$s Vad är nytt Läsläge Tema @@ -565,7 +564,6 @@ Hoppade över eftersom det finns olästa kapitel Hoppade över eftersom inga kapitel läses Tryck på för att få veta mer - %1$d uppdatering(ar) misslyckades Omvänt porträtt Flytta serien till toppen Inaktiverad diff --git a/i18n/src/commonMain/moko-resources/ta/strings.xml b/i18n/src/commonMain/moko-resources/ta/strings.xml index 1542ca832d..342cdb5009 100644 --- a/i18n/src/commonMain/moko-resources/ta/strings.xml +++ b/i18n/src/commonMain/moko-resources/ta/strings.xml @@ -575,7 +575,6 @@ கணினி பதிவுக்கு வாய்மொழி பதிவுகளை அச்சிடுக (பயன்பாட்டு செயல்திறனைக் குறைக்கிறது) தனியுரிமைக் கொள்கை புதுப்பிப்புகளை சரிபார்க்கவும் - V%1$s க்கு புதுப்பிக்கப்பட்டது செயலிழப்பு அறிக்கைகளை அனுப்பவும் எந்த பிழைகளையும் சரிசெய்ய உதவுகிறது. முக்கியமான தரவு எதுவும் அனுப்பப்படாது எஃப்-டிராய்டு கட்டடங்கள் அதிகாரப்பூர்வமாக ஆதரிக்கப்படவில்லை.\n மேலும் அறிய தட்டவும். @@ -766,7 +765,6 @@ அத்தியாயம் %1$s அத்தியாயம் %1$s மற்றும் %2$d மேலும் அத்தியாயங்கள் %1$s - %1$d புதுப்பிப்பு (கள்) தோல்வியடைந்தது மேலும் அறிய தட்டவும் அட்டையைப் புதுப்பிக்கத் தவறிவிட்டது இதைச் செய்வதற்கு முன் உங்கள் நூலகத்தில் உள்ளீட்டைச் சேர்க்கவும் @@ -892,7 +890,6 @@ நூலாசிரியர் கோப்பகத்தை உருவாக்குவதில் தோல்வி: %s பதிவிறக்க கோப்பகத்தை உருவாக்குவதில் தோல்வி - நன்கொடை தாவலை மூடு மங்கா விளக்கங்களில் படங்களை வழங்கவும் ASCII அல்லாத கோப்பு பெயர்களை அனுமதிக்க வேண்டாம் diff --git a/i18n/src/commonMain/moko-resources/th/strings.xml b/i18n/src/commonMain/moko-resources/th/strings.xml index 98f166ff3c..7c845aea2c 100644 --- a/i18n/src/commonMain/moko-resources/th/strings.xml +++ b/i18n/src/commonMain/moko-resources/th/strings.xml @@ -431,7 +431,6 @@ หยุดประวัติการอ่านชั่วคราว โหมดไม่ระบุตัวตน ดาวน์โหลดแล้วเท่านั้น - อัปเดตเป็น v%1$s แล้ว ตรวจสอบการอัปเดต ใบอนุญาตโอเพ่นซอร์ส มีอะไรใหม่ @@ -565,7 +564,6 @@ ข้ามรายการที่ยังไม่ได้เริ่มอ่าน ถูกข้ามเนื่องจากเรื่องนี้จบแล้ว แตะเพื่อเรียนรู้เพิ่มเติม - อัปเดตผิดพลาด %1$d รายการ แนวตั้งแบบกลับด้าน ย้ายเรื่องไปด้านบน ปิดใช้งาน @@ -848,7 +846,6 @@ รายการคลัง ส่งออกคลังแล้ว คุณกำลังจะลบรายการออกจากฐานข้อมูล - บริจาค เรนเดอร์รูปภาพในคำอธิบายเรื่อง ไม่อนุญาตชื่อไฟล์ที่ไม่ใช่ ASCII diff --git a/i18n/src/commonMain/moko-resources/tr/plurals.xml b/i18n/src/commonMain/moko-resources/tr/plurals.xml index 4dc2a29a41..60971a185b 100644 --- a/i18n/src/commonMain/moko-resources/tr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/tr/plurals.xml @@ -2,7 +2,7 @@ %1$s dakika sonra - %1$s dakika sonrası + %1$s dakika sonra %1$d yeni bölüm @@ -41,8 +41,8 @@ %d izleyici - %d bölüm atlandı, kaynak mevcut değil veya filtrelendi - %d bölüm atlandı, kaynak mevcut değil veya filtrelendi + %d bölüm atlandı, kaynak mevcut değil ya da filtrelendi + %d bölüm atlandı, kaynak mevcut değil ya da filtrelendi Dün @@ -70,7 +70,7 @@ Yarın - %1$d günleri içinde + %1$d gün içinde 1 sayfa @@ -88,4 +88,8 @@ Bir girdi atlandı %1$d girdi atlandı + + %1$d güncelleme başarısız + %1$d güncelleme başarısız + diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 9af7066614..227e65f707 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -393,7 +393,6 @@ Yatay Bilinmeyen durum Bilinmeyen yazar - v%1$s sürümüne güncellendi Yenilikler Depolama alanı az olduğu için bölümler indirilemedi Eklendiği tarih @@ -564,7 +563,6 @@ Hiçbir bölüm okunmadığı için atlandı Geniş görselleri kaydır Geniş görsellere otomatik yakınlaştır - %1$d güncelleme başarısız oldu Daha fazla bilgi için dokunun Ters portre Seriyi en üste taşı @@ -902,7 +900,6 @@ Durdur Eksik bölüm göstergelerini gizle Manga açıklamalarında bulunan görselleri göster - Bağış ASCII olmayan dosya isimlerine izin verme Unicode desteklemeyen bazı depolama sistemleri için uyumluluk sağlar. Etkinleştirildiğinde kaynak ve manga klasörlerini manuel olarak yeniden adlandırıp ASCII olmayan karakterleri küçük UTF-8 heksadesimal karşılıklarıyla değiştirmeniz gerekir. Bölüm dosyalarının yeniden adlandırılması gerekli değildir. Tek seferde indirilen sayfa sayısı diff --git a/i18n/src/commonMain/moko-resources/uk/strings.xml b/i18n/src/commonMain/moko-resources/uk/strings.xml index a0e392c346..9dd46af28b 100644 --- a/i18n/src/commonMain/moko-resources/uk/strings.xml +++ b/i18n/src/commonMain/moko-resources/uk/strings.xml @@ -393,7 +393,6 @@ Невідомий автор Потрібен перезапуск застосунку, щоб зміни вступили в дію Мережа - Оновлено до v%1$s Що новенького Не вдалось завантажити розділи через нестачу місця в сховищі Шукати \"%1$s\" глобально @@ -561,7 +560,6 @@ Пропустити непочаті глави Перемістити серію на початок Портретна навпаки - %1$d оновлення(нь) не вдалось Пропущено, оскільки серія завершена Пропущено через те, що є непрочитані розділи Пропущено, оскільки серія не почата diff --git a/i18n/src/commonMain/moko-resources/vi/strings.xml b/i18n/src/commonMain/moko-resources/vi/strings.xml index eef64e56f6..93ea0a1c7d 100644 --- a/i18n/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n/src/commonMain/moko-resources/vi/strings.xml @@ -418,7 +418,6 @@ Mục Lọc tất cả truyện trong thư viện Tắt chế độ ẩn danh - Được cập nhật đến phiên bản v%1$s Kiểm tra cập nhật Mở giấy phép nguồn mở Có gì mới @@ -563,7 +562,6 @@ Xoay ảnh rộng Lưới chỉ mỗi bìa Đảo ngược chân dung - %1$d cập nhật thất bại Đã bỏ qua vì bộ truyện đã hoàn thành Đã bỏ qua vì vẫn còn chương chưa đọc Đã bỏ qua vì chưa chương được đọc @@ -901,7 +899,6 @@ Hủy bỏ Hủy bỏ Không tìm thấy chương nào, mục này không thể được sử dụng để di chuyển - Quyên góp Hiển thị hình ảnh trong mô tả manga Không cho phép tên tệp không phải định dạng ASCII Đảm bảo khả năng tương thích với một số phương tiện lưu trữ nhất định không hỗ trợ Unicode. Khi tính năng này được bật, bạn sẽ cần đổi tên thủ công các thư mục nguồn và manga bằng cách thay thế các ký tự không phải ASCII bằng cách biểu diễn thập lục phân UTF-8 chữ thường của chúng. Các tập tin chương không cần phải đổi tên. diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml index d436da2789..52a68127e2 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml @@ -66,4 +66,7 @@ %1$d 个作品已跳过 + + %1$d 更新失败 + diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 46a2e7c593..45d110de47 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -392,7 +392,6 @@ 未知状态 未知作者 - 已更新至 v%1$s 更新日志 存储空间不足,无法下载章节 全局搜索 \"%1$s\" @@ -565,7 +564,6 @@ 已跳过,因为有未读章节 已跳过,因为尚未开始阅读 点击了解详情 - %1$d 个更新失败了 反向竖屏 将作品移到顶部 关闭 @@ -902,7 +900,6 @@ 迁移 隐藏缺失章节提示符 在漫画注释中显示图片 - 捐赠 禁用非ASCII文件名 确保与某些不支持 Unicode 的存储介质相兼容。启用此功能后,你需要手动重命名图源和漫画文件夹,将非 ASCII 字符改为对应的 UTF-8 小写十六进制表示形式。章节文件无需重命名。 同时下载的图源数量 @@ -911,4 +908,5 @@ 关闭标签 筛选排除翻译组 已添加书签 + 支持我们 diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml index e27b864d35..4b63924663 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml @@ -66,4 +66,7 @@ 已跳過 %1$d 個項目 + + %1$d 更新失敗 + diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml index c7a51d9fd9..93c4ddb23f 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -389,7 +389,6 @@ 佚名 在所有範圍內搜尋「%1$s」 由於儲存空間不足,無法下載章節 - 已更新至 v%1$s 閱讀模式 主題 收藏日期 @@ -563,7 +562,6 @@ 由於無已讀章節,因此略過 顛倒直向 瞭解詳情 - %1$d 項更新失敗 置頂此作品 停用 書櫃中尚無作品可供備份 @@ -893,7 +891,6 @@ 遷移 找不到章節,此項目無法用於遷移 在漫畫描述中顯示圖片 - 捐贈 禁止使用非 ASCII 檔名 確保與不支援 Unicode 的某些儲存媒體相容,啟用此選項後,你需要手動將來源與漫畫資料夾中的非 ASCII 字元改成對應的小寫 UTF-8 十六進位表示方式,章節檔案則不需要重新命名。 同時下載的來源數目 @@ -902,4 +899,5 @@ 關閉頁籤 篩選排除掃譯者 已新增書籤 + 支持我們 From 129ed521f58d91fd57efcc4c74af0a9fc5ef9e93 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 10 Apr 2026 02:06:37 +0100 Subject: [PATCH 30/59] Update aboutLibraries to v14.0.1 (#3217) (cherry picked from commit 2990762df97b2f5930fc2b81fe44cafd88195455) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cebd50065a..b4bcf36d15 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutLibraries = "14.0.0" +aboutLibraries = "14.0.1" android-desugar = "2.1.5" android-gradle = "8.13.2" androidx-activity-compose = "1.13.0" From fd8114462a02ead1ee4e28f80e9d76da95ef2235 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 10 Apr 2026 02:07:41 +0100 Subject: [PATCH 31/59] Update dependency com.google.firebase.crashlytics to v3.0.7 (#3219) (cherry picked from commit b1c69949774db5f88a6a016324c1961671e907ea) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4bcf36d15..211562d004 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ conscrypt = "2.5.3" directionalViewPager = "1.0.0" diskLruCache = "2.0.2" firebase-bom = "34.11.0" -firebase-crashlytics = "3.0.6" +firebase-crashlytics = "3.0.7" flexibleAdapter = "c8013533" google-services = "4.4.4" image-decoder = "41c059e540" From 5cdb2eac58db2d021bb6758cb1627e2239b2b0ce Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 10 Apr 2026 02:18:21 +0100 Subject: [PATCH 32/59] Update dependency com.google.firebase:firebase-bom to v34.12.0 (#3216) Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit eb3d2c66763b9e657adb07acdbfcace37dc2ce8e) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 211562d004..3082988d23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ composeWebview = "0.33.6" conscrypt = "2.5.3" directionalViewPager = "1.0.0" diskLruCache = "2.0.2" -firebase-bom = "34.11.0" +firebase-bom = "34.12.0" firebase-crashlytics = "3.0.7" flexibleAdapter = "c8013533" google-services = "4.4.4" From df49339aec078baeeb7c209c5d1b1d0b1681fa31 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 11 Apr 2026 07:50:41 +0200 Subject: [PATCH 33/59] Translations update from Hosted Weblate (#3222) Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eu/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals Co-authored-by: Bartolumiu Co-authored-by: Dexroneum Co-authored-by: Frosted Co-authored-by: Infy's Tagalog Translations Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com> Co-authored-by: ZerOriSama Co-authored-by: ndeerova (cherry picked from commit 253a560b20762807d99253cfd5fcc421b25a27a8) --- .../commonMain/moko-resources/de/strings.xml | 13 ++++++++++ .../commonMain/moko-resources/es/strings.xml | 13 ++++++++++ .../commonMain/moko-resources/eu/plurals.xml | 20 +++++++++++++++ .../commonMain/moko-resources/eu/strings.xml | 25 +++++++++++++++---- .../commonMain/moko-resources/fil/strings.xml | 12 +++++++++ .../commonMain/moko-resources/ru/strings.xml | 12 +++++++++ .../commonMain/moko-resources/tr/strings.xml | 13 ++++++++++ .../moko-resources/zh-rCN/strings.xml | 12 +++++++++ 8 files changed, 115 insertions(+), 5 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index d2d85394e3..aa1d309fd2 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -908,4 +908,17 @@ Tab schließen Ausgeschlossene Scanlationgruppen herausfiltern Mit Lesezeichen versehen + Unterstütze Uns + Unterstützende erhalten Zugang zu exklusiven Kanälen. Weitere Vorteile folgen. + Patreon (bevorzugt) + OpenCollective + Mihon wird aktuell von %d+ Personen unterstützt. Wir bedanken uns bei euch allen. Lasst uns Mihon gemeinsam noch besser machen. + Trete unserem Discord-Server für alternative Optionen oder bei weiteren Fragen bei. + Discord + Gefällt dir die App? + Hi! Ich bin der Hauptentwickler von Mihon und möchte darum bitten, die Entwicklung finanziell zu unterstützen. Mit genügend Unterstützung werde ich in der Lage sein, in Vollzeit an Mihon zu arbeiten und sowohl langjährige Probleme zu beheben als auch häufig gewünschte Features zu implementieren. + Du kannst entweder mittels Patreon (bevorzugt) oder OpenCollective spenden. Wenn du Fragen hast oder auf anderem Wege die Entwicklung unterstützen möchstest, dann kannst du mich gerne über Mihons Discord-Server erreichen. + Unabhängig davon wird Mihon weiterhin von mir und anderen Freiwilligen entwickelt werden und bleibt Open-Source-Software. + Discord + Verwerfen diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 2e08c726b5..ce3c986190 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -901,4 +901,17 @@ Cerrar pestaña Ocultar equipos de traducción excluidos Favoritos + Apóyanos + Los patrocinadores obtienen acceso a canales exclusivos, con más beneficios por venir. + Patreon (preferido) + OpenCollective + Mihon cuenta actualmente con el apoyo de %d+ patrocinadores. Queremos agradecerles a cada uno de ustedes. Hagamos Mihon aún mejor juntos. + Para opciones alternativas o cualquier pregunta relacionada, únete a nuestro servidor de Discord. + Discord + ¿Disfrutando la aplicación? + ¡Hola! Soy el desarrollador principal de Mihon, y me gustaría pedirte que consideres apoyar su desarrollo económicamente. Con suficiente apoyo, podré trabajar en Mihon a tiempo completo y centrarme en problemas antiguos, así como en funciones muy solicitadas. + Puedes donar a través de Patreon (preferido) o OpenCollective. Si tienes preguntas o prefieres apoyar usando otros métodos, no dudes en contactarme a través del servidor de Discord de Mihon. + De todas formas, Mihon seguirá siendo mantenido por mí y los voluntarios, y se mantendrá como código abierto. + Descartar + Discord diff --git a/i18n/src/commonMain/moko-resources/eu/plurals.xml b/i18n/src/commonMain/moko-resources/eu/plurals.xml index 55139ec509..2e4dd708e6 100644 --- a/i18n/src/commonMain/moko-resources/eu/plurals.xml +++ b/i18n/src/commonMain/moko-resources/eu/plurals.xml @@ -48,4 +48,24 @@ %1$s kapituluak eta bat gehiago %1$s kapituluak eta %2$d gehiago + + Bihar + %1$d egunetan + + + Irakurri gabeko hurrengo kapitulua + Irakurri gabeko hurrengo %d kapituluak + + + Egun 1 + %d egun + + + Orri 1 + %1$s orri + + + Hurrengo kapitulua + Hurrengo %d kapituluak + diff --git a/i18n/src/commonMain/moko-resources/eu/strings.xml b/i18n/src/commonMain/moko-resources/eu/strings.xml index 5e485f3d92..53a1b246cc 100644 --- a/i18n/src/commonMain/moko-resources/eu/strings.xml +++ b/i18n/src/commonMain/moko-resources/eu/strings.xml @@ -222,11 +222,7 @@ Ez da aurkitu fitxategiak hautatzeko aplikaziorik Denak Fabrikatzaile batzuek aplikazioen murrizketa gehigarriak dituzte, atzeko planoko zerbitzuak akabatzen dituztenak. Webgune honek informazio gehiago du nola konpondu jakiteko. - Luzapen hau fidagarria ez den ziurtagiri batekin sinatu zen eta ez zen aktibatu. -\n -\nLuzapen maltzur batek Mihon-n gordetako saio-hasierako kredentzialak irakur ditzake edo kode arbitrarioa exekutatu. -\n -\nZiurtagiri honetaz fidatzean, arrisku hauek onartzen dituzu. + Luzapen maltzurrek gordetako edozein saio kredentzialak irakur ditzake edota kode arbitrarioa exekutatu.\n\nLuzapen honetaz fidatzen bazara, arrisku horiek onartzen dituzu. Fidatu gabekoa Atzeko planoaren kolorea Eguneratu @@ -600,4 +596,23 @@ Ezarri denbora-bitartea Datu eta memoria Azkenengo egunearatze egiaztapena + Inoiz ez + Ausaz + Artista + Aplikazioa gustoko duzu? + Kaixo! Mihonen atzean dagoen garatzaile nagusia naiz, eta haren garapena ekonomikoki babestea aztertzeko eskatu nahi nizuke. Laguntza nahikoarekin, Mihonen denbora osoz lan egin ahal izango dut eta arazo zaharretan zein oso eskatutako funtzioetan zentratu ahal izango naiz. + Patreon (hobetsia) edo OpenCollective bitartez dirua eman dezakezu. Galderarik baduzu edo beste metodo batzuk erabiliz lagundu nahi baduzu, jar zaitez nirekin harremanetan Mihonen Discord zerbitzariaren bitartez. + Nolanahi ere, Mihon boluntarioek eta nik mantentzen jarraituko dugu, eta kode irekikoa izaten jarraituko du. + Discord + Baztertu + Mezenasek kanal esklusiboetarako sarbidea lortzen dute, abantaila gehiagorekin etorkizunean. + Patreon (hobetsia) + OpenCollective + Gaur egun %d+ mezenasek babesten dute Mihon. Eskerrak eman nahi dizkizuegu guztioi. Mihon are hobea egin dezagun elkarrekin. + Aukera alternatiboak edo lotutako edozein zalantza argitzeko, gure Discord zerbitzarian sartu. + Discord + Lagun iezaguzu + Etorkizunean + %s desblokeatu + Esteka kopiatu diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index d8b8095435..5d07554a95 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -909,4 +909,16 @@ I-filter ang na-ibukod na mga scanlator Na-bookmark Suportahan Kami + Ang mga patron ay may access sa exclusive channels, at marami pang perks ang darating. + Patreon (mas mainam) + OpenCollective + Ang Mihon ay kasalukuyang sinusuportahan ng %d+ na patron. Nais naming pasalamatan ang bawat isa sa inyo. Sama-sama nating pagandahin pa lalo ang Mihon. + Para sa iba pang alternatibong opsyon o anumang mga katanungan, sumali sa aming Discord server. + Discord + Nasisiyahan ka ba sa paggamit ng app? + Kumusta! Ako ang main developer ng Mihon. Nais ko sanang humiling ng inyong suportang pinansyal para sa development nito. Sa sapat na suporta, magagawa kong pagtuunan ng panahon ang Mihon full-time at mabigyang-pansin ang mga dati pang isyu pati na ang mga feature na madalas ninyong hinihiling. + Maaari kang mag-donate sa pamamagitan ng Patreon (mas mainam) o OpenCollective. Kung mayroon kang mga katanungan o nais tumulong gamit ang ibang paraan, huwag mag-atubiling makipag-ugnayan sa akin sa Discord server ng Mihon. + Anuman ang mangyari, patuloy na itataguyod ang Mihon ko at ng mga volunteer, at mananatili itong open source. + Discord + Alisin diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 268c6571e4..cd8aaffac2 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -909,4 +909,16 @@ Фильтровать исключенных переводчиков В закладках Поддержите нас + Patreon (Предпочтительно) + OpenCollective + Mihon сейчас поддерживается %d+ Patreon-спонсорами. Мы хотим поблагодарить каждого из вас. Давайте сделаем Mihon лучше. + Discord + Patreon-спонсоры получать доступ к эксклюзивным каналам с будущими привилегиями и возможностями. + Discord + Нравится приложение? + Приветствую! Я главный разработчик Mihon и хотел бы попросить вас об финансовой поддержке проекта. При достаточной поддержке я смогу работать над Mihon в полную силу и сосредоточиться как на давно существующих проблемах, так и на функциях, которые пользователи уже давно просят добавить. + Вы можете поддержать проект через Patreon (Предпочтительно) или OpenCollective. Если у вас есть вопросы или хотите поддержать, используя другие способы, вы можете связаться со мной через Discord-сервер нашего приложения. + Независимо от поддержки, проект останется с открытым исходным кодом, и я с волонтёрами будем и дальше поддерживать это приложение. + Отклонить + Если у вас есть другие способы поддержки или вопросы по этому поводу, можете присоединиться к нашему Discord-серверу. diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 227e65f707..e30880326c 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -908,4 +908,17 @@ Sekmeyi kapat Hariç tutulan Çevirenleri filtrele Yer imli + Bizi Destekleyin + Patronlar, özel kanallara ve gelecekte daha fazlasına erişim elde eder. + Patreon (tercih edilen) + OpenCollective + Mihon, şu anda %d+ patron tarafından desteklenmektedir. Her birinize ayrı olarak teşekkür etmek istiyoruz. Hadi Mihon\'u birlikte daha iyi yapalım. + Başka seçenekler ve sorularınız için Discord sunucumuza katılın. + Discord + Uygulamayı beğendiniz mi? + Merhaba! Ben, Mihon\'un ana geliştiricisiyim ve geliştirme sürecine maddi olarak destek olmayı düşünmenizi isterim. Yeterli destek olursa Mihon üzerinde tam zamanlı olarak çalışabilirim ve uzun zamandır olan sorunlara ve çok istenen özelliklere odaklanabilirim. + Patreon (tercih edilen) veya OpenCollective üzerinden bağışta bulunabilirsiniz. Sorularınız varsa veya başka bir yolla destek olmak isterseniz Mihon\'un Discord sunucusu üzerinden bana ulaşmaktan çekinmeyin. + Ancak ne olursa olsun Mihon, ben ve gönüllü insanlar tarafından geliştirilmeye devam edecek ve açık kaynak kodlu olarak kalacaktır. + Discord + Kapat diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 45d110de47..b50b4441ee 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -909,4 +909,16 @@ 筛选排除翻译组 已添加书签 支持我们 + 赞助者可访问专属频道,更多福利即将推出。 + Patreon(首选) + OpenCollective + 目前 Mihon 已获得 %d+ 位赞助者的支持。我们衷心感谢大家。让我们携手把 Mihon 办得更出色。 + 如需替代方案或有任何相关问题,请加入我们的 Discord 服务器。 + Discord + 喜欢本应用吗? + 你好!我是 Mihon 的首席开发者,恳请你考虑为 Mihon 的开发工作给予资金方面的支持。有了足够的资金支持,我就能全职投入 Mihon 的开发,专注于解决长期存在的问题以及用户呼声很高的功能。 + 您可以通过 Patreon(首选)或 OpenCollective 进行捐赠。如果您有任何疑问,或者希望使用其他方式提供支持,欢迎通过 Mihon 的 Discord 服务器联系我。 + 无论如何,Mihon 仍将由我和志愿者们继续维护,并且会保持开源。 + Discord + 放弃 From 496ecea8ac6d8aaa88f0773e47d855279a9b249a Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 20 Apr 2026 12:49:30 +0100 Subject: [PATCH 34/59] Update dependency org.jsoup:jsoup to v1.22.2 (#3245) (cherry picked from commit 34fa9138d9b92b2f065a242a79ccb67f70a17c7b) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3082988d23..ba765ac375 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ flexibleAdapter = "c8013533" google-services = "4.4.4" image-decoder = "41c059e540" injekt = "91edab2317" -jsoup = "1.22.1" +jsoup = "1.22.2" junit = "6.0.3" kotest-assertions = "6.1.11" kotlin-gradle = "2.3.20" From 192b521f05a719cc6101857e795723e88d0bc357 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 20 Apr 2026 12:55:23 +0100 Subject: [PATCH 35/59] Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.2 (#3232) (cherry picked from commit e676c8e6e15181e0eb8ddbd8dc4a9ca8e438375e) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba765ac375..d840c149f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,7 +67,7 @@ sqldelight-androidx-driver = "0.0.17" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" -tapmoc = "0.4.1" +tapmoc = "0.4.2" unifile = "e0def6b3dc" voyager = "1.1.0-beta03" xmlutil = "0.91.3" From e66fc14a33a3d5a77182e42305aecf81e28a9413 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 20 Apr 2026 12:55:44 +0100 Subject: [PATCH 36/59] Update softprops/action-gh-release action to v2.6.2 (#3230) (cherry picked from commit d68347f5fbc897c78ff255c1c343f90c161ca6f6) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 844d2effd7..a009f885c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,7 +110,7 @@ jobs: Animiru - name: Create GitHub Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 with: tag_name: ${{ needs.get_tag.outputs.tag }} name: Animiru ${{ needs.get_tag.outputs.tag }} From 77f4bfc7016ca3fb3e59cc8268d0b5df26e3de93 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 23 Apr 2026 19:53:40 +0100 Subject: [PATCH 37/59] Update kotlin monorepo to v2.3.21 (#3258) (cherry picked from commit cb6daa5e4ca4830f9818ad296a6413f089f81559) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d840c149f7..0e8340b509 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,7 @@ injekt = "91edab2317" jsoup = "1.22.2" junit = "6.0.3" kotest-assertions = "6.1.11" -kotlin-gradle = "2.3.20" +kotlin-gradle = "2.3.21" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines = "1.10.2" kotlinx-serialization = "1.11.0" From 28a017be9f0c25655ded0bca27e2f0e4ef8e54dc Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 23 Apr 2026 19:54:21 +0100 Subject: [PATCH 38/59] Update dependency sh.calvin.reorderable:reorderable to v3.1.0 (#3251) (cherry picked from commit 5b32a990a2380da119713f07169b1b7296a0ccb0) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e8340b509..b3dc4726ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,7 +58,7 @@ okhttp = "5.3.2" okio = "3.17.0" photoView = "2.3.0" quickJs = "547f5b1597" -reorderable = "3.0.0" +reorderable = "3.1.0" rxJava = "1.3.8" shizuku = "13.1.5" spotless = "8.4.0" From bf18c74ffa162ab6dfdd16fbc43e541f5a956167 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 24 Apr 2026 14:38:45 +0100 Subject: [PATCH 39/59] Update dependency androidx.compose:compose-bom-beta to v2026.04.01 (#3256) (cherry picked from commit 4faf89fc6fd9c5ab5786029ed1aebabeb0fd8e73) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3dc4726ff..7881b62d6a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" -androidx-compose-bom = "2026.04.00" +androidx-compose-bom = "2026.04.01" androidx-constraintLayout = "2.2.1" androidx-core = "1.18.0" androidx-coreSplashScreen = "1.2.0" From bde6d57e2a91546c5a5acf0a098acbcb376cb917 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sat, 25 Apr 2026 07:17:32 +0600 Subject: [PATCH 40/59] Update dependency image-decoder to e03b81e18a (#3262) (cherry picked from commit 38ccd7d5fc07cb7ddc353835ec57e88f43488301) --- gradle/libs.versions.toml | 4 ++-- gradle/mihon.versions.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7881b62d6a..bdaea52cff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ firebase-bom = "34.12.0" firebase-crashlytics = "3.0.7" flexibleAdapter = "c8013533" google-services = "4.4.4" -image-decoder = "41c059e540" +image-decoder = "e03b81e18a" injekt = "91edab2317" jsoup = "1.22.2" junit = "6.0.3" @@ -127,7 +127,7 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } flexibleAdapter = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter", version.ref = "flexibleAdapter" } -image-decoder = { module = "com.github.tachiyomiorg:image-decoder", version.ref = "image-decoder" } +image-decoder = { module = "com.github.mihonapp:image-decoder", version.ref = "image-decoder" } injekt = { module = "com.github.mihonapp:injekt", version.ref = "injekt" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } diff --git a/gradle/mihon.versions.toml b/gradle/mihon.versions.toml index ac68cd4e82..b7b4e396bd 100644 --- a/gradle/mihon.versions.toml +++ b/gradle/mihon.versions.toml @@ -1,7 +1,7 @@ [versions] # Used in [/gradle/build-config/src/main/kotlin/PluginKotlinMultiplatform.kt] android-sdk-min = "26" -android-sdk-compile = "36" +android-sdk-compile = "37" android-sdk-target = "36" android-ndk = "29.0.14206865" java = "17" From f5291b0ea42299d8bc3403efd1e34480191e49a5 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sun, 10 May 2026 22:25:12 +0200 Subject: [PATCH 41/59] Reapply "Update android.gradle to v9 (major)" Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .github/workflows/build.yml | 4 +- app/build.gradle.kts | 8 +--- build.gradle.kts | 7 +++ domain/build.gradle.kts | 1 + gradle.properties | 2 - .../src/main/kotlin/PluginAndroidBase.kt | 45 +++++++++++++++---- .../src/main/kotlin/PluginComposeAndroid.kt | 6 +++ .../main/kotlin/PluginKotlinMultiplatform.kt | 24 ++++++++-- .../mihon/gradle/configurations/Android.kt | 32 ------------- .../kotlin/mihon/gradle/extensions/Project.kt | 7 +-- gradle/libs.versions.toml | 8 ++-- i18n-animiru/build.gradle.kts | 4 +- i18n-aniyomi/build.gradle.kts | 4 +- i18n/build.gradle.kts | 4 +- source-api/build.gradle.kts | 12 +++-- source-local/build.gradle.kts | 4 +- 16 files changed, 102 insertions(+), 70 deletions(-) delete mode 100644 gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e15dc686c..18884b4184 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: - name: Run unit tests id: unit_tests - run: ./gradlew testReleaseUnitTest + run: ./gradlew testDebugUnitTest - name: Verify SQLDelight migrations run: ./gradlew verifySqlDelightMigration @@ -64,7 +64,7 @@ jobs: uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-report-${{ github.sha }} - path: app/build/reports/tests/testReleaseUnitTest + path: app/build/reports/tests/testDebugUnitTest - name: Build app run: ./gradlew assembleRelease -Penable-updater diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 837816b846..09bc955474 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,8 +74,8 @@ android { } sourceSets { - getByName("preview").res.srcDirs("src/debug/res") - getByName("benchmark").res.srcDirs("src/debug/res") + getByName("preview").res.directories.add("src/debug/res") + getByName("benchmark").res.directories.add("src/debug/res") } splits { @@ -138,10 +138,6 @@ android { viewBinding = true buildConfig = true aidl = true - - // Disable some unused things - renderScript = false - shaders = false } lint { diff --git a/build.gradle.kts b/build.gradle.kts index bfa5d8e0ab..253ff367fd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,13 @@ +buildscript { + dependencies { + classpath(libs.kotlin.gradle) + } +} + plugins { alias(libs.plugins.aboutLibraries) apply false alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.kmp.library) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.android.test) apply false alias(libs.plugins.kotlin.compose.compiler) apply false diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index ac3997c268..467dccef44 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { api(libs.sqldelight.androidxPaging) + compileOnly(platform(libs.androidx.compose.bom)) compileOnly(libs.androidx.compose.runtimeAnnotation) testImplementation(libs.bundles.test) diff --git a/gradle.properties b/gradle.properties index 5826add1c7..03ae0ec0a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,6 @@ android.nonTransitiveRClass=false -android.useAndroidX=true kotlin.code.style=official -kotlin.mpp.androidSourceSetLayoutVersion=2 # Configuration should be synced with [/gradle/build-logic/gradle.properties] org.gradle.caching=true diff --git a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt index 9f2022dae8..70e553ea32 100644 --- a/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt +++ b/gradle/build-logic/src/main/kotlin/PluginAndroidBase.kt @@ -1,21 +1,50 @@ -import mihon.gradle.configurations.configureAndroid +import com.android.build.api.dsl.ApplicationDefaultConfig +import com.android.build.api.dsl.CommonExtension +import com.android.build.api.dsl.CompileOptions +import com.android.build.api.dsl.DefaultConfig import mihon.gradle.configurations.configureKotlin -import mihon.gradle.extensions.alias +import mihon.gradle.extensions.android import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs -import mihon.gradle.extensions.plugins +import mihon.gradle.extensions.mihonx import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies @Suppress("UNUSED") class PluginAndroidBase : Plugin { override fun apply(target: Project): Unit = with(target) { - plugins { - alias(libs.plugins.kotlin.android) - } - configureKotlin() configureTest() - configureAndroid() + + android { + defaultConfig { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + if (this is ApplicationDefaultConfig) { + targetSdk = mihonx.versions.android.sdk.target.get().toInt() + } + + ndkVersion = mihonx.versions.android.ndk.get() + } + + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + + compileOptions { + isCoreLibraryDesugaringEnabled = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) + } } } + +private fun CommonExtension.defaultConfig(block: DefaultConfig.() -> Unit) { + defaultConfig.apply(block) +} + +private fun CommonExtension.compileOptions(block: CompileOptions.() -> Unit) { + compileOptions.apply(block) +} diff --git a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt index 24580206a4..8f961e06f3 100644 --- a/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt +++ b/gradle/build-logic/src/main/kotlin/PluginComposeAndroid.kt @@ -1,3 +1,5 @@ +import com.android.build.api.dsl.BuildFeatures +import com.android.build.api.dsl.CommonExtension import mihon.gradle.extensions.alias import mihon.gradle.extensions.android import mihon.gradle.extensions.api @@ -31,3 +33,7 @@ class PluginComposeAndroid : Plugin { } } } + +private fun CommonExtension.buildFeatures(block: BuildFeatures.() -> Unit) { + buildFeatures.apply(block) +} diff --git a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt index 7f37f863c7..5839156449 100644 --- a/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt +++ b/gradle/build-logic/src/main/kotlin/PluginKotlinMultiplatform.kt @@ -1,32 +1,44 @@ -import mihon.gradle.configurations.configureAndroid +import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget import mihon.gradle.configurations.configureKotlin import mihon.gradle.extensions.alias import mihon.gradle.extensions.configureTest +import mihon.gradle.extensions.coreLibraryDesugaring import mihon.gradle.extensions.libs +import mihon.gradle.extensions.mihonx import mihon.gradle.extensions.plugins import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import kotlin.text.toInt @Suppress("UNUSED") class PluginKotlinMultiplatform : Plugin { override fun apply(target: Project): Unit = with(target) { plugins { - alias(libs.plugins.android.library) + alias(libs.plugins.android.kmp.library) alias(libs.plugins.kotlin.multiplatform) } configureKotlin() configureTest() - configureAndroid() kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate() - androidTarget() + android { + minSdk = mihonx.versions.android.sdk.min.get().toInt() + compileSdk = mihonx.versions.android.sdk.compile.get().toInt() + enableCoreLibraryDesugaring = true + } + } + + dependencies { + coreLibraryDesugaring(libs.android.desugar) } } } @@ -34,3 +46,7 @@ class PluginKotlinMultiplatform : Plugin { private fun Project.kotlin(block: KotlinMultiplatformExtension.() -> Unit) { extensions.configure(block) } + +private fun KotlinMultiplatformExtension.android(block: KotlinMultiplatformAndroidLibraryTarget.() -> Unit) { + targets.withType().configureEach(block) +} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt deleted file mode 100644 index 2fb978311e..0000000000 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/configurations/Android.kt +++ /dev/null @@ -1,32 +0,0 @@ -package mihon.gradle.configurations - -import com.android.build.api.dsl.ApplicationDefaultConfig -import mihon.gradle.extensions.android -import mihon.gradle.extensions.coreLibraryDesugaring -import mihon.gradle.extensions.libs -import mihon.gradle.extensions.mihonx -import org.gradle.api.Project -import org.gradle.kotlin.dsl.dependencies - -fun Project.configureAndroid() { - android { - defaultConfig { - minSdk = mihonx.versions.android.sdk.min.get().toInt() - if (this is ApplicationDefaultConfig) { - targetSdk = mihonx.versions.android.sdk.target.get().toInt() - } - - ndkVersion = mihonx.versions.android.ndk.get() - } - - compileSdk = mihonx.versions.android.sdk.compile.get().toInt() - - compileOptions { - isCoreLibraryDesugaringEnabled = true - } - } - - dependencies { - coreLibraryDesugaring(libs.android.desugar) - } -} diff --git a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt index f3621c61d3..86cb5ebd92 100644 --- a/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt +++ b/gradle/build-logic/src/main/kotlin/mihon/gradle/extensions/Project.kt @@ -1,7 +1,6 @@ package mihon.gradle.extensions import com.android.build.api.dsl.CommonExtension -import com.android.build.gradle.BaseExtension import org.gradle.accessors.dm.LibrariesForAniyomilibs import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.accessors.dm.LibrariesForMihonx @@ -24,10 +23,8 @@ internal fun Project.plugins(block: PluginManager.() -> Unit) { pluginManager.apply(block) } -internal fun Project.android(block: CommonExtension<*, *, *, *, *, *>.() -> Unit) { - extensions.configure { - if (this is CommonExtension<*, *, *, *, *, *>) apply(block) - } +internal fun Project.android(block: CommonExtension.() -> Unit) { + extensions.configure(block) } fun Project.configureTest() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bdaea52cff..3aa44b4832 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] aboutLibraries = "14.0.1" android-desugar = "2.1.5" -android-gradle = "8.13.2" +android-gradle = "9.2.0" androidx-activity-compose = "1.13.0" androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" @@ -83,7 +83,7 @@ androidx-benchmark-macroJunit4 = { module = "androidx.benchmark:benchmark-macro- androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" } androidx-compose-animation = { module = "androidx.compose.animation:animation" } androidx-compose-animationGraphics = { module = "androidx.compose.animation:animation-graphics" } -androidx-compose-bom = { module = "androidx.compose:compose-bom-beta", version.ref = "androidx-compose-bom" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "androidx-compose-bom" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-materialIcons = { module = "androidx.compose.material:material-icons-extended" } @@ -186,11 +186,11 @@ xmlutil-serialization = { module = "io.github.pdvrieze.xmlutil:serialization", v [plugins] aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } android-application = { id = "com.android.application", version.ref = "android-gradle" } -android-library = { id = "com.android.library", version.ref = "android-gradle" } android-test = { id = "com.android.test", version.ref = "android-gradle" } +android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "android-gradle" } +android-library = { id = "com.android.library", version.ref = "android-gradle" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-gradle" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-gradle" } diff --git a/i18n-animiru/build.gradle.kts b/i18n-animiru/build.gradle.kts index c84ed9423b..843000d2a3 100644 --- a/i18n-animiru/build.gradle.kts +++ b/i18n-animiru/build.gradle.kts @@ -11,10 +11,12 @@ plugins { kotlin { android { namespace = "tachiyomi.i18n.animiru" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) - @Suppress("UnstableApiUsage") dependencies { api(libs.moko.resources) } diff --git a/i18n-aniyomi/build.gradle.kts b/i18n-aniyomi/build.gradle.kts index 40ee0601db..762bafe7c7 100644 --- a/i18n-aniyomi/build.gradle.kts +++ b/i18n-aniyomi/build.gradle.kts @@ -11,10 +11,12 @@ plugins { kotlin { android { namespace = "tachiyomi.i18n.aniyomi" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) - @Suppress("UnstableApiUsage") dependencies { api(libs.moko.resources) } diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index aba2cab73b..808068734d 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -11,10 +11,12 @@ plugins { kotlin { android { namespace = "tachiyomi.i18n" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) - @Suppress("UnstableApiUsage") dependencies { api(libs.moko.resources) } diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index 3d156f3e87..9683dbf047 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -8,16 +8,22 @@ plugins { } kotlin { + @Suppress("UnstableApiUsage") android { namespace = "eu.kanade.tachiyomi.source" - defaultConfig { - consumerProguardFile("consumer-proguard.pro") + optimization { + consumerKeepRules.apply { + publish = true + file("consumer-proguard.pro") + } } + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) - @Suppress("UnstableApiUsage") dependencies { api(libs.kotlinx.serialization.json) api(libs.injekt) diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index c0e2841468..998771d03a 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -8,10 +8,12 @@ plugins { kotlin { android { namespace = "tachiyomi.source.local" + + // TODO(antsy): Remove when https://youtrack.jetbrains.com/issue/KT-83319 is resolved + withHostTest { } } @OptIn(ExperimentalKotlinGradlePluginApi::class) - @Suppress("UnstableApiUsage") dependencies { implementation(projects.sourceApi) api(projects.i18n) From 7fba12cbe1df212ac4a3938b82649063b5905966 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Mon, 11 May 2026 09:24:53 +0200 Subject: [PATCH 42/59] Update sqldelight-androidx-driver and remove transaction management `sqldelight-androidx-driver` has it's own transaction management Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 2 + .../anime/interactor/GetExcludedScanlators.kt | 16 +- .../anime/interactor/SetExcludedScanlators.kt | 16 +- .../create/creators/AnimeBackupCreator.kt | 28 +-- .../backup/restore/restorers/AnimeRestorer.kt | 236 +++++++++--------- .../restore/restorers/CategoriesRestorer.kt | 11 +- .../restore/restorers/CustomButtonRestorer.kt | 27 +- .../restorers/ExtensionRepoRestorer.kt | 20 +- .../data/connection/syncmiru/SyncManager.kt | 17 +- .../java/eu/kanade/tachiyomi/di/AppModule.kt | 3 - data/build.gradle.kts | 1 + .../repository/ExtensionRepoRepositoryImpl.kt | 53 ++-- .../tachiyomi/data/AndroidDatabaseHandler.kt | 104 -------- .../java/tachiyomi/data/DatabaseHandler.kt | 47 ---- .../java/tachiyomi/data/QueryExtension.kt | 36 +++ .../java/tachiyomi/data/QueryPagingSource.kt | 17 +- .../java/tachiyomi/data/TransactionContext.kt | 165 ------------ .../java/tachiyomi/data/anime/AnimeMapper.kt | 2 +- .../data/anime/AnimeRepositoryImpl.kt | 99 ++++---- .../data/category/CategoryRepositoryImpl.kt | 76 ++---- .../CustomButtonRepositoryImpl.kt | 41 ++- .../data/episode/EpisodeRepositoryImpl.kt | 71 +++--- .../data/history/HistoryRepositoryImpl.kt | 34 ++- .../data/source/SourceRepositoryImpl.kt | 16 +- .../data/source/StubSourceRepositoryImpl.kt | 12 +- .../data/track/TrackRepositoryImpl.kt | 42 ++-- .../data/updates/UpdatesRepositoryImpl.kt | 60 +++-- .../main/sqldelight/tachiyomi/data/animes.sq | 12 +- .../sqldelight/tachiyomi/data/categories.sq | 12 +- .../tachiyomi/data/custom_buttons.sq | 12 +- .../sqldelight/tachiyomi/data/episodes.sq | 12 +- gradle/libs.versions.toml | 7 +- 32 files changed, 501 insertions(+), 806 deletions(-) delete mode 100644 data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt delete mode 100644 data/src/main/java/tachiyomi/data/DatabaseHandler.kt create mode 100644 data/src/main/java/tachiyomi/data/QueryExtension.kt delete mode 100644 data/src/main/java/tachiyomi/data/TransactionContext.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09bc955474..9d1c0392f6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -207,6 +207,8 @@ dependencies { implementation(libs.bundles.kotlinx.coroutines) + implementation(libs.sqldelight.async) + // AndroidX libraries implementation(libs.androidx.annotation) implementation(libs.androidx.appCompat) diff --git a/app/src/main/java/eu/kanade/domain/anime/interactor/GetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/anime/interactor/GetExcludedScanlators.kt index 0b25aa693a..215e018ac6 100644 --- a/app/src/main/java/eu/kanade/domain/anime/interactor/GetExcludedScanlators.kt +++ b/app/src/main/java/eu/kanade/domain/anime/interactor/GetExcludedScanlators.kt @@ -1,24 +1,24 @@ package eu.kanade.domain.anime.interactor +import app.cash.sqldelight.async.coroutines.awaitAsList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList class GetExcludedScanlators( - private val handler: DatabaseHandler, + private val database: Database, ) { suspend fun await(animeId: Long): Set { - return handler.awaitList { - excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) - } + return database.excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) + .awaitAsList() .toSet() } fun subscribe(animeId: Long): Flow> { - return handler.subscribeToList { - excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) - } + return database.excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) + .subscribeToList() .map { it.toSet() } } } diff --git a/app/src/main/java/eu/kanade/domain/anime/interactor/SetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/anime/interactor/SetExcludedScanlators.kt index 0b7cc4c5e8..f4b64b4789 100644 --- a/app/src/main/java/eu/kanade/domain/anime/interactor/SetExcludedScanlators.kt +++ b/app/src/main/java/eu/kanade/domain/anime/interactor/SetExcludedScanlators.kt @@ -1,22 +1,22 @@ package eu.kanade.domain.anime.interactor -import tachiyomi.data.DatabaseHandler +import app.cash.sqldelight.async.coroutines.awaitAsList +import tachiyomi.data.Database class SetExcludedScanlators( - private val handler: DatabaseHandler, + private val database: Database, ) { suspend fun await(animeId: Long, excludedScanlators: Set) { - handler.await(inTransaction = true) { - val currentExcluded = handler.awaitList { - excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) - }.toSet() + database.transaction { + val currentExcluded = database.excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(animeId) + .awaitAsList().toSet() val toAdd = excludedScanlators.minus(currentExcluded) for (scanlator in toAdd) { - excluded_scanlatorsQueries.insert(animeId, scanlator) + database.excluded_scanlatorsQueries.insert(animeId, scanlator) } val toRemove = currentExcluded.minus(excludedScanlators) - excluded_scanlatorsQueries.remove(animeId, toRemove) + database.excluded_scanlatorsQueries.remove(animeId, toRemove) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/AnimeBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/AnimeBackupCreator.kt index 5d19b86de6..b50333da9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/AnimeBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/AnimeBackupCreator.kt @@ -1,12 +1,14 @@ package eu.kanade.tachiyomi.data.backup.create.creators +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne import eu.kanade.tachiyomi.data.backup.create.BackupOptions import eu.kanade.tachiyomi.data.backup.models.BackupAnime import eu.kanade.tachiyomi.data.backup.models.BackupEpisode import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.backupEpisodeMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.domain.anime.interactor.GetCustomAnimeInfo import tachiyomi.domain.anime.model.Anime import tachiyomi.domain.anime.model.CustomAnimeInfo @@ -16,7 +18,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class AnimeBackupCreator( - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), private val getHistory: GetHistory = Injekt.get(), // AM (CUSTOM_INFORMATION) --> @@ -38,19 +40,17 @@ class AnimeBackupCreator( // <-- AM (CUSTOM_INFORMATION) ) - animeObject.excludedScanlators = handler.awaitList { - excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(anime.id) - } + animeObject.excludedScanlators = database.excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(anime.id) + .awaitAsList() if (options.episodes) { // Backup all the episodes - handler.awaitList { - episodesQueries.getEpisodesByAnimeId( - animeId = anime.id, - applyScanlatorFilter = 0, // false - mapper = backupEpisodeMapper, - ) - } + database.episodesQueries.getEpisodesByAnimeId( + animeId = anime.id, + applyScanlatorFilter = 0, // false + mapper = backupEpisodeMapper, + ) + .awaitAsList() .takeUnless(List::isEmpty) ?.let { animeObject.episodes = it } } @@ -64,7 +64,7 @@ class AnimeBackupCreator( } if (options.tracking) { - val tracks = handler.awaitList { anime_syncQueries.getTracksByAnimeId(anime.id, backupTrackMapper) } + val tracks = database.anime_syncQueries.getTracksByAnimeId(anime.id, backupTrackMapper).awaitAsList() if (tracks.isNotEmpty()) { animeObject.tracking = tracks } @@ -74,7 +74,7 @@ class AnimeBackupCreator( val historyByAnimeId = getHistory.await(anime.id) if (historyByAnimeId.isNotEmpty()) { val history = historyByAnimeId.map { history -> - val episode = handler.awaitOne { episodesQueries.getEpisodeById(history.episodeId) } + val episode = database.episodesQueries.getEpisodeById(history.episodeId).awaitAsOne() BackupHistory(episode.url, history.seenAt?.time ?: 0L) } if (history.isNotEmpty()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt index df1cd984ef..096cf3bc85 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt @@ -1,12 +1,15 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.tachiyomi.data.backup.models.BackupAnime import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupEpisode import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupTracking -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.data.FetchTypeColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.domain.anime.interactor.FetchInterval @@ -27,7 +30,7 @@ import java.util.Date import kotlin.math.max class AnimeRestorer( - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), private val getAnimeByUrlAndSourceId: GetAnimeByUrlAndSourceId = Injekt.get(), private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get(), @@ -49,7 +52,8 @@ class AnimeRestorer( } suspend fun sortByNew(backupAnimes: List): List { - val urlsBySource = handler.awaitList { animesQueries.getAllAnimeSourceAndUrl() } + val urlsBySource = database.animesQueries.getAllAnimeSourceAndUrl() + .awaitAsList() .groupBy({ it.source }, { it.url }) return backupAnimes @@ -69,7 +73,7 @@ class AnimeRestorer( backupSeasons: List, // <-- AY ) { - handler.await(inTransaction = true) { + database.transaction { val dbAnime = findExistingAnime(backupAnime) val anime = backupAnime.getAnimeImpl() val restoredAnime = if (dbAnime == null) { @@ -144,42 +148,40 @@ class AnimeRestorer( } internal suspend fun updateAnime(anime: Anime): Anime { - handler.await(true) { - animesQueries.update( - source = anime.source, - url = anime.url, - artist = anime.artist, - author = anime.author, - description = anime.description, - genre = anime.genre?.joinToString(separator = ", "), - title = anime.title, - status = anime.status, - thumbnailUrl = anime.thumbnailUrl, - favorite = anime.favorite, - lastUpdate = anime.lastUpdate, - nextUpdate = null, - calculateInterval = null, - initialized = anime.initialized, - viewer = anime.viewerFlags, - episodeFlags = anime.episodeFlags, - coverLastModified = anime.coverLastModified, - dateAdded = anime.dateAdded, - animeId = anime.id, - updateStrategy = anime.updateStrategy.let(UpdateStrategyColumnAdapter::encode), - version = anime.version, - isSyncing = 1, - notes = anime.notes, - // AY --> - fetchType = anime.fetchType.let(FetchTypeColumnAdapter::encode), - parentId = anime.parentId, - seasonFlags = anime.seasonFlags, - seasonNumber = anime.seasonNumber, - seasonSourceOrder = anime.seasonSourceOrder, - backgroundUrl = anime.backgroundUrl, - backgroundLastModified = anime.backgroundLastModified, - // <-- AY - ) - } + database.animesQueries.update( + source = anime.source, + url = anime.url, + artist = anime.artist, + author = anime.author, + description = anime.description, + genre = anime.genre?.joinToString(separator = ", "), + title = anime.title, + status = anime.status, + thumbnailUrl = anime.thumbnailUrl, + favorite = anime.favorite, + lastUpdate = anime.lastUpdate, + nextUpdate = null, + calculateInterval = null, + initialized = anime.initialized, + viewer = anime.viewerFlags, + episodeFlags = anime.episodeFlags, + coverLastModified = anime.coverLastModified, + dateAdded = anime.dateAdded, + animeId = anime.id, + updateStrategy = anime.updateStrategy.let(UpdateStrategyColumnAdapter::encode), + version = anime.version, + isSyncing = 1, + notes = anime.notes, + // AY --> + fetchType = anime.fetchType.let(FetchTypeColumnAdapter::encode), + parentId = anime.parentId, + seasonFlags = anime.seasonFlags, + seasonNumber = anime.seasonNumber, + seasonSourceOrder = anime.seasonSourceOrder, + backgroundUrl = anime.backgroundUrl, + backgroundLastModified = anime.backgroundLastModified, + // <-- AY + ) return anime } @@ -240,9 +242,9 @@ class AnimeRestorer( this.copy(id = 0L, animeId = 0L, dateFetch = 0L, dateUpload = 0L, lastModifiedAt = 0L, version = 0L) private suspend fun insertNewEpisodes(episodes: List) { - handler.await(true) { + database.transaction { episodes.forEach { episode -> - episodesQueries.insert( + database.episodesQueries.insert( episode.animeId, episode.url, episode.name, @@ -271,9 +273,9 @@ class AnimeRestorer( } private suspend fun updateExistingEpisodes(episodes: List) { - handler.await(true) { + database.transaction { episodes.forEach { episode -> - episodesQueries.update( + database.episodesQueries.update( animeId = null, url = null, name = null, @@ -309,41 +311,39 @@ class AnimeRestorer( * @return id of [Anime], null if not found */ private suspend fun insertAnime(anime: Anime): Long { - return handler.awaitOneExecutable(true) { - animesQueries.insert( - source = anime.source, - url = anime.url, - artist = anime.artist, - author = anime.author, - description = anime.description, - genre = anime.genre, - title = anime.title, - status = anime.status, - thumbnailUrl = anime.thumbnailUrl, - favorite = anime.favorite, - lastUpdate = anime.lastUpdate, - nextUpdate = 0L, - calculateInterval = 0L, - initialized = anime.initialized, - viewerFlags = anime.viewerFlags, - episodeFlags = anime.episodeFlags, - coverLastModified = anime.coverLastModified, - dateAdded = anime.dateAdded, - updateStrategy = anime.updateStrategy, - version = anime.version, - notes = anime.notes, - // AY --> - fetchType = anime.fetchType, - parentId = anime.parentId, - seasonFlags = anime.seasonFlags, - seasonNumber = anime.seasonNumber, - seasonSourceOrder = anime.seasonSourceOrder, - backgroundUrl = anime.backgroundUrl, - backgroundLastModified = anime.backgroundLastModified, - // <-- AY - ) - animesQueries.selectLastInsertedRowId() - } + return database.animesQueries.insert( + source = anime.source, + url = anime.url, + artist = anime.artist, + author = anime.author, + description = anime.description, + genre = anime.genre, + title = anime.title, + status = anime.status, + thumbnailUrl = anime.thumbnailUrl, + favorite = anime.favorite, + lastUpdate = anime.lastUpdate, + nextUpdate = 0L, + calculateInterval = 0L, + initialized = anime.initialized, + viewerFlags = anime.viewerFlags, + episodeFlags = anime.episodeFlags, + coverLastModified = anime.coverLastModified, + dateAdded = anime.dateAdded, + updateStrategy = anime.updateStrategy, + version = anime.version, + notes = anime.notes, + // AY --> + fetchType = anime.fetchType, + parentId = anime.parentId, + seasonFlags = anime.seasonFlags, + seasonNumber = anime.seasonNumber, + seasonSourceOrder = anime.seasonSourceOrder, + backgroundUrl = anime.backgroundUrl, + backgroundLastModified = anime.backgroundLastModified, + // <-- AY + ) + .awaitAsOne() } private suspend fun restoreAnimeDetails( @@ -395,10 +395,10 @@ class AnimeRestorer( } if (animeCategoriesToUpdate.isNotEmpty()) { - handler.await(true) { - animes_categoriesQueries.deleteAnimeCategoryByAnimeId(anime.id) + database.transaction { + database.animes_categoriesQueries.deleteAnimeCategoryByAnimeId(anime.id) animeCategoriesToUpdate.forEach { (animeId, categoryId) -> - animes_categoriesQueries.insert(animeId, categoryId) + database.animes_categoriesQueries.insert(animeId, categoryId) } } } @@ -406,11 +406,11 @@ class AnimeRestorer( private suspend fun restoreHistory(backupHistory: List) { val toUpdate = backupHistory.mapNotNull { history -> - val dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByEpisodeUrl(history.url) } + val dbHistory = database.historyQueries.getHistoryByEpisodeUrl(history.url).awaitAsOneOrNull() val item = history.getHistoryImpl() if (dbHistory == null) { - val episode = handler.awaitOneOrNull { episodesQueries.getEpisodeByUrl(history.url) } + val episode = database.episodesQueries.getEpisodeByUrl(history.url).awaitAsOneOrNull() return@mapNotNull if (episode == null) { // Episode doesn't exist; skip null @@ -430,14 +430,13 @@ class AnimeRestorer( ) } - if (toUpdate.isNotEmpty()) { - handler.await(true) { - toUpdate.forEach { - historyQueries.upsert( - it.episodeId, - it.seenAt, - ) - } + if (toUpdate.isEmpty()) return + database.transaction { + toUpdate.forEach { + database.historyQueries.upsert( + it.episodeId, + it.seenAt, + ) } } } @@ -472,26 +471,25 @@ class AnimeRestorer( if (newTracks.isNotEmpty()) { insertTrack.awaitAll(newTracks) } - if (existingTracks.isNotEmpty()) { - handler.await(true) { - existingTracks.forEach { track -> - anime_syncQueries.update( - track.animeId, - track.trackerId, - track.remoteId, - track.libraryId, - track.title, - track.lastEpisodeSeen, - track.totalEpisodes, - track.status, - track.score, - track.remoteUrl, - track.startDate, - track.finishDate, - track.private, - track.id, - ) - } + if (existingTracks.isEmpty()) return + database.transaction { + existingTracks.forEach { track -> + database.anime_syncQueries.update( + track.animeId, + track.trackerId, + track.remoteId, + track.libraryId, + track.title, + track.lastEpisodeSeen, + track.totalEpisodes, + track.status, + track.score, + track.remoteUrl, + track.startDate, + track.finishDate, + track.private, + track.id, + ) } } } @@ -506,16 +504,12 @@ class AnimeRestorer( */ private suspend fun restoreExcludedScanlators(anime: Anime, excludedScanlators: List) { if (excludedScanlators.isEmpty()) return - val existingExcludedScanlators = handler.awaitList { - excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(anime.id) - } + val existingExcludedScanlators = database.excluded_scanlatorsQueries.getExcludedScanlatorsByAnimeId(anime.id) + .awaitAsList() val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators } - if (toInsert.isNotEmpty()) { - handler.await { - toInsert.forEach { - excluded_scanlatorsQueries.insert(anime.id, it) - } - } + if (toInsert.isEmpty()) return + toInsert.forEach { + database.excluded_scanlatorsQueries.insert(anime.id, it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index 3d73cfefc1..8300e25610 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -1,14 +1,15 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers +import app.cash.sqldelight.async.coroutines.awaitAsOne import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class CategoriesRestorer( - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), ) { @@ -25,10 +26,8 @@ class CategoriesRestorer( val dbCategory = dbCategoriesByName[it.name] if (dbCategory != null) return@map dbCategory val order = nextOrder++ - handler.awaitOneExecutable { - categoriesQueries.insert(it.name, order, it.flags) - categoriesQueries.selectLastInsertedRowId() - } + database.categoriesQueries.insert(it.name, order, it.flags) + .awaitAsOne() .let { id -> it.toCategory(id).copy(order = order) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CustomButtonRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CustomButtonRestorer.kt index af50f6918b..e16221d030 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CustomButtonRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CustomButtonRestorer.kt @@ -1,14 +1,15 @@ // AY --> package eu.kanade.tachiyomi.data.backup.restore.restorers +import app.cash.sqldelight.async.coroutines.awaitAsOne import eu.kanade.tachiyomi.data.backup.models.BackupCustomButtons -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.domain.custombutton.interactor.GetCustomButtons import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class CustomButtonRestorer( - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val getCustomButtons: GetCustomButtons = Injekt.get(), ) { suspend operator fun invoke( @@ -26,18 +27,16 @@ class CustomButtonRestorer( val dbCustomButton = dbCustomButtonsByName[it.name] if (dbCustomButton != null) return@map dbCustomButton val sortIndex = nextSortIndex++ - handler.awaitOneExecutable { - val isFavorite = it.isFavorite && !dbHasFavorite - custom_buttonsQueries.insert( - it.name, - isFavorite, - sortIndex, - it.content, - it.longPressContent, - it.onStartup, - ) - custom_buttonsQueries.selectLastInsertedRowId() - } + val isFavorite = it.isFavorite && !dbHasFavorite + database.custom_buttonsQueries.insert( + it.name, + isFavorite, + sortIndex, + it.content, + it.longPressContent, + it.onStartup, + ) + .awaitAsOne() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt index 006e48df16..7dbc0631f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos import mihon.domain.extensionrepo.interactor.GetExtensionRepo -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class ExtensionRepoRestorer( - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val getExtensionRepos: GetExtensionRepo = Injekt.get(), ) { @@ -26,15 +26,13 @@ class ExtensionRepoRestorer( } else if (shaExists != null) { error("${shaExists.name} has the same signing key fingerprint") } else { - handler.await { - extension_reposQueries.insert( - backupRepo.baseUrl, - backupRepo.name, - backupRepo.shortName, - backupRepo.website, - backupRepo.signingKeyFingerprint, - ) - } + database.extension_reposQueries.insert( + backupRepo.baseUrl, + backupRepo.name, + backupRepo.shortName, + backupRepo.website, + backupRepo.signingKeyFingerprint, + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/connection/syncmiru/SyncManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/connection/syncmiru/SyncManager.kt index 4b241b8d50..1e219ddae6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/connection/syncmiru/SyncManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/connection/syncmiru/SyncManager.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.connection.syncmiru import android.content.Context import android.net.Uri +import app.cash.sqldelight.async.coroutines.awaitAsList import eu.kanade.domain.connection.SyncPreferences import eu.kanade.tachiyomi.data.backup.create.BackupCreator import eu.kanade.tachiyomi.data.backup.create.BackupOptions @@ -19,7 +20,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.data.Episodes import tachiyomi.data.anime.AnimeMapper.mapAnime import tachiyomi.domain.anime.model.Anime @@ -40,7 +41,7 @@ import logcat.logcat as log */ class SyncManager( private val context: Context, - private val handler: DatabaseHandler = Injekt.get(), + private val database: Database = Injekt.get(), private val syncPreferences: SyncPreferences = Injekt.get(), private var json: Json = Json { encodeDefaults = true @@ -61,9 +62,9 @@ class SyncManager( suspend fun syncData() { // Reset isSyncing in case it was left over or failed syncing during restore. - handler.await(inTransaction = true) { - animesQueries.resetIsSyncing() - episodesQueries.resetIsSyncing() + database.transaction { + database.animesQueries.resetIsSyncing() + database.episodesQueries.resetIsSyncing() } val syncOptions = syncPreferences.getSyncSettings() @@ -208,15 +209,15 @@ class SyncManager( * @return a list of all anime stored in the database */ private suspend fun getAllAnimeFromDB(): List { - return handler.awaitList { animesQueries.getAllAnime(::mapAnime) } + return database.animesQueries.getAllAnime(::mapAnime).awaitAsList() } private suspend fun getAllAnimeThatNeedsSync(): List { - return handler.awaitList { animesQueries.getAnimesWithFavoriteTimestamp(::mapAnime) } + return database.animesQueries.getAnimesWithFavoriteTimestamp(::mapAnime).awaitAsList() } private suspend fun isAnimeDifferent(localAnime: Anime, remoteAnime: BackupAnime): Boolean { - val localEpisodes = handler.await { episodesQueries.getEpisodesByAnimeId(localAnime.id, 0).executeAsList() } + val localEpisodes = database.episodesQueries.getEpisodesByAnimeId(localAnime.id, 0).awaitAsList() val localCategories = getCategories.await(localAnime.id).map { it.order } if (areEpisodesDifferent(localEpisodes, remoteAnime.episodes)) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index b879ff6150..a8de838fce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -32,10 +32,8 @@ import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML import tachiyomi.core.common.storage.AndroidStorageFolderProvider -import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.Animes import tachiyomi.data.Database -import tachiyomi.data.DatabaseHandler import tachiyomi.data.DateColumnAdapter import tachiyomi.data.FetchTypeColumnAdapter import tachiyomi.data.History @@ -95,7 +93,6 @@ class AppModule(val app: Application) : InjektModule { ), ) } - addSingletonFactory { AndroidDatabaseHandler(get(), get()) } addSingletonFactory { Json { diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 38386f7c3d..002c1a0b3c 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -15,6 +15,7 @@ android { packageName.set("tachiyomi.data") dialect(libs.sqldelight.sqliteDialect338) schemaOutputDirectory.set(project.file("./src/main/sqldelight")) + generateAsync.set(true) } } } diff --git a/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt b/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt index 61c279630a..0b7bfbf3f9 100644 --- a/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt +++ b/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt @@ -1,36 +1,49 @@ package mihon.data.repository import android.database.SQLException +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import mihon.domain.extensionrepo.exception.SaveExtensionRepoException import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.repository.ExtensionRepoRepository -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList +import tachiyomi.data.subscribeToOne class ExtensionRepoRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : ExtensionRepoRepository { override fun subscribeAll(): Flow> { - return handler.subscribeToList { extension_reposQueries.findAll(::mapExtensionRepo) } + return database.extension_reposQueries + .findAll(::mapExtensionRepo) + .subscribeToList() } override suspend fun getAll(): List { - return handler.awaitList { extension_reposQueries.findAll(::mapExtensionRepo) } + return database.extension_reposQueries + .findAll(::mapExtensionRepo) + .awaitAsList() } override suspend fun getRepo(baseUrl: String): ExtensionRepo? { - return handler.awaitOneOrNull { extension_reposQueries.findOne(baseUrl, ::mapExtensionRepo) } + return database.extension_reposQueries + .findOne(baseUrl, ::mapExtensionRepo) + .awaitAsOneOrNull() } override suspend fun getRepoBySigningKeyFingerprint(fingerprint: String): ExtensionRepo? { - return handler.awaitOneOrNull { - extension_reposQueries.findOneBySigningKeyFingerprint(fingerprint, ::mapExtensionRepo) - } + return database.extension_reposQueries + .findOneBySigningKeyFingerprint(fingerprint, ::mapExtensionRepo) + .awaitAsOneOrNull() } override fun getCount(): Flow { - return handler.subscribeToOne { extension_reposQueries.count() }.map { it.toInt() } + return database.extension_reposQueries + .count() + .subscribeToOne() + .map { it.toInt() } } override suspend fun insertRepo( @@ -41,7 +54,7 @@ class ExtensionRepoRepositoryImpl( signingKeyFingerprint: String, ) { try { - handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, signingKeyFingerprint) } + database.extension_reposQueries.insert(baseUrl, name, shortName, website, signingKeyFingerprint) } catch (ex: SQLException) { throw SaveExtensionRepoException(ex) } @@ -55,26 +68,24 @@ class ExtensionRepoRepositoryImpl( signingKeyFingerprint: String, ) { try { - handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, signingKeyFingerprint) } + database.extension_reposQueries.upsert(baseUrl, name, shortName, website, signingKeyFingerprint) } catch (ex: SQLException) { throw SaveExtensionRepoException(ex) } } override suspend fun replaceRepo(newRepo: ExtensionRepo) { - handler.await { - extension_reposQueries.replace( - newRepo.baseUrl, - newRepo.name, - newRepo.shortName, - newRepo.website, - newRepo.signingKeyFingerprint, - ) - } + database.extension_reposQueries.replace( + newRepo.baseUrl, + newRepo.name, + newRepo.shortName, + newRepo.website, + newRepo.signingKeyFingerprint, + ) } override suspend fun deleteRepo(baseUrl: String) { - return handler.await { extension_reposQueries.delete(baseUrl) } + database.extension_reposQueries.delete(baseUrl) } private fun mapExtensionRepo( diff --git a/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt b/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt deleted file mode 100644 index be74a1102a..0000000000 --- a/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt +++ /dev/null @@ -1,104 +0,0 @@ -package tachiyomi.data - -import androidx.paging.PagingSource -import app.cash.sqldelight.ExecutableQuery -import app.cash.sqldelight.Query -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList -import app.cash.sqldelight.coroutines.mapToOne -import app.cash.sqldelight.coroutines.mapToOneOrNull -import app.cash.sqldelight.db.SqlDriver -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext - -class AndroidDatabaseHandler( - val db: Database, - private val driver: SqlDriver, - val queryDispatcher: CoroutineDispatcher = Dispatchers.IO, - val transactionDispatcher: CoroutineDispatcher = queryDispatcher, -) : DatabaseHandler { - - val suspendingTransactionId = ThreadLocal() - - override suspend fun await(inTransaction: Boolean, block: suspend Database.() -> T): T { - return dispatch(inTransaction, block) - } - - override suspend fun awaitList( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): List { - return dispatch(inTransaction) { block(db).executeAsList() } - } - - override suspend fun awaitOne( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): T { - return dispatch(inTransaction) { block(db).executeAsOne() } - } - - override suspend fun awaitOneExecutable( - inTransaction: Boolean, - block: suspend Database.() -> ExecutableQuery, - ): T { - return dispatch(inTransaction) { block(db).executeAsOne() } - } - - override suspend fun awaitOneOrNull( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): T? { - return dispatch(inTransaction) { block(db).executeAsOneOrNull() } - } - - override suspend fun awaitOneOrNullExecutable( - inTransaction: Boolean, - block: suspend Database.() -> ExecutableQuery, - ): T? { - return dispatch(inTransaction) { block(db).executeAsOneOrNull() } - } - - override fun subscribeToList(block: Database.() -> Query): Flow> { - return block(db).asFlow().mapToList(queryDispatcher) - } - - override fun subscribeToOne(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOne(queryDispatcher) - } - - override fun subscribeToOneOrNull(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOneOrNull(queryDispatcher) - } - - override fun subscribeToPagingSource( - countQuery: Database.() -> Query, - queryProvider: Database.(Long, Long) -> Query, - ): PagingSource { - return QueryPagingSource( - handler = this, - countQuery = countQuery, - queryProvider = { limit, offset -> - queryProvider.invoke(db, limit, offset) - }, - ) - } - - private suspend fun dispatch(inTransaction: Boolean, block: suspend Database.() -> T): T { - // Create a transaction if needed and run the calling block inside it. - if (inTransaction) { - return withTransaction { block(db) } - } - - // If we're currently in the transaction thread, there's no need to dispatch our query. - if (driver.currentTransaction() != null) { - return block(db) - } - - // Get the current database context and run the calling block. - val context = getCurrentDatabaseContext() - return withContext(context) { block(db) } - } -} diff --git a/data/src/main/java/tachiyomi/data/DatabaseHandler.kt b/data/src/main/java/tachiyomi/data/DatabaseHandler.kt deleted file mode 100644 index 57f58f7909..0000000000 --- a/data/src/main/java/tachiyomi/data/DatabaseHandler.kt +++ /dev/null @@ -1,47 +0,0 @@ -package tachiyomi.data - -import androidx.paging.PagingSource -import app.cash.sqldelight.ExecutableQuery -import app.cash.sqldelight.Query -import kotlinx.coroutines.flow.Flow - -interface DatabaseHandler { - - suspend fun await(inTransaction: Boolean = false, block: suspend Database.() -> T): T - - suspend fun awaitList( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): List - - suspend fun awaitOne( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): T - - suspend fun awaitOneExecutable( - inTransaction: Boolean = false, - block: suspend Database.() -> ExecutableQuery, - ): T - - suspend fun awaitOneOrNull( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): T? - - suspend fun awaitOneOrNullExecutable( - inTransaction: Boolean = false, - block: suspend Database.() -> ExecutableQuery, - ): T? - - fun subscribeToList(block: Database.() -> Query): Flow> - - fun subscribeToOne(block: Database.() -> Query): Flow - - fun subscribeToOneOrNull(block: Database.() -> Query): Flow - - fun subscribeToPagingSource( - countQuery: Database.() -> Query, - queryProvider: Database.(Long, Long) -> Query, - ): PagingSource -} diff --git a/data/src/main/java/tachiyomi/data/QueryExtension.kt b/data/src/main/java/tachiyomi/data/QueryExtension.kt new file mode 100644 index 0000000000..45c90b3e70 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/QueryExtension.kt @@ -0,0 +1,36 @@ +package tachiyomi.data + +import app.cash.sqldelight.Query +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull +import app.cash.sqldelight.coroutines.asFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +fun Query.subscribeToList( + context: CoroutineContext = EmptyCoroutineContext, +): Flow> = asFlow().map { + withContext(context) { + it.awaitAsList() + } +} + +fun Query.subscribeToOne( + context: CoroutineContext = EmptyCoroutineContext, +): Flow = asFlow().map { + withContext(context) { + it.awaitAsOne() + } +} + +fun Query.subscribeToOneOrNull( + context: CoroutineContext = EmptyCoroutineContext, +): Flow = asFlow().map { + withContext(context) { + it.awaitAsOneOrNull() + } +} diff --git a/data/src/main/java/tachiyomi/data/QueryPagingSource.kt b/data/src/main/java/tachiyomi/data/QueryPagingSource.kt index 16974e368c..5b963ff6b1 100644 --- a/data/src/main/java/tachiyomi/data/QueryPagingSource.kt +++ b/data/src/main/java/tachiyomi/data/QueryPagingSource.kt @@ -3,12 +3,14 @@ package tachiyomi.data import androidx.paging.PagingSource import androidx.paging.PagingState import app.cash.sqldelight.Query +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne import kotlin.properties.Delegates +@Suppress("unused") class QueryPagingSource( - val handler: DatabaseHandler, - val countQuery: Database.() -> Query, - val queryProvider: Database.(Long, Long) -> Query, + val countQuery: () -> Query, + val queryProvider: (Long, Long) -> Query, ) : PagingSource(), Query.Listener { override val jumpingSupported: Boolean = true @@ -29,17 +31,16 @@ class QueryPagingSource( try { val key = params.key ?: 0L val loadSize = params.loadSize - val count = handler.awaitOne { countQuery() } + val count = countQuery().awaitAsOne() val (offset, limit) = when (params) { is LoadParams.Prepend -> key - loadSize to loadSize.toLong() else -> key to loadSize.toLong() } - val data = handler.awaitList { - queryProvider(limit, offset) - .also { currentQuery = it } - } + val data = queryProvider(limit, offset) + .also { currentQuery = it } + .awaitAsList() val (prevKey, nextKey) = when (params) { is LoadParams.Append -> (offset - loadSize to offset + loadSize) diff --git a/data/src/main/java/tachiyomi/data/TransactionContext.kt b/data/src/main/java/tachiyomi/data/TransactionContext.kt deleted file mode 100644 index f804a0b8e2..0000000000 --- a/data/src/main/java/tachiyomi/data/TransactionContext.kt +++ /dev/null @@ -1,165 +0,0 @@ -package tachiyomi.data - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Job -import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import java.util.concurrent.RejectedExecutionException -import kotlin.concurrent.atomics.AtomicInt -import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.concurrent.atomics.decrementAndFetch -import kotlin.concurrent.atomics.incrementAndFetch -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.coroutines.coroutineContext -import kotlin.coroutines.resume - -/** - * Returns the transaction dispatcher if we are on a transaction, or the database dispatchers. - */ -internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): CoroutineContext { - return coroutineContext[TransactionElement]?.transactionDispatcher ?: queryDispatcher -} - -/** - * Calls the specified suspending [block] in a database transaction. The transaction will be - * marked as successful unless an exception is thrown in the suspending [block] or the coroutine - * is cancelled. - * - * SQLDelight will only perform at most one transaction at a time, additional transactions are queued - * and executed on a first come, first serve order. - * - * Performing blocking database operations is not permitted in a coroutine scope other than the - * one received by the suspending block. It is recommended that all [Dao] function invoked within - * the [block] be suspending functions. - * - * The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor. - */ -internal suspend fun AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T { - // Use inherited transaction context if available, this allows nested suspending transactions. - val transactionContext = - coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext() - return withContext(transactionContext) { - val transactionElement = coroutineContext[TransactionElement]!! - transactionElement.acquire() - try { - db.transactionWithResult { - runBlocking(transactionContext) { - block() - } - } - } finally { - transactionElement.release() - } - } -} - -/** - * Creates a [CoroutineContext] for performing database operations within a coroutine transaction. - * - * The context is a combination of a dispatcher, a [TransactionElement] and a thread local element. - * - * * The dispatcher will dispatch coroutines to a single thread that is taken over from the SQLDelight - * query executor. If the coroutine context is switched, suspending DAO functions will be able to - * dispatch to the transaction thread. - * - * * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a - * switch of context, suspending DAO methods will be able to use the indicator to dispatch the - * database operation to the transaction thread. - * - * * The thread local element serves as a second indicator and marks threads that are used to - * execute coroutines within the coroutine transaction, more specifically it allows us to identify - * if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to - * this value, for now all we care is if its present or not. - */ -private suspend fun AndroidDatabaseHandler.createTransactionContext(): CoroutineContext { - val controlJob = Job() - // make sure to tie the control job to this context to avoid blocking the transaction if - // context get cancelled before we can even start using this job. Otherwise, the acquired - // transaction thread will forever wait for the controlJob to be cancelled. - // see b/148181325 - coroutineContext[Job]?.invokeOnCompletion { - controlJob.cancel() - } - - val dispatcher = transactionDispatcher.acquireTransactionThread(controlJob) - val transactionElement = TransactionElement(controlJob, dispatcher) - val threadLocalElement = - suspendingTransactionId.asContextElement(System.identityHashCode(controlJob)) - return dispatcher + transactionElement + threadLocalElement -} - -/** - * Acquires a thread from the executor and returns a [ContinuationInterceptor] to dispatch - * coroutines to the acquired thread. The [controlJob] is used to control the release of the - * thread by cancelling the job. - */ -private suspend fun CoroutineDispatcher.acquireTransactionThread( - controlJob: Job, -): ContinuationInterceptor { - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - // We got cancelled while waiting to acquire a thread, we can't stop our attempt to - // acquire a thread, but we can cancel the controlling job so once it gets acquired it - // is quickly released. - controlJob.cancel() - } - try { - dispatch(EmptyCoroutineContext) { - runBlocking { - // Thread acquired, resume coroutine - continuation.resume(coroutineContext[ContinuationInterceptor]!!) - controlJob.join() - } - } - } catch (ex: RejectedExecutionException) { - // Couldn't acquire a thread, cancel coroutine - continuation.cancel( - IllegalStateException( - "Unable to acquire a thread to perform the database transaction", - ex, - ), - ) - } - } -} - -/** - * A [CoroutineContext.Element] that indicates there is an on-going database transaction. - */ -@OptIn(ExperimentalAtomicApi::class) -private class TransactionElement( - private val transactionThreadControlJob: Job, - val transactionDispatcher: ContinuationInterceptor, -) : CoroutineContext.Element { - - companion object Key : CoroutineContext.Key - - override val key: CoroutineContext.Key - get() = TransactionElement - - /** - * Number of transactions (including nested ones) started with this element. - * Call [acquire] to increase the count and [release] to decrease it. If the count reaches zero - * when [release] is invoked then the transaction job is cancelled and the transaction thread - * is released. - */ - private val referenceCount = AtomicInt(0) - - fun acquire() { - referenceCount.incrementAndFetch() - } - - fun release() { - val count = referenceCount.decrementAndFetch() - if (count < 0) { - throw IllegalStateException("Transaction was never started or was already released") - } else if (count == 0) { - // Cancel the job that controls the transaction thread, causing it to be released. - transactionThreadControlJob.cancel() - } - } -} diff --git a/data/src/main/java/tachiyomi/data/anime/AnimeMapper.kt b/data/src/main/java/tachiyomi/data/anime/AnimeMapper.kt index 0c0186cc5e..3bba4abf86 100644 --- a/data/src/main/java/tachiyomi/data/anime/AnimeMapper.kt +++ b/data/src/main/java/tachiyomi/data/anime/AnimeMapper.kt @@ -9,6 +9,7 @@ import tachiyomi.domain.library.model.LibraryAnime import tachiyomi.domain.source.model.DeletableAnime object AnimeMapper { + @Suppress("UNUSED_PARAMETER") fun mapAnime( id: Long, source: Long, @@ -33,7 +34,6 @@ object AnimeMapper { lastModifiedAt: Long, favoriteModifiedAt: Long?, version: Long, - @Suppress("UNUSED_PARAMETER") isSyncing: Long, notes: String, // AY --> diff --git a/data/src/main/java/tachiyomi/data/anime/AnimeRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/anime/AnimeRepositoryImpl.kt index 441c259ef5..543964d352 100644 --- a/data/src/main/java/tachiyomi/data/anime/AnimeRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/anime/AnimeRepositoryImpl.kt @@ -1,13 +1,19 @@ package tachiyomi.data.anime import aniyomi.domain.anime.SeasonAnime +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.data.FetchTypeColumnAdapter import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter +import tachiyomi.data.subscribeToList +import tachiyomi.data.subscribeToOne +import tachiyomi.data.subscribeToOneOrNull import tachiyomi.domain.anime.model.Anime import tachiyomi.domain.anime.model.AnimeUpdate import tachiyomi.domain.anime.model.AnimeWithEpisodeCount @@ -18,73 +24,70 @@ import java.time.LocalDate import java.time.ZoneId class AnimeRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : AnimeRepository { override suspend fun getAnimeById(id: Long): Anime { - return handler.awaitOne { animesQueries.getAnimeById(id, AnimeMapper::mapAnime) } + return database.animesQueries.getAnimeById(id, AnimeMapper::mapAnime).awaitAsOne() } override suspend fun getAnimeByIdAsFlow(id: Long): Flow { - return handler.subscribeToOne { animesQueries.getAnimeById(id, AnimeMapper::mapAnime) } + return database.animesQueries.getAnimeById(id, AnimeMapper::mapAnime).subscribeToOne() } override suspend fun getAnimeByUrlAndSourceId(url: String, sourceId: Long): Anime? { - return handler.awaitOneOrNull { - animesQueries.getAnimeByUrlAndSource( - url, - sourceId, - AnimeMapper::mapAnime, - ) - } + return database.animesQueries.getAnimeByUrlAndSource( + url, + sourceId, + AnimeMapper::mapAnime, + ).awaitAsOneOrNull() } override fun getAnimeByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow { - return handler.subscribeToOneOrNull { - animesQueries.getAnimeByUrlAndSource( - url, - sourceId, - AnimeMapper::mapAnime, - ) - } + return database.animesQueries.getAnimeByUrlAndSource( + url, + sourceId, + AnimeMapper::mapAnime, + ).subscribeToOneOrNull() } override suspend fun getFavorites(): List { - return handler.awaitList { animesQueries.getFavorites(AnimeMapper::mapAnime) } + return database.animesQueries.getFavorites(AnimeMapper::mapAnime).awaitAsList() } override suspend fun getSeenAnimeNotInLibrary(): List { - return handler.awaitList { animesQueries.getSeenAnimeNotInLibrary(AnimeMapper::mapAnime) } + return database.animesQueries.getSeenAnimeNotInLibrary(AnimeMapper::mapAnime).awaitAsList() } override suspend fun getLibraryAnime(): List { - return handler.awaitList { libraryViewQueries.library(AnimeMapper::mapLibraryAnime) } + return database.libraryViewQueries.library(AnimeMapper::mapLibraryAnime).awaitAsList() } override fun getLibraryAnimeAsFlow(): Flow> { - return handler.subscribeToList { libraryViewQueries.library(AnimeMapper::mapLibraryAnime) } + return database.libraryViewQueries.library(AnimeMapper::mapLibraryAnime).subscribeToList() } override fun getFavoritesBySourceId(sourceId: Long): Flow> { - return handler.subscribeToList { animesQueries.getFavoriteBySourceId(sourceId, AnimeMapper::mapAnime) } + return database.animesQueries.getFavoriteBySourceId(sourceId, AnimeMapper::mapAnime).subscribeToList() } override suspend fun getDuplicateLibraryAnime(id: Long, title: String): List { - return handler.awaitList { - animesQueries.getDuplicateLibraryAnime(id, title, AnimeMapper::mapAnimeWithEpisodeCount) - } + return database.animesQueries.getDuplicateLibraryAnime( + id, + title, + AnimeMapper::mapAnimeWithEpisodeCount, + ).awaitAsList() } override suspend fun getUpcomingAnime(statuses: Set): Flow> { val epochMillis = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000 - return handler.subscribeToList { - animesQueries.getUpcomingAnime(epochMillis, statuses, AnimeMapper::mapAnime) - } + return database.animesQueries.getUpcomingAnime(epochMillis, statuses, AnimeMapper::mapAnime) + .subscribeToList() } override suspend fun resetViewerFlags(): Boolean { return try { - handler.await { animesQueries.resetViewerFlags() } + database.animesQueries.resetViewerFlags() true } catch (e: Exception) { logcat(LogPriority.ERROR, e) @@ -93,10 +96,10 @@ class AnimeRepositoryImpl( } override suspend fun setAnimeCategories(animeId: Long, categoryIds: List) { - handler.await(inTransaction = true) { - animes_categoriesQueries.deleteAnimeCategoryByAnimeId(animeId) - categoryIds.map { categoryId -> - animes_categoriesQueries.insert(animeId, categoryId) + database.transaction { + database.animes_categoriesQueries.deleteAnimeCategoryByAnimeId(animeId) + categoryIds.forEach { categoryId -> + database.animes_categoriesQueries.insert(animeId, categoryId) } } } @@ -122,9 +125,9 @@ class AnimeRepositoryImpl( } override suspend fun insertNetworkAnime(anime: List): List { - return handler.await(inTransaction = true) { + return database.transactionWithResult { anime.map { - animesQueries.insertNetworkAnime( + database.animesQueries.insertNetworkAnime( source = it.source, url = it.url, artist = it.artist, @@ -163,45 +166,45 @@ class AnimeRepositoryImpl( updateDetails = it.initialized, mapper = AnimeMapper::mapAnime, ) - .executeAsOne() + .awaitAsOne() } } } // AY --> override suspend fun getAnimeSeasonsById(parentId: Long): List { - return handler.awaitList { animeseasonsViewQueries.getAnimeSeasonsById(parentId, AnimeMapper::mapSeasonAnime) } + return database.animeseasonsViewQueries.getAnimeSeasonsById(parentId, AnimeMapper::mapSeasonAnime).awaitAsList() } override fun getAnimeSeasonsByIdAsFlow(parentId: Long): Flow> { - return handler.subscribeToList { - animeseasonsViewQueries.getAnimeSeasonsById(parentId, AnimeMapper::mapSeasonAnime) - } + return database.animeseasonsViewQueries.getAnimeSeasonsById( + parentId, + AnimeMapper::mapSeasonAnime, + ).subscribeToList() } override suspend fun removeParentIdByIds(animeIds: List) { try { - handler.await { animesQueries.removeParentIdByIds(animeIds) } + database.animesQueries.removeParentIdByIds(animeIds) } catch (e: Exception) { logcat(LogPriority.ERROR, e) } } override fun getDeletableParentAnime(): Flow> { - return handler.subscribeToList { - animedeletableViewQueries.getDeletableParentAnime(AnimeMapper::mapDeletableAnime) - } + return database.animedeletableViewQueries.getDeletableParentAnime(AnimeMapper::mapDeletableAnime) + .subscribeToList() } override suspend fun getChildrenByParentId(parentId: Long): List { - return handler.awaitList { animesQueries.getChildrenByParentId(parentId, AnimeMapper::mapAnime) } + return database.animesQueries.getChildrenByParentId(parentId, AnimeMapper::mapAnime).awaitAsList() } // <-- AY private suspend fun partialUpdate(vararg animeUpdates: AnimeUpdate) { - handler.await(inTransaction = true) { + database.transaction { animeUpdates.forEach { value -> - animesQueries.update( + database.animesQueries.update( source = value.source, url = value.url, artist = value.artist, diff --git a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt index 6c5dcf7b40..9a9ff278b2 100644 --- a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt @@ -1,88 +1,66 @@ package tachiyomi.data.category +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import tachiyomi.data.Database -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.subscribeToList import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.repository.CategoryRepository class CategoryRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : CategoryRepository { override suspend fun get(id: Long): Category? { - return handler.awaitOneOrNull { categoriesQueries.getCategory(id, ::mapCategory) } + return database.categoriesQueries.getCategory(id, ::mapCategory).awaitAsOneOrNull() } override suspend fun getAll(): List { - return handler.awaitList { categoriesQueries.getCategories(::mapCategory) } + return database.categoriesQueries.getCategories(::mapCategory).awaitAsList() } override fun getAllAsFlow(): Flow> { - return handler.subscribeToList { categoriesQueries.getCategories(::mapCategory) } + return database.categoriesQueries.getCategories(::mapCategory).subscribeToList() } override suspend fun getCategoriesByAnimeId(animeId: Long): List { - return handler.awaitList { - categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory) - } + return database.categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory).awaitAsList() } override fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow> { - return handler.subscribeToList { - categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory) - } + return database.categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory).subscribeToList() } // AY --> override suspend fun getAllVisible(): List { - return handler.awaitList { categoriesQueries.getVisibleCategories(::mapCategory) } + return database.categoriesQueries.getVisibleCategories(::mapCategory).awaitAsList() } override fun getAllVisibleAsFlow(): Flow> { - return handler.subscribeToList { categoriesQueries.getVisibleCategories(::mapCategory) } + return database.categoriesQueries.getVisibleCategories(::mapCategory).subscribeToList() } override suspend fun getVisibleCategoriesByAnimeId(animeId: Long): List { - return handler.awaitList { - categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory) - } + return database.categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory).awaitAsList() } override fun getVisibleCategoriesByAnimeIdAsFlow(animeId: Long): Flow> { - return handler.subscribeToList { - categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory) - } + return database.categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory).subscribeToList() } // <-- AY override suspend fun insert(category: Category) { - handler.await { - categoriesQueries.insert( - name = category.name, - order = category.order, - flags = category.flags, - ) - } + database.categoriesQueries.insert( + name = category.name, + order = category.order, + flags = category.flags, + ) } override suspend fun updatePartial(update: CategoryUpdate) { - handler.await { - updatePartialBlocking(update) - } - } - - override suspend fun updatePartial(updates: List) { - handler.await(inTransaction = true) { - for (update in updates) { - updatePartialBlocking(update) - } - } - } - - private fun Database.updatePartialBlocking(update: CategoryUpdate) { - categoriesQueries.update( + database.categoriesQueries.update( name = update.name, order = update.order, flags = update.flags, @@ -93,18 +71,18 @@ class CategoryRepositoryImpl( ) } - override suspend fun updateAllFlags(flags: Long?) { - handler.await { - categoriesQueries.updateAllFlags(flags) + override suspend fun updatePartial(updates: List) { + database.transaction { + updates.forEach { updatePartial(it) } } } + override suspend fun updateAllFlags(flags: Long?) { + database.categoriesQueries.updateAllFlags(flags) + } + override suspend fun delete(categoryId: Long) { - handler.await { - categoriesQueries.delete( - categoryId = categoryId, - ) - } + database.categoriesQueries.delete(categoryId = categoryId) } private fun mapCategory( diff --git a/data/src/main/java/tachiyomi/data/custombutton/CustomButtonRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/custombutton/CustomButtonRepositoryImpl.kt index 270b0baae4..fb763eb7f7 100644 --- a/data/src/main/java/tachiyomi/data/custombutton/CustomButtonRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/custombutton/CustomButtonRepositoryImpl.kt @@ -2,23 +2,24 @@ package tachiyomi.data.custombutton import android.database.sqlite.SQLiteException +import app.cash.sqldelight.async.coroutines.awaitAsList import kotlinx.coroutines.flow.Flow import tachiyomi.data.Database -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.subscribeToList import tachiyomi.domain.custombutton.exception.SaveCustomButtonException import tachiyomi.domain.custombutton.model.CustomButton import tachiyomi.domain.custombutton.model.CustomButtonUpdate import tachiyomi.domain.custombutton.repository.CustomButtonRepository class CustomButtonRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : CustomButtonRepository { override fun subscribeAll(): Flow> { - return handler.subscribeToList { custom_buttonsQueries.findAll(::mapCustomButton) } + return database.custom_buttonsQueries.findAll(::mapCustomButton).subscribeToList() } override suspend fun getAll(): List { - return handler.awaitList { custom_buttonsQueries.findAll(::mapCustomButton) } + return database.custom_buttonsQueries.findAll(::mapCustomButton).awaitAsList() } override suspend fun insertCustomButton( @@ -29,32 +30,14 @@ class CustomButtonRepositoryImpl( onStartup: String, ) { try { - handler.await { custom_buttonsQueries.insert(name, false, sortIndex, content, longPressContent, onStartup) } + database.custom_buttonsQueries.insert(name, false, sortIndex, content, longPressContent, onStartup) } catch (ex: SQLiteException) { throw SaveCustomButtonException(ex) } } override suspend fun updatePartialCustomButton(update: CustomButtonUpdate) { - handler.await { - updatePartialBlocking(update) - } - } - - override suspend fun updatePartialCustomButtons(updates: List) { - handler.await(inTransaction = true) { - for (update in updates) { - updatePartialBlocking(update) - } - } - } - - override suspend fun deleteCustomButton(customButtonId: Long) { - return handler.await { custom_buttonsQueries.delete(customButtonId) } - } - - private fun Database.updatePartialBlocking(update: CustomButtonUpdate) { - custom_buttonsQueries.update( + database.custom_buttonsQueries.update( name = update.name, isFavorite = update.isFavorite, sortIndex = update.sortIndex, @@ -65,6 +48,16 @@ class CustomButtonRepositoryImpl( ) } + override suspend fun updatePartialCustomButtons(updates: List) { + database.transaction { + updates.forEach { updatePartialCustomButton(it) } + } + } + + override suspend fun deleteCustomButton(customButtonId: Long) { + database.custom_buttonsQueries.delete(customButtonId) + } + private fun mapCustomButton( id: Long, name: String, diff --git a/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt index f9e76889ba..916b02f79c 100644 --- a/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt @@ -1,23 +1,27 @@ package tachiyomi.data.episode +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.lang.toLong import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.episode.model.Episode import tachiyomi.domain.episode.model.EpisodeUpdate import tachiyomi.domain.episode.repository.EpisodeRepository class EpisodeRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : EpisodeRepository { override suspend fun addAll(episodes: List): List { return try { - handler.await(inTransaction = true) { + database.transactionWithResult { episodes.map { episode -> - episodesQueries.insert( + val lastInsertId = database.episodesQueries.insert( episode.animeId, episode.url, episode.name, @@ -40,8 +44,7 @@ class EpisodeRepositoryImpl( episode.summary, episode.previewUrl, // <-- AY - ) - val lastInsertId = episodesQueries.selectLastInsertedRowId().executeAsOne() + ).awaitAsOne() episode.copy(id = lastInsertId) } } @@ -60,9 +63,9 @@ class EpisodeRepositoryImpl( } private suspend fun partialUpdate(vararg episodeUpdates: EpisodeUpdate) { - handler.await(inTransaction = true) { + database.transaction { episodeUpdates.forEach { episodeUpdate -> - episodesQueries.update( + database.episodesQueries.update( animeId = episodeUpdate.animeId, url = episodeUpdate.url, name = episodeUpdate.name, @@ -94,59 +97,56 @@ class EpisodeRepositoryImpl( override suspend fun removeEpisodesWithIds(episodeIds: List) { try { - handler.await { episodesQueries.removeEpisodesWithIds(episodeIds) } + database.episodesQueries.removeEpisodesWithIds(episodeIds) } catch (e: Exception) { logcat(LogPriority.ERROR, e) } } override suspend fun getEpisodeByAnimeId(animeId: Long, applyScanlatorFilter: Boolean): List { - return handler.awaitList { - episodesQueries.getEpisodesByAnimeId(animeId, applyScanlatorFilter.toLong(), ::mapEpisode) - } + return database.episodesQueries.getEpisodesByAnimeId( + animeId, + applyScanlatorFilter.toLong(), + ::mapEpisode, + ).awaitAsList() } override suspend fun getScanlatorsByAnimeId(animeId: Long): List { - return handler.awaitList { - episodesQueries.getScanlatorsByAnimeId(animeId) { it.orEmpty() } - } + return database.episodesQueries.getScanlatorsByAnimeId(animeId) { it.orEmpty() }.awaitAsList() } override fun getScanlatorsByAnimeIdAsFlow(animeId: Long): Flow> { - return handler.subscribeToList { - episodesQueries.getScanlatorsByAnimeId(animeId) { it.orEmpty() } - } + return database.episodesQueries.getScanlatorsByAnimeId(animeId) { it.orEmpty() }.subscribeToList() } override suspend fun getBookmarkedEpisodesByAnimeId(animeId: Long): List { - return handler.awaitList { - episodesQueries.getBookmarkedEpisodesByAnimeId( - animeId, - ::mapEpisode, - ) - } + return database.episodesQueries.getBookmarkedEpisodesByAnimeId( + animeId, + ::mapEpisode, + ).awaitAsList() } override suspend fun getEpisodeById(id: Long): Episode? { - return handler.awaitOneOrNull { episodesQueries.getEpisodeById(id, ::mapEpisode) } + return database.episodesQueries.getEpisodeById(id, ::mapEpisode).awaitAsOneOrNull() } override suspend fun getEpisodeByAnimeIdAsFlow(animeId: Long, applyScanlatorFilter: Boolean): Flow> { - return handler.subscribeToList { - episodesQueries.getEpisodesByAnimeId(animeId, applyScanlatorFilter.toLong(), ::mapEpisode) - } + return database.episodesQueries.getEpisodesByAnimeId( + animeId, + applyScanlatorFilter.toLong(), + ::mapEpisode, + ).subscribeToList() } override suspend fun getEpisodeByUrlAndAnimeId(url: String, animeId: Long): Episode? { - return handler.awaitOneOrNull { - episodesQueries.getEpisodeByUrlAndAnimeId( - url, - animeId, - ::mapEpisode, - ) - } + return database.episodesQueries.getEpisodeByUrlAndAnimeId( + url, + animeId, + ::mapEpisode, + ).awaitAsOneOrNull() } + @Suppress("UNUSED_PARAMETER") private fun mapEpisode( id: Long, animeId: Long, @@ -168,7 +168,6 @@ class EpisodeRepositoryImpl( dateUpload: Long, lastModifiedAt: Long, version: Long, - @Suppress("UNUSED_PARAMETER") isSyncing: Long, // AY --> summary: String?, diff --git a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt index 17622f4784..7b82437297 100644 --- a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt @@ -1,37 +1,37 @@ package tachiyomi.data.history +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.history.model.History import tachiyomi.domain.history.model.HistoryUpdate import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.repository.HistoryRepository class HistoryRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : HistoryRepository { override fun getHistory(query: String): Flow> { - return handler.subscribeToList { - historyViewQueries.history(query, HistoryMapper::mapHistoryWithRelations) - } + return database.historyViewQueries.history(query, HistoryMapper::mapHistoryWithRelations).subscribeToList() } override suspend fun getLastHistory(): HistoryWithRelations? { - return handler.awaitOneOrNull { - historyViewQueries.getLatestHistory(HistoryMapper::mapHistoryWithRelations) - } + return database.historyViewQueries.getLatestHistory(HistoryMapper::mapHistoryWithRelations) + .awaitAsOneOrNull() } override suspend fun getHistoryByAnimeId(animeId: Long): List { - return handler.awaitList { historyQueries.getHistoryByAnimeId(animeId, HistoryMapper::mapHistory) } + return database.historyQueries.getHistoryByAnimeId(animeId, HistoryMapper::mapHistory).awaitAsList() } override suspend fun resetHistory(historyId: Long) { try { - handler.await { historyQueries.resetHistoryById(historyId) } + database.historyQueries.resetHistoryById(historyId) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } @@ -39,7 +39,7 @@ class HistoryRepositoryImpl( override suspend fun resetHistoryByAnimeId(animeId: Long) { try { - handler.await { historyQueries.resetHistoryByAnimeId(animeId) } + database.historyQueries.resetHistoryByAnimeId(animeId) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } @@ -47,7 +47,7 @@ class HistoryRepositoryImpl( override suspend fun deleteAllHistory(): Boolean { return try { - handler.await { historyQueries.removeAllHistory() } + database.historyQueries.removeAllHistory() true } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) @@ -57,12 +57,10 @@ class HistoryRepositoryImpl( override suspend fun upsertHistory(historyUpdate: HistoryUpdate) { try { - handler.await { - historyQueries.upsert( - historyUpdate.episodeId, - historyUpdate.seenAt, - ) - } + database.historyQueries.upsert( + historyUpdate.episodeId, + historyUpdate.seenAt, + ) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } diff --git a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt index b1f7119de3..90c7a4aea3 100644 --- a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt @@ -7,7 +7,8 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.SourcePagingSource import tachiyomi.domain.source.repository.SourceRepository @@ -16,7 +17,7 @@ import tachiyomi.domain.source.model.Source as DomainSource class SourceRepositoryImpl( private val sourceManager: SourceManager, - private val handler: DatabaseHandler, + private val database: Database, ) : SourceRepository { override fun getSources(): Flow> { @@ -38,10 +39,13 @@ class SourceRepositoryImpl( } override fun getSourcesWithFavoriteCount(): Flow>> { - return combine( - handler.subscribeToList { animesQueries.getSourceIdWithFavoriteCount() }, - sourceManager.catalogueSources, - ) { sourceIdWithFavoriteCount, _ -> sourceIdWithFavoriteCount } + val sourceIdWithFavoriteCountFlow = database.animesQueries + .getSourceIdWithFavoriteCount() + .subscribeToList() + + return combine(sourceIdWithFavoriteCountFlow, sourceManager.catalogueSources) { sourceIdWithFavoriteCount, _ -> + sourceIdWithFavoriteCount + } .map { it.map { (sourceId, count) -> val source = sourceManager.getOrStub(sourceId) diff --git a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt index 157e1eb2f1..2c49573dfb 100644 --- a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt @@ -1,24 +1,26 @@ package tachiyomi.data.source +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.StubSourceRepository class StubSourceRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : StubSourceRepository { override fun subscribeAll(): Flow> { - return handler.subscribeToList { sourcesQueries.findAll(::mapStubSource) } + return database.sourcesQueries.findAll(::mapStubSource).subscribeToList() } override suspend fun getStubSource(id: Long): StubSource? { - return handler.awaitOneOrNull { sourcesQueries.findOne(id, ::mapStubSource) } + return database.sourcesQueries.findOne(id, ::mapStubSource).awaitAsOneOrNull() } override suspend fun upsertStubSource(id: Long, lang: String, name: String) { - handler.await { sourcesQueries.upsert(id, lang, name) } + database.sourcesQueries.upsert(id, lang, name) } private fun mapStubSource( diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index 81bdd5dc47..5726a8a951 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -1,44 +1,41 @@ package tachiyomi.data.track +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.data.track.TrackMapper.mapTrack import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.repository.TrackRepository class TrackRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : TrackRepository { override suspend fun getTrackById(id: Long): Track? { - return handler.awaitOneOrNull { anime_syncQueries.getTrackById(id, TrackMapper::mapTrack) } + return database.anime_syncQueries.getTrackById(id, TrackMapper::mapTrack).awaitAsOneOrNull() } override suspend fun getTracksByAnimeId(animeId: Long): List { - return handler.awaitList { - anime_syncQueries.getTracksByAnimeId(animeId, TrackMapper::mapTrack) - } + return database.anime_syncQueries.getTracksByAnimeId(animeId, TrackMapper::mapTrack).awaitAsList() } override fun getTracksAsFlow(): Flow> { - return handler.subscribeToList { - anime_syncQueries.getTracks(TrackMapper::mapTrack) - } + return database.anime_syncQueries.getTracks(TrackMapper::mapTrack) + .subscribeToList() } override fun getTracksByAnimeIdAsFlow(animeId: Long): Flow> { - return handler.subscribeToList { - anime_syncQueries.getTracksByAnimeId(animeId, TrackMapper::mapTrack) - } + return database.anime_syncQueries.getTracksByAnimeId(animeId, TrackMapper::mapTrack) + .subscribeToList() } override suspend fun delete(animeId: Long, trackerId: Long) { - handler.await { - anime_syncQueries.delete( - animeId = animeId, - syncId = trackerId, - ) - } + database.anime_syncQueries.delete( + animeId = animeId, + syncId = trackerId, + ) } override suspend fun insert(track: Track) { @@ -50,9 +47,9 @@ class TrackRepositoryImpl( } private suspend fun insertValues(vararg tracks: Track) { - handler.await(inTransaction = true) { + database.transaction { tracks.forEach { animeTrack -> - anime_syncQueries.insert( + database.anime_syncQueries.insert( animeId = animeTrack.animeId, syncId = animeTrack.trackerId, remoteId = animeTrack.remoteId, @@ -73,9 +70,8 @@ class TrackRepositoryImpl( // AM (GROUPING) --> override suspend fun getTracks(): List { - return handler.awaitList { - anime_syncQueries.getTracks(::mapTrack) - } + return database.anime_syncQueries.getTracks(::mapTrack) + .awaitAsList() } // <-- AM (GROUPING) } diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index 9c5eaabe88..258c5393bd 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -1,14 +1,16 @@ package tachiyomi.data.updates +import app.cash.sqldelight.async.coroutines.awaitAsList import kotlinx.coroutines.flow.Flow import tachiyomi.core.common.util.lang.toLong -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.anime.model.AnimeCover import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.repository.UpdatesRepository class UpdatesRepositoryImpl( - private val databaseHandler: DatabaseHandler, + private val database: Database, ) : UpdatesRepository { override suspend fun awaitWithSeen( @@ -16,14 +18,12 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): List { - return databaseHandler.awaitList { - updatesViewQueries.getUpdatesBySeenStatus( - seen = seen, - after = after, - limit = limit, - mapper = ::mapUpdatesWithRelations, - ) - } + return database.updatesViewQueries.getUpdatesBySeenStatus( + seen = seen, + after = after, + limit = limit, + mapper = ::mapUpdatesWithRelations, + ).awaitAsList() } override fun subscribeAll( @@ -35,19 +35,17 @@ class UpdatesRepositoryImpl( fillermarked: Boolean?, hideExcludedScanlators: Boolean, ): Flow> { - return databaseHandler.subscribeToList { - updatesViewQueries.getRecentUpdatesWithFilters( - after = after, - limit = limit, - // invert because unseen in Kotlin -> seen column in SQL - seen = unseen?.let { !it }, - started = started?.toLong(), - bookmarked = bookmarked, - fillermarked = fillermarked, - hideExcludedScanlators = hideExcludedScanlators.toLong(), - mapper = ::mapUpdatesWithRelations, - ) - } + return database.updatesViewQueries.getRecentUpdatesWithFilters( + after = after, + limit = limit, + // invert because unseen in Kotlin -> seen column in SQL + seen = unseen?.let { !it }, + started = started?.toLong(), + bookmarked = bookmarked, + fillermarked = fillermarked, + hideExcludedScanlators = hideExcludedScanlators.toLong(), + mapper = ::mapUpdatesWithRelations, + ).subscribeToList() } override fun subscribeWithSeen( @@ -55,16 +53,16 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): Flow> { - return databaseHandler.subscribeToList { - updatesViewQueries.getUpdatesBySeenStatus( - seen = seen, - after = after, - limit = limit, - mapper = ::mapUpdatesWithRelations, - ) - } + return database.updatesViewQueries.getUpdatesBySeenStatus( + seen = seen, + after = after, + limit = limit, + mapper = ::mapUpdatesWithRelations, + ) + .subscribeToList() } + @Suppress("UNUSED_PARAMETER") private fun mapUpdatesWithRelations( animeId: Long, animeTitle: String, diff --git a/data/src/main/sqldelight/tachiyomi/data/animes.sq b/data/src/main/sqldelight/tachiyomi/data/animes.sq index 403ed1595a..0374c83980 100644 --- a/data/src/main/sqldelight/tachiyomi/data/animes.sq +++ b/data/src/main/sqldelight/tachiyomi/data/animes.sq @@ -213,9 +213,12 @@ AND ( ) ); -insert: -INSERT INTO animes(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, episode_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes, fetch_type, parent_id, season_flags, season_number, season_source_order, background_url, background_last_modified) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :episodeFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes, :fetchType, :parentId, :seasonFlags, :seasonNumber, :seasonSourceOrder, :backgroundUrl, :backgroundLastModified); +insert { + INSERT INTO animes(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, episode_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes, fetch_type, parent_id, season_flags, season_number, season_source_order, background_url, background_last_modified) + VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :episodeFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes, :fetchType, :parentId, :seasonFlags, :seasonNumber, :seasonSourceOrder, :backgroundUrl, :backgroundLastModified); + + SELECT last_insert_rowid(); +} update: UPDATE animes SET @@ -252,9 +255,6 @@ UPDATE animes SET -- <-- AY WHERE _id = :animeId; -selectLastInsertedRowId: -SELECT last_insert_rowid(); - insertNetworkAnime { -- Insert the anime if it doesn't exist already INSERT INTO animes( diff --git a/data/src/main/sqldelight/tachiyomi/data/categories.sq b/data/src/main/sqldelight/tachiyomi/data/categories.sq index 733905e7dc..47bf9c61b1 100644 --- a/data/src/main/sqldelight/tachiyomi/data/categories.sq +++ b/data/src/main/sqldelight/tachiyomi/data/categories.sq @@ -78,9 +78,12 @@ ON C._id = AC.category_id WHERE AC.anime_id = :animeId AND C.hidden = 0; -- <-- AY -insert: -INSERT INTO categories(name, sort, flags) -VALUES (:name, :order, :flags); +insert { + INSERT INTO categories(name, sort, flags) + VALUES (:name, :order, :flags); + + SELECT last_insert_rowid(); +} delete: DELETE FROM categories @@ -99,6 +102,3 @@ WHERE _id = :categoryId; updateAllFlags: UPDATE categories SET flags = coalesce(?, flags); - -selectLastInsertedRowId: -SELECT last_insert_rowid(); diff --git a/data/src/main/sqldelight/tachiyomi/data/custom_buttons.sq b/data/src/main/sqldelight/tachiyomi/data/custom_buttons.sq index 63b7212098..42bc236b5b 100644 --- a/data/src/main/sqldelight/tachiyomi/data/custom_buttons.sq +++ b/data/src/main/sqldelight/tachiyomi/data/custom_buttons.sq @@ -35,9 +35,12 @@ SELECT * FROM custom_buttons ORDER BY sortIndex; -insert: -INSERT INTO custom_buttons(name, isFavorite, sortIndex, content, longPressContent, onStartup) -VALUES (:name, :isFavorite, :sortIndex, :content, :longPressContent, :onStartup); +insert { + INSERT INTO custom_buttons(name, isFavorite, sortIndex, content, longPressContent, onStartup) + VALUES (:name, :isFavorite, :sortIndex, :content, :longPressContent, :onStartup); + + SELECT last_insert_rowid(); +} delete: DELETE FROM custom_buttons @@ -52,7 +55,4 @@ SET name = coalesce(:name, name), longPressContent = coalesce(:longPressContent, longPressContent), onStartup = coalesce(:onStartup, onStartup) WHERE _id = :customButtonId; - -selectLastInsertedRowId: -SELECT last_insert_rowid(); -- <-- AY diff --git a/data/src/main/sqldelight/tachiyomi/data/episodes.sq b/data/src/main/sqldelight/tachiyomi/data/episodes.sq index c2dd9e23ca..d98f168c86 100644 --- a/data/src/main/sqldelight/tachiyomi/data/episodes.sq +++ b/data/src/main/sqldelight/tachiyomi/data/episodes.sq @@ -107,9 +107,12 @@ UPDATE episodes SET is_syncing = 0 WHERE is_syncing = 1; -insert: -INSERT INTO episodes(anime_id, url, name, scanlator, seen, bookmark, fillermark, last_second_seen, total_seconds, episode_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, summary, preview_url) -VALUES (:animeId, :url, :name, :scanlator, :seen, :bookmark, :fillermark, :lastSecondSeen, :totalSeconds, :episodeNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :summary, :previewUrl); +insert { + INSERT INTO episodes(anime_id, url, name, scanlator, seen, bookmark, fillermark, last_second_seen, total_seconds, episode_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, summary, preview_url) + VALUES (:animeId, :url, :name, :scanlator, :seen, :bookmark, :fillermark, :lastSecondSeen, :totalSeconds, :episodeNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :summary, :previewUrl); + + SELECT last_insert_rowid(); +} update: UPDATE episodes @@ -137,6 +140,3 @@ SET anime_id = coalesce(:animeId, anime_id), preview_url = coalesce(:previewUrl, preview_url) -- <-- AY WHERE _id = :episodeId; - -selectLastInsertedRowId: -SELECT last_insert_rowid(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3aa44b4832..88ed06f504 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ rxJava = "1.3.8" shizuku = "13.1.5" spotless = "8.4.0" sqldelight = "2.3.2" -sqldelight-androidx-driver = "0.0.17" +sqldelight-androidx-driver = "0.1.1" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" @@ -169,7 +169,8 @@ shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizu spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } sqldelight-androidxDriver = { module = "com.eygraber:sqldelight-androidx-driver", version.ref = "sqldelight-androidx-driver" } sqldelight-androidxPaging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } -sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } +sqldelight-async = { module = "app.cash.sqldelight:async-extensions", version.ref = "sqldelight" } +sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqldelight-sqliteDialect338 = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version.ref = "stringSimilarity" } subsamplingScaleImageView = { module = "com.github.tachiyomiorg:subsampling-scale-image-view", version.ref = "subsamplingScaleImageView" } @@ -208,6 +209,6 @@ markdown = ["markdown-core", "markdown-coil"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsOverHttps"] serialization = ["kotlinx-serialization-json", "kotlinx-serialization-jsonOkio", "kotlinx-serialization-protobuf", "xmlutil-core", "xmlutil-serialization"] shizuku = ["shizuku-api", "shizuku-provider"] -sqldelight = ["sqldelight-androidxDriver", "sqldelight-coroutines", "sqldelight-androidxPaging"] +sqldelight = ["sqldelight-androidxDriver", "sqldelight-async", "sqldelight-coroutines", "sqldelight-androidxPaging"] test = ["junit-jupiter", "kotest-assertions", "mockk"] voyager = ["voyager-navigator", "voyager-screenModel", "voyager-tabNavigator", "voyager-transitions"] From b35cf6e455195d5b2dcb9e7c4598775439ec8ad2 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 28 Apr 2026 18:30:39 +0100 Subject: [PATCH 43/59] Update Gradle to v9.5.0 (#3274) (cherry picked from commit 9678b8fd796dd3998db1a651bbe7e2101da54754) --- gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++- gradlew | 2 +- gradlew.bat | 31 ++++++++--------------- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From d2feadcff2f7ae43eac8e9311c940da3ae15adea Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 6 May 2026 12:50:41 +0100 Subject: [PATCH 44/59] Update android.gradle to v9.2.1 (#3294) (cherry picked from commit 5b8adfbdb03250daab7ac32a4a6af014fef02eee) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 88ed06f504..94704b6878 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] aboutLibraries = "14.0.1" android-desugar = "2.1.5" -android-gradle = "9.2.0" +android-gradle = "9.2.1" androidx-activity-compose = "1.13.0" androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" From b0e1a6984594013a53cc5d3e56d3b7e84d79e046 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 6 May 2026 13:01:53 +0100 Subject: [PATCH 45/59] Update moko.resources to v0.26.4 (#3297) (cherry picked from commit bb877e4b261412a73e7d0f003dc2223d8fe28292) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94704b6878..3a71908197 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ markdown = "0.39.2" material = "1.12.0" materialKolor = "5.0.0-alpha07" mockk = "1.14.9" -moko-resources = "0.26.3" +moko-resources = "0.26.4" okhttp = "5.3.2" okio = "3.17.0" photoView = "2.3.0" From 93d6f8d62f7ef9a948b8dbc1f612e70d073fed39 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 8 May 2026 17:39:25 +0100 Subject: [PATCH 46/59] Update kotlinx-coroutines monorepo to v1.11.0 (#3305) (cherry picked from commit 981b16094a80f33287aec8aa0cef34d97c57c4e5) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a71908197..7d7b8c9a75 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ junit = "6.0.3" kotest-assertions = "6.1.11" kotlin-gradle = "2.3.21" kotlinx-collections-immutable = "0.4.0" -kotlinx-coroutines = "1.10.2" +kotlinx-coroutines = "1.11.0" kotlinx-serialization = "1.11.0" ktlint = "1.8.0" leakCanary = "2.14" From dfd86193379ae8ff1957c273af70b89265aad596 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 8 May 2026 17:48:08 +0100 Subject: [PATCH 47/59] Update dependency androidx.compose:compose-bom-alpha to v2026.05.00 (#3302) (cherry picked from commit b65825b3ca035024ba6625157aa2ee3d34e1ff43) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d7b8c9a75..3da9ce18da 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" -androidx-compose-bom = "2026.04.01" +androidx-compose-bom = "2026.05.00" androidx-constraintLayout = "2.2.1" androidx-core = "1.18.0" androidx-coreSplashScreen = "1.2.0" From 9d8bceef5884df47b88724b00c85c713b94112ec Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 8 May 2026 17:49:00 +0100 Subject: [PATCH 48/59] Update dependency com.google.firebase:firebase-bom to v34.13.0 (#3303) (cherry picked from commit cfefa51296299720b4db78f1ddc0303651b02750) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3da9ce18da..1d211076e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ composeWebview = "0.33.6" conscrypt = "2.5.3" directionalViewPager = "1.0.0" diskLruCache = "2.0.2" -firebase-bom = "34.12.0" +firebase-bom = "34.13.0" firebase-crashlytics = "3.0.7" flexibleAdapter = "c8013533" google-services = "4.4.4" From 21d108655024e7711375e75dfc321e8d173a29d6 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 8 May 2026 17:52:17 +0100 Subject: [PATCH 49/59] Update aboutLibraries to v14.2.0 (#3283) (cherry picked from commit 09d3e16183e1a21c4815b6a243373a48b5f2598d) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d211076e4..18f3e31107 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutLibraries = "14.0.1" +aboutLibraries = "14.2.0" android-desugar = "2.1.5" android-gradle = "9.2.1" androidx-activity-compose = "1.13.0" From 2bf18b4899cf4a2f927be40e24c908ba7bb3baa5 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 8 May 2026 17:53:11 +0100 Subject: [PATCH 50/59] Update dependency com.eygraber:sqldelight-androidx-driver to v0.2.0 (#3292) (cherry picked from commit da9b5d9b9dd1af6bfc27f0d6697b388a91970433) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18f3e31107..5b32932850 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ rxJava = "1.3.8" shizuku = "13.1.5" spotless = "8.4.0" sqldelight = "2.3.2" -sqldelight-androidx-driver = "0.1.1" +sqldelight-androidx-driver = "0.2.0" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" From f8f60659ec2eaa65097e61a62ec5aad8b7adf5c2 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Mon, 11 May 2026 09:25:58 +0200 Subject: [PATCH 51/59] Remove redundant KGP classpath in :app Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9d1c0392f6..1aac9df567 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -348,9 +348,3 @@ androidComponents { it.packaging.resources.excludes.add("META-INF/*.version") } } - -buildscript { - dependencies { - classpath(libs.kotlin.gradle) - } -} From 98053c43a1f5959819b0e1db1356ab8ace76a88c Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 11 May 2026 20:29:31 +0100 Subject: [PATCH 52/59] Update dependency com.mohamedrejeb.richeditor:richeditor-compose to v1.0.0-rc14 (#3271) (cherry picked from commit cb2a0ea570b1baea59a373b76b23eb25d78ffb17) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b32932850..b8c7367a66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ archive = "1.1.6" coil = "3.4.0" composeGrid = "1.2.2" composeMaterialMotion = "2.0.1" -composeRichEditor = "1.0.0-rc13" +composeRichEditor = "1.0.0-rc14" composeWebview = "0.33.6" conscrypt = "2.5.3" directionalViewPager = "1.0.0" From ddf147ec64bb2f13b10da97255b16c089cc27fa1 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Wed, 27 May 2026 07:59:14 +0200 Subject: [PATCH 53/59] Fix chapters and categories not being added to database Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../data/backup/restore/restorers/AnimeRestorer.kt | 2 +- .../backup/restore/restorers/CategoriesRestorer.kt | 1 - .../data/episode/EpisodeRepositoryImpl.kt | 7 ++++--- data/src/main/sqldelight/tachiyomi/data/animes.sq | 10 ++++------ .../main/sqldelight/tachiyomi/data/categories.sq | 9 +++------ .../src/main/sqldelight/tachiyomi/data/episodes.sq | 14 ++++++++------ 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt index 096cf3bc85..a03fdc598a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/AnimeRestorer.kt @@ -311,7 +311,7 @@ class AnimeRestorer( * @return id of [Anime], null if not found */ private suspend fun insertAnime(anime: Anime): Long { - return database.animesQueries.insert( + return database.animesQueries.insertReturningId( source = anime.source, url = anime.url, artist = anime.artist, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index 8300e25610..9c6cb7307c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -27,7 +27,6 @@ class CategoriesRestorer( if (dbCategory != null) return@map dbCategory val order = nextOrder++ database.categoriesQueries.insert(it.name, order, it.flags) - .awaitAsOne() .let { id -> it.toCategory(id).copy(order = order) } } diff --git a/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt index 916b02f79c..2c34373c14 100644 --- a/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/episode/EpisodeRepositoryImpl.kt @@ -21,7 +21,7 @@ class EpisodeRepositoryImpl( return try { database.transactionWithResult { episodes.map { episode -> - val lastInsertId = database.episodesQueries.insert( + val episodeId = database.episodesQueries.insertReturningId( episode.animeId, episode.url, episode.name, @@ -44,8 +44,9 @@ class EpisodeRepositoryImpl( episode.summary, episode.previewUrl, // <-- AY - ).awaitAsOne() - episode.copy(id = lastInsertId) + ) + .awaitAsOne() + episode.copy(id = episodeId) } } } catch (e: Exception) { diff --git a/data/src/main/sqldelight/tachiyomi/data/animes.sq b/data/src/main/sqldelight/tachiyomi/data/animes.sq index 0374c83980..c911e2e151 100644 --- a/data/src/main/sqldelight/tachiyomi/data/animes.sq +++ b/data/src/main/sqldelight/tachiyomi/data/animes.sq @@ -213,12 +213,10 @@ AND ( ) ); -insert { - INSERT INTO animes(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, episode_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes, fetch_type, parent_id, season_flags, season_number, season_source_order, background_url, background_last_modified) - VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :episodeFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes, :fetchType, :parentId, :seasonFlags, :seasonNumber, :seasonSourceOrder, :backgroundUrl, :backgroundLastModified); - - SELECT last_insert_rowid(); -} +insertReturningId: +INSERT INTO animes(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, episode_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes, fetch_type, parent_id, season_flags, season_number, season_source_order, background_url, background_last_modified) +VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :episodeFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes, :fetchType, :parentId, :seasonFlags, :seasonNumber, :seasonSourceOrder, :backgroundUrl, :backgroundLastModified) +RETURNING _id; update: UPDATE animes SET diff --git a/data/src/main/sqldelight/tachiyomi/data/categories.sq b/data/src/main/sqldelight/tachiyomi/data/categories.sq index 47bf9c61b1..f65d78fea6 100644 --- a/data/src/main/sqldelight/tachiyomi/data/categories.sq +++ b/data/src/main/sqldelight/tachiyomi/data/categories.sq @@ -78,12 +78,9 @@ ON C._id = AC.category_id WHERE AC.anime_id = :animeId AND C.hidden = 0; -- <-- AY -insert { - INSERT INTO categories(name, sort, flags) - VALUES (:name, :order, :flags); - - SELECT last_insert_rowid(); -} +insert: +INSERT INTO categories(name, sort, flags) +VALUES (:name, :order, :flags); delete: DELETE FROM categories diff --git a/data/src/main/sqldelight/tachiyomi/data/episodes.sq b/data/src/main/sqldelight/tachiyomi/data/episodes.sq index d98f168c86..c3d374c6ce 100644 --- a/data/src/main/sqldelight/tachiyomi/data/episodes.sq +++ b/data/src/main/sqldelight/tachiyomi/data/episodes.sq @@ -107,12 +107,14 @@ UPDATE episodes SET is_syncing = 0 WHERE is_syncing = 1; -insert { - INSERT INTO episodes(anime_id, url, name, scanlator, seen, bookmark, fillermark, last_second_seen, total_seconds, episode_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, summary, preview_url) - VALUES (:animeId, :url, :name, :scanlator, :seen, :bookmark, :fillermark, :lastSecondSeen, :totalSeconds, :episodeNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :summary, :previewUrl); - - SELECT last_insert_rowid(); -} +insert: +INSERT INTO episodes(anime_id, url, name, scanlator, seen, bookmark, fillermark, last_second_seen, total_seconds, episode_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, summary, preview_url) +VALUES (:animeId, :url, :name, :scanlator, :seen, :bookmark, :fillermark, :lastSecondSeen, :totalSeconds, :episodeNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :summary, :previewUrl); + +insertReturningId: +INSERT INTO episodes(anime_id, url, name, scanlator, seen, bookmark, fillermark, last_second_seen, total_seconds, episode_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, summary, preview_url) +VALUES (:animeId, :url, :name, :scanlator, :seen, :bookmark, :fillermark, :lastSecondSeen, :totalSeconds, :episodeNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :summary, :previewUrl) +RETURNING _id; update: UPDATE episodes From 39cda3b14e1a9d9c9ecc051ea78502b8256d4522 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 14 May 2026 17:33:12 +0100 Subject: [PATCH 54/59] Update Gradle to v9.5.1 (#3315) (cherry picked from commit 48179083f161b1971256d3fb2c60d736a917b26f) --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b52fb7e713..df6a6ad763 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-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From 85ca5bc8da79dd63fd2b9ea2ce11385fff52da41 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 22 May 2026 09:49:15 +0100 Subject: [PATCH 55/59] Update spotless to v8.5.1 (#3322) (cherry picked from commit 5bb92a75c72a50a9b4da46b20a13ecc693e926d4) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8c7367a66..d9909e69c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ quickJs = "547f5b1597" reorderable = "3.1.0" rxJava = "1.3.8" shizuku = "13.1.5" -spotless = "8.4.0" +spotless = "8.5.1" sqldelight = "2.3.2" sqldelight-androidx-driver = "0.2.0" stringSimilarity = "0.1.0" From 5507eaae09451a1d88f71fb68330c7dd55c501d4 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 22 May 2026 09:49:38 +0100 Subject: [PATCH 56/59] Update dependency org.junit.jupiter:junit-jupiter to v6.1.0 (#3335) (cherry picked from commit 3b94df6108d14f66b6be29bac676af02394c8e91) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9909e69c5..4ccd597acb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ google-services = "4.4.4" image-decoder = "e03b81e18a" injekt = "91edab2317" jsoup = "1.22.2" -junit = "6.0.3" +junit = "6.1.0" kotest-assertions = "6.1.11" kotlin-gradle = "2.3.21" kotlinx-collections-immutable = "0.4.0" From 63e7799ffa77ce3ac3f63960fe46eed343790c78 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 22 May 2026 09:50:44 +0100 Subject: [PATCH 57/59] Update dependency androidx.compose:compose-bom-alpha to v2026.05.01 (#3334) (cherry picked from commit 82338d7d64725d941f2edb8bc5e4988155869a48) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ccd597acb..e175ee76a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-annotation = "1.10.0" androidx-appCompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-biometric = "1.2.0-alpha05" -androidx-compose-bom = "2026.05.00" +androidx-compose-bom = "2026.05.01" androidx-constraintLayout = "2.2.1" androidx-core = "1.18.0" androidx-coreSplashScreen = "1.2.0" From b9e2439c8194848d538ef337dd49938cf57b6ccf Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 25 May 2026 09:15:12 +0100 Subject: [PATCH 58/59] Update aboutLibraries to v14.2.1 (#3343) (cherry picked from commit ae3aa7926ac36dd9b80ae5ec14de8be434e1baaa) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e175ee76a8..0d7089631d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutLibraries = "14.2.0" +aboutLibraries = "14.2.1" android-desugar = "2.1.5" android-gradle = "9.2.1" androidx-activity-compose = "1.13.0" From c4b74eab8b0537a59eba12c52b973754577152c8 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Wed, 27 May 2026 08:03:57 +0200 Subject: [PATCH 59/59] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e136af5640..bbfe0bc66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,8 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Added - Added system font fallback ([@Secozzi](https://github.com/Secozzi)) ([#156](https://github.com/quickdesh/Animiru/pull/156)) -### Fixed -- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) +### Other +- Merge from Mihon ([@Secozzi](https://github.com/Secozzi)) ([#155](https://github.com/quickdesh/Animiru/pull/155)) ## [v0.19.7.3] - 2026-04-17 ### Improved