Compare commits

..

No commits in common. "master" and "1.9.4" have entirely different histories.

1807 changed files with 60811 additions and 113601 deletions

View File

@ -1,40 +1,7 @@
root = true
[*]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{xml,sq,sqm}]
indent_size = 4
# noinspection EditorConfigKeyCorrectness
[*.{kt,kts}] [*.{kt,kts}]
indent_size = 4 indent_size=4
max_line_length = 120 insert_final_newline=true
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_name_count_to_use_star_import = 2147483647 ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_class-signature = disabled
ktlint_standard_discouraged-comment-location = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_function-signature = disabled
# SY
ktlint_standard_filename = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_function-naming = disabled
ktlint_standard_property-naming = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_string-template-indent = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_max-line-length = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_value-argument-comment = disabled
ktlint_standard_value-parameter-comment = disabled

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
ko_fi: inorichi

34
.github/ISSUE_TEMPLATE.md vendored Executable file
View File

@ -0,0 +1,34 @@
**PLEASE READ THIS**
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v1.9.4)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## Device information
* Tachiyomi version: ?
* Android version: ?
* Device: ?
## Steps to reproduce
1. First step
2. Second step
## Issue/Request
?
## Other details
Additional details and attachments.
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.

View File

@ -1,8 +1,11 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: ❌ Help with Extensions - name: ⚠️ Extension/source issue
url: https://mihon.app/docs/faq/browse/extensions url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
about: For extension-related questions/issues about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
- name: 🖥️ Mihon website - name: 📦 Tachiyomi extensions
url: https://mihon.app/ url: https://tachiyomi.org/extensions
about: List of all available extensions with download links
- name: 🖥️ Tachiyomi website
url: https://tachiyomi.org/help/
about: Guides, troubleshooting, and answers to common questions about: Guides, troubleshooting, and answers to common questions

View File

@ -1,5 +1,5 @@
name: 🐞 Issue report name: 🐞 Issue report
description: Report an issue in TachiyomiSY description: Report an issue in Tachiyomi
labels: [Bug] labels: [Bug]
body: body:
@ -43,17 +43,17 @@ body:
attributes: attributes:
label: Crash logs label: Crash logs
description: | description: |
If you're experiencing crashes, if possible, go to the app's **More → Settings → Advanced** page, press **Dump crash logs** and share the crash logs here. If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
placeholder: | placeholder: |
You can upload the crash log file as an attachment, or paste the crash logs in plain text if needed. You can paste the crash logs in plain text or upload it as an attachment.
- type: input - type: input
id: tachiyomisy-version id: tachiyomi-version
attributes: attributes:
label: TachiyomiSY version label: Tachiyomi version
description: You can find your TachiyomiSY version in **More → About**. description: You can find your Tachiyomi version in **More → About**.
placeholder: | placeholder: |
Example: "1.12.0" Example: "1.9.4"
validations: validations:
required: true required: true
@ -63,7 +63,7 @@ body:
label: Android version label: Android version
description: You can find this somewhere in your Android settings. description: You can find this somewhere in your Android settings.
placeholder: | placeholder: |
Example: "Android 14" Example: "Android 11"
validations: validations:
required: true required: true
@ -73,7 +73,7 @@ body:
label: Device label: Device
description: List your device and model. description: List your device and model.
placeholder: | placeholder: |
Example: "Google Pixel 8" Example: "Google Pixel 5"
validations: validations:
required: true required: true
@ -90,15 +90,19 @@ body:
label: Acknowledgements label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this. description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options: options:
- label: This is a TachiyomiSY specific issue that does not happen in [Tachiyomi Preview](https://github.com/tachiyomiorg/tachiyomi/).
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). - label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true required: true
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**. - label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have updated the app to version **[1.9.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true required: true
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions. - label: I have updated all installed extensions.
required: true
- label: I will fill out all of the requested information in this form.
required: true required: true

View File

@ -1,5 +1,5 @@
name: ⭐ Feature request name: ⭐ Feature request
description: Suggest a feature to improve TachiyomiSY description: Suggest a feature to improve Tachiyomi
labels: [Feature request] labels: [Feature request]
body: body:
@ -7,7 +7,7 @@ body:
id: feature-description id: feature-description
attributes: attributes:
label: Describe your suggested feature label: Describe your suggested feature
description: How can TachiyomiSY be improved? description: How can Tachiyomi be improved?
placeholder: | placeholder: |
Example: Example:
"It should work like this..." "It should work like this..."
@ -31,7 +31,9 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**. - label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have updated the app to version **[1.9.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 KiB

After

Width:  |  Height:  |  Size: 489 KiB

View File

@ -1,7 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["Dependencies"],
"includePaths": [".github/workflows/*", "gradle/sy.versions.toml"],
"semanticCommits": "disabled"
}

View File

@ -6,28 +6,39 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: check_wrapper:
name: Build app name: Validate Gradle Wrapper
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up JDK - name: Validate Gradle Wrapper
uses: actions/setup-java@v4 uses: gradle/wrapper-validation-action@v1
build:
name: Build app
needs: check_wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with: with:
java-version: 17 java-version: 11
distribution: temurin distribution: adopt
- name: Set up gradle
uses: gradle/actions/setup-gradle@v4
- name: Build app - name: Build app
run: ./gradlew spotlessCheck assembleDevDebug uses: gradle/gradle-command-action@v2
with:
arguments: assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: TachiyomiSY-${{ github.sha }}.apk name: TachiyomiSY-${{ github.sha }}.apk
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk path: app/build/outputs/apk/dev/debug/app-dev-debug.apk

View File

@ -15,45 +15,30 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Setup Android SDK - name: Validate Gradle Wrapper
run: | uses: gradle/wrapper-validation-action@v1
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
- name: Set up JDK - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: 17 java-version: 11
distribution: temurin distribution: adopt
- name: Set up gradle # SY <--
uses: gradle/actions/setup-gradle@v4
# SY -->
- name: Write google-services.json - name: Write google-services.json
uses: DamianReeves/write-file-action@v1.3 uses: DamianReeves/write-file-action@v1.2
with: with:
path: app/google-services.json path: app/google-services.json
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }} contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
write-mode: overwrite write-mode: overwrite
# SY -->
- name: Write client_secrets.json - name: Build app and run unit tests
uses: DamianReeves/write-file-action@v1.3 uses: gradle/gradle-command-action@v2
with: with:
path: app/src/main/assets/client_secrets.json arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest --stacktrace
contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
write-mode: overwrite
# SY <--
- name: Check code format
run: ./gradlew spotlessCheck
- name: Build app
run: ./gradlew assembleStandardRelease
- name: Run unit tests
run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1
@ -63,8 +48,6 @@ jobs:
alias: ${{ secrets.ALIAS }} alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: '35.0.1'
- name: Clean up build artifacts - name: Clean up build artifacts
run: | run: |
@ -74,24 +57,24 @@ jobs:
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
- name: Create release - name: Create release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ github.run_number }} tag_name: ${{ github.run_number }}
name: TachiyomiSY name: TachiyomiSY
@ -107,8 +90,6 @@ jobs:
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }} | | armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }} |
| x86 | ${{ env.APK_X86_SHA }} | | x86 | ${{ env.APK_X86_SHA }} |
| x86_64 | ${{ env.APK_X86_64_SHA }} | | x86_64 | ${{ env.APK_X86_64_SHA }} |
## If you are unsure which version to choose then go with TachiyomiSY.apk
files: | files: |
TachiyomiSY.apk TachiyomiSY.apk
TachiyomiSY-arm64-v8a.apk TachiyomiSY-arm64-v8a.apk

View File

@ -3,30 +3,28 @@ name: Remote Dispatch Action Initiator
on: on:
push: push:
branches: branches:
- 'preview' - 'master'
jobs: jobs:
trigger_preview_build: trigger_preview_build:
name: Trigger preview build name: Trigger preview build
if: ${{ github.ref == 'refs/heads/master' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up JDK - name: Validate Gradle Wrapper
uses: actions/setup-java@v4 uses: gradle/wrapper-validation-action@v1
with:
java-version: 17
distribution: temurin
- name: Set up gradle - name: TAG - Bump version and push tag
uses: gradle/actions/setup-gradle@v4 uses: anothrNick/github-tag-action@1.39.0
env:
- name: Create Tag GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | WITH_V: true
git tag "preview-${{ github.run_number }}" RELEASE_BRANCHES: master
git push origin "preview-${{ github.run_number }}" DEFAULT_BUMP: patch
- name: PING - Dispatch initiating repository event - name: PING - Dispatch initiating repository event
run: | run: |
@ -34,6 +32,3 @@ jobs:
-H 'Accept: application/vnd.github.everest-preview+json' \ -H 'Accept: application/vnd.github.everest-preview+json' \
-u ${{ secrets.ACCESS_TOKEN }} \ -u ${{ secrets.ACCESS_TOKEN }} \
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}' --data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
- name: Run unit tests
run: ./gradlew testDebugUnitTest testDevDebugUnitTest

View File

@ -11,11 +11,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Moderate issues - name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v2.6.1 uses: tachiyomiorg/issue-moderator-action@v1
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
duplicate-label: Duplicate
auto-close-rules: | auto-close-rules: |
[ [
{ {
@ -33,19 +31,5 @@ jobs:
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$", "regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
"ignoreCase": true, "ignoreCase": true,
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi" "message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
},
{
"type": "both",
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
"ignoreCase": true,
"labels": ["Cloudflare protected"],
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
},
{
"type": "both",
"regex": "^.*(myanimelist|mal).*$",
"ignoreCase": true,
"message": "For issues with linking MyAnimeList, please follow these steps:\n1. Update Mihon to version 0.16.4 or newer\n2. Change your default User-Agent (`More → Settings → Advanced → Default user agent string`)\n3. Close and restart Mihon\n4. Attempt to link MyAnimeList again\n\nIf you had MyAnimeList linked before, try to unlink it first before trying these steps."
} }
] ]
auto-close-ignore-label: do-not-autoclose

19
.github/workflows/lock.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Lock threads
on:
# Daily
schedule:
- cron: '0 0 * * *'
# Manual trigger
workflow_dispatch:
inputs:
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-inactive-days: '2'
pr-inactive-days: '2'

View File

@ -1,26 +0,0 @@
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
});
}

37
.gitignore vendored
View File

@ -1,24 +1,23 @@
# Build files
.gradle .gradle
.kotlin /local.properties
build /.idea/workspace.xml
# IDE files
*.iml
.idea/*
!.idea/icon.png
/captures
# Configuration files
local.properties
# macOS specific files
.DS_Store .DS_Store
.idea/
*iml
*.iml
/mainframer
/.mainframer
# SY ignores # Built files
google-services.json */build
/app/src/main/assets/client_secrets.json /build
*.apk *.apk
app/**/output.json
# Custom ignores # Unnecessary file
/keys *.swp
TODO.md
CHANGELOG.md
/captures
build.sh

