Compare commits

..
45 Commits
Author SHA1 Message Date
AntsyLich bc4fc765b2 Release v0.19.8 2026-04-11 12:00:00 +06:00
253a560b20 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 <bartolumiu@tr25.es>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: ndeerova <ovandoerik@gmail.com>
2026-04-11 11:50:41 +06:00
Mend RenovateandAntsyLich eb3d2c6676 Update dependency com.google.firebase:firebase-bom to v34.12.0 (#3216)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-04-10 01:18:21 +00:00
Mend Renovate b1c6994977 Update dependency com.google.firebase.crashlytics to v3.0.7 (#3219) 2026-04-10 07:07:41 +06:00
Mend Renovate 2990762df9 Update aboutLibraries to v14.0.1 (#3217) 2026-04-10 07:06:37 +06:00
MajorTanya 8e6af6492b Fix AniList user list item fetch erroring (#3220)
The countryOfOrigin field was not included in the findLibManga query
so it is not returned by AniList. Defaulting to an empty string here
should be fine since neither countryOfOrigin nor format are shown in
the findLibManga consumers (i.e. the tracker sheet of a given library
entry).
2026-04-10 07:05:25 +06:00
AntsyLich c4a606112a Slightly tweak paragraph 3 of donation popup 2026-04-10 02:38:16 +06:00
+6 230dc6fd05 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 <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ram <abhyram@tutamail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Victoria Niedzielska <royal.victoria@proton.me>
Co-authored-by: ZenVinny <atdenada@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: on9686 <on9686@gmail.com>
2026-04-10 02:28:15 +06:00
AntsyLich e4b7002909 Add a one time popup asking for donation from long time users (#3203)
Also remove the empty "Updated to X.Y.Z" popup after app update
2026-04-09 20:08:20 +00:00
Mend Renovate 02c6427823 Update kotlinx.serialization to v1.11.0 (#3218) 2026-04-10 02:06:43 +06:00
AntsyLich 50b0ccb928 Add dedicated "Support Us" screen (#3200) 2026-04-09 19:56:24 +00:00
Mend Renovate 1712b9cdc5 Update dependency com.google.android.material:material to v1.13.0 (#3104) 2026-04-09 23:34:03 +06:00
AntsyLich b201ed163e Remove usage of deprecated LocalBroadcastManager (#3215) 2026-04-09 23:24:13 +06:00
Mend Renovate 3521ccfb4f Update moko.resources to v0.26.3 (#3213) 2026-04-09 22:58:44 +06:00
Mend Renovate b7cdfb1b30 Update aboutLibraries to v14 (major) (#3195)
Update aboutLibraries to v14
2026-04-09 10:26:25 +06:00
Mend Renovate 4b1ae0526b Update dependency androidx.compose:compose-bom-beta to v2026.04.00 (#3205) 2026-04-09 10:19:03 +06:00
Ananda Umamil 378e734eed Show AniList publishing type based on country of origin (#3100) 2026-04-09 01:45:59 +00:00
Mend Renovate ed3b5305f1 Update dependency androidx.annotation:annotation to v1.10.0 (#3204) 2026-04-08 20:38:00 +00:00
Leodyver SemillaandAntsyLich cfc69d8d0b Fix app trying to split long strip when not needed (#3121)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-04-08 20:27:44 +00:00
Mend Renovate 2a1859d7d7 Update moko.resources to v0.26.2 (#3202) 2026-04-08 09:41:04 +00:00
AntsyLich 8a2a2b1f36 Make "Support Us" more prominent on More tab (#3196) 2026-04-08 01:27:40 +06:00
Mend Renovate af04276bed Update dependency io.kotest:kotest-assertions-core to v6.1.11 (#3185) 2026-04-08 00:36:33 +06:00
ArthurKunandAntsyLich 25d4bf5e2f Add gradle.properties to build-logic synced with root gradle.properties (#3159)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-31 05:40:06 +06:00
MajorTanya d75db63258 Run tests before compiling APK in CI (#3174) 2026-03-30 23:17:26 +00:00
MajorTanya 52fc38e965 Run SQLDelight database migration verification task in CI (#3170) 2026-03-31 05:15:32 +06:00
Mend Renovate 580863e891 Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.1 (#3173) 2026-03-30 22:52:48 +00:00
Mend Renovate 85eb539c4d Update dependency io.kotest:kotest-assertions-core to v6.1.10 (#3172) 2026-03-31 04:42:10 +06:00
Leodyver SemillaandAntsyLich 09ee38bb7a Don't prompt users to enable Google Play services if disabled or unavailable (#3152)
* fix(telemetry): prevent Google Play Services spam notifications

Check for Google Play Services availability before initializing Firebase
to prevent system notifications when GPS is disabled or uninstalled.

- Add GPS availability check using GoogleApiAvailability
- Wrap Firebase initialization in try-catch for additional safety
- Skip Firebase initialization gracefully when GPS unavailable
- Add proper logging for debugging purposes
- Add core.common dependency to telemetry module for logcat support

Fixes #3135

* docs: add changelog entry for Google Play Services notification fix

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-26 09:35:16 +00:00
MajorTanya af4f17efc5 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.
2026-03-26 15:22:37 +06:00
Mend Renovate fa197a4ab2 Update dependency androidx.compose:compose-bom-beta to v2026.03.01 (#3147) 2026-03-26 14:36:15 +06:00
Mend Renovate c3243882b1 Update r0adkll/sign-android-release digest to a391126 (#3145) 2026-03-26 14:34:25 +06:00
Mend Renovate a938708e1f Update dependency androidx.work:work-runtime to v2.11.2 (#3146) 2026-03-26 14:34:09 +06:00
Christos 2e635ff2ce 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
2026-03-26 14:28:11 +06:00
AntsyLich 209805359e Merge tag 'v0.19.7' 2026-03-25 23:58:00 +06:00
AntsyLich eb5b762515 Revert "Update android.gradle to v9 (major) (#3105)"
This reverts commit 34afdabcfe.
2026-03-25 23:57:35 +06:00
Mend Renovate 7c5034275f Update dependency io.kotest:kotest-assertions-core to v6.1.9 (#3137) 2026-03-25 15:43:04 +06:00
Mend Renovate d75a125b26 Update dependency io.kotest:kotest-assertions-core to v6.1.8 (#3132) 2026-03-24 14:10:52 +06:00
AntsyLich 54d2c7b9e0 Release v0.19.7 2026-03-23 22:31:36 +06:00
MajorTanya 8b9d0a89c8 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.
2026-03-23 22:22:53 +06:00
Mend RenovateandAntsyLich 34afdabcfe Update android.gradle to v9 (major) (#3105)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-22 19:02:55 +06:00
MajorTanya bc32308327 Address CancellableContinuation.resume deprecation (#3115)
According to the source docs, this has been a warning since
kotlinx.coroutines 1.9.0.
2026-03-21 20:17:35 +00:00
Mend Renovate c95269afd7 Update Gradle to v9 (#2333) 2026-03-21 19:49:57 +00:00
AntsyLich 4af8f1eab0 Cleanup and rework build logic (#3113) 2026-03-22 01:34:45 +06:00
+2 2366fbb4b3 Translations update from Hosted Weblate (#3003)
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 <ogloppi@mailbox.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Kehribar <103407696+dpentx@users.noreply.github.com>
Co-authored-by: Lucas Correia <anicetoclucas@gmail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: lilp <felipegabriel.avila6@gmail.com>
Co-authored-by: nadevko <ormak@protonmail.com>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-03-22 00:39:16 +06:00
AntsyLich e930f38608 Merge and cleanup version catalogs (#3103) 2026-03-20 21:15:40 +06:00
179 changed files with 2737 additions and 1419 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to version **[0.19.6](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.8](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+2 -2
View File
@@ -52,7 +52,7 @@ body:
label: Mihon version
description: You can find your Mihon version in **More → About**.
placeholder: |
Example: "0.19.6"
Example: "0.19.8"
validations:
required: true
@@ -95,7 +95,7 @@ body:
required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.19.6](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.8](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
+6 -3
View File
@@ -44,13 +44,13 @@ jobs:
- name: Check code format
run: ./gradlew spotlessCheck
- name: Build app
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Run unit tests
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
@@ -58,6 +58,9 @@ jobs:
name: test-report-${{ github.sha }}
path: app/build/reports/tests/testReleaseUnitTest
- name: Build app
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Upload APK
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
+2 -2
View File
@@ -44,7 +44,7 @@ jobs:
run: ./gradlew assembleRelease -Pinclude-telemetry -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 }}
@@ -100,7 +100,7 @@ jobs:
run: ./gradlew assembleFoss -Penable-updater
- name: Sign APK
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
uses: r0adkll/sign-android-release@a391126403df92c6a0a3988a829dd47f79b89ed7
with:
releaseDirectory: app/build/outputs/apk/foss
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
+25 -1
View File
@@ -12,6 +12,28 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
## [Unreleased]
## [v0.19.8] - 2026-04-11
### Added
- Add dedicated "Support Us" screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3200](https://github.com/mihonapp/mihon/pull/3200))
- Add a one time popup asking for donation from long time users ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
### Changed
- Don't prompt users to enable Google Play services if disabled or unavailable ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3152](https://github.com/mihonapp/mihon/pull/3152))
- Make "Support Us" more prominent in More screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3196](https://github.com/mihonapp/mihon/pull/3196))
### Improved
- Show informative error when trying to add unapproved titles to list on MAL ([@MajorTanya](https://github.com/MajorTanya)) ([#3155](https://github.com/mihonapp/mihon/pull/3155))
- Show AniList publishing type based on country of origin ([@zweimach](https://github.com/zweimach)) ([#3100](https://github.com/mihonapp/mihon/pull/3100))
### Fixed
- Fix app trying to split long strip when not needed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3121](https://github.com/mihonapp/mihon/pull/3121))
### Removed
- Remove the empty "Updated to X.Y.Z" popup after app update ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
## [v0.19.7] - 2026-03-23
Same as v0.19.6
## [v0.19.6] - 2026-03-23
### Fixed
- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) ([`cb0e329`](https://github.com/mihonapp/mihon/commit/cb0e329))
@@ -502,7 +524,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.6...main
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.8...main
[v0.19.8]: https://github.com/mihonapp/mihon/compare/v0.19.7...v0.19.8
[v0.19.7]: https://github.com/mihonapp/mihon/compare/v0.19.6...v0.19.7
[v0.19.6]: https://github.com/mihonapp/mihon/compare/v0.19.5...v0.19.6
[v0.19.5]: https://github.com/mihonapp/mihon/compare/v0.19.4...v0.19.5
[v0.19.4]: https://github.com/mihonapp/mihon/compare/v0.19.3...v0.19.4
+75 -64
View File
@@ -1,14 +1,16 @@
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)
}
if (Config.includeTelemetry) {
@@ -18,20 +20,18 @@ if (Config.includeTelemetry) {
}
}
shortcutHelper.setFilePath("./shortcuts.xml")
android {
namespace = "eu.kanade.tachiyomi"
defaultConfig {
applicationId = "app.mihon"
versionCode = 19
versionName = "0.19.5"
versionCode = 21
versionName = "0.19.8"
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", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
@@ -41,7 +41,7 @@ android {
buildTypes {
val debug by getting {
applicationIdSuffix = ".dev"
versionNameSuffix = "-${getCommitCount()}"
versionNameSuffix = "-${getLatestCommitCount()}"
isPseudoLocalesEnabled = true
}
val release by getting {
@@ -50,7 +50,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)
@@ -72,7 +72,7 @@ android {
matchingFallbacks.addAll(commonMatchingFallbacks)
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
}
create("benchmark") {
initWith(release)
@@ -185,91 +185,89 @@ dependencies {
implementation(projects.telemetry)
// 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(androidx.corektx)
implementation(androidx.splashscreen)
implementation(androidx.recyclerview)
implementation(androidx.viewpager)
implementation(androidx.profileinstaller)
implementation(libs.androidx.annotation)
implementation(libs.androidx.appCompat)
implementation(libs.androidx.biometric)
implementation(libs.androidx.constraintLayout)
implementation(libs.androidx.core)
implementation(libs.androidx.coreSplashScreen)
implementation(libs.androidx.recyclerView)
implementation(libs.androidx.viewPager)
implementation(libs.androidx.profileInstaller)
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")
}
implementation(libs.image.decoder)
// UI libraries
implementation(libs.material)
implementation(libs.flexible.adapter.core)
implementation(libs.photoview)
implementation(libs.directionalviewpager) {
implementation(libs.flexibleAdapter)
implementation(libs.photoView)
implementation(libs.directionalViewPager) {
exclude(group = "androidx.viewpager", module = "viewpager")
}
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)
@@ -288,13 +286,26 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher)
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android)
implementation(libs.leakcanary.plumber)
// debugImplementation(libs.leakCanary.android)
implementation(libs.leakCanary.plumber)
testImplementation(kotlinx.coroutines.test)
testImplementation(libs.kotlinx.coroutines.test)
}
androidComponents {
onVariants { variant ->
val resSource = variant.sources.res ?: return@onVariants
val variantName = variant.name.replaceFirstChar { it.uppercase() }
val replaceShortcutsPlaceholderTask = tasks.register<ReplaceShortcutsPlaceholderTask>(
"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
@@ -304,6 +315,6 @@ androidComponents {
buildscript {
dependencies {
classpath(kotlinx.gradle)
classpath(libs.kotlin.gradle)
}
}
@@ -46,4 +46,9 @@ class BasePreferences(
)
val installationId: Preference<String> = preferenceStore.getString(Preference.appStateKey("installation_id"), "")
val donationCampaignShown: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("donation_campaign_shown"),
false,
)
}
@@ -28,7 +28,7 @@ fun NavigatorAdaptiveSheet(
content = { sheetNavigator ->
AdaptiveSheet(
onDismissRequest = onDismissRequest,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
enableImplicitDismiss = enableSwipeDismiss(sheetNavigator),
) {
ScreenTransition(
navigator = sheetNavigator,
@@ -63,7 +63,7 @@ fun NavigatorAdaptiveSheet(
fun AdaptiveSheet(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
enableSwipeDismiss: Boolean = true,
enableImplicitDismiss: Boolean = true,
content: @Composable () -> Unit,
) {
val isTabletUi = isTabletUi()
@@ -74,7 +74,7 @@ fun AdaptiveSheet(
) {
AdaptiveSheetImpl(
isTabletUi = isTabletUi,
enableSwipeDismiss = enableSwipeDismiss,
enableImplicitDismiss = enableImplicitDismiss,
onDismissRequest = onDismissRequest,
modifier = modifier,
) {
@@ -1,6 +1,7 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -15,7 +16,9 @@ import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
@Composable
fun LogoHeader() {
fun LogoHeader(
iconPadding: PaddingValues = PaddingValues(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -25,7 +28,7 @@ fun LogoHeader() {
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.padding(vertical = 56.dp)
.padding(iconPadding)
.size(64.dp),
)
@@ -1,10 +1,10 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.Info
@@ -13,10 +13,10 @@ import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Storage
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R
@@ -40,16 +40,17 @@ fun MoreScreen(
onClickStats: () -> Unit,
onClickDataAndStorage: () -> Unit,
onClickSettings: () -> Unit,
onClickSupport: () -> Unit,
onClickAbout: () -> Unit,
) {
val uriHandler = LocalUriHandler.current
Scaffold { contentPadding ->
ScrollbarLazyColumn(
modifier = Modifier.padding(contentPadding),
) {
ScrollbarLazyColumn(contentPadding = contentPadding) {
item {
LogoHeader()
LogoHeader(
iconPadding = PaddingValues(vertical = 32.dp),
)
}
item {
SwitchPreferenceWidget(
@@ -132,6 +133,13 @@ fun MoreScreen(
onPreferenceClick = onClickSettings,
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.label_support_us),
icon = Icons.Default.VolunteerActivism,
onPreferenceClick = onClickSupport,
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.pref_category_about),
@@ -146,13 +154,6 @@ fun MoreScreen(
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.label_donate),
icon = Icons.Outlined.AttachMoney,
onPreferenceClick = { uriHandler.openUri(Constants.URL_DONATE) },
)
}
}
}
}
@@ -3,6 +3,7 @@ package eu.kanade.presentation.more.settings.screen.about
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.updaterEnabled
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
@@ -85,7 +87,9 @@ object AboutScreen : Screen() {
contentPadding = contentPadding,
) {
item {
LogoHeader()
LogoHeader(
iconPadding = PaddingValues(vertical = 56.dp),
)
}
item {
@@ -176,7 +180,7 @@ object AboutScreen : Screen() {
LinkIcon(
label = "Discord",
icon = CustomIcons.Discord,
url = "https://discord.gg/mihon",
url = Constants.URL_DISCORD,
)
LinkIcon(
label = "X",
@@ -153,7 +153,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_mihon)
@@ -160,6 +160,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|large
|}
|format
|countryOfOrigin
|status
|chapters
|description
@@ -14,13 +14,22 @@ data class ALSearchItem(
val chapters: Long?,
val averageScore: Int?,
val staff: ALStaff,
val countryOfOrigin: String = "",
) {
fun toALManga(): ALManga = ALManga(
remoteId = id,
title = title.userPreferred,
imageUrl = coverImage.large,
description = description,
format = format.replace("_", "-"),
format = if (format != "MANGA") {
format.replace("_", "-")
} else {
when (countryOfOrigin) {
"KR" -> "Manhwa"
"CN", "TW" -> "Manhua"
else -> "Manga"
}
},
publishingStatus = status ?: "",
startDateFuzzy = startDate.toEpochMilli(),
totalChapters = chapters ?: 0,
@@ -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
@@ -122,8 +124,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<MALListItemStatus>()
.let { parseMangaItem(it, track) }
}
@@ -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")
@@ -1,15 +1,17 @@
package eu.kanade.tachiyomi.extension.installer
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import androidx.annotation.CallSuper
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.InstallStep
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import uy.kohesive.injekt.injectLazy
import java.util.Collections
import kotlin.concurrent.atomics.AtomicReference
@@ -26,13 +28,6 @@ abstract class Installer(private val service: Service) {
private var waitingInstall = AtomicReference<Entry?>(null)
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
private val cancelReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return
cancelQueue(downloadId)
}
}
/**
* Installer readiness. If false, queue check will not run.
*
@@ -114,7 +109,7 @@ abstract class Installer(private val service: Service) {
*/
@CallSuper
open fun onDestroy() {
LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver)
scope.cancel()
queue.forEach { extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) }
queue.clear()
waitingInstall.store(null)
@@ -149,24 +144,24 @@ abstract class Installer(private val service: Service) {
*/
data class Entry(val downloadId: Long, val uri: Uri)
private val scope = CoroutineScope(Dispatchers.Main)
init {
val filter = IntentFilter(ACTION_CANCEL_QUEUE)
LocalBroadcastManager.getInstance(service).registerReceiver(cancelReceiver, filter)
cancelChannel.receiveAsFlow()
.onEach(::cancelQueue)
.launchIn(scope)
}
companion object {
private const val ACTION_CANCEL_QUEUE = "Installer.action.CANCEL_QUEUE"
private const val EXTRA_DOWNLOAD_ID = "Installer.extra.DOWNLOAD_ID"
private val cancelChannel = Channel<Long>(Channel.UNLIMITED)
/**
* Attempts to cancel the installation entry for the provided download ID.
*
* @param downloadId Download ID as known by [ExtensionManager]
*/
fun cancelInstallQueue(context: Context, downloadId: Long) {
val intent = Intent(ACTION_CANCEL_QUEUE)
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
suspend fun cancelInstallQueue(downloadId: Long) {
cancelChannel.send(downloadId)
}
}
}
@@ -140,8 +140,10 @@ internal class ExtensionInstaller(
* Cancels extension install and remove from download manager and installer.
*/
fun cancelInstall(pkgName: String) {
activeJobs.remove(pkgName)?.cancel()
Installer.cancelInstallQueue(context, pkgName.hashCode().toLong())
scope.launch {
activeJobs.remove(pkgName)?.cancel()
Installer.cancelInstallQueue(pkgName.hashCode().toLong())
}
}
/**
@@ -5,6 +5,7 @@ import android.app.SearchManager
import android.app.assist.AssistContent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
@@ -14,21 +15,32 @@ import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.AlertDialog
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -41,6 +53,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen
@@ -55,6 +69,7 @@ import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
@@ -63,12 +78,10 @@ import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.extension.api.ExtensionApi
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
@@ -80,7 +93,6 @@ import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.updaterEnabled
import eu.kanade.tachiyomi.util.view.setComposeContent
import kotlinx.coroutines.channels.awaitClose
@@ -93,6 +105,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.core.migration.Migrator
import mihon.feature.support.SupportUsScreen
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.logcat
@@ -100,9 +113,14 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.injectLazy
import kotlin.time.Clock
import kotlin.time.Duration.Companion.days
import kotlin.time.Instant
import kotlin.time.times
class MainActivity : BaseActivity() {
@@ -131,7 +149,7 @@ class MainActivity : BaseActivity() {
super.onCreate(savedInstanceState)
val didMigration = Migrator.awaitAndRelease()
Migrator.awaitAndRelease()
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
if (!isTaskRoot) {
@@ -240,24 +258,7 @@ class MainActivity : BaseActivity() {
CheckForUpdates()
ShowOnboarding()
}
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
if (showChangelog) {
AlertDialog(
onDismissRequest = { showChangelog = false },
title = { Text(text = stringResource(MR.strings.updated_version, BuildConfig.VERSION_NAME)) },
dismissButton = {
TextButton(onClick = { openInBrowser(RELEASE_URL) }) {
Text(text = stringResource(MR.strings.whats_new))
}
},
confirmButton = {
TextButton(onClick = { showChangelog = false }) {
Text(text = stringResource(MR.strings.action_ok))
}
},
)
ShowDonationCampaign()
}
}
@@ -343,6 +344,128 @@ class MainActivity : BaseActivity() {
}
}
@Composable
private fun ShowDonationCampaign() {
val navigator = LocalNavigator.currentOrThrow
var showCampaign by remember { mutableStateOf(false) }
if (showCampaign) {
val uriHandler = LocalUriHandler.current
val dismissSupportMessage = {
preferences.donationCampaignShown.set(true)
@Suppress("AssignedValueIsNeverRead")
showCampaign = false
}
AdaptiveSheet(
onDismissRequest = dismissSupportMessage,
enableImplicitDismiss = false,
) {
Column {
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.weight(1f, fill = false)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringResource(MR.strings.donationCampaign_title),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.headlineSmall,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph1),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph2),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph3),
style = MaterialTheme.typography.bodyMedium,
)
}
HorizontalDivider()
Button(
modifier = Modifier
.padding(top = MaterialTheme.padding.small)
.padding(horizontal = MaterialTheme.padding.medium)
.fillMaxWidth(),
onClick = {
navigator.push(SupportUsScreen())
dismissSupportMessage()
},
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
Icon(
imageVector = Icons.Default.VolunteerActivism,
contentDescription = null,
)
Text(
text = stringResource(MR.strings.label_support_us),
color = MaterialTheme.colorScheme.onPrimary,
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
modifier = Modifier
.padding(bottom = MaterialTheme.padding.small)
.padding(horizontal = MaterialTheme.padding.medium),
) {
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
onClick = { uriHandler.openUri(Constants.URL_DISCORD) },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
Text(
text = stringResource(MR.strings.donationCampaign_contactPlatform),
)
Icon(
imageVector = Icons.AutoMirrored.Default.OpenInNew,
contentDescription = null,
)
}
}
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
onClick = dismissSupportMessage,
) {
Text(
text = stringResource(MR.strings.donationCampaign_dismiss),
)
}
}
}
}
}
LaunchedEffect(Unit) {
try {
val firstInstallTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
val eligibleTime = Instant.fromEpochMilliseconds(firstInstallTime).plus(6 * 30.days)
@Suppress("AssignedValueIsNeverRead")
showCampaign = (Clock.System.now() >= eligibleTime && !preferences.donationCampaignShown.get())
} catch (_: PackageManager.NameNotFoundException) {
}
}
}
/**
* Sets custom splash screen exit animation on devices prior to Android 12.
*
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
@@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import mihon.feature.support.SupportUsScreen
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@@ -57,7 +57,6 @@ data object MoreTab : Tab {
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { MoreScreenModel() }
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
@@ -72,6 +71,7 @@ data object MoreTab : Tab {
onClickStats = { navigator.push(StatsScreen()) },
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
onClickSettings = { navigator.push(SettingsScreen()) },
onClickSupport = { navigator.push(SupportUsScreen()) },
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
)
}
@@ -0,0 +1,143 @@
package mihon.feature.support
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.Screen
import tachiyomi.core.common.Constants
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.icons.CustomIcons
import tachiyomi.presentation.core.icons.Discord
import tachiyomi.presentation.core.icons.OpenCollective
import tachiyomi.presentation.core.icons.Patreon
class SupportUsScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val uriHandler = LocalUriHandler.current
Scaffold(
topBar = {
AppBar(
title = stringResource(MR.strings.label_support_us),
navigateUp = navigator::pop,
)
},
) { paddingValues ->
Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
modifier = Modifier.verticalScroll(rememberScrollState()).padding(
remember(paddingValues) {
object : PaddingValues {
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp {
return paddingValues.calculateLeftPadding(layoutDirection) +
MaterialTheme.padding.medium
}
override fun calculateTopPadding(): Dp {
return 0.dp
}
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp {
return paddingValues.calculateRightPadding(layoutDirection) +
MaterialTheme.padding.medium
}
override fun calculateBottomPadding(): Dp {
return 0.dp
}
}
},
),
) {
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding()))
Text(
text = stringResource(MR.strings.supportUsScreen_perks),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
SupportItem(
icon = CustomIcons.Patreon,
title = stringResource(MR.strings.supportUsScreen_donationPlatform_patreon),
onClick = { uriHandler.openUri(Constants.URL_DONATE_PATREON) },
)
SupportItem(
icon = CustomIcons.OpenCollective,
title = stringResource(MR.strings.supportUsScreen_donationPlatform_opencollective),
onClick = { uriHandler.openUri(Constants.URL_DONATE_OPENCOLLECTIVE) },
)
Text(
text = stringResource(MR.strings.supportUsScreen_currentlySupportedBy, 200),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
Text(
text = stringResource(MR.strings.supportUsScreen_contactForDetailsMessage),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
SupportItem(
icon = CustomIcons.Discord,
title = stringResource(MR.strings.supportUsScreen_contactPlatform),
onClick = { uriHandler.openUri(Constants.URL_DISCORD) },
)
Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@Composable
private fun SupportItem(
icon: ImageVector,
title: String,
onClick: () -> Unit,
) {
Card {
TextPreferenceWidget(
title = title,
icon = icon,
widget = {
Icon(
imageVector = Icons.AutoMirrored.Filled.OpenInNew,
contentDescription = null,
)
},
onPreferenceClick = onClick,
)
}
}
}
@@ -8,7 +8,8 @@
android:shortcutShortLabel="@string/label_library">
<intent
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
android:targetPackage="${applicationId}" />
</shortcut>
<shortcut
android:enabled="true"
@@ -19,7 +20,8 @@
android:shortcutShortLabel="@string/label_recent_updates">
<intent
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
android:targetPackage="${applicationId}" />
</shortcut>
<shortcut
android:enabled="true"
@@ -30,7 +32,8 @@
android:shortcutShortLabel="@string/label_recent_manga">
<intent
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
android:targetPackage="${applicationId}" />
</shortcut>
<shortcut
android:enabled="true"
@@ -41,6 +44,7 @@
android:shortcutShortLabel="@string/browse">
<intent
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
android:targetPackage="${applicationId}" />
</shortcut>
</shortcuts>
+15 -10
View File
@@ -1,18 +1,23 @@
buildscript {
dependencies {
classpath(libs.android.shortcut.gradle)
}
}
plugins {
alias(kotlinx.plugins.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.firebase.crashlytics) apply false
alias(libs.plugins.google.services) apply false
alias(libs.plugins.moko) 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<Delete>("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"))
}
}
}
-22
View File
@@ -1,22 +0,0 @@
plugins {
`kotlin-dsl`
}
dependencies {
implementation(androidx.gradle)
implementation(kotlinx.gradle)
implementation(kotlinx.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 {
gradlePluginPortal()
mavenCentral()
google()
}
-18
View File
@@ -1,18 +0,0 @@
dependencyResolutionManagement {
versionCatalogs {
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"))
}
}
}
rootProject.name = "mihon-buildSrc"
@@ -1,12 +0,0 @@
import mihon.buildlogic.configureCompose
plugins {
id("com.android.application")
kotlin("android")
id("mihon.code.lint")
}
android {
configureCompose(this)
}
@@ -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()
}
@@ -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()
}
@@ -1,35 +0,0 @@
import org.gradle.accessors.dm.LibrariesForLibs
plugins {
id("com.diffplug.spotless")
}
val libs = the<LibrariesForLibs>()
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.core.get().version)
trimTrailingWhitespace()
endWithNewline()
}
format("xml") {
target("**/*.xml")
targetExclude(*xmlFormatExclude)
trimTrailingWhitespace()
endWithNewline()
}
}
@@ -1,11 +0,0 @@
import mihon.buildlogic.configureCompose
plugins {
id("com.android.library")
id("mihon.code.lint")
}
android {
configureCompose(this)
}
@@ -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()
}
@@ -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
}
@@ -1,101 +0,0 @@
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
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.androidx get() = the<LibrariesForAndroidx>()
val Project.compose get() = the<LibrariesForCompose>()
val Project.kotlinx get() = the<LibrariesForKotlinx>()
val Project.libs get() = the<LibrariesForLibs>()
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<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(AndroidConfig.JvmTarget)
freeCompilerArgs.addAll(
"-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.desugar)
}
}
internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, *, *, *>) {
pluginManager.apply(kotlinx.plugins.compose.compiler.get().pluginId)
commonExtension.apply {
buildFeatures {
compose = true
}
dependencies {
"implementation"(platform(compose.bom))
}
}
extensions.configure<ComposeCompilerGradlePluginExtension> {
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<Test> {
useJUnitPlatform()
testLogging {
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
}
val Project.generatedBuildDir: File get() = project.layout.buildDirectory.asFile.get().resolve("generated/mihon")
@@ -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 = "<resources>\\s*</resources>|<resources\\s*/>".toRegex()
fun Project.getLocalesConfigTask(outputResourceDir: File): TaskProvider<Task> {
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") { "| <locale android:name=\"$it\"/>" }
val content = """
|<?xml version="1.0" encoding="utf-8"?>
|<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
$locales
|</locale-config>
""".trimMargin()
outputResourceDir.resolve("xml/locales_config.xml").apply {
parentFile.mkdirs()
writeText(content)
}
}
}
+5 -9
View File
@@ -1,20 +1,16 @@
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 {
implementation(projects.sourceApi)
implementation(kotlinx.bundles.serialization)
implementation(libs.bundles.serialization)
}
View File
-21
View File
@@ -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
+5 -4
View File
@@ -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 {
@@ -10,6 +11,6 @@ android {
dependencies {
implementation(libs.jsoup)
implementation(libs.libarchive)
implementation(libs.archive)
implementation(libs.unifile)
}
+12 -11
View File
@@ -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 {
@@ -22,24 +23,24 @@ 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)
implementation(libs.image.decoder)
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)
@@ -47,7 +48,7 @@ dependencies {
implementation(libs.natural.comparator)
// JavaScript engine
implementation(libs.bundles.js.engine)
implementation(libs.quickJs)
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.junit.platform.launcher)
@@ -77,7 +77,7 @@ private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
val callback =
object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response) {
continuation.resume(response) { _, _, _ ->
response.body.close()
}
}
@@ -3,7 +3,9 @@ package tachiyomi.core.common
object Constants {
const val URL_HELP = "https://mihon.app/docs/guides/troubleshooting/"
const val URL_HELP_UPCOMING = "https://mihon.app/docs/faq/updates/upcoming"
const val URL_DONATE = "https://patreon.com/mihon/membership"
const val URL_DONATE_PATREON = "https://patreon.com/mihon/membership"
const val URL_DONATE_OPENCOLLECTIVE = "https://opencollective.com/mihon/contribute"
const val URL_DISCORD = "https://discord.gg/mihon"
const val MANGA_EXTRA = "manga"
@@ -205,7 +205,11 @@ object ImageUtil {
*/
private fun isTallImage(imageSource: BufferedSource): Boolean {
val options = extractImageOptions(imageSource)
return (options.outHeight / options.outWidth) > 3
return TallImageSplitCalculator.shouldSplit(
imageWidth = options.outWidth,
imageHeight = options.outHeight,
optimalImageHeight = optimalImageHeight,
)
}
/**
@@ -226,7 +230,6 @@ object ImageUtil {
val options = extractImageOptions(imageSource).apply {
inJustDecodeBounds = false
}
val splitDataList = options.splitData
return try {
@@ -273,8 +276,7 @@ object ImageUtil {
val imageHeight = outHeight
val imageWidth = outWidth
// -1 so it doesn't try to split when imageHeight = optimalImageHeight
val partCount = (imageHeight - 1) / optimalImageHeight + 1
val partCount = TallImageSplitCalculator.calculatePartCount(imageHeight, optimalImageHeight)
val optimalSplitHeight = imageHeight / partCount
logcat {
@@ -0,0 +1,17 @@
package tachiyomi.core.common.util.system
internal object TallImageSplitCalculator {
fun calculatePartCount(imageHeight: Int, optimalImageHeight: Int): Int {
require(imageHeight > 0) { "imageHeight must be positive" }
require(optimalImageHeight > 0) { "optimalImageHeight must be positive" }
// -1 so it doesn't try to split when imageHeight = optimalImageHeight
return (imageHeight - 1) / optimalImageHeight + 1
}
fun shouldSplit(imageWidth: Int, imageHeight: Int, optimalImageHeight: Int): Boolean {
require(imageWidth > 0) { "imageWidth must be positive" }
return imageHeight > imageWidth * 3 &&
calculatePartCount(imageHeight, optimalImageHeight) > 1
}
}
@@ -0,0 +1,37 @@
package tachiyomi.core.common.util.system
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class TallImageSplitCalculatorTest {
@Test
fun `does not split when aspect ratio is tall but computed split count is one`() {
assertFalse(
TallImageSplitCalculator.shouldSplit(
imageWidth = 1024,
imageHeight = 4385,
optimalImageHeight = 4386,
),
)
}
@Test
fun `splits when aspect ratio is tall and computed split count is greater than one`() {
assertTrue(
TallImageSplitCalculator.shouldSplit(
imageWidth = 1024,
imageHeight = 4385,
optimalImageHeight = 4384,
),
)
}
@Test
fun `calculate part count rounds boundary correctly`() {
assertEquals(1, TallImageSplitCalculator.calculatePartCount(imageHeight = 4384, optimalImageHeight = 4384))
assertEquals(2, TallImageSplitCalculator.calculatePartCount(imageHeight = 4385, optimalImageHeight = 4384))
}
}
+5 -8
View File
@@ -1,22 +1,19 @@
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") {
packageName.set("tachiyomi.data")
dialect(libs.sqldelight.dialects.sql)
dialect(libs.sqldelight.sqliteDialect338)
schemaOutputDirectory.set(project.file("./src/main/sqldelight"))
}
}
View File
-21
View File
@@ -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
@@ -18,7 +18,7 @@ SELECT
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch,
excluded_scanlators.scanlator as excludedScanlator
excluded_scanlators.scanlator AS excludedScanlator
FROM mangas JOIN chapters
ON mangas._id = chapters.manga_id
LEFT JOIN excluded_scanlators
+9 -14
View File
@@ -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 {
@@ -23,17 +19,16 @@ dependencies {
implementation(projects.sourceApi)
implementation(projects.core.common)
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)
}
View File
-21
View File
@@ -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
+1
View File
@@ -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
-40
View File
@@ -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"]
+75
View File
@@ -0,0 +1,75 @@
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))
}
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"
}
}
}
+8
View File
@@ -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
+18
View File
@@ -0,0 +1,18 @@
dependencyResolutionManagement {
@Suppress("UnstableApiUsage")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
versionCatalogs {
create("libs") {
from(files("../libs.versions.toml"))
}
create("mihonx") {
from(files("../mihon.versions.toml"))
}
}
}
rootProject.name = "build-logic"
@@ -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<Project> {
override fun apply(target: Project): Unit = with(target) {
plugins {
alias(libs.plugins.android.application)
alias(mihonx.plugins.android.base)
}
}
}
@@ -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<Project> {
override fun apply(target: Project): Unit = with(target) {
plugins {
alias(libs.plugins.kotlin.android)
}
configureKotlin()
configureTest()
configureAndroid()
}
}
@@ -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<Project> {
override fun apply(target: Project): Unit = with(target) {
plugins {
alias(libs.plugins.android.library)
alias(mihonx.plugins.android.base)
}
}
}
@@ -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<Project> {
override fun apply(target: Project): Unit = with(target) {
plugins {
alias(libs.plugins.android.test)
alias(mihonx.plugins.android.base)
}
}
}
@@ -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<Project> {
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)
}
}
}
@@ -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<Project> {
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)
}
@@ -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<Project> {
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)
}
@@ -1,4 +1,4 @@
package mihon.buildlogic
package mihon.gradle
import org.gradle.api.Project
@@ -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(" ")
}
@@ -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)
}
}
@@ -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)
}
@@ -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<MinimalExternalModuleDependency>) {
add("api", dependencyNotation)
}
fun DependencyHandlerScope.coreLibraryDesugaring(dependencyNotation: Provider<MinimalExternalModuleDependency>) {
add("coreLibraryDesugaring", dependencyNotation)
}
fun DependencyHandlerScope.debugApi(dependencyNotation: Provider<MinimalExternalModuleDependency>) {
add("debugApi", dependencyNotation)
}
@JvmName("implementationBundle")
fun DependencyHandlerScope.implementation(dependencyNotation: Provider<ExternalModuleDependencyBundle>) {
add("implementation", dependencyNotation)
}
fun DependencyHandlerScope.implementation(dependencyNotation: Provider<MinimalExternalModuleDependency>) {
add("implementation", dependencyNotation)
}
fun DependencyHandlerScope.implementation(dependencyNotation: Project) {
add("implementation", dependencyNotation)
}
@@ -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<PluginDependency>) {
apply(notation.get().pluginId)
}
@@ -0,0 +1,35 @@
package mihon.gradle.extensions
import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.BaseExtension
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<LibrariesForLibs>()
internal val Project.mihonx get() = the<LibrariesForMihonx>()
internal fun Project.plugins(block: PluginManager.() -> Unit) {
pluginManager.apply(block)
}
internal fun Project.android(block: CommonExtension<*, *, *, *, *, *>.() -> Unit) {
extensions.configure<BaseExtension> {
if (this is CommonExtension<*, *, *, *, *, *>) apply(block)
}
}
fun Project.configureTest() {
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
}
@@ -0,0 +1,51 @@
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 = objectFactory.fileTree()
.from("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") { "| <locale android:name=\"$it\"/>" }
val content = """
|<?xml version="1.0" encoding="utf-8"?>
|<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
$locales
|</locale-config>
""".trimMargin()
outputDir.get().file("xml/locales_config.xml").asFile.apply {
parentFile.mkdirs()
writeText(content)
}
}
companion object {
private val emptyResourcesElement = "<resources>\\s*</resources>|<resources\\s*/>".toRegex()
}
}
@@ -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<String>
@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)
}
}
}
-19
View File
@@ -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"
-33
View File
@@ -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" }
+195 -104
View File
@@ -1,122 +1,213 @@
[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"
firebase-bom = "34.11.0"
markdown = "0.39.2"
aboutLibraries = "14.0.1"
android-desugar = "2.1.5"
android-gradle = "8.13.2"
androidx-activity-compose = "1.13.0"
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-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.2"
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.12.0"
firebase-crashlytics = "3.0.7"
flexibleAdapter = "c8013533"
google-services = "4.4.4"
image-decoder = "41c059e540"
injekt = "91edab2317"
jsoup = "1.22.1"
junit = "6.0.3"
kotest-assertions = "6.1.11"
kotlin-gradle = "2.3.20"
kotlinx-collections-immutable = "0.4.0"
kotlinx-coroutines = "1.10.2"
kotlinx-serialization = "1.11.0"
ktlint = "1.8.0"
leakCanary = "2.14"
logcat = "0.4"
markdown = "0.39.2"
material = "1.13.0"
materialKolor = "5.0.0-alpha07"
mockk = "1.14.9"
moko-resources = "0.26.3"
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"
tapmoc = "0.4.1"
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" }
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" }
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" }
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" }
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" }
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"]
shizuku = ["shizuku-api", "shizuku-provider"]
sqldelight = ["sqldelight-androidx-driver", "sqldelight-coroutines", "sqldelight-android-paging"]
voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-transitions"]
test = ["junit-jupiter", "kotest-assertions", "mockk"]
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-androidxDriver", "sqldelight-coroutines", "sqldelight-androidxPaging"]
test = ["junit-jupiter", "kotest-assertions", "mockk"]
voyager = ["voyager-navigator", "voyager-screenModel", "voyager-tabNavigator", "voyager-transitions"]
+16
View File
@@ -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" }
Binary file not shown.
+1 -1
View File
@@ -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
Vendored
+2 -5
View File
@@ -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" \
"$@"
Vendored
+1 -2
View File
@@ -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
+18 -35
View File
@@ -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(libs.plugins.moko)
alias(mihonx.plugins.kotlin.multiplatform)
alias(mihonx.plugins.spotless)
alias(libs.plugins.moko.resources)
}
kotlin {
androidTarget()
android {
namespace = "tachiyomi.i18n"
}
applyDefaultHierarchyTemplate()
sourceSets {
commonMain {
dependencies {
api(libs.moko.core)
}
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
@Suppress("UnstableApiUsage")
dependencies {
api(libs.moko.resources)
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
@@ -27,31 +25,16 @@ kotlin {
}
}
val generatedAndroidResourceDir = generatedBuildDir.resolve("android/res")
androidComponents {
onVariants { variant ->
val resSource = variant.sources.res ?: return@onVariants
android {
namespace = "tachiyomi.i18n"
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<GenerateLocalesConfigTask>("generate${variantName}LocalesConfig")
resSource.addGeneratedSourceDirectory(task) { it.outputDir }
}
}
multiplatformResources {
resourcesPackage.set("tachiyomi.i18n")
}
tasks {
val localesConfigTask = project.getLocalesConfigTask(generatedAndroidResourceDir)
preBuild {
dependsOn(localesConfigTask)
}
}
@@ -409,7 +409,6 @@
<string name="unknown_author">مؤلّف غير معروف</string>
<string name="no_pinned_sources">لا تمتلك أيّة مصادر مثبّتة</string>
<string name="action_global_search_query">إبحث عن \"%1$s\" بشكلٍ شامل</string>
<string name="updated_version">حُدِّث إلى الإصدار v%1$s</string>
<string name="no_chapters_error">لم يُعثر على فصول</string>
<string name="share_page_info">%1$s: %2$s, صفحة %3$d</string>
<string name="chapter_settings_updated">إعدادات الفصل الإفتراضية المُحدثة</string>
@@ -567,7 +566,6 @@
<string name="error_saving_picture">خطأ أثناء حفظ الصورة</string>
<string name="disabled_nav">معطل</string>
<string name="rotation_reverse_portrait">رأسي بالعكس</string>
<string name="notification_update_error">فشل %1$d تحديث أو تحديثات</string>
<string name="learn_more">اضغط لمعرفة المزيد</string>
<string name="action_move_to_top_all_for_series">نقل سلسلة الفصول للأعلى</string>
<string name="empty_backup_error">لا توجد مدخلات في المكتبة لإجراء نسخة احتياطي‮‮ة</string>
@@ -852,7 +850,6 @@
<string name="possible_duplicates_title">التكرارات المحتملة</string>
<string name="confirm_tracker_update">تحديث المتابعات إلى الفصل %d؟</string>
<string name="pref_incognito_mode_extension_summary">إيقاف قراءة التاريخ مؤقتًا للتمديد</string>
<string name="label_donate">تبرع</string>
<string name="theme_catppuccin">كاتبوتشين</string>
<string name="pref_display_images_description">­تقديم الصور في أوصاف المانجا</string>
<string name="pref_hide_missing_chapter_indicators">إخفاء مؤشرات الفصل المفقودة</string>
@@ -551,7 +551,6 @@
<string name="licenses">খুলা উৎস লাইচেন্স</string>
<string name="privacy_policy">গোপনীয়তা নীতি</string>
<string name="check_for_updates">আপডেট চাওক</string>
<string name="updated_version">v%1$s ত আপডেট কৰা হৈছে</string>
<string name="pref_enable_acra">ক্রাশ ৰিপ\'ৰ্ট পঠিয়াব</string>
<string name="pref_acra_summary">যিকোনো বাগ মেলাবলৈ সহায় কৰে। কোনো সংবেদনশীল তথ্য পঠিয়াব নোৱাৰি</string>
<string name="fdroid_warning">F-Droid নিৰ্মাণসমূহৰ আনুষ্ঠানিক সমৰ্থন নাই।
@@ -755,7 +754,6 @@
<string name="notification_chapters_single">অধ্যায় %1$s</string>
<string name="notification_chapters_single_and_more">অধ্যায় %1$s আৰু %2$d অধিক</string>
<string name="notification_chapters_multiple">অধ্যায়সমূহ %1$s</string>
<string name="notification_update_error">%1$d আপডেট(s) ব্যৰ্থ</string>
<string name="learn_more">অধিক জানিবলৈ টেপ কৰক</string>
<string name="notification_cover_update_failed">কভাৰ আপডেট কৰিবলৈ ব্যৰ্থ</string>
<string name="notification_first_add_to_library">এইটো কৰিবলৈ আগত অনুগ্ৰহ কৰি ৰেকৰ্ডটো আপোনাৰ লাইব্রেৰীত যোগ কৰক</string>
@@ -109,4 +109,9 @@
<item quantity="one">An entry was skipped</item>
<item quantity="other">%1$d entries were skipped</item>
</plurals>
<plurals name="notification_update_error">
<item quantity="one">%1$d update failed</item>
<item quantity="other">%1$d updates failed</item>
</plurals>
</resources>
@@ -36,7 +36,7 @@
<string name="label_extensions">Extensions</string>
<string name="label_extension_info">Extension info</string>
<string name="label_help">Help</string>
<string name="label_donate">Donate</string>
<string name="label_support_us">Support Us</string>
<string name="label_default">Default</string>
<string name="label_warning">Warning</string>
<string name="label_auto">Auto</string>
@@ -648,7 +648,6 @@
<string name="licenses">Open source licenses</string>
<string name="privacy_policy">Privacy policy</string>
<string name="check_for_updates">Check for updates</string>
<string name="updated_version">Updated to v%1$s</string>
<!-- ACRA -->
<string name="pref_enable_acra">Send crash reports</string>
@@ -921,7 +920,6 @@
<string name="notification_chapters_single">Chapter %1$s</string>
<string name="notification_chapters_single_and_more">Chapter %1$s and %2$d more</string>
<string name="notification_chapters_multiple">Chapters %1$s</string>
<string name="notification_update_error">%1$d update(s) failed</string>
<string name="learn_more">Tap to learn more</string>
<string name="notification_cover_update_failed">Failed to update cover</string>
<string name="notification_first_add_to_library">Please add the entry to your library before doing this</string>
@@ -1048,4 +1046,18 @@
<string name="migrationListScreen.migrateDialog.cancelLabel">Cancel</string>
<string name="migrationListScreen.progressDialog.cancelLabel">Cancel</string>
<string name="migrationListScreen.matchWithoutChapterToast">No chapters found, this entry cannot be used for migration</string>
<string name="supportUsScreen.perks">Patrons get access to exclusive channels, with more perks to come.</string>
<string name="supportUsScreen.donationPlatform.patreon">Patreon (preferred)</string>
<string name="supportUsScreen.donationPlatform.opencollective">OpenCollective</string>
<string name="supportUsScreen.currentlySupportedBy">Mihon is currently backed by %d+ patrons. We want to thank each and everyone of you. Let's make Mihon even better together.</string>
<string name="supportUsScreen.contactForDetailsMessage">For alternative options or any related questions, join our Discord server.</string>
<string name="supportUsScreen.contactPlatform">Discord</string>
<string name="donationCampaign.title">Enjoying the app?</string>
<string name="donationCampaign.paragraph1">Hi! I’m the main developer behind Mihon, and I’d like to ask you to consider supporting its development financially. With enough support, I’ll be able to work on Mihon full-time and focus on long-standing issues as well as highly requested features.</string>
<string name="donationCampaign.paragraph2">You can donate through either Patreon (preferred) or OpenCollective. If you have questions or would like to support using alternative methods, feel free to reach out to me through Mihon's Discord server.</string>
<string name="donationCampaign.paragraph3">Regardless, Mihon will continue to be maintained by me and the volunteers, and it will stay open source.</string>
<string name="donationCampaign.contactPlatform">Discord</string>
<string name="donationCampaign.dismiss">Dismiss</string>
</resources>
@@ -7,10 +7,10 @@
<item quantity="other">%d катэгорый</item>
</plurals>
<plurals name="lock_after_mins">
<item quantity="one">Пасля %1$s хвіліны</item>
<item quantity="few">Пасля %1$s хвілін</item>
<item quantity="many">Пасля %1$s хвілін</item>
<item quantity="other">Пасля %1$s хвілін</item>
<item quantity="one">Праз %1$s хвіліну</item>
<item quantity="few">Праз %1$s хвіліны</item>
<item quantity="many">Праз %1$s хвілін</item>
<item quantity="other">Праз %1$s хвілін</item>
</plurals>
<plurals name="relative_time">
<item quantity="one">Учора</item>
@@ -19,9 +19,117 @@
<item quantity="other">%1$d дзён таму</item>
</plurals>
<plurals name="next_unread_chapters">
<item quantity="one">Наступная непрачытаная глава</item>
<item quantity="few">Наступныя %d непрачытаныя главы</item>
<item quantity="many">Наступныя %d непрачытаных глав</item>
<item quantity="other">Наступныя %d непрачытаных глав</item>
<item quantity="one">Наступны непрачытаная раздзел</item>
<item quantity="few">Наступныя %d непрачытаныя раздзелы</item>
<item quantity="many">Наступныя %d непрачытаных раздзелаў</item>
<item quantity="other">Наступныя %d непрачытаных раздзелаў</item>
</plurals>
<plurals name="upcoming_relative_time">
<item quantity="one">Заўтра</item>
<item quantity="few">Праз %1$d дні</item>
<item quantity="many">Праз %1$d дзён</item>
<item quantity="other">Праз %1$d дзён</item>
</plurals>
<plurals name="restore_completed_message">
<item quantity="one">Выканана за %1$s з %2$s памылкай</item>
<item quantity="few">Выканана за %1$s з %2$s памылкамі</item>
<item quantity="many">Выканана за %1$s з %2$s памылкамі</item>
<item quantity="other">Выканана за %1$s з %2$s памылкамі</item>
</plurals>
<plurals name="download_queue_summary">
<item quantity="one">Застаўся %1$s</item>
<item quantity="few">Засталося %1$s</item>
<item quantity="many">Засталося %1$s</item>
<item quantity="other">Засталося %1$s</item>
</plurals>
<plurals name="day">
<item quantity="one">%d дзень</item>
<item quantity="few">%d дні</item>
<item quantity="many">%d дзён</item>
<item quantity="other">%d дзён</item>
</plurals>
<plurals name="pref_pages">
<item quantity="one">%1$s старонка</item>
<item quantity="few">%1$s старонкі</item>
<item quantity="many">%1$s старонак</item>
<item quantity="other">%1$s старонак</item>
</plurals>
<plurals name="missing_chapters">
<item quantity="one">Адсутнічае %1$s раздзел</item>
<item quantity="few">Адсутнічаюць %1$s раздзелы</item>
<item quantity="many">Адсутнічаюць %1$s раздзелаў</item>
<item quantity="other">Адсутнічаюць %1$s раздзелаў</item>
</plurals>
<plurals name="manga_num_chapters">
<item quantity="one">%1$s раздзел</item>
<item quantity="few">%1$s раздзелы</item>
<item quantity="many">%1$s раздзелаў</item>
<item quantity="other">%1$s раздзелаў</item>
</plurals>
<plurals name="download_amount">
<item quantity="one">Наступны раздзел</item>
<item quantity="few">Наступныя %d раздзелы</item>
<item quantity="many">Наступныя %d раздзелаў</item>
<item quantity="other">Наступныя %d раздзелаў</item>
</plurals>
<plurals name="num_trackers">
<item quantity="one">%d трэкер</item>
<item quantity="few">%d трэкеры</item>
<item quantity="many">%d трэкераў</item>
<item quantity="other">%d трэкераў</item>
</plurals>
<plurals name="missing_chapters_warning">
<item quantity="one">Прапускаецца %d раздзел: адсутнічае ў крыніцы або адфільтраваны</item>
<item quantity="few">Прапускаюцца %d раздзелы: адсутнічаюць у крыніцы або адфільтраваныя</item>
<item quantity="many">Прапускаецца %d раздзелаў: адсутнічаюць у крыніцы або адфільтраваныя</item>
<item quantity="other">Прапускаецца %d раздзелаў: адсутнічаюць у крыніцы або адфільтраваныя</item>
</plurals>
<plurals name="notification_new_chapters_summary">
<item quantity="one">Для %d твора</item>
<item quantity="few">Для %d твораў</item>
<item quantity="many">Для %d твораў</item>
<item quantity="other">Для %d твораў</item>
</plurals>
<plurals name="notification_chapters_generic">
<item quantity="one">%1$d новы раздзел</item>
<item quantity="few">%1$d новыя раздзелы</item>
<item quantity="many">%1$d новых раздзелаў</item>
<item quantity="other">%1$d новых раздзелаў</item>
</plurals>
<plurals name="migrationListScreen.migrateDialog.skipText">
<item quantity="one">%1$d твор быў прапушчаны</item>
<item quantity="few">%1$d творы былі прапушчаны</item>
<item quantity="many">%1$d твораў было прапушчана</item>
<item quantity="other">%1$d твораў было прапушчана</item>
</plurals>
<plurals name="notification_chapters_multiple_and_more">
<item quantity="one">Раздзелы %1$s і яшчэ адзін</item>
<item quantity="few">Раздзелы %1$s і яшчэ %2$d</item>
<item quantity="many">Раздзелы %1$s і яшчэ %2$d</item>
<item quantity="other">Раздзелы %1$s і яшчэ %2$d</item>
</plurals>
<plurals name="update_check_notification_ext_updates">
<item quantity="one">Даступна абнаўленне пашырэння</item>
<item quantity="few">Даступна %d абнаўленні пашырэнняў</item>
<item quantity="many">Даступна %d абнаўленняў пашырэнняў</item>
<item quantity="other">Даступна %d абнаўленняў пашырэнняў</item>
</plurals>
<plurals name="num_repos">
<item quantity="one">%d рэпазіторый</item>
<item quantity="few">%d рэпазіторыі</item>
<item quantity="many">%d рэпазіторыяў</item>
<item quantity="other">%d рэпазіторыяў</item>
</plurals>
<plurals name="migrationListScreen.migrateDialog.migrateTitle">
<item quantity="one">Перанесці %1$d твор?</item>
<item quantity="few">Перанесці %1$d творы?</item>
<item quantity="many">Перанесці %1$d твораў?</item>
<item quantity="other">Перанесці %1$d твораў?</item>
</plurals>
<plurals name="migrationListScreen.migrateDialog.copyTitle">
<item quantity="one">Скапіяваць %1$d твор?</item>
<item quantity="few">Скапіяваць %1$d творы?</item>
<item quantity="many">Скапіяваць %1$d твораў?</item>
<item quantity="other">Скапіяваць %1$d твораў?</item>
</plurals>
</resources>
File diff suppressed because it is too large Load Diff
@@ -400,7 +400,6 @@
<string name="unknown_author">Неизвестен автор</string>
<string name="no_pinned_sources">Нямате отметнати източници</string>
<string name="action_global_search_query">Търси „%1$s“ глобално</string>
<string name="updated_version">Обновена до v%1$s</string>
<string name="whats_new">Какво е новото</string>
<string name="backup_restore_missing_trackers">Не сте влезли като потребител в:</string>
<string name="pref_remove_bookmarked_chapters">Позволи изтриване на отметнати глави</string>
@@ -618,7 +617,6 @@
<string name="ext_info_version">Версия</string>
<string name="on_hiatus">В хиатус</string>
<string name="custom_cover">Корица по избор</string>
<string name="notification_update_error">%1$d провалени обновление/я</string>
<string name="action_search_hint">Търсене…</string>
<string name="disabled_nav">Деактивирано</string>
<string name="learn_more">Натисни, за да научиш повече</string>
@@ -410,7 +410,6 @@
<string name="pref_incognito_mode_summary">পড়ার ইতিহাসে বিরতি দেয়</string>
<string name="pref_incognito_mode">ছদ্মবেশী মোড</string>
<string name="label_downloaded_only">শুধু ডাউনলোড</string>
<string name="updated_version">v%1$s এ আপডেট হয়েছে</string>
<string name="check_for_updates">সংস্করণ যাচাই</string>
<string name="licenses">ওপেন সোর্স লাইসেন্স</string>
<string name="whats_new">নতুন কি</string>
@@ -551,7 +550,6 @@
<string name="skipped_reason_completed">এড়িয়ে যাওয়া হয়েছে কারণ সিরিজটি সম্পূর্ণ</string>
<string name="cancelled">বাতিল করা হয়েছে</string>
<string name="on_hiatus">বিরতিতে রয়েছে</string>
<string name="notification_update_error">%1$d আপডেট(গুলি) ব্যর্থ হয়েছে</string>
<string name="publishing_finished">সম্পূর্ণ প্রকাশিত</string>
<string name="action_sort_last_manga_update">সর্বশেষ হালনাগাদ চেক</string>
<string name="delete_category_confirmation">আপনি কি “%s” ক্যাটাগরিটি মুছে ফেলতে চান?</string>
@@ -687,7 +685,6 @@
<string name="pref_flash_style_white_black">সাদা আর কালো</string>
<string name="pref_hardware_bitmap_threshold_summary">যদি রিডার একটি ফাঁকা ছবি লোড করে, ধাপে ধাপে থ্রেশহোল্ড কমিয়ে দিন।\nসিলেক্টেড %s</string>
<string name="pref_always_decode_long_strip_with_ssiv_summary">পারফরম্যান্স অ্যাফেক্ট করবে। ফাঁকা ইমেজ সমস্যা থ্রেশহোল্ড কমিয়ে ঠিক না হলে কেবল এনেবল করবে</string>
<string name="label_donate">ডোনেট</string>
<string name="pref_downloads_summary">অটোমেটিক ডাউনলোড, আগাম ডাউনলোড</string>
<string name="pref_tracking_summary">ওয়ান-ওয়ে প্রগ্রেস সিঙ্ক, এনহ্যান্সড সিঙ্ক</string>
<string name="pref_browse_summary">সোর্স, এক্সটেনশন, গ্লোবাল সার্চ</string>
@@ -386,7 +386,6 @@
<string name="unknown_status">Estat desconegut</string>
<string name="unknown_author">Autor desconegut</string>
<string name="action_global_search_query">Cerca «%1$s» globalment</string>
<string name="updated_version">S’ha actualitzat a la v%1$s</string>
<string name="whats_new">Novetats</string>
<string name="requires_app_restart">Cal reiniciar l’aplicació perquè tingui efecte</string>
<string name="label_network">Xarxa</string>
@@ -562,7 +561,6 @@
<string name="skipped_reason_not_started">S’ha omès perquè no hi ha cap capítol llegit</string>
<string name="skipped_reason_not_caught_up">S’ha omès perquè hi ha capítols no llegits</string>
<string name="learn_more">Premeu per a obtenir més informació</string>
<string name="notification_update_error">Han fallat %1$d actualitzacions</string>
<string name="rotation_reverse_portrait">Vertical inversa</string>
<string name="action_move_to_top_all_for_series">Mou la sèrie a dalt de tot</string>
<string name="disabled_nav">Desactivat</string>
@@ -833,7 +831,6 @@
<string name="theme_monochrome">Monocrom</string>
<string name="library_list">Llista de la biblioteca</string>
<string name="action_toggle_private_on">Segueix de manera privada</string>
<string name="label_donate">Dóna</string>
<string name="action_display_unread_badge">Capítols no llegits</string>
<string name="action_notes">Notes</string>
<string name="action_edit_notes">Edita les notes</string>
@@ -419,7 +419,6 @@
<string name="pref_incognito_mode">Incognito mode</string>
<string name="version">Bersyon</string>
<string name="check_for_updates">Susiha ang mga update</string>
<string name="updated_version">Mga update sa v%1$s</string>
<string name="pref_enable_acra">Ipadala ang mga taho sa pagkahagsa</string>
<string name="logout_success">Naka-log out ka na karon</string>
<string name="password">Password</string>
@@ -332,7 +332,6 @@
<string name="pref_incognito_mode_summary">Pozastaví historii čtení</string>
<string name="pref_incognito_mode">Anonymní režim</string>
<string name="label_downloaded_only">Pouze staženo</string>
<string name="updated_version">Aktualizováno na verzi v%1$s</string>
<string name="check_for_updates">Zkontrolovat aktualizace</string>
<string name="licenses">Open Source licence</string>
<string name="whats_new">Co je nového</string>
@@ -558,7 +557,6 @@
<string name="action_show_manga">Zobrazit položku</string>
<string name="action_display_cover_only_grid">Mřížka jen s přebaly</string>
<string name="pref_update_only_started">Přeskočit nerozečtené položky</string>
<string name="notification_update_error">%1$d aktualizace(í) selhalo</string>
<string name="on_hiatus">Má pauzu</string>
<string name="skipped_reason_not_caught_up">Přeskočeno, protože obsahuje nepřečtené kapitoly</string>
<string name="action_move_to_top_all_for_series">Přesunout sérii na začátek</string>
@@ -860,7 +858,6 @@
<string name="storage_failed_to_create_directory">Nepodařilo se vytvořit adresář: %s</string>
<string name="clear_database_text">Chystáte se odstranit položky z databáze</string>
<string name="clear_db_exclude_read">Ponechat položky s přečtenými kapitolami</string>
<string name="label_donate">Přispěj</string>
<string name="theme_catppuccin">Catppuccin</string>
<string name="pref_hide_missing_chapter_indicators">Skrýt indikátory chybějících kapitol</string>
<string name="pref_disallow_non_ascii_filenames">Nepovolovat soubory s non-ASCII znaky</string>
@@ -868,7 +865,6 @@
<string name="pref_download_concurrent_sources">Souběžné stahování zdrojů</string>
<string name="pref_download_concurrent_pages">Souběžné stahování stránek</string>
<string name="action_webview_close_tab">Zavřít okno</string>
<string name="calculating">Vypočítávání…</string>
<string name="pref_download_concurrent_pages_summary">Souběžně stahované stránky na zdroj</string>
<string name="migrationConfigScreen.selectAllLabel">Vybrat vše</string>
<string name="migrationConfigScreen.selectNoneLabel">Vybrat žádné</string>
@@ -391,7 +391,6 @@
<string name="pref_restore_backup_summ">Вулавӑша йантӑлавран тавӑр</string>
<string name="pref_restore_backup">Йантӑлав тавӑр</string>
<string name="pref_create_backup">Йантӑлав ту</string>
<string name="updated_version">v%1$s верссиччен ҫӗнетнӗ</string>
<string name="whats_new">Мӗн ҫӗнни</string>
<string name="pref_category_theme">Темӗ</string>
<string name="action_sort_date_added">Хушни вӑхӑчӗпе</string>
@@ -88,4 +88,8 @@
<item quantity="one">Ein Eintrag wurde übersprungen</item>
<item quantity="other">%1$d Einträge wurden übersprungen</item>
</plurals>
<plurals name="notification_update_error">
<item quantity="one">%1$d Aktualisierung ist fehlgeschlagen</item>
<item quantity="other">%1$d Aktualisierungen sind fehlgeschlagen</item>
</plurals>
</resources>
@@ -393,7 +393,6 @@
<string name="tapping_inverted_none">–</string>
<string name="unknown_status">Unbekannter Status</string>
<string name="unknown_author">Unbekannter Autor</string>
<string name="updated_version">Auf v%1$s aktualisiert</string>
<string name="whats_new">Was ist neu</string>
<string name="download_insufficient_space">Herunterladen von Kapiteln aufgrund von zu wenig Speicherplatz nicht möglich</string>
<string name="action_global_search_query">Überall nach „%1$s“ suchen</string>
@@ -564,7 +563,6 @@
<string name="skipped_reason_completed">Übersprungen, da die Serie abgeschlossen ist</string>
<string name="skipped_reason_not_caught_up">Übersprungen, weil es ungelesene Kapitel gibt</string>
<string name="skipped_reason_not_started">Übersprungen, weil keine Kapitel gelesen wurden</string>
<string name="notification_update_error">%1$d Aktualisierung(en) fehlgeschlagen</string>
<string name="learn_more">Antippen, um mehr zu erfahren</string>
<string name="rotation_reverse_portrait">Umgekehrtes Hochformat</string>
<string name="action_move_to_top_all_for_series">Serie nach oben verschieben</string>
@@ -901,7 +899,6 @@
<string name="migrationListScreen.migrateDialog.cancelLabel">Abbrechen</string>
<string name="migrationListScreen.progressDialog.cancelLabel">Abbrechen</string>
<string name="migrationListScreen.matchWithoutChapterToast">Keine Kapitel gefunden, dieser Eintrag konnte nicht für eine Migration verwendet werden</string>
<string name="label_donate">Spenden</string>
<string name="pref_display_images_description">Bilder in Mangabeschreibungen anzeigen</string>
<string name="pref_disallow_non_ascii_filenames">Nicht-ASCII-Dateinamen nicht zulassen</string>
<string name="pref_disallow_non_ascii_filenames_details">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.</string>
@@ -910,6 +907,18 @@
<string name="pref_download_concurrent_pages_summary">Pro Quelle gleichzeitig heruntergeladene Seiten</string>
<string name="action_webview_close_tab">Tab schließen</string>
<string name="action_filter_excluded_scanlators">Ausgeschlossene Scanlationgruppen herausfiltern</string>
<string name="calculating">Wird berechnet…</string>
<string name="download_bookmarked">Mit Lesezeichen versehen</string>
<string name="label_support_us">Unterstütze Uns</string>
<string name="supportUsScreen.perks">Unterstützende erhalten Zugang zu exklusiven Kanälen. Weitere Vorteile folgen.</string>
<string name="supportUsScreen.donationPlatform.patreon">Patreon (bevorzugt)</string>
<string name="supportUsScreen.donationPlatform.opencollective">OpenCollective</string>
<string name="supportUsScreen.currentlySupportedBy">Mihon wird aktuell von %d+ Personen unterstützt. Wir bedanken uns bei euch allen. Lasst uns Mihon gemeinsam noch besser machen.</string>
<string name="supportUsScreen.contactForDetailsMessage">Trete unserem Discord-Server für alternative Optionen oder bei weiteren Fragen bei.</string>
<string name="supportUsScreen.contactPlatform">Discord</string>
<string name="donationCampaign.title">Gefällt dir die App?</string>
<string name="donationCampaign.paragraph1">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.</string>
<string name="donationCampaign.paragraph2">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.</string>
<string name="donationCampaign.paragraph3">Unabhängig davon wird Mihon weiterhin von mir und anderen Freiwilligen entwickelt werden und bleibt Open-Source-Software.</string>
<string name="donationCampaign.contactPlatform">Discord</string>
<string name="donationCampaign.dismiss">Verwerfen</string>
</resources>
@@ -88,4 +88,8 @@
<item quantity="one">Μια καταχώρηση παραλείφθηκε</item>
<item quantity="other">%1$d καταχωρήσεις παραλείφθηκαν</item>
</plurals>
<plurals name="notification_update_error">
<item quantity="one">%1$d ενημέρωση απέτυχε</item>
<item quantity="other">%1$d ενημερώσεις απέτυχαν</item>
</plurals>
</resources>
@@ -393,7 +393,6 @@
<string name="pref_read_with_tapping_inverted">Αντιστροφή ζωνών πατήματος</string>
<string name="unknown_status">Άγνωστη κατάσταση</string>
<string name="unknown_author">Άγνωστος συγγραφέας</string>
<string name="updated_version">Ενημερώθηκε σε v%1$s</string>
<string name="whats_new">Τι νέο υπάρχει</string>
<string name="download_insufficient_space">Δεν ήταν δυνατή η λήψη κεφαλαίων λόγω χαμηλού αποθηκευτικού χώρου</string>
<string name="action_global_search_query">Αναζήτηση του \"%1$s\" καθολικά</string>
@@ -564,7 +563,6 @@
<string name="skipped_reason_completed">Παραλείφθηκε επειδή η σειρά ολοκληρώθηκε</string>
<string name="skipped_reason_not_caught_up">Παραβλέφθηκε επειδή υπάρχουν μη αναγνωσμένα κεφάλαια</string>
<string name="skipped_reason_not_started">Παραλείπεται επειδή δεν έχουν διαβαστεί κεφάλαια</string>
<string name="notification_update_error">%1$d ενημέρωση(ες) απέτυχε(-αν)</string>
<string name="learn_more">Πατήστε για να μάθετε περισσότερα</string>
<string name="rotation_reverse_portrait">Αντίστροφο πορτρέτο</string>
<string name="action_move_to_top_all_for_series">Μετακίνηση σειράς προς τα πάνω</string>
@@ -901,7 +899,6 @@
<string name="clear_db_exclude_read">Κρατήστε καταχωρήσεις με αναγνωσμένα κεφάλαια</string>
<string name="storage_failed_to_create_download_directory">Αποτυχία δημιουργίας καταλόγου λήψης</string>
<string name="storage_failed_to_create_directory">Αποτυχία δημιουργίας καταλόγου: %s</string>
<string name="label_donate">Δωρεά</string>
<string name="pref_display_images_description">Αναπαράσταση εικόνων σε περιγραφές manga</string>
<string name="pref_disallow_non_ascii_filenames">Απαγόρευση μη ASCII ονομάτων αρχείων</string>
<string name="pref_disallow_non_ascii_filenames_details">Εξασφαλίζει συμβατότητα με ορισμένα μέσα αποθήκευσης που δεν υποστηρίζουν Unicode. Όταν αυτή η επιλογή είναι ενεργοποιημένη, θα πρέπει να μετονομάσετε χειροκίνητα τους φακέλους πηγής και manga, αντικαθιστώντας τους χαρακτήρες που δεν είναι ASCII με τις μικρές κεφαλαίες δεκαεξαδικές αναπαραστάσεις UTF-8. Τα αρχεία κεφαλαίων δε χρειάζεται να μετονομάζονται.</string>
@@ -909,7 +906,7 @@
<string name="pref_download_concurrent_sources">Ταυτόχρονες λήψεις πηγών</string>
<string name="pref_download_concurrent_pages">Ταυτόχρονες λήψεις σελίδων</string>
<string name="pref_download_concurrent_pages_summary">Σελίδες που λήφθηκαν ταυτόχρονα ανά πηγή</string>
<string name="calculating">Υπολογισμός…</string>
<string name="download_bookmarked">Προστέθηκε στους σελιδοδείκτες</string>
<string name="action_filter_excluded_scanlators">Φιλτράρισμα εξαιρούμενων scanlator</string>
<string name="label_support_us">Υποστηρίξτε μας</string>
</resources>
@@ -297,7 +297,6 @@
<string name="login">Ensaluti</string>
<string name="login_title">Ensaluti al %1$s</string>
<string name="label_downloaded_only">Nur elŝutitaj</string>
<string name="updated_version">Ĝisdatigita al v%1$s</string>
<string name="licenses">Malfermitkodaj permesiloj</string>
<string name="whats_new">Kio novas</string>
<string name="version">Versio</string>
@@ -815,7 +814,6 @@
<string name="download_queue_size_warning">Averto: grandaj amasaj elŝutoj povas igi fontojn fariĝi pli malrapidaj kaj/aŭ bloki %s. Tuŝetu por ekscii pli.</string>
<string name="notification_updating_progress">Ĝisdatigado de biblioteko… (%s)</string>
<string name="notification_size_warning">Grandaj ĝisdatigoj povas damaĝi fontojn kaj konduki al pli malrapidaj ĝisdatigoj kaj pliigan baterian uzadon. Tuŝetu por ekscii pli.</string>
<string name="notification_update_error">%1$d ĝisdatigo(j) fiaskis</string>
<string name="learn_more">Tuŝetu por ekscii pli</string>
<string name="notification_first_add_to_library">Bonvolu aldoni la elementon al via biblioteko antaŭ ol fari tion</string>
<string name="library_errors_help">Por helpo pri kiel ripari erarojn de bibliotekaj ĝisdatigoj, vidu %1$s</string>
@@ -889,7 +887,6 @@
<string name="migrationListScreen.migrateDialog.cancelLabel">Nuligi</string>
<string name="migrationListScreen.progressDialog.cancelLabel">Nuligi</string>
<string name="migrationListScreen.matchWithoutChapterToast">Neniu ĉapitro trovita, ĉi tiu elemento ne povas esti uzota por migri</string>
<string name="label_donate">Donacu</string>
<string name="theme_catppuccin">Katpuĉino</string>
<string name="pref_display_images_description">Montri bildojn en mangaaj priskriboj</string>
<string name="pref_hide_missing_chapter_indicators">Kaŝi mankantajn ĉapitrajn indikilojn</string>
@@ -901,6 +898,5 @@
<string name="pref_download_concurrent_pages">Kunrulaj elŝutoj de paĝoj</string>
<string name="pref_download_concurrent_pages_summary">Paĝoj elŝutitaj sammomente por fonto</string>
<string name="action_filter_excluded_scanlators">Filtri ekskluzivitajn skantradukistojn</string>
<string name="calculating">Kalkulado…</string>
<string name="download_bookmarked">Legosignita</string>
</resources>
@@ -110,4 +110,9 @@
<item quantity="many">¿Migrar estos %1$d elementos?</item>
<item quantity="other">¿Migrar estos %1$d elementos?</item>
</plurals>
<plurals name="notification_update_error">
<item quantity="one">No se ha podido actualizar %1$d elemento</item>
<item quantity="many">No se han podido actualizar %1$d elementos</item>
<item quantity="other">No se han podido actualizar %1$d elementos</item>
</plurals>
</resources>
@@ -391,7 +391,6 @@
<string name="tapping_inverted_none">No invertir</string>
<string name="pref_read_with_tapping_inverted">Invertir zonas de toque</string>
<string name="unknown_author">Autor desconocido</string>
<string name="updated_version">Actualizado a v%1$s</string>
<string name="whats_new">Novedades</string>
<string name="download_insufficient_space">No se pudo descargar ningún capítulo, queda muy poco espacio</string>
<string name="action_global_search_query">Buscar por «%1$s» en todas las fuentes</string>
@@ -563,7 +562,6 @@
<string name="skipped_reason_not_caught_up">Omitido porque todavía hay capítulos sin leer</string>
<string name="skipped_reason_not_started">Omitido porque no hay capítulos leídos</string>
<string name="learn_more">Ver más detalles</string>
<string name="notification_update_error">Actualizaciones fallidas: %1$d</string>
<string name="rotation_reverse_portrait">En vertical, al revés</string>
<string name="action_move_to_top_all_for_series">Mover al primer puesto</string>
<string name="disabled_nav">Desactivado</string>
@@ -894,7 +892,6 @@
<string name="migrationListScreenTitleWithProgress">Migrando (%1$d/%2$d)</string>
<string name="migrationListScreen.migrateNowActionLabel">Migrar ahora</string>
<string name="pref_hide_missing_chapter_indicators">Ocultar los indicadores de capítulos que falten</string>
<string name="label_donate">Donar</string>
<string name="pref_display_images_description">Ver imágenes en las descripciones de manga</string>
<string name="pref_disallow_non_ascii_filenames">Prohibir los nombres de archivo que no sean ASCII</string>
<string name="pref_disallow_non_ascii_filenames_details">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.</string>
@@ -903,6 +900,18 @@
<string name="pref_download_concurrent_pages_summary">Cantidad de páginas a descargar simultáneamente desde cada fuente remota</string>
<string name="action_webview_close_tab">Cerrar pestaña</string>
<string name="action_filter_excluded_scanlators">Ocultar equipos de traducción excluidos</string>
<string name="calculating">Calculando…</string>
<string name="download_bookmarked">Favoritos</string>
<string name="label_support_us">Apóyanos</string>
<string name="supportUsScreen.perks">Los patrocinadores obtienen acceso a canales exclusivos, con más beneficios por venir.</string>
<string name="supportUsScreen.donationPlatform.patreon">Patreon (preferido)</string>
<string name="supportUsScreen.donationPlatform.opencollective">OpenCollective</string>
<string name="supportUsScreen.currentlySupportedBy">Mihon cuenta actualmente con el apoyo de %d+ patrocinadores. Queremos agradecerles a cada uno de ustedes. Hagamos Mihon aún mejor juntos.</string>
<string name="supportUsScreen.contactForDetailsMessage">Para opciones alternativas o cualquier pregunta relacionada, únete a nuestro servidor de Discord.</string>
<string name="supportUsScreen.contactPlatform">Discord</string>
<string name="donationCampaign.title">¿Disfrutando la aplicación?</string>
<string name="donationCampaign.paragraph1">¡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.</string>
<string name="donationCampaign.paragraph2">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.</string>
<string name="donationCampaign.paragraph3">De todas formas, Mihon seguirá siendo mantenido por mí y los voluntarios, y se mantendrá como código abierto.</string>
<string name="donationCampaign.dismiss">Descartar</string>
<string name="donationCampaign.contactPlatform">Discord</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More