Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbf82a9d6a |
@@ -1,7 +0,0 @@
|
||||
[*.{kt,kts}]
|
||||
indent_size=4
|
||||
insert_final_newline=true
|
||||
ij_kotlin_allow_trailing_comma=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_for_members = 2147483647
|
||||
@@ -1 +1,2 @@
|
||||
github: inorichi
|
||||
ko_fi: inorichi
|
||||
|
||||
@@ -2,15 +2,9 @@
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v1.9.3)
|
||||
- All extensions
|
||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
||||
- I have updated all extensions
|
||||
- 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**
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: "🐞 Bug report"
|
||||
about: Report a bug
|
||||
title: "[Bug] <Write short description here>"
|
||||
labels: "bug"
|
||||
---
|
||||
|
||||
**PLEASE READ THIS**
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||
|
||||
**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
|
||||
|
||||
### Expected behavior
|
||||
This should happen.
|
||||
|
||||
### Actual behavior
|
||||
This happened instead.
|
||||
|
||||
## Other details
|
||||
Additional details and attachments.
|
||||
|
||||
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
|
||||
@@ -1,11 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⚠️ Extension/source issue
|
||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
||||
- name: 📦 Tachiyomi extensions
|
||||
url: https://tachiyomi.org/extensions
|
||||
about: List of all available extensions with download links
|
||||
- name: 🖥️ Tachiyomi website
|
||||
- name: Tachiyomi help website
|
||||
url: https://tachiyomi.org/help/
|
||||
about: Guides, troubleshooting, and answers to common questions
|
||||
about: Common questions are answered here.
|
||||
- name: Tachiyomi extensions GitHub repository
|
||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||
about: Issues about an extension/source/catalogue should be opened here instead.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: "🌟 Feature request"
|
||||
about: Suggest a feature to improve Tachiyomi
|
||||
title: "[Feature Request] <Write short description here>"
|
||||
labels: "feature"
|
||||
---
|
||||
|
||||
**PLEASE READ THIS**
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||
|
||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||
|
||||
---
|
||||
|
||||
## Why/User Benefit/User Problem
|
||||
(explain why this feature should be added)
|
||||
|
||||
## What/Requirements
|
||||
(explain how this feature would behave)
|
||||
@@ -1,108 +0,0 @@
|
||||
name: 🐞 Issue report
|
||||
description: Report an issue in Tachiyomi
|
||||
labels: [Bug]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Provide an example of the issue.
|
||||
placeholder: |
|
||||
Example:
|
||||
1. First step
|
||||
2. Second step
|
||||
3. Issue here
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Explain what you should expect to happen.
|
||||
placeholder: |
|
||||
Example:
|
||||
"This should happen..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
description: Explain what actually happens.
|
||||
placeholder: |
|
||||
Example:
|
||||
"This happened instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: crash-logs
|
||||
attributes:
|
||||
label: Crash logs
|
||||
description: |
|
||||
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
||||
placeholder: |
|
||||
You can paste the crash logs in plain text or upload it as an attachment.
|
||||
|
||||
- type: input
|
||||
id: tachiyomi-version
|
||||
attributes:
|
||||
label: Tachiyomi version
|
||||
description: You can find your Tachiyomi version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "1.9.3"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: android-version
|
||||
attributes:
|
||||
label: Android version
|
||||
description: You can find this somewhere in your Android settings.
|
||||
placeholder: |
|
||||
Example: "Android 11"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: device
|
||||
attributes:
|
||||
label: Device
|
||||
description: List your device and model.
|
||||
placeholder: |
|
||||
Example: "Google Pixel 5"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||
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.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- 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 tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[1.9.3](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
@@ -1,39 +0,0 @@
|
||||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve Tachiyomi
|
||||
labels: [Feature request]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Describe your suggested feature
|
||||
description: How can Tachiyomi be improved?
|
||||
placeholder: |
|
||||
Example:
|
||||
"It should work like this..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||
options:
|
||||
- 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
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- 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.3](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: "Extension/source/catalogue issue"
|
||||
about: "Do not open an issue here. See https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||
labels: "catalog, invalid"
|
||||
---
|
||||
|
||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||
@@ -1,12 +0,0 @@
|
||||
<!--
|
||||
Please include a summary of the change and which issue is fixed.
|
||||
Also make sure you've tested your code and also done a self-review of it.
|
||||
Don't forget to check all base themes and tablet mode for relevant changes.
|
||||
|
||||
If your changes are visual, please provide images below:
|
||||
|
||||
### Images
|
||||
| Image 1 | Image 2 |
|
||||
| ------- | ------- |
|
||||
|  |  |
|
||||
-->
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 489 KiB After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,6 @@
|
||||
org.gradle.daemon=false
|
||||
org.gradle.jvmargs=-Xmx5120m
|
||||
org.gradle.workers.max=2
|
||||
|
||||
kotlin.incremental=false
|
||||
kotlin.compiler.execution.strategy=in-process
|
||||
+14
-5
@@ -6,20 +6,29 @@ on:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
trigger_preview_build:
|
||||
name: Trigger preview build
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
check_wrapper:
|
||||
name: Validate Gradle Wrapper
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
preview:
|
||||
name: Build app preview
|
||||
needs: check_wrapper
|
||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: TAG - Bump version and push tag
|
||||
uses: anothrNick/github-tag-action@1.39.0
|
||||
uses: anothrNick/github-tag-action@1.17.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WITH_V: true
|
||||
@@ -0,0 +1,91 @@
|
||||
name: Release Builder
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
check_wrapper:
|
||||
name: Validate Gradle Wrapper
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
build:
|
||||
name: Build app
|
||||
needs: check_wrapper
|
||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Cancel previous runs
|
||||
uses: styfle/cancel-workflow-action@0.5.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Copy CI gradle.properties
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Write google-services.json
|
||||
uses: DamianReeves/write-file-action@v1.0
|
||||
with:
|
||||
# The path to the file to write
|
||||
path: app/google-services.json
|
||||
# The contents of the file
|
||||
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
||||
# The mode of writing to use: `overwrite`, `append`, or `preserve`.
|
||||
write-mode: overwrite # optional, default is preserve
|
||||
|
||||
- name: Build app
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
arguments: assembleRelease --stacktrace
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/standard/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.run_number }}
|
||||
release_name: TachiyomiSY
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- name: Upload APK to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ env.SIGNED_RELEASE_FILE }}
|
||||
asset_name: TachiyomiSY.apk
|
||||
asset_content_type: application/vnd.android.package-archive
|
||||
@@ -1,10 +1,6 @@
|
||||
name: CI
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_wrapper:
|
||||
name: Validate Gradle Wrapper
|
||||
@@ -12,7 +8,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
@@ -20,25 +16,37 @@ jobs:
|
||||
build:
|
||||
name: Build app
|
||||
needs: check_wrapper
|
||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
- name: Cancel previous runs
|
||||
uses: styfle/cancel-workflow-action@0.5.0
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Copy CI gradle.properties
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Build app
|
||||
uses: gradle/gradle-command-action@v2
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
arguments: assembleDevDebug
|
||||
|
||||
arguments: assembleStandardDebug
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
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
|
||||
@@ -1,102 +0,0 @@
|
||||
name: Release Builder
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build release app
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
# SY <--
|
||||
- name: Write google-services.json
|
||||
uses: DamianReeves/write-file-action@v1.2
|
||||
with:
|
||||
path: app/google-services.json
|
||||
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
||||
write-mode: overwrite
|
||||
# SY -->
|
||||
|
||||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest --stacktrace
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/standard/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Clean up build artifacts
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk TachiyomiSY.apk
|
||||
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
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 }'`
|
||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
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 }'`
|
||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
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 }'`
|
||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
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 }'`
|
||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ github.run_number }}
|
||||
name: TachiyomiSY
|
||||
body: |
|
||||
---
|
||||
|
||||
### Checksums
|
||||
|
||||
| Variant | SHA-256 |
|
||||
| ------- | ------- |
|
||||
| Universal | ${{ env.APK_UNIVERSAL_SHA }} |
|
||||
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }} |
|
||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }} |
|
||||
| x86 | ${{ env.APK_X86_SHA }} |
|
||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
||||
files: |
|
||||
TachiyomiSY.apk
|
||||
TachiyomiSY-arm64-v8a.apk
|
||||
TachiyomiSY-armeabi-v7a.apk
|
||||
TachiyomiSY-x86.apk
|
||||
TachiyomiSY-x86_64.apk
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,21 +1,28 @@
|
||||
name: Issue moderator
|
||||
|
||||
name: Issue closer
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
moderate:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Moderate issues
|
||||
uses: tachiyomiorg/issue-moderator-action@v1
|
||||
- name: Autoclose issues
|
||||
uses: arkon/issue-closer-action@v3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-close-rules: |
|
||||
rules: |
|
||||
[
|
||||
{
|
||||
"type": "title",
|
||||
"regex": ".*THIS ISSUE IS IN THE WRONG REPO.*",
|
||||
"message": "It was not opened in the correct repo, as the template mentioned."
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"regex": ".*<Write short description here>*",
|
||||
"message": "The description in the title was not filled out."
|
||||
},
|
||||
{
|
||||
"type": "body",
|
||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
||||
@@ -25,11 +32,5 @@ jobs:
|
||||
"type": "body",
|
||||
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
|
||||
"message": "Requested information in the template was not filled out."
|
||||
},
|
||||
{
|
||||
"type": "both",
|
||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
@@ -3,7 +3,7 @@ name: Lock threads
|
||||
on:
|
||||
# Daily
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: '0 * * * *'
|
||||
# Manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -12,8 +12,8 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
pr-inactive-days: '2'
|
||||
issue-lock-inactive-days: '2'
|
||||
pr-lock-inactive-days: '2'
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@
|
||||
*.apk
|
||||
app/**/output.json
|
||||
|
||||
# Unnecessary file
|
||||
*.swp
|
||||
# Hebrew assets are copied on build
|
||||
app/src/main/res/values-iw/
|
||||
|
||||
TODO.md
|
||||
CHANGELOG.md
|
||||
|
||||
+49
-99
@@ -1,126 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
## Our Responsibilities
|
||||
|
||||
Community moderators are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Community moderators have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community moderators responsible for enforcement at
|
||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
reported by contacting the project team at the Tachiyomi [Discord server](https://discord.gg/tachiyomi). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
All community moderators are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community moderators will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community moderators, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
||||
version 2.1, available at
|
||||
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
|
||||
at [translations](https://www.contributor-covenant.org/translations).
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
+1
-17
@@ -10,23 +10,7 @@ Thanks for your interest in contributing to Tachiyomi!
|
||||
Pull requests are welcome!
|
||||
|
||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
||||
You do not need to ask for permission nor an assignment.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you.
|
||||
|
||||
- Basic [Android development](https://developer.android.com/)
|
||||
- [Kotlin](https://kotlinlang.org/)
|
||||
|
||||
### Tools
|
||||
|
||||
- [Android Studio](https://developer.android.com/studio)
|
||||
- Emulator or phone with developer options enabled to test changes.
|
||||
|
||||
## Getting help
|
||||
|
||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||
|
||||
# Translations
|
||||
|
||||
@@ -42,7 +26,7 @@ When creating a fork, remember to:
|
||||
- To avoid confusion with the main app:
|
||||
- Change the app name
|
||||
- Change the app icon
|
||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt)
|
||||
- To avoid installation conflicts:
|
||||
- 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:
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
|
||||
|
||||
# TachiyomiSY
|
||||
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.
|
||||
Tachiyomi is a free and open source manga reader for Android 5.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.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
Features of Tachiyomi(original) include:
|
||||
* Online reading from a variety of sources
|
||||
* Local reading of downloaded content
|
||||
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/tachiyomiorg/tachiyomi-extensions)
|
||||
* Local reading of downloaded manga
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* 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
|
||||
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Categories to organize your library
|
||||
* Light and dark themes
|
||||
* Schedule updating your library for new chapters
|
||||
@@ -58,8 +58,11 @@ Custom sources:
|
||||
Additional features for some extensions, features include custom description, opening in app, batch add to library, and a bunch of other things based on the source:
|
||||
* 8Muses (EroMuse)
|
||||
* HBrowse
|
||||
* HentaiCafe (inside Foolside)
|
||||
* Hitomi.la
|
||||
* Mangadex
|
||||
* NHentai
|
||||
* PervEden (EN and IT)
|
||||
* Puruin
|
||||
* Tsumino
|
||||
|
||||
@@ -81,12 +84,13 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
||||
|
||||
<details><summary>Bugs</summary>
|
||||
|
||||
* Include version (More → About → Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Include version (More > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* 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 screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
* For large logs use http://pastebin.com/ (or similar)
|
||||
* Don't group unrelated requests into one issue
|
||||
|
||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||
@@ -116,4 +120,4 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
|
||||
+243
-206
@@ -1,88 +1,79 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
kotlin("plugin.parcelize")
|
||||
kotlin("plugin.serialization")
|
||||
//id("com.github.zellius.shortcut-helper")
|
||||
id("com.github.ben-manes.versions")
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
// Realm (EH)
|
||||
id("realm-android")
|
||||
}
|
||||
|
||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||
if (!gradle.startParameter.taskRequests.toString().contains("Debug")) {
|
||||
apply(plugin = "com.google.gms.google-services")
|
||||
// Firebase Crashlytics
|
||||
apply(plugin = "com.google.firebase.crashlytics")
|
||||
}
|
||||
|
||||
//shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
|
||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
compileSdkVersion(AndroidConfig.compileSdk)
|
||||
buildToolsVersion(AndroidConfig.buildTools)
|
||||
ndkVersion = AndroidConfig.ndk
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi.sy"
|
||||
versionCode = 50
|
||||
versionName = "1.9.3"
|
||||
minSdkVersion(AndroidConfig.minSdk)
|
||||
targetSdkVersion(AndroidConfig.targetSdk)
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode = 14
|
||||
versionName = "1.6.0"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||
|
||||
multiDexEnabled = true
|
||||
|
||||
ndk {
|
||||
abiFilters += SUPPORTED_ABIS
|
||||
abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86")
|
||||
}
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include(*SUPPORTED_ABIS.toTypedArray())
|
||||
isUniversalApk = true
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("debug") {
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
applicationIdSuffix = ".debug"
|
||||
isPseudoLocalesEnabled = true
|
||||
}
|
||||
create("releaseTest") {
|
||||
applicationIdSuffix = ".rt"
|
||||
//isMinifyEnabled = true
|
||||
//isShrinkResources = true
|
||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
||||
matchingFallbacks.add("release")
|
||||
isZipAlignEnabled = true
|
||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
|
||||
}
|
||||
named("release") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
isDebuggable = false
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
isZipAlignEnabled = true
|
||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||
}
|
||||
|
||||
flavorDimensions.add("default")
|
||||
flavorDimensions("default")
|
||||
|
||||
productFlavors {
|
||||
create("standard") {
|
||||
@@ -93,244 +84,290 @@ android {
|
||||
dimension = "default"
|
||||
}
|
||||
create("dev") {
|
||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||
resConfigs("en", "xxhdpi")
|
||||
dimension = "default"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources.excludes.addAll(listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
))
|
||||
exclude("META-INF/DEPENDENCIES")
|
||||
exclude("LICENSE.txt")
|
||||
exclude("META-INF/LICENSE")
|
||||
exclude("META-INF/LICENSE.txt")
|
||||
exclude("META-INF/NOTICE")
|
||||
exclude("META-INF/*.kotlin_module")
|
||||
|
||||
// Compatibility for two RxJava versions (EXH)
|
||||
exclude("META-INF/rxjava.properties")
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
|
||||
// Disable some unused things
|
||||
aidl = false
|
||||
renderScript = false
|
||||
shaders = false
|
||||
lintOptions {
|
||||
disable("MissingTranslation", "ExtraTranslation")
|
||||
isAbortOnError = false
|
||||
isCheckReleaseBuilds = false
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
checkReleaseBuilds = false
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":i18n"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":presentation-core"))
|
||||
implementation(project(":presentation-widget"))
|
||||
|
||||
// Compose
|
||||
implementation(platform(compose.bom))
|
||||
implementation(compose.activity)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3.core)
|
||||
implementation(compose.material.core)
|
||||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
implementation(compose.ui.tooling)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.flowlayout)
|
||||
implementation(compose.accompanist.permissions)
|
||||
implementation(compose.accompanist.themeadapter)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
||||
implementation(libs.bundles.sqlite)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
// Source models and interfaces from Tachiyomi 1.x
|
||||
implementation("tachiyomi.sourceapi:source-api:1.1")
|
||||
|
||||
// AndroidX libraries
|
||||
implementation(androidx.annotation)
|
||||
implementation(androidx.appcompat)
|
||||
implementation(androidx.biometricktx)
|
||||
implementation(androidx.constraintlayout)
|
||||
implementation(androidx.coordinatorlayout)
|
||||
implementation(androidx.corektx)
|
||||
implementation(androidx.splashscreen)
|
||||
implementation(androidx.recyclerview)
|
||||
implementation(androidx.viewpager)
|
||||
implementation(androidx.profileinstaller)
|
||||
implementation("androidx.annotation:annotation:1.3.0-alpha01")
|
||||
implementation("androidx.appcompat:appcompat:1.3.0-rc01")
|
||||
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha03")
|
||||
implementation("androidx.browser:browser:1.3.0")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta01")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
|
||||
implementation("androidx.core:core-ktx:1.3.2")
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.0")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
val lifecycleVersion = "2.3.0"
|
||||
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
|
||||
implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.bundles.workmanager)
|
||||
implementation("androidx.work:work-runtime-ktx:2.5.0")
|
||||
|
||||
// RX
|
||||
implementation(libs.bundles.reactivex)
|
||||
implementation(libs.flowreactivenetwork)
|
||||
// UI library
|
||||
implementation("com.google.android.material:material:1.3.0")
|
||||
|
||||
"standardImplementation"("com.google.firebase:firebase-core:18.0.3")
|
||||
|
||||
// ReactiveX
|
||||
implementation("io.reactivex:rxandroid:1.2.1")
|
||||
implementation("io.reactivex:rxjava:1.3.8")
|
||||
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
||||
|
||||
// Network client
|
||||
implementation(libs.bundles.okhttp)
|
||||
implementation(libs.okio)
|
||||
val okhttpVersion = "5.0.0-alpha.2"
|
||||
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||
implementation("com.squareup.okio:okio:2.10.0")
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation(libs.conscrypt.android)
|
||||
implementation("org.conscrypt:conscrypt-android:2.5.1")
|
||||
|
||||
// Data serialization (JSON, protobuf)
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
// JSON
|
||||
val kotlinSerializationVersion = "1.0.1"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
||||
implementation("com.google.code.gson:gson:2.8.6")
|
||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||
|
||||
// HTML parser
|
||||
implementation(libs.jsoup)
|
||||
// JavaScript engine
|
||||
implementation("com.squareup.duktape:duktape-android:1.3.0")
|
||||
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.junrar)
|
||||
implementation("com.jakewharton:disklrucache:2.0.2")
|
||||
implementation("com.github.inorichi:unifile:e9ee588")
|
||||
implementation("com.github.junrar:junrar:7.4.0")
|
||||
|
||||
// HTML parser
|
||||
implementation("org.jsoup:jsoup:1.13.1")
|
||||
|
||||
// Database
|
||||
implementation("androidx.sqlite:sqlite-ktx:2.1.0")
|
||||
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
||||
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
|
||||
implementation("io.requery:sqlite-android:3.33.0")
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.4")
|
||||
|
||||
// Model View Presenter
|
||||
val nucleusVersion = "3.0.0"
|
||||
implementation("info.android15.nucleus:nucleus:$nucleusVersion")
|
||||
implementation("info.android15.nucleus:nucleus-support-v7:$nucleusVersion")
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt.core)
|
||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||
|
||||
// Image loading
|
||||
implementation(libs.bundles.coil)
|
||||
implementation(libs.subsamplingscaleimageview) {
|
||||
exclude(module = "image-decoder")
|
||||
}
|
||||
implementation(libs.image.decoder)
|
||||
// Image library
|
||||
val glideVersion = "4.12.0"
|
||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
|
||||
kapt("com.github.bumptech.glide:compiler:$glideVersion")
|
||||
|
||||
// Sort
|
||||
implementation(libs.natural.comparator)
|
||||
|
||||
// UI libraries
|
||||
implementation(libs.material)
|
||||
implementation(libs.flexible.adapter.core)
|
||||
implementation(libs.flexible.adapter.ui)
|
||||
implementation(libs.photoview)
|
||||
implementation(libs.directionalviewpager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
}
|
||||
implementation(libs.insetter)
|
||||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.cascade)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.wheelpicker)
|
||||
implementation(libs.materialmotion.core)
|
||||
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:547d9c0")
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
implementation("com.jakewharton.timber:timber:4.7.1")
|
||||
|
||||
// Crash reports/analytics
|
||||
// implementation(libs.acra.http)
|
||||
// "standardImplementation"(libs.firebase.analytics)
|
||||
// Crash reports
|
||||
//implementation("ch.acra:acra-http:5.7.0")
|
||||
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
// Sort
|
||||
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
|
||||
|
||||
// UI
|
||||
implementation("com.dmitrymalkovich.android:material-design-dimens:1.4")
|
||||
implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4")
|
||||
implementation("eu.davidea:flexible-adapter:5.1.0")
|
||||
implementation("eu.davidea:flexible-adapter-ui:1.0.0")
|
||||
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
|
||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0")
|
||||
implementation("dev.chrisbanes.insetter:insetter:0.5.0")
|
||||
|
||||
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices
|
||||
val materialDialogsVersion = "3.1.1"
|
||||
implementation("com.afollestad.material-dialogs:core:$materialDialogsVersion")
|
||||
implementation("com.afollestad.material-dialogs:input:$materialDialogsVersion")
|
||||
implementation("com.afollestad.material-dialogs:datetime:$materialDialogsVersion")
|
||||
|
||||
// Conductor
|
||||
implementation("com.bluelinelabs:conductor:2.1.5")
|
||||
implementation("com.bluelinelabs:conductor-support:2.1.5") {
|
||||
exclude(group = "com.android.support")
|
||||
}
|
||||
implementation("com.github.tachiyomiorg:conductor-support-preference:2.0.1")
|
||||
|
||||
// FlowBinding
|
||||
val flowbindingVersion = "0.12.0"
|
||||
implementation("io.github.reactivecircus.flowbinding:flowbinding-android:$flowbindingVersion")
|
||||
implementation("io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbindingVersion")
|
||||
implementation("io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbindingVersion")
|
||||
implementation("io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbindingVersion")
|
||||
implementation("io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbindingVersion")
|
||||
|
||||
// Licenses
|
||||
implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.junit)
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.assertj:assertj-core:3.16.1")
|
||||
testImplementation("org.mockito:mockito-core:1.10.19")
|
||||
|
||||
val robolectricVersion = "3.1.4"
|
||||
testImplementation("org.robolectric:robolectric:$robolectricVersion")
|
||||
testImplementation("org.robolectric:shadows-multidex:$robolectricVersion")
|
||||
testImplementation("org.robolectric:shadows-play-services:$robolectricVersion")
|
||||
|
||||
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
|
||||
|
||||
val coroutinesVersion = "1.4.2"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation(libs.leakcanary.android)
|
||||
implementation(libs.leakcanary.plumber)
|
||||
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
|
||||
|
||||
// SY -->
|
||||
// [EXH] Android 7 SSL Workaround
|
||||
implementation("com.google.android.gms:play-services-safetynet:17.0.0")
|
||||
|
||||
// Changelog
|
||||
implementation("com.github.gabrielemariotti.changeloglib:changelog:2.1.0")
|
||||
|
||||
// Text distance (EH)
|
||||
implementation (sylibs.simularity)
|
||||
implementation ("info.debatty:java-string-similarity:2.0.0")
|
||||
|
||||
// Firebase (EH)
|
||||
implementation(sylibs.firebase.analytics)
|
||||
implementation(sylibs.firebase.crashlytics.ktx)
|
||||
implementation("com.google.firebase:firebase-analytics-ktx:18.0.3")
|
||||
implementation("com.google.firebase:firebase-crashlytics-ktx:17.4.1")
|
||||
|
||||
// Better logging (EH)
|
||||
implementation(sylibs.xlog)
|
||||
implementation("com.elvishew:xlog:1.9.0")
|
||||
|
||||
// Debug utils (EH)
|
||||
val debugOverlayVersion = "1.1.3"
|
||||
debugImplementation("com.ms-square:debugoverlay:$debugOverlayVersion")
|
||||
"releaseTestImplementation"("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||
releaseImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||
testImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||
|
||||
// RatingBar (SY)
|
||||
implementation(sylibs.ratingbar)
|
||||
implementation(sylibs.composeRatingbar)
|
||||
}
|
||||
implementation ("me.zhanghai.android.materialratingbar:library:1.4.0")
|
||||
|
||||
androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
}
|
||||
}
|
||||
onVariants(selector().withFlavor("default" to "standard")) {
|
||||
// Only excluding in standard flavor because this breaks
|
||||
// Layout Inspector's Compose tree
|
||||
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||
}
|
||||
// JsonReader for similar manga
|
||||
implementation("com.squareup.moshi:moshi:1.12.0")
|
||||
|
||||
implementation("com.mikepenz:fastadapter:5.4.0")
|
||||
// SY <--
|
||||
}
|
||||
|
||||
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",
|
||||
"-Xopt-in=kotlin.Experimental",
|
||||
"-Xopt-in=kotlin.RequiresOptIn",
|
||||
"-Xuse-experimental=kotlin.ExperimentalStdlibApi",
|
||||
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
|
||||
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-Xuse-experimental=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"
|
||||
)
|
||||
}
|
||||
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||
val copyHebrewStrings = task("copyHebrewStrings", type = Copy::class) {
|
||||
from("./src/main/res/values-he")
|
||||
into("./src/main/res/values-iw")
|
||||
include("**/*")
|
||||
}
|
||||
|
||||
preBuild {
|
||||
dependsOn(formatKotlin, copyHebrewStrings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath(kotlinx.gradle)
|
||||
classpath(kotlin("gradle-plugin", version = BuildPluginsVersion.KOTLIN))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Git is needed in your system PATH for these commands to work.
|
||||
// If it's not installed, you can return a random value as a workaround
|
||||
fun getCommitCount(): String {
|
||||
return runCommand("git rev-list --count HEAD")
|
||||
// return "1"
|
||||
}
|
||||
|
||||
fun getGitSha(): String {
|
||||
return runCommand("git rev-parse --short HEAD")
|
||||
// return "1"
|
||||
}
|
||||
|
||||
fun getBuildTime(): String {
|
||||
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||
df.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return df.format(Date())
|
||||
}
|
||||
|
||||
fun runCommand(command: String): String {
|
||||
val byteOut = ByteArrayOutputStream()
|
||||
project.exec {
|
||||
commandLine = command.split(" ")
|
||||
standardOutput = byteOut
|
||||
}
|
||||
return String(byteOut.toByteArray()).trim()
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
-allowaccessmodification
|
||||
-dontusemixedcaseclassnames
|
||||
-verbose
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-keep class androidx.annotation.Keep
|
||||
|
||||
-keep @androidx.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <init>(...);
|
||||
}
|
||||
Vendored
+121
-84
@@ -1,29 +1,87 @@
|
||||
-dontobfuscate
|
||||
|
||||
# Keep common dependencies used in extensions
|
||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
||||
-keep,allowoptimization class okio.** { 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 uy.kohesive.injekt.** { public protected *; }
|
||||
# Extensions may require methods unused in the core app
|
||||
-dontwarn eu.kanade.tachiyomi.**
|
||||
-keep class eu.kanade.tachiyomi.** { public protected private *; }
|
||||
|
||||
# From extensions-lib
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.SpecificHostRateLimitInterceptorKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.NetworkHelper { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.OkHttpExtensionsKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
||||
-keep class org.jsoup.** { *; }
|
||||
-keep class kotlin.** { *; }
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep class com.google.gson.** { *; }
|
||||
-keep class com.github.salomonbrys.kotson.** { *; }
|
||||
-keep class com.squareup.duktape.** { *; }
|
||||
|
||||
# Debug functions
|
||||
-keep,allowoptimization class exh.debug.DebugFunctions { public *; }
|
||||
# === Keep EH classes
|
||||
-keep class exh.** { *; }
|
||||
-keep class xyz.nulldev.** { *; }
|
||||
|
||||
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
||||
# === Keep RxAndroid, https://github.com/ReactiveX/RxAndroid/issues/350
|
||||
-keep class rx.android.** { *; }
|
||||
|
||||
# Design library
|
||||
-dontwarn com.google.android.material.**
|
||||
-keep class com.google.android.material.** { *; }
|
||||
-keep interface com.google.android.material.** { *; }
|
||||
-keep public class com.google.android.material.R$* { *; }
|
||||
|
||||
-keep class com.hippo.image.** { *; }
|
||||
-keep interface com.hippo.image.** { *; }
|
||||
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||
<init>();
|
||||
}
|
||||
|
||||
# Kotlin Serialization
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
||||
|
||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class eu.kanade.tachiyomi.**$$serializer { *; }
|
||||
-keepclassmembers class eu.kanade.tachiyomi.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class eu.kanade.tachiyomi.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class exh.**$$serializer { *; }
|
||||
-keepclassmembers class exh.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class exh.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
|
||||
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class xyz.nulldev.ts.api.http.serializer.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Madokami extension username and password crash fix
|
||||
-keepclassmembers class androidx.preference.EditTextPreference {
|
||||
*** mOnBindEditTextListener;
|
||||
*** mText;
|
||||
public *;
|
||||
}
|
||||
|
||||
# Hitomi extension crash fix
|
||||
-keepclassmembers class rx.Single {
|
||||
*** onSubscribe;
|
||||
final *;
|
||||
protected *;
|
||||
public *;
|
||||
}
|
||||
|
||||
# RxJava 1.1.0
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||
@@ -40,58 +98,6 @@
|
||||
}
|
||||
|
||||
-dontnote rx.internal.util.PlatformDependent
|
||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||
|
||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
||||
|
||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class eu.kanade.**$$serializer { *; }
|
||||
-keepclassmembers class eu.kanade.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class eu.kanade.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class tachiyomi.**$$serializer { *; }
|
||||
-keepclassmembers class tachiyomi.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class tachiyomi.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class exh.**$$serializer { *; }
|
||||
-keepclassmembers class exh.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class exh.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Filter serializer
|
||||
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
|
||||
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class xyz.nulldev.ts.api.http.serializer.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep class kotlinx.serialization.**
|
||||
-keepclassmembers class kotlinx.serialization.** {
|
||||
<methods>;
|
||||
}
|
||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
||||
|
||||
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
@@ -112,20 +118,31 @@
|
||||
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
||||
-dontwarn okio.**
|
||||
|
||||
# === Keep RxAndroid, https://github.com/ReactiveX/RxAndroid/issues/350
|
||||
-keep class rx.android.** { *; }
|
||||
# === GSON: https://raw.githubusercontent.com/google/gson/master/examples/android-proguard-example/proguard.cfg
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||
# For using GSON @Expose annotation
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# Design library
|
||||
-dontwarn com.google.android.material.**
|
||||
-keep class com.google.android.material.** { *; }
|
||||
-keep interface com.google.android.material.** { *; }
|
||||
-keep public class com.google.android.material.R$* { *; }
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc.**
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
-keep class com.hippo.image.** { *; }
|
||||
-keep interface com.hippo.image.** { *; }
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# == Nucleus
|
||||
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||
@@ -138,6 +155,26 @@
|
||||
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
||||
-keep class uy.kohesive.injekt.** { *; }
|
||||
|
||||
|
||||
# === Glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||
|
||||
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
|
||||
-dontwarn jp.co.cyberagent.android.gpuimage.**
|
||||
|
||||
# === Conductor
|
||||
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
# === RxBinding
|
||||
-dontwarn com.google.auto.value.AutoValue
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
android:shortcutShortLabel="@string/label_library">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
||||
android:targetPackage="eu.kanade.tachiyomi.sy"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
@@ -18,10 +17,9 @@
|
||||
android:shortcutDisabledMessage="@string/app_not_available"
|
||||
android:shortcutId="show_recently_updated"
|
||||
android:shortcutLongLabel="@string/label_recent_updates"
|
||||
android:shortcutShortLabel="@string/label_recent_updates">
|
||||
android:shortcutShortLabel="@string/short_recent_updates">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
||||
android:targetPackage="eu.kanade.tachiyomi.sy"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
@@ -33,7 +31,6 @@
|
||||
android:shortcutShortLabel="@string/label_recent_manga">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
||||
android:targetPackage="eu.kanade.tachiyomi.sy"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
@@ -45,7 +42,6 @@
|
||||
android:shortcutShortLabel="@string/browse">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
||||
android:targetPackage="eu.kanade.tachiyomi.sy"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -2,5 +2,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
||||
@@ -2,5 +2,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
||||
+205
-227
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
package="eu.kanade.tachiyomi">
|
||||
|
||||
<!-- Internet -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@@ -19,41 +19,26 @@
|
||||
<!-- For managing extensions -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<!-- To view extension packages in API 30+ -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
||||
|
||||
<!-- Remove permission from Firebase dependency -->
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:hardwareAccelerated="true"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Tachiyomi.Light"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!-- enable profiling by macrobenchmark -->
|
||||
<profileable
|
||||
android:shell="true"
|
||||
tools:targetApi="q" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
||||
android:exported="true">
|
||||
android:theme="@style/Theme.Splash">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -63,18 +48,11 @@
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:process=":error_handler"
|
||||
android:name=".crash.CrashActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/action_global_search"
|
||||
android:exported="true">
|
||||
android:label="@string/action_global_search">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||
@@ -95,11 +73,9 @@
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.reader.ReaderActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false">
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||
</intent-filter>
|
||||
@@ -107,26 +83,15 @@
|
||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
android:resource="@xml/s_pen_actions"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.security.UnlockActivity"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:exported="false" />
|
||||
|
||||
android:name=".ui.security.BiometricUnlockActivity"
|
||||
android:theme="@style/Theme.Splash" />
|
||||
<activity
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
android:configChanges="uiMode|orientation|screenSize"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="false" />
|
||||
|
||||
android:configChanges="uiMode|orientation|screenSize" />
|
||||
<activity
|
||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||
android:label="Anilist"
|
||||
android:exported="true">
|
||||
android:label="Anilist">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -140,8 +105,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
||||
android:label="MyAnimeList"
|
||||
android:exported="true">
|
||||
android:label="MyAnimeList">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -155,8 +119,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
||||
android:label="Shikimori"
|
||||
android:exported="true">
|
||||
android:label="Shikimori">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -170,8 +133,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
||||
android:label="Bangumi"
|
||||
android:exported="true">
|
||||
android:label="Bangumi">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -184,56 +146,13 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<activity
|
||||
android:name="exh.ui.login.EhLoginActivity"
|
||||
android:label="EHentaiLogin"
|
||||
android:exported="false"/>
|
||||
|
||||
<receiver
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
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
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
android:label="EHentaiLogin" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
@@ -245,158 +164,217 @@
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
<receiver
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
<service
|
||||
android:name=".data.library.LibraryUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Disable advertising ID collection for Firebase -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
<service
|
||||
android:name=".data.download.DownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.updater.UpdaterService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupCreateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- EH -->
|
||||
<service
|
||||
android:name="exh.md.similar.SimilarUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name="exh.eh.EHentaiUpdateWorker"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name="exh.ui.intercept.InterceptActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:exported="true">
|
||||
<!-- E-Hentai -->
|
||||
android:theme="@style/Theme.EHActivity">
|
||||
<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="https" />
|
||||
<data android:scheme="http" />
|
||||
<!-- EH -->
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:host="e-hentai.org" />
|
||||
<data android:host="www.e-hentai.org" />
|
||||
<data android:host="g.e-hentai.org" />
|
||||
<!-- EXH -->
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:pathPattern="/g/..*" />
|
||||
</intent-filter>
|
||||
<!-- ExHentai -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- nhentai -->
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Perv Eden -->
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="http" />
|
||||
<!-- Hentai Cafe -->
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPrefix="/hc.fyi/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPrefix="/hc.fyi/"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:host="exhentai.org" />
|
||||
<data android:host="www.exhentai.org" />
|
||||
<!-- Tsumino -->
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:pathPattern="/g/..*" />
|
||||
</intent-filter>
|
||||
<!-- NHentai -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- Hitomi.la -->
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="https" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Pururin.io -->
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="http" />
|
||||
<!-- HBrowse -->
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:scheme="https" />
|
||||
|
||||
<data android:host="nhentai.net" />
|
||||
<data android:host="www.nhentai.net" />
|
||||
|
||||
<data android:pathPattern="/g/..*" />
|
||||
</intent-filter>
|
||||
<!-- Tsumino -->
|
||||
<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="https" />
|
||||
<data android:scheme="http" />
|
||||
|
||||
<data android:host="tsumino.com" />
|
||||
<data android:host="www.tsumino.com" />
|
||||
|
||||
<data android:pathPattern="/Read/View/..*" />
|
||||
<data android:pathPattern="/Book/Info/..*" />
|
||||
</intent-filter>
|
||||
<!-- Pururin -->
|
||||
<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="https" />
|
||||
<data android:scheme="http" />
|
||||
|
||||
<data android:host="pururin.io" />
|
||||
|
||||
<data android:pathPattern="/gallery/..*" />
|
||||
</intent-filter>
|
||||
<!-- HBrowse -->
|
||||
<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="https" />
|
||||
<data android:scheme="http" />
|
||||
|
||||
<data android:host="hbrowse.com" />
|
||||
<data android:host="www.hbrowse.com" />
|
||||
|
||||
<!--<data android:pathPattern="/gallery/..*" />-->
|
||||
</intent-filter>
|
||||
<!-- Mangadex -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:host="mangadex.org" />
|
||||
<data android:host="mangadex.cc" />
|
||||
<data android:host="www.mangadex.org" />
|
||||
<data android:host="www.mangadex.cc" />
|
||||
|
||||
<data android:pathPattern="/manga/..*" />
|
||||
<data android:pathPattern="/title/..*" />
|
||||
<data android:pathPattern="/chapter/..*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="exh.md.MangaDexLoginActivity"
|
||||
android:label="MangaDexLogin"
|
||||
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" />
|
||||
<!-- MangaDex -->
|
||||
<!--<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.org"
|
||||
android:pathPrefix="/manga/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="mangadex.org"
|
||||
android:pathPrefix="/manga/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/manga/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/manga/" />
|
||||
|
||||
<data
|
||||
android:host="mangadex-auth"
|
||||
android:scheme="tachiyomisy" />
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.org"
|
||||
android:pathPrefix="/title/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="mangadex.org"
|
||||
android:pathPrefix="/title/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/title/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/title/" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.org"
|
||||
android:pathPrefix="/chapter/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="mangadex.org"
|
||||
android:pathPrefix="/chapter/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/chapter/" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.mangadex.cc"
|
||||
android:pathPrefix="/chapter/" />-->
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||
android:theme="@style/Theme.EHActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
||||
package eu.kanade.core.prefs
|
||||
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
|
||||
sealed class CheckboxState<T>(open val value: T) {
|
||||
abstract fun next(): CheckboxState<T>
|
||||
|
||||
sealed class State<T>(override val value: T) : CheckboxState<T>(value) {
|
||||
data class Checked<T>(override val value: T) : State<T>(value)
|
||||
data class None<T>(override val value: T) : State<T>(value)
|
||||
|
||||
val isChecked: Boolean
|
||||
get() = this is Checked
|
||||
|
||||
override fun next(): CheckboxState<T> {
|
||||
return when (this) {
|
||||
is Checked -> None(value)
|
||||
is None -> Checked(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 Exclude<T>(override val value: T) : TriState<T>(value)
|
||||
data class None<T>(override val value: T) : TriState<T>(value)
|
||||
|
||||
override fun next(): CheckboxState<T> {
|
||||
return when (this) {
|
||||
is Exclude -> None(value)
|
||||
is Include -> Exclude(value)
|
||||
is None -> Include(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun asState(): ToggleableState {
|
||||
return when (this) {
|
||||
is Exclude -> ToggleableState.Indeterminate
|
||||
is Include -> ToggleableState.On
|
||||
is None -> ToggleableState.Off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> T.asCheckboxState(condition: (T) -> Boolean): CheckboxState.State<T> {
|
||||
return if (condition(this)) {
|
||||
CheckboxState.State.Checked(this)
|
||||
} else {
|
||||
CheckboxState.State.None(this)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.mapAsCheckboxState(condition: (T) -> Boolean): List<CheckboxState.State<T>> {
|
||||
return this.map { it.asCheckboxState(condition) }
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package eu.kanade.core.prefs
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import tachiyomi.core.preference.Preference
|
||||
|
||||
class PreferenceMutableState<T>(
|
||||
private val preference: Preference<T>,
|
||||
scope: CoroutineScope,
|
||||
) : MutableState<T> {
|
||||
|
||||
private val state = mutableStateOf(preference.get())
|
||||
|
||||
init {
|
||||
preference.changes()
|
||||
.onEach { state.value = it }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
override var value: T
|
||||
get() = state.value
|
||||
set(value) {
|
||||
preference.set(value)
|
||||
}
|
||||
|
||||
override fun component1(): T {
|
||||
return state.value
|
||||
}
|
||||
|
||||
override fun component2(): (T) -> Unit {
|
||||
return { preference.set(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
||||
@@ -1,148 +0,0 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||
generator: (T?, T?) -> R?,
|
||||
): List<R> {
|
||||
if (isEmpty()) return emptyList()
|
||||
val newList = mutableListOf<R>()
|
||||
for (i in -1..lastIndex) {
|
||||
val before = getOrNull(i)
|
||||
before?.let { newList.add(it) }
|
||||
val after = getOrNull(i + 1)
|
||||
val separator = generator.invoke(before, after)
|
||||
separator?.let { newList.add(it) }
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||
*/
|
||||
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
||||
val mutableMap = ConcurrentHashMap<R, V>()
|
||||
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
||||
return mutableMap
|
||||
}
|
||||
|
||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||
if (shouldAdd) {
|
||||
add(value)
|
||||
} else {
|
||||
remove(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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].
|
||||
*
|
||||
* **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>.fastFilterNot(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 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the original collection into pair of lists,
|
||||
* where *first* list contains elements for which [predicate] yielded `true`,
|
||||
* while *second* list contains elements for which [predicate] yielded `false`.
|
||||
*
|
||||
* **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>.fastPartition(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
|
||||
contract { callsInPlace(predicate) }
|
||||
val first = ArrayList<T>()
|
||||
val second = ArrayList<T>()
|
||||
fastForEach {
|
||||
if (predicate(it)) {
|
||||
first.add(it)
|
||||
} else {
|
||||
second.add(it)
|
||||
}
|
||||
}
|
||||
return Pair(first, second)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries not 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>.fastCountNot(predicate: (T) -> Boolean): Int {
|
||||
contract { callsInPlace(predicate) }
|
||||
var count = size
|
||||
fastForEach { if (predicate(it)) --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
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
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()
|
||||
@@ -1,91 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package eu.kanade.domain
|
||||
|
||||
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.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.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
||||
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
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.UpdateManga
|
||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||
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.GetSourcesWithNonLibraryManga
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
||||
import eu.kanade.domain.track.interactor.GetTracks
|
||||
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||
import tachiyomi.data.source.SourceDataRepositoryImpl
|
||||
import tachiyomi.data.track.TrackRepositoryImpl
|
||||
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.history.interactor.GetHistory
|
||||
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.source.repository.SourceDataRepository
|
||||
import tachiyomi.domain.track.repository.TrackRepository
|
||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
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 {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
||||
addFactory { GetCategories(get()) }
|
||||
addFactory { ResetCategoryFlags(get(), get()) }
|
||||
addFactory { SetDisplayModeForCategory(get(), get()) }
|
||||
addFactory { SetSortModeForCategory(get(), get()) }
|
||||
addFactory { CreateCategoryWithName(get(), get()) }
|
||||
addFactory { RenameCategory(get()) }
|
||||
addFactory { ReorderCategory(get()) }
|
||||
addFactory { UpdateCategory(get()) }
|
||||
addFactory { DeleteCategory(get()) }
|
||||
|
||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||
addFactory { GetDuplicateLibraryManga(get()) }
|
||||
addFactory { GetFavorites(get()) }
|
||||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapters(get(), get(), get(), get()) }
|
||||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
|
||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||
addFactory { DeleteTrack(get()) }
|
||||
addFactory { GetTracksPerManga(get()) }
|
||||
addFactory { GetTracks(get()) }
|
||||
addFactory { InsertTrack(get()) }
|
||||
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
||||
|
||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
addFactory { UpsertHistory(get()) }
|
||||
addFactory { RemoveHistory(get()) }
|
||||
addFactory { GetTotalReadDuration(get()) }
|
||||
|
||||
addFactory { DeleteDownload(get(), get()) }
|
||||
|
||||
addFactory { GetExtensionsByType(get(), get()) }
|
||||
addFactory { GetExtensionSources(get()) }
|
||||
addFactory { GetExtensionLanguages(get(), get()) }
|
||||
|
||||
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
||||
addFactory { GetUpdates(get()) }
|
||||
|
||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||
addFactory { GetRemoteManga(get()) }
|
||||
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
||||
addFactory { SetMigrateSorting(get()) }
|
||||
addFactory { ToggleLanguage(get()) }
|
||||
addFactory { ToggleSource(get()) }
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package eu.kanade.domain
|
||||
|
||||
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.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.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.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.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.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.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.DeleteSourceRepos
|
||||
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.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.SetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import exh.search.SearchEngine
|
||||
import tachiyomi.data.manga.CustomMangaRepositoryImpl
|
||||
import tachiyomi.data.manga.FavoritesEntryRepositoryImpl
|
||||
import tachiyomi.data.manga.MangaMergeRepositoryImpl
|
||||
import tachiyomi.data.manga.MangaMetadataRepositoryImpl
|
||||
import tachiyomi.data.source.FeedSavedSearchRepositoryImpl
|
||||
import tachiyomi.data.source.SavedSearchRepositoryImpl
|
||||
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
||||
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
|
||||
import tachiyomi.domain.manga.repository.CustomMangaRepository
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
import tachiyomi.domain.source.repository.FeedSavedSearchRepository
|
||||
import tachiyomi.domain.source.repository.SavedSearchRepository
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
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
|
||||
|
||||
class SYDomainModule : InjektModule {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addFactory { GetShowLatest(get()) }
|
||||
addFactory { ToggleExcludeFromDataSaver(get()) }
|
||||
addFactory { SetSourceCategories(get()) }
|
||||
addFactory { SetMangaFilteredScanlators(get()) }
|
||||
addFactory { GetAllManga(get()) }
|
||||
addFactory { GetMangaBySource(get()) }
|
||||
addFactory { DeleteChapters(get()) }
|
||||
addFactory { DeleteMangaById(get()) }
|
||||
addFactory { FilterSerializer() }
|
||||
addFactory { GetHistoryByMangaId(get()) }
|
||||
addFactory { GetChapterByUrl(get()) }
|
||||
addFactory { CreateSourceRepo(get()) }
|
||||
addFactory { DeleteSourceRepos(get()) }
|
||||
addFactory { GetSourceRepos(get()) }
|
||||
addFactory { GetSourceCategories(get()) }
|
||||
addFactory { CreateSourceCategory(get()) }
|
||||
addFactory { RenameSourceCategory(get(), get()) }
|
||||
addFactory { DeleteSourceCategory(get()) }
|
||||
addFactory { GetSortTag(get()) }
|
||||
addFactory { CreateSortTag(get(), get()) }
|
||||
addFactory { DeleteSortTag(get(), get()) }
|
||||
addFactory { ReorderSortTag(get(), get()) }
|
||||
addFactory { GetPagePreviews(get(), get()) }
|
||||
addFactory { SearchEngine() }
|
||||
|
||||
// Required for [MetadataSource]
|
||||
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
||||
addFactory<MetadataSource.GetFlatMetadataById> { GetFlatMetadataById(get()) }
|
||||
addFactory<MetadataSource.InsertFlatMetadata> { InsertFlatMetadata(get()) }
|
||||
|
||||
addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) }
|
||||
addFactory { GetFlatMetadataById(get()) }
|
||||
addFactory { InsertFlatMetadata(get()) }
|
||||
addFactory { GetExhFavoriteMangaWithMetadata(get()) }
|
||||
addFactory { GetSearchMetadata(get()) }
|
||||
addFactory { GetSearchTags(get()) }
|
||||
addFactory { GetSearchTitles(get()) }
|
||||
addFactory { GetIdsOfFavoriteMangaWithMetadata(get()) }
|
||||
|
||||
addSingletonFactory<MangaMergeRepository> { MangaMergeRepositoryImpl(get()) }
|
||||
addFactory { GetMergedManga(get()) }
|
||||
addFactory { GetMergedMangaById(get()) }
|
||||
addFactory { GetMergedReferencesById(get()) }
|
||||
addFactory { GetMergedChapterByMangaId(get(), get()) }
|
||||
addFactory { InsertMergedReference(get()) }
|
||||
addFactory { UpdateMergedSettings(get()) }
|
||||
addFactory { DeleteByMergeId(get()) }
|
||||
addFactory { DeleteMergeById(get()) }
|
||||
addFactory { GetMergedMangaForDownloading(get()) }
|
||||
|
||||
addSingletonFactory<FavoritesEntryRepository> { FavoritesEntryRepositoryImpl(get()) }
|
||||
addFactory { GetFavoriteEntries(get()) }
|
||||
addFactory { InsertFavoriteEntries(get()) }
|
||||
addFactory { DeleteFavoriteEntries(get()) }
|
||||
|
||||
addSingletonFactory<SavedSearchRepository> { SavedSearchRepositoryImpl(get()) }
|
||||
addFactory { GetSavedSearchById(get()) }
|
||||
addFactory { GetSavedSearchBySourceId(get()) }
|
||||
addFactory { DeleteSavedSearchById(get()) }
|
||||
addFactory { InsertSavedSearch(get()) }
|
||||
addFactory { GetExhSavedSearch(get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<FeedSavedSearchRepository> { FeedSavedSearchRepositoryImpl(get()) }
|
||||
addFactory { InsertFeedSavedSearch(get()) }
|
||||
addFactory { DeleteFeedSavedSearchById(get()) }
|
||||
addFactory { GetFeedSavedSearchGlobal(get()) }
|
||||
addFactory { GetFeedSavedSearchBySourceId(get()) }
|
||||
addFactory { CountFeedSavedSearchGlobal(get()) }
|
||||
addFactory { CountFeedSavedSearchBySourceId(get()) }
|
||||
addFactory { GetSavedSearchGlobalFeed(get()) }
|
||||
addFactory { GetSavedSearchBySourceIdFeed(get()) }
|
||||
|
||||
addSingletonFactory<CustomMangaRepository> { CustomMangaRepositoryImpl(get<Application>()) }
|
||||
addFactory { GetCustomMangaInfo(get()) }
|
||||
addFactory { SetCustomMangaInfo(get()) }
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
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())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
class BasePreferences(
|
||||
val context: Context,
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", 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 acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller
|
||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.getEnum
|
||||
|
||||
class ExtensionInstallerPreference(
|
||||
private val context: Context,
|
||||
preferenceStore: PreferenceStore,
|
||||
) : Preference<ExtensionInstaller> {
|
||||
|
||||
private val basePref = preferenceStore.getEnum(key(), defaultValue())
|
||||
|
||||
override fun key() = "extension_installer"
|
||||
|
||||
val entries get() = ExtensionInstaller.values().run {
|
||||
if (context.hasMiuiPackageInstaller) {
|
||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||
} else {
|
||||
toList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun defaultValue() = if (context.hasMiuiPackageInstaller) {
|
||||
ExtensionInstaller.LEGACY
|
||||
} else {
|
||||
ExtensionInstaller.PACKAGEINSTALLER
|
||||
}
|
||||
|
||||
private fun check(value: ExtensionInstaller): ExtensionInstaller {
|
||||
when (value) {
|
||||
ExtensionInstaller.PACKAGEINSTALLER -> {
|
||||
if (context.hasMiuiPackageInstaller) return ExtensionInstaller.LEGACY
|
||||
}
|
||||
ExtensionInstaller.SHIZUKU -> {
|
||||
if (!context.isShizukuInstalled) return defaultValue()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
override fun get(): ExtensionInstaller {
|
||||
val value = basePref.get()
|
||||
val checkedValue = check(value)
|
||||
if (value != checkedValue) {
|
||||
basePref.set(checkedValue)
|
||||
}
|
||||
return checkedValue
|
||||
}
|
||||
|
||||
override fun set(value: ExtensionInstaller) {
|
||||
basePref.set(check(value))
|
||||
}
|
||||
|
||||
override fun isSet() = basePref.isSet()
|
||||
|
||||
override fun delete() = basePref.delete()
|
||||
|
||||
override fun changes() = basePref.changes()
|
||||
|
||||
override fun stateIn(scope: CoroutineScope) = basePref.stateIn(scope)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class CreateCategoryWithName(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
private val preferences: LibraryPreferences,
|
||||
) {
|
||||
|
||||
private val initialFlags: Long
|
||||
get() {
|
||||
val sort = preferences.librarySortingMode().get()
|
||||
return preferences.libraryDisplayMode().get().flag or
|
||||
sort.type.flag or
|
||||
sort.direction.flag
|
||||
}
|
||||
|
||||
suspend fun await(name: String): Result = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll()
|
||||
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
||||
val newCategory = Category(
|
||||
id = 0,
|
||||
name = name,
|
||||
order = nextOrder,
|
||||
flags = initialFlags,
|
||||
)
|
||||
|
||||
try {
|
||||
categoryRepository.insert(newCategory)
|
||||
Result.Success(/* SY --> */newCategory/* SY <-- */)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
// SY -->
|
||||
data class Success(val category: Category) : Result()
|
||||
|
||||
// SY <--
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class RenameCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
||||
val update = CategoryUpdate(
|
||||
id = categoryId,
|
||||
name = name,
|
||||
)
|
||||
|
||||
try {
|
||||
categoryRepository.updatePartial(update)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category, name: String) = await(category.id, name)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import java.util.Collections
|
||||
|
||||
class ReorderCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
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 {
|
||||
val categories = categoryRepository.getAll()
|
||||
.filterNot(Category::isSystemCategory)
|
||||
.toMutableList()
|
||||
|
||||
val currentIndex = categories.indexOfFirst { it.id == category.id }
|
||||
if (currentIndex == -1) {
|
||||
return@withNonCancellableContext Result.Unchanged
|
||||
}
|
||||
|
||||
val newPosition = when (moveTo) {
|
||||
MoveTo.UP -> currentIndex - 1
|
||||
MoveTo.DOWN -> currentIndex + 1
|
||||
}.toInt()
|
||||
|
||||
try {
|
||||
Collections.swap(categories, currentIndex, newPosition)
|
||||
|
||||
val updates = categories.mapIndexed { index, category ->
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
order = index.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
categoryRepository.updatePartial(updates)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object Unchanged : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
|
||||
private enum class MoveTo {
|
||||
UP,
|
||||
DOWN,
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetMangaCategories(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
|
||||
try {
|
||||
mangaRepository.setMangaCategories(mangaId, categoryIds)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class UpdateCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(payload: CategoryUpdate): Result = withNonCancellableContext {
|
||||
try {
|
||||
categoryRepository.updatePartial(payload)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class Error(val error: Exception) : Result()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class DeleteChapters(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(chapters: List<Long>) {
|
||||
chapterRepository.removeChaptersWithIds(chapters)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetChapter(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): Chapter? {
|
||||
return try {
|
||||
chapterRepository.getChapterById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(url: String, mangaId: Long): Chapter? {
|
||||
return try {
|
||||
chapterRepository.getChapterByUrlAndMangaId(url, mangaId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetChapterByMangaId(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): List<Chapter> {
|
||||
return try {
|
||||
chapterRepository.getChapterByMangaId(mangaId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetChapterByUrl(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(url: String): List<Chapter> {
|
||||
return try {
|
||||
chapterRepository.getChapterByUrl(url)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.MergedMangaReference
|
||||
|
||||
class GetMergedChapterByMangaId(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
private val getMergedReferencesById: GetMergedReferencesById,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, dedupe: Boolean = true): List<Chapter> {
|
||||
return transformMergedChapters(getMergedReferencesById.await(mangaId), getFromDatabase(mangaId), dedupe)
|
||||
}
|
||||
|
||||
suspend fun subscribe(mangaId: Long, dedupe: Boolean = true): Flow<List<Chapter>> {
|
||||
return try {
|
||||
chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId)
|
||||
.combine(getMergedReferencesById.subscribe(mangaId)) { chapters, references ->
|
||||
transformMergedChapters(references, chapters, dedupe)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
flowOf(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFromDatabase(mangaId: Long): List<Chapter> {
|
||||
return try {
|
||||
chapterRepository.getMergedChapterByMangaId(mangaId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun transformMergedChapters(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>, dedupe: Boolean): List<Chapter> {
|
||||
return if (dedupe) dedupeChapterList(mangaReferences, chapterList) else chapterList
|
||||
}
|
||||
|
||||
private fun dedupeChapterList(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
|
||||
return when (mangaReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }?.chapterSortMode) {
|
||||
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE, MergedMangaReference.CHAPTER_SORT_NONE -> chapterList
|
||||
MergedMangaReference.CHAPTER_SORT_PRIORITY -> dedupeByPriority(mangaReferences, chapterList)
|
||||
MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS -> {
|
||||
findSourceWithMostChapters(chapterList)?.let { mangaId ->
|
||||
chapterList.filter { it.mangaId == mangaId }
|
||||
} ?: chapterList
|
||||
}
|
||||
MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER -> {
|
||||
findSourceWithHighestChapterNumber(chapterList)?.let { mangaId ->
|
||||
chapterList.filter { it.mangaId == mangaId }
|
||||
} ?: chapterList
|
||||
}
|
||||
else -> chapterList
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSourceWithMostChapters(chapterList: List<Chapter>): Long? {
|
||||
return chapterList.groupBy { it.mangaId }.maxByOrNull { it.value.size }?.key
|
||||
}
|
||||
|
||||
private fun findSourceWithHighestChapterNumber(chapterList: List<Chapter>): Long? {
|
||||
return chapterList.maxByOrNull { it.chapterNumber }?.mangaId
|
||||
}
|
||||
|
||||
private fun dedupeByPriority(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
|
||||
val sortedChapterList = mutableListOf<Chapter>()
|
||||
|
||||
var existingChapterIndex: Int
|
||||
chapterList.groupBy { it.mangaId }
|
||||
.entries
|
||||
.sortedBy { (mangaId) ->
|
||||
mangaReferences.find { it.mangaId == mangaId }?.chapterPriority ?: Int.MAX_VALUE
|
||||
}
|
||||
.forEach { (_, chapters) ->
|
||||
existingChapterIndex = -1
|
||||
chapters.forEach { chapter ->
|
||||
val oldChapterIndex = existingChapterIndex
|
||||
if (chapter.isRecognizedNumber) {
|
||||
existingChapterIndex = sortedChapterList.indexOfFirst {
|
||||
it.isRecognizedNumber && it.chapterNumber == chapter.chapterNumber && // check if the chapter is not already there
|
||||
it.mangaId != chapter.mangaId // allow multiple chapters of the same number from the same source
|
||||
}
|
||||
if (existingChapterIndex == -1) {
|
||||
sortedChapterList.add(oldChapterIndex + 1, chapter)
|
||||
existingChapterIndex = oldChapterIndex + 1
|
||||
}
|
||||
} else {
|
||||
sortedChapterList.add(oldChapterIndex + 1, chapter)
|
||||
existingChapterIndex = oldChapterIndex + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sortedChapterList.mapIndexed { index, chapter ->
|
||||
chapter.copy(sourceOrder = index.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class SetMangaDefaultChapterFlags(
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
private val setMangaChapterFlags: SetMangaChapterFlags,
|
||||
private val getFavorites: GetFavorites,
|
||||
) {
|
||||
|
||||
suspend fun await(manga: Manga) {
|
||||
withNonCancellableContext {
|
||||
with(libraryPreferences) {
|
||||
setMangaChapterFlags.awaitSetAllFlags(
|
||||
mangaId = manga.id,
|
||||
unreadFilter = filterChapterByRead().get(),
|
||||
downloadedFilter = filterChapterByDownloaded().get(),
|
||||
bookmarkedFilter = filterChapterByBookmarked().get(),
|
||||
sortingMode = sortChapterBySourceOrNumber().get(),
|
||||
sortingDirection = sortChapterByAscendingOrDescending().get(),
|
||||
displayMode = displayChapterByNameOrNumber().get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitAll() {
|
||||
withNonCancellableContext {
|
||||
getFavorites.await().forEach { await(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetReadStatus(
|
||||
private val downloadPreferences: DownloadPreferences,
|
||||
private val deleteDownload: DeleteDownload,
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
// SY -->
|
||||
private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
|
||||
// SY <--
|
||||
) {
|
||||
|
||||
private val mapper = { chapter: Chapter, read: Boolean ->
|
||||
ChapterUpdate(
|
||||
read = read,
|
||||
lastPageRead = if (!read) 0 else null,
|
||||
id = chapter.id,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||
val chaptersToUpdate = chapters.filter {
|
||||
when (read) {
|
||||
true -> !it.read
|
||||
false -> it.read || it.lastPageRead > 0
|
||||
}
|
||||
}
|
||||
if (chaptersToUpdate.isEmpty()) {
|
||||
return@withNonCancellableContext Result.NoChapters
|
||||
}
|
||||
|
||||
try {
|
||||
chapterRepository.updateAll(
|
||||
chaptersToUpdate.map { mapper(it, read) },
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
return@withNonCancellableContext Result.InternalError(e)
|
||||
}
|
||||
|
||||
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
|
||||
chaptersToUpdate
|
||||
.groupBy { it.mangaId }
|
||||
.forEach { (mangaId, chapters) ->
|
||||
deleteDownload.awaitAll(
|
||||
manga = mangaRepository.getMangaById(mangaId),
|
||||
chapters = chapters.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Result.Success
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, read: Boolean): Result = withNonCancellableContext {
|
||||
await(
|
||||
read = read,
|
||||
chapters = chapterRepository
|
||||
.getChapterByMangaId(mangaId)
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
private suspend fun awaitMerged(mangaId: Long, read: Boolean) = withNonCancellableContext f@{
|
||||
return@f await(
|
||||
read = read,
|
||||
chapters = getMergedChapterByMangaId
|
||||
.await(mangaId, dedupe = false)
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun await(manga: Manga, read: Boolean) = if (manga.source == MERGED_SOURCE_ID) {
|
||||
awaitMerged(manga.id, read)
|
||||
} else {
|
||||
await(manga.id, read)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoChapters : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||
import eu.kanade.domain.chapter.model.toSChapter
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.isLocal
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||
import exh.source.isEhBasedManga
|
||||
import tachiyomi.data.chapter.ChapterSanitizer
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Long.max
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
class SyncChaptersWithSource(
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||
) {
|
||||
|
||||
/**
|
||||
* Method to synchronize db chapters with source ones
|
||||
*
|
||||
* @param rawSourceChapters the chapters from the source.
|
||||
* @param manga the manga the chapters belong to.
|
||||
* @param source the source the manga belongs to.
|
||||
* @return Newly added chapters
|
||||
*/
|
||||
suspend fun await(
|
||||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
source: Source,
|
||||
): List<Chapter> {
|
||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||
throw NoChaptersException()
|
||||
}
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sChapter ->
|
||||
Chapter.create()
|
||||
.copyFromSChapter(sChapter)
|
||||
.copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) })
|
||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||
}
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
||||
|
||||
// Chapters from the source not in db.
|
||||
val toAdd = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters whose metadata have changed.
|
||||
val toChange = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters from the db not in source.
|
||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
||||
sourceChapters.any { sourceChapter ->
|
||||
dbChapter.url == sourceChapter.url
|
||||
}
|
||||
}
|
||||
|
||||
val rightNow = Date().time
|
||||
|
||||
// Used to not set upload date of older chapters
|
||||
// to a higher value than newer chapters
|
||||
var maxSeenUploadDate = 0L
|
||||
|
||||
val sManga = manga.toSManga()
|
||||
for (sourceChapter in sourceChapters) {
|
||||
var chapter = sourceChapter
|
||||
|
||||
// Update metadata from source if necessary.
|
||||
if (source is HttpSource) {
|
||||
val sChapter = chapter.toSChapter()
|
||||
source.prepareNewChapter(sChapter, sManga)
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
|
||||
// Recognize chapter number for the chapter.
|
||||
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
|
||||
chapter = chapter.copy(chapterNumber = chapterNumber)
|
||||
|
||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||
|
||||
if (dbChapter == null) {
|
||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
||||
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
|
||||
chapter.copy(dateUpload = altDateUpload)
|
||||
} else {
|
||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||
chapter
|
||||
}
|
||||
toAdd.add(toAddChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)
|
||||
|
||||
if (shouldRenameChapter) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||
}
|
||||
var toChangeChapter = dbChapter.copy(
|
||||
name = chapter.name,
|
||||
chapterNumber = chapter.chapterNumber,
|
||||
scanlator = chapter.scanlator,
|
||||
sourceOrder = chapter.sourceOrder,
|
||||
)
|
||||
if (chapter.dateUpload != 0L) {
|
||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||
}
|
||||
toChange.add(toChangeChapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val reAdded = mutableListOf<Chapter>()
|
||||
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||
|
||||
toDelete.forEach { chapter ->
|
||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||
deletedChapterNumbers.add(chapter.chapterNumber)
|
||||
}
|
||||
|
||||
val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
|
||||
.associate { it.chapterNumber to it.dateFetch }
|
||||
|
||||
// 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.
|
||||
var itemCount = toAdd.size
|
||||
var updatedToAdd = toAdd.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
||||
|
||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
|
||||
chapter = chapter.copy(
|
||||
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
||||
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
||||
)
|
||||
|
||||
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||
chapter = chapter.copy(dateFetch = it)
|
||||
}
|
||||
|
||||
reAdded.add(chapter)
|
||||
|
||||
chapter
|
||||
}
|
||||
|
||||
// --> EXH (carry over reading progress)
|
||||
if (manga.isEhBasedManga()) {
|
||||
val finalAdded = updatedToAdd.subtract(reAdded)
|
||||
if (finalAdded.isNotEmpty()) {
|
||||
val max = dbChapters.maxOfOrNull { it.lastPageRead }
|
||||
if (max != null && max > 0) {
|
||||
updatedToAdd = updatedToAdd.map {
|
||||
if (it !in reAdded) {
|
||||
it.copy(lastPageRead = max)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// <-- EXH
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
val toDeleteIds = toDelete.map { it.id }
|
||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||
}
|
||||
|
||||
if (updatedToAdd.isNotEmpty()) {
|
||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
updateManga.awaitUpdateLastUpdate(manga.id)
|
||||
|
||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class UpdateChapter(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(chapterUpdate: ChapterUpdate) {
|
||||
try {
|
||||
chapterRepository.update(chapterUpdate)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitAll(chapterUpdates: List<ChapterUpdate>) {
|
||||
try {
|
||||
chapterRepository.updateAll(chapterUpdates)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package eu.kanade.domain.chapter.model
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||
|
||||
// TODO: Remove when all deps are migrated
|
||||
fun Chapter.toSChapter(): SChapter {
|
||||
return SChapter.create().also {
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
|
||||
fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||
return this.copy(
|
||||
name = sChapter.name,
|
||||
url = sChapter.url,
|
||||
dateUpload = sChapter.date_upload,
|
||||
chapterNumber = sChapter.chapter_number,
|
||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.id = id
|
||||
it.manga_id = mangaId
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.scanlator = scanlator
|
||||
it.read = read
|
||||
it.bookmark = bookmark
|
||||
it.last_page_read = lastPageRead.toInt()
|
||||
it.date_fetch = dateFetch
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.source_order = sourceOrder.toInt()
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package eu.kanade.domain.chapter.model
|
||||
|
||||
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.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.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
val bookmarkedFilter = manga.bookmarkedFilter
|
||||
|
||||
return filter { chapter ->
|
||||
when (unreadFilter) {
|
||||
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 { 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
val bookmarkedFilter = manga.bookmarkedFilter
|
||||
return asSequence()
|
||||
.filter { (chapter) ->
|
||||
when (unreadFilter) {
|
||||
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) }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package eu.kanade.domain.download.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class DeleteDownload(
|
||||
private val sourceManager: SourceManager,
|
||||
private val downloadManager: DownloadManager,
|
||||
) {
|
||||
|
||||
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
||||
sourceManager.get(manga.source)?.let { source ->
|
||||
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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())
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class GetExtensionLanguages(
|
||||
private val preferences: SourcePreferences,
|
||||
private val extensionManager: ExtensionManager,
|
||||
) {
|
||||
fun subscribe(): Flow<List<String>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { enabledLanguage, availableExtensions ->
|
||||
availableExtensions
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
listOf(ext.lang)
|
||||
} else {
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
}
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetExtensionSources(
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(extension: Extension.Installed): Flow<List<ExtensionSourceItem>> {
|
||||
val isMultiSource = extension.sources.size > 1
|
||||
val isMultiLangSingleSource =
|
||||
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
|
||||
|
||||
return preferences.disabledSources().changes().map { disabledSources ->
|
||||
fun Source.isEnabled() = id.toString() !in disabledSources
|
||||
|
||||
extension.sources
|
||||
.map { source ->
|
||||
ExtensionSourceItem(
|
||||
source = source,
|
||||
enabled = source.isEnabled(),
|
||||
labelAsName = isMultiSource && isMultiLangSingleSource.not(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ExtensionSourceItem(
|
||||
val source: Source,
|
||||
val enabled: Boolean,
|
||||
val labelAsName: Boolean,
|
||||
)
|
||||
@@ -1,60 +0,0 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.extension.model.Extensions
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class GetExtensionsByType(
|
||||
private val preferences: SourcePreferences,
|
||||
private val extensionManager: ExtensionManager,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Extensions> {
|
||||
val showNsfwSources = preferences.showNsfwSource().get()
|
||||
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
extensionManager.installedExtensionsFlow,
|
||||
extensionManager.untrustedExtensionsFlow,
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||
val (updates, installed) = _installed
|
||||
.filter { (showNsfwSources || it.isNsfw.not()) }
|
||||
.sortedWith(
|
||||
compareBy<Extension.Installed> { it.isObsolete.not() /* SY --> */ && it.isRedundant.not() /* SY <-- */ }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
.partition { it.hasUpdate }
|
||||
|
||||
val untrusted = _untrusted
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
|
||||
val available = _available
|
||||
.filter { extension ->
|
||||
_installed.none { it.pkgName == extension.pkgName } &&
|
||||
_untrusted.none { it.pkgName == extension.pkgName } &&
|
||||
(showNsfwSources || extension.isNsfw.not())
|
||||
}
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
||||
}
|
||||
ext.sources.filter { it.lang in _activeLanguages }
|
||||
.map {
|
||||
ext.copy(
|
||||
name = it.name,
|
||||
lang = it.lang,
|
||||
pkgName = "${ext.pkgName}-${it.id}",
|
||||
sources = listOf(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
|
||||
Extensions(updates, installed, available, untrusted)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package eu.kanade.domain.extension.model
|
||||
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
|
||||
data class Extensions(
|
||||
val updates: List<Extension.Installed>,
|
||||
val installed: List<Extension.Installed>,
|
||||
val available: List<Extension.Available>,
|
||||
val untrusted: List<Extension.Untrusted>,
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
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.isEhBasedManga
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import kotlin.math.max
|
||||
|
||||
class GetNextChapters(
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
// SY -->
|
||||
private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
|
||||
// SY <--
|
||||
private val getManga: GetManga,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(onlyUnread: Boolean = true): List<Chapter> {
|
||||
val history = historyRepository.getLastHistory() ?: return emptyList()
|
||||
return await(history.mangaId, history.chapterId, onlyUnread)
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||
|
||||
// SY -->
|
||||
if (manga.source == MERGED_SOURCE_ID) {
|
||||
val chapters = getMergedChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
return if (onlyUnread) {
|
||||
chapters.filterNot { it.read }
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
if (manga.isEhBasedManga()) {
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
return if (onlyUnread) {
|
||||
chapters.takeLast(1).takeUnless { it.firstOrNull()?.read == true }.orEmpty()
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
return if (onlyUnread) {
|
||||
chapters.filterNot { it.read }
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, fromChapterId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
val chapters = await(mangaId, onlyUnread)
|
||||
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
|
||||
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)
|
||||
|
||||
if (onlyUnread) {
|
||||
return nextChapters
|
||||
}
|
||||
|
||||
// The "next chapter" is either:
|
||||
// - The current chapter if it isn't completely read
|
||||
// - The chapters after the current chapter if the current one is completely read
|
||||
val fromChapter = chapters.getOrNull(currChapterIndex)
|
||||
return if (fromChapter != null && !fromChapter.read) {
|
||||
nextChapters
|
||||
} else {
|
||||
nextChapters.drop(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package eu.kanade.domain.library.model
|
||||
|
||||
enum class GroupLibraryMode {
|
||||
GLOBAL,
|
||||
ALL_BUT_UNGROUPED,
|
||||
ALL,
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
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 <--
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
|
||||
class CreateSortTag(
|
||||
private val preferences: LibraryPreferences,
|
||||
private val getSortTag: GetSortTag,
|
||||
) {
|
||||
|
||||
fun await(tag: String): Result {
|
||||
// Do not allow duplicate categories.
|
||||
// Do not allow duplicate categories.
|
||||
if (tagExists(tag.trim())) {
|
||||
return Result.TagExists
|
||||
}
|
||||
|
||||
val size = preferences.sortTagsForLibrary().get().size
|
||||
|
||||
preferences.sortTagsForLibrary() += encodeTag(size, tag)
|
||||
|
||||
return Result.Success
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object TagExists : Result()
|
||||
object Success : Result()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a tag with the given name already exists.
|
||||
*/
|
||||
private fun tagExists(name: String): Boolean {
|
||||
return getSortTag.await().any { it.equals(name) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun encodeTag(index: Int, tag: String) = "$index|${tag.trim()}"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class DeleteByMergeId(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long) {
|
||||
return mangaMergeRepository.deleteByMergeId(id)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
|
||||
class DeleteFavoriteEntries(
|
||||
private val favoriteEntryRepository: FavoritesEntryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await() {
|
||||
return favoriteEntryRepository.deleteAll()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class DeleteMangaById(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long) {
|
||||
return mangaRepository.deleteManga(id)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class DeleteMergeById(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long) {
|
||||
return mangaMergeRepository.deleteById(id)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
|
||||
class DeleteSortTag(
|
||||
private val preferences: LibraryPreferences,
|
||||
private val getSortTag: GetSortTag,
|
||||
) {
|
||||
|
||||
fun await(tag: String) {
|
||||
preferences.sortTagsForLibrary().set(
|
||||
(getSortTag.await() - tag).mapIndexed { index, s ->
|
||||
CreateSortTag.encodeTag(index, s)
|
||||
}.toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetAllManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Manga> {
|
||||
return mangaRepository.getAll()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
|
||||
class GetExhFavoriteMangaWithMetadata(
|
||||
private val mangaMetadataRepository: MangaMetadataRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Manga> {
|
||||
return mangaMetadataRepository.getExhFavoriteMangaWithMetadata()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
|
||||
class GetFavoriteEntries(
|
||||
private val favoriteEntryRepository: FavoritesEntryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<FavoriteEntry> {
|
||||
return favoriteEntryRepository.selectAll()
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetFavorites(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Manga> {
|
||||
return mangaRepository.getFavorites()
|
||||
}
|
||||
|
||||
fun subscribe(sourceId: Long): Flow<List<Manga>> {
|
||||
return mangaRepository.getFavoritesBySourceId(sourceId)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import exh.metadata.metadata.base.FlatMetadata
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
|
||||
class GetFlatMetadataById(
|
||||
private val mangaMetadataRepository: MangaMetadataRepository,
|
||||
) : MetadataSource.GetFlatMetadataById {
|
||||
|
||||
override suspend fun await(id: Long): FlatMetadata? {
|
||||
return try {
|
||||
val meta = mangaMetadataRepository.getMetadataById(id)
|
||||
return if (meta != null) {
|
||||
val tags = mangaMetadataRepository.getTagsById(id)
|
||||
val titles = mangaMetadataRepository.getTitlesById(id)
|
||||
|
||||
FlatMetadata(meta, tags, titles)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribe(id: Long): Flow<FlatMetadata?> {
|
||||
return combine(
|
||||
mangaMetadataRepository.subscribeMetadataById(id),
|
||||
mangaMetadataRepository.subscribeTagsById(id),
|
||||
mangaMetadataRepository.subscribeTitlesById(id),
|
||||
) { meta, tags, titles ->
|
||||
if (meta != null) {
|
||||
FlatMetadata(meta, tags, titles)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
|
||||
class GetIdsOfFavoriteMangaWithMetadata(
|
||||
private val mangaMetadataRepository: MangaMetadataRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Long> {
|
||||
return mangaMetadataRepository.getIdsOfFavoriteMangaWithMetadata()
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.retry
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetLibraryManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<LibraryManga> {
|
||||
return mangaRepository.getLibraryManga()
|
||||
}
|
||||
|
||||
fun subscribe(): Flow<List<LibraryManga>> {
|
||||
return mangaRepository.getLibraryMangaAsFlow()
|
||||
// SY -->
|
||||
.let {
|
||||
var retries = 0
|
||||
it.retry {
|
||||
(retries++ < 3) && it is NullPointerException
|
||||
}.onEach {
|
||||
retries = 0
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) : MetadataSource.GetMangaId {
|
||||
|
||||
suspend fun await(id: Long): Manga? {
|
||||
return try {
|
||||
mangaRepository.getMangaById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<Manga> {
|
||||
return mangaRepository.getMangaByIdAsFlow(id)
|
||||
}
|
||||
|
||||
fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
|
||||
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
suspend fun await(url: String, sourceId: Long): Manga? {
|
||||
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
|
||||
}
|
||||
|
||||
override suspend fun awaitId(url: String, sourceId: Long): Long? {
|
||||
return await(url, sourceId)?.id
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetMangaBySource(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(sourceId: Long): List<Manga> {
|
||||
return mangaRepository.getMangaBySourceId(sourceId)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetMangaWithChapters(
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
|
||||
return combine(
|
||||
mangaRepository.getMangaByIdAsFlow(id),
|
||||
chapterRepository.getChapterByMangaIdAsFlow(id),
|
||||
) { manga, chapters ->
|
||||
Pair(manga, chapters)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitManga(id: Long): Manga {
|
||||
return mangaRepository.getMangaById(id)
|
||||
}
|
||||
|
||||
suspend fun awaitChapters(id: Long): List<Chapter> {
|
||||
return chapterRepository.getChapterByMangaId(id)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class GetMergedManga(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Manga> {
|
||||
return try {
|
||||
mangaMergeRepository.getMergedManga()
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(): Flow<List<Manga>> {
|
||||
return mangaMergeRepository.subscribeMergedManga()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class GetMergedMangaById(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): List<Manga> {
|
||||
return try {
|
||||
mangaMergeRepository.getMergedMangaById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<List<Manga>> {
|
||||
return mangaMergeRepository.subscribeMergedMangaById(id)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class GetMergedMangaForDownloading(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mergeId: Long): List<Manga> {
|
||||
return mangaMergeRepository.getMergeMangaForDownloading(mergeId)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.MergedMangaReference
|
||||
import tachiyomi.domain.manga.repository.MangaMergeRepository
|
||||
|
||||
class GetMergedReferencesById(
|
||||
private val mangaMergeRepository: MangaMergeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): List<MergedMangaReference> {
|
||||
return try {
|
||||
mangaMergeRepository.getReferencesById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<List<MergedMangaReference>> {
|
||||
return mangaMergeRepository.subscribeReferencesById(id)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.model.toSChapter
|
||||
import eu.kanade.domain.manga.model.PagePreview
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.cache.PagePreviewCache
|
||||
import eu.kanade.tachiyomi.source.PagePreviewSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import exh.source.getMainSource
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class GetPagePreviews(
|
||||
private val pagePreviewCache: PagePreviewCache,
|
||||
private val getChapters: GetChapterByMangaId,
|
||||
) {
|
||||
|
||||
suspend fun await(manga: Manga, source: Source, page: Int): Result {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val source = source.getMainSource<PagePreviewSource>() ?: return Result.Unused
|
||||
val chapters = getChapters.await(manga.id).sortedByDescending { it.sourceOrder }
|
||||
val chapterIds = chapters.map { it.id }
|
||||
return try {
|
||||
val pagePreviews = try {
|
||||
pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
|
||||
} catch (e: Exception) {
|
||||
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
|
||||
pagePreviewCache.putPageListToCache(manga, chapterIds, it)
|
||||
}
|
||||
}
|
||||
Result.Success(
|
||||
pagePreviews.pagePreviews.map {
|
||||
PagePreview(it.index, it.imageUrl, source.id)
|
||||
},
|
||||
pagePreviews.hasNextPage,
|
||||
pagePreviews.pagePreviewPages,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Unused : Result()
|
||||
data class Success(
|
||||
val pagePreviews: List<PagePreview>,
|
||||
val hasNextPage: Boolean,
|
||||
val pageCount: Int?,
|
||||
) : Result()
|
||||
data class Error(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import exh.metadata.sql.models.SearchMetadata
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
|
||||
class GetSearchMetadata(
|
||||
private val mangaMetadataRepository: MangaMetadataRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): SearchMetadata? {
|
||||
return mangaMetadataRepository.getMetadataById(mangaId)
|
||||
}
|
||||
|
||||
suspend fun await(): List<SearchMetadata> {
|
||||
return mangaMetadataRepository.getSearchMetadata()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import exh.metadata.sql.models.SearchTag
|
||||
import tachiyomi.domain.manga.repository.MangaMetadataRepository
|
||||
|
||||
class GetSearchTags(
|
||||
private val mangaMetadataRepository: MangaMetadataRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): List<SearchTag> {
|
||||
return mangaMetadataRepository.getTagsById(mangaId)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user