BIN
.idea/icon.png generated

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community moderators responsible for enforcement at reported to the community moderators responsible for enforcement at
the [Mihon Discord server](https://discord.gg/mihon). the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.
All community moderators are obligated to respect the privacy and security of the All community moderators are obligated to respect the privacy and security of the

View File

@ -1,4 +1,4 @@
Looking to report an issue/bug or make a feature request? Please refer to the [README file](/README.md#issues-feature-requests-and-contributing). 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).
--- ---
@ -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/jobobby04/TachiyomiSY/issues), please comment on it so others are aware. If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/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
@ -26,39 +26,25 @@ Before you start, please note that the ability to use following technologies is
## Getting help ## Getting help
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing. - Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
# Translations # Translations
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details. Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details.
# Forks # Forks
Forks are allowed so long as they abide by [the project's LICENSE](/LICENSE). Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/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](/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt) - 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)
- To avoid installation conflicts: - To avoid installation conflicts:
- Change the `applicationId` in [`build.gradle.kts`](/app/build.gradle.kts) - Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/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
### Supporting Cloud Sync - Google Drive Implementation - 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
1. Go to [Google Cloud Console](https://console.cloud.google.com)
2. Create a new project
3. Go to API & Services -> Library -> Google Drive API and click enable
4. Go to API & Services -> Oauth consent screen
5. Create it, fill in the app name, user support email, and developer contact information
6. In the next screen, click add or remove scopes, and add the `.../auth/drive.appdata` and `.../auth/drive.file` scopes
7. Don't add any test users and go back to the dashboard
8. Click publish
9. Go to API & Services -> Credentials
10. Click Create credentials -> Oauth client ID
11. Select Android, give it a name, and set `eu.kanade.google.oauth` as the package name
12. To get the SHA-1 key, run `keytool -printcert -jarfile app-standard-universal-release.apk` on your apk, and copy the listed SHA-1
13. Expand advanced settings, and enable Custom URL scheme
14. After that just download the json, name it to `client_secrets.json` and put it in `app/src/main/assets/`

View File

@ -1,20 +1,20 @@
| Preview Builds | Release Builds | Mihon Support Server | | Preview Builds | Release Builds | Tachiyomi Support Server |
|-------|----------|----------| |-------|----------|----------|
| [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/latest) | [![Discord](https://img.shields.io/discord/1195734228319617024.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/mihon) | | [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/latest) | [![Discord](https://img.shields.io/discord/349436576037732353.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/tachiyomi) |
# ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY # ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY
Mihon is a free and open source manga reader for Android 6.0 and above. This version of Mihon, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko. Tachiyomi is a free and open source manga reader for Android 6.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
![screenshots of app](./.github/readme-images/screens.png) ![screenshots of app](./.github/readme-images/screens.png)
## Features ## Features
Features of Mihon(original) include: Features of Tachiyomi(original) include:
* Online reading from a variety of sources * Online reading from a variety of sources
* Local reading of downloaded content * Local reading of downloaded content
* A configurable reader with multiple viewers, reading directions and other settings. * A configurable reader with multiple viewers, reading directions and other settings.
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support * Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
* Categories to organize your library * Categories to organize your library
* Light and dark themes * Light and dark themes
* Schedule updating your library for new chapters * Schedule updating your library for new chapters
@ -42,6 +42,7 @@ Features of TachiyomiSY include:
* Page preload customization * Page preload customization
* Customize image cache size * Customize image cache size
* Batch import of custom sources and featured extensions * Batch import of custom sources and featured extensions
* Automatic CAPTCHA solving
* Advanced source settings page, searching, enable/disable all * Advanced source settings page, searching, enable/disable all
* Click tag for local search, long click tag for global search * Click tag for local search, long click tag for global search
* Merge multiple of the same manga from different sources * Merge multiple of the same manga from different sources
@ -67,23 +68,14 @@ Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/re
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases). If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases).
## Translation
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/mihon/tachiyomisy/)
<details><summary>Translation Progress</summary>
<a href="https://hosted.weblate.org/engage/mihon/">
<img src="https://hosted.weblate.org/widgets/mihon/-/tachiyomisy/multi-auto.svg" alt="Translation status" />
</a>
</details>
## Issues, Feature Requests and Contributing ## Issues, Feature Requests and Contributing
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not. Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
<details><summary>Issues</summary> <details><summary>Issues</summary>
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).** 1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), 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/349436576037732353.svg)](https://discord.gg/tachiyomi)
</details> </details>
@ -91,13 +83,15 @@ Please make sure to read the full guidelines. Your issue may be closed without w
* Include version (More → About → Version) * Include version (More → About → Version)
* If not latest, try updating, it may have already been solved * If not latest, try updating, it may have already been solved
* Preview version is equal to the number of commits as seen on the main page * Preview version is equal to the number of commits as seen in the main page
* Include steps to reproduce (if not obvious from description) * Include steps to reproduce (if not obvious from description)
* Include screenshot (if needed) * Include screenshot (if needed)
* If it could be device-dependent, try reproducing on another device (if possible) * If it could be device-dependent, try reproducing on another device (if possible)
* Don't group unrelated requests into one issue * Don't group unrelated requests into one issue
Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose) to submit a bug. DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
</details> </details>
@ -106,7 +100,7 @@ Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does" * Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
* Include screenshot (if needed) * Include screenshot (if needed)
Source requests are not accepted. Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
</details> </details>
<details><summary>Contributing</summary> <details><summary>Contributing</summary>
@ -121,5 +115,5 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
## FAQ ## FAQ
[See our website.](https://mihon.app/) [See our website.](https://tachiyomi.org/)
You can also reach out to us on [Discord](https://discord.gg/mihon). You can also reach out to us on [Discord](https://discord.gg/tachiyomi).

6
app/.gitignore vendored Executable file
View File

@ -0,0 +1,6 @@
/build
*iml
*.iml
custom.gradle
google-services.json
output.json

View File

@ -1,46 +1,41 @@
@file:Suppress("ChromeOsAbiSupport") import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jmailen.gradle.kotlinter.tasks.LintTask
import mihon.buildlogic.getBuildTime
import mihon.buildlogic.getCommitCount
import mihon.buildlogic.getGitSha
plugins { plugins {
id("mihon.android.application") id("com.android.application")
id("mihon.android.application.compose") id("com.mikepenz.aboutlibraries.plugin")
kotlin("android")
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
kotlin("plugin.serialization") kotlin("plugin.serialization")
// id("com.github.zellius.shortcut-helper") //id("com.github.zellius.shortcut-helper")
alias(libs.plugins.aboutLibraries)
id("com.github.ben-manes.versions") id("com.github.ben-manes.versions")
} }
if (gradle.startParameter.taskRequests.toString().contains("Standard")) { if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
pluginManager.apply { apply<com.google.gms.googleservices.GoogleServicesPlugin>()
apply(libs.plugins.google.services.get().pluginId) // Firebase Crashlytics
apply(libs.plugins.firebase.crashlytics.get().pluginId) apply(plugin = "com.google.firebase.crashlytics")
}
} }
// shortcutHelper.setFilePath("./shortcuts.xml") //shortcutHelper.setFilePath("./shortcuts.xml")
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android { android {
namespace = "eu.kanade.tachiyomi" namespace = "eu.kanade.tachiyomi"
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 51
versionCode = 75 versionName = "1.9.4"
versionName = "1.12.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"") buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
buildConfigField("boolean", "INCLUDE_UPDATER", "false") buildConfigField("boolean", "INCLUDE_UPDATER", "false")
ndk { ndk {
abiFilters += supportedAbis abiFilters += SUPPORTED_ABIS
} }
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@ -49,7 +44,7 @@ android {
abi { abi {
isEnable = true isEnable = true
reset() reset()
include(*supportedAbis.toTypedArray()) include(*SUPPORTED_ABIS.toTypedArray())
isUniversalApk = true isUniversalApk = true
} }
} }
@ -62,8 +57,8 @@ android {
} }
create("releaseTest") { create("releaseTest") {
applicationIdSuffix = ".rt" applicationIdSuffix = ".rt"
// isMinifyEnabled = true //isMinifyEnabled = true
// isShrinkResources = true //isShrinkResources = true
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")) setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
matchingFallbacks.add("release") matchingFallbacks.add("release")
} }
@ -71,8 +66,6 @@ android {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")) setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
} }
create("benchmark") { create("benchmark") {
initWith(getByName("release")) initWith(getByName("release"))
@ -80,7 +73,6 @@ android {
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
matchingFallbacks.add("release") matchingFallbacks.add("release")
isDebuggable = false isDebuggable = false
isProfileable = true
versionNameSuffix = "-benchmark" versionNameSuffix = "-benchmark"
applicationIdSuffix = ".benchmark" applicationIdSuffix = ".benchmark"
} }
@ -101,25 +93,22 @@ android {
dimension = "default" dimension = "default"
} }
create("dev") { create("dev") {
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
dimension = "default" dimension = "default"
} }
} }
packaging { packagingOptions {
resources.excludes.addAll( resources.excludes.addAll(listOf(
listOf( "META-INF/DEPENDENCIES",
"kotlin-tooling-metadata.json", "LICENSE.txt",
"META-INF/DEPENDENCIES", "META-INF/LICENSE",
"LICENSE.txt", "META-INF/LICENSE.txt",
"META-INF/LICENSE", "META-INF/README.md",
"META-INF/**/LICENSE.txt", "META-INF/NOTICE",
"META-INF/*.properties", "META-INF/*.kotlin_module",
"META-INF/**/*.properties", ))
"META-INF/README.md",
"META-INF/NOTICE",
"META-INF/*.version",
),
)
} }
dependenciesInfo { dependenciesInfo {
@ -128,7 +117,7 @@ android {
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
buildConfig = true compose = true
// Disable some unused things // Disable some unused things
aidl = false aidl = false
@ -140,63 +129,44 @@ android {
abortOnError = false abortOnError = false
checkReleaseBuilds = false checkReleaseBuilds = false
} }
}
kotlin { composeOptions {
compilerOptions { kotlinCompilerExtensionVersion = compose.versions.compiler.get()
freeCompilerArgs.addAll(
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
} }
} }
dependencies { dependencies {
implementation(projects.i18n) implementation(project(":i18n"))
// SY --> implementation(project(":core"))
implementation(projects.i18nSy) implementation(project(":source-api"))
// SY <-- implementation(project(":data"))
implementation(projects.core.common) implementation(project(":domain"))
implementation(projects.coreMetadata) implementation(project(":presentation-core"))
implementation(projects.sourceApi) implementation(project(":presentation-widget"))
implementation(projects.sourceLocal)
implementation(projects.data)
implementation(projects.domain)
implementation(projects.presentationCore)
implementation(projects.presentationWidget)
// Compose // Compose
implementation(platform(compose.bom))
implementation(compose.activity) implementation(compose.activity)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material3.core) implementation(compose.material3.core)
implementation(compose.material.core)
implementation(compose.material.icons) implementation(compose.material.icons)
implementation(compose.animation) implementation(compose.animation)
implementation(compose.animation.graphics) implementation(compose.animation.graphics)
debugImplementation(compose.ui.tooling) implementation(compose.ui.tooling)
implementation(compose.ui.tooling.preview)
implementation(compose.ui.util) implementation(compose.ui.util)
implementation(compose.accompanist.webview)
implementation(androidx.interpolator) implementation(compose.accompanist.flowlayout)
implementation(compose.accompanist.permissions)
implementation(compose.accompanist.themeadapter)
implementation(compose.accompanist.systemuicontroller)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite) implementation(libs.bundles.sqlite)
// SY -->
implementation(sylibs.sqlcipher)
// SY <--
implementation(kotlinx.reflect) implementation(kotlinx.reflect)
implementation(kotlinx.immutables)
implementation(platform(kotlinx.coroutines.bom)) implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines) implementation(kotlinx.bundles.coroutines)
@ -206,6 +176,7 @@ dependencies {
implementation(androidx.appcompat) implementation(androidx.appcompat)
implementation(androidx.biometricktx) implementation(androidx.biometricktx)
implementation(androidx.constraintlayout) implementation(androidx.constraintlayout)
implementation(androidx.coordinatorlayout)
implementation(androidx.corektx) implementation(androidx.corektx)
implementation(androidx.splashscreen) implementation(androidx.splashscreen)
implementation(androidx.recyclerview) implementation(androidx.recyclerview)
@ -215,17 +186,20 @@ dependencies {
implementation(androidx.bundles.lifecycle) implementation(androidx.bundles.lifecycle)
// Job scheduling // Job scheduling
implementation(androidx.workmanager) implementation(androidx.bundles.workmanager)
// RxJava // RX
implementation(libs.rxjava) implementation(libs.bundles.reactivex)
implementation(libs.flowreactivenetwork)
// Networking // Network client
implementation(libs.bundles.okhttp) implementation(libs.bundles.okhttp)
implementation(libs.okio) implementation(libs.okio)
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
// Data serialization (JSON, protobuf, xml) // TLS 1.3 support for Android < 10
implementation(libs.conscrypt.android)
// Data serialization (JSON, protobuf)
implementation(kotlinx.bundles.serialization) implementation(kotlinx.bundles.serialization)
// HTML parser // HTML parser
@ -234,67 +208,64 @@ dependencies {
// Disk // Disk
implementation(libs.disklrucache) implementation(libs.disklrucache)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.junrar)
// Preferences // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)
// Dependency injection // Dependency injection
implementation(libs.injekt) implementation(libs.injekt.core)
// Image loading // Image loading
implementation(platform(libs.coil.bom))
implementation(libs.bundles.coil) implementation(libs.bundles.coil)
implementation(libs.subsamplingscaleimageview) { implementation(libs.subsamplingscaleimageview) {
exclude(module = "image-decoder") exclude(module = "image-decoder")
} }
implementation(libs.image.decoder) implementation(libs.image.decoder)
// Sort
implementation(libs.natural.comparator)
// UI libraries // UI libraries
implementation(libs.material) implementation(libs.material)
implementation(libs.flexible.adapter.core) implementation(libs.flexible.adapter.core)
implementation(libs.flexible.adapter.ui)
implementation(libs.photoview) implementation(libs.photoview)
implementation(libs.directionalviewpager) { implementation(libs.directionalviewpager) {
exclude(group = "androidx.viewpager", module = "viewpager") exclude(group = "androidx.viewpager", module = "viewpager")
} }
implementation(libs.insetter) implementation(libs.insetter)
implementation(libs.richeditor.compose) implementation(libs.bundles.richtext)
implementation(libs.aboutLibraries.compose) implementation(libs.aboutLibraries.compose)
implementation(libs.cascade)
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion) implementation(libs.wheelpicker)
implementation(libs.swipe) implementation(libs.materialmotion.core)
implementation(libs.compose.webview)
implementation(libs.compose.grid)
implementation(libs.reorderable)
implementation(libs.bundles.markdown)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)
// Crash reports/analytics // Crash reports/analytics
// "standardImplementation"(platform(libs.firebase.bom)) // implementation(libs.acra.http)
// "standardImplementation"(libs.firebase.analytics) // "standardImplementation"(libs.firebase.analytics)
// "standardImplementation"(libs.firebase.crashlytics)
// Shizuku // Shizuku
implementation(libs.bundles.shizuku) implementation(libs.bundles.shizuku)
// Tests // Tests
testImplementation(libs.bundles.test) testImplementation(libs.junit)
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android) // debugImplementation(libs.leakcanary.android)
implementation(libs.leakcanary.plumber) implementation(libs.leakcanary.plumber)
testImplementation(kotlinx.coroutines.test)
// SY --> // SY -->
// Text distance (EH) // Text distance (EH)
implementation(sylibs.simularity) implementation (sylibs.simularity)
// Firebase (EH) // Firebase (EH)
implementation(platform(libs.firebase.bom)) implementation(sylibs.firebase.analytics)
implementation(libs.firebase.analytics) implementation(sylibs.firebase.crashlytics.ktx)
implementation(libs.firebase.crashlytics)
// Better logging (EH) // Better logging (EH)
implementation(sylibs.xlog) implementation(sylibs.xlog)
@ -302,20 +273,15 @@ dependencies {
// RatingBar (SY) // RatingBar (SY)
implementation(sylibs.ratingbar) implementation(sylibs.ratingbar)
implementation(sylibs.composeRatingbar) implementation(sylibs.composeRatingbar)
// Google drive
implementation(sylibs.google.api.services.drive)
implementation(sylibs.google.api.client.oauth)
// Koin
implementation(sylibs.koin.core)
implementation(sylibs.koin.android)
// ZXing Android Embedded
implementation(sylibs.zxing.android.embedded)
} }
androidComponents { androidComponents {
beforeVariants { variantBuilder ->
// Disables standardBenchmark
if (variantBuilder.buildType == "benchmark") {
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
}
}
onVariants(selector().withFlavor("default" to "standard")) { onVariants(selector().withFlavor("default" to "standard")) {
// Only excluding in standard flavor because this breaks // Only excluding in standard flavor because this breaks
// Layout Inspector's Compose tree // Layout Inspector's Compose tree
@ -323,6 +289,46 @@ androidComponents {
} }
} }
tasks {
withType<LintTask>().configureEach {
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
}
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf(
"-opt-in=coil.annotation.ExperimentalCoilApi",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
kotlinOptions.freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.buildDir.absolutePath + "/compose_metrics"
)
kotlinOptions.freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
project.buildDir.absolutePath + "/compose_metrics"
)
}
}
}
buildscript { buildscript {
dependencies { dependencies {
classpath(kotlinx.gradle) classpath(kotlinx.gradle)

View File

@ -1,6 +1,5 @@
-allowaccessmodification -allowaccessmodification
-dontusemixedcaseclassnames -dontusemixedcaseclassnames
-ignorewarnings
-verbose -verbose
-keepattributes *Annotation* -keepattributes *Annotation*
@ -15,7 +14,7 @@
} }
-keepclassmembers class * implements android.os.Parcelable { -keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR; public static final ** CREATOR;
} }
-keep class androidx.annotation.Keep -keep class androidx.annotation.Keep

View File

@ -1,19 +1,14 @@
-dontobfuscate -dontobfuscate
-keep,allowoptimization class eu.kanade.**
-keep,allowoptimization class tachiyomi.**
-keep,allowoptimization class mihon.**
# Keep common dependencies used in extensions # Keep common dependencies used in extensions
-keep,allowoptimization class androidx.preference.** { public protected *; } -keep,allowoptimization class androidx.preference.** { public protected *; }
-keep,allowoptimization class kotlin.** { public protected *; } -keep,allowoptimization class kotlin.** { public protected *; }
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; } -keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
-keep,allowoptimization class kotlinx.serialization.** { public protected *; } -keep,allowoptimization class kotlinx.serialization.** { public protected *; }
-keep,allowoptimization class kotlin.time.** { public protected *; }
-keep,allowoptimization class okhttp3.** { public protected *; } -keep,allowoptimization class okhttp3.** { public protected *; }
-keep,allowoptimization class okio.** { public protected *; } -keep,allowoptimization class okio.** { public protected *; }
-keep,allowoptimization class org.jsoup.** { public protected *; }
-keep,allowoptimization class rx.** { public protected *; } -keep,allowoptimization class rx.** { public protected *; }
-keep,allowoptimization class org.jsoup.** { public protected *; }
-keep,allowoptimization class app.cash.quickjs.** { public protected *; } -keep,allowoptimization class app.cash.quickjs.** { public protected *; }
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; } -keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
@ -47,13 +42,9 @@
-dontnote rx.internal.util.PlatformDependent -dontnote rx.internal.util.PlatformDependent
##---------------End: proguard configuration for RxJava 1.x ---------- ##---------------End: proguard configuration for RxJava 1.x ----------
##---------------Begin: proguard configuration for okhttp ----------
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
##---------------End: proguard configuration for okhttp ----------
##---------------Begin: proguard configuration for kotlinx.serialization ---------- ##---------------Begin: proguard configuration for kotlinx.serialization ----------
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.** # core serialization annotations -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** { -keepclassmembers class kotlinx.serialization.json.** {
@ -127,25 +118,6 @@
# XmlUtil # XmlUtil
-keep public enum nl.adaptivity.xmlutil.EventType { *; } -keep public enum nl.adaptivity.xmlutil.EventType { *; }
# Firebase
-keep class com.google.firebase.installations.** { *; }
-keep interface com.google.firebase.installations.** { *; }
# Google Drive
-keep class com.google.api.services.** { *; }
# Google OAuth
-keep class com.google.api.client.** { *; }
# SY -->
# SqlCipher
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteCustomFunction { *; }
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteConnection { *; }
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteGlobal { *; }
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteDebug { *; }
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteDebug$* { *; }
# SY <--
# Design library # Design library
-dontwarn com.google.android.material.** -dontwarn com.google.android.material.**
-keep class com.google.android.material.** { *; } -keep class com.google.android.material.** { *; }
@ -155,6 +127,13 @@
-keep class com.hippo.image.** { *; } -keep class com.hippo.image.** { *; }
-keep interface com.hippo.image.** { *; } -keep interface com.hippo.image.** { *; }
# == Nucleus
-keepclassmembers class * extends nucleus.presenter.Presenter {
<init>();
}
# TODO Changeloglib? Does it need proguard?
# === Injekt # === Injekt
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1" ## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
-keep class uy.kohesive.injekt.** { *; } -keep class uy.kohesive.injekt.** { *; }
@ -264,38 +243,3 @@
-keep class com.google.apphosting.api.ApiProxy { -keep class com.google.apphosting.api.ApiProxy {
static *** getCurrentEnvironment (...); static *** getCurrentEnvironment (...);
} }
# R8 full mode
-keepattributes Signature
-keep,allowoptimization class kotlin.coroutines.Continuation
-keep,allowoptimization class * extends uy.kohesive.injekt.api.TypeReference
-keep,allowoptimization public class io.requery.android.database.sqlite.SQLiteConnection { *; }
# Keep apache http client
-keep class org.apache.http.** { *; }
# Suggested rules
-dontwarn com.oracle.svm.core.annotate.AutomaticFeature
-dontwarn com.oracle.svm.core.annotate.Delete
-dontwarn com.oracle.svm.core.annotate.Substitute
-dontwarn com.oracle.svm.core.annotate.TargetClass
-dontwarn com.oracle.svm.core.configure.ResourcesRegistry
-dontwarn org.graalvm.nativeimage.ImageSingletons
-dontwarn org.graalvm.nativeimage.hosted.Feature$BeforeAnalysisAccess
-dontwarn org.graalvm.nativeimage.hosted.Feature
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn java.lang.Module
-dontwarn org.graalvm.nativeimage.hosted.RuntimeResourceAccess
-dontwarn org.jspecify.annotations.NullMarked
-dontwarn javax.naming.InvalidNameException
-dontwarn javax.naming.NamingException
-dontwarn javax.naming.directory.Attribute
-dontwarn javax.naming.directory.Attributes
-dontwarn javax.naming.ldap.LdapName
-dontwarn javax.naming.ldap.Rdn
-dontwarn org.ietf.jgss.GSSContext
-dontwarn org.ietf.jgss.GSSCredential
-dontwarn org.ietf.jgss.GSSException
-dontwarn org.ietf.jgss.GSSManager
-dontwarn org.ietf.jgss.GSSName
-dontwarn org.ietf.jgss.Oid

