Compare commits

...

68 Commits

Author SHA1 Message Date
Tim Schneeberger 31f967235f Add Namicomi support for external chapters on MD (#1188) 2024-06-01 18:26:05 -04:00
Cuong M. Tran 7d6e746257 Fix build warning: remove non-default string resources (#1180) 2024-06-01 18:25:53 -04:00
Timo d306139047 Update README.md (#1174)
Changed DO and DON'T for bug repports to a link with the issue template.
2024-06-01 18:25:37 -04:00
Jobobby04 7e6811692e Fix WorkManager cancellation issues 2024-06-01 17:51:03 -04:00
renovate[bot] 8df0446020 chore(deps): update dependency gradle to v8.8 (#856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 116579d38c08c203fe2c4996419e277c7bf9c165)
2024-06-01 17:40:26 -04:00
renovate[bot] 8ccf8fc74d fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0-rc01 (#855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 098f925519502a99aef9eb33c9b88b1280351a73)
2024-06-01 17:40:18 -04:00
Jobobby04 4deaa41c53 Cleanup some build warnings 2024-06-01 14:52:54 -04:00
renovate[bot] c843789f66 fix(deps): update aboutlib.version to v11.2.1 (#846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 9f5db70572cee99243c850d2da55fe0a10d52809)
2024-06-01 14:02:25 -04:00
renovate[bot] 78da81fa42 fix(deps): update dependency com.google.firebase:firebase-analytics to v22.0.1 (#848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 1f286f1a357a6c0f8cf2a7274b11b175315de4ea)
2024-06-01 14:02:17 -04:00
renovate[bot] 4fc96f263d fix(deps): update dependency com.google.gms:google-services to v4.4.2 (#849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7ab7f5ac37e8c102ca259144f136616b59172e6d)
2024-06-01 14:02:02 -04:00
renovate[bot] 6a12b54ecb fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0-rc01 (#851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e567250b17c231abee151541fea1f97b269b516a)
2024-06-01 14:01:52 -04:00
renovate[bot] 8ffcd5efec fix(deps): update dependency androidx.appcompat:appcompat to v1.7.0 (#845)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 095da924b9a1d724e6a228cb4abcc4dfe948db7d)
2024-06-01 14:01:44 -04:00
renovate[bot] 26121efeb4 fix(deps): update lifecycle.version to v2.8.1 (#844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b9da98b527e3d9ac56e263f7b595768239fa718e)
2024-06-01 14:01:36 -04:00
renovate[bot] 1ecf3a567b fix(deps): update paging.version to v3.3.0 (#810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit af8696cb9060e652f41220ea4a6074568df8bc3d)
2024-06-01 14:01:28 -04:00
renovate[bot] 20fff5798d fix(deps): update dependency org.apache.commons:commons-compress to v1.26.2 (#826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit de5a64aa73c8a2229fa87e9baba9981197474f51)
2024-06-01 14:01:20 -04:00
renovate[bot] 0ba580ba30 fix(deps): update aboutlib.version to v11.2.0 (#823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 9b944092c75046b68ac38e2d9d3a5e49033d7588)
2024-06-01 14:01:10 -04:00
renovate[bot] aaf28ee4f1 fix(deps): update dependency com.android.tools.build:gradle to v8.4.1 (#818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 0cb1794a445c8c7f4e9c163bd850f1681cdbef6a)
2024-06-01 14:01:03 -04:00
renovate[bot] 8558c110a9 chore(deps): update kotlin and compose compiler (#800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 2f243fae11e48799437b1672ec1a8571552caacb)

# Conflicts:
#	gradle/compose.versions.toml
2024-06-01 14:00:54 -04:00
renovate[bot] 3865583c28 fix(deps): update lifecycle.version to v2.8.0 (#809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit d2e5c78074829ca7c7bace3e5bfee25ea430e44c)
2024-06-01 14:00:15 -04:00
renovate[bot] b2cc61f6fd fix(deps): update dependency androidx.annotation:annotation to v1.8.0 (#808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 5912d6b08f3624a385f820582e49f1dde1b55864)
2024-06-01 14:00:05 -04:00
renovate[bot] 47dd58de2a fix(deps): update dependency io.mockk:mockk to v1.13.11 (#803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 99b550ae0d9eaf530d644f7755162d9705c302f4)

# Conflicts:
#	gradle/libs.versions.toml
2024-06-01 13:59:55 -04:00
renovate[bot] 67d42c9c2b fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0-beta01 (#801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 8d187f786594356dd47899dad660d602b52099ef)
2024-06-01 13:59:25 -04:00
renovate[bot] b97f322d6f fix(deps): update dependency com.google.android.material:material to v1.12.0 (#754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 653d5d3e252ff2508b100b0f291fdcfaf2a98097)
2024-06-01 13:59:17 -04:00
renovate[bot] d8cc4f8b45 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0-beta01 (#797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit ce497003e38d838e846b6e4ce88aafe4610d522b)
2024-06-01 13:57:49 -04:00
CrepeTF f78752fbdf Update themes to follow new compose update changes (#766)
* Update Green Apple theme

* Add some Green Apple theme comments

* Update Lavender theme

* Update Midnight Dusk theme

* Update Nord theme

* Update Strawberry Daiquiri theme

* Update Tako theme

* Update Teal & Turquoise theme

* Update Lavender secondaryContainer and onSecondaryContainer colour

* Update M.Dusk secondaryContainer and onSecondaryContainer colour

* Update Tako secondaryContainer and onSecondaryContainer colour

* Comments

* Update Tidal Wave theme

* Update Yin Yang theme

* Update Yotsuba theme

* Fix navbar tinted background on pure black

* Add surfaceContainer levels to Lavender theme

* Resolve detekt issues

* Add surfaceContainer levels to Midnight Dusk theme

* Add surfaceContainer levels to Nord theme

* Add surfaceContainer levels to Tako theme

* Add surfaceContainer levels to Teal & Turquoise theme

* Add surfaceContainer levels to Tidal Wave theme

* Add surfaceContainer levels to Yin Yang theme

* Add surfaceContainer levels to Yotsuba theme

* Add dark theme surfaceContainer levels to Yotsuba theme

* surfaceContainer tweaks to Yotsuba theme

* surfaceContainer tweaks to Strawberry Daiquiri theme

* surfaceContainer tweaks to Nord theme

* surfaceContainer tweaks to Lavender theme

* Update Tachiyomi theme

* Update Pure Black theme

* Resolve detekt issues

* Oopsie

(cherry picked from commit 16392adcbba4027cbed0a44e2fc62df330af6385)
2024-06-01 13:57:37 -04:00
AwkwardPeak7 d968d58cd6 update r8 rules for MultipartBody.Builder in extensions (#783)
(cherry picked from commit f603db3f3fac72954997bfa0c2cd77a0b92cd7f0)
2024-06-01 13:57:27 -04:00
AntsyLich 54d5f9baaf Remove dependency on compose material 2 components
(cherry picked from commit fb9423028eb017c110cb805f2d0601e5b02e50f9)
2024-06-01 12:57:07 -04:00
FooIbar c1bf53e28a Fix tap control area shifting after zooming out (#767)
(cherry picked from commit 8e9396a9cfe5f5e87e4e5f2093421a3fa24d43db)
2024-06-01 12:56:57 -04:00
AntsyLich 517fd3a8f4 Use new SurfaceContainer color roles
Non-dynamic themes need to be updated

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit 1df87eabf2b301cf6fc60cfa5f9391756984b790)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt
2024-06-01 12:56:48 -04:00
AntsyLich 089d1aba57 Fix search bar style
(cherry picked from commit ca7391bbf3d49428f6321aa6baa03b0d2b5abff9)
2024-06-01 12:55:18 -04:00
Jobobby04 46bf139f01 Possibly fix extension obsolete bug 2024-05-05 13:17:29 -04:00
Jobobby04 c3fb5c0bec Revert "Bump compose version"
This reverts commit 5550ddad4e.
2024-05-05 12:58:03 -04:00
Reagan 000a4ffc3f Change keyboard type in extension repo dialog (#764)
(cherry picked from commit 550f1197e818c35c7c05fd6184e69c7d29559e9f)
2024-05-05 12:57:45 -04:00
Jobobby04 7b0b879d65 Downgrade crashlytics plugin 2024-05-05 00:07:41 -04:00
Dexroneum 8a622f6c7d [RU] Translations (#1161)
* [RU] Translations

* [RU] Deleted unused strings
2024-05-04 23:39:57 -04:00
Jobobby04 253060a3bc Minor cleanup 2024-05-04 23:15:17 -04:00
Jobobby04 b6b33e8c00 Get new page url on image fetch failure for EHentai 2024-05-04 23:13:52 -04:00
Jobobby04 2e4f811090 Add getImageUrl override to EHentai 2024-05-04 23:13:26 -04:00
Jobobby04 215a1908f7 Include lewd filter in filter highlight 2024-05-04 23:13:06 -04:00
Jobobby04 082acf000c Fix Local Manga details edit 2024-05-04 23:12:45 -04:00
Jobobby04 2d1240b274 Update dependencies and cleanup 2024-05-04 18:41:03 -04:00
Jobobby04 1e98709cc3 Revert "Fix badge count getting cut off on tab title"
This reverts commit f9148c0c5e.
2024-05-04 18:12:54 -04:00
AntsyLich 5550ddad4e Bump compose version
Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit e473c7f09fc009161145aca94bd70027f042b0bf)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchResultItems.kt
#	app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt
2024-05-04 17:07:27 -04:00
AntsyLich f9148c0c5e Fix badge count getting cut off on tab title
Fixes #335

(cherry picked from commit 263e467cdeb948b8f3679e2ea0282a291cf2f131)
2024-05-04 16:57:20 -04:00
Radon Rosborough 089e6268e7 Massively improve findFile performance (#728)
* Massively improve findFile performance

* Update libs.versions.toml

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 7ec2108812fbe0483111dbe996e29e5a621b583a)
2024-05-04 16:56:45 -04:00
AntsyLich 712cd1493f Address firebase ktx module deprecation
(cherry picked from commit 28dca3b7b818ad095008e7cd49ec07a82b0ebcad)

# Conflicts:
#	gradle/libs.versions.toml
2024-05-04 16:52:36 -04:00
AntsyLich bbc8adc3e8 Trust extension by repo (#570)
(cherry picked from commit 70cd688ac245a70a3146e2ac7374f24b0c3453ab)
2024-05-04 16:51:47 -04:00
AntsyLich 077b673c0a Fix some extension related issue and cleanups
- Extension being marked as not installed instead of untrusted after updating with private installer
- Extension update counter not updating due to extension being marked as untrusted
- Minimize `Key "extension-XXX-YYY" was already used` crash

(cherry picked from commit 21145144cdf550aa775047603e06e261951ebc42)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
2024-05-04 16:51:26 -04:00
renovate[bot] 49eacf5178 fix(deps): update leakcanary to v2.14 (#715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit fa6dba6cc76f0b08cbc9bf222b0e087f4fb16d76)

# Conflicts:
#	gradle/libs.versions.toml
2024-05-04 16:01:19 -04:00
renovate[bot] 98d1dddf4a fix(deps): update dependency com.android.tools.build:gradle to v8.4.0 (#753)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 8a51d56c594e1f7ae4ebc01fc6a639292dde78bd)
2024-05-04 16:00:27 -04:00
renovate[bot] 37a616f3db fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0-alpha04 (#749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit a2f7d47a0a65bf88ac609b2227d440a7a2f841bf)
2024-05-04 16:00:14 -04:00
renovate[bot] ad18696a1a fix(deps): update dependency androidx.core:core-ktx to v1.13.1 (#748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b720f34267e4111466cdabf3a298d006231e4b55)
2024-05-04 15:59:53 -04:00
renovate[bot] 34bb012a1c fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0-alpha04 (#751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit c6a1412f18cb16f89c4ddeadb3448c141a49072e)
2024-05-04 15:59:43 -04:00
renovate[bot] 08c4989aa3 fix(deps): update aboutlib.version to v11.1.4 (#744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 6290cf222df922240575e2199459ab7b707d6ae2)

# Conflicts:
#	gradle/libs.versions.toml
2024-05-04 15:59:38 -04:00
FooIbar 14dae420f5 Log app crash exceptions in dumped crash logs (#742)
(cherry picked from commit a3d438e2f5b427eb8b4c391ab9fe10c5a83baf29)
2024-05-04 15:59:04 -04:00
w 65ed3c5ae6 Update subsampling-scale-image-view (#687)
Update libs.versions.toml

(cherry picked from commit 80461d883f7d6ca2203ae7455223ff49e8ef96ab)
2024-05-04 15:58:49 -04:00
FooIbar 5ae3508665 Use Coil pipeline instead of SSIV for image decode (#692)
(cherry picked from commit c3e7bb12f4cccf42dd3ea169111c771876e640fe)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt
2024-05-04 15:58:41 -04:00
MajorTanya e32eb0e009 Add MyAnimeList issue autoclose (#703)
[skip ci] Add MyAnimeList issue autoclose

This rule is intended to automatically close issues that report
problems with linking MAL that would be solved with the standard
solution of updating & changing the default UA.

The RegEx might be too general, but there isn't any neat pattern in
the previously filed issues.

(cherry picked from commit 9a3ffe2ea6cbf7ef2c2966c304a54b715a5fa682)
2024-05-04 15:51:26 -04:00
renovate[bot] e0812ab5c8 fix(deps): update dependency androidx.compose.compiler:compiler to v1.5.12 (#685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 213effa169e28e144f3e323290d865b02d0bf94b)
2024-05-04 15:51:13 -04:00
renovate[bot] df9f79c120 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.4 (#684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 25570147a1ca8ac374a75d7f29cf105bd686954b)
2024-05-04 15:51:04 -04:00
renovate[bot] 990eb33b98 fix(deps): update dependency androidx.activity:activity-compose to v1.9.0 (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 2ad462b4d882c4a03359092515aa6b8d3cb4fd5d)
2024-05-04 15:50:50 -04:00
renovate[bot] e1bab1172a fix(deps): update dependency androidx.core:core-ktx to v1.13.0 (#690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7fd8f653529b8e1488dd57c051000abf2a80ed12)
2024-05-04 15:50:41 -04:00
FooIbar aeeff72bed Use Okio instead of java.io for image processing (#691)
(cherry picked from commit b152e3881bffd9050a8a0ed4030823886e3fe04f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
2024-05-04 15:50:20 -04:00
FooIbar 5895e78b39 Use m3 ripple and clean up interactionSource usage (#675)
Also remove a leftover of scoped storage adaptation.

(cherry picked from commit f27ca3b1b2f92258c213bca6b27d8eff4c7363ad)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt
2024-05-04 15:05:16 -04:00
FooIbar b24719a3e9 Update compose bom and fix renovate config for it (#674)
(cherry picked from commit 843daa5304d0b1a93ba69f8cc69791e446a58596)

# Conflicts:
#	.github/renovate.json5
2024-05-04 15:04:40 -04:00
renovate[bot] d551619d9d fix(deps): update dependency com.google.firebase:firebase-analytics-ktx to v21.6.2 (#656)
Update dependency com.google.firebase:firebase-analytics-ktx to v21.6.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f080a4937e61d3dde5473876c34db8f16844e30c)

# Conflicts:
#	gradle/libs.versions.toml
2024-05-04 15:04:12 -04:00
renovate[bot] 06ad6c2e16 Update aboutlib.version to v11.1.3 (#654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 4c43a0ef66e9c8a321fb745b860319aaa074c57f)

# Conflicts:
#	gradle/libs.versions.toml
2024-05-04 15:03:39 -04:00
renovate[bot] df7e470e08 Update dependency com.android.tools.build:gradle to v8.3.2 (#655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit ea0fe2414e1e30b6e82ddf65144035283b31a5c4)
2024-05-04 15:02:59 -04:00
91 changed files with 1091 additions and 736 deletions
+6
View File
@@ -40,6 +40,12 @@ jobs:
"ignoreCase": true, "ignoreCase": true,
"labels": ["Cloudflare protected"], "labels": ["Cloudflare protected"],
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection." "message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
},
{
"type": "both",
"regex": "^.*(myanimelist|mal).*$",
"ignoreCase": true,
"message": "For issues with linking MyAnimeList, please follow these steps:\n1. Update Mihon to version 0.16.4 or newer\n2. Change your default User-Agent (`More → Settings → Advanced → Default user agent string`)\n3. Close and restart Mihon\n4. Attempt to link MyAnimeList again\n\nIf you had MyAnimeList linked before, try to unlink it first before trying these steps."
} }
] ]
auto-close-ignore-label: do-not-autoclose auto-close-ignore-label: do-not-autoclose
+1 -3
View File
@@ -88,9 +88,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
* If it could be device-dependent, try reproducing on another device (if possible) * If it could be device-dependent, try reproducing on another device (if possible)
* Don't group unrelated requests into one issue * Don't group unrelated requests into one issue
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71 Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose) to submit a bug.
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
</details> </details>
+1 -4
View File
@@ -29,7 +29,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 67 versionCode = 68
versionName = "1.10.5" versionName = "1.10.5"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@@ -155,7 +155,6 @@ dependencies {
implementation(compose.activity) implementation(compose.activity)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material3.core) implementation(compose.material3.core)
implementation(compose.material.core)
implementation(compose.material.icons) implementation(compose.material.icons)
implementation(compose.animation) implementation(compose.animation)
implementation(compose.animation.graphics) implementation(compose.animation.graphics)
@@ -254,7 +253,6 @@ dependencies {
implementation(libs.logcat) implementation(libs.logcat)
// Crash reports/analytics // Crash reports/analytics
// implementation(libs.bundles.acra)
// "standardImplementation"(libs.firebase.analytics) // "standardImplementation"(libs.firebase.analytics)
// Shizuku // Shizuku
@@ -315,7 +313,6 @@ tasks {
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi", "-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi", "-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
+4
View File
@@ -47,6 +47,10 @@
-dontnote rx.internal.util.PlatformDependent -dontnote rx.internal.util.PlatformDependent
##---------------End: proguard configuration for RxJava 1.x ---------- ##---------------End: proguard configuration for RxJava 1.x ----------
##---------------Begin: proguard configuration for okhttp ----------
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
##---------------End: proguard configuration for okhttp ----------
##---------------Begin: proguard configuration for kotlinx.serialization ---------- ##---------------Begin: proguard configuration for kotlinx.serialization ----------
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.** # core serialization annotations -dontnote kotlinx.serialization.** # core serialization annotations
@@ -179,7 +179,7 @@ class DomainModule : InjektModule {
addFactory { ToggleLanguage(get()) } addFactory { ToggleLanguage(get()) }
addFactory { ToggleSource(get()) } addFactory { ToggleSource(get()) }
addFactory { ToggleSourcePin(get()) } addFactory { ToggleSourcePin(get()) }
addFactory { TrustExtension(get()) } addFactory { TrustExtension(get(), get()) }
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) } addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
addFactory { ExtensionRepoService(get(), get()) } addFactory { ExtensionRepoService(get(), get()) }
@@ -2,8 +2,6 @@ package eu.kanade.domain.base
import android.content.Context import android.content.Context
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -22,8 +20,6 @@ class BasePreferences(
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore) fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false) fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) { enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
@@ -20,7 +20,7 @@ class GetExtensionsByType(
extensionManager.installedExtensionsFlow, extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow, extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow, extensionManager.availableExtensionsFlow,
) { _activeLanguages, _installed, _untrusted, _available -> ) { enabledLanguages, _installed, _untrusted, _available ->
val (updates, installed) = _installed val (updates, installed) = _installed
.filter { (showNsfwSources || !it.isNsfw) } .filter { (showNsfwSources || !it.isNsfw) }
.sortedWith( .sortedWith(
@@ -41,9 +41,9 @@ class GetExtensionsByType(
} }
.flatMap { ext -> .flatMap { ext ->
if (ext.sources.isEmpty()) { if (ext.sources.isEmpty()) {
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList() return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
} }
ext.sources.filter { it.lang in _activeLanguages } ext.sources.filter { it.lang in enabledLanguages }
.map { .map {
ext.copy( ext.copy(
name = it.name, name = it.name,
@@ -3,15 +3,18 @@ package eu.kanade.domain.extension.interactor
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
class TrustExtension( class TrustExtension(
private val extensionRepoRepository: ExtensionRepoRepository,
private val preferences: SourcePreferences, private val preferences: SourcePreferences,
) { ) {
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean { suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash" val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
return key in preferences.trustedExtensions().get() val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
} }
fun trust(pkgName: String, versionCode: Long, signatureHash: String) { fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
@@ -19,9 +22,7 @@ class TrustExtension(
// Remove previously trusted versions // Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
removed.also { removed.also { it += "$pkgName:$versionCode:$signatureHash" }
it += "$pkgName:$versionCode:$signatureHash"
}
} }
} }
@@ -5,7 +5,6 @@ import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -362,10 +361,8 @@ private fun InfoText(
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge, primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
) { ) {
val interactionSource = remember { MutableInteractionSource() }
val clickableModifier = if (onClick != null) { val clickableModifier = if (onClick != null) {
Modifier.clickable(interactionSource, indication = null) { onClick() } Modifier.clickable(interactionSource = null, indication = null, onClick = onClick)
} else { } else {
Modifier Modifier
} }
@@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
@@ -23,7 +21,6 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl
@Composable @Composable
fun NavigatorAdaptiveSheet( fun NavigatorAdaptiveSheet(
screen: Screen, screen: Screen,
tonalElevation: Dp = 1.dp,
enableSwipeDismiss: (Navigator) -> Boolean = { true }, enableSwipeDismiss: (Navigator) -> Boolean = { true },
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
@@ -31,7 +28,6 @@ fun NavigatorAdaptiveSheet(
screen = screen, screen = screen,
content = { sheetNavigator -> content = { sheetNavigator ->
AdaptiveSheet( AdaptiveSheet(
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator), enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
@@ -73,7 +69,6 @@ fun NavigatorAdaptiveSheet(
fun AdaptiveSheet( fun AdaptiveSheet(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
tonalElevation: Dp = 1.dp,
enableSwipeDismiss: Boolean = true, enableSwipeDismiss: Boolean = true,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@@ -86,7 +81,6 @@ fun AdaptiveSheet(
AdaptiveSheetImpl( AdaptiveSheetImpl(
modifier = modifier, modifier = modifier,
isTabletUi = isTabletUi, isTabletUi = isTabletUi,
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss, enableSwipeDismiss = enableSwipeDismiss,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
@@ -21,6 +20,7 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -312,7 +312,7 @@ fun SearchToolbar(
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
interactionSource = interactionSource, interactionSource = interactionSource,
decorationBox = { innerTextField -> decorationBox = { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox( TextFieldDefaults.DecorationBox(
value = searchQuery, value = searchQuery,
innerTextField = innerTextField, innerTextField = innerTextField,
enabled = true, enabled = true,
@@ -331,6 +331,7 @@ fun SearchToolbar(
), ),
) )
}, },
container = {},
) )
}, },
) )
@@ -58,6 +58,7 @@ fun TabbedDialog(
PrimaryTabRow( PrimaryTabRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
divider = {}, divider = {},
) { ) {
tabTitles.fastForEachIndexed { index, tab -> tabTitles.fastForEachIndexed { index, tab ->
@@ -37,7 +37,7 @@ fun CrashScreen(
acceptText = stringResource(MR.strings.pref_dump_crash_logs), acceptText = stringResource(MR.strings.pref_dump_crash_logs),
onAcceptClick = { onAcceptClick = {
scope.launch { scope.launch {
CrashLogUtil(context).dumpLogs() CrashLogUtil(context).dumpLogs(exception)
} }
}, },
rejectText = stringResource(MR.strings.crash_screen_restart_application), rejectText = stringResource(MR.strings.crash_screen_restart_application),
@@ -9,13 +9,13 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.outlined.ArrowDownward import androidx.compose.material.icons.outlined.ArrowDownward
import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.ripple
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -8,7 +8,6 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -33,12 +32,12 @@ import androidx.compose.material.icons.outlined.Label
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.icons.outlined.SwapCalls import androidx.compose.material.icons.outlined.SwapCalls
import androidx.compose.material.ripple
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
@@ -90,7 +89,7 @@ fun MangaBottomActionMenu(
Surface( Surface(
modifier = modifier, modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
@@ -199,7 +198,7 @@ private fun RowScope.Button(
.size(48.dp) .size(48.dp)
.weight(animatedWeight) .weight(animatedWeight)
.combinedClickable( .combinedClickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = null,
indication = ripple(bounded = false), indication = ripple(bounded = false),
onLongClick = onLongClick, onLongClick = onLongClick,
onClick = onClick, onClick = onClick,
@@ -252,7 +251,7 @@ fun LibraryBottomActionMenu(
Surface( Surface(
modifier = modifier, modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = val confirm =
@@ -1,6 +1,7 @@
package eu.kanade.presentation.more.settings.screen.browse.components package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -14,6 +15,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.KeyboardType
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.model.ExtensionRepo
@@ -74,6 +76,7 @@ fun ExtensionRepoCreateDialog(
Text(text = stringResource(msgRes)) Text(text = stringResource(msgRes))
}, },
isError = name.isNotEmpty() && nameAlreadyExists, isError = name.isNotEmpty() && nameAlreadyExists,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
singleLine = true, singleLine = true,
) )
} }
@@ -223,13 +223,12 @@ fun AppThemePreviewItem(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
) { ) {
Surface( Surface(
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainer,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.height(32.dp) .height(32.dp)
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -43,9 +43,7 @@ fun ReaderPageActionsDialog(
var useExtraPage by remember { mutableStateOf(false) } var useExtraPage by remember { mutableStateOf(false) }
// SY <-- // SY <--
AdaptiveSheet( AdaptiveSheet(onDismissRequest = onDismissRequest) {
onDismissRequest = onDismissRequest,
) {
Column(modifier = Modifier.padding(vertical = 16.dp)) { Column(modifier = Modifier.padding(vertical = 16.dp)) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
@@ -8,6 +8,12 @@ internal abstract class BaseColorScheme {
abstract val darkScheme: ColorScheme abstract val darkScheme: ColorScheme
abstract val lightScheme: ColorScheme abstract val lightScheme: ColorScheme
// Cannot be pure black as there's content scrolling behind it
// https://m3.material.io/components/navigation-bar/guidelines#90615a71-607e-485e-9e09-778bfc080563
private val surfaceContainer = Color(0xFF0C0C0C)
private val surfaceContainerHigh = Color(0xFF131313)
private val surfaceContainerHighest = Color(0xFF1B1B1B)
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
if (!isDark) return lightScheme if (!isDark) return lightScheme
@@ -18,6 +24,12 @@ internal abstract class BaseColorScheme {
onBackground = Color.White, onBackground = Color.White,
surface = Color.Black, surface = Color.Black,
onSurface = Color.White, onSurface = Color.White,
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
surfaceContainerLowest = surfaceContainer,
surfaceContainerLow = surfaceContainer,
surfaceContainer = surfaceContainer, // Navigation bar background
surfaceContainerHigh = surfaceContainerHigh,
surfaceContainerHighest = surfaceContainerHighest,
) )
} }
} }
@@ -19,53 +19,77 @@ internal object GreenAppleColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFF7ADB8F), primary = Color(0xFF7ADB8F),
onPrimary = Color(0xFF003915), onPrimary = Color(0xFF003917),
primaryContainer = Color(0xFF005322), primaryContainer = Color(0xFF017737),
onPrimaryContainer = Color(0xFF96F8A9), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFF7ADB8F), // Unread badge
secondary = Color(0xFF7ADB8F), onSecondary = Color(0xFF003917), // Unread badge text
onSecondary = Color(0xFF003915), secondaryContainer = Color(0xFF017737), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF005322), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF96F8A9), tertiary = Color(0xFFFFB3AC), // Downloaded badge
tertiary = Color(0xFFFFB3AA), onTertiary = Color(0xFF680008), // Downloaded badge text
onTertiary = Color(0xFF680006), tertiaryContainer = Color(0xFFC7282A),
tertiaryContainer = Color(0xFF93000D), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFFFFDAD5), error = Color(0xFFFFB4AB),
background = Color(0xFF1A1C19), onError = Color(0xFF690005),
onBackground = Color(0xFFE1E3DD), errorContainer = Color(0xFF93000A),
surface = Color(0xFF1A1C19), onErrorContainer = Color(0xFFFFDAD6),
onSurface = Color(0xFFE1E3DD), background = Color(0xFF0F1510),
surfaceVariant = Color(0xFF414941), onBackground = Color(0xFFDFE4DB),
onSurfaceVariant = Color(0xFFC1C8BE), surface = Color(0xFF0F1510),
surfaceTint = Color(0xFF7ADB8F), onSurface = Color(0xFFDFE4DB),
inverseSurface = Color(0xFFE1E3DD), surfaceVariant = Color(0xFF3F493F), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFF1A1C19), onSurfaceVariant = Color(0xFFBECABC),
outline = Color(0xFF8B9389), outline = Color(0xFF889487),
outlineVariant = Color(0xFF3F493F),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFFDFE4DB),
inverseOnSurface = Color(0xFF2C322C),
inversePrimary = Color(0xFF006D32),
surfaceDim = Color(0xFF0F1510),
surfaceBright = Color(0xFF353B35),
surfaceContainerLowest = Color(0xFF0A0F0B),
surfaceContainerLow = Color(0xFF181D18),
surfaceContainer = Color(0xFF1C211C), // Navigation bar background
surfaceContainerHigh = Color(0xFF262B26),
surfaceContainerHighest = Color(0xFF313630),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF006D2F), primary = Color(0xFF005927),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF96F8A9), primaryContainer = Color(0xFF188140),
onPrimaryContainer = Color(0xFF002109), onPrimaryContainer = Color(0xFFFFFFFF),
secondary = Color(0xFF005927), // Unread badge
onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFF97f7a9), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF000000), // Navigation bar selected icon
tertiary = Color(0xFF9D0012), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFD33131),
onTertiaryContainer = Color(0xFFFFFFFF),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFF6FBF2),
onBackground = Color(0xFF181D18),
surface = Color(0xFFF6FBF2),
onSurface = Color(0xFF181D18),
surfaceVariant = Color(0xFFDAE6D7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF3F493F),
outline = Color(0xFF6F7A6E),
outlineVariant = Color(0xFFBECABC),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF2C322C),
inverseOnSurface = Color(0xFFEDF2E9),
inversePrimary = Color(0xFF7ADB8F), inversePrimary = Color(0xFF7ADB8F),
secondary = Color(0xFF006D2F), surfaceDim = Color(0xFFD6DCD3),
onSecondary = Color(0xFFFFFFFF), surfaceBright = Color(0xFFF6FBF2),
secondaryContainer = Color(0xFF96F8A9), surfaceContainerLowest = Color(0xFFFFFFFF),
onSecondaryContainer = Color(0xFF002109), surfaceContainerLow = Color(0xFFF0F5EC),
tertiary = Color(0xFFB91D22), surfaceContainer = Color(0xFFEAEFE6), // Navigation bar background
onTertiary = Color(0xFFFFFFFF), surfaceContainerHigh = Color(0xFFE4EAE1),
tertiaryContainer = Color(0xFFFFDAD5), surfaceContainerHighest = Color(0xFFDFE4DB),
onTertiaryContainer = Color(0xFF410003),
background = Color(0xFFFBFDF7),
onBackground = Color(0xFF1A1C19),
surface = Color(0xFFFBFDF7),
onSurface = Color(0xFF1A1C19),
surfaceVariant = Color(0xFFDDE5DA),
onSurfaceVariant = Color(0xFF414941),
surfaceTint = Color(0xFF006D2F),
inverseSurface = Color(0xFF2F312E),
inverseOnSurface = Color(0xFFF0F2EC),
outline = Color(0xFF717970),
) )
} }
@@ -18,53 +18,77 @@ internal object LavenderColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFA177FF), primary = Color(0xFFA177FF),
onPrimary = Color(0xFF111129), onPrimary = Color(0xFF3D0090),
primaryContainer = Color(0xFFA177FF), primaryContainer = Color(0xFFA177FF),
onPrimaryContainer = Color(0xFF111129), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFFA177FF), // Unread badge
secondary = Color(0xFFA177FF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFF111129), secondaryContainer = Color(0xFF423271), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFA177FF), onSecondaryContainer = Color(0xFFA177FF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF111129), tertiary = Color(0xFFCDBDFF), // Downloaded badge
tertiary = Color(0xFF5E25E1), onTertiary = Color(0xFF360096), // Downloaded badge text
onTertiary = Color(0xFFE8E8E8), tertiaryContainer = Color(0xFF5512D8),
tertiaryContainer = Color(0xFF111129), onTertiaryContainer = Color(0xFFEFE6FF),
onTertiaryContainer = Color(0xFFDEE8FF), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF111129), background = Color(0xFF111129),
onBackground = Color(0xFFDEE8FF), onBackground = Color(0xFFE7E0EC),
surface = Color(0xFF111129), surface = Color(0xFF111129),
onSurface = Color(0xFFDEE8FF), onSurface = Color(0xFFE7E0EC),
surfaceVariant = Color(0x2CB6B6B6), surfaceVariant = Color(0xFF3D2F6B), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFE8E8E8), onSurfaceVariant = Color(0xFFCBC3D6),
surfaceTint = Color(0xFFA177FF), outline = Color(0xFF958E9F),
inverseSurface = Color(0xFF221247), outlineVariant = Color(0xFF4A4453),
inverseOnSurface = Color(0xFFDEE8FF), scrim = Color(0xFF000000),
outline = Color(0xA8905FFF), inverseSurface = Color(0xFFE7E0EC),
inverseOnSurface = Color(0xFF322F38),
inversePrimary = Color(0xFF6D41C8),
surfaceDim = Color(0xFF111129),
surfaceBright = Color(0xFF3B3841),
surfaceContainerLowest = Color(0xFF15132d),
surfaceContainerLow = Color(0xFF171531),
surfaceContainer = Color(0xFF1D193B), // Navigation bar background
surfaceContainerHigh = Color(0xFF241f41),
surfaceContainerHighest = Color(0xFF282446),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF7B46AF), primary = Color(0xFF6D41C8),
onPrimary = Color(0xFFEDE2FF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF7B46AF), primaryContainer = Color(0xFF7B46AF),
onPrimaryContainer = Color(0xFFEDE2FF), onPrimaryContainer = Color(0xFF130038),
inversePrimary = Color(0xFFD6BAFF), secondary = Color(0xFF7B46AF), // Unread badge
secondary = Color(0xFF7B46AF), onSecondary = Color(0xFFEDE2FF), // Unread badge text
onSecondary = Color(0xFFEDE2FF), secondaryContainer = Color(0xFFC9B0E6), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF7B46AF), onSecondaryContainer = Color(0xFF7B46AF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFEDE2FF), tertiary = Color(0xFFEDE2FF), // Downloaded badge
tertiary = Color(0xFFEDE2FF), onTertiary = Color(0xFF7B46AF), // Downloaded badge text
onTertiary = Color(0xFF7B46AF), tertiaryContainer = Color(0xFF6D3BF0),
tertiaryContainer = Color(0xFFEDE2FF), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF7B46AF), error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFEDE2FF), background = Color(0xFFEDE2FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1D1A22),
surface = Color(0xFFEDE2FF), surface = Color(0xFFEDE2FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1D1A22),
surfaceVariant = Color(0xFFB9B0CC), surfaceVariant = Color(0xFFE4D5F8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xD849454E), onSurfaceVariant = Color(0xFF4A4453),
surfaceTint = Color(0xFF7B46AF), outline = Color(0xFF7B7485),
inverseSurface = Color(0xFF313033), outlineVariant = Color(0xFFCBC3D6),
inverseOnSurface = Color(0xFFF3EFF4), scrim = Color(0xFF000000),
outline = Color(0xFF7B46AF), inverseSurface = Color(0xFF322F38),
inverseOnSurface = Color(0xFFF5EEFA),
inversePrimary = Color(0xFFA177FF),
surfaceDim = Color(0xFFDED7E3),
surfaceBright = Color(0xFFEDE2FF),
surfaceContainerLowest = Color(0xFFDACCEC),
surfaceContainerLow = Color(0xFFDED0F1),
surfaceContainer = Color(0xFFE4D5F8), // Navigation bar background
surfaceContainerHigh = Color(0xFFEADCFD),
surfaceContainerHighest = Color(0xFFEEE2FF),
) )
} }
@@ -23,24 +23,29 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFBD1C5C), primaryContainer = Color(0xFFBD1C5C),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFF02475), inversePrimary = Color(0xFFF02475),
secondary = Color(0xFFF02475), secondary = Color(0xFFF02475), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFF16151D), // Unread badge text
secondaryContainer = Color(0xFFF02475), secondaryContainer = Color(0xFF66183C), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFFFFF), onSecondaryContainer = Color(0xFFF02475), // Navigation bar selector icon
tertiary = Color(0xFF55971C), tertiary = Color(0xFF55971C), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFF16151D), // Downloaded badge text
tertiaryContainer = Color(0xFF386412), tertiaryContainer = Color(0xFF386412),
onTertiaryContainer = Color(0xFFE5E1E5), onTertiaryContainer = Color(0xFFE5E1E5),
background = Color(0xFF16151D), background = Color(0xFF16151D),
onBackground = Color(0xFFE5E1E5), onBackground = Color(0xFFE5E1E5),
surface = Color(0xFF16151D), surface = Color(0xFF16151D),
onSurface = Color(0xFFE5E1E5), onSurface = Color(0xFFE5E1E5),
surfaceVariant = Color(0xFF524346), surfaceVariant = Color(0xFF281624), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD6C1C4), onSurfaceVariant = Color(0xFFD6C1C4),
surfaceTint = Color(0xFFF02475), surfaceTint = Color(0xFFF02475),
inverseSurface = Color(0xFF333043), inverseSurface = Color(0xFF333043),
inverseOnSurface = Color(0xFFFFFFFF), inverseOnSurface = Color(0xFFFFFFFF),
outline = Color(0xFF9F8C8F), outline = Color(0xFF9F8C8F),
surfaceContainerLowest = Color(0xFF221320),
surfaceContainerLow = Color(0xFF251522),
surfaceContainer = Color(0xFF281624), // Navigation bar background
surfaceContainerHigh = Color(0xFF2D1C2A),
surfaceContainerHighest = Color(0xFF2F1F2C),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFD9E1), primaryContainer = Color(0xFFFFD9E1),
onPrimaryContainer = Color(0xFF3F0017), onPrimaryContainer = Color(0xFF3F0017),
inversePrimary = Color(0xFFFFB1C4), inversePrimary = Color(0xFFFFB1C4),
secondary = Color(0xFFBB0054), secondary = Color(0xFFBB0054), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFD9E1), secondaryContainer = Color(0xFFEFBAD4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3F0017), onSecondaryContainer = Color(0xFFD1377C), // Navigation bar selector icon
tertiary = Color(0xFF006638), tertiary = Color(0xFF006638), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00894b), tertiaryContainer = Color(0xFF00894b),
onTertiaryContainer = Color(0xFF2D1600), onTertiaryContainer = Color(0xFF2D1600),
background = Color(0xFFFFFBFF), background = Color(0xFFFFFBFF),
onBackground = Color(0xFF1C1B1F), onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFF), surface = Color(0xFFFFFBFF),
onSurface = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFF3DDE0), surfaceVariant = Color(0xFFF9E6F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF524346), onSurfaceVariant = Color(0xFF524346),
surfaceTint = Color(0xFFBB0054), surfaceTint = Color(0xFFBB0054),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF4F0F4), inverseOnSurface = Color(0xFFF4F0F4),
outline = Color(0xFF847376), outline = Color(0xFF847376),
surfaceContainerLowest = Color(0xFFDAC0CD),
surfaceContainerLow = Color(0xFFE8D1DD),
surfaceContainer = Color(0xFFF9E6F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF3F8),
surfaceContainerHighest = Color(0xFFFEF9FC),
) )
} }
@@ -17,19 +17,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF88C0D0), primaryContainer = Color(0xFF88C0D0),
onPrimaryContainer = Color(0xFF2E3440), onPrimaryContainer = Color(0xFF2E3440),
inversePrimary = Color(0xFF397E91), inversePrimary = Color(0xFF397E91),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon
tertiary = Color(0xFF5E81AC), tertiary = Color(0xFF5E81AC), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF5E81AC), tertiaryContainer = Color(0xFF5E81AC),
onTertiaryContainer = Color(0xFF000000), onTertiaryContainer = Color(0xFF000000),
background = Color(0xFF2E3440), background = Color(0xFF2E3440),
onBackground = Color(0xFFECEFF4), onBackground = Color(0xFFECEFF4),
surface = Color(0xFF3B4252), surface = Color(0xFF2E3440),
onSurface = Color(0xFFECEFF4), onSurface = Color(0xFFECEFF4),
surfaceVariant = Color(0xFF2E3440), surfaceVariant = Color(0xFF414C5C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFECEFF4), onSurfaceVariant = Color(0xFFECEFF4),
surfaceTint = Color(0xFF88C0D0), surfaceTint = Color(0xFF88C0D0),
inverseSurface = Color(0xFFD8DEE9), inverseSurface = Color(0xFFD8DEE9),
@@ -39,6 +39,11 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFF2E3440), onError = Color(0xFF2E3440),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFF373F4D),
surfaceContainerLow = Color(0xFF3E4756),
surfaceContainer = Color(0xFF414C5C),
surfaceContainerHigh = Color(0xFF4E5766),
surfaceContainerHighest = Color(0xFF505968), // Navigation bar background
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -47,19 +52,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF5E81AC), primaryContainer = Color(0xFF5E81AC),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF8CA8CD), inversePrimary = Color(0xFF8CA8CD),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon
tertiary = Color(0xFF88C0D0), tertiary = Color(0xFF88C0D0), // Downloaded badge
onTertiary = Color(0xFF2E3440), onTertiary = Color(0xFF2E3440), // Downloaded badge text
tertiaryContainer = Color(0xFF88C0D0), tertiaryContainer = Color(0xFF88C0D0),
onTertiaryContainer = Color(0xFF2E3440), onTertiaryContainer = Color(0xFF2E3440),
background = Color(0xFFECEFF4), background = Color(0xFFECEFF4),
onBackground = Color(0xFF2E3440), onBackground = Color(0xFF2E3440),
surface = Color(0xFFE5E9F0), surface = Color(0xFFE5E9F0),
onSurface = Color(0xFF2E3440), onSurface = Color(0xFF2E3440),
surfaceVariant = Color(0xFFffffff), surfaceVariant = Color(0xFFDAE0EA), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF2E3440), onSurfaceVariant = Color(0xFF2E3440),
surfaceTint = Color(0xFF5E81AC), surfaceTint = Color(0xFF5E81AC),
inverseSurface = Color(0xFF3B4252), inverseSurface = Color(0xFF3B4252),
@@ -68,5 +73,10 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFFECEFF4), onError = Color(0xFFECEFF4),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFFD1D7E0),
surfaceContainerLow = Color(0xFFD6DCE6),
surfaceContainer = Color(0xFFDAE0EA), // Navigation bar background
surfaceContainerHigh = Color(0xFFE9EDF3),
surfaceContainerHighest = Color(0xFFF2F4F8),
) )
} }
@@ -18,54 +18,78 @@ import androidx.compose.ui.graphics.Color
internal object StrawberryColorScheme : BaseColorScheme() { internal object StrawberryColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFFFB2B9), primary = Color(0xFFFFB2B8),
onPrimary = Color(0xFF67001B), onPrimary = Color(0xFF67001D),
primaryContainer = Color(0xFF91002A), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFFFFDADD), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFB61E40), secondary = Color(0xFFED4A65), // Unread badge
secondary = Color(0xFFFFB2B9), onSecondary = Color(0xFF201A1A), // Unread badge text
onSecondary = Color(0xFF67001B), secondaryContainer = Color(0xFF91002A), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF91002A), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFFFDADD), tertiary = Color(0xFFE8C08E), // Downloaded badge
tertiary = Color(0xFFE8C08E), onTertiary = Color(0xFF201A1A), // Downloaded badge text
onTertiary = Color(0xFF432C06), tertiaryContainer = Color(0xFF775930),
tertiaryContainer = Color(0xFF5D421B), onTertiaryContainer = Color(0xFFFFF7F1),
onTertiaryContainer = Color(0xFFFFDDB1), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF201A1A), background = Color(0xFF201A1A),
onBackground = Color(0xFFECDFDF), onBackground = Color(0xFFF7DCDD),
surface = Color(0xFF201A1A), surface = Color(0xFF201A1A),
onSurface = Color(0xFFECDFDF), onSurface = Color(0xFFF7DCDD),
surfaceVariant = Color(0xFF534344), surfaceVariant = Color(0xFF322727), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD7C1C2), onSurfaceVariant = Color(0xFFE1BEC0),
surfaceTint = Color(0xFFFFB2B9), outline = Color(0xFFA9898B),
inverseSurface = Color(0xFFECDFDF), outlineVariant = Color(0xFF594042),
inverseOnSurface = Color(0xFF201A1A), scrim = Color(0xFF000000),
outline = Color(0xFFA08C8D), inverseSurface = Color(0xFFF7DCDD),
inverseOnSurface = Color(0xFF3D2C2D),
inversePrimary = Color(0xFFB61F40),
surfaceDim = Color(0xFF1D1011),
surfaceBright = Color(0xFF463536),
surfaceContainerLowest = Color(0xFF2C2222),
surfaceContainerLow = Color(0xFF302525),
surfaceContainer = Color(0xFF322727), // Navigation bar background
surfaceContainerHigh = Color(0xFF3C2F2F),
surfaceContainerHighest = Color(0xFF463737),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFFB61E40), primary = Color(0xFFA10833),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFFFDADD), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFF40000D), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFFFB2B9), secondary = Color(0xFFA10833), // Unread badge
secondary = Color(0xFFB61E40), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFFFFFFFF), secondaryContainer = Color(0xFFD53855), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFFFDADD), onSecondaryContainer = Color(0xFFF6EAED), // Navigation bar selector icon
onSecondaryContainer = Color(0xFF40000D), tertiary = Color(0xFF5F441D), // Downloaded badge
tertiary = Color(0xFF775930), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
onTertiary = Color(0xFFFFFFFF), tertiaryContainer = Color(0xFF87683D),
tertiaryContainer = Color(0xFFFFDDB1), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF2A1800), error = Color(0xFFBA1A1A),
background = Color(0xFFFCFCFC), onError = Color(0xFFFFFFFF),
onBackground = Color(0xFF201A1A), errorContainer = Color(0xFFFFDAD6),
surface = Color(0xFFFCFCFC), onErrorContainer = Color(0xFF410002),
onSurface = Color(0xFF201A1A), background = Color(0xFFFAFAFA),
surfaceVariant = Color(0xFFF4DDDD), onBackground = Color(0xFF261819),
onSurfaceVariant = Color(0xFF534344), surface = Color(0xFFFAFAFA),
surfaceTint = Color(0xFFB61E40), onSurface = Color(0xFF261819),
inverseSurface = Color(0xFF362F2F), surfaceVariant = Color(0xFFF6EAED), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFFFBEDED), onSurfaceVariant = Color(0xFF594042),
outline = Color(0xFF857374), outline = Color(0xFF8D7071),
outlineVariant = Color(0xFFE1BEC0),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF3D2C2D),
inverseOnSurface = Color(0xFFFFECED),
inversePrimary = Color(0xFFFFB2B8),
surfaceDim = Color(0xFFEED4D5),
surfaceBright = Color(0xFFFFF8F7),
surfaceContainerLowest = Color(0xFFF7DCDD),
surfaceContainerLow = Color(0xFFFDE2E3),
surfaceContainer = Color(0xFFF6EAED), // Navigation bar background
surfaceContainerHigh = Color(0xFFFFF0F0),
surfaceContainerHighest = Color(0xFFFFFFFF),
) )
} }
@@ -22,19 +22,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF00429B), primaryContainer = Color(0xFF00429B),
onPrimaryContainer = Color(0xFFD9E2FF), onPrimaryContainer = Color(0xFFD9E2FF),
inversePrimary = Color(0xFF0058CA), inversePrimary = Color(0xFF0058CA),
secondary = Color(0xFFB0C6FF), secondary = Color(0xFFB0C6FF), // Unread badge
onSecondary = Color(0xFF002D6E), onSecondary = Color(0xFF002D6E), // Unread badge text
secondaryContainer = Color(0xFF00429B), secondaryContainer = Color(0xFF00429B), // Navigation bar selector pill & pro
onSecondaryContainer = Color(0xFFD9E2FF), onSecondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector icon
tertiary = Color(0xFF7ADC77), tertiary = Color(0xFF7ADC77), // Downloaded badge
onTertiary = Color(0xFF003909), onTertiary = Color(0xFF003909), // Downloaded badge text
tertiaryContainer = Color(0xFF005312), tertiaryContainer = Color(0xFF005312),
onTertiaryContainer = Color(0xFF95F990), onTertiaryContainer = Color(0xFF95F990),
background = Color(0xFF1B1B1F), background = Color(0xFF1B1B1F),
onBackground = Color(0xFFE3E2E6), onBackground = Color(0xFFE3E2E6),
surface = Color(0xFF1B1B1F), surface = Color(0xFF1B1B1F),
onSurface = Color(0xFFE3E2E6), onSurface = Color(0xFFE3E2E6),
surfaceVariant = Color(0xFF44464F), surfaceVariant = Color(0xFF211F26), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFC5C6D0), onSurfaceVariant = Color(0xFFC5C6D0),
surfaceTint = Color(0xFFB0C6FF), surfaceTint = Color(0xFFB0C6FF),
inverseSurface = Color(0xFFE3E2E6), inverseSurface = Color(0xFFE3E2E6),
@@ -45,6 +45,11 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFFFFDAD6), onErrorContainer = Color(0xFFFFDAD6),
outline = Color(0xFF8F9099), outline = Color(0xFF8F9099),
outlineVariant = Color(0xFF44464F), outlineVariant = Color(0xFF44464F),
surfaceContainerLowest = Color(0xFF1A181D),
surfaceContainerLow = Color(0xFF1E1C22),
surfaceContainer = Color(0xFF211F26), // Navigation bar background
surfaceContainerHigh = Color(0xFF292730),
surfaceContainerHighest = Color(0xFF302E38),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -53,19 +58,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFD9E2FF), primaryContainer = Color(0xFFD9E2FF),
onPrimaryContainer = Color(0xFF001945), onPrimaryContainer = Color(0xFF001945),
inversePrimary = Color(0xFFB0C6FF), inversePrimary = Color(0xFFB0C6FF),
secondary = Color(0xFF0058CA), secondary = Color(0xFF0058CA), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFD9E2FF), secondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001945), onSecondaryContainer = Color(0xFF001945), // Navigation bar selector icon
tertiary = Color(0xFF006E1B), tertiary = Color(0xFF006E1B), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF95F990), tertiaryContainer = Color(0xFF95F990),
onTertiaryContainer = Color(0xFF002203), onTertiaryContainer = Color(0xFF002203),
background = Color(0xFFFEFBFF), background = Color(0xFFFEFBFF),
onBackground = Color(0xFF1B1B1F), onBackground = Color(0xFF1B1B1F),
surface = Color(0xFFFEFBFF), surface = Color(0xFFFEFBFF),
onSurface = Color(0xFF1B1B1F), onSurface = Color(0xFF1B1B1F),
surfaceVariant = Color(0xFFE1E2EC), surfaceVariant = Color(0xFFF3EDF7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF44464F), onSurfaceVariant = Color(0xFF44464F),
surfaceTint = Color(0xFF0058CA), surfaceTint = Color(0xFF0058CA),
inverseSurface = Color(0xFF303034), inverseSurface = Color(0xFF303034),
@@ -76,5 +81,10 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFF410002), onErrorContainer = Color(0xFF410002),
outline = Color(0xFF757780), outline = Color(0xFF757780),
outlineVariant = Color(0xFFC5C6D0), outlineVariant = Color(0xFFC5C6D0),
surfaceContainerLowest = Color(0xFFF5F1F8),
surfaceContainerLow = Color(0xFFF7F2FA),
surfaceContainer = Color(0xFFF3EDF7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF7FF),
surfaceContainerHighest = Color(0xFFFCF7FF),
) )
} }
@@ -23,24 +23,29 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFF3B375), primaryContainer = Color(0xFFF3B375),
onPrimaryContainer = Color(0xFF38294E), onPrimaryContainer = Color(0xFF38294E),
inversePrimary = Color(0xFF84531E), inversePrimary = Color(0xFF84531E),
secondary = Color(0xFFF3B375), secondary = Color(0xFFF3B375), // Unread badge
onSecondary = Color(0xFF38294E), onSecondary = Color(0xFF38294E), // Unread badge text
secondaryContainer = Color(0xFFF3B375), secondaryContainer = Color(0xFF5C4D4B), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF38294E), onSecondaryContainer = Color(0xFFF3B375), // Navigation bar selector icon
tertiary = Color(0xFF66577E), tertiary = Color(0xFF66577E), // Downloaded badge
onTertiary = Color(0xFFF3B375), onTertiary = Color(0xFFF3B375), // Downloaded badge text
tertiaryContainer = Color(0xFF4E4065), tertiaryContainer = Color(0xFF4E4065),
onTertiaryContainer = Color(0xFFEDDCFF), onTertiaryContainer = Color(0xFFEDDCFF),
background = Color(0xFF21212E), background = Color(0xFF21212E),
onBackground = Color(0xFFE3E0F2), onBackground = Color(0xFFE3E0F2),
surface = Color(0xFF21212E), surface = Color(0xFF21212E),
onSurface = Color(0xFFE3E0F2), onSurface = Color(0xFFE3E0F2),
surfaceVariant = Color(0xFF49454E), surfaceVariant = Color(0xFF2A2A3C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFCBC4CE), onSurfaceVariant = Color(0xFFCBC4CE),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFFE5E1E6), inverseSurface = Color(0xFFE5E1E6),
inverseOnSurface = Color(0xFF1B1B1E), inverseOnSurface = Color(0xFF1B1B1E),
outline = Color(0xFF958F99), outline = Color(0xFF958F99),
surfaceContainerLowest = Color(0xFF20202E),
surfaceContainerLow = Color(0xFF262636),
surfaceContainer = Color(0xFF2A2A3C), // Navigation bar background
surfaceContainerHigh = Color(0xFF303044),
surfaceContainerHighest = Color(0xFF36364D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF66577E), primaryContainer = Color(0xFF66577E),
onPrimaryContainer = Color(0xFFF3B375), onPrimaryContainer = Color(0xFFF3B375),
inversePrimary = Color(0xFFD6BAFF), inversePrimary = Color(0xFFD6BAFF),
secondary = Color(0xFF66577E), secondary = Color(0xFF66577E), // Unread badge
onSecondary = Color(0xFFF3B375), onSecondary = Color(0xFFF3B375), // Unread badge text
secondaryContainer = Color(0xFF66577E), secondaryContainer = Color(0xFFC8BED0), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFF3B375), onSecondaryContainer = Color(0xFF66577E), // Navigation bar selector icon
tertiary = Color(0xFFF3B375), tertiary = Color(0xFFF3B375), // Downloaded badge
onTertiary = Color(0xFF574360), onTertiary = Color(0xFF574360), // Downloaded badge text
tertiaryContainer = Color(0xFFFDD6B0), tertiaryContainer = Color(0xFFFDD6B0),
onTertiaryContainer = Color(0xFF221437), onTertiaryContainer = Color(0xFF221437),
background = Color(0xFFF7F5FF), background = Color(0xFFF7F5FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1B1B22),
surface = Color(0xFFF7F5FF), surface = Color(0xFFF7F5FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1B1B22),
surfaceVariant = Color(0xFFE8E0EB), surfaceVariant = Color(0xFFE8E0EB), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF49454E), onSurfaceVariant = Color(0xFF49454E),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF3EFF4), inverseOnSurface = Color(0xFFF3EFF4),
outline = Color(0xFF7A757E), outline = Color(0xFF7A757E),
surfaceContainerLowest = Color(0xFFD7D0DA),
surfaceContainerLow = Color(0xFFDFD8E2),
surfaceContainer = Color(0xFFE8E0EB), // Navigation bar background
surfaceContainerHigh = Color(0xFFEEE6F1),
surfaceContainerHighest = Color(0xFFF7EEFA),
) )
} }
@@ -15,24 +15,29 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF40E0D0), primaryContainer = Color(0xFF40E0D0),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF008080), inversePrimary = Color(0xFF008080),
secondary = Color(0xFF40E0D0), secondary = Color(0xFF40E0D0), // Unread badge
onSecondary = Color(0xFF000000), onSecondary = Color(0xFF000000), // Unread badge text
secondaryContainer = Color(0xFF18544E), secondaryContainer = Color(0xFF18544E), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF40E0D0), onSecondaryContainer = Color(0xFF40E0D0), // Navigation bar selector icon
tertiary = Color(0xFFBF1F2F), tertiary = Color(0xFFBF1F2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF200508), tertiaryContainer = Color(0xFF200508),
onTertiaryContainer = Color(0xFFBF1F2F), onTertiaryContainer = Color(0xFFBF1F2F),
background = Color(0xFF202125), background = Color(0xFF202125),
onBackground = Color(0xFFDFDEDA), onBackground = Color(0xFFDFDEDA),
surface = Color(0xFF202125), surface = Color(0xFF202125),
onSurface = Color(0xFFDFDEDA), onSurface = Color(0xFFDFDEDA),
surfaceVariant = Color(0xFF3F4947), surfaceVariant = Color(0xFF233133), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFDFDEDA), onSurfaceVariant = Color(0xFFDFDEDA),
surfaceTint = Color(0xFF40E0D0), surfaceTint = Color(0xFF40E0D0),
inverseSurface = Color(0xFFDFDEDA), inverseSurface = Color(0xFFDFDEDA),
inverseOnSurface = Color(0xFF202125), inverseOnSurface = Color(0xFF202125),
outline = Color(0xFF899391), outline = Color(0xFF899391),
surfaceContainerLowest = Color(0xFF202C2E),
surfaceContainerLow = Color(0xFF222F31),
surfaceContainer = Color(0xFF233133), // Navigation bar background
surfaceContainerHigh = Color(0xFF28383A),
surfaceContainerHighest = Color(0xFF2F4244),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -41,23 +46,28 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF008080), primaryContainer = Color(0xFF008080),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF40E0D0), inversePrimary = Color(0xFF40E0D0),
secondary = Color(0xFF008080), secondary = Color(0xFF008080), // Unread badge text
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFBFDFDF), secondaryContainer = Color(0xFFCFE5E4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF008080), onSecondaryContainer = Color(0xFF008080), // Navigation bar selector icon
tertiary = Color(0xFFFF7F7F), tertiary = Color(0xFFFF7F7F), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF2A1616), tertiaryContainer = Color(0xFF2A1616),
onTertiaryContainer = Color(0xFFFF7F7F), onTertiaryContainer = Color(0xFFFF7F7F),
background = Color(0xFFFAFAFA), background = Color(0xFFFAFAFA),
onBackground = Color(0xFF050505), onBackground = Color(0xFF050505),
surface = Color(0xFFFAFAFA), surface = Color(0xFFFAFAFA),
onSurface = Color(0xFF050505), onSurface = Color(0xFF050505),
surfaceVariant = Color(0xFFDAE5E2), surfaceVariant = Color(0xFFEBF3F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF050505), onSurfaceVariant = Color(0xFF050505),
surfaceTint = Color(0xFFBFDFDF), surfaceTint = Color(0xFFBFDFDF),
inverseSurface = Color(0xFF050505), inverseSurface = Color(0xFF050505),
inverseOnSurface = Color(0xFFFAFAFA), inverseOnSurface = Color(0xFFFAFAFA),
outline = Color(0xFF6F7977), outline = Color(0xFF6F7977),
surfaceContainerLowest = Color(0xFFE1E9E7),
surfaceContainerLow = Color(0xFFE6EEEC),
surfaceContainer = Color(0xFFEBF3F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFF0F8F6),
surfaceContainerHighest = Color(0xFFF7FFFD),
) )
} }
@@ -22,24 +22,29 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF004d61), primaryContainer = Color(0xFF004d61),
onPrimaryContainer = Color(0xFFb8eaff), onPrimaryContainer = Color(0xFFb8eaff),
inversePrimary = Color(0xFFa12b03), inversePrimary = Color(0xFFa12b03),
secondary = Color(0xFF5ed4fc), secondary = Color(0xFF5ed4fc), // Unread badge
onSecondary = Color(0xFF003544), onSecondary = Color(0xFF003544), // Unread badge text
secondaryContainer = Color(0xFF004d61), secondaryContainer = Color(0xFF004d61), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFb8eaff), onSecondaryContainer = Color(0xFFb8eaff), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFF001c3b), background = Color(0xFF001c3b),
onBackground = Color(0xFFd5e3ff), onBackground = Color(0xFFd5e3ff),
surface = Color(0xFF001c3b), surface = Color(0xFF001c3b),
onSurface = Color(0xFFd5e3ff), onSurface = Color(0xFFd5e3ff),
surfaceVariant = Color(0xFF40484c), surfaceVariant = Color(0xFF082b4b), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFbfc8cc), onSurfaceVariant = Color(0xFFbfc8cc),
surfaceTint = Color(0xFF5ed4fc), surfaceTint = Color(0xFF5ed4fc),
inverseSurface = Color(0xFFffe3c4), inverseSurface = Color(0xFFffe3c4),
inverseOnSurface = Color(0xFF001c3b), inverseOnSurface = Color(0xFF001c3b),
outline = Color(0xFF8a9296), outline = Color(0xFF8a9296),
surfaceContainerLowest = Color(0xFF072642),
surfaceContainerLow = Color(0xFF072947),
surfaceContainer = Color(0xFF082b4b), // Navigation bar background
surfaceContainerHigh = Color(0xFF093257),
surfaceContainerHighest = Color(0xFF0A3861),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -48,23 +53,28 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFB4D4DF), primaryContainer = Color(0xFFB4D4DF),
onPrimaryContainer = Color(0xFF001f28), onPrimaryContainer = Color(0xFF001f28),
inversePrimary = Color(0xFFff987f), inversePrimary = Color(0xFFff987f),
secondary = Color(0xFF006780), secondary = Color(0xFF006780), // Unread badge
onSecondary = Color(0xFFffffff), onSecondary = Color(0xFFffffff), // Unread badge text
secondaryContainer = Color(0xFFb8eaff), secondaryContainer = Color(0xFF9AE1FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001f28), onSecondaryContainer = Color(0xFF001f28), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFFfdfbff), background = Color(0xFFfdfbff),
onBackground = Color(0xFF001c3b), onBackground = Color(0xFF001c3b),
surface = Color(0xFFfdfbff), surface = Color(0xFFfdfbff),
onSurface = Color(0xFF001c3b), onSurface = Color(0xFF001c3b),
surfaceVariant = Color(0xFFdce4e8), surfaceVariant = Color(0xFFe8eff5), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF40484c), onSurfaceVariant = Color(0xFF40484c),
surfaceTint = Color(0xFF006780), surfaceTint = Color(0xFF006780),
inverseSurface = Color(0xFF020400), inverseSurface = Color(0xFF020400),
inverseOnSurface = Color(0xFFffe3c4), inverseOnSurface = Color(0xFFffe3c4),
outline = Color(0xFF70787c), outline = Color(0xFF70787c),
surfaceContainerLowest = Color(0xFFe2e8ec),
surfaceContainerLow = Color(0xFFe5ecf1),
surfaceContainer = Color(0xFFe8eff5), // Navigation bar background
surfaceContainerHigh = Color(0xFFedf4fA),
surfaceContainerHighest = Color(0xFFf5faff),
) )
} }
@@ -17,24 +17,29 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFFFFF), primaryContainer = Color(0xFFFFFFFF),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFFCECECE), inversePrimary = Color(0xFFCECECE),
secondary = Color(0xFFFFFFFF), secondary = Color(0xFFFFFFFF), // Unread badge
onSecondary = Color(0xFF5A5A5A), onSecondary = Color(0xFF5A5A5A), // Unread badge text
secondaryContainer = Color(0xFF717171), secondaryContainer = Color(0xFF717171), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFE4E4E4), onSecondaryContainer = Color(0xFFE4E4E4), // Navigation bar selector icon
tertiary = Color(0xFF000000), tertiary = Color(0xFF000000), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00419E), tertiaryContainer = Color(0xFF00419E),
onTertiaryContainer = Color(0xFFD8E2FF), onTertiaryContainer = Color(0xFFD8E2FF),
background = Color(0xFF1E1E1E), background = Color(0xFF1E1E1E),
onBackground = Color(0xFFE6E6E6), onBackground = Color(0xFFE6E6E6),
surface = Color(0xFF1E1E1E), surface = Color(0xFF1E1E1E),
onSurface = Color(0xFFE6E6E6), onSurface = Color(0xFFE6E6E6),
surfaceVariant = Color(0xFF4E4E4E), surfaceVariant = Color(0xFF313131), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD1D1D1), onSurfaceVariant = Color(0xFFD1D1D1),
surfaceTint = Color(0xFFFFFFFF), surfaceTint = Color(0xFFFFFFFF),
inverseSurface = Color(0xFFE6E6E6), inverseSurface = Color(0xFFE6E6E6),
inverseOnSurface = Color(0xFF1E1E1E), inverseOnSurface = Color(0xFF1E1E1E),
outline = Color(0xFF999999), outline = Color(0xFF999999),
surfaceContainerLowest = Color(0xFF2A2A2A),
surfaceContainerLow = Color(0xFF2D2D2D),
surfaceContainer = Color(0xFF313131), // Navigation bar background
surfaceContainerHigh = Color(0xFF383838),
surfaceContainerHighest = Color(0xFF3F3F3F),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -43,23 +48,28 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF000000), primaryContainer = Color(0xFF000000),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFA6A6A6), inversePrimary = Color(0xFFA6A6A6),
secondary = Color(0xFF000000), secondary = Color(0xFF000000), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFDDDDDD), secondaryContainer = Color(0xFFDDDDDD), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF0C0C0C), onSecondaryContainer = Color(0xFF0C0C0C), // Navigation bar selector icon
tertiary = Color(0xFFFFFFFF), tertiary = Color(0xFFFFFFFF), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFFD8E2FF), tertiaryContainer = Color(0xFFD8E2FF),
onTertiaryContainer = Color(0xFF001947), onTertiaryContainer = Color(0xFF001947),
background = Color(0xFFFDFDFD), background = Color(0xFFFDFDFD),
onBackground = Color(0xFF222222), onBackground = Color(0xFF222222),
surface = Color(0xFFFDFDFD), surface = Color(0xFFFDFDFD),
onSurface = Color(0xFF222222), onSurface = Color(0xFF222222),
surfaceVariant = Color(0xFFEDEDED), surfaceVariant = Color(0xFFE8E8E8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF515151), onSurfaceVariant = Color(0xFF515151),
surfaceTint = Color(0xFF000000), surfaceTint = Color(0xFF000000),
inverseSurface = Color(0xFF333333), inverseSurface = Color(0xFF333333),
inverseOnSurface = Color(0xFFF4F4F4), inverseOnSurface = Color(0xFFF4F4F4),
outline = Color(0xFF838383), outline = Color(0xFF838383),
surfaceContainerLowest = Color(0xFFCFCFCF),
surfaceContainerLow = Color(0xFFDADADA),
surfaceContainer = Color(0xFFE8E8E8), // Navigation bar background
surfaceContainerHigh = Color(0xFFECECEC),
surfaceContainerHighest = Color(0xFFEFEFEF),
) )
} }
@@ -23,24 +23,29 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF862200), primaryContainer = Color(0xFF862200),
onPrimaryContainer = Color(0xFFFFDBCF), onPrimaryContainer = Color(0xFFFFDBCF),
inversePrimary = Color(0xFFAE3200), inversePrimary = Color(0xFFAE3200),
secondary = Color(0xFFFFB59D), secondary = Color(0xFFFFB59D), // Unread badge
onSecondary = Color(0xFF5F1600), onSecondary = Color(0xFF5F1600), // Unread badge text
secondaryContainer = Color(0xFF862200), secondaryContainer = Color(0xFF862200), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFDBCF), onSecondaryContainer = Color(0xFFFFDBCF), // Navigation bar selector icon
tertiary = Color(0xFFD7C68D), tertiary = Color(0xFFD7C68D), // Downloaded badge
onTertiary = Color(0xFF3A2F05), onTertiary = Color(0xFF3A2F05), // Downloaded badge text
tertiaryContainer = Color(0xFF524619), tertiaryContainer = Color(0xFF524619),
onTertiaryContainer = Color(0xFFF5E2A7), onTertiaryContainer = Color(0xFFF5E2A7),
background = Color(0xFF211A18), background = Color(0xFF211A18),
onBackground = Color(0xFFEDE0DD), onBackground = Color(0xFFEDE0DD),
surface = Color(0xFF211A18), surface = Color(0xFF211A18),
onSurface = Color(0xFFEDE0DD), onSurface = Color(0xFFEDE0DD),
surfaceVariant = Color(0xFF53433F), surfaceVariant = Color(0xFF332723), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD8C2BC), onSurfaceVariant = Color(0xFFD8C2BC),
surfaceTint = Color(0xFFFFB59D), surfaceTint = Color(0xFFFFB59D),
inverseSurface = Color(0xFFEDE0DD), inverseSurface = Color(0xFFEDE0DD),
inverseOnSurface = Color(0xFF211A18), inverseOnSurface = Color(0xFF211A18),
outline = Color(0xFFA08C87), outline = Color(0xFFA08C87),
surfaceContainerLowest = Color(0xFF2E221F),
surfaceContainerLow = Color(0xFF312521),
surfaceContainer = Color(0xFF332723), // Navigation bar background
surfaceContainerHigh = Color(0xFF413531),
surfaceContainerHighest = Color(0xFF4C403D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFDBCF), primaryContainer = Color(0xFFFFDBCF),
onPrimaryContainer = Color(0xFF3B0A00), onPrimaryContainer = Color(0xFF3B0A00),
inversePrimary = Color(0xFFFFB59D), inversePrimary = Color(0xFFFFB59D),
secondary = Color(0xFFAE3200), secondary = Color(0xFFAE3200), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFDBCF), secondaryContainer = Color(0xFFEBCDC2), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3B0A00), onSecondaryContainer = Color(0xFF3B0A00), // Navigation bar selector icon
tertiary = Color(0xFF6B5E2F), tertiary = Color(0xFF6B5E2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFF5E2A7), tertiaryContainer = Color(0xFFF5E2A7),
onTertiaryContainer = Color(0xFF231B00), onTertiaryContainer = Color(0xFF231B00),
background = Color(0xFFFCFCFC), background = Color(0xFFFCFCFC),
onBackground = Color(0xFF211A18), onBackground = Color(0xFF211A18),
surface = Color(0xFFFCFCFC), surface = Color(0xFFFCFCFC),
onSurface = Color(0xFF211A18), onSurface = Color(0xFF211A18),
surfaceVariant = Color(0xFFF5DED8), surfaceVariant = Color(0xFFF6EBE7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF53433F), onSurfaceVariant = Color(0xFF53433F),
surfaceTint = Color(0xFFAE3200), surfaceTint = Color(0xFFAE3200),
inverseSurface = Color(0xFF362F2D), inverseSurface = Color(0xFF362F2D),
inverseOnSurface = Color(0xFFFBEEEB), inverseOnSurface = Color(0xFFFBEEEB),
outline = Color(0xFF85736E), outline = Color(0xFF85736E),
surfaceContainerLowest = Color(0xFFECE3E0),
surfaceContainerLow = Color(0xFFF1E7E4),
surfaceContainer = Color(0xFFF6EBE7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFAF4F2),
surfaceContainerHighest = Color(0xFFFBF6F4),
) )
} }
@@ -186,7 +186,7 @@ private fun TrackInfoItem(
modifier = Modifier modifier = Modifier
.padding(top = 12.dp) .padding(top = 12.dp)
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(8.dp) .padding(8.dp)
.clip(RoundedCornerShape(6.dp)), .clip(RoundedCornerShape(6.dp)),
) { ) {
@@ -38,6 +38,7 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.crash.CrashActivity import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer import eu.kanade.tachiyomi.data.coil.MangaKeyer
@@ -208,6 +209,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy)) add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaKeyer()) add(MangaKeyer())
add(MangaCoverKeyer()) add(MangaCoverKeyer())
add(BufferedSourceFetcher.Factory())
// SY --> // SY -->
add(PagePreviewKeyer()) add(PagePreviewKeyer())
add(PagePreviewFetcher.Factory(callFactoryLazy)) add(PagePreviewFetcher.Factory(callFactoryLazy))
@@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.data.coil
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import okio.BufferedSource
class BufferedSourceFetcher(
private val data: BufferedSource,
private val options: Options,
) : Fetcher {
override suspend fun fetch(): FetchResult {
return SourceFetchResult(
source = ImageSource(
source = data,
fileSystem = options.fileSystem,
),
mimeType = null,
dataSource = DataSource.MEMORY,
)
}
class Factory : Fetcher.Factory<BufferedSource> {
override fun create(
data: BufferedSource,
options: Options,
imageLoader: ImageLoader,
): Fetcher {
return BufferedSourceFetcher(data, options)
}
}
}
@@ -1,15 +1,18 @@
package eu.kanade.tachiyomi.data.coil package eu.kanade.tachiyomi.data.coil
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import androidx.core.graphics.drawable.toDrawable
import coil3.ImageLoader import coil3.ImageLoader
import coil3.asCoilImage import coil3.asCoilImage
import coil3.decode.DecodeResult import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils
import coil3.decode.Decoder import coil3.decode.Decoder
import coil3.decode.ImageSource import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult import coil3.fetch.SourceFetchResult
import coil3.request.Options import coil3.request.Options
import coil3.request.bitmapConfig
import eu.kanade.tachiyomi.util.storage.CbzCrypto import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.system.GLUtil
import net.lingala.zip4j.ZipFile import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.FileHeader import net.lingala.zip4j.model.FileHeader
import okio.BufferedSource import okio.BufferedSource
@@ -38,29 +41,58 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
} }
val decoder = resources.sourceOrNull()?.use { val decoder = resources.sourceOrNull()?.use {
zip4j.use { zipFile -> zip4j.use { zipFile ->
ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream()) ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream(), options.cropBorders, displayProfile)
} }
} }
// SY <-- // SY <--
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" } check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" }
val bitmap = decoder.decode() val srcWidth = decoder.width
val srcHeight = decoder.height
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
val sampleSize = DecodeUtils.calculateInSampleSize(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale,
)
var bitmap = decoder.decode(sampleSize = sampleSize)
decoder.recycle() decoder.recycle()
check(bitmap != null) { "Failed to decode image" } check(bitmap != null) { "Failed to decode image" }
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
options.bitmapConfig == Bitmap.Config.HARDWARE &&
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) {
bitmap.recycle()
bitmap = hwBitmap
}
}
return DecodeResult( return DecodeResult(
image = bitmap.asCoilImage(), image = bitmap.asCoilImage(),
isSampled = false, isSampled = sampleSize > 1,
) )
} }
class Factory : Decoder.Factory { class Factory : Decoder.Factory {
override fun create(result: SourceFetchResult, options: Options, imageLoader: ImageLoader): Decoder? { override fun create(result: SourceFetchResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (!isApplicable(result.source.source())) return null return if (options.customDecoder || isApplicable(result.source.source())) {
return TachiyomiImageDecoder(result.source, options) TachiyomiImageDecoder(result.source, options)
} else {
null
}
} }
private fun isApplicable(source: BufferedSource): Boolean { private fun isApplicable(source: BufferedSource): Boolean {
@@ -83,4 +115,8 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
override fun hashCode() = javaClass.hashCode() override fun hashCode() = javaClass.hashCode()
} }
companion object {
var displayProfile: ByteArray? = null
}
} }
@@ -0,0 +1,44 @@
package eu.kanade.tachiyomi.data.coil
import coil3.Extras
import coil3.getExtra
import coil3.request.ImageRequest
import coil3.request.Options
import coil3.size.Dimension
import coil3.size.Scale
import coil3.size.Size
import coil3.size.isOriginal
import coil3.size.pxOrElse
internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else width.toPx(scale)
}
internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else height.toPx(scale)
}
internal fun Dimension.toPx(scale: Scale): Int = pxOrElse {
when (scale) {
Scale.FILL -> Int.MIN_VALUE
Scale.FIT -> Int.MAX_VALUE
}
}
fun ImageRequest.Builder.cropBorders(enable: Boolean) = apply {
extras[cropBordersKey] = enable
}
val Options.cropBorders: Boolean
get() = getExtra(cropBordersKey)
private val cropBordersKey = Extras.Key(default = false)
fun ImageRequest.Builder.customDecoder(enable: Boolean) = apply {
extras[customDecoderKey] = enable
}
val Options.customDecoder: Boolean
get() = getExtra(customDecoderKey)
private val customDecoderKey = Extras.Key(default = false)
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import exh.log.xLogE import exh.log.xLogE
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@@ -35,6 +36,7 @@ import uy.kohesive.injekt.api.get
* and retrieved through dependency injection. You can use this class to queue new chapters or query * and retrieved through dependency injection. You can use this class to queue new chapters or query
* downloaded chapters. * downloaded chapters.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class DownloadManager( class DownloadManager(
private val context: Context, private val context: Context,
private val provider: DownloadProvider = Injekt.get(), private val provider: DownloadProvider = Injekt.get(),
@@ -475,7 +477,7 @@ class DownloadManager(
fun renameMangaDir(oldTitle: String, newTitle: String, source: Long) { fun renameMangaDir(oldTitle: String, newTitle: String, source: Long) {
val sourceDir = provider.findSourceDir(sourceManager.getOrStub(source)) ?: return val sourceDir = provider.findSourceDir(sourceManager.getOrStub(source)) ?: return
val mangaDir = sourceDir.findFile(DiskUtil.buildValidFilename(oldTitle), true) ?: return val mangaDir = sourceDir.findFile(DiskUtil.buildValidFilename(oldTitle)) ?: return
mangaDir.renameTo(DiskUtil.buildValidFilename(newTitle)) mangaDir.renameTo(DiskUtil.buildValidFilename(newTitle))
} }
} }
@@ -57,7 +57,7 @@ class DownloadProvider(
* @param source the source to query. * @param source the source to query.
*/ */
fun findSourceDir(source: Source): UniFile? { fun findSourceDir(source: Source): UniFile? {
return downloadsDir?.findFile(getSourceDirName(source), true) return downloadsDir?.findFile(getSourceDirName(source))
} }
/** /**
@@ -68,7 +68,7 @@ class DownloadProvider(
*/ */
fun findMangaDir(mangaTitle: String, source: Source): UniFile? { fun findMangaDir(mangaTitle: String, source: Source): UniFile? {
val sourceDir = findSourceDir(source) val sourceDir = findSourceDir(source)
return sourceDir?.findFile(getMangaDirName(mangaTitle), true) return sourceDir?.findFile(getMangaDirName(mangaTitle))
} }
/** /**
@@ -82,7 +82,7 @@ class DownloadProvider(
fun findChapterDir(chapterName: String, chapterScanlator: String?, mangaTitle: String, source: Source): UniFile? { fun findChapterDir(chapterName: String, chapterScanlator: String?, mangaTitle: String, source: Source): UniFile? {
val mangaDir = findMangaDir(mangaTitle, source) val mangaDir = findMangaDir(mangaTitle, source)
return getValidChapterDirNames(chapterName, chapterScanlator).asSequence() return getValidChapterDirNames(chapterName, chapterScanlator).asSequence()
.mapNotNull { mangaDir?.findFile(it, true) } .mapNotNull { mangaDir?.findFile(it) }
.firstOrNull() .firstOrNull()
} }
@@ -97,7 +97,7 @@ class DownloadProvider(
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList() val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList()
return mangaDir to chapters.mapNotNull { chapter -> return mangaDir to chapters.mapNotNull { chapter ->
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence() getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence()
.mapNotNull { mangaDir.findFile(it, true) } .mapNotNull { mangaDir.findFile(it) }
.firstOrNull() .firstOrNull()
} }
} }
@@ -16,10 +16,12 @@ import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import exh.source.isEhBasedSource
import exh.util.DataSaver import exh.util.DataSaver
import exh.util.DataSaver.Companion.getImage import exh.util.DataSaver.Companion.getImage
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -75,6 +77,7 @@ import java.util.zip.ZipOutputStream
* *
* Its queue contains the list of chapters to download. * Its queue contains the list of chapters to download.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class Downloader( class Downloader(
private val context: Context, private val context: Context,
private val provider: DownloadProvider, private val provider: DownloadProvider,
@@ -511,6 +514,9 @@ class Downloader(
.retryWhen { _, attempt -> .retryWhen { _, attempt ->
if (attempt < 3) { if (attempt < 3) {
delay((2L shl attempt.toInt()) * 1000) delay((2L shl attempt.toInt()) * 1000)
if (source.isEhBasedSource()) {
page.imageUrl = source.getImageUrl(page)
}
true true
} else { } else {
false false
@@ -709,7 +715,7 @@ class Downloader(
) )
// Remove the old file // Remove the old file
dir.findFile(COMIC_INFO_FILE, true)?.delete() dir.findFile(COMIC_INFO_FILE)?.delete()
dir.createFile(COMIC_INFO_FILE)!!.openOutputStream().use { dir.createFile(COMIC_INFO_FILE)!!.openOutputStream().use {
val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo) val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo)
it.write(comicInfoString.toByteArray()) it.write(comicInfoString.toByteArray())
@@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.getBitmapOrNull
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify import eu.kanade.tachiyomi.util.system.notify
import kotlinx.coroutines.DelicateCoroutinesApi
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
@@ -41,6 +42,7 @@ import uy.kohesive.injekt.api.get
import java.math.RoundingMode import java.math.RoundingMode
import java.text.NumberFormat import java.text.NumberFormat
@OptIn(DelicateCoroutinesApi::class)
class LibraryUpdateNotifier( class LibraryUpdateNotifier(
private val context: Context, private val context: Context,
@@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
@@ -41,6 +42,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
* Pending Broadcasts should be made from here. * Pending Broadcasts should be made from here.
* NOTE: Use local broadcasts if possible. * NOTE: Use local broadcasts if possible.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class NotificationReceiver : BroadcastReceiver() { class NotificationReceiver : BroadcastReceiver() {
private val getManga: GetManga by injectLazy() private val getManga: GetManga by injectLazy()
@@ -45,7 +45,7 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
notifier.showSyncError(e.message) notifier.showSyncError(e.message)
Result.failure() Result.success() // try again next time
} finally { } finally {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
} }
@@ -153,7 +153,7 @@ class SyncManager(
} }
// Stop the sync early if the remote backup is null or empty // Stop the sync early if the remote backup is null or empty
if (remoteBackup.backupManga?.size == 0) { if (remoteBackup.backupManga.size == 0) {
notifier.showSyncError("No data found on remote server.") notifier.showSyncError("No data found on remote server.")
return return
} }
@@ -20,9 +20,8 @@ import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -32,7 +31,6 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchNow
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.model.StubSource
@@ -54,10 +52,10 @@ class ExtensionManager(
private val trustExtension: TrustExtension = Injekt.get(), private val trustExtension: TrustExtension = Injekt.get(),
) { ) {
// SY --> val scope = CoroutineScope(SupervisorJob())
private val _isInitialized = MutableStateFlow(false) private val _isInitialized = MutableStateFlow(false)
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow() val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
// SY <--
/** /**
* API where all the available extensions can be found. * API where all the available extensions can be found.
@@ -71,13 +69,31 @@ class ExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>() private val iconMap = mutableMapOf<String, Drawable>()
private val _installedExtensionsFlow = MutableStateFlow(emptyList<Extension.Installed>()) private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow() val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
// SY -->
val availableExtensionsFlow = _availableExtensionsMapFlow.map { it.filterNotBlacklisted().values.toList() }
.stateIn(scope, SharingStarted.Lazily, _availableExtensionsMapFlow.value.values.toList())
// SY <--
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope)
init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? { fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName val pkgName = _installedExtensionsMapFlow.value.values
.find { ext ->
ext.sources.any { it.id == sourceId }
}
?.pkgName
if (pkgName != null) { if (pkgName != null) {
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
@@ -95,15 +111,6 @@ class ExtensionManager(
// SY <-- // SY <--
} }
private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>())
// SY -->
@OptIn(DelicateCoroutinesApi::class)
val availableExtensionsFlow = _availableExtensionsFlow
.map { it.filterNotBlacklisted() }
.stateIn(GlobalScope, SharingStarted.Eagerly, emptyList())
// SY <--
private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap() private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) { private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) {
@@ -115,38 +122,30 @@ class ExtensionManager(
fun getSourceData(id: Long) = availableExtensionsSourcesData[id] fun getSourceData(id: Long) = availableExtensionsSourcesData[id]
private val _untrustedExtensionsFlow = MutableStateFlow(emptyList<Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow()
init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}
/** /**
* Loads and registers the installed extensions. * Loads and registers the installed extensions.
*/ */
private fun initExtensions() { private fun initExtensions() {
val extensions = ExtensionLoader.loadExtensions(context) val extensions = ExtensionLoader.loadExtensions(context)
_installedExtensionsFlow.value = extensions _installedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>() .filterIsInstance<LoadResult.Success>()
.map { it.extension } .associate { it.extension.pkgName to it.extension }
_untrustedExtensionsFlow.value = extensions _untrustedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>() .filterIsInstance<LoadResult.Untrusted>()
.map { it.extension } .associate { it.extension.pkgName to it.extension }
// SY --> // SY -->
.filterNotBlacklisted() .filterNotBlacklisted()
// SY <--
_isInitialized.value = true _isInitialized.value = true
// SY <--
} }
// EXH --> // EXH -->
private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> { private fun <T : Extension> Map<String, T>.filterNotBlacklisted(): Map<String, T> {
val blacklistEnabled = preferences.enableSourceBlacklist().get() val blacklistEnabled = preferences.enableSourceBlacklist().get()
return filterNot { extension -> return filterNot { (_, extension) ->
extension.isBlacklisted(blacklistEnabled) extension.isBlacklisted(blacklistEnabled)
.also { .also {
if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName) if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
@@ -160,7 +159,7 @@ class ExtensionManager(
// EXH <-- // EXH <--
/** /**
* Finds the available extensions in the [api] and updates [availableExtensions]. * Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow].
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<Extension.Available> = try { val extensions: List<Extension.Available> = try {
@@ -173,7 +172,7 @@ class ExtensionManager(
enableAdditionalSubLanguages(extensions) enableAdditionalSubLanguages(extensions)
_availableExtensionsFlow.value = extensions _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions) updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions) setupAvailableExtensionsSourcesDataMap(extensions)
} }
@@ -219,42 +218,36 @@ class ExtensionManager(
return return
} }
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap()
var changed = false var changed = false
for ((pkgName, extension) in installedExtensionsMap) {
val availableExt = availableExtensions.find { it.pkgName == pkgName }
for ((index, installedExt) in mutInstalledExtensions.withIndex()) { if (availableExt == null && !extension.isObsolete) {
val pkgName = installedExt.pkgName installedExtensionsMap[pkgName] = extension.copy(isObsolete = true)
// SY -->
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName }
// SY <--
if (availableExt == null && !installedExt.isObsolete) {
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
changed = true changed = true
// SY --> // SY -->
} else if (installedExt.isBlacklisted() && !installedExt.isRedundant) { } else if (extension.isBlacklisted() && !extension.isRedundant) {
mutInstalledExtensions[index] = installedExt.copy(isRedundant = true) installedExtensionsMap[pkgName] = extension.copy(isRedundant = true)
changed = true changed = true
// SY <-- // SY <--
} else if (availableExt != null) { } else if (availableExt != null) {
val hasUpdate = installedExt.updateExists(availableExt) val hasUpdate = extension.updateExists(availableExt)
if (extension.hasUpdate != hasUpdate) {
if (installedExt.hasUpdate != hasUpdate) { installedExtensionsMap[pkgName] = extension.copy(
mutInstalledExtensions[index] = installedExt.copy(
hasUpdate = hasUpdate, hasUpdate = hasUpdate,
repoUrl = availableExt.repoUrl, repoUrl = availableExt.repoUrl,
) )
changed = true
} else { } else {
mutInstalledExtensions[index] = installedExt.copy( installedExtensionsMap[pkgName] = extension.copy(
repoUrl = availableExt.repoUrl, repoUrl = availableExt.repoUrl,
) )
changed = true
} }
changed = true
} }
} }
if (changed) { if (changed) {
_installedExtensionsFlow.value = mutInstalledExtensions _installedExtensionsMapFlow.value = installedExtensionsMap
} }
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@@ -278,8 +271,7 @@ class ExtensionManager(
* @param extension The extension to be updated. * @param extension The extension to be updated.
*/ */
fun updateExtension(extension: Extension.Installed): Flow<InstallStep> { fun updateExtension(extension: Extension.Installed): Flow<InstallStep> {
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow()
?: return emptyFlow()
return installExtension(availableExt) return installExtension(availableExt)
} }
@@ -315,24 +307,16 @@ class ExtensionManager(
* *
* @param extension the extension to trust * @param extension the extension to trust
*/ */
fun trust(extension: Extension.Untrusted) { suspend fun trust(extension: Extension.Untrusted) {
val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet() _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return
if (extension.pkgName !in untrustedPkgNames) return
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)
val nowTrustedExtensions = _untrustedExtensionsFlow.value _untrustedExtensionsMapFlow.value -= extension.pkgName
.filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode }
_untrustedExtensionsFlow.value -= nowTrustedExtensions
launchNow { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
nowTrustedExtensions .let { it as? LoadResult.Success }
.map { extension -> ?.let { registerNewExtension(it.extension) }
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
}
} }
/** /**
@@ -348,7 +332,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsFlow.value += extension _installedExtensionsMapFlow.value += extension
} }
/** /**
@@ -365,13 +349,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() _installedExtensionsMapFlow.value += extension
val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName }
if (oldExtension != null) {
mutInstalledExtensions -= oldExtension
}
mutInstalledExtensions += extension
_installedExtensionsFlow.value = mutInstalledExtensions
} }
/** /**
@@ -381,14 +359,8 @@ class ExtensionManager(
* @param pkgName The package name of the uninstalled application. * @param pkgName The package name of the uninstalled application.
*/ */
private fun unregisterExtension(pkgName: String) { private fun unregisterExtension(pkgName: String) {
val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName } _installedExtensionsMapFlow.value -= pkgName
if (installedExtension != null) { _untrustedExtensionsMapFlow.value -= pkgName
_installedExtensionsFlow.value -= installedExtension
}
val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName }
if (untrustedExtension != null) {
_untrustedExtensionsFlow.value -= untrustedExtension
}
} }
/** /**
@@ -407,14 +379,9 @@ class ExtensionManager(
} }
override fun onExtensionUntrusted(extension: Extension.Untrusted) { override fun onExtensionUntrusted(extension: Extension.Untrusted) {
val installedExtension = _installedExtensionsFlow.value _installedExtensionsMapFlow.value -= extension.pkgName
.find { it.pkgName == extension.pkgName } _untrustedExtensionsMapFlow.value += extension
updatePendingUpdatesCount()
if (installedExtension != null) {
_installedExtensionsFlow.value -= installedExtension
} else {
_untrustedExtensionsFlow.value += extension
}
} }
override fun onPackageUninstalled(pkgName: String) { override fun onPackageUninstalled(pkgName: String) {
@@ -436,17 +403,24 @@ class ExtensionManager(
} }
private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean { private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } val availableExt = availableExtension
?: _availableExtensionsMapFlow.value[pkgName]
?: return false ?: return false
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
} }
private fun updatePendingUpdatesCount() { private fun updatePendingUpdatesCount() {
val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate } val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate }
preferences.extensionUpdatesCount().set(pendingUpdateCount) preferences.extensionUpdatesCount().set(pendingUpdateCount)
if (pendingUpdateCount == 0) { if (pendingUpdateCount == 0) {
ExtensionUpdateNotifier(context).dismiss() ExtensionUpdateNotifier(context).dismiss()
} }
} }
private operator fun <T : Extension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
}
} }
@@ -9,12 +9,10 @@ import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch
import kotlinx.coroutines.async
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchNow
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
/** /**
@@ -23,29 +21,23 @@ import tachiyomi.core.common.util.system.logcat
* *
* @param listener The listener that should be notified of extension installation events. * @param listener The listener that should be notified of extension installation events.
*/ */
internal class ExtensionInstallReceiver(private val listener: Listener) : internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() {
BroadcastReceiver() {
val scope = CoroutineScope(SupervisorJob())
/**
* Registers this broadcast receiver
*/
fun register(context: Context) { fun register(context: Context) {
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED) ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
} }
/** private val filter = IntentFilter().apply {
* Returns the intent filter this receiver should subscribe to. addAction(Intent.ACTION_PACKAGE_ADDED)
*/ addAction(Intent.ACTION_PACKAGE_REPLACED)
private val filter addAction(Intent.ACTION_PACKAGE_REMOVED)
get() = IntentFilter().apply { addAction(ACTION_EXTENSION_ADDED)
addAction(Intent.ACTION_PACKAGE_ADDED) addAction(ACTION_EXTENSION_REPLACED)
addAction(Intent.ACTION_PACKAGE_REPLACED) addAction(ACTION_EXTENSION_REMOVED)
addAction(Intent.ACTION_PACKAGE_REMOVED) addDataScheme("package")
addAction(ACTION_EXTENSION_ADDED) }
addAction(ACTION_EXTENSION_REPLACED)
addAction(ACTION_EXTENSION_REMOVED)
addDataScheme("package")
}
/** /**
* Called when one of the events of the [filter] is received. When the package is an extension, * Called when one of the events of the [filter] is received. When the package is an extension,
@@ -58,7 +50,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> {
if (isReplacing(intent)) return if (isReplacing(intent)) return
launchNow { scope.launch {
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionInstalled(result.extension) is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
@@ -67,7 +59,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
} }
} }
Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> {
launchNow { scope.launch {
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionUpdated(result.extension) is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
@@ -107,9 +99,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
logcat(LogPriority.WARN) { "Package name not found" } logcat(LogPriority.WARN) { "Package name not found" }
return LoadResult.Error return LoadResult.Error
} }
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { return ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
}.await()
} }
/** /**
@@ -172,7 +172,7 @@ internal object ExtensionLoader {
* Attempts to load an extension from the given package name. It checks if the extension * Attempts to load an extension from the given package name. It checks if the extension
* contains the required feature flag before trying to load it. * contains the required feature flag before trying to load it.
*/ */
fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult { suspend fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult {
val extensionPackage = getExtensionInfoFromPkgName(context, pkgName) val extensionPackage = getExtensionInfoFromPkgName(context, pkgName)
if (extensionPackage == null) { if (extensionPackage == null) {
logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" } logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" }
@@ -223,7 +223,8 @@ internal object ExtensionLoader {
* @param context The application context. * @param context The application context.
* @param extensionInfo The extension to load. * @param extensionInfo The extension to load.
*/ */
private fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult { @Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
val pkgManager = context.packageManager val pkgManager = context.packageManager
val pkgInfo = extensionInfo.packageInfo val pkgInfo = extensionInfo.packageInfo
val appInfo = pkgInfo.applicationInfo val appInfo = pkgInfo.applicationInfo
@@ -252,7 +253,7 @@ internal object ExtensionLoader {
if (signatures.isNullOrEmpty()) { if (signatures.isNullOrEmpty()) {
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
return LoadResult.Error return LoadResult.Error
} else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) { } else if (!trustExtension.isTrusted(pkgInfo, signatures)) {
val extension = Extension.Untrusted( val extension = Extension.Untrusted(
extName, extName,
pkgName, pkgName,
@@ -806,6 +806,11 @@ class EHentai(
override fun pageListParse(response: Response) = override fun pageListParse(response: Response) =
throw UnsupportedOperationException("Unused method was called somehow!") throw UnsupportedOperationException("Unused method was called somehow!")
override suspend fun getImageUrl(page: Page): String {
val imageUrlResponse = client.newCall(imageUrlRequest(page)).awaitSuccess()
return realImageUrlParse(imageUrlResponse, page)
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl")) @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
override fun fetchImageUrl(page: Page): Observable<String> { override fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page)) return client.newCall(imageUrlRequest(page))
@@ -31,6 +31,7 @@ import exh.md.handlers.FollowsHandler
import exh.md.handlers.MangaHandler import exh.md.handlers.MangaHandler
import exh.md.handlers.MangaHotHandler import exh.md.handlers.MangaHotHandler
import exh.md.handlers.MangaPlusHandler import exh.md.handlers.MangaPlusHandler
import exh.md.handlers.NamicomiHandler
import exh.md.handlers.PageHandler import exh.md.handlers.PageHandler
import exh.md.handlers.SimilarHandler import exh.md.handlers.SimilarHandler
import exh.md.network.MangaDexLoginHelper import exh.md.network.MangaDexLoginHelper
@@ -123,6 +124,9 @@ class MangaDex(delegate: HttpSource, val context: Context) :
private val mangaHotHandler by lazy { private val mangaHotHandler by lazy {
MangaHotHandler(network.client, network.defaultUserAgentProvider()) MangaHotHandler(network.client, network.defaultUserAgentProvider())
} }
private val namicomiHandler by lazy {
NamicomiHandler(network.client, network.defaultUserAgentProvider())
}
private val pageHandler by lazy { private val pageHandler by lazy {
PageHandler( PageHandler(
headers, headers,
@@ -132,6 +136,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
bilibiliHandler, bilibiliHandler,
azukHandler, azukHandler,
mangaHotHandler, mangaHotHandler,
namicomiHandler,
trackPreferences, trackPreferences,
mdList, mdList,
) )
@@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@@ -195,7 +196,9 @@ class ExtensionsScreenModel(
} }
fun trustExtension(extension: Extension.Untrusted) { fun trustExtension(extension: Extension.Untrusted) {
extensionManager.trust(extension) screenModelScope.launch {
extensionManager.trust(extension)
}
} }
@Immutable @Immutable
@@ -56,9 +56,7 @@ fun SourceFilterDialog(
) { ) {
val updateFilters = { onUpdate(filters) } val updateFilters = { onUpdate(filters) }
AdaptiveSheet( AdaptiveSheet(onDismissRequest = onDismissRequest) {
onDismissRequest = onDismissRequest,
) {
LazyColumn { LazyColumn {
stickyHeader { stickyHeader {
Row( Row(
@@ -234,6 +234,9 @@ class LibraryScreenModel(
prefs.filterBookmarked, prefs.filterBookmarked,
prefs.filterCompleted, prefs.filterCompleted,
prefs.filterIntervalCustom, prefs.filterIntervalCustom,
// SY -->
prefs.filterLewd,
// SY <--
) + trackFilter.values ) + trackFilter.values
).any { it != TriState.DISABLED } ).any { it != TriState.DISABLED }
} }
@@ -62,6 +62,7 @@ import eu.kanade.presentation.reader.appbars.NavBarType
import eu.kanade.presentation.reader.appbars.ReaderAppBars import eu.kanade.presentation.reader.appbars.ReaderAppBars
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
@@ -1280,7 +1281,9 @@ class ReaderActivity : BaseActivity() {
input.copyTo(output) input.copyTo(output)
} }
} }
SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray()) val data = outputStream.toByteArray()
SubsamplingScaleImageView.setDisplayProfile(data)
TachiyomiImageDecoder.displayProfile = data
} }
} }
@@ -1141,7 +1141,7 @@ class ReaderViewModel @JvmOverloads constructor(
return imageSaver.save( return imageSaver.save(
image = Image.Page( image = Image.Page(
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg) }, inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg).inputStream() },
name = filename, name = filename,
location = location, location = location,
), ),
@@ -13,6 +13,7 @@ import exh.util.DataSaver
import exh.util.DataSaver.Companion.getImage import exh.util.DataSaver.Companion.getImage
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@@ -31,6 +32,7 @@ import kotlin.math.min
/** /**
* Loader used to load chapters from an online source. * Loader used to load chapters from an online source.
*/ */
@OptIn(DelicateCoroutinesApi::class)
internal class HttpPageLoader( internal class HttpPageLoader(
private val chapter: ReaderChapter, private val chapter: ReaderChapter,
private val source: HttpSource, private val source: HttpSource,
@@ -43,7 +43,9 @@ internal class ZipPageLoader(file: UniFile, context: Context) : PageLoader() {
} }
private val apacheZip: ZipFile? = if (!file.isEncryptedZip() && Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { private val apacheZip: ZipFile? = if (!file.isEncryptedZip() && Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
ZipFile(channel) ZipFile.Builder()
.setSeekableByteChannel(channel)
.get()
} else { } else {
null null
} }
@@ -18,23 +18,27 @@ import androidx.annotation.StyleRes
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil3.BitmapImage
import coil3.dispose import coil3.dispose
import coil3.imageLoader import coil3.imageLoader
import coil3.request.CachePolicy import coil3.request.CachePolicy
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.crossfade import coil3.request.crossfade
import coil3.size.Precision
import coil3.size.ViewSizeResolver
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT_QUAD import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT_QUAD
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
import com.github.chrisbanes.photoview.PhotoView import com.github.chrisbanes.photoview.PhotoView
import eu.kanade.tachiyomi.data.coil.cropBorders
import eu.kanade.tachiyomi.data.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import java.io.InputStream import okio.BufferedSource
import java.nio.ByteBuffer
/** /**
* A wrapper view for showing page image. * A wrapper view for showing page image.
@@ -140,14 +144,14 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
} }
fun setImage(inputStream: InputStream, isAnimated: Boolean, config: Config) { fun setImage(source: BufferedSource, isAnimated: Boolean, config: Config) {
this.config = config this.config = config
if (isAnimated) { if (isAnimated) {
prepareAnimatedImageView() prepareAnimatedImageView()
setAnimatedImage(inputStream, config) setAnimatedImage(source, config)
} else { } else {
prepareNonAnimatedImageView() prepareNonAnimatedImageView()
setNonAnimatedImage(inputStream, config) setNonAnimatedImage(source, config)
} }
} }
@@ -256,7 +260,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
private fun setNonAnimatedImage( private fun setNonAnimatedImage(
image: Any, data: Any,
config: Config, config: Config,
) = (pageView as? SubsamplingScaleImageView)?.apply { ) = (pageView as? SubsamplingScaleImageView)?.apply {
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration()) setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
@@ -277,12 +281,36 @@ open class ReaderPageImageView @JvmOverloads constructor(
}, },
) )
when (image) { if (isWebtoon) {
is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap)) val request = ImageRequest.Builder(context)
is InputStream -> setImage(ImageSource.inputStream(image)) .data(data)
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}") .memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val image = result as BitmapImage
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
this@ReaderPageImageView.onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
context.imageLoader.enqueue(request)
} else {
when (data) {
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
}
isVisible = true
} }
isVisible = true
} }
private fun prepareAnimatedImageView() { private fun prepareAnimatedImageView() {
@@ -325,18 +353,13 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
private fun setAnimatedImage( private fun setAnimatedImage(
image: Any, data: Any,
config: Config, config: Config,
) = (pageView as? AppCompatImageView)?.apply { ) = (pageView as? AppCompatImageView)?.apply {
if (this is PhotoView) { if (this is PhotoView) {
setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration()) setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration())
} }
val data = when (image) {
is Drawable -> image
is InputStream -> ByteBuffer.wrap(image.readBytes())
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
}
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.data(data) .data(data)
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
@@ -19,15 +19,14 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.InputStream
import kotlin.math.max import kotlin.math.max
/** /**
@@ -159,53 +158,45 @@ class PagerPageHolder(
val streamFn2 = extraPage?.stream val streamFn2 = extraPage?.stream
try { try {
val (bais, isAnimated, background) = withIOContext { val (source, isAnimated, background) = withIOContext {
streamFn().buffered(16).use { stream -> streamFn().buffered(16).use { source ->
// SY --> // SY -->
( if (extraPage != null) {
if (extraPage != null) { streamFn2?.invoke()
streamFn2?.invoke() ?.buffered(16)
?.buffered(16) } else {
null
}.use { source2 ->
val itemSource = if (viewer.config.dualPageSplit) {
process(item.first, Buffer().readFrom(source))
} else {
mergePages(Buffer().readFrom(source), source2?.let { Buffer().readFrom(it) })
}
// SY <--
val isAnimated = ImageUtil.isAnimatedAndSupported(itemSource)
val background = if (!isAnimated && viewer.config.automaticBackground) {
ImageUtil.chooseBackground(context, itemSource.peek())
} else { } else {
null null
} }
).use { stream2 -> Triple(itemSource, isAnimated, background)
if (viewer.config.dualPageSplit) { }
process(item.first, stream)
} else {
mergePages(stream, stream2)
}.use { itemStream ->
// SY <--
val bais = ByteArrayInputStream(itemStream.readBytes())
val isAnimated = ImageUtil.isAnimatedAndSupported(bais)
bais.reset()
val background = if (!isAnimated && viewer.config.automaticBackground) {
ImageUtil.chooseBackground(context, bais)
} else {
null
}
bais.reset()
Triple(bais, isAnimated, background)
}
}
} }
} }
withUIContext { withUIContext {
bais.use { setImage(
setImage( source,
it, isAnimated,
isAnimated, Config(
Config( zoomDuration = viewer.config.doubleTapAnimDuration,
zoomDuration = viewer.config.doubleTapAnimDuration, minimumScaleType = viewer.config.imageScaleType,
minimumScaleType = viewer.config.imageScaleType, cropBorders = viewer.config.imageCropBorders,
cropBorders = viewer.config.imageCropBorders, zoomStartPosition = viewer.config.imageZoomType,
zoomStartPosition = viewer.config.imageZoomType, landscapeZoom = viewer.config.landscapeZoom,
landscapeZoom = viewer.config.landscapeZoom, ),
), )
) if (!isAnimated) {
if (!isAnimated) { pageBackground = background
pageBackground = background
}
} }
removeErrorLayout() removeErrorLayout()
} }
@@ -217,124 +208,119 @@ class PagerPageHolder(
} }
} }
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream { private fun process(page: ReaderPage, imageSource: BufferedSource): BufferedSource {
if (viewer.config.dualPageRotateToFit) { if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageStream) return rotateDualPage(imageSource)
} }
if (!viewer.config.dualPageSplit) { if (!viewer.config.dualPageSplit) {
return imageStream return imageSource
} }
if (page is InsertPage) { if (page is InsertPage) {
return splitInHalf(imageStream) return splitInHalf(imageSource)
} }
val isDoublePage = ImageUtil.isWideImage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageSource)
if (!isDoublePage) { if (!isDoublePage) {
return imageStream return imageSource
} }
onPageSplit(page) onPageSplit(page)
return splitInHalf(imageStream) return splitInHalf(imageSource)
} }
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream { private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
val isDoublePage = ImageUtil.isWideImage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageSource)
return if (isDoublePage) { return if (isDoublePage) {
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
ImageUtil.rotateImage(imageStream, rotation) ImageUtil.rotateImage(imageSource, rotation)
} else { } else {
imageStream imageSource
} }
} }
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream { private fun mergePages(imageSource: BufferedSource, imageSource2: BufferedSource?): BufferedSource {
// Handle adding a center margin to wide images if requested // Handle adding a center margin to wide images if requested
if (imageStream2 == null) { if (imageSource2 == null) {
return if (imageStream is BufferedInputStream && return if (
!ImageUtil.isAnimatedAndSupported(imageStream) && !ImageUtil.isAnimatedAndSupported(imageSource) &&
ImageUtil.isWideImage(imageStream) && ImageUtil.isWideImage(imageSource) &&
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 && viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
!viewer.config.imageCropBorders !viewer.config.imageCropBorders
) { ) {
ImageUtil.addHorizontalCenterMargin(imageStream, height, context) ImageUtil.addHorizontalCenterMargin(imageSource, height, context)
} else { } else {
imageStream imageSource
} }
} }
if (page.fullPage) return imageStream if (page.fullPage) return imageSource
if (ImageUtil.isAnimatedAndSupported(imageStream)) { if (ImageUtil.isAnimatedAndSupported(imageSource)) {
page.fullPage = true page.fullPage = true
splitDoublePages() splitDoublePages()
return imageStream return imageSource
} else if (ImageUtil.isAnimatedAndSupported(imageStream2)) { } else if (ImageUtil.isAnimatedAndSupported(imageSource2)) {
page.isolatedPage = true page.isolatedPage = true
extraPage?.fullPage = true extraPage?.fullPage = true
splitDoublePages() splitDoublePages()
return imageStream return imageSource
} }
val imageBytes = imageStream.readBytes()
val imageBitmap = try { val imageBitmap = try {
ImageDecoder.newInstance(imageBytes.inputStream())?.decode() ImageDecoder.newInstance(imageSource.inputStream())?.decode()
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Cannot combine pages" } logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
null null
} }
if (imageBitmap == null) { if (imageBitmap == null) {
imageStream2.close() imageSource2.close()
imageStream.close()
page.fullPage = true page.fullPage = true
splitDoublePages() splitDoublePages()
logcat(LogPriority.ERROR) { "Cannot combine pages" } logcat(LogPriority.ERROR) { "Cannot combine pages" }
return imageBytes.inputStream() return imageSource
} }
scope.launch { progressIndicator.setProgress(96) } scope.launch { progressIndicator.setProgress(96) }
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
if (height < width) { if (height < width) {
imageStream2.close() imageSource2.close()
imageStream.close()
page.fullPage = true page.fullPage = true
splitDoublePages() splitDoublePages()
return imageBytes.inputStream() return imageSource
} }
val imageBytes2 = imageStream2.readBytes()
val imageBitmap2 = try { val imageBitmap2 = try {
ImageDecoder.newInstance(imageBytes2.inputStream())?.decode() ImageDecoder.newInstance(imageSource2.inputStream())?.decode()
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Cannot combine pages" } logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
null null
} }
if (imageBitmap2 == null) { if (imageBitmap2 == null) {
imageStream2.close() imageSource2.close()
imageStream.close()
extraPage?.fullPage = true extraPage?.fullPage = true
page.isolatedPage = true page.isolatedPage = true
splitDoublePages() splitDoublePages()
logcat(LogPriority.ERROR) { "Cannot combine pages" } logcat(LogPriority.ERROR) { "Cannot combine pages" }
return imageBytes.inputStream() return imageSource
} }
scope.launch { progressIndicator.setProgress(97) } scope.launch { progressIndicator.setProgress(97) }
val height2 = imageBitmap2.height val height2 = imageBitmap2.height
val width2 = imageBitmap2.width val width2 = imageBitmap2.width
if (height2 < width2) { if (height2 < width2) {
imageStream2.close() imageSource2.close()
imageStream.close()
extraPage?.fullPage = true extraPage?.fullPage = true
page.isolatedPage = true page.isolatedPage = true
splitDoublePages() splitDoublePages()
return imageBytes.inputStream() return imageSource
} }
val isLTR = (viewer !is R2LPagerViewer) xor viewer.config.invertDoublePages val isLTR = (viewer !is R2LPagerViewer) xor viewer.config.invertDoublePages
imageStream.close() imageSource.close()
imageStream2.close() imageSource2.close()
val centerMargin = if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 && !viewer.config.imageCropBorders) { val centerMargin = if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 && !viewer.config.imageCropBorders) {
96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1) 96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1)
@@ -363,7 +349,7 @@ class PagerPageHolder(
} }
} }
private fun splitInHalf(imageStream: InputStream): InputStream { private fun splitInHalf(imageSource: BufferedSource): BufferedSource {
var side = when { var side = when {
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
@@ -387,7 +373,7 @@ class PagerPageHolder(
0 0
} }
return ImageUtil.splitInHalf(imageStream, side, sideMargin) return ImageUtil.splitInHalf(imageSource, side, sideMargin)
} }
private fun onPageSplit(page: ReaderPage) { private fun onPageSplit(page: ReaderPage) {
@@ -22,15 +22,14 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.suspendCancellableCoroutine
import logcat.LogPriority import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import java.io.BufferedInputStream
import java.io.InputStream
/** /**
* Holder of the webtoon reader for a single page of a chapter. * Holder of the webtoon reader for a single page of a chapter.
@@ -188,16 +187,14 @@ class WebtoonPageHolder(
val streamFn = page?.stream ?: return val streamFn = page?.stream ?: return
try { try {
val (openStream, isAnimated) = withIOContext { val (source, isAnimated) = withIOContext {
val stream = streamFn().buffered(16) val source = streamFn().use { process(Buffer().readFrom(it)) }
val openStream = process(stream) val isAnimated = ImageUtil.isAnimatedAndSupported(source)
Pair(source, isAnimated)
val isAnimated = ImageUtil.isAnimatedAndSupported(stream)
Pair(openStream, isAnimated)
} }
withUIContext { withUIContext {
frame.setImage( frame.setImage(
openStream, source,
isAnimated, isAnimated,
ReaderPageImageView.Config( ReaderPageImageView.Config(
zoomDuration = viewer.config.doubleTapAnimDuration, zoomDuration = viewer.config.doubleTapAnimDuration,
@@ -207,10 +204,6 @@ class WebtoonPageHolder(
) )
removeErrorLayout() removeErrorLayout()
} }
// Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled
suspendCancellableCoroutine<Nothing> { continuation ->
continuation.invokeOnCancellation { openStream.close() }
}
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
withUIContext { withUIContext {
@@ -219,29 +212,29 @@ class WebtoonPageHolder(
} }
} }
private fun process(imageStream: BufferedInputStream): InputStream { private fun process(imageSource: BufferedSource): BufferedSource {
if (viewer.config.dualPageRotateToFit) { if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageStream) return rotateDualPage(imageSource)
} }
if (viewer.config.dualPageSplit) { if (viewer.config.dualPageSplit) {
val isDoublePage = ImageUtil.isWideImage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageSource)
if (isDoublePage) { if (isDoublePage) {
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
return ImageUtil.splitAndMerge(imageStream, upperSide) return ImageUtil.splitAndMerge(imageSource, upperSide)
} }
} }
return imageStream return imageSource
} }
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream { private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
val isDoublePage = ImageUtil.isWideImage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageSource)
return if (isDoublePage) { return if (isDoublePage) {
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
ImageUtil.rotateImage(imageStream, rotation) ImageUtil.rotateImage(imageSource, rotation)
} else { } else {
imageStream imageSource
} }
} }
@@ -28,7 +28,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
private var atFirstPosition = false private var atFirstPosition = false
private var halfWidth = 0 private var halfWidth = 0
private var halfHeight = 0 private var halfHeight = 0
private var originalHeight = 0 var originalHeight = 0
private set
private var heightSet = false private var heightSet = false
private var firstVisibleItemPosition = 0 private var firstVisibleItemPosition = 0
private var lastVisibleItemPosition = 0 private var lastVisibleItemPosition = 0
@@ -124,7 +124,7 @@ class WebtoonViewer(
recycler.getLocationInWindow(viewPositionRelativeToWindow) recycler.getLocationInWindow(viewPositionRelativeToWindow)
val pos = PointF( val pos = PointF(
(event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / recycler.width, (event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / recycler.width,
(event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.height, (event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.originalHeight,
) )
when (config.navigator.getAction(pos)) { when (config.navigator.getAction(pos)) {
NavigationRegion.MENU -> activity.toggleMenu() NavigationRegion.MENU -> activity.toggleMenu()
@@ -20,12 +20,13 @@ class CrashLogUtil(
private val extensionManager: ExtensionManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(),
) { ) {
suspend fun dumpLogs() = withNonCancellableContext { suspend fun dumpLogs(exception: Throwable? = null) = withNonCancellableContext {
try { try {
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt") val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
file.appendText(getDebugInfo() + "\n\n") file.appendText(getDebugInfo() + "\n\n")
getExtensionsInfo()?.let { file.appendText("$it\n\n") } getExtensionsInfo()?.let { file.appendText("$it\n\n") }
exception?.let { file.appendText("$it\n\n") }
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor() Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
@@ -61,14 +61,14 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
return try { return try {
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) { if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
Result.failure() Result.success() // retry again later
} else { } else {
startUpdating() startUpdating()
logger.d("Update job completed!") logger.d("Update job completed!")
Result.success() Result.success()
} }
} catch (e: Exception) { } catch (e: Exception) {
Result.failure() Result.success() // retry again later
} }
} }
@@ -17,10 +17,13 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okio.BufferedSource
import okio.buffer
import okio.sink
import okio.source
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.concurrent.thread import kotlin.concurrent.thread
@@ -67,7 +70,7 @@ class MemAutoFlushingLookupTable<T>(
Runtime.getRuntime().addShutdownHook(shutdownHook) Runtime.getRuntime().addShutdownHook(shutdownHook)
} }
private fun InputStream.requireBytes(targetArray: ByteArray, byteCount: Int): Boolean { private fun BufferedSource.requireBytes(targetArray: ByteArray, byteCount: Int): Boolean {
var readIter = 0 var readIter = 0
while (true) { while (true) {
val readThisIter = read(targetArray, readIter, byteCount - readIter) val readThisIter = read(targetArray, readIter, byteCount - readIter)
@@ -80,7 +83,7 @@ class MemAutoFlushingLookupTable<T>(
private fun initialLoad() { private fun initialLoad() {
launch { launch {
try { try {
atomicFile.openRead().buffered().use { input -> atomicFile.openRead().source().buffer().use { input ->
val bb = ByteBuffer.allocate(8) val bb = ByteBuffer.allocate(8)
while (true) { while (true) {
@@ -126,7 +129,7 @@ class MemAutoFlushingLookupTable<T>(
val fos = atomicFile.startWrite() val fos = atomicFile.startWrite()
try { try {
val out = fos.buffered() val out = fos.sink().buffer()
table.forEach { key, value -> table.forEach { key, value ->
val v = serializer.write(value).toByteArray(Charsets.UTF_8) val v = serializer.write(value).toByteArray(Charsets.UTF_8)
bb.putInt(0, key) bb.putInt(0, key)
@@ -0,0 +1,49 @@
package exh.md.handlers
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
class NamicomiHandler(currentClient: OkHttpClient, userAgent: String) {
private val apiUrl = "https://api.namicomi.com"
private val headers = Headers.Builder()
.add("User-Agent", userAgent)
.build()
val client: OkHttpClient = currentClient
suspend fun fetchPageList(externalUrl: String, dataSaver: Boolean): List<Page> {
val chapterId = externalUrl.substringBefore("?").substringAfterLast("/")
val request =
GET(
"$apiUrl/images/chapter/$chapterId?newQualities=true",
headers,
)
return pageListParse(client.newCall(request).awaitSuccess(), chapterId, dataSaver)
}
private fun pageListParse(response: Response, chapterId: String, dataSaver: Boolean): List<Page> {
val data = Json.parseToJsonElement(response.body.string()).jsonObject["data"]!!
val baseUrl = data.jsonObject["baseUrl"]!!.jsonPrimitive.content
val hash = data.jsonObject["hash"]!!.jsonPrimitive.content
/* Available quality levels: source, high, medium, low */
val quality = if(dataSaver) "low" else "high"
return data
.jsonObject[quality]!!
.jsonArray.mapIndexed { index, element ->
val fileName = element.jsonObject["filename"]!!.jsonPrimitive.content
val url = "$baseUrl/chapter/$chapterId/$hash/$quality/$fileName"
Page(index, url, url)
}
}
}
@@ -27,6 +27,7 @@ class PageHandler(
private val bilibiliHandler: BilibiliHandler, private val bilibiliHandler: BilibiliHandler,
private val azukiHandler: AzukiHandler, private val azukiHandler: AzukiHandler,
private val mangaHotHandler: MangaHotHandler, private val mangaHotHandler: MangaHotHandler,
private val namicomiHandler: NamicomiHandler,
private val preferences: TrackPreferences, private val preferences: TrackPreferences,
private val mdList: MdList, private val mdList: MdList,
) { ) {
@@ -54,6 +55,10 @@ class PageHandler(
chapter.scanlator.equals("mangahot", true) -> mangaHotHandler.fetchPageList( chapter.scanlator.equals("mangahot", true) -> mangaHotHandler.fetchPageList(
chapterResponse.data.attributes.externalUrl, chapterResponse.data.attributes.externalUrl,
) )
chapter.scanlator.equals("namicomi", true) -> namicomiHandler.fetchPageList(
chapterResponse.data.attributes.externalUrl,
dataSaver = dataSaver,
)
else -> throw Exception("${chapter.scanlator} not supported") else -> throw Exception("${chapter.scanlator} not supported")
} }
} else { } else {
@@ -122,6 +127,9 @@ class PageHandler(
page.imageUrl?.contains("mangahot", true) == true -> { page.imageUrl?.contains("mangahot", true) == true -> {
mangaHotHandler.client.newCachelessCallWithProgress(GET(page.imageUrl!!, mangaHotHandler.headers), page) mangaHotHandler.client.newCachelessCallWithProgress(GET(page.imageUrl!!, mangaHotHandler.headers), page)
} }
page.imageUrl?.contains("namicomi", true) == true -> {
mangaHotHandler.client.newCachelessCallWithProgress(GET(page.imageUrl!!, mangaHotHandler.headers), page)
}
else -> null else -> null
} }
} }
@@ -7,7 +7,7 @@ import tachiyomi.core.common.util.lang.withIOContext
/** /**
* Util for evaluating JavaScript in sources. * Util for evaluating JavaScript in sources.
*/ */
@Suppress("UNUSED", "UNCHECKED_CAST") @Suppress("UNUSED", "UNCHECKED_CAST", "UNUSED_PARAMETER")
class JavaScriptEngine(context: Context) { class JavaScriptEngine(context: Context) {
/** /**
@@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.DelicateCoroutinesApi
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
@@ -48,6 +49,7 @@ abstract class WebViewInterceptor(
abstract fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response abstract fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response
@OptIn(DelicateCoroutinesApi::class)
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() val request = chain.request()
val response = chain.proceed(request) val response = chain.proceed(request)
@@ -26,9 +26,9 @@ class EpubFile(file: UniFile, context: Context) : Closeable {
* Zip file of this epub. * Zip file of this epub.
*/ */
private val zip = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { private val zip = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
ZipFile(tempFileManager.createTempFile(file)) ZipFile.Builder().setFile(tempFileManager.createTempFile(file)).get()
} else { } else {
ZipFile(file.openReadOnlyChannel(context)) ZipFile.Builder().setSeekableByteChannel(file.openReadOnlyChannel(context)).get()
} }
// SY <-- // SY <--
@@ -3,6 +3,7 @@ package tachiyomi.core.common.util.lang
import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@@ -64,7 +65,7 @@ private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutin
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) = internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
invokeOnCancellation { sub.unsubscribe() } invokeOnCancellation { sub.unsubscribe() }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
fun <T> runAsObservable( fun <T> runAsObservable(
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE, backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
block: suspend () -> T, block: suspend () -> T,
@@ -26,11 +26,10 @@ import androidx.core.graphics.red
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import logcat.LogPriority import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.decoder.Format import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.URLConnection import java.net.URLConnection
@@ -83,9 +82,9 @@ object ImageUtil {
?: "jpg" ?: "jpg"
} }
fun isAnimatedAndSupported(stream: InputStream): Boolean { fun isAnimatedAndSupported(source: BufferedSource): Boolean {
return try { return try {
val type = getImageType(stream) ?: return false val type = getImageType(source.peek().inputStream()) ?: return false
// https://coil-kt.github.io/coil/getting_started/#supported-image-formats // https://coil-kt.github.io/coil/getting_started/#supported-image-formats
when (type.format) { when (type.format) {
Format.Gif -> true Format.Gif -> true
@@ -132,18 +131,16 @@ object ImageUtil {
* *
* @return true if the width is greater than the height * @return true if the width is greater than the height
*/ */
fun isWideImage(imageStream: BufferedInputStream): Boolean { fun isWideImage(imageSource: BufferedSource): Boolean {
val options = extractImageOptions(imageStream) val options = extractImageOptions(imageSource)
return options.outWidth > options.outHeight return options.outWidth > options.outHeight
} }
/** /**
* Extract the 'side' part from imageStream and return it as InputStream. * Extract the 'side' part from [BufferedSource] and return it as [BufferedSource].
*/ */
fun splitInHalf(imageStream: InputStream, side: Side, sidePadding: Int): InputStream { fun splitInHalf(imageSource: BufferedSource, side: Side, sidePadding: Int): BufferedSource {
val imageBytes = imageStream.readBytes() val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
@@ -157,22 +154,20 @@ object ImageUtil {
half.applyCanvas { half.applyCanvas {
drawBitmap(imageBitmap, part, singlePage, null) drawBitmap(imageBitmap, part, singlePage, null)
} }
val output = ByteArrayOutputStream() val output = Buffer()
half.compress(Bitmap.CompressFormat.JPEG, 100, output) half.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray()) return output
} }
fun rotateImage(imageStream: InputStream, degrees: Float): InputStream { fun rotateImage(imageSource: BufferedSource, degrees: Float): BufferedSource {
val imageBytes = imageStream.readBytes() val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val rotated = rotateBitMap(imageBitmap, degrees) val rotated = rotateBitMap(imageBitmap, degrees)
val output = ByteArrayOutputStream() val output = Buffer()
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output) rotated.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray()) return output
} }
private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap { private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
@@ -184,10 +179,8 @@ object ImageUtil {
* Split the image into left and right parts, then merge them into a * Split the image into left and right parts, then merge them into a
* new vertically-aligned image. * new vertically-aligned image.
*/ */
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream { fun splitAndMerge(imageSource: BufferedSource, upperSide: Side): BufferedSource {
val imageBytes = imageStream.readBytes() val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
@@ -209,9 +202,9 @@ object ImageUtil {
drawBitmap(imageBitmap, leftPart, bottomPart, null) drawBitmap(imageBitmap, leftPart, bottomPart, null)
} }
val output = ByteArrayOutputStream() val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output) result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray()) return output
} }
enum class Side { enum class Side {
@@ -225,8 +218,8 @@ object ImageUtil {
* to compensate for scaling. * to compensate for scaling.
*/ */
fun addHorizontalCenterMargin(imageStream: InputStream, viewHeight: Int, backgroundContext: Context): InputStream { fun addHorizontalCenterMargin(imageSource: BufferedSource, viewHeight: Int, backgroundContext: Context): BufferedSource {
val imageBitmap = ImageDecoder.newInstance(imageStream)?.decode()!! val imageBitmap = ImageDecoder.newInstance(imageSource.inputStream())?.decode()!!
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
@@ -237,7 +230,7 @@ object ImageUtil {
val leftTargetPart = Rect(0, 0, width / 2, height) val leftTargetPart = Rect(0, 0, width / 2, height)
val rightTargetPart = Rect(width / 2 + centerPadding, 0, width + centerPadding, height) val rightTargetPart = Rect(width / 2 + centerPadding, 0, width + centerPadding, height)
val bgColor = chooseBackground(backgroundContext, imageStream) val bgColor = chooseBackground(backgroundContext, imageSource)
bgColor.setBounds(width / 2, 0, width / 2 + centerPadding, height) bgColor.setBounds(width / 2, 0, width / 2 + centerPadding, height)
val result = createBitmap(width + centerPadding, height) val result = createBitmap(width + centerPadding, height)
@@ -247,9 +240,9 @@ object ImageUtil {
bgColor.draw(this) bgColor.draw(this)
} }
val output = ByteArrayOutputStream() val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output) result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray()) return output
} }
// SY <-- // SY <--
@@ -258,11 +251,8 @@ object ImageUtil {
* *
* @return true if the height:width ratio is greater than 3. * @return true if the height:width ratio is greater than 3.
*/ */
private fun isTallImage(imageStream: InputStream): Boolean { private fun isTallImage(imageSource: BufferedSource): Boolean {
val options = extractImageOptions( val options = extractImageOptions(imageSource)
imageStream,
resetAfterExtraction = false,
)
return (options.outHeight / options.outWidth) > 3 return (options.outHeight / options.outWidth) > 3
} }
@@ -275,22 +265,18 @@ object ImageUtil {
imageFile: UniFile, imageFile: UniFile,
filenamePrefix: String, filenamePrefix: String,
): Boolean { ): Boolean {
if (isAnimatedAndSupported(imageFile.openInputStream()) || val imageSource = imageFile.openInputStream().use { Buffer().readFrom(it) }
!isTallImage(imageFile.openInputStream()) if (isAnimatedAndSupported(imageSource) || !isTallImage(imageSource)) {
) {
return true return true
} }
val bitmapRegionDecoder = getBitmapRegionDecoder(imageFile.openInputStream()) val bitmapRegionDecoder = getBitmapRegionDecoder(imageSource.peek().inputStream())
if (bitmapRegionDecoder == null) { if (bitmapRegionDecoder == null) {
logcat { "Failed to create new instance of BitmapRegionDecoder" } logcat { "Failed to create new instance of BitmapRegionDecoder" }
return false return false
} }
val options = extractImageOptions( val options = extractImageOptions(imageSource).apply {
imageFile.openInputStream(),
resetAfterExtraction = false,
).apply {
inJustDecodeBounds = false inJustDecodeBounds = false
} }
@@ -380,8 +366,8 @@ object ImageUtil {
/** /**
* Algorithm for determining what background to accompany a comic/manga page * Algorithm for determining what background to accompany a comic/manga page
*/ */
fun chooseBackground(context: Context, imageStream: InputStream): Drawable { fun chooseBackground(context: Context, imageSource: BufferedSource): Drawable {
val decoder = ImageDecoder.newInstance(imageStream) val decoder = ImageDecoder.newInstance(imageSource.inputStream())
val image = decoder?.decode() val image = decoder?.decode()
decoder?.recycle() decoder?.recycle()
@@ -603,16 +589,9 @@ object ImageUtil {
/** /**
* Used to check an image's dimensions without loading it in the memory. * Used to check an image's dimensions without loading it in the memory.
*/ */
private fun extractImageOptions( private fun extractImageOptions(imageSource: BufferedSource): BitmapFactory.Options {
imageStream: InputStream,
resetAfterExtraction: Boolean = true,
): BitmapFactory.Options {
imageStream.mark(Int.MAX_VALUE)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) BitmapFactory.decodeStream(imageSource.peek().inputStream(), null, options)
if (resetAfterExtraction) imageStream.reset()
return options return options
} }
@@ -657,7 +636,7 @@ object ImageUtil {
centerMargin: Int, centerMargin: Int,
@ColorInt background: Int = Color.WHITE, @ColorInt background: Int = Color.WHITE,
progressCallback: ((Int) -> Unit)? = null, progressCallback: ((Int) -> Unit)? = null,
): ByteArrayInputStream { ): BufferedSource {
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
val height2 = imageBitmap2.height val height2 = imageBitmap2.height
@@ -687,10 +666,10 @@ object ImageUtil {
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null) canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
progressCallback?.invoke(99) progressCallback?.invoke(99)
val output = ByteArrayOutputStream() val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output) result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
progressCallback?.invoke(100) progressCallback?.invoke(100)
return ByteArrayInputStream(output.toByteArray()) return output
} }
private val Bitmap.rect: Rect private val Bitmap.rect: Rect
+9 -9
View File
@@ -1,16 +1,16 @@
[versions] [versions]
agp_version = "8.3.1" agp_version = "8.4.1"
lifecycle_version = "2.7.0" lifecycle_version = "2.8.1"
paging_version = "3.2.1" paging_version = "3.3.0"
[libraries] [libraries]
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
annotation = "androidx.annotation:annotation:1.7.1" annotation = "androidx.annotation:annotation:1.8.0"
appcompat = "androidx.appcompat:appcompat:1.6.1" appcompat = "androidx.appcompat:appcompat:1.7.0"
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
corektx = "androidx.core:core-ktx:1.12.0" corektx = "androidx.core:core-ktx:1.13.1"
splashscreen = "androidx.core:core-splashscreen:1.0.1" splashscreen = "androidx.core:core-splashscreen:1.0.1"
recyclerview = "androidx.recyclerview:recyclerview:1.3.2" recyclerview = "androidx.recyclerview:recyclerview:1.3.2"
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
@@ -25,9 +25,9 @@ workmanager = "androidx.work:work-runtime:2.9.0"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.3" benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.4"
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha03" test-ext = "androidx.test.ext:junit-ktx:1.2.0-rc01"
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha03" test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-rc01"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0" test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0"
[bundles] [bundles]
+4 -6
View File
@@ -1,12 +1,13 @@
[versions] [versions]
compiler = "1.5.11" compiler = "1.5.14"
compose-bom = "2024.02.00-alpha02" # 2024.04.00-alpha01 has several bugs with the new animateItem() modifier
compose-bom = "2024.03.00-alpha02"
accompanist = "0.35.0-alpha" accompanist = "0.35.0-alpha"
[libraries] [libraries]
compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" } compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" }
activity = "androidx.activity:activity-compose:1.8.2" activity = "androidx.activity:activity-compose:1.9.0"
bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" }
foundation = { module = "androidx.compose.foundation:foundation" } foundation = { module = "androidx.compose.foundation:foundation" }
animation = { module = "androidx.compose.animation:animation" } animation = { module = "androidx.compose.animation:animation" }
@@ -18,9 +19,6 @@ ui-util = { module = "androidx.compose.ui:ui-util" }
material3-core = { module = "androidx.compose.material3:material3" } material3-core = { module = "androidx.compose.material3:material3" }
material-icons = { module = "androidx.compose.material:material-icons-extended" } material-icons = { module = "androidx.compose.material:material-icons-extended" }
# Some components aren't available in Material3
material-core = { module = "androidx.compose.material:material" }
glance = "androidx.glance:glance-appwidget:1.0.0" glance = "androidx.glance:glance-appwidget:1.0.0"
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" } accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
+1 -1
View File
@@ -1,5 +1,5 @@
[versions] [versions]
kotlin_version = "1.9.23" kotlin_version = "1.9.24"
serialization_version = "1.6.3" serialization_version = "1.6.3"
xml_serialization_version = "0.86.3" xml_serialization_version = "0.86.3"
+10 -14
View File
@@ -1,7 +1,6 @@
[versions] [versions]
aboutlib_version = "11.1.1" aboutlib_version = "11.2.1"
acra = "5.11.3" leakcanary = "2.14"
leakcanary = "2.13"
moko = "0.23.0" moko = "0.23.0"
okhttp_version = "5.0.0-alpha.12" okhttp_version = "5.0.0-alpha.12"
richtext = "0.20.0" richtext = "0.20.0"
@@ -15,7 +14,7 @@ detektCompose = "0.3.12"
[libraries] [libraries]
desugar = "com.android.tools:desugar_jdk_libs:2.0.4" desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
google-services-gradle = "com.google.gms:google-services:4.4.1" google-services-gradle = "com.google.gms:google-services:4.4.2"
rxjava = "io.reactivex:rxjava:1.3.8" rxjava = "io.reactivex:rxjava:1.3.8"
@@ -32,8 +31,8 @@ quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2"
jsoup = "org.jsoup:jsoup:1.17.2" jsoup = "org.jsoup:jsoup:1.17.2"
disklrucache = "com.jakewharton:disklrucache:2.0.2" disklrucache = "com.jakewharton:disklrucache:2.0.2"
unifile = "com.github.tachiyomiorg:unifile:7c257e1c64" unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc"
common-compress = "org.apache.commons:commons-compress:1.26.1" common-compress = "org.apache.commons:commons-compress:1.26.2"
junrar = "com.github.junrar:junrar:7.5.5" junrar = "com.github.junrar:junrar:7.5.5"
zip4j = "net.lingala.zip4j:zip4j:2.11.5" zip4j = "net.lingala.zip4j:zip4j:2.11.5"
@@ -52,7 +51,7 @@ coil-gif = { module = "io.coil-kt.coil3:coil-gif" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose" } coil-compose = { module = "io.coil-kt.coil3:coil-compose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" }
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:aeaa170036" subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:b8e1b0ed2b"
image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535" image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535"
exifinterface = "androidx.exifinterface:exifinterface:1.3.7" exifinterface = "androidx.exifinterface:exifinterface:1.3.7"
@@ -61,7 +60,7 @@ natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" } richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" } richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
material = "com.google.android.material:material:1.11.0" material = "com.google.android.material:material:1.12.0"
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533" flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
photoview = "com.github.chrisbanes:PhotoView:2.3.0" photoview = "com.github.chrisbanes:PhotoView:2.3.0"
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
@@ -77,9 +76,7 @@ moko-gradle = { module = "dev.icerock.moko:resources-generator", version.ref = "
logcat = "com.squareup.logcat:logcat:0.1" logcat = "com.squareup.logcat:logcat:0.1"
acra-http = { module = "ch.acra:acra-http", version.ref = "acra" } firebase-analytics = "com.google.firebase:firebase-analytics:22.0.1"
acra-scheduler = { module = "ch.acra:acra-advanced-scheduler", version.ref = "acra" }
firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.6.1"
aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" } aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" }
aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlib_version" } aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlib_version" }
@@ -97,8 +94,8 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect",
sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" } sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" }
junit = "org.junit.jupiter:junit-jupiter:5.10.2" junit = "org.junit.jupiter:junit-jupiter:5.10.2"
kotest-assertions = "io.kotest:kotest-assertions-core:5.8.1" kotest-assertions = "io.kotest:kotest-assertions-core:5.9.0"
mockk = "io.mockk:mockk:1.13.10" mockk = "io.mockk:mockk:1.13.11"
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
@@ -113,7 +110,6 @@ google-api-services-drive = "com.google.apis:google-api-services-drive:v3-rev197
google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1" google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1"
[bundles] [bundles]
acra = ["acra-http", "acra-scheduler"]
archive = ["common-compress", "junrar"] archive = ["common-compress", "junrar"]
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
js-engine = ["quickjs-android"] js-engine = ["quickjs-android"]
+3 -3
View File
@@ -1,8 +1,8 @@
[versions] [versions]
[libraries] [libraries]
firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.6.1" firebase-analytics = "com.google.firebase:firebase-analytics:22.0.0"
firebase-crashlytics-ktx = "com.google.firebase:firebase-crashlytics-ktx:18.6.3" firebase-crashlytics-ktx = "com.google.firebase:firebase-crashlytics:19.0.0"
firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.9.9" firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
simularity = "info.debatty:java-string-similarity:2.0.0" simularity = "info.debatty:java-string-similarity:2.0.0"
@@ -11,4 +11,4 @@ xlog = "com.elvishew:xlog:1.11.0"
ratingbar = "me.zhanghai.android.materialratingbar:library:1.4.0" ratingbar = "me.zhanghai.android.materialratingbar:library:1.4.0"
composeRatingbar = "com.github.a914-gowtham:compose-ratingbar:1.2.3" composeRatingbar = "com.github.a914-gowtham:compose-ratingbar:1.2.3"
versionsx = "com.github.ben-manes:gradle-versions-plugin:0.42.0" versionsx = "com.github.ben-manes:gradle-versions-plugin:0.51.0"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -203,10 +203,8 @@
<string name="pref_local_source_hidden_folders_summery">Allow local source to read hidden folders</string> <string name="pref_local_source_hidden_folders_summery">Allow local source to read hidden folders</string>
<!-- Backup settings --> <!-- Backup settings -->
<string name="pref_backup_and_sync_summary">Manual &amp; automatic backups and sync</string>
<string name="custom_entry_info">Custom entry info</string> <string name="custom_entry_info">Custom entry info</string>
<string name="all_read_entries">All read entries</string> <string name="all_read_entries">All read entries</string>
<string name="label_backup">Backup</string>
<string name="label_sync">Sync</string> <string name="label_sync">Sync</string>
<string name="label_triggers">Triggers</string> <string name="label_triggers">Triggers</string>
@@ -220,24 +218,16 @@
<string name="pref_sync_api_key_summ">Enter the API key to synchronize your library</string> <string name="pref_sync_api_key_summ">Enter the API key to synchronize your library</string>
<string name="pref_sync_now_group_title">Sync Actions</string> <string name="pref_sync_now_group_title">Sync Actions</string>
<string name="pref_sync_now">Sync now</string> <string name="pref_sync_now">Sync now</string>
<string name="pref_sync_confirmation_title">Sync confirmation</string>
<string name="pref_sync_now_subtitle">Initiate immediate synchronization of your data</string> <string name="pref_sync_now_subtitle">Initiate immediate synchronization of your data</string>
<string name="pref_sync_confirmation_message">Syncing will overwrite your local library with the remote library. Are you sure you want to continue?</string>
<string name="pref_sync_service">Service</string> <string name="pref_sync_service">Service</string>
<string name="pref_sync_service_summ">Select the service to sync your library with</string>
<string name="pref_sync_service_category">Sync</string> <string name="pref_sync_service_category">Sync</string>
<string name="pref_sync_automatic_category">Automatic Synchronization</string> <string name="pref_sync_automatic_category">Automatic Synchronization</string>
<string name="pref_sync_interval">Synchronization frequency</string> <string name="pref_sync_interval">Synchronization frequency</string>
<string name="pref_choose_what_to_sync">Choose what to sync</string> <string name="pref_choose_what_to_sync">Choose what to sync</string>
<string name="success_reset_sync_timestamp">Last sync timestamp reset</string>
<string name="syncyomi">SyncYomi</string> <string name="syncyomi">SyncYomi</string>
<string name="sync_completed_message">Done in %1$s</string>
<string name="last_synchronization">Last Synchronization: %1$s</string> <string name="last_synchronization">Last Synchronization: %1$s</string>
<string name="google_drive">Google Drive</string> <string name="google_drive">Google Drive</string>
<string name="pref_google_drive_sign_in">Sign in</string> <string name="pref_google_drive_sign_in">Sign in</string>
<string name="google_drive_sign_in_success">Signed in successfully</string>
<string name="google_drive_sign_in_failed">Sign in failed</string>
<string name="authentication">Authentication</string>
<string name="pref_google_drive_purge_sync_data">Clear Sync Data from Google Drive</string> <string name="pref_google_drive_purge_sync_data">Clear Sync Data from Google Drive</string>
<string name="google_drive_sync_data_purged">Sync data purged from Google Drive</string> <string name="google_drive_sync_data_purged">Sync data purged from Google Drive</string>
<string name="google_drive_sync_data_not_found">No sync data found in Google Drive</string> <string name="google_drive_sync_data_not_found">No sync data found in Google Drive</string>
@@ -134,7 +134,6 @@
<string name="data_saver_image_format_summary_on">La taille du fichier Jpeg est considérablement plus petite que Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n Actuellement compresse en Jpeg</string> <string name="data_saver_image_format_summary_on">La taille du fichier Jpeg est considérablement plus petite que Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n Actuellement compresse en Jpeg</string>
<string name="data_saver_image_format_summary_off">La taille du fichier Jpeg est considérablement plus petite que celle de Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n. Actuellement compresse en Webp</string> <string name="data_saver_image_format_summary_off">La taille du fichier Jpeg est considérablement plus petite que celle de Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n. Actuellement compresse en Webp</string>
<string name="data_saver_color_bw">Convertir en noir et blanc</string> <string name="data_saver_color_bw">Convertir en noir et blanc</string>
<string name="data_saver_server">Serveur proxy Bandwidth Hero</string>
<string name="data_saver_server_summary">Mettez ici l\'URL du serveur Bandwidth Hero Proxy</string> <string name="data_saver_server_summary">Mettez ici l\'URL du serveur Bandwidth Hero Proxy</string>
<!-- Log Level --> <!-- Log Level -->
@@ -139,7 +139,6 @@
<string name="data_saver_image_format_summary_on">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Jpeg</string> <string name="data_saver_image_format_summary_on">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Jpeg</string>
<string name="data_saver_image_format_summary_off">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Webp</string> <string name="data_saver_image_format_summary_off">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Webp</string>
<string name="data_saver_color_bw">Konversi ke Hitam Putih</string> <string name="data_saver_color_bw">Konversi ke Hitam Putih</string>
<string name="data_saver_server">Server Bandwidth Hero Proxy</string>
<string name="data_saver_server_summary">Masukkan url server Bandwidth Hero Proxy disini</string> <string name="data_saver_server_summary">Masukkan url server Bandwidth Hero Proxy disini</string>
<string name="clear_db_exclude_read">Abaikab manga yang sudah dibaca</string> <string name="clear_db_exclude_read">Abaikab manga yang sudah dibaca</string>
@@ -138,7 +138,6 @@
<string name="data_saver_image_format_summary_on">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Jpeg</string> <string name="data_saver_image_format_summary_on">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Jpeg</string>
<string name="data_saver_image_format_summary_off">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Webp</string> <string name="data_saver_image_format_summary_off">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Webp</string>
<string name="data_saver_color_bw">Converter em Preto e Branco</string> <string name="data_saver_color_bw">Converter em Preto e Branco</string>
<string name="data_saver_server">Servidor Bandwidth Hero Proxy</string>
<string name="data_saver_server_summary">Insira a url do servidor Bandwidth Hero Proxy aqui</string> <string name="data_saver_server_summary">Insira a url do servidor Bandwidth Hero Proxy aqui</string>
<string name="clear_db_exclude_read">Manter mangás com capítulos lidos</string> <string name="clear_db_exclude_read">Manter mangás com capítulos lidos</string>
@@ -179,6 +179,9 @@
<string name="pref_mark_read_dupe_chapters_summary">Помечать повторяющиеся главы как «Прочитано» после прочтения</string> <string name="pref_mark_read_dupe_chapters_summary">Помечать повторяющиеся главы как «Прочитано» после прочтения</string>
<string name="pref_library_mark_duplicate_chapters">Помечать новые повторяющиеся главы как «Прочитано»</string> <string name="pref_library_mark_duplicate_chapters">Помечать новые повторяющиеся главы как «Прочитано»</string>
<string name="pref_library_mark_duplicate_chapters_summary">Автоматически помечать новые главы как «Прочитано», если они были ранее прочитаны</string> <string name="pref_library_mark_duplicate_chapters_summary">Автоматически помечать новые главы как «Прочитано», если они были ранее прочитаны</string>
<string name="update_30min">Каждые 30 минут</string>
<string name="update_1hour">Каждый час</string>
<string name="update_3hour">Каждые 3 часа</string>
<!-- Browse settings --> <!-- Browse settings -->
<string name="pref_hide_feed">Скрыть вкладку «Лента»</string> <string name="pref_hide_feed">Скрыть вкладку «Лента»</string>
@@ -194,6 +197,47 @@
<!-- Backup settings --> <!-- Backup settings -->
<string name="custom_entry_info">Сведенья пользователя</string> <string name="custom_entry_info">Сведенья пользователя</string>
<string name="all_read_entries">Прочитанные серии</string> <string name="all_read_entries">Прочитанные серии</string>
<string name="label_sync">Синхронизировать</string>
<string name="label_triggers">Триггеры</string>
<!-- Sync settings -->
<string name="sync_error">Не удалось синхронизировать библиотеку</string>
<string name="sync_complete">Синхронизация библиотеки завершена</string>
<string name="sync_in_progress">Синхронизация уже выполняется</string>
<string name="pref_sync_host">Хост</string>
<string name="pref_sync_host_summ">Введите адрес хоста для синхронизации вашей библиотеки</string>
<string name="pref_sync_api_key">Ключ API</string>
<string name="pref_sync_api_key_summ">Введите API ключ для синхронизации вашей библиотеки</string>
<string name="pref_sync_now_group_title">Действия синхронизации</string>
<string name="pref_sync_now">Синхронизировать сейчас</string>
<string name="pref_sync_now_subtitle">Начать немедленную синхронизацию ваших данных</string>
<string name="pref_sync_service">Сервис</string>
<string name="pref_sync_service_category">Синхронизация</string>
<string name="pref_sync_automatic_category">Автоматическая синхронизация</string>
<string name="pref_sync_interval">Частота синхронизации</string>
<string name="pref_choose_what_to_sync">Выберите, что синхронизировать</string>
<string name="last_synchronization">Последняя синхронизация: %1$s</string>
<string name="google_drive">Google Диск</string>
<string name="pref_google_drive_sign_in">Войти в Google Диск</string>
<string name="pref_google_drive_purge_sync_data">Очистить данные синхронизации с Google Диска</string>
<string name="google_drive_sync_data_purged">Данные синхронизации удалены из Google Диска</string>
<string name="google_drive_sync_data_not_found">Данные синхронизации не найдены в Google Диске</string>
<string name="google_drive_sync_data_purge_error">Ошибка при очистке данных синхронизации с Google Диска, попробуйте войти снова.</string>
<string name="google_drive_login_success">Выполнен вход в Google Диск</string>
<string name="google_drive_login_failed">Не удалось войти в Google Диск: %s</string>
<string name="google_drive_not_signed_in">Не выполнен вход в Google Диск</string>
<string name="error_uploading_sync_data">Ошибка при загрузке данных синхронизации на Google Диск</string>
<string name="error_deleting_google_drive_lock_file">Ошибка удаления файла блокировки Google Диска</string>
<string name="error_before_sync_gdrive">Ошибка перед синхронизацией: %s</string>
<string name="pref_purge_confirmation_title">Подтверждение очистки</string>
<string name="pref_purge_confirmation_message">Очистка данных синхронизации приведет к удалению всех данных синхронизации с Google Диска. Вы уверены, что хотите продолжить?</string>
<string name="pref_sync_options">Создать триггеры синхронизации</string>
<string name="pref_sync_options_summ">Можно использовать для настройки триггеров синхронизации</string>
<string name="sync_on_chapter_read">Синхронизовать при прочтении главы</string>
<string name="sync_on_chapter_open">Синхронизовать при открытии главы</string>
<string name="sync_on_app_start">Синхронизовать при запуске приложения</string>
<string name="sync_on_app_resume">Синхронизовать при возвращении в приложение</string>
<string name="sync_library">Синхронизировать библиотеку</string>
<!-- Security settings --> <!-- Security settings -->
<string name="biometric_lock_times">Биометрическое время блокировки</string> <string name="biometric_lock_times">Биометрическое время блокировки</string>
@@ -496,7 +496,6 @@
<string name="launching_app">應用程式啟動中……</string> <string name="launching_app">應用程式啟動中……</string>
<string name="error_with_reason">錯誤:%1$s</string> <string name="error_with_reason">錯誤:%1$s</string>
<string name="could_not_open_entry">無法開啟此作品:\n\n%1$s</string> <string name="could_not_open_entry">無法開啟此作品:\n\n%1$s</string>
<string name="entry_id_is_null">作品 ID 為空!</string>
<string name="loading_entry">作品載入中……</string> <string name="loading_entry">作品載入中……</string>
<!-- Page previews --> <!-- Page previews -->
@@ -612,7 +611,6 @@
<!-- Similar --> <!-- Similar -->
<string name="similar">與 %1$s 相似</string> <string name="similar">與 %1$s 相似</string>
<string name="similar_no_results">相似作品未找到</string>
<!-- MangaDex relations--> <!-- MangaDex relations-->
<string name="relation_similar">相似</string> <string name="relation_similar">相似</string>
-1
View File
@@ -24,7 +24,6 @@ dependencies {
implementation(compose.activity) implementation(compose.activity)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material3.core) implementation(compose.material3.core)
implementation(compose.material.core)
implementation(compose.material.icons) implementation(compose.material.icons)
implementation(compose.animation) implementation(compose.animation)
implementation(compose.animation.graphics) implementation(compose.animation.graphics)
@@ -10,7 +10,6 @@ import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.gestures.animateTo import androidx.compose.foundation.gestures.animateTo
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
@@ -39,7 +38,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -54,7 +52,6 @@ private val sheetAnimationSpec = tween<Float>(durationMillis = 350)
@Composable @Composable
fun AdaptiveSheet( fun AdaptiveSheet(
isTabletUi: Boolean, isTabletUi: Boolean,
tonalElevation: Dp,
enableSwipeDismiss: Boolean, enableSwipeDismiss: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -78,8 +75,7 @@ fun AdaptiveSheet(
Box( Box(
modifier = Modifier modifier = Modifier
.clickable( .clickable(
enabled = true, interactionSource = null,
interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
onClick = internalOnDismissRequest, onClick = internalOnDismissRequest,
) )
@@ -91,7 +87,7 @@ fun AdaptiveSheet(
modifier = Modifier modifier = Modifier
.requiredWidthIn(max = 460.dp) .requiredWidthIn(max = 460.dp)
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = null,
indication = null, indication = null,
onClick = {}, onClick = {},
) )
@@ -99,7 +95,7 @@ fun AdaptiveSheet(
.padding(vertical = 16.dp) .padding(vertical = 16.dp)
.then(modifier), .then(modifier),
shape = MaterialTheme.shapes.extraLarge, shape = MaterialTheme.shapes.extraLarge,
tonalElevation = tonalElevation, color = MaterialTheme.colorScheme.surfaceContainerHigh,
content = { content = {
BackHandler(enabled = alpha > 0f, onBack = internalOnDismissRequest) BackHandler(enabled = alpha > 0f, onBack = internalOnDismissRequest)
content() content()
@@ -122,14 +118,14 @@ fun AdaptiveSheet(
) )
} }
val internalOnDismissRequest = { val internalOnDismissRequest = {
if (anchoredDraggableState.currentValue == 0) { if (anchoredDraggableState.settledValue == 0) {
scope.launch { anchoredDraggableState.animateTo(1) } scope.launch { anchoredDraggableState.animateTo(1) }
} }
} }
Box( Box(
modifier = Modifier modifier = Modifier
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = null,
indication = null, indication = null,
onClick = internalOnDismissRequest, onClick = internalOnDismissRequest,
) )
@@ -147,7 +143,7 @@ fun AdaptiveSheet(
modifier = Modifier modifier = Modifier
.widthIn(max = 460.dp) .widthIn(max = 460.dp)
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = null,
indication = null, indication = null,
onClick = {}, onClick = {},
) )
@@ -180,7 +176,7 @@ fun AdaptiveSheet(
.navigationBarsPadding() .navigationBarsPadding()
.statusBarsPadding(), .statusBarsPadding(),
shape = MaterialTheme.shapes.extraLarge, shape = MaterialTheme.shapes.extraLarge,
tonalElevation = tonalElevation, color = MaterialTheme.colorScheme.surfaceContainerHigh,
content = { content = {
BackHandler( BackHandler(
enabled = anchoredDraggableState.targetValue == 0, enabled = anchoredDraggableState.targetValue == 0,
@@ -192,7 +188,7 @@ fun AdaptiveSheet(
LaunchedEffect(anchoredDraggableState) { LaunchedEffect(anchoredDraggableState) {
scope.launch { anchoredDraggableState.animateTo(0) } scope.launch { anchoredDraggableState.animateTo(0) }
snapshotFlow { anchoredDraggableState.currentValue } snapshotFlow { anchoredDraggableState.settledValue }
.drop(1) .drop(1)
.filter { it == 1 } .filter { it == 1 }
.collectLatest { .collectLatest {
@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -18,9 +17,8 @@ import androidx.compose.ui.unit.dp
fun Pill( fun Pill(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.background, color: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor: Color = MaterialTheme.colorScheme.onBackground, contentColor: Color = MaterialTheme.colorScheme.onSurface,
elevation: Dp = 1.dp,
fontSize: TextUnit = LocalTextStyle.current.fontSize, fontSize: TextUnit = LocalTextStyle.current.fontSize,
) { ) {
Surface( Surface(
@@ -29,7 +27,6 @@ fun Pill(
shape = MaterialTheme.shapes.extraLarge, shape = MaterialTheme.shapes.extraLarge,
color = color, color = color,
contentColor = contentColor, contentColor = contentColor,
tonalElevation = elevation,
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.ArrowUpward
@@ -57,6 +56,8 @@ object SettingsItemsPaddings {
val Vertical = 10.dp val Vertical = 10.dp
} }
private const val DisabledContentAlpha = 0.38f
@Composable @Composable
fun HeadingItem(labelRes: StringResource) { fun HeadingItem(labelRes: StringResource) {
HeadingItem(stringResource(labelRes)) HeadingItem(stringResource(labelRes))
@@ -282,7 +283,7 @@ fun TriStateItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large),
) { ) {
val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled val stateAlpha = if (enabled && onClick != null) 1f else DisabledContentAlpha
Icon( Icon(
imageVector = when (state) { imageVector = when (state) {
@@ -295,7 +296,7 @@ fun TriStateItem(
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha)
} else { } else {
when (onClick) { when (onClick) {
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled) null -> MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledContentAlpha)
else -> MaterialTheme.colorScheme.primary else -> MaterialTheme.colorScheme.primary
} }
}, },
@@ -100,7 +100,6 @@ fun Button(
val containerColor = colors.containerColor(enabled).value val containerColor = colors.containerColor(enabled).value
val contentColor = colors.contentColor(enabled).value val contentColor = colors.contentColor(enabled).value
val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
val tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp
Surface( Surface(
onClick = onClick, onClick = onClick,
@@ -109,7 +108,6 @@ fun Button(
shape = shape, shape = shape,
color = containerColor, color = containerColor,
contentColor = contentColor, contentColor = contentColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation, shadowElevation = shadowElevation,
border = border, border = border,
interactionSource = interactionSource, interactionSource = interactionSource,
@@ -6,13 +6,13 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.material.ripple
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.LocalAbsoluteTonalElevation import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.NonRestartableComposable
@@ -1,7 +1,6 @@
package tachiyomi.presentation.core.util package tachiyomi.presentation.core.util
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.isImeVisible
@@ -42,14 +41,12 @@ fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)
fun Modifier.clickableNoIndication( fun Modifier.clickableNoIndication(
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: () -> Unit, onClick: () -> Unit,
): Modifier = composed { ) = this.combinedClickable(
Modifier.combinedClickable( interactionSource = null,
interactionSource = remember { MutableInteractionSource() }, indication = null,
indication = null, onLongClick = onLongClick,
onLongClick = onLongClick, onClick = onClick,
onClick = onClick, )
)
}
/** /**
* For TextField, the provided [action] will be invoked when * For TextField, the provided [action] will be invoked when
@@ -17,16 +17,10 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import logcat.LogPriority import logcat.LogPriority
import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
import tachiyomi.core.metadata.comicinfo.getComicInfo
import tachiyomi.core.metadata.tachiyomi.MangaDetails
import tachiyomi.core.common.storage.UniFileTempFileManager import tachiyomi.core.common.storage.UniFileTempFileManager
import tachiyomi.core.common.storage.addStreamToZip import tachiyomi.core.common.storage.addStreamToZip
import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.extension
@@ -37,6 +31,12 @@ import tachiyomi.core.common.storage.nameWithoutExtension
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
import tachiyomi.core.metadata.comicinfo.getComicInfo
import tachiyomi.core.metadata.tachiyomi.MangaDetails
import tachiyomi.domain.chapter.service.ChapterRecognition import tachiyomi.domain.chapter.service.ChapterRecognition
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -157,17 +157,39 @@ actual class LocalSource(
// SY --> // SY -->
fun updateMangaInfo(manga: SManga) { fun updateMangaInfo(manga: SManga) {
val existingFile = fileSystem.getFilesInMangaDirectory(manga.url).find { it.extension == "json" } val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url)
val file = existingFile val existingFile = mangaDirFiles
?: fileSystem.getMangaDirectory(manga.url)?.createFile("info.json") .firstOrNull { it.name == COMIC_INFO_FILE }
?: return val comicInfoArchiveFile = mangaDirFiles
file.openOutputStream().use { .firstOrNull { it.name == COMIC_INFO_ARCHIVE }
json.encodeToStream(manga.toJson(), it) val existingComicInfo = (existingFile?.openInputStream() ?: comicInfoArchiveFile?.getZipInputStream(COMIC_INFO_FILE))?.use {
AndroidXmlReader(it, StandardCharsets.UTF_8.name()).use {
xml.decodeFromReader<ComicInfo>(it)
}
}
val newComicInfo = if (existingComicInfo != null) {
manga.run {
existingComicInfo.copy(
series = ComicInfo.Series(title),
summary = description?.let { ComicInfo.Summary(it) },
writer = author?.let { ComicInfo.Writer(it) },
penciller = artist?.let { ComicInfo.Penciller(it) },
genre = genre?.let { ComicInfo.Genre(it) },
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
ComicInfoPublishingStatus.toComicInfoValue(status.toLong()),
),
)
}
} else {
manga.getComicInfo()
} }
}
private fun SManga.toJson(): MangaDetails { fileSystem.getMangaDirectory(manga.url)?.let {
return MangaDetails(title, author, artist, description, genre?.split(", "), status) copyComicInfoFile(
xml.encodeToString(ComicInfo.serializer(), newComicInfo).byteInputStream(),
it
)
}
} }
// SY <-- // SY <--
@@ -368,8 +390,8 @@ actual class LocalSource(
try { try {
val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2) val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2)
return fileSystem.getBaseDirectory() return fileSystem.getBaseDirectory()
?.findFile(mangaDirName, true) ?.findFile(mangaDirName)
?.findFile(chapterName, true) ?.findFile(chapterName)
?.let(Format.Companion::valueOf) ?.let(Format.Companion::valueOf)
?: throw Exception(context.stringResource(MR.strings.chapter_not_found)) ?: throw Exception(context.stringResource(MR.strings.chapter_not_found))
} catch (e: Format.UnknownFormatException) { } catch (e: Format.UnknownFormatException) {
@@ -17,13 +17,13 @@ actual class LocalSourceFileSystem(
actual fun getMangaDirectory(name: String): UniFile? { actual fun getMangaDirectory(name: String): UniFile? {
return getBaseDirectory() return getBaseDirectory()
?.findFile(name, true) ?.findFile(name)
?.takeIf { it.isDirectory } ?.takeIf { it.isDirectory }
} }
actual fun getFilesInMangaDirectory(name: String): List<UniFile> { actual fun getFilesInMangaDirectory(name: String): List<UniFile> {
return getBaseDirectory() return getBaseDirectory()
?.findFile(name, true) ?.findFile(name)
?.takeIf { it.isDirectory } ?.takeIf { it.isDirectory }
?.listFiles().orEmpty().toList() ?.listFiles().orEmpty().toList()
} }