Compare commits

...

30 Commits

Author SHA1 Message Date
AntsyLich dea38912fc Use feature flags in compose compiler plugin
And slight cleanup

(cherry picked from commit 8f9a325895bb7b94c2ec92dd969094fc30b3b5e2)
2024-09-01 11:39:34 -04:00
renovate[bot] 5243346356 fix(deps): update dependency com.android.tools.build:gradle to v8.6.0 (#1178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f74071ab0a70c4fd649b451e58841539d011496a)
2024-09-01 11:39:27 -04:00
renovate[bot] 6bb3ec5b8a fix(deps): update dependency com.android.tools:desugar_jdk_libs to v2.1.1 (#1172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7fb3ef48e4fafce471173111fe1632754e5e9e99)
2024-09-01 11:39:20 -04:00
renovate[bot] 7390e72045 fix(deps): update serialization.version to v1.7.2 (#1173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 1837faa573f11a6b97fe13f358d6fa0e980c2ef7)
2024-09-01 11:39:12 -04:00
AntsyLich 64ff5cb9af Remove legacy broken source and history backup
(cherry picked from commit 518abf032ccb9bb45d197927be2a5faca4167d29)
2024-09-01 11:38:46 -04:00
Roshan Varughese 82f011e48e Hide keyboard when a Tracker SearchResultItem is clicked (#1168)
* Hide keyboard on select

* Code Review Suggestion

(cherry picked from commit 7ca64a67c5c64103aa3a5c7efb9227d3a98b715d)
2024-09-01 11:38:36 -04:00
renovate[bot] 8868a5db2b fix(deps): update dependency com.android.tools:desugar_jdk_libs to v2.1.0 (#1162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 607e56a4ec6393a3bfd25fe74cbae676fd94df22)
2024-09-01 11:38:27 -04:00
Catting a335feedfc Add "show entry" action to download notifications (#1159)
* Add 'show entry' to download notifications

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>

* fixup! Add 'show entry' to download notifications

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>

* fixup! Add 'show entry' to download notifications

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>

* spotless! Add 'show entry' to download notifications

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* fixup! spotless- Apply suggestions from code review

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>

---------

Signed-off-by: Catting <5874051+mm12@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 952a98c1804b2134e59fcb471c54cf7c4e1f415e)
2024-09-01 11:38:14 -04:00
Roshan Varughese 90ff5e69ec Add confirmation when adding repo via URI (#1158)
* Add confirmation when adding repo via URI

* Blank lines

* Suggestions

* Reverting Changes

* Removing Unused Imports

(cherry picked from commit 45628b14db477b266eb1f1f4ca9bec0b43f741cc)
2024-09-01 11:37:58 -04:00
Roshan Varughese 68a1820695 Respect privacy settings in extension update notification (#1156)
* Hide Extension Names in Update Notifications when Content is Hidden

* Moving `val` inside if

* [skip ci] Update CHANGELOG.md

(cherry picked from commit 5dc6569a683da47f5323c252fce1bd4094a5d232)

# Conflicts:
#	CHANGELOG.md
2024-09-01 11:37:48 -04:00
renovate[bot] 292b551027 fix(deps): update aboutlib.version to v11.2.3 (#1151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit fba9bacdc19dee7cdf9e3d1cb4ee4a496fa7b514)
2024-09-01 11:37:23 -04:00
Dani e19c62a8ae Add option to skip downloading duplicate read chapters (#1125)
* Add query to get chapter count by manga and chapter number

* Add functions to get chapter count by manga and chapter number

* Only count read chapters

* Add interactor

* Savepoint

* Extract new chapter logic to separate function

* Update javadocs

* Add preference to toggle new functionality

* Add todo

* Add debug logcat

* Use string resource instead of hardcoding title

* Add temporary logcat for debugging

* Fix detekt issues

* Update javadocs

* Update download unread chapters preference

* Remove debug logcat calls

* Update javadocs

* Resolve issue where read chapters were still being downloaded during manual manga fetch

* Apply code review changes

* Apply code review changes

* Revert "Apply code review changes"

This reverts commit 1a2dce78acc66a7c529ce5b572bdaf94804b1a30.

* Revert "Apply code review changes"

This reverts commit ac2a77829313967ad39ce3cb0c0231083b9d640d.

* Group download chapter logic inside the interactor GetChaptersToDownload

* Update javadocs

* Apply code review

* Apply code review

* Apply code review

* Update CHANGELOG.md to include the new feature

* Run spotless

* Update domain/src/main/java/mihon/domain/chapter/interactor/FilterChaptersForDownload.kt

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit ca968f162ef7a61a9036b7ab9bea407a6334801d)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2024-09-01 11:37:04 -04:00
renovate[bot] 348cb335c4 fix(deps): update moko to v0.24.2 (#1148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 379d5878266ba0287bfcc4a06452c27d70f33ba1)
2024-09-01 10:53:49 -04:00
renovate[bot] c426d11d76 chore(deps): update kotlin monorepo to v2.0.20 (#1144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 034ec4cb120c0f36cad1303de1314c28c4ec4969)
2024-09-01 10:53:39 -04:00
renovate[bot] 1965c0825d fix(deps): update dependency com.google.firebase:firebase-analytics to v22.1.0 (#1146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit ab2b734d49299dc3b30e0a7f0d5cbb268b0bff97)
2024-09-01 10:53:27 -04:00
renovate[bot] 0124763fcd fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.3.0 (#1142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 08ae51ea8c5ceccc8c5c65120f387d7b19d18052)
2024-09-01 10:53:18 -04:00
Hosted Weblate b0d737592c Translations update from Hosted Weblate
Co-authored-by: Ahmed seif al-nasr <ahmdsyfalnsr2@gmail.com>
Co-authored-by: Anas KANJO <anas.kanjo2022@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <cinardogan110@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gekka <1778962971@qq.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals
(cherry picked from commit 4387ae5ff3131dd4aaaacd75fa6e82e7b322d474)

# Conflicts:
#	i18n/src/commonMain/moko-resources/tr/strings.xml
2024-09-01 10:53:09 -04:00
renovate[bot] e14596465b fix(deps): update dependency org.conscrypt:conscrypt-android to v2.5.3 (#1135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b2f1719c50365279e157a3b9ee015fc6c13a9a92)
2024-09-01 10:51:14 -04:00
renovate[bot] 5e52dfcc66 chore(deps): update dependency gradle to v8.10 (#1122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 3f050a83dd0907e0ffb56a1e1833f9de5b10b329)
2024-09-01 10:51:07 -04:00
renovate[bot] a09643fc77 fix(deps): update dependency org.junit.jupiter:junit-jupiter to v5.11.0 (#1121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 6f4e3f776f98d7a47dfa33b2cdfe992fc211ec28)
2024-09-01 10:51:00 -04:00
NGB-Was-Taken 19bc08659b Move "Choose what to sync" out of "Sync now" (#1264) 2024-09-01 10:45:30 -04:00
NGB-Was-Taken 2cb8f8f872 Show local chapters as downloaded on merged entries. (#1262)
* Show local chapters as downloaded on merged entries.

* Disable downloadIndicator for local chapters on merged entries.
2024-09-01 10:45:20 -04:00
NGB-Was-Taken 23c7bb09d3 Update links. (#1261)
* Update links.

* [skip ci] Change a tachiyomi.org link.
2024-09-01 10:45:11 -04:00
NGB-Was-Taken bdb8553e28 Respect thumbnailQuality and tryUsingFirstVolumeCover preferences. (MD) (#1260) 2024-09-01 10:45:01 -04:00
Weblate (bot) fbac29e0cd Translations update from Hosted Weblate (#1259)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Fadhil Muhammad <alpanumerik1@gmail.com>
Co-authored-by: HDYOU <308485965@qq.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NGB-Was-Taken <myalternate34@gmail.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2024-09-01 10:44:49 -04:00
Jobobby04 b0aa2ffc42 Test to auto-add translations label 2024-08-23 16:39:17 -04:00
Jobobby04 45b5d9b8a4 Exclude weblate strings 2024-08-23 16:26:54 -04:00
Tran M. Cuong 91b98cdb82 Fix spotless error caused by #1253 being created before apply spotless (#1258) 2024-08-23 08:59:22 -04:00
Weblate (bot) 7f544f7163 Translations update from Hosted Weblate (#1253)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ja/
Translation: Mihon/TachiyomiSY

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2024-08-22 21:37:06 -04:00
Tran M. Cuong 3705880a77 Implement Mihon's spotless PR (#1257)
* Remove detekt (mihonapp/mihon#1130)

Annoying. More annoying in this project.

(cherry picked from commit 777ae2461e1eb277a3aa0c998ff69e4f100387a1)

* Add spotless (with ktlint) (mihonapp/mihon#1136)

(cherry picked from commit 5ae8095ef1ed2ae9f98486f9148e933c77a28692)

* Address spotless lint errors (mihonapp/mihon#1138)

* Add spotless (with ktlint)

* Run spotlessApply

* screaming case screaming case screaming case

* Update PagerViewerAdapter.kt

* Update ReaderTransitionView.kt

(cherry picked from commit d6252ab7703d52ecf9f43de3ee36fd63e665a31f)

* Generate locales_config.xml in build dir

(cherry picked from commit ac41bffdc97b4cfed923de6b9e8e01cccf3eb6eb)

* Address more spotless lint errors in SY

* some more missed

* more missed

* still missing, not sure while it won't report error when running locally

* one more

* more

* more

* correct comment

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-08-22 21:24:50 -04:00
397 changed files with 2200 additions and 3551 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
- name: Build app - name: Build app
run: ./gradlew detekt assembleDevDebug run: ./gradlew spotlessCheck assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
# SY --> # SY -->
- name: Build app and run unit tests - name: Build app and run unit tests
run: ./gradlew detekt assembleStandardRelease testStandardReleaseUnitTest --stacktrace run: ./gradlew spotlessCheck assembleStandardRelease testStandardReleaseUnitTest --stacktrace
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1
+26
View File
@@ -0,0 +1,26 @@
name: Label PRs
on:
pull_request:
types: [opened]
jobs:
label_pr:
runs-on: ubuntu-latest
steps:
- name: Check PR and Add Label
uses: actions/github-script@v7
with:
script: |
const prAuthor = context.payload.pull_request.user.login;
if (prAuthor === 'weblate') {
const labels = ['Translations'];
await github.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: labels
});
}
+7 -14
View File
@@ -1,4 +1,4 @@
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing). Looking to report an issue/bug or make a feature request? Please refer to the [README file](/README.md#issues-feature-requests-and-contributing).
--- ---
@@ -9,7 +9,7 @@ Thanks for your interest in contributing to Tachiyomi!
Pull requests are welcome! Pull requests are welcome!
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware. If you're interested in taking on [an open issue](https://github.com/jobobby04/TachiyomiSY/issues), please comment on it so others are aware.
You do not need to ask for permission nor an assignment. You do not need to ask for permission nor an assignment.
## Prerequisites ## Prerequisites
@@ -24,34 +24,27 @@ Before you start, please note that the ability to use following technologies is
- [Android Studio](https://developer.android.com/studio) - [Android Studio](https://developer.android.com/studio)
- Emulator or phone with developer options enabled to test changes. - Emulator or phone with developer options enabled to test changes.
## Linting
Run the `detekt` gradle task. If the build fails, a report of issues can be found in `app/build/reports/detekt/`. The report is availble in several formats and details each issue that needs attention.
## Getting help ## Getting help
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing. - Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
# Translations # Translations
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details. Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
# Forks # Forks
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE). Forks are allowed so long as they abide by [the project's LICENSE](/LICENSE).
When creating a fork, remember to: When creating a fork, remember to:
- To avoid confusion with the main app: - To avoid confusion with the main app:
- Change the app name - Change the app name
- Change the app icon - Change the app icon
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt) - Change or disable the [app update checker](/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
- To avoid installation conflicts: - To avoid installation conflicts:
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) - Change the `applicationId` in [`build.gradle.kts`](/app/build.gradle.kts)
- To avoid having your data polluting the main app's analytics and crash report services:
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/standard/google-services.json) with your own
- If you want to use ACRA crash reporting, replace the `ACRA_URI` endpoint in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) with your own
### Supporting Cloud Sync - Google Drive Implementation ### Supporting Cloud Sync - Google Drive Implementation
+1 -1
View File
@@ -82,7 +82,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
<details><summary>Issues</summary> <details><summary>Issues</summary>
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/docs/faq/general), the [changelog](https://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).** 1. **Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).**
2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/1195734228319617024.svg)](https://discord.gg/mihon) 2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/1195734228319617024.svg)](https://discord.gg/mihon)
</details> </details>
+4 -5
View File
@@ -21,7 +21,7 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
// shortcutHelper.setFilePath("./shortcuts.xml") // shortcutHelper.setFilePath("./shortcuts.xml")
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android { android {
namespace = "eu.kanade.tachiyomi" namespace = "eu.kanade.tachiyomi"
@@ -38,7 +38,7 @@ android {
buildConfigField("boolean", "INCLUDE_UPDATER", "false") buildConfigField("boolean", "INCLUDE_UPDATER", "false")
ndk { ndk {
abiFilters += SUPPORTED_ABIS abiFilters += supportedAbis
} }
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -47,7 +47,7 @@ android {
abi { abi {
isEnable = true isEnable = true
reset() reset()
include(*SUPPORTED_ABIS.toTypedArray()) include(*supportedAbis.toTypedArray())
isUniversalApk = true isUniversalApk = true
} }
} }
@@ -162,7 +162,7 @@ dependencies {
implementation(compose.ui.tooling.preview) implementation(compose.ui.tooling.preview)
implementation(compose.ui.util) implementation(compose.ui.util)
implementation(compose.accompanist.systemuicontroller) implementation(compose.accompanist.systemuicontroller)
implementation(androidx.interpolator) implementation(androidx.interpolator)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)
@@ -243,7 +243,6 @@ dependencies {
implementation(libs.compose.webview) implementation(libs.compose.webview)
implementation(libs.compose.grid) implementation(libs.compose.grid)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)
@@ -3,4 +3,4 @@
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" /> <monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
</adaptive-icon> </adaptive-icon>
@@ -3,4 +3,4 @@
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" /> <monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
</adaptive-icon> </adaptive-icon>
@@ -24,6 +24,7 @@ import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.interactor.TrackChapter import eu.kanade.domain.track.interactor.TrackChapter
import mihon.data.repository.ExtensionRepoRepositoryImpl import mihon.data.repository.ExtensionRepoRepositoryImpl
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepo import mihon.domain.extensionrepo.interactor.GetExtensionRepo
@@ -152,6 +153,7 @@ class DomainModule : InjektModule {
addFactory { ShouldUpdateDbChapter() } addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) } addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
addFactory { GetAvailableScanlators(get()) } addFactory { GetAvailableScanlators(get()) }
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) } addFactory { GetHistory(get()) }
@@ -32,10 +32,11 @@ class GetEnabledSources(
) { a, b, c -> Triple(a, b, c) }, ) { a, b, c -> Triple(a, b, c) },
// SY <-- // SY <--
repository.getSources(), repository.getSources(),
) { pinnedSourceIds, ) {
(enabledLanguages, disabledSources, lastUsedSource), pinnedSourceIds,
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter), (enabledLanguages, disabledSources, lastUsedSource),
sources, (excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
sources,
-> ->
val sourcesAndCategories = sourcesInCategories.map { val sourcesAndCategories = sourcesInCategories.map {
@@ -13,7 +13,7 @@ class SyncPreferences(
fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "") fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "")
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L) fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
fun lastSyncEtag() = preferenceStore.getString("sync_etag", "") fun lastSyncEtag() = preferenceStore.getString("sync_etag", "")
fun syncInterval() = preferenceStore.getInt("sync_interval", 0) fun syncInterval() = preferenceStore.getInt("sync_interval", 0)
fun syncService() = preferenceStore.getInt("sync_service", 0) fun syncService() = preferenceStore.getInt("sync_service", 0)
@@ -18,12 +18,20 @@ class UiPreferences(
fun themeMode() = preferenceStore.getEnum( fun themeMode() = preferenceStore.getEnum(
"pref_theme_mode_key", "pref_theme_mode_key",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT }, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ThemeMode.SYSTEM
} else {
ThemeMode.LIGHT
},
) )
fun appTheme() = preferenceStore.getEnum( fun appTheme() = preferenceStore.getEnum(
"pref_app_theme", "pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT }, if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET
} else {
AppTheme.DEFAULT
},
) )
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
@@ -240,7 +240,7 @@ private fun DetailsHeader(
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName}) Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode}) Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
NSFW: ${extension.isNsfw} NSFW: ${extension.isNsfw}
""".trimIndent() """.trimIndent(),
) )
if (extension is Extension.Installed) { if (extension is Extension.Installed) {
@@ -250,8 +250,8 @@ private fun DetailsHeader(
Update available: ${extension.hasUpdate} Update available: ${extension.hasUpdate}
Obsolete: ${extension.isObsolete} Obsolete: ${extension.isObsolete}
Shared: ${extension.isShared} Shared: ${extension.isShared}
Repository: ${extension.repoUrl} Repository: ${extension.repoUrl}
""".trimIndent() """.trimIndent(),
) )
} }
} }
@@ -219,7 +219,9 @@ private fun ExtensionContent(
when (it) { when (it) {
is Extension.Available -> onInstallExtension(it) is Extension.Available -> onInstallExtension(it)
is Extension.Installed -> onOpenExtension(it) is Extension.Installed -> onOpenExtension(it)
is Extension.Untrusted -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
@@ -241,7 +243,9 @@ private fun ExtensionContent(
onOpenExtension(it) onOpenExtension(it)
} }
} }
is Extension.Untrusted -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
) )
@@ -35,7 +35,7 @@ import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.components.material.topSmallPaddingValues
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@@ -179,7 +179,7 @@ private fun SourcePinButton(
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else { } else {
MaterialTheme.colorScheme.onBackground.copy( MaterialTheme.colorScheme.onBackground.copy(
alpha = SecondaryItemAlpha, alpha = SECONDARY_ALPHA,
) )
} }
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
@@ -79,7 +79,7 @@ fun TabbedDialog(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageContent = { page -> content(page) } pageContent = { page -> content(page) },
) )
} }
} }
@@ -207,7 +207,6 @@ private fun ColumnScope.SortPage(
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty()) }.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
// SY <-- // SY <--
val trackerSortOption = if (trackers.isEmpty()) { val trackerSortOption = if (trackers.isEmpty()) {
emptyList() emptyList()
} else { } else {
@@ -62,7 +62,7 @@ private val ContinueReadingButtonIconSizeLarge = 20.dp
private val ContinueReadingButtonGridPadding = 6.dp private val ContinueReadingButtonGridPadding = 6.dp
private val ContinueReadingButtonListSpacing = 8.dp private val ContinueReadingButtonListSpacing = 8.dp
private const val GridSelectedCoverAlpha = 0.76f private const val GRID_SELECTED_COVER_ALPHA = 0.76f
/** /**
* Layout of grid list item with title overlaying the cover. * Layout of grid list item with title overlaying the cover.
@@ -90,7 +90,7 @@ fun MangaCompactGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@@ -197,7 +197,7 @@ fun MangaComfortableGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@@ -371,7 +371,7 @@ fun MangaListItem(
size = ContinueReadingButtonSizeSmall, size = ContinueReadingButtonSizeSmall,
iconSize = ContinueReadingButtonIconSizeSmall, iconSize = ContinueReadingButtonIconSizeSmall,
onClick = onClickContinueReading, onClick = onClickContinueReading,
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing) modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
) )
} }
} }
@@ -392,7 +392,7 @@ private fun ContinueReadingButton(
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f), containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer), contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
), ),
modifier = Modifier.size(size) modifier = Modifier.size(size),
) { ) {
Icon( Icon(
imageVector = Icons.Filled.PlayArrow, imageVector = Icons.Filled.PlayArrow,
@@ -77,6 +77,7 @@ import eu.kanade.tachiyomi.source.online.english.Pururin
import eu.kanade.tachiyomi.source.online.english.Tsumino import eu.kanade.tachiyomi.source.online.english.Tsumino
import eu.kanade.tachiyomi.ui.manga.ChapterList import eu.kanade.tachiyomi.ui.manga.ChapterList
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import eu.kanade.tachiyomi.ui.manga.MergedMangaData
import eu.kanade.tachiyomi.ui.manga.PagePreviewState import eu.kanade.tachiyomi.ui.manga.PagePreviewState
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil import exh.metadata.MetadataUtil
@@ -578,6 +579,7 @@ private fun MangaScreenSmallImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
mergedData = state.mergedData,
chapters = listItem, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected }, isAnyChapterSelected = chapters.fastAny { it.selected },
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
@@ -880,6 +882,7 @@ fun MangaScreenLargeImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
mergedData = state.mergedData,
chapters = listItem, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected }, isAnyChapterSelected = chapters.fastAny { it.selected },
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
@@ -944,6 +947,7 @@ private fun SharedMangaBottomActionMenu(
private fun LazyListScope.sharedChapterItems( private fun LazyListScope.sharedChapterItems(
manga: Manga, manga: Manga,
mergedData: MergedMangaData?,
chapters: List<ChapterList>, chapters: List<ChapterList>,
isAnyChapterSelected: Boolean, isAnyChapterSelected: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
@@ -995,7 +999,9 @@ private fun LazyListScope.sharedChapterItems(
// SY <-- // SY <--
}, },
readProgress = item.chapter.lastPageRead readProgress = item.chapter.lastPageRead
.takeIf { /* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L } .takeIf {
/* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L
}
?.let { ?.let {
stringResource( stringResource(
MR.strings.chapter_progress, MR.strings.chapter_progress,
@@ -1011,7 +1017,8 @@ private fun LazyListScope.sharedChapterItems(
read = item.chapter.read, read = item.chapter.read,
bookmark = item.chapter.bookmark, bookmark = item.chapter.bookmark,
selected = item.selected, selected = item.selected,
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(), downloadIndicatorEnabled =
!isAnyChapterSelected && !(mergedData?.manga?.get(item.chapter.mangaId) ?: manga).isLocal(),
downloadStateProvider = { item.downloadState }, downloadStateProvider = { item.downloadState },
downloadProgressProvider = { item.downloadProgress }, downloadProgressProvider = { item.downloadProgress },
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
@@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@@ -60,6 +60,6 @@ private fun MissingChaptersWarning(count: Int) {
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha), color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
) )
} }
@@ -40,8 +40,8 @@ import eu.kanade.tachiyomi.data.download.model.Download
import me.saket.swipe.SwipeableActionsBox import me.saket.swipe.SwipeableActionsBox
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@@ -135,7 +135,7 @@ fun MangaChapterListItem(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height }, onTextLayout = { textHeight = it.size.height },
color = LocalContentColor.current.copy(alpha = if (read) ReadItemAlpha else 1f), color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
) )
} }
@@ -143,7 +143,7 @@ fun MangaChapterListItem(
val subtitleStyle = MaterialTheme.typography.bodySmall val subtitleStyle = MaterialTheme.typography.bodySmall
.merge( .merge(
color = LocalContentColor.current color = LocalContentColor.current
.copy(alpha = if (read) ReadItemAlpha else SecondaryItemAlpha) .copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
) )
ProvideTextStyle(value = subtitleStyle) { ProvideTextStyle(value = subtitleStyle) {
if (date != null) { if (date != null) {
@@ -152,14 +152,19 @@ fun MangaChapterListItem(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
if (readProgress != null || scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() if (readProgress != null ||
scanlator != null/* SY --> */ ||
sourceName != null/* SY <-- */
) {
DotSeparatorText()
}
} }
if (readProgress != null) { if (readProgress != null) {
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
) )
if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
} }
@@ -82,6 +82,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.TextButton import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
@@ -181,7 +182,7 @@ fun MangaActionRow(
// SY <-- // SY <--
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA)
// TODO: show something better when using custom interval // TODO: show something better when using custom interval
val nextUpdateDays = remember(nextUpdate) { val nextUpdateDays = remember(nextUpdate) {
@@ -44,7 +44,7 @@ import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
private fun PagePreviewLoading( private fun PagePreviewLoading(
setMaxWidth: (Dp) -> Unit setMaxWidth: (Dp) -> Unit,
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
Box( Box(
@@ -63,7 +63,7 @@ private fun PagePreviewLoading(
@Composable @Composable
private fun PagePreviewRow( private fun PagePreviewRow(
onOpenPage: (Int) -> Unit, onOpenPage: (Int) -> Unit,
items: ImmutableList<PagePreview> items: ImmutableList<PagePreview>,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -88,7 +88,7 @@ private fun PagePreviewMore(
) { ) {
Box( Box(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center,
) { ) {
TextButton(onClick = onMorePreviewsClicked) { TextButton(onClick = onMorePreviewsClicked) {
Text(stringResource(SYMR.strings.more_previews)) Text(stringResource(SYMR.strings.more_previews))
@@ -116,7 +116,7 @@ fun PagePreviews(
pagePreviewState.pagePreviews.take(rowCount * itemPerRowCount).chunked(itemPerRowCount).forEach { pagePreviewState.pagePreviews.take(rowCount * itemPerRowCount).chunked(itemPerRowCount).forEach {
PagePreviewRow( PagePreviewRow(
onOpenPage = onOpenPage, onOpenPage = onOpenPage,
items = remember(it) { it.toImmutableList() } items = remember(it) { it.toImmutableList() },
) )
} }
@@ -153,7 +153,7 @@ fun LazyListScope.PagePreviewItems(
) { ) {
PagePreviewRow( PagePreviewRow(
onOpenPage = onOpenPage, onOpenPage = onOpenPage,
items = remember(it) { it.toImmutableList() } items = remember(it) { it.toImmutableList() },
) )
} }
item( item(
@@ -75,7 +75,7 @@ private fun NewUpdateScreenPreview() {
changelogInfo = """ changelogInfo = """
## Yay ## Yay
Foobar Foobar
### More info ### More info
- Hello - Hello
- World - World
@@ -3,7 +3,6 @@ package eu.kanade.presentation.more.settings.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Build
import android.provider.Settings import android.provider.Settings
import android.webkit.WebStorage import android.webkit.WebStorage
import android.webkit.WebView import android.webkit.WebView
@@ -376,7 +375,7 @@ object SettingsAdvancedScreen : SearchableSettings {
chooseColorProfile.launch(arrayOf("*/*")) chooseColorProfile.launch(arrayOf("*/*"))
}, },
), ),
) ),
) )
} }
@@ -180,11 +180,15 @@ object SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = previewsRowCount, value = previewsRowCount,
title = stringResource(SYMR.strings.pref_previews_row_count), title = stringResource(SYMR.strings.pref_previews_row_count),
subtitle = if (previewsRowCount > 0) pluralStringResource( subtitle = if (previewsRowCount > 0) {
SYMR.plurals.row_count, pluralStringResource(
previewsRowCount, SYMR.plurals.row_count,
previewsRowCount, previewsRowCount,
) else stringResource(MR.strings.disabled), previewsRowCount,
)
} else {
stringResource(MR.strings.disabled)
},
min = 0, min = 0,
max = 10, max = 10,
onValueChanged = { onValueChanged = {
@@ -94,7 +94,7 @@ object SettingsBrowseScreen : SearchableSettings {
pref = uiPreferences.feedTabInFront(), pref = uiPreferences.feedTabInFront(),
title = stringResource(SYMR.strings.pref_feed_position), title = stringResource(SYMR.strings.pref_feed_position),
subtitle = stringResource(SYMR.strings.pref_feed_position_summery), subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
enabled = hideFeedTab.not() enabled = hideFeedTab.not(),
), ),
), ),
), ),
@@ -394,11 +394,23 @@ object SettingsDataScreen : SearchableSettings {
syncServiceType: SyncManager.SyncService, syncServiceType: SyncManager.SyncService,
syncPreferences: SyncPreferences, syncPreferences: SyncPreferences,
): List<Preference> { ): List<Preference> {
return when (syncServiceType) { val navigator = LocalNavigator.currentOrThrow
val preferences = when (syncServiceType) {
SyncManager.SyncService.NONE -> emptyList() SyncManager.SyncService.NONE -> emptyList()
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences) SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences() SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
} }
return if (syncServiceType != SyncManager.SyncService.NONE) {
preferences + Preference.PreferenceItem.TextPreference(
title = stringResource(SYMR.strings.pref_choose_what_to_sync),
onClick = {
navigator.push(SyncSettingsSelector())
},
)
} else {
preferences
}
} }
@Composable @Composable
@@ -515,7 +527,7 @@ object SettingsDataScreen : SearchableSettings {
@Composable @Composable
private fun getSyncNowPref(): Preference.PreferenceGroup { private fun getSyncNowPref(): Preference.PreferenceGroup {
val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(SYMR.strings.pref_sync_now_group_title), title = stringResource(SYMR.strings.pref_sync_now_group_title),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
@@ -524,7 +536,11 @@ object SettingsDataScreen : SearchableSettings {
title = stringResource(SYMR.strings.pref_sync_now), title = stringResource(SYMR.strings.pref_sync_now),
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle), subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
onClick = { onClick = {
navigator.push(SyncSettingsSelector()) if (!SyncDataJob.isRunning(context)) {
SyncDataJob.startNow(context)
} else {
context.toast(SYMR.strings.sync_in_progress)
}
}, },
), ),
), ),
@@ -120,6 +120,7 @@ object SettingsDownloadScreen : SearchableSettings {
allCategories: List<Category>, allCategories: List<Category>,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters() val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories() val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude() val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
@@ -152,6 +153,11 @@ object SettingsDownloadScreen : SearchableSettings {
pref = downloadNewChaptersPref, pref = downloadNewChaptersPref,
title = stringResource(MR.strings.pref_download_new), title = stringResource(MR.strings.pref_download_new),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = downloadNewUnreadChaptersOnlyPref,
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
enabled = downloadNewChapters,
),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.categories), title = stringResource(MR.strings.categories),
subtitle = getCategoriesLabel( subtitle = getCategoriesLabel(
@@ -37,7 +37,7 @@ class OpenSourceLicensesScreen : Screen() {
name = it.name, name = it.name,
website = it.website, website = it.website,
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(), license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
) ),
) )
}, },
) )
@@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
@@ -32,7 +33,7 @@ class ExtensionReposScreen(
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
LaunchedEffect(url) { LaunchedEffect(url) {
url?.let { screenModel.createRepo(it) } url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
} }
if (state is RepoScreenState.Loading) { if (state is RepoScreenState.Loading) {
@@ -67,7 +68,6 @@ class ExtensionReposScreen(
repo = dialog.repo, repo = dialog.repo,
) )
} }
is RepoDialog.Conflict -> { is RepoDialog.Conflict -> {
ExtensionRepoConflictDialog( ExtensionRepoConflictDialog(
onDismissRequest = screenModel::dismissDialog, onDismissRequest = screenModel::dismissDialog,
@@ -76,6 +76,13 @@ class ExtensionReposScreen(
newRepo = dialog.newRepo, newRepo = dialog.newRepo,
) )
} }
is RepoDialog.Confirm -> {
ExtensionRepoConfirmDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(dialog.url) },
repo = dialog.url,
)
}
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -125,6 +125,7 @@ sealed class RepoDialog {
data object Create : RepoDialog() data object Create : RepoDialog()
data class Delete(val repo: String) : RepoDialog() data class Delete(val repo: String) : RepoDialog()
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog() data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
data class Confirm(val url: String) : RepoDialog()
} }
sealed class RepoScreenState { sealed class RepoScreenState {
@@ -152,3 +152,35 @@ fun ExtensionRepoConflictDialog(
}, },
) )
} }
@Composable
fun ExtensionRepoConfirmDialog(
onDismissRequest: () -> Unit,
onCreate: () -> Unit,
repo: String,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(MR.strings.action_add_repo))
},
text = {
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
},
confirmButton = {
TextButton(
onClick = {
onCreate()
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_add))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
)
}
@@ -26,7 +26,6 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.LazyColumnWithAction import tachiyomi.presentation.core.components.LazyColumnWithAction
import tachiyomi.presentation.core.components.SectionCard import tachiyomi.presentation.core.components.SectionCard
@@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
@@ -16,7 +15,6 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.data.backup.create.BackupOptions import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.sync.SyncDataJob import eu.kanade.tachiyomi.data.sync.SyncDataJob
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -32,7 +30,6 @@ import uy.kohesive.injekt.api.get
class SyncSettingsSelector : Screen() { class SyncSettingsSelector : Screen() {
@Composable @Composable
override fun Content() { override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val model = rememberScreenModel { SyncSettingsSelectorModel() } val model = rememberScreenModel { SyncSettingsSelectorModel() }
val state by model.state.collectAsState() val state by model.state.collectAsState()
@@ -48,15 +45,10 @@ class SyncSettingsSelector : Screen() {
) { contentPadding -> ) { contentPadding ->
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(SYMR.strings.label_sync), actionLabel = stringResource(MR.strings.action_save),
actionEnabled = state.options.canCreate(), actionEnabled = true,
onClickAction = { onClickAction = {
if (!SyncDataJob.isRunning(context)) { navigator.pop()
model.syncNow(context)
navigator.pop()
} else {
context.toast(SYMR.strings.sync_in_progress)
}
}, },
) { ) {
item { item {
@@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource
class BackupSchemaScreen : Screen() { class BackupSchemaScreen : Screen() {
companion object { companion object {
const val title = "Backup file schema" const val TITLE = "Backup file schema"
} }
@Composable @Composable
@@ -41,7 +41,7 @@ class BackupSchemaScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@@ -50,7 +50,7 @@ class BackupSchemaScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, schema) context.copyToClipboard(TITLE, schema)
}, },
), ),
), ),
@@ -31,11 +31,11 @@ class DebugInfoScreen : Screen() {
itemsProvider = { itemsProvider = {
listOf( listOf(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = WorkerInfoScreen.title, title = WorkerInfoScreen.TITLE,
onClick = { navigator.push(WorkerInfoScreen()) }, onClick = { navigator.push(WorkerInfoScreen()) },
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = BackupSchemaScreen.title, title = BackupSchemaScreen.TITLE,
onClick = { navigator.push(BackupSchemaScreen()) }, onClick = { navigator.push(BackupSchemaScreen()) },
), ),
getAppInfoGroup(), getAppInfoGroup(),
@@ -49,7 +49,7 @@ import java.time.ZoneId
class WorkerInfoScreen : Screen() { class WorkerInfoScreen : Screen() {
companion object { companion object {
const val title = "Worker info" const val TITLE = "Worker info"
} }
@Composable @Composable
@@ -65,7 +65,7 @@ class WorkerInfoScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@@ -74,7 +74,7 @@ class WorkerInfoScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, enqueued + finished + running) context.copyToClipboard(TITLE, enqueued + finished + running)
}, },
), ),
), ),
@@ -159,7 +159,7 @@ class WorkerInfoScreen : Screen() {
Injekt.get<UiPreferences>().dateFormat().get(), Injekt.get<UiPreferences>().dateFormat().get(),
), ),
) )
appendLine("Next scheduled run: $timestamp",) appendLine("Next scheduled run: $timestamp")
appendLine("Attempt #${workInfo.runAttemptCount + 1}") appendLine("Attempt #${workInfo.runAttemptCount + 1}")
} }
appendLine() appendLine()
@@ -34,7 +34,9 @@ import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart import tachiyomi.presentation.core.util.isScrolledToStart
private enum class State { private enum class State {
CHECKED, INVERSED, UNCHECKED CHECKED,
INVERSED,
UNCHECKED,
} }
@Composable @Composable
@@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
@@ -73,7 +73,7 @@ private fun RowScope.BaseStatsItem(
style = subtitleStyle style = subtitleStyle
.copy( .copy(
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
.copy(alpha = SecondaryItemAlpha), .copy(alpha = SECONDARY_ALPHA),
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
@@ -226,7 +226,7 @@ private fun ChapterText(
Text( Text(
text = buildAnnotatedString { text = buildAnnotatedString {
if (downloaded) { if (downloaded) {
appendInlineContent(DownloadedIconContentId) appendInlineContent(DOWNLOADED_ICON_ID)
append(' ') append(' ')
} }
append(name) append(name)
@@ -236,7 +236,7 @@ private fun ChapterText(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
inlineContent = persistentMapOf( inlineContent = persistentMapOf(
DownloadedIconContentId to InlineTextContent( DOWNLOADED_ICON_ID to InlineTextContent(
Placeholder( Placeholder(
width = 22.sp, width = 22.sp,
height = 22.sp, height = 22.sp,
@@ -273,7 +273,7 @@ private val CardColor: CardColors
) )
private val VerticalSpacerSize = 24.dp private val VerticalSpacerSize = 24.dp
private const val DownloadedIconContentId = "downloaded" private const val DOWNLOADED_ICON_ID = "downloaded"
private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy( private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy(
id = 0L, id = 0L,
@@ -63,7 +63,7 @@ fun ExhUtils(
modifier modifier
.fillMaxWidth() .fillMaxWidth()
.background(backgroundColor), .background(backgroundColor),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
AnimatedVisibility(visible = isVisible) { AnimatedVisibility(visible = isVisible) {
Column { Column {
@@ -84,7 +84,7 @@ fun ExhUtils(
) { ) {
Column( Column(
Modifier.weight(3f), Modifier.weight(3f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text( Text(
text = stringResource(SYMR.strings.eh_autoscroll), text = stringResource(SYMR.strings.eh_autoscroll),
@@ -93,17 +93,17 @@ fun ExhUtils(
fontFamily = FontFamily.SansSerif, fontFamily = FontFamily.SansSerif,
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
modifier = Modifier.fillMaxWidth(0.75f), modifier = Modifier.fillMaxWidth(0.75f),
textAlign = TextAlign.Center textAlign = TextAlign.Center,
) )
} }
Column( Column(
Modifier.weight(1f), Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Switch( Switch(
checked = isAutoScroll, checked = isAutoScroll,
onCheckedChange = null, onCheckedChange = null,
enabled = isAutoScrollEnabled enabled = isAutoScrollEnabled,
) )
} }
} }
@@ -114,7 +114,7 @@ fun ExhUtils(
) { ) {
Column( Column(
Modifier.weight(3f), Modifier.weight(3f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
var autoScrollFrequencyState by remember { var autoScrollFrequencyState by remember {
mutableStateOf(autoScrollFrequency) mutableStateOf(autoScrollFrequency)
@@ -38,7 +38,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -58,8 +57,6 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
private const val UnsetStatusTextAlpha = 0.5F
@Composable @Composable
fun TrackInfoDialogHome( fun TrackInfoDialogHome(
trackItems: List<TrackItem>, trackItems: List<TrackItem>,
@@ -211,10 +208,9 @@ private fun TrackInfoItem(
if (onScoreClick != null) { if (onScoreClick != null) {
VerticalDivider() VerticalDivider()
TrackDetailsItem( TrackDetailsItem(
modifier = Modifier modifier = Modifier.weight(1f),
.weight(1f) text = score,
.alpha(if (score == null) UnsetStatusTextAlpha else 1f), placeholder = stringResource(MR.strings.score),
text = score ?: stringResource(MR.strings.score),
onClick = onScoreClick, onClick = onScoreClick,
) )
} }
@@ -243,6 +239,8 @@ private fun TrackInfoItem(
} }
} }
private const val UNSET_TEXT_ALPHA = 0.5F
@Composable @Composable
private fun TrackDetailsItem( private fun TrackDetailsItem(
text: String?, text: String?,
@@ -263,7 +261,7 @@ private fun TrackDetailsItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UnsetStatusTextAlpha else 1f), color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UNSET_TEXT_ALPHA else 1f),
) )
} }
} }
@@ -224,6 +224,7 @@ private fun SearchResultItem(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val clipboardManager: ClipboardManager = LocalClipboardManager.current val clipboardManager: ClipboardManager = LocalClipboardManager.current
val focusManager = LocalFocusManager.current
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
val description = trackSearch.summary.trim() val description = trackSearch.summary.trim()
@@ -243,7 +244,10 @@ private fun SearchResultItem(
) )
.combinedClickable( .combinedClickable(
onLongClick = { dropDownMenuExpanded = true }, onLongClick = { dropDownMenuExpanded = true },
onClick = onClick, onClick = {
focusManager.clearFocus()
onClick()
},
) )
.padding(12.dp), .padding(12.dp),
) { ) {
@@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ListGroupHeader import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@@ -107,8 +107,10 @@ internal fun LazyListScope.updatesUiItems(
readProgress = updatesItem.update.lastPageRead readProgress = updatesItem.update.lastPageRead
.takeIf { .takeIf {
/* SY --> */( /* SY --> */(
!updatesItem.update.read || (preserveReadingPosition && updatesItem.isEhBasedUpdate()) !updatesItem.update.read ||
)/* SY <-- */ && it > 0L (preserveReadingPosition && updatesItem.isEhBasedUpdate())
)/* SY <-- */ &&
it > 0L
} }
?.let { ?.let {
stringResource( stringResource(
@@ -152,7 +154,7 @@ private fun UpdatesUiItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val textAlpha = if (update.read) ReadItemAlpha else 1f val textAlpha = if (update.read) DISABLED_ALPHA else 1f
Row( Row(
modifier = modifier modifier = modifier
@@ -226,7 +228,7 @@ private fun UpdatesUiItem(
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }
@@ -1,6 +1,5 @@
package eu.kanade.presentation.util package eu.kanade.presentation.util
import android.annotation.SuppressLint
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
@@ -28,7 +27,6 @@ import soup.compose.material.motion.animation.rememberSlideDistance
/** /**
* For invoking back press to the parent activity * For invoking back press to the parent activity
*/ */
@SuppressLint("ComposeCompositionLocalUsage")
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
interface Tab : cafe.adriel.voyager.navigator.tab.Tab { interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
+3 -6
View File
@@ -174,8 +174,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val syncPreferences: SyncPreferences = Injekt.get() val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions() val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
) {
SyncDataJob.startNow(this@App) SyncDataJob.startNow(this@App)
} }
@@ -199,7 +198,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
) )
} }
@Suppress("MagicNumber")
override fun newImageLoader(context: Context): ImageLoader { override fun newImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(this).apply { return ImageLoader.Builder(this).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client } val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
@@ -239,8 +237,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val syncPreferences: SyncPreferences = Injekt.get() val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions() val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppResume if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppResume) {
) {
SyncDataJob.startNow(this@App) SyncDataJob.startNow(this@App)
} }
} }
@@ -339,7 +336,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
""" """
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}) App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
Preview build: $syDebugVersion Preview build: $syDebugVersion
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
Android build ID: ${Build.DISPLAY} Android build ID: ${Build.DISPLAY}
Device brand: ${Build.BRAND} Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER} Device manufacturer: ${Build.MANUFACTURER}
@@ -57,7 +57,7 @@ class BackupCreator(
// SY --> // SY -->
private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(), private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(),
private val getMergedManga: GetMergedManga = Injekt.get(), private val getMergedManga: GetMergedManga = Injekt.get(),
private val handler: DatabaseHandler = Injekt.get() private val handler: DatabaseHandler = Injekt.get(),
// SY <-- // SY <--
) { ) {
@@ -92,7 +92,7 @@ class BackupCreator(
} else { } else {
emptyList() emptyList()
} + getMergedManga.await(), // SY <-- } + getMergedManga.await(), // SY <--
options options,
) )
val backup = Backup( val backup = Backup(
backupManga = backupManga, backupManga = backupManga,
@@ -39,7 +39,8 @@ data class BackupOptions(
// SY <-- // SY <--
) )
fun canCreate() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings || savedSearches fun canCreate() =
libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings || savedSearches
companion object { companion object {
val libraryOptions = persistentListOf( val libraryOptions = persistentListOf(
@@ -7,7 +7,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SavedSearchBackupCreator( class SavedSearchBackupCreator(
private val handler: DatabaseHandler = Injekt.get() private val handler: DatabaseHandler = Injekt.get(),
) { ) {
suspend operator fun invoke(): List<BackupSavedSearch> { suspend operator fun invoke(): List<BackupSavedSearch> {
@@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Suppress("MagicNumber")
@Serializable @Serializable
data class Backup( data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>, @ProtoNumber(1) val backupManga: List<BackupManga>,
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(), @ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
@ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(), // @ProtoNumber(100) var backupBrokenSources, legacy source model with non-compliant proto number,
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(), @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
@ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(), @ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(),
@ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(), @ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(),
@@ -4,7 +4,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@Suppress("MagicNumber")
@Serializable @Serializable
data class BackupChapter( data class BackupChapter(
// in 1.x some of these values have different names // in 1.x some of these values have different names
@@ -4,7 +4,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.model.ExtensionRepo
@Suppress("MagicNumber")
@Serializable @Serializable
class BackupExtensionRepos( class BackupExtensionRepos(
@ProtoNumber(1) var baseUrl: String, @ProtoNumber(1) var baseUrl: String,
@@ -18,15 +18,3 @@ data class BackupHistory(
) )
} }
} }
@Deprecated("Replaced with BackupHistory. This is retained for legacy reasons.")
@Serializable
data class BrokenBackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long,
@ProtoNumber(2) var readDuration: Long = 0,
) {
fun toBackupHistory(): BackupHistory {
return BackupHistory(url, lastRead, readDuration)
}
}
@@ -3,13 +3,9 @@ package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@Suppress( @Suppress("DEPRECATION")
"DEPRECATION",
"MagicNumber",
)
@Serializable @Serializable
data class BackupManga( data class BackupManga(
// in 1.x some of these values have different names // in 1.x some of these values have different names
@@ -36,7 +32,7 @@ data class BackupManga(
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x // Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
@ProtoNumber(100) var favorite: Boolean = true, @ProtoNumber(100) var favorite: Boolean = true,
@ProtoNumber(101) var chapterFlags: Int = 0, @ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(), // @ProtoNumber(102) var brokenHistory, legacy history model with non-compliant proto number
@ProtoNumber(103) var viewer_flags: Int? = null, @ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(), @ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
@@ -36,7 +36,8 @@ data class BackupMergedMangaReference(
} }
val backupMergedMangaReferenceMapper = val backupMergedMangaReferenceMapper =
{ _: Long, {
_: Long,
isInfoManga: Boolean, isInfoManga: Boolean,
getChapterUpdates: Boolean, getChapterUpdates: Boolean,
chapterSortMode: Long, chapterSortMode: Long,
@@ -8,11 +8,3 @@ data class BackupSource(
@ProtoNumber(1) var name: String = "", @ProtoNumber(1) var name: String = "",
@ProtoNumber(2) var sourceId: Long, @ProtoNumber(2) var sourceId: Long,
) )
@Serializable
data class BrokenBackupSource(
@ProtoNumber(0) var name: String = "",
@ProtoNumber(1) var sourceId: Long,
) {
fun toBackupSource() = BackupSource(name, sourceId)
}
@@ -53,7 +53,20 @@ data class BackupTracking(
} }
val backupTrackMapper = { val backupTrackMapper = {
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long -> _: Long,
_: Long,
syncId: Long,
mediaId: Long,
libraryId: Long?,
title: String,
lastChapterRead: Double,
totalChapters: Long,
status: Long,
score: Double,
remoteUrl: String,
startDate: Long,
finishDate: Long,
->
BackupTracking( BackupTracking(
syncId = syncId.toInt(), syncId = syncId.toInt(),
mediaId = mediaId, mediaId = mediaId,
@@ -74,7 +74,7 @@ class BackupRestorer(
val backup = BackupDecoder(context).decode(uri) val backup = BackupDecoder(context).decode(uri)
// Store source mapping for error messages // Store source mapping for error messages
val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() } val backupMaps = backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
if (options.libraryEntries) { if (options.libraryEntries) {
@@ -200,7 +200,7 @@ class BackupRestorer(
} }
private fun CoroutineScope.restoreExtensionRepos( private fun CoroutineScope.restoreExtensionRepos(
backupExtensionRepo: List<BackupExtensionRepos> backupExtensionRepo: List<BackupExtensionRepos>,
) = launch { ) = launch {
backupExtensionRepo backupExtensionRepo
.forEach { .forEach {
@@ -27,7 +27,13 @@ data class RestoreOptions(
// SY <-- // SY <--
) )
fun canRestore() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings /* SY --> */ || savedSearches /* SY <-- */ fun canRestore() =
libraryEntries ||
categories ||
appSettings ||
extensionRepoSettings ||
sourceSettings /* SY --> */ ||
savedSearches /* SY <-- */
companion object { companion object {
val options = persistentListOf( val options = persistentListOf(
@@ -72,7 +78,7 @@ data class RestoreOptions(
extensionRepoSettings = array[3], extensionRepoSettings = array[3],
sourceSettings = array[4], sourceSettings = array[4],
// SY --> // SY -->
savedSearches = array[5] savedSearches = array[5],
// SY <-- // SY <--
) )
} }
@@ -8,7 +8,7 @@ import uy.kohesive.injekt.api.get
class ExtensionRepoRestorer( class ExtensionRepoRestorer(
private val handler: DatabaseHandler = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val getExtensionRepos: GetExtensionRepo = Injekt.get() private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
@@ -32,7 +32,7 @@ class ExtensionRepoRestorer(
backupRepo.name, backupRepo.name,
backupRepo.shortName, backupRepo.shortName,
backupRepo.website, backupRepo.website,
backupRepo.signingKeyFingerprint backupRepo.signingKeyFingerprint,
) )
} }
} }
@@ -89,7 +89,7 @@ class MangaRestorer(
chapters = backupManga.chapters, chapters = backupManga.chapters,
categories = backupManga.categories, categories = backupManga.categories,
backupCategories = backupCategories, backupCategories = backupCategories,
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() }, history = backupManga.history,
tracks = backupManga.tracking, tracks = backupManga.tracking,
excludedScanlators = backupManga.excludedScanlators, excludedScanlators = backupManga.excludedScanlators,
// SY --> // SY -->
@@ -36,8 +36,8 @@ class ChapterCache(
private val context: Context, private val context: Context,
private val json: Json, private val json: Json,
// SY --> // SY -->
readerPreferences: ReaderPreferences readerPreferences: ReaderPreferences,
//S Y <-- // SY <--
) { ) {
// --> EH // --> EH
@@ -45,7 +45,6 @@ import java.io.IOException
* Available request parameter: * Available request parameter:
* - [USE_CUSTOM_COVER_KEY]: Use custom cover if set by user, default is true * - [USE_CUSTOM_COVER_KEY]: Use custom cover if set by user, default is true
*/ */
@Suppress("LongParameterList")
class MangaCoverFetcher( class MangaCoverFetcher(
private val url: String?, private val url: String?,
private val isLibraryManga: Boolean, private val isLibraryManga: Boolean,
@@ -86,7 +85,7 @@ class MangaCoverFetcher(
source = ImageSource( source = ImageSource(
file = file.toOkioPath(), file = file.toOkioPath(),
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey diskCacheKey = diskCacheKey,
), ),
mimeType = "image/*", mimeType = "image/*",
dataSource = DataSource.DISK, dataSource = DataSource.DISK,
@@ -58,7 +58,7 @@ class PagePreviewFetcher(
source = ImageSource( source = ImageSource(
file = file.toOkioPath(), file = file.toOkioPath(),
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey diskCacheKey = diskCacheKey,
), ),
mimeType = "image/*", mimeType = "image/*",
dataSource = DataSource.DISK, dataSource = DataSource.DISK,
@@ -230,7 +230,7 @@ class PagePreviewFetcher(
file = data, file = data,
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey, diskCacheKey = diskCacheKey,
closeable = this closeable = this,
) )
} }
@@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
@@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
class ChapterImpl : Chapter { class ChapterImpl : Chapter {
@@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import java.io.Serializable import java.io.Serializable
@@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
class TrackImpl : Track { class TrackImpl : Track {
@@ -83,6 +83,11 @@ internal class DownloadNotifier(private val context: Context) {
context.stringResource(MR.strings.action_pause), context.stringResource(MR.strings.action_pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context), NotificationReceiver.pauseDownloadsPendingBroadcast(context),
) )
addAction(
R.drawable.ic_book_24dp,
context.stringResource(MR.strings.action_show_manga),
NotificationReceiver.openEntryPendingActivity(context, download.manga.id),
)
} }
val downloadingProgressText = context.stringResource( val downloadingProgressText = context.stringResource(
@@ -160,9 +165,10 @@ internal class DownloadNotifier(private val context: Context) {
* *
* @param reason the text to show. * @param reason the text to show.
* @param timeout duration after which to automatically dismiss the notification. * @param timeout duration after which to automatically dismiss the notification.
* @param mangaId the id of the entry being warned about
* Only works on Android 8+. * Only works on Android 8+.
*/ */
fun onWarning(reason: String, timeout: Long? = null, contentIntent: PendingIntent? = null) { fun onWarning(reason: String, timeout: Long? = null, contentIntent: PendingIntent? = null, mangaId: Long? = null) {
with(errorNotificationBuilder) { with(errorNotificationBuilder) {
setContentTitle(context.stringResource(MR.strings.download_notifier_downloader_title)) setContentTitle(context.stringResource(MR.strings.download_notifier_downloader_title))
setStyle(NotificationCompat.BigTextStyle().bigText(reason)) setStyle(NotificationCompat.BigTextStyle().bigText(reason))
@@ -170,6 +176,13 @@ internal class DownloadNotifier(private val context: Context) {
setAutoCancel(true) setAutoCancel(true)
clearActions() clearActions()
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
if (mangaId != null) {
addAction(
R.drawable.ic_book_24dp,
context.stringResource(MR.strings.action_show_manga),
NotificationReceiver.openEntryPendingActivity(context, mangaId),
)
}
setProgress(0, 0, false) setProgress(0, 0, false)
timeout?.let { setTimeoutAfter(it) } timeout?.let { setTimeoutAfter(it) }
contentIntent?.let { setContentIntent(it) } contentIntent?.let { setContentIntent(it) }
@@ -187,8 +200,9 @@ internal class DownloadNotifier(private val context: Context) {
* *
* @param error string containing error information. * @param error string containing error information.
* @param chapter string containing chapter title. * @param chapter string containing chapter title.
* @param mangaId the id of the entry that the error occurred on
*/ */
fun onError(error: String? = null, chapter: String? = null, mangaTitle: String? = null) { fun onError(error: String? = null, chapter: String? = null, mangaTitle: String? = null, mangaId: Long? = null) {
// Create notification // Create notification
with(errorNotificationBuilder) { with(errorNotificationBuilder) {
setContentTitle( setContentTitle(
@@ -198,6 +212,13 @@ internal class DownloadNotifier(private val context: Context) {
setSmallIcon(R.drawable.ic_warning_white_24dp) setSmallIcon(R.drawable.ic_warning_white_24dp)
clearActions() clearActions()
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
if (mangaId != null) {
addAction(
R.drawable.ic_book_24dp,
context.stringResource(MR.strings.action_show_manga),
NotificationReceiver.openEntryPendingActivity(context, mangaId),
)
}
setProgress(0, 0, false) setProgress(0, 0, false)
show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR) show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
@@ -121,7 +121,8 @@ class DownloadProvider(
getValidChapterDirNames(chp.name, chp.scanlator).any { dir -> getValidChapterDirNames(chp.name, chp.scanlator).any { dir ->
mangaDir.findFile(dir) != null mangaDir.findFile(dir) != null
} }
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true } == null ||
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
} }
} }
// SY <-- // SY <--
@@ -190,7 +190,7 @@ class Downloader(
fun clearQueue() { fun clearQueue() {
cancelDownloaderJob() cancelDownloaderJob()
_clearQueue() internalClearQueue()
notifier.dismissProgress() notifier.dismissProgress()
} }
@@ -204,9 +204,12 @@ class Downloader(
val activeDownloadsFlow = queueState.transformLatest { queue -> val activeDownloadsFlow = queueState.transformLatest { queue ->
while (true) { while (true) {
val activeDownloads = queue.asSequence() val activeDownloads = queue.asSequence()
.filter { it.status.value <= Download.State.DOWNLOADING.value } // Ignore completed downloads, leave them in the queue // Ignore completed downloads, leave them in the queue
.filter { it.status.value <= Download.State.DOWNLOADING.value }
.groupBy { it.source } .groupBy { it.source }
.toList().take(5) // Concurrently download from 5 different sources .toList()
// Concurrently download from 5 different sources
.take(5)
.map { (_, downloads) -> downloads.first() } .map { (_, downloads) -> downloads.first() }
emit(activeDownloads) emit(activeDownloads)
@@ -333,6 +336,7 @@ class Downloader(
context.stringResource(MR.strings.download_insufficient_space), context.stringResource(MR.strings.download_insufficient_space),
download.chapter.name, download.chapter.name,
download.manga.title, download.manga.title,
download.manga.id,
) )
return return
} }
@@ -422,7 +426,7 @@ class Downloader(
// If the page list threw, it will resume here // If the page list threw, it will resume here
logcat(LogPriority.ERROR, error) logcat(LogPriority.ERROR, error)
download.status = Download.State.ERROR download.status = Download.State.ERROR
notifier.onError(error.message, download.chapter.name, download.manga.title) notifier.onError(error.message, download.chapter.name, download.manga.title, download.manga.id)
} }
} }
@@ -471,7 +475,7 @@ class Downloader(
// Mark this page as error and allow to download the remaining // Mark this page as error and allow to download the remaining
page.progress = 0 page.progress = 0
page.status = Page.State.ERROR page.status = Page.State.ERROR
notifier.onError(e.message, download.chapter.name, download.manga.title) notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
} }
} }
@@ -650,7 +654,7 @@ class Downloader(
chapter, chapter,
urls, urls,
categories, categories,
source.name source.name,
) )
// Remove the old file // Remove the old file
@@ -710,7 +714,7 @@ class Downloader(
removeFromQueueIf { it.manga.id == manga.id } removeFromQueueIf { it.manga.id == manga.id }
} }
private fun _clearQueue() { private fun internalClearQueue() {
_queueState.update { _queueState.update {
it.forEach { download -> it.forEach { download ->
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) { if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
@@ -732,7 +736,7 @@ class Downloader(
} }
pause() pause()
_clearQueue() internalClearQueue()
addAllToQueue(downloads) addAllToQueue(downloads)
if (wasRunning) { if (wasRunning) {
@@ -34,7 +34,6 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isConnectedToWifi
@@ -58,17 +57,16 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority import logcat.LogPriority
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.model.GroupLibraryMode import tachiyomi.domain.library.model.GroupLibraryMode
import tachiyomi.domain.library.model.LibraryGroup import tachiyomi.domain.library.model.LibraryGroup
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
@@ -108,16 +106,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
private val sourceManager: SourceManager = Injekt.get() private val sourceManager: SourceManager = Injekt.get()
private val downloadPreferences: DownloadPreferences = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get() private val libraryPreferences: LibraryPreferences = Injekt.get()
private val downloadManager: DownloadManager = Injekt.get() private val downloadManager: DownloadManager = Injekt.get()
private val coverCache: CoverCache = Injekt.get() private val coverCache: CoverCache = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get() private val getLibraryManga: GetLibraryManga = Injekt.get()
private val getManga: GetManga = Injekt.get() private val getManga: GetManga = Injekt.get()
private val updateManga: UpdateManga = Injekt.get() private val updateManga: UpdateManga = Injekt.get()
private val getCategories: GetCategories = Injekt.get()
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get() private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
private val fetchInterval: FetchInterval = Injekt.get() private val fetchInterval: FetchInterval = Injekt.get()
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get()
// SY --> // SY -->
private val getFavorites: GetFavorites = Injekt.get() private val getFavorites: GetFavorites = Injekt.get()
@@ -363,14 +360,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
async { async {
semaphore.withPermit { semaphore.withPermit {
if ( if (
mdlistLogged && mangaInSource.firstOrNull() mdlistLogged &&
mangaInSource.firstOrNull()
?.let { it.manga.source in mangaDexSourceIds } == true ?.let { it.manga.source in mangaDexSourceIds } == true
) { ) {
launch { launch {
mangaInSource.forEach { (manga) -> mangaInSource.forEach { (manga) ->
try { try {
val tracks = getTracks.await(manga.id) val tracks = getTracks.await(manga.id)
if (tracks.isEmpty() || tracks.none { it.trackerId == TrackerManager.MDLIST }) { if (tracks.isEmpty() ||
tracks.none { it.trackerId == TrackerManager.MDLIST }
) {
val track = mdList.createInitialTracker(manga) val track = mdList.createInitialTracker(manga)
insertTrack.await(mdList.refresh(track).toDomainTrack(false)!!) insertTrack.await(mdList.refresh(track).toDomainTrack(false)!!)
} }
@@ -400,10 +400,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// SY --> // SY -->
.sortedByDescending { it.sourceOrder }.run { .sortedByDescending { it.sourceOrder }.run {
if (libraryPreferences.libraryReadDuplicateChapters().get()) { if (libraryPreferences.libraryReadDuplicateChapters().get()) {
val readChapters = getChaptersByMangaId.await(manga.id).filter { it.read } val readChapters = getChaptersByMangaId.await(manga.id).filter {
it.read
}
val newReadChapters = this.filter { chapter -> val newReadChapters = this.filter { chapter ->
chapter.chapterNumber > 0 && chapter.chapterNumber > 0 &&
readChapters.any { it.chapterNumber == chapter.chapterNumber } readChapters.any {
it.chapterNumber == chapter.chapterNumber
}
} }
if (newReadChapters.isNotEmpty()) { if (newReadChapters.isNotEmpty()) {
@@ -415,12 +419,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
this this
} }
} }
//SY <-- // SY <--
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {
val categoryIds = getCategories.await(manga.id).map { it.id } val chaptersToDownload = filterChaptersForDownload.await(manga, newChapters)
if (manga.shouldDownloadNewChapters(categoryIds, downloadPreferences)) {
downloadChapters(manga, newChapters) if (chaptersToDownload.isNotEmpty()) {
downloadChapters(manga, chaptersToDownload)
hasDownloads.set(true) hasDownloads.set(true)
} }
@@ -766,7 +771,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val constraints = Constraints( val constraints = Constraints(
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED NetworkType.UNMETERED
} else { NetworkType.CONNECTED }, } else {
NetworkType.CONNECTED
},
requiresCharging = DEVICE_CHARGING in restrictions, requiresCharging = DEVICE_CHARGING in restrictions,
requiresBatteryNotLow = true, requiresBatteryNotLow = true,
) )
@@ -265,6 +265,8 @@ class NotificationReceiver : BroadcastReceiver() {
private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER" private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
private const val ACTION_DOWNLOAD_CHAPTER = "$ID.$NAME.ACTION_DOWNLOAD_CHAPTER" private const val ACTION_DOWNLOAD_CHAPTER = "$ID.$NAME.ACTION_DOWNLOAD_CHAPTER"
private const val ACTION_OPEN_ENTRY = "$ID.$NAME.ACTION_OPEN_ENTRY"
private const val ACTION_RESUME_DOWNLOADS = "$ID.$NAME.ACTION_RESUME_DOWNLOADS" private const val ACTION_RESUME_DOWNLOADS = "$ID.$NAME.ACTION_RESUME_DOWNLOADS"
private const val ACTION_PAUSE_DOWNLOADS = "$ID.$NAME.ACTION_PAUSE_DOWNLOADS" private const val ACTION_PAUSE_DOWNLOADS = "$ID.$NAME.ACTION_PAUSE_DOWNLOADS"
private const val ACTION_CLEAR_DOWNLOADS = "$ID.$NAME.ACTION_CLEAR_DOWNLOADS" private const val ACTION_CLEAR_DOWNLOADS = "$ID.$NAME.ACTION_CLEAR_DOWNLOADS"
@@ -503,6 +505,26 @@ class NotificationReceiver : BroadcastReceiver() {
) )
} }
/**
* Returns [PendingIntent] that opens the manga info controller
*
* @param context context of application
* @param mangaId id of the entry to open
*/
internal fun openEntryPendingActivity(context: Context, mangaId: Long): PendingIntent {
val newIntent = Intent(context, MainActivity::class.java).setAction(Constants.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(Constants.MANGA_EXTRA, mangaId)
.putExtra("notificationId", mangaId.hashCode())
return PendingIntent.getActivity(
context,
mangaId.hashCode(),
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
/** /**
* Returns [PendingIntent] that starts a service which stops the library update * Returns [PendingIntent] that starts a service which stops the library update
* *
@@ -147,7 +147,7 @@ class SyncManager(
return return
} }
if (remoteBackup === syncData.backup){ if (remoteBackup === syncData.backup) {
// nothing changed // nothing changed
logcat(LogPriority.DEBUG) { "Skip restore due to remote was overwrite from local" } logcat(LogPriority.DEBUG) { "Skip restore due to remote was overwrite from local" }
syncPreferences.lastSyncTimestamp().set(Date().time) syncPreferences.lastSyncTimestamp().set(Date().time)
@@ -72,7 +72,7 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
try { try {
val remoteSData = pullSyncData() val remoteSData = pullSyncData()
if (remoteSData != null ){ if (remoteSData != null) {
// Get local unique device ID // Get local unique device ID
val localDeviceId = syncPreferences.uniqueDeviceID() val localDeviceId = syncPreferences.uniqueDeviceID()
val lastSyncDeviceId = remoteSData.deviceId val lastSyncDeviceId = remoteSData.deviceId
@@ -86,7 +86,7 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
return if (lastSyncDeviceId == localDeviceId) { return if (lastSyncDeviceId == localDeviceId) {
pushSyncData(syncData) pushSyncData(syncData)
syncData.backup syncData.backup
}else{ } else {
// Merge the local and remote sync data // Merge the local and remote sync data
val mergedSyncData = mergeSyncData(syncData, remoteSData) val mergedSyncData = mergeSyncData(syncData, remoteSData)
pushSyncData(mergedSyncData) pushSyncData(mergedSyncData)
@@ -165,7 +165,9 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
appProperties = mapOf("deviceId" to syncData.deviceId) appProperties = mapOf("deviceId" to syncData.deviceId)
} }
drive.files().update(fileId, fileMetadata, mediaContent).execute() drive.files().update(fileId, fileMetadata, mediaContent).execute()
logcat(LogPriority.DEBUG) { "Updated existing sync data file in Google Drive with file ID: $fileId" } logcat(LogPriority.DEBUG) {
"Updated existing sync data file in Google Drive with file ID: $fileId"
}
} else { } else {
val fileMetadata = File().apply { val fileMetadata = File().apply {
name = remoteFileName name = remoteFileName
@@ -176,7 +178,9 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
val uploadedFile = drive.files().create(fileMetadata, mediaContent) val uploadedFile = drive.files().create(fileMetadata, mediaContent)
.setFields("id") .setFields("id")
.execute() .execute()
logcat(LogPriority.DEBUG) { "Created new sync data file in Google Drive with file ID: ${uploadedFile.id}" } logcat(LogPriority.DEBUG) {
"Created new sync data file in Google Drive with file ID: ${uploadedFile.id}"
}
} }
} }
} }
@@ -203,7 +207,6 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
} }
} }
suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus { suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus {
val drive = googleDriveService.driveService val drive = googleDriveService.driveService
@@ -26,7 +26,7 @@ abstract class SyncService(
val json: Json, val json: Json,
val syncPreferences: SyncPreferences, val syncPreferences: SyncPreferences,
) { ) {
abstract suspend fun doSync(syncData: SyncData): Backup?; abstract suspend fun doSync(syncData: SyncData): Backup?
/** /**
* Merges the local and remote sync data into a single JSON string. * Merges the local and remote sync data into a single JSON string.
@@ -44,7 +44,8 @@ abstract class SyncService(
remoteSyncData.backup?.backupManga, remoteSyncData.backup?.backupManga,
localSyncData.backup?.backupCategories ?: emptyList(), localSyncData.backup?.backupCategories ?: emptyList(),
remoteSyncData.backup?.backupCategories ?: emptyList(), remoteSyncData.backup?.backupCategories ?: emptyList(),
mergedCategoriesList) mergedCategoriesList,
)
val mergedSourcesList = val mergedSourcesList =
mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources) mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources)
@@ -120,11 +121,13 @@ abstract class SyncService(
val mergedCategoriesMapByName = mergedCategories.associateBy { it.name } val mergedCategoriesMapByName = mergedCategories.associateBy { it.name }
fun updateCategories(theManga: BackupManga, theMap: Map<Long, BackupCategory>): BackupManga { fun updateCategories(theManga: BackupManga, theMap: Map<Long, BackupCategory>): BackupManga {
return theManga.copy(categories = theManga.categories.mapNotNull { return theManga.copy(
theMap[it]?.let { category -> categories = theManga.categories.mapNotNull {
mergedCategoriesMapByName[category.name]?.order theMap[it]?.let { category ->
} mergedCategoriesMapByName[category.name]?.order
}) }
},
)
} }
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
@@ -147,7 +150,7 @@ abstract class SyncService(
} }
updateCategories( updateCategories(
local.copy(chapters = mergeChapters(local.chapters, remote.chapters)), local.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
localCategoriesMapByOrder localCategoriesMapByOrder,
) )
} else { } else {
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
@@ -155,7 +158,7 @@ abstract class SyncService(
} }
updateCategories( updateCategories(
remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)), remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
remoteCategoriesMapByOrder remoteCategoriesMapByOrder,
) )
} }
} }
@@ -301,7 +304,7 @@ abstract class SyncService(
private fun mergeSourcesLists( private fun mergeSourcesLists(
localSources: List<BackupSource>?, localSources: List<BackupSource>?,
remoteSources: List<BackupSource>? remoteSources: List<BackupSource>?,
): List<BackupSource> { ): List<BackupSource> {
val logTag = "MergeSources" val logTag = "MergeSources"
@@ -346,7 +349,7 @@ abstract class SyncService(
private fun mergePreferencesLists( private fun mergePreferencesLists(
localPreferences: List<BackupPreference>?, localPreferences: List<BackupPreference>?,
remotePreferences: List<BackupPreference>? remotePreferences: List<BackupPreference>?,
): List<BackupPreference> { ): List<BackupPreference> {
val logTag = "MergePreferences" val logTag = "MergePreferences"
@@ -394,7 +397,7 @@ abstract class SyncService(
private fun mergeSourcePreferencesLists( private fun mergeSourcePreferencesLists(
localPreferences: List<BackupSourcePreferences>?, localPreferences: List<BackupSourcePreferences>?,
remotePreferences: List<BackupSourcePreferences>? remotePreferences: List<BackupSourcePreferences>?,
): List<BackupSourcePreferences> { ): List<BackupSourcePreferences> {
val logTag = "MergeSourcePreferences" val logTag = "MergeSourcePreferences"
@@ -408,38 +411,39 @@ abstract class SyncService(
} }
// Merge both source preferences maps // Merge both source preferences maps
val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { sourceKey -> val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct()
val localSourcePreference = localPreferencesMap[sourceKey] .mapNotNull { sourceKey ->
val remoteSourcePreference = remotePreferencesMap[sourceKey] val localSourcePreference = localPreferencesMap[sourceKey]
val remoteSourcePreference = remotePreferencesMap[sourceKey]
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
"Processing source preference key: $sourceKey. " + "Processing source preference key: $sourceKey. " +
"Local source preference: ${localSourcePreference != null}, " + "Local source preference: ${localSourcePreference != null}, " +
"Remote source preference: ${remoteSourcePreference != null}" "Remote source preference: ${remoteSourcePreference != null}"
} }
when { when {
localSourcePreference != null && remoteSourcePreference == null -> { localSourcePreference != null && remoteSourcePreference == null -> {
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
"Using local source preference: ${localSourcePreference.sourceKey}." "Using local source preference: ${localSourcePreference.sourceKey}."
}
localSourcePreference
} }
localSourcePreference remoteSourcePreference != null && localSourcePreference == null -> {
} logcat(LogPriority.DEBUG, logTag) {
remoteSourcePreference != null && localSourcePreference == null -> { "Using remote source preference: ${remoteSourcePreference.sourceKey}."
logcat(LogPriority.DEBUG, logTag) { }
"Using remote source preference: ${remoteSourcePreference.sourceKey}." remoteSourcePreference
} }
remoteSourcePreference localSourcePreference != null && remoteSourcePreference != null -> {
// Merge the individual preferences within the source preferences
val mergedPrefs =
mergeIndividualPreferences(localSourcePreference.prefs, remoteSourcePreference.prefs)
BackupSourcePreferences(sourceKey, mergedPrefs)
}
else -> null
} }
localSourcePreference != null && remoteSourcePreference != null -> {
// Merge the individual preferences within the source preferences
val mergedPrefs =
mergeIndividualPreferences(localSourcePreference.prefs, remoteSourcePreference.prefs)
BackupSourcePreferences(sourceKey, mergedPrefs)
}
else -> null
} }
}
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
"Source preferences merge completed. Total merged source preferences: ${mergedSourcePreferences.size}" "Source preferences merge completed. Total merged source preferences: ${mergedSourcePreferences.size}"
@@ -450,7 +454,7 @@ abstract class SyncService(
private fun mergeIndividualPreferences( private fun mergeIndividualPreferences(
localPrefs: List<BackupPreference>, localPrefs: List<BackupPreference>,
remotePrefs: List<BackupPreference> remotePrefs: List<BackupPreference>,
): List<BackupPreference> { ): List<BackupPreference> {
val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key } val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key }
return mergedPrefsMap.values.toList() return mergedPrefsMap.values.toList()
@@ -459,7 +463,7 @@ abstract class SyncService(
// SY --> // SY -->
private fun mergeSavedSearchesLists( private fun mergeSavedSearchesLists(
localSearches: List<BackupSavedSearch>?, localSearches: List<BackupSavedSearch>?,
remoteSearches: List<BackupSavedSearch>? remoteSearches: List<BackupSavedSearch>?,
): List<BackupSavedSearch> { ): List<BackupSavedSearch> {
val logTag = "MergeSavedSearches" val logTag = "MergeSavedSearches"
@@ -38,7 +38,7 @@ class SyncYomiSyncService(
try { try {
val (remoteData, etag) = pullSyncData() val (remoteData, etag) = pullSyncData()
val finalSyncData = if (remoteData != null){ val finalSyncData = if (remoteData != null) {
assert(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" } assert(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
logcat(LogPriority.DEBUG, "SyncService") { logcat(LogPriority.DEBUG, "SyncService") {
"Try update remote data with ETag($etag)" "Try update remote data with ETag($etag)"
@@ -54,7 +54,6 @@ class SyncYomiSyncService(
pushSyncData(finalSyncData, etag) pushSyncData(finalSyncData, etag)
return finalSyncData.backup return finalSyncData.backup
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR) { "Error syncing: ${e.message}" } logcat(LogPriority.ERROR) { "Error syncing: ${e.message}" }
notifier.showSyncError(e.message) notifier.showSyncError(e.message)
@@ -113,7 +112,6 @@ class SyncYomiSyncService(
// return default value so we can overwrite it // return default value so we can overwrite it
Pair(null, "") Pair(null, "")
} }
} else { } else {
val responseBody = response.body.string() val responseBody = response.body.string()
notifier.showSyncError("Failed to download sync data: $responseBody") notifier.showSyncError("Failed to download sync data: $responseBody")
@@ -165,11 +163,9 @@ class SyncYomiSyncService(
.takeIf { it?.isNotEmpty() == true } ?: throw SyncYomiException("Missing ETag") .takeIf { it?.isNotEmpty() == true } ?: throw SyncYomiException("Missing ETag")
syncPreferences.lastSyncEtag().set(newETag) syncPreferences.lastSyncEtag().set(newETag)
logcat(LogPriority.DEBUG) { "SyncYomi sync completed" } logcat(LogPriority.DEBUG) { "SyncYomi sync completed" }
} else if (response.code == HttpStatus.SC_PRECONDITION_FAILED) { } else if (response.code == HttpStatus.SC_PRECONDITION_FAILED) {
// other clients updated remote data, will try next time // other clients updated remote data, will try next time
logcat(LogPriority.DEBUG) { "SyncYomi sync failed with 412" } logcat(LogPriority.DEBUG) { "SyncYomi sync failed with 412" }
} else { } else {
val responseBody = response.body.string() val responseBody = response.body.string()
notifier.showSyncError("Failed to upload sync data: $responseBody") notifier.showSyncError("Failed to upload sync data: $responseBody")
@@ -47,10 +47,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
return withIOContext { return withIOContext {
val query = """ val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
| status | status
|} |}
|} |}
| |
""".trimMargin() """.trimMargin()
@@ -65,7 +65,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@@ -109,7 +109,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date)) put("completedAt", createDate(track.finished_reading_date))
} }
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess() .awaitSuccess()
track track
} }
@@ -119,9 +119,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
withIOContext { withIOContext {
val query = """ val query = """
|mutation DeleteManga(${'$'}listId: Int) { |mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry(id: ${'$'}listId) { |DeleteMediaListEntry(id: ${'$'}listId) {
|deleted |deleted
|} |}
|} |}
| |
""".trimMargin() """.trimMargin()
@@ -131,7 +131,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("listId", track.libraryId) put("listId", track.libraryId)
} }
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess() .awaitSuccess()
} }
} }
@@ -172,7 +172,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@@ -242,7 +242,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@@ -286,7 +286,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@@ -364,17 +364,17 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
companion object { companion object {
private const val clientId = "16329" private const val CLIENT_ID = "16329"
private const val apiUrl = "https://graphql.anilist.co/" private const val API_URL = "https://graphql.anilist.co/"
private const val baseUrl = "https://anilist.co/api/v2/" private const val BASE_URL = "https://anilist.co/api/v2/"
private const val baseMangaUrl = "https://anilist.co/manga/" private const val BASE_MANGA_URL = "https://anilist.co/manga/"
fun mangaUrl(mediaId: Long): String { fun mangaUrl(mediaId: Long): String {
return baseMangaUrl + mediaId return BASE_MANGA_URL + mediaId
} }
fun authUrl(): Uri = "${baseUrl}oauth/authorize".toUri().buildUpon() fun authUrl(): Uri = "${BASE_URL}oauth/authorize".toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("response_type", "token") .appendQueryParameter("response_type", "token")
.build() .build()
} }
@@ -42,7 +42,7 @@ class BangumiApi(
.add("rating", track.score.toInt().toString()) .add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = body)) authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = body))
.awaitSuccess() .awaitSuccess()
track track
} }
@@ -55,7 +55,7 @@ class BangumiApi(
.add("rating", track.score.toInt().toString()) .add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = sbody)) authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = sbody))
.awaitSuccess() .awaitSuccess()
// chapter update // chapter update
@@ -64,7 +64,7 @@ class BangumiApi(
.build() .build()
authClient.newCall( authClient.newCall(
POST( POST(
"$apiUrl/subject/${track.remote_id}/update/watched_eps", "$API_URL/subject/${track.remote_id}/update/watched_eps",
body = body, body = body,
), ),
).awaitSuccess() ).awaitSuccess()
@@ -75,7 +75,7 @@ class BangumiApi(
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}" val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
.toUri() .toUri()
.buildUpon() .buildUpon()
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
@@ -124,7 +124,7 @@ class BangumiApi(
suspend fun findLibManga(track: Track): Track? { suspend fun findLibManga(track: Track): Track? {
return withIOContext { return withIOContext {
with(json) { with(json) {
authClient.newCall(GET("$apiUrl/subject/${track.remote_id}")) authClient.newCall(GET("$API_URL/subject/${track.remote_id}"))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { jsonToSearch(it) } .let { jsonToSearch(it) }
@@ -134,7 +134,7 @@ class BangumiApi(
suspend fun statusLibManga(track: Track): Track? { suspend fun statusLibManga(track: Track): Track? {
return withIOContext { return withIOContext {
val urlUserRead = "$apiUrl/collection/${track.remote_id}" val urlUserRead = "$API_URL/collection/${track.remote_id}"
val requestUserRead = Request.Builder() val requestUserRead = Request.Builder()
.url(urlUserRead) .url(urlUserRead)
.cacheControl(CacheControl.FORCE_NETWORK) .cacheControl(CacheControl.FORCE_NETWORK)
@@ -171,41 +171,41 @@ class BangumiApi(
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("code", code) .add("code", code)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
companion object { companion object {
private const val clientId = "bgm291665acbd06a4c28" private const val CLIENT_ID = "bgm291665acbd06a4c28"
private const val clientSecret = "43e5ce36b207de16e5d3cfd3e79118db" private const val CLIENT_SECRET = "43e5ce36b207de16e5d3cfd3e79118db"
private const val apiUrl = "https://api.bgm.tv" private const val API_URL = "https://api.bgm.tv"
private const val oauthUrl = "https://bgm.tv/oauth/access_token" private const val OAUTH_URL = "https://bgm.tv/oauth/access_token"
private const val loginUrl = "https://bgm.tv/oauth/authorize" private const val LOGIN_URL = "https://bgm.tv/oauth/authorize"
private const val redirectUrl = "mihon://bangumi-auth" private const val REDIRECT_URL = "mihon://bangumi-auth"
fun authUrl(): Uri = fun authUrl(): Uri =
loginUrl.toUri().buildUpon() LOGIN_URL.toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("response_type", "code") .appendQueryParameter("response_type", "code")
.appendQueryParameter("redirect_uri", redirectUrl) .appendQueryParameter("redirect_uri", REDIRECT_URL)
.build() .build()
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("refresh_token", token) .add("refresh_token", token)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
} }
@@ -66,7 +66,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
"${baseUrl}library-entries", "${BASE_URL}library-entries",
headers = headersOf( headers = headersOf(
"Content-Type", "Content-Type",
"application/vnd.api+json", "application/vnd.api+json",
@@ -104,7 +104,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
with(json) { with(json) {
authClient.newCall( authClient.newCall(
Request.Builder() Request.Builder()
.url("${baseUrl}library-entries/${track.remote_id}") .url("${BASE_URL}library-entries/${track.remote_id}")
.headers( .headers(
headersOf( headersOf(
"Content-Type", "Content-Type",
@@ -130,7 +130,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
authClient authClient
.newCall( .newCall(
DELETE( DELETE(
"${baseUrl}library-entries/${track.remoteId}", "${BASE_URL}library-entries/${track.remoteId}",
headers = headersOf( headers = headersOf(
"Content-Type", "Content-Type",
"application/vnd.api+json", "application/vnd.api+json",
@@ -143,7 +143,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun search(query: String): List<TrackSearch> { suspend fun search(query: String): List<TrackSearch> {
return withIOContext { return withIOContext {
with(json) { with(json) {
authClient.newCall(GET(algoliaKeyUrl)) authClient.newCall(GET(ALGOLIA_KEY_URL))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { .let {
@@ -157,16 +157,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> { private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val jsonObject = buildJsonObject { val jsonObject = buildJsonObject {
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter") put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$ALGOLIA_FILTER")
} }
with(json) { with(json) {
client.newCall( client.newCall(
POST( POST(
algoliaUrl, ALGOLIA_URL,
headers = headersOf( headers = headersOf(
"X-Algolia-Application-Id", "X-Algolia-Application-Id",
algoliaAppId, ALGOLIA_APP_ID,
"X-Algolia-API-Key", "X-Algolia-API-Key",
key, key,
), ),
@@ -187,7 +187,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun findLibManga(track: Track, userId: String): Track? { suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext { return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon() val url = "${BASE_URL}library-entries".toUri().buildUpon()
.encodedQuery("filter[manga_id]=${track.remote_id}&filter[user_id]=$userId") .encodedQuery("filter[manga_id]=${track.remote_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga") .appendQueryParameter("include", "manga")
.build() .build()
@@ -210,7 +210,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun getLibManga(track: Track): Track { suspend fun getLibManga(track: Track): Track {
return withIOContext { return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon() val url = "${BASE_URL}library-entries".toUri().buildUpon()
.encodedQuery("filter[id]=${track.remote_id}") .encodedQuery("filter[id]=${track.remote_id}")
.appendQueryParameter("include", "manga") .appendQueryParameter("include", "manga")
.build() .build()
@@ -237,11 +237,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("username", username) .add("username", username)
.add("password", password) .add("password", password)
.add("grant_type", "password") .add("grant_type", "password")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.build() .build()
with(json) { with(json) {
client.newCall(POST(loginUrl, body = formBody)) client.newCall(POST(LOGIN_URL, body = formBody))
.awaitSuccess() .awaitSuccess()
.parseAs() .parseAs()
} }
@@ -250,7 +250,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun getCurrentUser(): String { suspend fun getCurrentUser(): String {
return withIOContext { return withIOContext {
val url = "${baseUrl}users".toUri().buildUpon() val url = "${BASE_URL}users".toUri().buildUpon()
.encodedQuery("filter[self]=true") .encodedQuery("filter[self]=true")
.build() .build()
with(json) { with(json) {
@@ -265,35 +265,31 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
} }
companion object { companion object {
private const val clientId = private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
"dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd" private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val clientSecret =
"54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val baseUrl = "https://kitsu.app/api/edge/" private const val BASE_URL = "https://kitsu.app/api/edge/"
private const val loginUrl = "https://kitsu.app/api/oauth/token" private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
private const val baseMangaUrl = "https://kitsu.app/manga/" private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
private const val algoliaKeyUrl = "https://kitsu.app/api/edge/algolia-keys/media/" private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
private const val algoliaUrl = private const val ALGOLIA_APP_ID = "AWQO5J657S"
"https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/" private const val ALGOLIA_URL = "https://$ALGOLIA_APP_ID-dsn.algolia.net/1/indexes/production_media/query/"
private const val algoliaAppId = "AWQO5J657S" private const val ALGOLIA_FILTER = "&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=" +
private const val algoliaFilter = "%5B%22synopsis%22%2C%22averageRating%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22" +
"&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=" + "posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
"%5B%22synopsis%22%2C%22averageRating%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22" +
"posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
fun mangaUrl(remoteId: Long): String { fun mangaUrl(remoteId: Long): String {
return baseMangaUrl + remoteId return BASE_MANGA_URL + remoteId
} }
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
loginUrl, LOGIN_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("refresh_token", token) .add("refresh_token", token)
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.build(), .build(),
) )
} }
@@ -6,8 +6,8 @@ import java.util.Locale
object KitsuDateHelper { object KitsuDateHelper {
private const val pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" private const val PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
private val formatter = SimpleDateFormat(pattern, Locale.ENGLISH) private val formatter = SimpleDateFormat(PATTERN, Locale.ENGLISH)
fun convert(dateValue: Long): String? { fun convert(dateValue: Long): String? {
if (dateValue == 0L) return null if (dateValue == 0L) return null
@@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.track.model package eu.kanade.tachiyomi.data.track.model
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -54,7 +54,7 @@ class ShikimoriApi(
} }
authClient.newCall( authClient.newCall(
POST( POST(
"$apiUrl/v2/user_rates", "$API_URL/v2/user_rates",
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
).awaitSuccess() ).awaitSuccess()
@@ -72,14 +72,14 @@ class ShikimoriApi(
suspend fun deleteLibManga(track: DomainTrack) { suspend fun deleteLibManga(track: DomainTrack) {
withIOContext { withIOContext {
authClient authClient
.newCall(DELETE("$apiUrl/v2/user_rates/${track.libraryId}")) .newCall(DELETE("$API_URL/v2/user_rates/${track.libraryId}"))
.awaitSuccess() .awaitSuccess()
} }
} }
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val url = "$apiUrl/mangas".toUri().buildUpon() val url = "$API_URL/mangas".toUri().buildUpon()
.appendQueryParameter("order", "popularity") .appendQueryParameter("order", "popularity")
.appendQueryParameter("search", search) .appendQueryParameter("search", search)
.appendQueryParameter("limit", "20") .appendQueryParameter("limit", "20")
@@ -102,10 +102,10 @@ class ShikimoriApi(
remote_id = obj["id"]!!.jsonPrimitive.long remote_id = obj["id"]!!.jsonPrimitive.long
title = obj["name"]!!.jsonPrimitive.content title = obj["name"]!!.jsonPrimitive.content
total_chapters = obj["chapters"]!!.jsonPrimitive.long total_chapters = obj["chapters"]!!.jsonPrimitive.long
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content cover_url = BASE_URL + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
summary = "" summary = ""
score = obj["score"]!!.jsonPrimitive.double score = obj["score"]!!.jsonPrimitive.double
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content tracking_url = BASE_URL + obj["url"]!!.jsonPrimitive.content
publishing_status = obj["status"]!!.jsonPrimitive.content publishing_status = obj["status"]!!.jsonPrimitive.content
publishing_type = obj["kind"]!!.jsonPrimitive.content publishing_type = obj["kind"]!!.jsonPrimitive.content
start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: "" start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: ""
@@ -121,13 +121,13 @@ class ShikimoriApi(
last_chapter_read = obj["chapters"]!!.jsonPrimitive.double last_chapter_read = obj["chapters"]!!.jsonPrimitive.double
score = obj["score"]!!.jsonPrimitive.int.toDouble() score = obj["score"]!!.jsonPrimitive.int.toDouble()
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content) status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content tracking_url = BASE_URL + mangas["url"]!!.jsonPrimitive.content
} }
} }
suspend fun findLibManga(track: Track, userId: String): Track? { suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext { return withIOContext {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon() val urlMangas = "$API_URL/mangas".toUri().buildUpon()
.appendPath(track.remote_id.toString()) .appendPath(track.remote_id.toString())
.build() .build()
val mangas = with(json) { val mangas = with(json) {
@@ -136,7 +136,7 @@ class ShikimoriApi(
.parseAs<JsonObject>() .parseAs<JsonObject>()
} }
val url = "$apiUrl/v2/user_rates".toUri().buildUpon() val url = "$API_URL/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", userId) .appendQueryParameter("user_id", userId)
.appendQueryParameter("target_id", track.remote_id.toString()) .appendQueryParameter("target_id", track.remote_id.toString())
.appendQueryParameter("target_type", "Manga") .appendQueryParameter("target_type", "Manga")
@@ -160,7 +160,7 @@ class ShikimoriApi(
suspend fun getCurrentUser(): Int { suspend fun getCurrentUser(): Int {
return with(json) { return with(json) {
authClient.newCall(GET("$apiUrl/users/whoami")) authClient.newCall(GET("$API_URL/users/whoami"))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { .let {
@@ -180,39 +180,39 @@ class ShikimoriApi(
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("code", code) .add("code", code)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
companion object { companion object {
private const val clientId = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA" private const val CLIENT_ID = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA"
private const val clientSecret = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus" private const val CLIENT_SECRET = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus"
private const val baseUrl = "https://shikimori.one" private const val BASE_URL = "https://shikimori.one"
private const val apiUrl = "$baseUrl/api" private const val API_URL = "$BASE_URL/api"
private const val oauthUrl = "$baseUrl/oauth/token" private const val OAUTH_URL = "$BASE_URL/oauth/token"
private const val loginUrl = "$baseUrl/oauth/authorize" private const val LOGIN_URL = "$BASE_URL/oauth/authorize"
private const val redirectUrl = "mihon://shikimori-auth" private const val REDIRECT_URL = "mihon://shikimori-auth"
fun authUrl(): Uri = loginUrl.toUri().buildUpon() fun authUrl(): Uri = LOGIN_URL.toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("redirect_uri", redirectUrl) .appendQueryParameter("redirect_uri", REDIRECT_URL)
.appendQueryParameter("response_type", "code") .appendQueryParameter("response_type", "code")
.build() .build()
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("refresh_token", token) .add("refresh_token", token)
.build(), .build(),
) )
@@ -69,17 +69,18 @@ class ExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>() private val iconMap = mutableMapOf<String, Drawable>()
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>()) private val installedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope) val installedExtensionsFlow = installedExtensionMapFlow.mapExtensions(scope)
private val availableExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
// SY --> // SY -->
val availableExtensionsFlow = _availableExtensionsMapFlow.map { it.filterNotBlacklisted().values.toList() } val availableExtensionsFlow = availableExtensionMapFlow.map { it.filterNotBlacklisted().values.toList() }
.stateIn(scope, SharingStarted.Lazily, _availableExtensionsMapFlow.value.values.toList()) .stateIn(scope, SharingStarted.Lazily, availableExtensionMapFlow.value.values.toList())
// SY <-- // SY <--
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>()) private val untrustedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope) val untrustedExtensionsFlow = untrustedExtensionMapFlow.mapExtensions(scope)
init { init {
initExtensions() initExtensions()
@@ -89,7 +90,7 @@ class ExtensionManager(
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? { fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = _installedExtensionsMapFlow.value.values val pkgName = installedExtensionMapFlow.value.values
.find { ext -> .find { ext ->
ext.sources.any { it.id == sourceId } ext.sources.any { it.id == sourceId }
} }
@@ -128,11 +129,11 @@ class ExtensionManager(
private fun initExtensions() { private fun initExtensions() {
val extensions = ExtensionLoader.loadExtensions(context) val extensions = ExtensionLoader.loadExtensions(context)
_installedExtensionsMapFlow.value = extensions installedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>() .filterIsInstance<LoadResult.Success>()
.associate { it.extension.pkgName to it.extension } .associate { it.extension.pkgName to it.extension }
_untrustedExtensionsMapFlow.value = extensions untrustedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>() .filterIsInstance<LoadResult.Untrusted>()
.associate { it.extension.pkgName to it.extension } .associate { it.extension.pkgName to it.extension }
// SY --> // SY -->
@@ -159,7 +160,7 @@ class ExtensionManager(
// EXH <-- // EXH <--
/** /**
* Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow]. * Finds the available extensions in the [api] and updates [availableExtensionMapFlow].
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<Extension.Available> = try { val extensions: List<Extension.Available> = try {
@@ -172,7 +173,7 @@ class ExtensionManager(
enableAdditionalSubLanguages(extensions) enableAdditionalSubLanguages(extensions)
_availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } availableExtensionMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions) updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions) setupAvailableExtensionsSourcesDataMap(extensions)
} }
@@ -218,7 +219,7 @@ class ExtensionManager(
return return
} }
val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap() val installedExtensionsMap = installedExtensionMapFlow.value.toMutableMap()
var changed = false var changed = false
for ((pkgName, extension) in installedExtensionsMap) { for ((pkgName, extension) in installedExtensionsMap) {
val availableExt = availableExtensions.find { it.pkgName == pkgName } val availableExt = availableExtensions.find { it.pkgName == pkgName }
@@ -247,7 +248,7 @@ class ExtensionManager(
} }
} }
if (changed) { if (changed) {
_installedExtensionsMapFlow.value = installedExtensionsMap installedExtensionMapFlow.value = installedExtensionsMap
} }
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@@ -271,7 +272,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 = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() val availableExt = availableExtensionMapFlow.value[extension.pkgName] ?: return emptyFlow()
return installExtension(availableExt) return installExtension(availableExt)
} }
@@ -308,11 +309,11 @@ class ExtensionManager(
* @param extension the extension to trust * @param extension the extension to trust
*/ */
suspend fun trust(extension: Extension.Untrusted) { suspend fun trust(extension: Extension.Untrusted) {
_untrustedExtensionsMapFlow.value[extension.pkgName] ?: return untrustedExtensionMapFlow.value[extension.pkgName] ?: return
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)
_untrustedExtensionsMapFlow.value -= extension.pkgName untrustedExtensionMapFlow.value -= extension.pkgName
ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
.let { it as? LoadResult.Success } .let { it as? LoadResult.Success }
@@ -332,7 +333,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsMapFlow.value += extension installedExtensionMapFlow.value += extension
} }
/** /**
@@ -349,7 +350,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsMapFlow.value += extension installedExtensionMapFlow.value += extension
} }
/** /**
@@ -359,8 +360,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) {
_installedExtensionsMapFlow.value -= pkgName installedExtensionMapFlow.value -= pkgName
_untrustedExtensionsMapFlow.value -= pkgName untrustedExtensionMapFlow.value -= pkgName
} }
/** /**
@@ -379,8 +380,8 @@ class ExtensionManager(
} }
override fun onExtensionUntrusted(extension: Extension.Untrusted) { override fun onExtensionUntrusted(extension: Extension.Untrusted) {
_installedExtensionsMapFlow.value -= extension.pkgName installedExtensionMapFlow.value -= extension.pkgName
_untrustedExtensionsMapFlow.value += extension untrustedExtensionMapFlow.value += extension
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@@ -404,14 +405,14 @@ 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 val availableExt = availableExtension
?: _availableExtensionsMapFlow.value[pkgName] ?: availableExtensionMapFlow.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 = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } val pendingUpdateCount = installedExtensionMapFlow.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()
@@ -34,8 +34,10 @@ internal class ExtensionApi {
private val getExtensionRepo: GetExtensionRepo by injectLazy() private val getExtensionRepo: GetExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy() private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
private val extensionManager: ExtensionManager by injectLazy() private val extensionManager: ExtensionManager by injectLazy()
// SY --> // SY -->
private val sourcePreferences: SourcePreferences by injectLazy() private val sourcePreferences: SourcePreferences by injectLazy()
// SY <-- // SY <--
private val json: Json by injectLazy() private val json: Json by injectLazy()
@@ -3,15 +3,20 @@ package eu.kanade.tachiyomi.extension.api
import android.content.Context import android.content.Context
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
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.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notify import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionUpdateNotifier(private val context: Context) { class ExtensionUpdateNotifier(
private val context: Context,
private val securityPreferences: SecurityPreferences = Injekt.get(),
) {
fun promptUpdates(names: List<String>) { fun promptUpdates(names: List<String>) {
context.notify( context.notify(
Notifications.ID_UPDATES_TO_EXTS, Notifications.ID_UPDATES_TO_EXTS,
@@ -24,9 +29,11 @@ class ExtensionUpdateNotifier(private val context: Context) {
names.size, names.size,
), ),
) )
val extNames = names.joinToString(", ") if (!securityPreferences.hideNotificationContent().get()) {
setContentText(extNames) val extNames = names.joinToString(", ")
setStyle(NotificationCompat.BigTextStyle().bigText(extNames)) setContentText(extNames)
setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
}
setSmallIcon(R.drawable.ic_extension_24dp) setSmallIcon(R.drawable.ic_extension_24dp)
setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context)) setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context))
setAutoCancel(true) setAutoCancel(true)
@@ -1,7 +1,13 @@
package eu.kanade.tachiyomi.extension.model package eu.kanade.tachiyomi.extension.model
enum class InstallStep { enum class InstallStep {
Idle, Pending, Downloading, Installing, Installed, Error; Idle,
Pending,
Downloading,
Installing,
Installed,
Error,
;
fun isCompleted(): Boolean { fun isCompleted(): Boolean {
return this == Installed || this == Error || this == Idle return this == Installed || this == Error || this == Idle
@@ -223,7 +223,6 @@ 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.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult { 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
@@ -383,7 +383,7 @@ class EHentai(
doc.select("#gdd .gdt1").find { el -> doc.select("#gdd .gdt1").find { el ->
el.text().lowercase() == "posted:" el.text().lowercase() == "posted:"
}!!.nextElementSibling()!!.text(), }!!.nextElementSibling()!!.text(),
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
)!!.toInstant().toEpochMilli(), )!!.toInstant().toEpochMilli(),
scanlator = EHentaiSearchMetadata.galleryId(location), scanlator = EHentaiSearchMetadata.galleryId(location),
) )
@@ -401,7 +401,7 @@ class EHentai(
chapter_number = index + 2f, chapter_number = index + 2f,
date_upload = ZonedDateTime.parse( date_upload = ZonedDateTime.parse(
posted, posted,
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
).toInstant().toEpochMilli(), ).toInstant().toEpochMilli(),
scanlator = EHentaiSearchMetadata.galleryId(link), scanlator = EHentaiSearchMetadata.galleryId(link),
) )
@@ -542,9 +542,10 @@ class EHentai(
if ( if (
MATCH_SEEK_REGEX.matches(jumpSeekValue) || MATCH_SEEK_REGEX.matches(jumpSeekValue) ||
( (
MATCH_YEAR_REGEX.matches(jumpSeekValue) && jumpSeekValue.toIntOrNull()?.let { MATCH_YEAR_REGEX.matches(jumpSeekValue) &&
it in 2007..2099 jumpSeekValue.toIntOrNull()?.let {
} == true it in 2007..2099
} == true
) )
) { ) {
uri.appendQueryParameter("seek", jumpSeekValue) uri.appendQueryParameter("seek", jumpSeekValue)
@@ -715,7 +716,7 @@ class EHentai(
when (left.removeSuffix(":").lowercase()) { when (left.removeSuffix(":").lowercase()) {
"posted" -> datePosted = ZonedDateTime.parse( "posted" -> datePosted = ZonedDateTime.parse(
right, right,
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
).toInstant().toEpochMilli() ).toInstant().toEpochMilli()
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/ // Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/ // Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
@@ -87,6 +87,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
private fun usePort443Only() = sourcePreferences.getBoolean(getStandardHttpsPreferenceKey(mdLang.lang), false) private fun usePort443Only() = sourcePreferences.getBoolean(getStandardHttpsPreferenceKey(mdLang.lang), false)
private fun blockedGroups() = sourcePreferences.getString(getBlockedGroupsPrefKey(mdLang.lang), "").orEmpty() private fun blockedGroups() = sourcePreferences.getString(getBlockedGroupsPrefKey(mdLang.lang), "").orEmpty()
private fun blockedUploaders() = sourcePreferences.getString(getBlockedUploaderPrefKey(mdLang.lang), "").orEmpty() private fun blockedUploaders() = sourcePreferences.getString(getBlockedUploaderPrefKey(mdLang.lang), "").orEmpty()
private fun coverQuality() = sourcePreferences.getString(getCoverQualityPrefKey(mdLang.lang), "").orEmpty()
private fun tryUsingFirstVolumeCover() = sourcePreferences.getBoolean(getTryUsingFirstVolumeCoverKey(mdLang.lang), false)
private val mangadexService by lazy { private val mangadexService by lazy {
MangaDexService(client) MangaDexService(client)
@@ -189,11 +191,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails")) @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return mangaHandler.fetchMangaDetailsObservable(manga, id) return mangaHandler.fetchMangaDetailsObservable(manga, id, coverQuality(), tryUsingFirstVolumeCover())
} }
override suspend fun getMangaDetails(manga: SManga): SManga { override suspend fun getMangaDetails(manga: SManga): SManga {
return mangaHandler.getMangaDetails(manga, id) return mangaHandler.getMangaDetails(manga, id, coverQuality(), tryUsingFirstVolumeCover())
} }
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList")) @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
@@ -239,7 +241,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
override fun newMetaInstance() = MangaDexSearchMetadata() override fun newMetaInstance() = MangaDexSearchMetadata()
override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Triple<MangaDto, List<String>, StatisticsMangaDto>) { override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Triple<MangaDto, List<String>, StatisticsMangaDto>) {
apiMangaParser.parseIntoMetadata(metadata, input.first, input.second, input.third) apiMangaParser.parseIntoMetadata(metadata, input.first, input.second, input.third, null, coverQuality())
} }
// LoginSource methods // LoginSource methods
@@ -334,5 +336,17 @@ class MangaDex(delegate: HttpSource, val context: Context) :
fun getBlockedUploaderPrefKey(dexLang: String): String { fun getBlockedUploaderPrefKey(dexLang: String): String {
return "${blockedUploaderPref}_$dexLang" return "${blockedUploaderPref}_$dexLang"
} }
private const val coverQualityPref = "thumbnailQuality"
fun getCoverQualityPrefKey(dexLang: String): String {
return "${coverQualityPref}_$dexLang"
}
private const val tryUsingFirstVolumeCover = "tryUsingFirstVolumeCover"
fun getTryUsingFirstVolumeCoverKey(dexLang: String): String {
return "${tryUsingFirstVolumeCover}_$dexLang"
}
} }
} }
@@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.copy import eu.kanade.tachiyomi.source.model.copy
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async import kotlinx.coroutines.async
@@ -19,6 +18,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import okhttp3.Response import okhttp3.Response
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
@@ -42,6 +42,7 @@ class MergedSource : HttpSource() {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
private val downloadPreferences: DownloadPreferences by injectLazy() private val downloadPreferences: DownloadPreferences by injectLazy()
private val filterChaptersForDownload: FilterChaptersForDownload by injectLazy()
override val id: Long = MERGED_SOURCE_ID override val id: Long = MERGED_SOURCE_ID
@@ -119,12 +120,6 @@ class MergedSource : HttpSource() {
"Manga references are empty, chapters unavailable, merge is likely corrupted" "Manga references are empty, chapters unavailable, merge is likely corrupted"
} }
val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters(
getCategories.await(manga.id).map {
it.id
},
downloadPreferences,
)
val semaphore = Semaphore(5) val semaphore = Semaphore(5)
var exception: Exception? = null var exception: Exception? = null
return supervisorScope { return supervisorScope {
@@ -141,11 +136,15 @@ class MergedSource : HttpSource() {
val chapterList = source.getChapterList(loadedManga.toSManga()) val chapterList = source.getChapterList(loadedManga.toSManga())
val results = val results =
syncChaptersWithSource.await(chapterList, loadedManga, source) syncChaptersWithSource.await(chapterList, loadedManga, source)
if (ifDownloadNewChapters && reference.downloadChapters) {
downloadManager.downloadChapters( if (reference.downloadChapters) {
loadedManga, val chaptersToDownload = filterChaptersForDownload.await(manga, results)
results, if (chaptersToDownload.isNotEmpty()) {
) downloadManager.downloadChapters(
loadedManga,
chaptersToDownload,
)
}
} }
results results
} else { } else {
@@ -73,16 +73,17 @@ interface SecureActivityDelegate {
} }
val lockedDays = preferences.authenticatorDays().get() val lockedDays = preferences.authenticatorDays().get()
val canLockToday = lockedDays == LOCK_ALL_DAYS || when (today.get(Calendar.DAY_OF_WEEK)) { val canLockToday = lockedDays == LOCK_ALL_DAYS ||
Calendar.SUNDAY -> (lockedDays and LOCK_SUNDAY) == LOCK_SUNDAY when (today.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> (lockedDays and LOCK_MONDAY) == LOCK_MONDAY Calendar.SUNDAY -> (lockedDays and LOCK_SUNDAY) == LOCK_SUNDAY
Calendar.TUESDAY -> (lockedDays and LOCK_TUESDAY) == LOCK_TUESDAY Calendar.MONDAY -> (lockedDays and LOCK_MONDAY) == LOCK_MONDAY
Calendar.WEDNESDAY -> (lockedDays and LOCK_WEDNESDAY) == LOCK_WEDNESDAY Calendar.TUESDAY -> (lockedDays and LOCK_TUESDAY) == LOCK_TUESDAY
Calendar.THURSDAY -> (lockedDays and LOCK_THURSDAY) == LOCK_THURSDAY Calendar.WEDNESDAY -> (lockedDays and LOCK_WEDNESDAY) == LOCK_WEDNESDAY
Calendar.FRIDAY -> (lockedDays and LOCK_FRIDAY) == LOCK_FRIDAY Calendar.THURSDAY -> (lockedDays and LOCK_THURSDAY) == LOCK_THURSDAY
Calendar.SATURDAY -> (lockedDays and LOCK_SATURDAY) == LOCK_SATURDAY Calendar.FRIDAY -> (lockedDays and LOCK_FRIDAY) == LOCK_FRIDAY
else -> false Calendar.SATURDAY -> (lockedDays and LOCK_SATURDAY) == LOCK_SATURDAY
} else -> false
}
return canLockNow && canLockToday return canLockNow && canLockToday
} }
@@ -99,11 +100,13 @@ interface SecureActivityDelegate {
// `requireUnlock` can be true on process start or if app was closed in locked state // `requireUnlock` can be true on process start or if app was closed in locked state
if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) { if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) {
requireUnlock = /* SY --> */ canLockNow(preferences) && /* SY <-- */ when (val lockDelay = preferences.lockAppAfter().get()) { requireUnlock =
-1 -> false // Never /* SY --> */ canLockNow(preferences) &&
0 -> true // Always /* SY <-- */ when (val lockDelay = preferences.lockAppAfter().get()) {
else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis() -1 -> false // Never
} 0 -> true // Always
else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis()
}
} }
lastClosedPref.delete() lastClosedPref.delete()
@@ -140,7 +143,7 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
val incognitoModeFlow = preferences.incognitoMode().changes() val incognitoModeFlow = preferences.incognitoMode().changes()
combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode -> combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode ->
secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS || secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS ||
secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode (secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode)
} }
.onEach(activity.window::setSecureScreen) .onEach(activity.window::setSecureScreen)
.launchIn(activity.lifecycleScope) .launchIn(activity.lifecycleScope)
@@ -41,7 +41,7 @@ class ExtensionsScreenModel(
private val getExtensions: GetExtensionsByType = Injekt.get(), private val getExtensions: GetExtensionsByType = Injekt.get(),
) : StateScreenModel<ExtensionsScreenModel.State>(State()) { ) : StateScreenModel<ExtensionsScreenModel.State>(State()) {
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf()) private val currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
init { init {
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
@@ -62,14 +62,20 @@ class ExtensionsScreenModel(
it.name.contains(input, ignoreCase = true) || it.name.contains(input, ignoreCase = true) ||
it.baseUrl.contains(input, ignoreCase = true) || it.baseUrl.contains(input, ignoreCase = true) ||
it.id == input.toLongOrNull() it.id == input.toLongOrNull()
} || extension.name.contains(input, ignoreCase = true) } ||
extension.name.contains(input, ignoreCase = true)
} }
is Extension.Installed -> { is Extension.Installed -> {
extension.sources.any { extension.sources.any {
it.name.contains(input, ignoreCase = true) || it.name.contains(input, ignoreCase = true) ||
it.id == input.toLongOrNull() || it.id == input.toLongOrNull() ||
if (it is HttpSource) { it.baseUrl.contains(input, ignoreCase = true) } else false if (it is HttpSource) {
} || extension.name.contains(input, ignoreCase = true) it.baseUrl.contains(input, ignoreCase = true)
} else {
false
}
} ||
extension.name.contains(input, ignoreCase = true)
} }
is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true) is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true)
} }
@@ -80,7 +86,7 @@ class ExtensionsScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
combine( combine(
state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS),
_currentDownloads, currentDownloads,
getExtensions.subscribe(), getExtensions.subscribe(),
) { query, downloads, (_updates, _installed, _available, _untrusted) -> ) { query, downloads, (_updates, _installed, _available, _untrusted) ->
val searchQuery = query ?: "" val searchQuery = query ?: ""
@@ -103,7 +109,8 @@ class ExtensionsScreenModel(
.groupBy { it.lang } .groupBy { it.lang }
.toSortedMap(LocaleHelper.comparator) .toSortedMap(LocaleHelper.comparator)
.map { (lang, exts) -> .map { (lang, exts) ->
ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to exts.map(extensionMapper(downloads)) ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to
exts.map(extensionMapper(downloads))
} }
if (languagesWithExtensions.isNotEmpty()) { if (languagesWithExtensions.isNotEmpty()) {
itemsGroups.putAll(languagesWithExtensions) itemsGroups.putAll(languagesWithExtensions)
@@ -165,11 +172,11 @@ class ExtensionsScreenModel(
} }
private fun addDownloadState(extension: Extension, installStep: InstallStep) { private fun addDownloadState(extension: Extension, installStep: InstallStep) {
_currentDownloads.update { it + Pair(extension.pkgName, installStep) } currentDownloads.update { it + Pair(extension.pkgName, installStep) }
} }
private fun removeDownloadState(extension: Extension) { private fun removeDownloadState(extension: Extension) {
_currentDownloads.update { it - extension.pkgName } currentDownloads.update { it - extension.pkgName }
} }
private suspend fun Flow<InstallStep>.collectToInstallUpdate(extension: Extension) = private suspend fun Flow<InstallStep>.collectToInstallUpdate(extension: Extension) =
@@ -121,7 +121,11 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
) )
val onDismissRequest = { screenModel.dialog.value = null } val onDismissRequest = { screenModel.dialog.value = null }
when (@Suppress("NAME_SHADOWING") val dialog = dialog) { when
(
@Suppress("NAME_SHADOWING")
val dialog = dialog
) {
is MigrationListScreenModel.Dialog.MigrateMangaDialog -> { is MigrationListScreenModel.Dialog.MigrateMangaDialog -> {
MigrationMangaDialog( MigrationMangaDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@@ -223,7 +223,9 @@ class MigrationListScreenModel(
smartSearchEngine.normalSearch(source, mangaObj.ogTitle) smartSearchEngine.normalSearch(source, mangaObj.ogTitle)
} }
if (searchResult != null && !(searchResult.url == mangaObj.url && source.id == mangaObj.source)) { if (searchResult != null &&
!(searchResult.url == mangaObj.url && source.id == mangaObj.source)
) {
val localManga = networkToLocalManga.await(searchResult) val localManga = networkToLocalManga.await(searchResult)
val chapters = if (source is EHentai) { val chapters = if (source is EHentai) {
@@ -237,7 +239,8 @@ class MigrationListScreenModel(
} catch (e: Exception) { } catch (e: Exception) {
return@async2 null return@async2 null
} }
manga.progress.value = validSources.size to processedSources.incrementAndGet() manga.progress.value =
validSources.size to processedSources.incrementAndGet()
localManga to chapters.size localManga to chapters.size
} else { } else {
null null
@@ -314,7 +317,8 @@ class MigrationListScreenModel(
if (result == null && hideNotFound) { if (result == null && hideNotFound) {
removeManga(manga) removeManga(manga)
} }
if (result != null && showOnlyUpdates && if (result != null &&
showOnlyUpdates &&
(getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0) (getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0)
) { ) {
removeManga(manga) removeManga(manga)
@@ -363,7 +367,10 @@ class MigrationListScreenModel(
dbChapters.forEach { chapter -> dbChapters.forEach { chapter ->
if (chapter.isRecognizedNumber) { if (chapter.isRecognizedNumber) {
val prevChapter = prevMangaChapters.find { it.isRecognizedNumber && it.chapterNumber == chapter.chapterNumber } val prevChapter = prevMangaChapters.find {
it.isRecognizedNumber &&
it.chapterNumber == chapter.chapterNumber
}
if (prevChapter != null) { if (prevChapter != null) {
chapterUpdates += ChapterUpdate( chapterUpdates += ChapterUpdate(
id = chapter.id, id = chapter.id,
@@ -119,7 +119,10 @@ class SourcesScreenModel(
items = byLang items = byLang
.flatMap { .flatMap {
listOf( listOf(
SourceUiModel.Header(it.key.removePrefix(CATEGORY_KEY_PREFIX), it.value.firstOrNull()?.category != null), SourceUiModel.Header(
it.key.removePrefix(CATEGORY_KEY_PREFIX),
it.value.firstOrNull()?.category != null,
),
*it.value.map { source -> *it.value.map { source ->
SourceUiModel.Item(source) SourceUiModel.Item(source)
}.toTypedArray(), }.toTypedArray(),
@@ -48,7 +48,15 @@ class BiometricTimesScreen : Screen() {
fun showTimePicker(startTime: Duration? = null) { fun showTimePicker(startTime: Duration? = null) {
val activity = context as? MainActivity ?: return val activity = context as? MainActivity ?: return
val picker = MaterialTimePicker.Builder() val picker = MaterialTimePicker.Builder()
.setTitleText(if (startTime == null) SYMR.strings.biometric_lock_start_time.getString(context) else SYMR.strings.biometric_lock_end_time.getString(context)) .setTitleText(
if (startTime ==
null
) {
SYMR.strings.biometric_lock_start_time.getString(context)
} else {
SYMR.strings.biometric_lock_end_time.getString(context)
},
)
.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK) .setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK)
.build() .build()
picker.addOnPositiveButtonClickListener { picker.addOnPositiveButtonClickListener {
@@ -70,8 +70,8 @@ object HomeScreen : Screen() {
private val openTabEvent = Channel<Tab>() private val openTabEvent = Channel<Tab>()
private val showBottomNavEvent = Channel<Boolean>() private val showBottomNavEvent = Channel<Boolean>()
private const val TabFadeDuration = 200 private const val TAB_FADE_DURATION = 200
private const val TabNavigatorKey = "HomeTabs" private const val TAB_NAVIGATOR_KEY = "HomeTabs"
private val tabs = listOf( private val tabs = listOf(
LibraryTab, LibraryTab,
@@ -94,7 +94,7 @@ object HomeScreen : Screen() {
TabNavigator( TabNavigator(
tab = LibraryTab, tab = LibraryTab,
key = TabNavigatorKey, key = TAB_NAVIGATOR_KEY,
) { tabNavigator -> ) { tabNavigator ->
// Provide usable navigator to content screen // Provide usable navigator to content screen
CompositionLocalProvider(LocalNavigator provides navigator) { CompositionLocalProvider(LocalNavigator provides navigator) {
@@ -144,8 +144,11 @@ object HomeScreen : Screen() {
AnimatedContent( AnimatedContent(
targetState = tabNavigator.current, targetState = tabNavigator.current,
transitionSpec = { transitionSpec = {
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith materialFadeThroughIn(
materialFadeThroughOut(durationMillis = TabFadeDuration) initialScale = 1f,
durationMillis = TAB_FADE_DURATION,
) togetherWith
materialFadeThroughOut(durationMillis = TAB_FADE_DURATION)
}, },
label = "tabContent", label = "tabContent",
) { ) {
@@ -184,7 +184,11 @@ class LibraryScreenModel(
.applyGrouping(groupType) .applyGrouping(groupType)
// SY <-- // SY <--
.applyFilters(tracks, trackingFiler) .applyFilters(tracks, trackingFiler)
.applySort(tracks, trackingFiler.keys,/* SY --> */sort.takeIf { groupType != LibraryGroup.BY_DEFAULT } /* SY <-- */) .applySort(
tracks, trackingFiler.keys, /* SY --> */sort.takeIf {
groupType != LibraryGroup.BY_DEFAULT
}, /* SY <-- */
)
.mapValues { (_, value) -> .mapValues { (_, value) ->
if (searchQuery != null) { if (searchQuery != null) {
// Filter query // Filter query
@@ -278,7 +282,6 @@ class LibraryScreenModel(
/** /**
* Applies library filters to the given map of manga. * Applies library filters to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
trackingFiler: Map<Long, TriState>, trackingFiler: Map<Long, TriState>,
@@ -373,7 +376,6 @@ class LibraryScreenModel(
/** /**
* Applies library sorting to the given map of manga. * Applies library sorting to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun LibraryMap.applySort( private fun LibraryMap.applySort(
// Map<MangaId, List<Track>> // Map<MangaId, List<Track>>
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
@@ -387,7 +389,8 @@ class LibraryScreenModel(
.asSequence() .asSequence()
.mapNotNull { .mapNotNull {
val list = it.split("|") val list = it.split("|")
(list.getOrNull(0)?.toIntOrNull() ?: return@mapNotNull null) to (list.getOrNull(1) ?: return@mapNotNull null) (list.getOrNull(0)?.toIntOrNull() ?: return@mapNotNull null) to
(list.getOrNull(1) ?: return@mapNotNull null)
} }
.sortedBy { it.first } .sortedBy { it.first }
.map { it.second } .map { it.second }
@@ -453,8 +456,12 @@ class LibraryScreenModel(
} }
// SY --> // SY -->
LibrarySort.Type.TagList -> { LibrarySort.Type.TagList -> {
val manga1IndexOfTag = listOfTags.indexOfFirst { i1.libraryManga.manga.genre?.contains(it) ?: false } val manga1IndexOfTag = listOfTags.indexOfFirst {
val manga2IndexOfTag = listOfTags.indexOfFirst { i2.libraryManga.manga.genre?.contains(it) ?: false } i1.libraryManga.manga.genre?.contains(it) ?: false
}
val manga2IndexOfTag = listOfTags.indexOfFirst {
i2.libraryManga.manga.genre?.contains(it) ?: false
}
manga1IndexOfTag.compareTo(manga2IndexOfTag) manga1IndexOfTag.compareTo(manga2IndexOfTag)
} }
// SY <-- // SY <--
@@ -822,9 +829,12 @@ class LibraryScreenModel(
if (source != null) { if (source != null) {
if (source is MergedSource) { if (source is MergedSource) {
val mergedMangas = getMergedMangaById.await(manga.id) val mergedMangas = getMergedMangaById.await(manga.id)
val sources = mergedMangas.distinctBy { it.source }.map { sourceManager.getOrStub(it.source) } val sources = mergedMangas.distinctBy {
it.source
}.map { sourceManager.getOrStub(it.source) }
mergedMangas.forEach merge@{ mergedManga -> mergedMangas.forEach merge@{ mergedManga ->
val mergedSource = sources.firstOrNull { mergedManga.source == it.id } as? HttpSource ?: return@merge val mergedSource =
sources.firstOrNull { mergedManga.source == it.id } as? HttpSource ?: return@merge
downloadManager.deleteManga(mergedManga, mergedSource) downloadManager.deleteManga(mergedManga, mergedSource)
} }
} else { } else {
@@ -903,10 +913,11 @@ class LibraryScreenModel(
} else { } else {
categoryName categoryName
} }
LibraryGroup.BY_TRACK_STATUS -> TrackStatus.entries LibraryGroup.BY_TRACK_STATUS ->
.find { it.int.toLong() == category?.id } TrackStatus.entries
.let { it ?: TrackStatus.OTHER } .find { it.int.toLong() == category?.id }
.let { context.stringResource(it.res) } .let { it ?: TrackStatus.OTHER }
.let { context.stringResource(it.res) }
LibraryGroup.UNGROUPED -> context.stringResource(SYMR.strings.ungrouped) LibraryGroup.UNGROUPED -> context.stringResource(SYMR.strings.ungrouped)
else -> categoryName else -> categoryName
} }
@@ -993,49 +1004,66 @@ class LibraryScreenModel(
(manga.description?.contains(query, true) == true) || (manga.description?.contains(query, true) == true) ||
(source?.name?.contains(query, true) == true) || (source?.name?.contains(query, true) == true) ||
(sourceIdString != null && sourceIdString == query) || (sourceIdString != null && sourceIdString == query) ||
(loggedInTrackServices.isNotEmpty() && tracks != null && filterTracks(query, tracks, context)) || (
loggedInTrackServices.isNotEmpty() &&
tracks != null &&
filterTracks(query, tracks, context)
) ||
(genre.fastAny { it.contains(query, true) }) || (genre.fastAny { it.contains(query, true) }) ||
(searchTags?.fastAny { it.name.contains(query, true) } == true) || (searchTags?.fastAny { it.name.contains(query, true) } == true) ||
(searchTitles?.fastAny { it.title.contains(query, true) } == true) (searchTitles?.fastAny { it.title.contains(query, true) } == true)
} }
is Namespace -> { is Namespace -> {
searchTags != null && searchTags.fastAny { searchTags != null &&
val tag = queryComponent.tag searchTags.fastAny {
(it.namespace.equals(queryComponent.namespace, true) && tag?.run { it.name.contains(tag.asQuery(), true) } == true) || val tag = queryComponent.tag
(tag == null && it.namespace.equals(queryComponent.namespace, true)) (
} it.namespace.equals(queryComponent.namespace, true) &&
tag?.run { it.name.contains(tag.asQuery(), true) } == true
) ||
(tag == null && it.namespace.equals(queryComponent.namespace, true))
}
} }
else -> true else -> true
} }
true -> when (queryComponent) { true -> when (queryComponent) {
is Text -> { is Text -> {
val query = queryComponent.asQuery() val query = queryComponent.asQuery()
query.isBlank() || ( query.isBlank() ||
(!manga.title.contains(query, true)) && (
(manga.author?.contains(query, true) != true) && (!manga.title.contains(query, true)) &&
(manga.artist?.contains(query, true) != true) && (manga.author?.contains(query, true) != true) &&
(manga.description?.contains(query, true) != true) && (manga.artist?.contains(query, true) != true) &&
(source?.name?.contains(query, true) != true) && (manga.description?.contains(query, true) != true) &&
(sourceIdString != null && sourceIdString != query) && (source?.name?.contains(query, true) != true) &&
(loggedInTrackServices.isEmpty() || tracks == null || !filterTracks(query, tracks, context)) && (sourceIdString != null && sourceIdString != query) &&
(!genre.fastAny { it.contains(query, true) }) && (
(searchTags?.fastAny { it.name.contains(query, true) } != true) && loggedInTrackServices.isEmpty() ||
(searchTitles?.fastAny { it.title.contains(query, true) } != true) tracks == null ||
) !filterTracks(query, tracks, context)
) &&
(!genre.fastAny { it.contains(query, true) }) &&
(searchTags?.fastAny { it.name.contains(query, true) } != true) &&
(searchTitles?.fastAny { it.title.contains(query, true) } != true)
)
} }
is Namespace -> { is Namespace -> {
val searchedTag = queryComponent.tag?.asQuery() val searchedTag = queryComponent.tag?.asQuery()
searchTags == null || (queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) || searchTags.fastAll { mangaTag -> searchTags == null ||
if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) { (queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) ||
!mangaTag.name.contains(searchedTag, true) searchTags.fastAll { mangaTag ->
} else if (searchedTag.isNullOrBlank()) { if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) {
mangaTag.namespace == null || !mangaTag.namespace.equals(queryComponent.namespace, true) !mangaTag.name.contains(searchedTag, true)
} else if (mangaTag.namespace.isNullOrBlank()) { } else if (searchedTag.isNullOrBlank()) {
true mangaTag.namespace == null ||
} else { !mangaTag.namespace.equals(queryComponent.namespace, true)
!mangaTag.name.contains(searchedTag, true) || !mangaTag.namespace.equals(queryComponent.namespace, true) } else if (mangaTag.namespace.isNullOrBlank()) {
true
} else {
!mangaTag.name.contains(searchedTag, true) ||
!mangaTag.namespace.equals(queryComponent.namespace, true)
}
} }
}
} }
else -> true else -> true
} }

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