View File

@ -1,11 +0,0 @@
package mihon.core.firebase
import android.content.Context
object FirebaseConfig {
fun init(context: Context) = Unit
fun setAnalyticsEnabled(enabled: Boolean) = Unit
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
}

View File

@ -8,9 +8,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Storage --> <!-- Storage -->
<uses-permission <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<!-- For background jobs --> <!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -23,99 +21,43 @@
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<!-- To view extension packages in API 30+ --> <!-- To view extension packages in API 30+ -->
<uses-permission <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <!-- Remove permission from Firebase dependency -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
<!-- Remove unnecessary permissions from Firebase dependency -->
<uses-permission
android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"
tools:node="remove" />
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />
<uses-permission
android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION"
tools:node="remove" />
<uses-permission
android:name="android.permission.ACCESS_ADSERVICES_AD_ID"
tools:node="remove" /> tools:node="remove" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="false" android:allowBackup="false"
android:enableOnBackInvokedCallback="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true" android:largeHeap="true"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Tachiyomi"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Tachiyomi"> android:networkSecurityConfig="@xml/network_security_config">
<!-- enable profiling by macrobenchmark -->
<profileable
android:shell="true"
tools:targetApi="q" />
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/Theme.Tachiyomi.SplashScreen"> android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<!-- Deep link to add repos -->
<intent-filter android:label="@string/action_add_repo">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tachiyomi" />
<data android:host="add-repo" />
</intent-filter>
<!-- Open backup files -->
<intent-filter android:label="@string/pref_restore_backup">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<!--
Work around Android's ugly primitive PatternMatcher
implementation that can't cope with finding a . early in
the path unless it's explicitly matched.
See https://stackoverflow.com/a/31028507
-->
<data android:pathPattern=".*\\.tachibk" />
<data android:pathPattern=".*\\..*\\.tachibk" />
<data android:pathPattern=".*\\..*\\..*\\.tachibk" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.tachibk" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.tachibk" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
</intent-filter>
<!--suppress AndroidDomInspection --> <!--suppress AndroidDomInspection -->
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
@ -123,16 +65,16 @@
</activity> </activity>
<activity <activity
android:process=":error_handler"
android:name=".crash.CrashActivity" android:name=".crash.CrashActivity"
android:exported="false" android:exported="false" />
android:process=":error_handler" />
<activity <activity
android:name=".ui.deeplink.DeepLinkActivity" android:name=".ui.main.DeepLinkActivity"
android:exported="true"
android:label="@string/action_search"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.NoDisplay"
android:label="@string/action_global_search"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" /> <action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
@ -156,21 +98,20 @@
<activity <activity
android:name=".ui.reader.ReaderActivity" android:name=".ui.reader.ReaderActivity"
android:exported="false" android:launchMode="singleTask"
android:launchMode="singleTask"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.samsung.android.support.REMOTE_ACTION" /> <action android:name="com.samsung.android.support.REMOTE_ACTION" />
</intent-filter> </intent-filter>
<meta-data <meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:name="com.samsung.android.support.REMOTE_ACTION" android:resource="@xml/s_pen_actions"/>
android:resource="@xml/s_pen_actions" />
</activity> </activity>
<activity <activity
android:name=".ui.security.UnlockActivity" android:name=".ui.security.UnlockActivity"
android:exported="false" android:theme="@style/Theme.Tachiyomi"
android:theme="@style/Theme.Tachiyomi" /> android:exported="false" />
<activity <activity
android:name=".ui.webview.WebViewActivity" android:name=".ui.webview.WebViewActivity"
@ -179,30 +120,12 @@
<activity <activity
android:name=".extension.util.ExtensionInstallActivity" android:name=".extension.util.ExtensionInstallActivity"
android:exported="false" android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:exported="false" />
<activity <activity
android:name=".ui.setting.track.TrackLoginActivity" android:name=".ui.setting.track.AnilistLoginActivity"
android:exported="true" android:label="Anilist"
android:label="@string/track_activity_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mihon" />
<data android:host="anilist-auth" />
<data android:host="bangumi-auth" />
<data android:host="myanimelist-auth" />
<data android:host="shikimori-auth" />
</intent-filter>
</activity>
<activity
android:name=".ui.setting.track.GoogleDriveLoginActivity"
android:label="GoogleDrive"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -211,7 +134,53 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="eu.kanade.google.oauth" /> android:host="anilist-auth"
android:scheme="tachiyomi" />
</intent-filter>
</activity>
<activity
android:name=".ui.setting.track.MyAnimeListLoginActivity"
android:label="MyAnimeList"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myanimelist-auth"
android:scheme="tachiyomi" />
</intent-filter>
</activity>
<activity
android:name=".ui.setting.track.ShikimoriLoginActivity"
android:label="Shikimori"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="shikimori-auth"
android:scheme="tachiyomi" />
</intent-filter>
</activity>
<activity
android:name=".ui.setting.track.BangumiLoginActivity"
android:label="Bangumi"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="bangumi-auth"
android:scheme="tachiyomi" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -224,10 +193,38 @@
android:name=".data.notification.NotificationReceiver" android:name=".data.notification.NotificationReceiver"
android:exported="false" /> android:exported="false" />
<service <receiver
android:name=".extension.util.ExtensionInstallService" android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
android:enabled="@bool/glance_appwidget_available"
android:exported="false" android:exported="false"
android:foregroundServiceType="shortService" /> android:label="@string/label_recent_updates">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/updates_grid_glance_widget_info" />
</receiver>
<service
android:name=".data.library.LibraryUpdateService"
android:exported="false" />
<service
android:name=".data.download.DownloadService"
android:exported="false" />
<service
android:name=".data.updater.AppUpdateService"
android:exported="false" />
<service
android:name=".data.backup.BackupRestoreService"
android:exported="false" />
<service android:name=".extension.util.ExtensionInstallService"
android:exported="false" />
<service <service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService" android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
@ -238,11 +235,6 @@
android:value="true" /> android:value="true" />
</service> </service>
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@ -256,9 +248,9 @@
<provider <provider
android:name="rikka.shizuku.ShizukuProvider" android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku" android:authorities="${applicationId}.shizuku"
android:multiprocess="false"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:multiprocess="false"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" /> android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<meta-data <meta-data
@ -268,14 +260,6 @@
android:name="android.webkit.WebView.MetricsOptOut" android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" /> android:value="true" />
<!-- Disable for manual opt-in -->
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<!-- Disable advertising ID collection for Firebase --> <!-- Disable advertising ID collection for Firebase -->
<meta-data <meta-data
android:name="google_analytics_adid_collection_enabled" android:name="google_analytics_adid_collection_enabled"
@ -359,7 +343,7 @@
<data android:scheme="https" /> <data android:scheme="https" />
<data android:scheme="http" /> <data android:scheme="http" />
<data android:host="pururin.me" /> <data android:host="pururin.io" />
<data android:pathPattern="/gallery/..*" /> <data android:pathPattern="/gallery/..*" />
</intent-filter> </intent-filter>
@ -413,13 +397,6 @@
android:scheme="tachiyomisy" /> android:scheme="tachiyomisy" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
tools:remove="screenOrientation" />
</application> </application>
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
tools:ignore="ManifestOrder" />
</manifest> </manifest>

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
package eu.kanade.core.preference
import androidx.compose.ui.state.ToggleableState
import tachiyomi.core.common.preference.CheckboxState
fun <T> CheckboxState.TriState<T>.asToggleableState() = when (this) {
is CheckboxState.TriState.Exclude -> ToggleableState.Indeterminate
is CheckboxState.TriState.Include -> ToggleableState.On
is CheckboxState.TriState.None -> ToggleableState.Off
}

View File

@ -1,7 +1,8 @@
package tachiyomi.core.common.preference package eu.kanade.core.prefs
import androidx.compose.ui.state.ToggleableState
sealed class CheckboxState<T>(open val value: T) { sealed class CheckboxState<T>(open val value: T) {
abstract fun next(): CheckboxState<T> abstract fun next(): CheckboxState<T>
sealed class State<T>(override val value: T) : CheckboxState<T>(value) { sealed class State<T>(override val value: T) : CheckboxState<T>(value) {
@ -18,7 +19,6 @@ sealed class CheckboxState<T>(open val value: T) {
} }
} }
} }
sealed class TriState<T>(override val value: T) : CheckboxState<T>(value) { sealed class TriState<T>(override val value: T) : CheckboxState<T>(value) {
data class Include<T>(override val value: T) : TriState<T>(value) data class Include<T>(override val value: T) : TriState<T>(value)
data class Exclude<T>(override val value: T) : TriState<T>(value) data class Exclude<T>(override val value: T) : TriState<T>(value)
@ -31,6 +31,14 @@ sealed class CheckboxState<T>(open val value: T) {
is None -> Include(value) is None -> Include(value)
} }
} }
fun asState(): ToggleableState {
return when (this) {
is Exclude -> ToggleableState.Indeterminate
is Include -> ToggleableState.On
is None -> ToggleableState.Off
}
}
} }
} }

View File

@ -1,11 +1,11 @@
package eu.kanade.core.preference package eu.kanade.core.prefs
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import tachiyomi.core.common.preference.Preference import tachiyomi.core.preference.Preference
class PreferenceMutableState<T>( class PreferenceMutableState<T>(
private val preference: Preference<T>, private val preference: Preference<T>,
@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
} }
override fun component2(): (T) -> Unit { override fun component2(): (T) -> Unit {
return preference::set return { preference.set(it) }
} }
} }

View File

@ -1,41 +1,32 @@
package eu.kanade.core.util package eu.kanade.core.util
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import java.util.concurrent.ConcurrentHashMap
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
fun <T : R, R : Any> List<T>.insertSeparators( fun <T : R, R : Any> List<T>.insertSeparators(
generator: (before: T?, after: T?) -> R?, generator: (T?, T?) -> R?,
): List<R> { ): List<R> {
if (isEmpty()) return emptyList() if (isEmpty()) return emptyList()
val newList = mutableListOf<R>() val newList = mutableListOf<R>()
for (i in -1..lastIndex) { for (i in -1..lastIndex) {
val before = getOrNull(i) val before = getOrNull(i)
before?.let(newList::add) before?.let { newList.add(it) }
val after = getOrNull(i + 1) val after = getOrNull(i + 1)
val separator = generator.invoke(before, after) val separator = generator.invoke(before, after)
separator?.let(newList::add) separator?.let { newList.add(it) }
} }
return newList return newList
} }
/** /**
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element * Returns a new map containing only the key entries of [transform] that are not null.
*/ */
fun <T : R, R : Any> List<T>.insertSeparatorsReversed( inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
generator: (before: T?, after: T?) -> R?, val mutableMap = ConcurrentHashMap<R, V>()
): List<R> { forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
if (isEmpty()) return emptyList() return mutableMap
val newList = mutableListOf<R>()
for (i in size downTo 0) {
val after = getOrNull(i)
after?.let(newList::add)
val before = getOrNull(i - 1)
val separator = generator.invoke(before, after)
separator?.let(newList::add)
}
return newList.asReversed()
} }
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) { fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
@ -46,6 +37,21 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
} }
} }
/**
* Returns a list containing only elements matching the given [predicate].
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) }
val destination = ArrayList<T>()
fastForEach { if (predicate(it)) destination.add(it) }
return destination
}
/** /**
* Returns a list containing all elements not matching the given [predicate]. * Returns a list containing all elements not matching the given [predicate].
* *
@ -56,7 +62,27 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> { inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) } contract { callsInPlace(predicate) }
return fastFilter { !predicate(it) } val destination = ArrayList<T>()
fastForEach { if (!predicate(it)) destination.add(it) }
return destination
}
/**
* Returns a list containing only the non-null results of applying the
* given [transform] function to each element in the original collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
contract { callsInPlace(transform) }
val destination = ArrayList<R>()
fastForEach { element ->
transform(element)?.let { destination.add(it) }
}
return destination
} }
/** /**
@ -97,3 +123,26 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
fastForEach { if (predicate(it)) --count } fastForEach { if (predicate(it)) --count }
return count return count
} }
/**
* Returns a list containing only elements from the given collection
* having distinct keys returned by the given [selector] function.
*
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
* The elements in the resulting list are in the same order as they were in the source collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
contract { callsInPlace(selector) }
val set = HashSet<K>()
val list = ArrayList<T>()
fastForEach {
val key = selector(it)
if (set.add(key)) list.add(it)
}
return list
}

View File

@ -0,0 +1,16 @@
package eu.kanade.core.util
import android.content.Context
import eu.kanade.tachiyomi.R
import kotlin.time.Duration
fun Duration.toDurationString(context: Context, fallback: String): String {
return toComponents { days, hours, minutes, seconds, _ ->
buildList(4) {
if (days != 0L) add(context.getString(R.string.day_short, days))
if (hours != 0) add(context.getString(R.string.hour_short, hours))
if (minutes != 0 && (days == 0L || hours == 0)) add(context.getString(R.string.minute_short, minutes))
if (seconds != 0 && days == 0L && hours == 0) add(context.getString(R.string.seconds_short, seconds))
}.joinToString(" ").ifBlank { fallback }
}
}

View File

@ -0,0 +1,61 @@
package eu.kanade.core.util
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import rx.Emitter
import rx.Observable
import rx.Observer
import kotlin.coroutines.CoroutineContext
fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
val observer = object : Observer<T> {
override fun onNext(t: T) {
trySend(t)
}
override fun onError(e: Throwable) {
close(e)
}
override fun onCompleted() {
close()
}
}
val subscription = subscribe(observer)
awaitClose { subscription.unsubscribe() }
}
fun <T : Any> Flow<T>.asObservable(
context: CoroutineContext = Dispatchers.Unconfined,
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
): Observable<T> {
return Observable.create(
{ emitter ->
/*
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
* asObservable is already invoked from unconfined
*/
val job = GlobalScope.launch(context = context, start = CoroutineStart.ATOMIC) {
try {
collect { emitter.onNext(it) }
emitter.onCompleted()
} catch (e: Throwable) {
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
if (e !is CancellationException) {
emitter.onError(e)
} else {
emitter.onCompleted()
}
}
}
emitter.setCancellation { job.cancel() }
},
backpressureMode,
)
}

View File

@ -0,0 +1,45 @@
package eu.kanade.data.source
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.metadata.metadata.base.RaisedSearchMetadata
import tachiyomi.core.util.lang.awaitSingle
abstract class EHentaiPagingSource(override val source: EHentai) : SourcePagingSource(source) {
override fun getPageLoadResult(
params: LoadParams<Long>,
mangasPage: MangasPage,
): LoadResult.Page<Long, Pair<SManga, RaisedSearchMetadata?>> {
mangasPage as MetadataMangasPage
val metadata = mangasPage.mangasMetadata
return LoadResult.Page(
data = mangasPage.mangas
.mapIndexed { index, sManga -> sManga to metadata.getOrNull(index) },
prevKey = null,
nextKey = mangasPage.nextKey,
)
}
}
class EHentaiSearchPagingSource(source: EHentai, val query: String, val filters: FilterList) : EHentaiPagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
}
}
class EHentaiPopularPagingSource(source: EHentai) : EHentaiPagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchPopularManga(currentPage).awaitSingle()
}
}
class EHentaiLatestPagingSource(source: EHentai) : EHentaiPagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchLatestUpdates(currentPage).awaitSingle()
}
}

View File

@ -0,0 +1,19 @@
package eu.kanade.data.source
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import tachiyomi.domain.source.model.Source
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
Source(
source.id,
source.lang,
source.name,
supportsLatest = false,
isStub = source is SourceManager.StubSource,
)
}
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
}

View File

@ -0,0 +1,87 @@
package eu.kanade.data.source
import androidx.paging.PagingState
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.withIOContext
abstract class SourcePagingSource(
protected open val source: CatalogueSource,
) : SourcePagingSourceType() {
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */> {
val page = params.key ?: 1
val mangasPage = try {
withIOContext {
requestNextPage(page.toInt())
.takeIf { it.mangas.isNotEmpty() }
?: throw NoResultsException()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
// SY -->
return getPageLoadResult(params, mangasPage)
// SY <--
}
// SY -->
open fun getPageLoadResult(params: LoadParams<Long>, mangasPage: MangasPage): LoadResult.Page<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */> {
val page = params.key ?: 1
// SY -->
val metadata = if (mangasPage is MetadataMangasPage) {
mangasPage.mangasMetadata
} else {
emptyList()
}
// SY <--
return LoadResult.Page(
data = mangasPage.mangas
// SY -->
.mapIndexed { index, sManga -> sManga to metadata.getOrNull(index) },
// SY <--
prevKey = null,
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
)
}
// SY <--
override fun getRefreshKey(state: PagingState<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */>): Long? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey ?: anchorPage?.nextKey
}
}
}
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
}
}
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchPopularManga(currentPage).awaitSingle()
}
}
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchLatestUpdates(currentPage).awaitSingle()
}
}
class NoResultsException : Exception()

View File

@ -0,0 +1,91 @@
package eu.kanade.data.source
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.source.repository.SourceRepository
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
import tachiyomi.domain.source.model.Source
import tachiyomi.domain.source.model.SourceWithCount
class SourceRepositoryImpl(
private val sourceManager: SourceManager,
private val handler: DatabaseHandler,
) : SourceRepository {
override fun getSources(): Flow<List<Source>> {
return sourceManager.catalogueSources.map { sources ->
sources.map(catalogueSourceMapper)
}
}
override fun getOnlineSources(): Flow<List<Source>> {
return sourceManager.onlineSources.map { sources ->
sources.map(sourceMapper)
}
}
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
sourceIdsWithCount
.filterNot { it.source == LocalSource.ID /* SY --> */ || it.source == MERGED_SOURCE_ID /* SY <-- */ }
.map { (sourceId, count) ->
val source = sourceManager.getOrStub(sourceId).run {
sourceMapper(this)
}
source to count
}
}
}
override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
return sourceIdWithNonLibraryManga.map { sourceId ->
sourceId.map { (sourceId, count) ->
val source = sourceManager.getOrStub(sourceId)
SourceWithCount(sourceMapper(source), count)
}
}
}
override fun search(
sourceId: Long,
query: String,
filterList: FilterList,
): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source is EHentai) {
return EHentaiSearchPagingSource(source, query, filterList)
}
// SY <--
return SourceSearchPagingSource(source, query, filterList)
}
override fun getPopular(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source is EHentai) {
return EHentaiPopularPagingSource(source)
}
// SY <--
return SourcePopularPagingSource(source)
}
override fun getLatest(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source is EHentai) {
return EHentaiLatestPagingSource(source)
}
// SY <--
return SourceLatestPagingSource(source)
}
}

View File

@ -1,103 +1,77 @@
package eu.kanade.domain package eu.kanade.domain
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators import eu.kanade.data.source.SourceRepositoryImpl
import eu.kanade.domain.category.interactor.CreateCategoryWithName
import eu.kanade.domain.category.interactor.DeleteCategory
import eu.kanade.domain.category.interactor.RenameCategory
import eu.kanade.domain.category.interactor.ReorderCategory
import eu.kanade.domain.category.interactor.ResetCategoryFlags
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.interactor.SetSortModeForCategory
import eu.kanade.domain.category.interactor.UpdateCategory
import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.download.interactor.DeleteDownload import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.extension.interactor.GetExtensionLanguages import eu.kanade.domain.extension.interactor.GetExtensionLanguages
import eu.kanade.domain.extension.interactor.GetExtensionSources import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.extension.interactor.TrustExtension import eu.kanade.domain.history.interactor.GetNextChapters
import eu.kanade.domain.manga.interactor.GetExcludedScanlators import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.SetExcludedScanlators import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.interactor.GetLanguagesWithSources import eu.kanade.domain.source.interactor.GetLanguagesWithSources
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.track.interactor.AddTracks import eu.kanade.domain.source.repository.SourceRepository
import eu.kanade.domain.track.interactor.RefreshTracks import eu.kanade.domain.track.interactor.DeleteTrack
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.TrackChapter import eu.kanade.domain.track.interactor.GetTracksPerManga
import eu.kanade.tachiyomi.di.InjektModule import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.tachiyomi.di.addFactory
import eu.kanade.tachiyomi.di.addSingletonFactory
import mihon.data.repository.ExtensionRepoRepositoryImpl
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extensionrepo.service.ExtensionRepoService
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl import tachiyomi.data.history.HistoryRepositoryImpl
import tachiyomi.data.manga.MangaRepositoryImpl import tachiyomi.data.manga.MangaRepositoryImpl
import tachiyomi.data.release.ReleaseServiceImpl import tachiyomi.data.source.SourceDataRepositoryImpl
import tachiyomi.data.source.SourceRepositoryImpl
import tachiyomi.data.source.StubSourceRepositoryImpl
import tachiyomi.data.track.TrackRepositoryImpl import tachiyomi.data.track.TrackRepositoryImpl
import tachiyomi.data.updates.UpdatesRepositoryImpl import tachiyomi.data.updates.UpdatesRepositoryImpl
import tachiyomi.domain.category.interactor.CreateCategoryWithName
import tachiyomi.domain.category.interactor.DeleteCategory
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.RenameCategory
import tachiyomi.domain.category.interactor.ReorderCategory
import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.interactor.SetDisplayMode
import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.interactor.SetSortModeForCategory
import tachiyomi.domain.category.interactor.UpdateCategory
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.history.interactor.GetHistory import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.history.interactor.GetNextChapters
import tachiyomi.domain.history.interactor.GetTotalReadDuration import tachiyomi.domain.history.interactor.GetTotalReadDuration
import tachiyomi.domain.history.interactor.RemoveHistory import tachiyomi.domain.history.interactor.RemoveHistory
import tachiyomi.domain.history.interactor.UpsertHistory import tachiyomi.domain.history.interactor.UpsertHistory
import tachiyomi.domain.history.repository.HistoryRepository import tachiyomi.domain.history.repository.HistoryRepository
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.release.interactor.GetApplicationRelease import tachiyomi.domain.source.repository.SourceDataRepository
import tachiyomi.domain.release.service.ReleaseService
import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
import tachiyomi.domain.source.repository.SourceRepository
import tachiyomi.domain.source.repository.StubSourceRepository
import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.GetTracksPerManga
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.repository.TrackRepository import tachiyomi.domain.track.repository.TrackRepository
import tachiyomi.domain.updates.interactor.GetUpdates import tachiyomi.domain.updates.interactor.GetUpdates
import tachiyomi.domain.updates.repository.UpdatesRepository import tachiyomi.domain.updates.repository.UpdatesRepository
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class DomainModule : InjektModule { class DomainModule : InjektModule {
@ -105,58 +79,43 @@ class DomainModule : InjektModule {
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) } addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
addFactory { GetCategories(get()) } addFactory { GetCategories(get()) }
addFactory { ResetCategoryFlags(get(), get()) } addFactory { ResetCategoryFlags(get(), get()) }
addFactory { SetDisplayMode(get()) } addFactory { SetDisplayModeForCategory(get(), get()) }
addFactory { SetSortModeForCategory(get(), get()) } addFactory { SetSortModeForCategory(get(), get()) }
addFactory { CreateCategoryWithName(get(), get()) } addFactory { CreateCategoryWithName(get(), get()) }
addFactory { RenameCategory(get()) } addFactory { RenameCategory(get()) }
addFactory { ReorderCategory(get()) } addFactory { ReorderCategory(get()) }
addFactory { UpdateCategory(get()) } addFactory { UpdateCategory(get()) }
addFactory { DeleteCategory(get(), get(), get()) } addFactory { DeleteCategory(get()) }
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) } addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) } addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) } addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) } addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaByUrlAndSourceId(get()) }
addFactory { GetManga(get()) } addFactory { GetManga(get()) }
addFactory { GetNextChapters(get(), get(), get(), get()) } addFactory { GetNextChapters(get(), get(), get(), get()) }
addFactory { GetUpcomingManga(get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { FetchInterval(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) } addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) } addFactory { SetMangaViewerFlags(get()) }
addFactory { NetworkToLocalManga(get()) } addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get(), get()) } addFactory { UpdateManga(get()) }
addFactory { UpdateMangaNotes(get()) }
addFactory { SetMangaCategories(get()) } addFactory { SetMangaCategories(get()) }
addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) }
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get(), get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) } addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { AddTracks(get(), get(), get(), get()) }
addFactory { RefreshTracks(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) } addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(get(), get()) } addFactory { GetTracksPerManga(get()) }
addFactory { GetTracks(get()) } addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) } addFactory { InsertTrack(get()) }
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) } addFactory { GetChapter(get()) }
addFactory { GetChaptersByMangaId(get()) } addFactory { GetChapterByMangaId(get()) }
addFactory { GetChapterByUrlAndMangaId(get()) }
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get(), get()) } addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() } addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get(), get()) } addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
addFactory { GetAvailableScanlators(get()) } addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) } addFactory { GetHistory(get()) }
@ -174,7 +133,7 @@ class DomainModule : InjektModule {
addFactory { GetUpdates(get()) } addFactory { GetUpdates(get()) }
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) } addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
addSingletonFactory<StubSourceRepository> { StubSourceRepositoryImpl(get()) } addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
addFactory { GetEnabledSources(get(), get()) } addFactory { GetEnabledSources(get(), get()) }
addFactory { GetLanguagesWithSources(get(), get()) } addFactory { GetLanguagesWithSources(get(), get()) }
addFactory { GetRemoteManga(get()) } addFactory { GetRemoteManga(get()) }
@ -184,17 +143,5 @@ class DomainModule : InjektModule {
addFactory { ToggleLanguage(get()) } addFactory { ToggleLanguage(get()) }
addFactory { ToggleSource(get()) } addFactory { ToggleSource(get()) }
addFactory { ToggleSourcePin(get()) } addFactory { ToggleSourcePin(get()) }
addFactory { TrustExtension(get(), get()) }
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
addFactory { ExtensionRepoService(get(), get()) }
addFactory { GetExtensionRepo(get()) }
addFactory { GetExtensionRepoCount(get()) }
addFactory { CreateExtensionRepo(get(), get()) }
addFactory { DeleteExtensionRepo(get()) }
addFactory { ReplaceExtensionRepo(get()) }
addFactory { UpdateExtensionRepo(get(), get()) }
addFactory { ToggleIncognito(get()) }
addFactory { GetIncognitoState(get(), get(), get()) }
} }
} }

View File

@ -1,22 +1,61 @@
package eu.kanade.domain package eu.kanade.domain
import android.app.Application import android.app.Application
import eu.kanade.domain.chapter.interactor.DeleteChapters
import eu.kanade.domain.chapter.interactor.GetChapterByUrl
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.history.interactor.GetHistoryByMangaId
import eu.kanade.domain.manga.interactor.CreateSortTag import eu.kanade.domain.manga.interactor.CreateSortTag
import eu.kanade.domain.manga.interactor.DeleteByMergeId
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
import eu.kanade.domain.manga.interactor.DeleteMangaById
import eu.kanade.domain.manga.interactor.DeleteMergeById
import eu.kanade.domain.manga.interactor.DeleteSortTag import eu.kanade.domain.manga.interactor.DeleteSortTag
import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFavoriteEntries
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetIdsOfFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaBySource
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.GetPagePreviews import eu.kanade.domain.manga.interactor.GetPagePreviews
import eu.kanade.domain.manga.interactor.GetSearchMetadata
import eu.kanade.domain.manga.interactor.GetSearchTags
import eu.kanade.domain.manga.interactor.GetSearchTitles
import eu.kanade.domain.manga.interactor.GetSortTag import eu.kanade.domain.manga.interactor.GetSortTag
import eu.kanade.domain.manga.interactor.InsertFavoriteEntries
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.InsertMergedReference
import eu.kanade.domain.manga.interactor.ReorderSortTag import eu.kanade.domain.manga.interactor.ReorderSortTag
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateMergedSettings
import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId
import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal
import eu.kanade.domain.source.interactor.CreateSourceCategory import eu.kanade.domain.source.interactor.CreateSourceCategory
import eu.kanade.domain.source.interactor.CreateSourceRepo
import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById
import eu.kanade.domain.source.interactor.DeleteSavedSearchById
import eu.kanade.domain.source.interactor.DeleteSourceCategory import eu.kanade.domain.source.interactor.DeleteSourceCategory
import eu.kanade.domain.source.interactor.DeleteSourceRepos
import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId
import eu.kanade.domain.source.interactor.GetFeedSavedSearchGlobal
import eu.kanade.domain.source.interactor.GetSavedSearchById
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceId
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed
import eu.kanade.domain.source.interactor.GetSavedSearchGlobalFeed
import eu.kanade.domain.source.interactor.GetShowLatest import eu.kanade.domain.source.interactor.GetShowLatest
import eu.kanade.domain.source.interactor.GetSourceCategories import eu.kanade.domain.source.interactor.GetSourceCategories
import eu.kanade.domain.source.interactor.GetSourceRepos
import eu.kanade.domain.source.interactor.InsertFeedSavedSearch
import eu.kanade.domain.source.interactor.InsertSavedSearch
import eu.kanade.domain.source.interactor.RenameSourceCategory import eu.kanade.domain.source.interactor.RenameSourceCategory
import eu.kanade.domain.source.interactor.SetSourceCategories import eu.kanade.domain.source.interactor.SetSourceCategories
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
import eu.kanade.tachiyomi.di.InjektModule
import eu.kanade.tachiyomi.di.addFactory
import eu.kanade.tachiyomi.di.addSingletonFactory
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import exh.search.SearchEngine import exh.search.SearchEngine
import tachiyomi.data.manga.CustomMangaRepositoryImpl import tachiyomi.data.manga.CustomMangaRepositoryImpl
@ -25,55 +64,19 @@ import tachiyomi.data.manga.MangaMergeRepositoryImpl
import tachiyomi.data.manga.MangaMetadataRepositoryImpl import tachiyomi.data.manga.MangaMetadataRepositoryImpl
import tachiyomi.data.source.FeedSavedSearchRepositoryImpl import tachiyomi.data.source.FeedSavedSearchRepositoryImpl
import tachiyomi.data.source.SavedSearchRepositoryImpl import tachiyomi.data.source.SavedSearchRepositoryImpl
import tachiyomi.domain.chapter.interactor.DeleteChapters
import tachiyomi.domain.chapter.interactor.GetChapterByUrl
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
import tachiyomi.domain.manga.interactor.DeleteByMergeId
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
import tachiyomi.domain.manga.interactor.DeleteMangaById
import tachiyomi.domain.manga.interactor.DeleteMergeById
import tachiyomi.domain.manga.interactor.GetAllManga
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import tachiyomi.domain.manga.interactor.GetFavoriteEntries
import tachiyomi.domain.manga.interactor.GetFlatMetadataById
import tachiyomi.domain.manga.interactor.GetIdsOfFavoriteMangaWithMetadata
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaBySource
import tachiyomi.domain.manga.interactor.GetMergedManga
import tachiyomi.domain.manga.interactor.GetMergedMangaById
import tachiyomi.domain.manga.interactor.GetMergedMangaForDownloading
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
import tachiyomi.domain.manga.interactor.GetReadMangaNotInLibraryView
import tachiyomi.domain.manga.interactor.GetSearchMetadata
import tachiyomi.domain.manga.interactor.GetSearchTags
import tachiyomi.domain.manga.interactor.GetSearchTitles
import tachiyomi.domain.manga.interactor.InsertFavoriteEntries
import tachiyomi.domain.manga.interactor.InsertFavoriteEntryAlternative
import tachiyomi.domain.manga.interactor.InsertFlatMetadata
import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
import tachiyomi.domain.manga.repository.CustomMangaRepository import tachiyomi.domain.manga.repository.CustomMangaRepository
import tachiyomi.domain.manga.repository.FavoritesEntryRepository import tachiyomi.domain.manga.repository.FavoritesEntryRepository
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository
import tachiyomi.domain.source.interactor.CountFeedSavedSearchBySourceId
import tachiyomi.domain.source.interactor.CountFeedSavedSearchGlobal
import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById
import tachiyomi.domain.source.interactor.DeleteSavedSearchById
import tachiyomi.domain.source.interactor.GetFeedSavedSearchBySourceId
import tachiyomi.domain.source.interactor.GetFeedSavedSearchGlobal
import tachiyomi.domain.source.interactor.GetSavedSearchById
import tachiyomi.domain.source.interactor.GetSavedSearchBySourceId
import tachiyomi.domain.source.interactor.GetSavedSearchBySourceIdFeed
import tachiyomi.domain.source.interactor.GetSavedSearchGlobalFeed
import tachiyomi.domain.source.interactor.InsertFeedSavedSearch
import tachiyomi.domain.source.interactor.InsertSavedSearch
import tachiyomi.domain.source.repository.FeedSavedSearchRepository import tachiyomi.domain.source.repository.FeedSavedSearchRepository
import tachiyomi.domain.source.repository.SavedSearchRepository import tachiyomi.domain.source.repository.SavedSearchRepository
import tachiyomi.domain.track.interactor.IsTrackUnfollowed import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
import xyz.nulldev.ts.api.http.serializer.FilterSerializer import xyz.nulldev.ts.api.http.serializer.FilterSerializer
class SYDomainModule : InjektModule { class SYDomainModule : InjektModule {
@ -82,12 +85,17 @@ class SYDomainModule : InjektModule {
addFactory { GetShowLatest(get()) } addFactory { GetShowLatest(get()) }
addFactory { ToggleExcludeFromDataSaver(get()) } addFactory { ToggleExcludeFromDataSaver(get()) }
addFactory { SetSourceCategories(get()) } addFactory { SetSourceCategories(get()) }
addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetAllManga(get()) } addFactory { GetAllManga(get()) }
addFactory { GetMangaBySource(get()) } addFactory { GetMangaBySource(get()) }
addFactory { DeleteChapters(get()) } addFactory { DeleteChapters(get()) }
addFactory { DeleteMangaById(get()) } addFactory { DeleteMangaById(get()) }
addFactory { FilterSerializer() } addFactory { FilterSerializer() }
addFactory { GetHistoryByMangaId(get()) }
addFactory { GetChapterByUrl(get()) } addFactory { GetChapterByUrl(get()) }
addFactory { CreateSourceRepo(get()) }
addFactory { DeleteSourceRepos(get()) }
addFactory { GetSourceRepos(get()) }
addFactory { GetSourceCategories(get()) } addFactory { GetSourceCategories(get()) }
addFactory { CreateSourceCategory(get()) } addFactory { CreateSourceCategory(get()) }
addFactory { RenameSourceCategory(get(), get()) } addFactory { RenameSourceCategory(get(), get()) }
@ -98,8 +106,6 @@ class SYDomainModule : InjektModule {
addFactory { ReorderSortTag(get(), get()) } addFactory { ReorderSortTag(get(), get()) }
addFactory { GetPagePreviews(get(), get()) } addFactory { GetPagePreviews(get(), get()) }
addFactory { SearchEngine() } addFactory { SearchEngine() }
addFactory { IsTrackUnfollowed() }
addFactory { GetReadMangaNotInLibraryView(get()) }
// Required for [MetadataSource] // Required for [MetadataSource]
addFactory<MetadataSource.GetMangaId> { GetManga(get()) } addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
@ -119,7 +125,7 @@ class SYDomainModule : InjektModule {
addFactory { GetMergedManga(get()) } addFactory { GetMergedManga(get()) }
addFactory { GetMergedMangaById(get()) } addFactory { GetMergedMangaById(get()) }
addFactory { GetMergedReferencesById(get()) } addFactory { GetMergedReferencesById(get()) }
addFactory { GetMergedChaptersByMangaId(get(), get()) } addFactory { GetMergedChapterByMangaId(get(), get()) }
addFactory { InsertMergedReference(get()) } addFactory { InsertMergedReference(get()) }
addFactory { UpdateMergedSettings(get()) } addFactory { UpdateMergedSettings(get()) }
addFactory { DeleteByMergeId(get()) } addFactory { DeleteByMergeId(get()) }
@ -130,7 +136,6 @@ class SYDomainModule : InjektModule {
addFactory { GetFavoriteEntries(get()) } addFactory { GetFavoriteEntries(get()) }
addFactory { InsertFavoriteEntries(get()) } addFactory { InsertFavoriteEntries(get()) }
addFactory { DeleteFavoriteEntries(get()) } addFactory { DeleteFavoriteEntries(get()) }
addFactory { InsertFavoriteEntryAlternative(get()) }
addSingletonFactory<SavedSearchRepository> { SavedSearchRepositoryImpl(get()) } addSingletonFactory<SavedSearchRepository> { SavedSearchRepositoryImpl(get()) }
addFactory { GetSavedSearchById(get()) } addFactory { GetSavedSearchById(get()) }

View File

@ -0,0 +1,89 @@
package eu.kanade.domain
import tachiyomi.core.preference.PreferenceStore
class UnsortedPreferences(
private val preferenceStore: PreferenceStore,
) {
// SY -->
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "")
fun migrationSources() = preferenceStore.getString("migrate_sources", "")
fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false)
fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false)
fun skipPreMigration() = preferenceStore.getBoolean("skip_pre_migration", false)
fun hideNotFoundMigration() = preferenceStore.getBoolean("hide_not_found_migration", false)
fun isHentaiEnabled() = preferenceStore.getBoolean("eh_is_hentai_enabled", true)
fun enableExhentai() = preferenceStore.getBoolean("enable_exhentai", false)
fun imageQuality() = preferenceStore.getString("ehentai_quality", "auto")
fun useHentaiAtHome() = preferenceStore.getInt("eh_enable_hah", 0)
fun useJapaneseTitle() = preferenceStore.getBoolean("use_jp_title", false)
fun exhUseOriginalImages() = preferenceStore.getBoolean("eh_useOrigImages", false)
fun ehTagFilterValue() = preferenceStore.getInt("eh_tag_filtering_value", 0)
fun ehTagWatchingValue() = preferenceStore.getInt("eh_tag_watching_value", 0)
// EH Cookies
fun memberIdVal() = preferenceStore.getString("eh_ipb_member_id", "")
fun passHashVal() = preferenceStore.getString("eh_ipb_pass_hash", "")
fun igneousVal() = preferenceStore.getString("eh_igneous", "")
fun ehSettingsProfile() = preferenceStore.getInt("eh_ehSettingsProfile", -1)
fun exhSettingsProfile() = preferenceStore.getInt("eh_exhSettingsProfile", -1)
fun exhSettingsKey() = preferenceStore.getString("eh_settingsKey", "")
fun exhSessionCookie() = preferenceStore.getString("eh_sessionCookie", "")
fun exhHathPerksCookies() = preferenceStore.getString("eh_hathPerksCookie", "")
fun exhShowSyncIntro() = preferenceStore.getBoolean("eh_show_sync_intro", true)
fun exhReadOnlySync() = preferenceStore.getBoolean("eh_sync_read_only", false)
fun exhLenientSync() = preferenceStore.getBoolean("eh_lenient_sync", false)
fun exhShowSettingsUploadWarning() = preferenceStore.getBoolean("eh_showSettingsUploadWarning2", true)
fun logLevel() = preferenceStore.getInt("eh_log_level", 0)
fun exhAutoUpdateFrequency() = preferenceStore.getInt("eh_auto_update_frequency", 1)
fun exhAutoUpdateRequirements() = preferenceStore.getStringSet("eh_auto_update_restrictions", emptySet())
fun exhAutoUpdateStats() = preferenceStore.getString("eh_auto_update_stats", "")
fun exhWatchedListDefaultState() = preferenceStore.getBoolean("eh_watched_list_default_state", false)
fun exhSettingsLanguages() = preferenceStore.getString(
"eh_settings_languages",
"false*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false",
)
fun exhEnabledCategories() = preferenceStore.getString(
"eh_enabled_categories",
"false,false,false,false,false,false,false,false,false,false",
)
fun enhancedEHentaiView() = preferenceStore.getBoolean("enhanced_e_hentai_view", true)
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
fun mangadexSyncToLibraryIndexes() = preferenceStore.getStringSet("pref_mangadex_sync_to_library_indexes", emptySet())
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
}

View File

@ -0,0 +1,16 @@
package eu.kanade.domain.backup.service
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.provider.FolderProvider
class BackupPreferences(
private val folderProvider: FolderProvider,
private val preferenceStore: PreferenceStore,
) {
fun backupsDirectory() = preferenceStore.getString("backup_directory", folderProvider.path())
fun numberOfBackups() = preferenceStore.getInt("backup_slots", 2)
fun backupInterval() = preferenceStore.getInt("backup_interval", 12)
}

View File

@ -1,38 +1,24 @@
package eu.kanade.domain.base package eu.kanade.domain.base
import android.content.Context import android.content.Context
import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.core.common.preference.Preference import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR
class BasePreferences( class BasePreferences(
val context: Context, val context: Context,
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
) { ) {
fun downloadedOnly() = preferenceStore.getBoolean( fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", false)
Preference.appStateKey("pref_downloaded_only"),
false,
)
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false) fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore) fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false) fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
LEGACY(MR.strings.ext_installer_legacy, true),
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller, true),
SHIZUKU(MR.strings.ext_installer_shizuku, false),
PRIVATE(MR.strings.ext_installer_private, false),
}
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false)
} }

View File

@ -1,13 +1,13 @@
package eu.kanade.domain.base package eu.kanade.domain.base
import android.content.Context import android.content.Context
import eu.kanade.domain.base.BasePreferences.ExtensionInstaller import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
import eu.kanade.tachiyomi.util.system.isShizukuInstalled import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import tachiyomi.core.common.preference.Preference import tachiyomi.core.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum import tachiyomi.core.preference.getEnum
class ExtensionInstallerPreference( class ExtensionInstallerPreference(
private val context: Context, private val context: Context,
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
override fun key() = "extension_installer" override fun key() = "extension_installer"
val entries get() = ExtensionInstaller.entries.run { val entries get() = ExtensionInstaller.values().run {
if (context.hasMiuiPackageInstaller) { if (context.hasMiuiPackageInstaller) {
filter { it != ExtensionInstaller.PACKAGEINSTALLER } filter { it != ExtensionInstaller.PACKAGEINSTALLER }
} else { } else {

View File

@ -1,11 +1,11 @@
package tachiyomi.domain.category.interactor package eu.kanade.domain.category.interactor
import eu.kanade.domain.library.service.LibraryPreferences
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.library.service.LibraryPreferences
class CreateCategoryWithName( class CreateCategoryWithName(
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
@ -14,8 +14,10 @@ class CreateCategoryWithName(
private val initialFlags: Long private val initialFlags: Long
get() { get() {
val sort = preferences.sortingMode().get() val sort = preferences.librarySortingMode().get()
return sort.type.flag or sort.direction.flag return preferences.libraryDisplayMode().get().flag or
sort.type.flag or
sort.direction.flag
} }
suspend fun await(name: String): Result = withNonCancellableContext { suspend fun await(name: String): Result = withNonCancellableContext {
@ -37,11 +39,11 @@ class CreateCategoryWithName(
} }
} }
sealed interface Result { sealed class Result {
// SY --> // SY -->
data class Success(val category: Category) : Result data class Success(val category: Category) : Result()
// SY <-- // SY <--
data class InternalError(val error: Throwable) : Result data class InternalError(val error: Throwable) : Result()
} }
} }

View File

@ -0,0 +1,42 @@
package eu.kanade.domain.category.interactor
import logcat.LogPriority
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository
class DeleteCategory(
private val categoryRepository: CategoryRepository,
) {
suspend fun await(categoryId: Long) = withNonCancellableContext {
try {
categoryRepository.delete(categoryId)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
return@withNonCancellableContext Result.InternalError(e)
}
val categories = categoryRepository.getAll()
val updates = categories.mapIndexed { index, category ->
CategoryUpdate(
id = category.id,
order = index.toLong(),
)
}
try {
categoryRepository.updatePartial(updates)
Result.Success
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.InternalError(e)
}
}
sealed class Result {
object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View File

@ -1,8 +1,8 @@
package tachiyomi.domain.category.interactor package eu.kanade.domain.category.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
@ -28,8 +28,8 @@ class RenameCategory(
suspend fun await(category: Category, name: String) = await(category.id, name) suspend fun await(category: Category, name: String) = await(category.id, name)
sealed interface Result { sealed class Result {
data object Success : Result object Success : Result()
data class InternalError(val error: Throwable) : Result data class InternalError(val error: Throwable) : Result()
} }
} }

View File

@ -1,20 +1,28 @@
package tachiyomi.domain.category.interactor package eu.kanade.domain.category.interactor
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
import java.util.Collections
class ReorderCategory( class ReorderCategory(
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
) { ) {
private val mutex = Mutex() private val mutex = Mutex()
suspend fun await(category: Category, newIndex: Int) = withNonCancellableContext { suspend fun moveUp(category: Category): Result =
await(category, MoveTo.UP)
suspend fun moveDown(category: Category): Result =
await(category, MoveTo.DOWN)
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
mutex.withLock { mutex.withLock {
val categories = categoryRepository.getAll() val categories = categoryRepository.getAll()
.filterNot(Category::isSystemCategory) .filterNot(Category::isSystemCategory)
@ -25,8 +33,13 @@ class ReorderCategory(
return@withNonCancellableContext Result.Unchanged return@withNonCancellableContext Result.Unchanged
} }
val newPosition = when (moveTo) {
MoveTo.UP -> currentIndex - 1
MoveTo.DOWN -> currentIndex + 1
}.toInt()
try { try {
categories.add(newIndex, categories.removeAt(currentIndex)) Collections.swap(categories, currentIndex, newPosition)
val updates = categories.mapIndexed { index, category -> val updates = categories.mapIndexed { index, category ->
CategoryUpdate( CategoryUpdate(
@ -44,9 +57,14 @@ class ReorderCategory(
} }
} }
sealed interface Result { sealed class Result {
data object Success : Result object Success : Result()
data object Unchanged : Result object Unchanged : Result()
data class InternalError(val error: Throwable) : Result data class InternalError(val error: Throwable) : Result()
}
private enum class MoveTo {
UP,
DOWN,
} }
} }

View File

@ -0,0 +1,17 @@
package eu.kanade.domain.category.interactor
import eu.kanade.domain.library.service.LibraryPreferences
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.library.model.plus
class ResetCategoryFlags(
private val preferences: LibraryPreferences,
private val categoryRepository: CategoryRepository,
) {
suspend fun await() {
val display = preferences.libraryDisplayMode().get()
val sort = preferences.librarySortingMode().get()
categoryRepository.updateAllFlags(display + sort.type + sort.direction)
}
}

View File

@ -0,0 +1,41 @@
package eu.kanade.domain.category.interactor
import eu.kanade.domain.library.model.LibraryGroup
import eu.kanade.domain.library.service.LibraryPreferences
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.plus
class SetDisplayModeForCategory(
private val preferences: LibraryPreferences,
private val categoryRepository: CategoryRepository,
) {
suspend fun await(categoryId: Long, display: LibraryDisplayMode) {
// SY -->
if (preferences.groupLibraryBy().get() != LibraryGroup.BY_DEFAULT) {
preferences.libraryDisplayMode().set(display)
return
}
// SY <--
val category = categoryRepository.get(categoryId) ?: return
val flags = category.flags + display
if (preferences.categorizedDisplaySettings().get()) {
categoryRepository.updatePartial(
CategoryUpdate(
id = category.id,
flags = flags,
),
)
} else {
preferences.libraryDisplayMode().set(display)
categoryRepository.updateAllFlags(flags)
}
}
suspend fun await(category: Category, display: LibraryDisplayMode) {
await(category.id, display)
}
}

View File

@ -1,7 +1,7 @@
package tachiyomi.domain.category.interactor package eu.kanade.domain.category.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
class SetMangaCategories( class SetMangaCategories(

View File

@ -0,0 +1,41 @@
package eu.kanade.domain.category.interactor
import eu.kanade.domain.library.model.LibraryGroup
import eu.kanade.domain.library.service.LibraryPreferences
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.plus
class SetSortModeForCategory(
private val preferences: LibraryPreferences,
private val categoryRepository: CategoryRepository,
) {
suspend fun await(categoryId: Long, type: LibrarySort.Type, direction: LibrarySort.Direction) {
// SY -->
if (preferences.groupLibraryBy().get() != LibraryGroup.BY_DEFAULT) {
preferences.librarySortingMode().set(LibrarySort(type, direction))
return
}
// SY <--
val category = categoryRepository.get(categoryId) ?: return
val flags = category.flags + type + direction
if (preferences.categorizedDisplaySettings().get()) {
categoryRepository.updatePartial(
CategoryUpdate(
id = category.id,
flags = flags,
),
)
} else {
preferences.librarySortingMode().set(LibrarySort(type, direction))
categoryRepository.updateAllFlags(flags)
}
}
suspend fun await(category: Category, type: LibrarySort.Type, direction: LibrarySort.Direction) {
await(category.id, type, direction)
}
}

View File

@ -1,6 +1,6 @@
package tachiyomi.domain.category.interactor package eu.kanade.domain.category.interactor
import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.model.CategoryUpdate
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
@ -17,8 +17,8 @@ class UpdateCategory(
} }
} }
sealed interface Result { sealed class Result {
data object Success : Result object Success : Result()
data class Error(val error: Exception) : Result data class Error(val error: Exception) : Result()
} }
} }

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository

View File

@ -1,36 +0,0 @@
package eu.kanade.domain.chapter.interactor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.domain.chapter.repository.ChapterRepository
class GetAvailableScanlators(
private val repository: ChapterRepository,
) {
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
return mapNotNull { it.ifBlank { null } }.toSet()
}
suspend fun await(mangaId: Long): Set<String> {
return repository.getScanlatorsByMangaId(mangaId)
.cleanupAvailableScanlators()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
.map { it.cleanupAvailableScanlators() }
}
// SY -->
suspend fun awaitMerge(mangaId: Long): Set<String> {
return repository.getScanlatorsByMergeId(mangaId)
.cleanupAvailableScanlators()
}
fun subscribeMerge(mangaId: Long): Flow<Set<String>> {
return repository.getScanlatorsByMergeIdAsFlow(mangaId)
.map { it.cleanupAvailableScanlators() }
}
// SY <--
}

View File

@ -1,7 +1,7 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository

View File

@ -1,17 +1,17 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
class GetChaptersByMangaId( class GetChapterByMangaId(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun await(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter> { suspend fun await(mangaId: Long): List<Chapter> {
return try { return try {
chapterRepository.getChapterByMangaId(mangaId, applyScanlatorFilter) chapterRepository.getChapterByMangaId(mangaId)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
emptyList() emptyList()

View File

@ -1,7 +1,7 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository

View File

@ -1,40 +1,28 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
import tachiyomi.domain.manga.model.MergedMangaReference import tachiyomi.domain.manga.model.MergedMangaReference
class GetMergedChaptersByMangaId( class GetMergedChapterByMangaId(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
private val getMergedReferencesById: GetMergedReferencesById, private val getMergedReferencesById: GetMergedReferencesById,
) { ) {
suspend fun await( suspend fun await(mangaId: Long, dedupe: Boolean = true): List<Chapter> {
mangaId: Long, return transformMergedChapters(getMergedReferencesById.await(mangaId), getFromDatabase(mangaId), dedupe)
dedupe: Boolean = true,
applyScanlatorFilter: Boolean = false,
): List<Chapter> {
return transformMergedChapters(
getMergedReferencesById.await(mangaId),
getFromDatabase(mangaId, applyScanlatorFilter),
dedupe,
)
} }
suspend fun subscribe( suspend fun subscribe(mangaId: Long, dedupe: Boolean = true): Flow<List<Chapter>> {
mangaId: Long,
dedupe: Boolean = true,
applyScanlatorFilter: Boolean = false,
): Flow<List<Chapter>> {
return try { return try {
chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId, applyScanlatorFilter) chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId)
.combine(getMergedReferencesById.subscribe(mangaId)) { chapters, references -> .combine(getMergedReferencesById.subscribe(mangaId)) { chapters, references ->
transformMergedChapters(references, chapters, dedupe) transformMergedChapters(references, chapters, dedupe)
} }
@ -44,30 +32,20 @@ class GetMergedChaptersByMangaId(
} }
} }
private suspend fun getFromDatabase( private suspend fun getFromDatabase(mangaId: Long): List<Chapter> {
mangaId: Long,
applyScanlatorFilter: Boolean = false,
): List<Chapter> {
return try { return try {
chapterRepository.getMergedChapterByMangaId(mangaId, applyScanlatorFilter) chapterRepository.getMergedChapterByMangaId(mangaId)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
emptyList() emptyList()
} }
} }
private fun transformMergedChapters( fun transformMergedChapters(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>, dedupe: Boolean): List<Chapter> {
mangaReferences: List<MergedMangaReference>,
chapterList: List<Chapter>,
dedupe: Boolean,
): List<Chapter> {
return if (dedupe) dedupeChapterList(mangaReferences, chapterList) else chapterList return if (dedupe) dedupeChapterList(mangaReferences, chapterList) else chapterList
} }
private fun dedupeChapterList( private fun dedupeChapterList(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
mangaReferences: List<MergedMangaReference>,
chapterList: List<Chapter>,
): List<Chapter> {
return when (mangaReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }?.chapterSortMode) { return when (mangaReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }?.chapterSortMode) {
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE, MergedMangaReference.CHAPTER_SORT_NONE -> chapterList MergedMangaReference.CHAPTER_SORT_NO_DEDUPE, MergedMangaReference.CHAPTER_SORT_NONE -> chapterList
MergedMangaReference.CHAPTER_SORT_PRIORITY -> dedupeByPriority(mangaReferences, chapterList) MergedMangaReference.CHAPTER_SORT_PRIORITY -> dedupeByPriority(mangaReferences, chapterList)
@ -93,10 +71,7 @@ class GetMergedChaptersByMangaId(
return chapterList.maxByOrNull { it.chapterNumber }?.mangaId return chapterList.maxByOrNull { it.chapterNumber }?.mangaId
} }
private fun dedupeByPriority( private fun dedupeByPriority(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
mangaReferences: List<MergedMangaReference>,
chapterList: List<Chapter>,
): List<Chapter> {
val sortedChapterList = mutableListOf<Chapter>() val sortedChapterList = mutableListOf<Chapter>()
var existingChapterIndex: Int var existingChapterIndex: Int
@ -111,11 +86,8 @@ class GetMergedChaptersByMangaId(
val oldChapterIndex = existingChapterIndex val oldChapterIndex = existingChapterIndex
if (chapter.isRecognizedNumber) { if (chapter.isRecognizedNumber) {
existingChapterIndex = sortedChapterList.indexOfFirst { existingChapterIndex = sortedChapterList.indexOfFirst {
// check if the chapter is not already there it.isRecognizedNumber && it.chapterNumber == chapter.chapterNumber && // check if the chapter is not already there
it.isRecognizedNumber && it.mangaId != chapter.mangaId // allow multiple chapters of the same number from the same source
it.chapterNumber == chapter.chapterNumber &&
// allow multiple chapters of the same number from the same source
it.mangaId != chapter.mangaId
} }
if (existingChapterIndex == -1) { if (existingChapterIndex == -1) {
sortedChapterList.add(oldChapterIndex + 1, chapter) sortedChapterList.add(oldChapterIndex + 1, chapter)

View File

@ -1,9 +1,9 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import tachiyomi.core.common.util.lang.withNonCancellableContext import eu.kanade.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences import eu.kanade.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.interactor.GetFavorites import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
class SetMangaDefaultChapterFlags( class SetMangaDefaultChapterFlags(

View File

@ -1,15 +1,14 @@
package eu.kanade.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.download.interactor.DeleteDownload import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.download.service.DownloadPreferences
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
@ -19,7 +18,7 @@ class SetReadStatus(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
// SY --> // SY -->
private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId, private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
// SY <-- // SY <--
) { ) {
@ -78,7 +77,7 @@ class SetReadStatus(
private suspend fun awaitMerged(mangaId: Long, read: Boolean) = withNonCancellableContext f@{ private suspend fun awaitMerged(mangaId: Long, read: Boolean) = withNonCancellableContext f@{
return@f await( return@f await(
read = read, read = read,
chapters = getMergedChaptersByMangaId chapters = getMergedChapterByMangaId
.await(mangaId, dedupe = false) .await(mangaId, dedupe = false)
.toTypedArray(), .toTypedArray(),
) )
@ -91,9 +90,9 @@ class SetReadStatus(
} }
// SY <-- // SY <--
sealed interface Result { sealed class Result {
data object Success : Result object Success : Result()
data object NoChapters : Result object NoChapters : Result()
data class InternalError(val error: Throwable) : Result data class InternalError(val error: Throwable) : Result()
} }
} }

View File

@ -2,41 +2,37 @@ package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.copyFromSChapter import eu.kanade.domain.chapter.model.copyFromSChapter
import eu.kanade.domain.chapter.model.toSChapter import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.isLocal
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import tachiyomi.data.chapter.ChapterSanitizer import tachiyomi.data.chapter.ChapterSanitizer
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter
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.chapter.model.toChapterUpdate import tachiyomi.domain.chapter.model.toChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.chapter.service.ChapterRecognition
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.source.local.isLocal import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Long.max import java.lang.Long.max
import java.time.ZonedDateTime import java.util.Date
import java.util.TreeSet import java.util.TreeSet
class SyncChaptersWithSource( class SyncChaptersWithSource(
private val downloadManager: DownloadManager, private val downloadManager: DownloadManager = Injekt.get(),
private val downloadProvider: DownloadProvider, private val downloadProvider: DownloadProvider = Injekt.get(),
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository = Injekt.get(),
private val shouldUpdateDbChapter: ShouldUpdateDbChapter, private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
private val updateManga: UpdateManga, private val updateManga: UpdateManga = Injekt.get(),
private val updateChapter: UpdateChapter, private val updateChapter: UpdateChapter = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId, private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getExcludedScanlators: GetExcludedScanlators,
private val libraryPreferences: LibraryPreferences,
) { ) {
/** /**
@ -51,16 +47,11 @@ class SyncChaptersWithSource(
rawSourceChapters: List<SChapter>, rawSourceChapters: List<SChapter>,
manga: Manga, manga: Manga,
source: Source, source: Source,
manualFetch: Boolean = false,
fetchWindow: Pair<Long, Long> = Pair(0, 0),
): List<Chapter> { ): List<Chapter> {
if (rawSourceChapters.isEmpty() && !source.isLocal()) { if (rawSourceChapters.isEmpty() && !source.isLocal()) {
throw NoChaptersException() throw NoChaptersException()
} }
val now = ZonedDateTime.now()
val nowMillis = now.toInstant().toEpochMilli()
val sourceChapters = rawSourceChapters val sourceChapters = rawSourceChapters
.distinctBy { it.url } .distinctBy { it.url }
.mapIndexed { i, sChapter -> .mapIndexed { i, sChapter ->
@ -70,58 +61,58 @@ class SyncChaptersWithSource(
.copy(mangaId = manga.id, sourceOrder = i.toLong()) .copy(mangaId = manga.id, sourceOrder = i.toLong())
} }
val dbChapters = getChaptersByMangaId.await(manga.id) // Chapters from db.
val dbChapters = getChapterByMangaId.await(manga.id)
val newChapters = mutableListOf<Chapter>() // Chapters from the source not in db.
val updatedChapters = mutableListOf<Chapter>() val toAdd = mutableListOf<Chapter>()
val removedChapters = dbChapters.filterNot { dbChapter ->
// Chapters whose metadata have changed.
val toChange = mutableListOf<Chapter>()
// Chapters from the db not in source.
val toDelete = dbChapters.filterNot { dbChapter ->
sourceChapters.any { sourceChapter -> sourceChapters.any { sourceChapter ->
dbChapter.url == sourceChapter.url dbChapter.url == sourceChapter.url
} }
} }
val rightNow = Date().time
// Used to not set upload date of older chapters // Used to not set upload date of older chapters
// to a higher value than newer chapters // to a higher value than newer chapters
var maxSeenUploadDate = 0L var maxSeenUploadDate = 0L
val sManga = manga.toSManga()
for (sourceChapter in sourceChapters) { for (sourceChapter in sourceChapters) {
var chapter = sourceChapter var chapter = sourceChapter
// Update metadata from source if necessary. // Update metadata from source if necessary.
if (source is HttpSource) { if (source is HttpSource) {
val sChapter = chapter.toSChapter() val sChapter = chapter.toSChapter()
source.prepareNewChapter(sChapter, manga.toSManga()) source.prepareNewChapter(sChapter, sManga)
chapter = chapter.copyFromSChapter(sChapter) chapter = chapter.copyFromSChapter(sChapter)
} }
// Recognize chapter number for the chapter. // Recognize chapter number for the chapter.
val chapterNumber = ChapterRecognition.parseChapterNumber( val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
manga.title,
chapter.name,
chapter.chapterNumber,
)
chapter = chapter.copy(chapterNumber = chapterNumber) chapter = chapter.copy(chapterNumber = chapterNumber)
val dbChapter = dbChapters.find { it.url == chapter.url } val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter == null) { if (dbChapter == null) {
val toAddChapter = if (chapter.dateUpload == 0L) { val toAddChapter = if (chapter.dateUpload == 0L) {
val altDateUpload = if (maxSeenUploadDate == 0L) nowMillis else maxSeenUploadDate val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
chapter.copy(dateUpload = altDateUpload) chapter.copy(dateUpload = altDateUpload)
} else { } else {
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload) maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
chapter chapter
} }
newChapters.add(toAddChapter) toAdd.add(toAddChapter)
} else { } else {
if (shouldUpdateDbChapter.await(dbChapter, chapter)) { if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) && val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)
dbChapter.name,
dbChapter.scanlator,
/* SY --> */ manga.ogTitle /* SY <-- */,
manga.source,
)
if (shouldRenameChapter) { if (shouldRenameChapter) {
downloadManager.renameChapter(source, manga, dbChapter, chapter) downloadManager.renameChapter(source, manga, dbChapter, chapter)
@ -135,59 +126,38 @@ class SyncChaptersWithSource(
if (chapter.dateUpload != 0L) { if (chapter.dateUpload != 0L) {
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload) toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
} }
updatedChapters.add(toChangeChapter) toChange.add(toChangeChapter)
} }
} }
} }
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions. // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) { if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
updateManga.awaitUpdateFetchInterval(
manga,
now,
fetchWindow,
)
}
return emptyList() return emptyList()
} }
val changedOrDuplicateReadUrls = mutableSetOf<String>() val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Double>() val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Double>() val deletedReadChapterNumbers = TreeSet<Float>()
val deletedBookmarkedChapterNumbers = TreeSet<Double>() val deletedBookmarkedChapterNumbers = TreeSet<Float>()
val readChapterNumbers = dbChapters toDelete.forEach { chapter ->
.asSequence()
.filter { it.read && it.isRecognizedNumber }
.map { it.chapterNumber }
.toSet()
removedChapters.forEach { chapter ->
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber) if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber) if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
deletedChapterNumbers.add(chapter.chapterNumber) deletedChapterNumbers.add(chapter.chapterNumber)
} }
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch } val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
.associate { it.chapterNumber to it.dateFetch } .associate { it.chapterNumber to it.dateFetch }
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones // Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
// Sources MUST return the chapters from most to less recent, which is common. // Sources MUST return the chapters from most to less recent, which is common.
var itemCount = newChapters.size var itemCount = toAdd.size
var updatedToAdd = newChapters.map { toAddItem -> var updatedToAdd = toAdd.map { toAddItem ->
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--) var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
if (chapter.chapterNumber in readChapterNumbers && markDuplicateAsRead) { if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
changedOrDuplicateReadUrls.add(chapter.url)
chapter = chapter.copy(read = true)
}
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
chapter = chapter.copy( chapter = chapter.copy(
read = chapter.chapterNumber in deletedReadChapterNumbers, read = chapter.chapterNumber in deletedReadChapterNumbers,
@ -199,19 +169,19 @@ class SyncChaptersWithSource(
chapter = chapter.copy(dateFetch = it) chapter = chapter.copy(dateFetch = it)
} }
changedOrDuplicateReadUrls.add(chapter.url) reAdded.add(chapter)
chapter chapter
} }
// --> EXH (carry over reading progress) // --> EXH (carry over reading progress)
if (manga.isEhBasedManga()) { if (manga.isEhBasedManga()) {
val finalAdded = updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls } val finalAdded = updatedToAdd.subtract(reAdded)
if (finalAdded.isNotEmpty()) { if (finalAdded.isNotEmpty()) {
val max = dbChapters.maxOfOrNull { it.lastPageRead } val max = dbChapters.maxOfOrNull { it.lastPageRead }
if (max != null && max > 0) { if (max != null && max > 0) {
updatedToAdd = updatedToAdd.map { updatedToAdd = updatedToAdd.map {
if (it.url !in changedOrDuplicateReadUrls) { if (it !in reAdded) {
it.copy(lastPageRead = max) it.copy(lastPageRead = max)
} else { } else {
it it
@ -222,8 +192,8 @@ class SyncChaptersWithSource(
} }
// <-- EXH // <-- EXH
if (removedChapters.isNotEmpty()) { if (toDelete.isNotEmpty()) {
val toDeleteIds = removedChapters.map { it.id } val toDeleteIds = toDelete.map { it.id }
chapterRepository.removeChaptersWithIds(toDeleteIds) chapterRepository.removeChaptersWithIds(toDeleteIds)
} }
@ -231,18 +201,17 @@ class SyncChaptersWithSource(
updatedToAdd = chapterRepository.addAll(updatedToAdd) updatedToAdd = chapterRepository.addAll(updatedToAdd)
} }
if (updatedChapters.isNotEmpty()) { if (toChange.isNotEmpty()) {
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() } val chapterUpdates = toChange.map { it.toChapterUpdate() }
updateChapter.awaitAll(chapterUpdates) updateChapter.awaitAll(chapterUpdates)
} }
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
// Set this manga as updated since chapters were changed // Set this manga as updated since chapters were changed
// Note that last_update actually represents last time the chapter list changed at all // Note that last_update actually represents last time the chapter list changed at all
updateManga.awaitUpdateLastUpdate(manga.id) updateManga.awaitUpdateLastUpdate(manga.id)
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet() val reAddedUrls = reAdded.map { it.url }.toHashSet()
return updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls || it.scanlator in excludedScanlators } return updatedToAdd.filterNot { it.url in reAddedUrls }
} }
} }

View File

@ -0,0 +1,41 @@
package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.data.track.TrackService
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.toChapterUpdate
import tachiyomi.domain.track.model.Track
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SyncChaptersWithTrackServiceTwoWay(
private val updateChapter: UpdateChapter = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
) {
suspend fun await(
chapters: List<Chapter>,
remoteTrack: Track,
service: TrackService,
) {
val sortedChapters = chapters.sortedBy { it.chapterNumber }
val chapterUpdates = sortedChapters
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
.map { it.copy(read = true).toChapterUpdate() }
// only take into account continuous reading
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
try {
service.update(updatedTrack.toDbTrack())
updateChapter.awaitAll(chapterUpdates)
insertTrack.await(updatedTrack)
} catch (e: Throwable) {
logcat(LogPriority.WARN, e)
}
}
}

View File

@ -1,7 +1,7 @@
package tachiyomi.domain.chapter.interactor package eu.kanade.domain.chapter.interactor
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository

View File

@ -11,7 +11,7 @@ fun Chapter.toSChapter(): SChapter {
it.url = url it.url = url
it.name = name it.name = name
it.date_upload = dateUpload it.date_upload = dateUpload
it.chapter_number = chapterNumber.toFloat() it.chapter_number = chapterNumber
it.scanlator = scanlator it.scanlator = scanlator
} }
} }
@ -21,8 +21,8 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
name = sChapter.name, name = sChapter.name,
url = sChapter.url, url = sChapter.url,
dateUpload = sChapter.date_upload, dateUpload = sChapter.date_upload,
chapterNumber = sChapter.chapter_number.toDouble(), chapterNumber = sChapter.chapter_number,
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(), scanlator = sChapter.scanlator?.ifBlank { null },
) )
} }
@ -37,7 +37,6 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
it.last_page_read = lastPageRead.toInt() it.last_page_read = lastPageRead.toInt()
it.date_fetch = dateFetch it.date_fetch = dateFetch
it.date_upload = dateUpload it.date_upload = dateUpload
it.chapter_number = chapterNumber.toFloat() it.chapter_number = chapterNumber
it.source_order = sourceOrder.toInt() it.source_order = sourceOrder.toInt()
it.last_modified = lastModifiedAt
} }

View File

@ -1,45 +1,57 @@
package eu.kanade.domain.chapter.model package eu.kanade.domain.chapter.model
import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterList import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import exh.md.utils.MdUtil
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.source.local.isLocal
/** /**
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
*/ */
fun List<Chapter>.applyFilters( fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
manga: Manga,
downloadManager: DownloadManager, /* SY --> */
mergedManga: Map<Long, Manga>, /* SY <-- */
): List<Chapter> {
val isLocalManga = manga.isLocal() val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter val downloadedFilter = manga.downloadedFilter
val bookmarkedFilter = manga.bookmarkedFilter val bookmarkedFilter = manga.bookmarkedFilter
return filter { chapter -> applyFilter(unreadFilter) { !chapter.read } } return filter { chapter ->
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } } when (unreadFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> !chapter.read
TriStateFilter.ENABLED_NOT -> chapter.read
}
}
.filter { chapter -> .filter { chapter ->
// SY --> when (bookmarkedFilter) {
@Suppress("NAME_SHADOWING") TriStateFilter.DISABLED -> true
val manga = mergedManga.getOrElse(chapter.mangaId) { manga } TriStateFilter.ENABLED_IS -> chapter.bookmark
// SY <-- TriStateFilter.ENABLED_NOT -> !chapter.bookmark
applyFilter(downloadedFilter) {
val downloaded = downloadManager.isChapterDownloaded(
chapter.name,
chapter.scanlator,
/* SY --> */ manga.ogTitle /* SY <-- */,
manga.source,
)
downloaded || isLocalManga
} }
} }
.filter { chapter ->
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)
val downloadState = when {
downloaded -> Download.State.DOWNLOADED
else -> Download.State.NOT_DOWNLOADED
}
when (downloadedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> downloadState == Download.State.DOWNLOADED || isLocalManga
TriStateFilter.ENABLED_NOT -> downloadState != Download.State.DOWNLOADED && !isLocalManga
}
}
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() || MdUtil.getScanlators(chapter.scanlator).any { group -> manga.filteredScanlators!!.contains(group) }
}
// SY <--
.sortedWith(getChapterSort(manga)) .sortedWith(getChapterSort(manga))
} }
@ -47,14 +59,37 @@ fun List<Chapter>.applyFilters(
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
*/ */
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> { fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
val isLocalManga = manga.isLocal() val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter val downloadedFilter = manga.downloadedFilter
val bookmarkedFilter = manga.bookmarkedFilter val bookmarkedFilter = manga.bookmarkedFilter
return asSequence() return asSequence()
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } } .filter { (chapter) ->
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } } when (unreadFilter) {
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } } TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> !chapter.read
TriStateFilter.ENABLED_NOT -> chapter.read
}
}
.filter { (chapter) ->
when (bookmarkedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> chapter.bookmark
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
}
}
.filter {
when (downloadedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
}
}
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() || MdUtil.getScanlators(chapter.chapter.scanlator).any { group -> manga.filteredScanlators!!.contains(group) }
}
// SY <--
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) } .sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
} }

View File

@ -1,10 +1,10 @@
package eu.kanade.domain.download.interactor package eu.kanade.domain.download.interactor
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import tachiyomi.core.common.util.lang.withNonCancellableContext import eu.kanade.tachiyomi.source.SourceManager
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
class DeleteDownload( class DeleteDownload(
private val sourceManager: SourceManager, private val sourceManager: SourceManager,

View File

@ -0,0 +1,34 @@
package eu.kanade.domain.download.service
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.provider.FolderProvider
class DownloadPreferences(
private val folderProvider: FolderProvider,
private val preferenceStore: PreferenceStore,
) {
fun downloadsDirectory() = preferenceStore.getString("download_directory", folderProvider.path())
fun downloadOnlyOverWifi() = preferenceStore.getBoolean("pref_download_only_over_wifi_key", true)
fun saveChaptersAsCBZ() = preferenceStore.getBoolean("save_chapter_as_cbz", true)
fun splitTallImages() = preferenceStore.getBoolean("split_tall_images", false)
fun autoDownloadWhileReading() = preferenceStore.getInt("auto_download_while_reading", 0)
fun removeAfterReadSlots() = preferenceStore.getInt("remove_after_read_slots", -1)
fun removeAfterMarkedAsRead() = preferenceStore.getBoolean("pref_remove_after_marked_as_read_key", false)
fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false)
fun removeExcludeCategories() = preferenceStore.getStringSet("remove_exclude_categories", emptySet())
fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false)
fun downloadNewChapterCategories() = preferenceStore.getStringSet("download_new_categories", emptySet())
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
}

View File

@ -23,7 +23,7 @@ class GetExtensionSources(
ExtensionSourceItem( ExtensionSourceItem(
source = source, source = source,
enabled = source.isEnabled(), enabled = source.isEnabled(),
labelAsName = isMultiSource && !isMultiLangSingleSource, labelAsName = isMultiSource && isMultiLangSingleSource.not(),
) )
} }
} }

View File

@ -20,13 +20,12 @@ class GetExtensionsByType(
extensionManager.installedExtensionsFlow, extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow, extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow, extensionManager.availableExtensionsFlow,
) { enabledLanguages, _installed, _untrusted, _available -> ) { _activeLanguages, _installed, _untrusted, _available ->
val (updates, installed) = _installed val (updates, installed) = _installed
.filter { (showNsfwSources || !it.isNsfw) } .filter { (showNsfwSources || it.isNsfw.not()) }
.sortedWith( .sortedWith(
compareBy<Extension.Installed> { compareBy<Extension.Installed> { it.isObsolete.not() /* SY --> */ && it.isRedundant.not() /* SY <-- */ }
!it.isObsolete /* SY --> */ && !it.isRedundant /* SY <-- */ .thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
}.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
) )
.partition { it.hasUpdate } .partition { it.hasUpdate }
@ -37,13 +36,13 @@ class GetExtensionsByType(
.filter { extension -> .filter { extension ->
_installed.none { it.pkgName == extension.pkgName } && _installed.none { it.pkgName == extension.pkgName } &&
_untrusted.none { it.pkgName == extension.pkgName } && _untrusted.none { it.pkgName == extension.pkgName } &&
(showNsfwSources || !extension.isNsfw) (showNsfwSources || extension.isNsfw.not())
} }
.flatMap { ext -> .flatMap { ext ->
if (ext.sources.isEmpty()) { if (ext.sources.isEmpty()) {
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList() return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
} }
ext.sources.filter { it.lang in enabledLanguages } ext.sources.filter { it.lang in _activeLanguages }
.map { .map {
ext.copy( ext.copy(
name = it.name, name = it.name,

View File

@ -1,32 +0,0 @@
package eu.kanade.domain.extension.interactor
import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat
import eu.kanade.domain.source.service.SourcePreferences
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.preference.getAndSet
class TrustExtension(
private val extensionRepoRepository: ExtensionRepoRepository,
private val preferences: SourcePreferences,
) {
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
}
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
preferences.trustedExtensions().getAndSet { exts ->
// Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
removed.also { it += "$pkgName:$versionCode:$signatureHash" }
}
}
fun revokeAll() {
preferences.trustedExtensions().delete()
}
}

View File

@ -0,0 +1,13 @@
package eu.kanade.domain.history.interactor
import tachiyomi.domain.history.model.History
import tachiyomi.domain.history.repository.HistoryRepository
class GetHistoryByMangaId(
private val repository: HistoryRepository,
) {
suspend fun await(mangaId: Long): List<History> {
return repository.getByMangaId(mangaId)
}
}

View File

@ -1,19 +1,19 @@
package tachiyomi.domain.history.interactor package eu.kanade.domain.history.interactor
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.history.repository.HistoryRepository import tachiyomi.domain.history.repository.HistoryRepository
import tachiyomi.domain.manga.interactor.GetManga
import kotlin.math.max import kotlin.math.max
class GetNextChapters( class GetNextChapters(
private val getChaptersByMangaId: GetChaptersByMangaId, private val getChapterByMangaId: GetChapterByMangaId,
// SY --> // SY -->
private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId, private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
// SY <-- // SY <--
private val getManga: GetManga, private val getManga: GetManga,
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
@ -29,7 +29,7 @@ class GetNextChapters(
// SY --> // SY -->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
val chapters = getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) val chapters = getMergedChapterByMangaId.await(mangaId)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {
@ -39,7 +39,7 @@ class GetNextChapters(
} }
} }
if (manga.isEhBasedManga()) { if (manga.isEhBasedManga()) {
val chapters = getChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) val chapters = getChapterByMangaId.await(mangaId)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {
@ -50,7 +50,7 @@ class GetNextChapters(
} }
// SY <-- // SY <--
val chapters = getChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) val chapters = getChapterByMangaId.await(mangaId)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {
@ -60,11 +60,7 @@ class GetNextChapters(
} }
} }
suspend fun await( suspend fun await(mangaId: Long, fromChapterId: Long, onlyUnread: Boolean = true): List<Chapter> {
mangaId: Long,
fromChapterId: Long,
onlyUnread: Boolean = true,
): List<Chapter> {
val chapters = await(mangaId, onlyUnread) val chapters = await(mangaId, onlyUnread)
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId } val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size) val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.library.model package eu.kanade.domain.library.model
enum class GroupLibraryMode { enum class GroupLibraryMode {
GLOBAL, GLOBAL,

View File

@ -0,0 +1,32 @@
package eu.kanade.domain.library.model
import eu.kanade.tachiyomi.R
object LibraryGroup {
const val BY_DEFAULT = 0
const val BY_SOURCE = 1
const val BY_STATUS = 2
const val BY_TRACK_STATUS = 3
const val UNGROUPED = 4
fun groupTypeStringRes(type: Int, hasCategories: Boolean = true): Int {
return when (type) {
BY_STATUS -> R.string.status
BY_SOURCE -> R.string.label_sources
BY_TRACK_STATUS -> R.string.tracking_status
UNGROUPED -> R.string.ungrouped
else -> if (hasCategories) R.string.categories else R.string.ungrouped
}
}
fun groupTypeDrawableRes(type: Int): Int {
return when (type) {
BY_STATUS -> R.drawable.ic_progress_clock_24dp
BY_TRACK_STATUS -> R.drawable.ic_sync_24dp
BY_SOURCE -> R.drawable.ic_browse_filled_24dp
UNGROUPED -> R.drawable.ic_ungroup_24dp
else -> R.drawable.ic_label_24dp
}
}
}

View File

@ -0,0 +1,128 @@
package eu.kanade.domain.library.service
import eu.kanade.domain.library.model.GroupLibraryMode
import eu.kanade.domain.library.model.LibraryGroup
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.getEnum
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.manga.model.Manga
class LibraryPreferences(
private val preferenceStore: PreferenceStore,
) {
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
// region Filter
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
// SY -->
fun filterLewd() = preferenceStore.getInt("pref_filter_library_lewd", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
// SY <--
fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
// endregion
// region Badges
fun downloadBadge() = preferenceStore.getBoolean("display_download_badge", false)
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
fun newShowUpdatesCount() = preferenceStore.getBoolean("library_show_updates_count", true)
fun newUpdatesCount() = preferenceStore.getInt("library_unseen_updates_count", 0)
// endregion
// region Category
fun defaultCategory() = preferenceStore.getInt("default_category", -1)
fun lastUsedCategory() = preferenceStore.getInt("last_used_category", 0)
fun categoryTabs() = preferenceStore.getBoolean("display_category_tabs", true)
fun categoryNumberOfItems() = preferenceStore.getBoolean("display_number_of_items", false)
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
// endregion
// region Chapter
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
// and upload date
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
fun setChapterSettingsDefault(manga: Manga) {
filterChapterByRead().set(manga.unreadFilterRaw)
filterChapterByDownloaded().set(manga.downloadedFilterRaw)
filterChapterByBookmarked().set(manga.bookmarkedFilterRaw)
sortChapterBySourceOrNumber().set(manga.sorting)
displayChapterByNameOrNumber().set(manga.displayMode)
sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
}
fun autoClearChapterCache() = preferenceStore.getBoolean("auto_clear_chapter_cache", false)
// endregion
// SY -->
fun sortTagsForLibrary() = preferenceStore.getStringSet("sort_tags_for_library", mutableSetOf())
fun groupLibraryUpdateType() = preferenceStore.getEnum("group_library_update_type", GroupLibraryMode.GLOBAL)
fun groupLibraryBy() = preferenceStore.getInt("group_library_by", LibraryGroup.BY_DEFAULT)
// SY <--
}

View File

@ -1,7 +1,7 @@
package eu.kanade.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.core.common.preference.plusAssign import eu.kanade.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences import eu.kanade.tachiyomi.util.preference.plusAssign
class CreateSortTag( class CreateSortTag(
private val preferences: LibraryPreferences, private val preferences: LibraryPreferences,
@ -23,8 +23,8 @@ class CreateSortTag(
} }
sealed class Result { sealed class Result {
data object TagExists : Result() object TagExists : Result()
data object Success : Result() object Success : Result()
} }
/** /**

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.repository.FavoritesEntryRepository import tachiyomi.domain.manga.repository.FavoritesEntryRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,6 +1,6 @@
package eu.kanade.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.library.service.LibraryPreferences import eu.kanade.domain.library.service.LibraryPreferences
class DeleteSortTag( class DeleteSortTag(
private val preferences: LibraryPreferences, private val preferences: LibraryPreferences,

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository

View File

@ -0,0 +1,13 @@
package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository
class GetDuplicateLibraryManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(title: String): Manga? {
return mangaRepository.getDuplicateLibraryManga(title.lowercase())
}
}

View File

@ -1,24 +0,0 @@
package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
class GetExcludedScanlators(
private val handler: DatabaseHandler,
) {
suspend fun await(mangaId: Long): Set<String> {
return handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
.toSet()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return handler.subscribeToList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
.map { it.toSet() }
}
}

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.FavoriteEntry import tachiyomi.domain.manga.model.FavoriteEntry
import tachiyomi.domain.manga.repository.FavoritesEntryRepository import tachiyomi.domain.manga.repository.FavoritesEntryRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga

View File

@ -1,11 +1,11 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import exh.metadata.metadata.base.FlatMetadata import exh.metadata.metadata.base.FlatMetadata
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository
class GetFlatMetadataById( class GetFlatMetadataById(

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository

View File

@ -1,14 +1,10 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.retry
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
import kotlin.time.Duration.Companion.seconds
class GetLibraryManga( class GetLibraryManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
@ -20,15 +16,15 @@ class GetLibraryManga(
fun subscribe(): Flow<List<LibraryManga>> { fun subscribe(): Flow<List<LibraryManga>> {
return mangaRepository.getLibraryMangaAsFlow() return mangaRepository.getLibraryMangaAsFlow()
.retry { // SY -->
if (it is NullPointerException) { .let {
delay(0.5.seconds) var retries = 0
true it.retry {
} else { (retries++ < 3) && it is NullPointerException
false }.onEach {
retries = 0
} }
}.catch {
this@GetLibraryManga.logcat(LogPriority.ERROR, it)
} }
// SY <--
} }
} }

View File

@ -1,9 +1,9 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -12,10 +12,10 @@ class GetMangaWithChapters(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun subscribe(id: Long, applyScanlatorFilter: Boolean = false): Flow<Pair<Manga, List<Chapter>>> { suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine( return combine(
mangaRepository.getMangaByIdAsFlow(id), mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id, applyScanlatorFilter), chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters -> ) { manga, chapters ->
Pair(manga, chapters) Pair(manga, chapters)
} }
@ -25,7 +25,7 @@ class GetMangaWithChapters(
return mangaRepository.getMangaById(id) return mangaRepository.getMangaById(id)
} }
suspend fun awaitChapters(id: Long, applyScanlatorFilter: Boolean = false): List<Chapter> { suspend fun awaitChapters(id: Long): List<Chapter> {
return chapterRepository.getChapterByMangaId(id, applyScanlatorFilter) return chapterRepository.getChapterByMangaId(id)
} }
} }

View File

@ -1,8 +1,8 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,8 +1,8 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,8 +1,8 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.MergedMangaReference import tachiyomi.domain.manga.model.MergedMangaReference
import tachiyomi.domain.manga.repository.MangaMergeRepository import tachiyomi.domain.manga.repository.MangaMergeRepository

View File

@ -1,5 +1,6 @@
package eu.kanade.domain.manga.interactor package eu.kanade.domain.manga.interactor
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.toSChapter import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.model.PagePreview import eu.kanade.domain.manga.model.PagePreview
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
@ -7,23 +8,22 @@ import eu.kanade.tachiyomi.data.cache.PagePreviewCache
import eu.kanade.tachiyomi.source.PagePreviewSource import eu.kanade.tachiyomi.source.PagePreviewSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import exh.source.getMainSource import exh.source.getMainSource
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
class GetPagePreviews( class GetPagePreviews(
private val pagePreviewCache: PagePreviewCache, private val pagePreviewCache: PagePreviewCache,
private val getChaptersByMangaId: GetChaptersByMangaId, private val getChapters: GetChapterByMangaId,
) { ) {
suspend fun await(manga: Manga, source: Source, page: Int): Result { suspend fun await(manga: Manga, source: Source, page: Int): Result {
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
val source = source.getMainSource<PagePreviewSource>() ?: return Result.Unused val source = source.getMainSource<PagePreviewSource>() ?: return Result.Unused
val chapters = getChaptersByMangaId.await(manga.id).sortedByDescending { it.sourceOrder } val chapters = getChapters.await(manga.id).sortedByDescending { it.sourceOrder }
val chapterIds = chapters.map { it.id } val chapterIds = chapters.map { it.id }
return try { return try {
val pagePreviews = try { val pagePreviews = try {
pagePreviewCache.getPageListFromCache(manga, chapterIds, page) pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
} catch (_: Exception) { } catch (e: Exception) {
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also { source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
pagePreviewCache.putPageListToCache(manga, chapterIds, it) pagePreviewCache.putPageListToCache(manga, chapterIds, it)
} }
@ -41,7 +41,7 @@ class GetPagePreviews(
} }
sealed class Result { sealed class Result {
data object Unused : Result() object Unused : Result()
data class Success( data class Success(
val pagePreviews: List<PagePreview>, val pagePreviews: List<PagePreview>,
val hasNextPage: Boolean, val hasNextPage: Boolean,

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.models.SearchMetadata
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTag
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository

View File

@ -1,4 +1,4 @@
package tachiyomi.domain.manga.interactor package eu.kanade.domain.manga.interactor
import exh.metadata.sql.models.SearchTitle import exh.metadata.sql.models.SearchTitle
import tachiyomi.domain.manga.repository.MangaMetadataRepository import tachiyomi.domain.manga.repository.MangaMetadataRepository

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