Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
706fa82a37 | |||
57c54fa275 | |||
38c5234e46 | |||
767ee164e6 |
@ -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
|
|
24
.gitattributes
vendored
@ -1,24 +0,0 @@
|
|||||||
* text=auto
|
|
||||||
* text eol=lf
|
|
||||||
|
|
||||||
# Windows forced line-endings
|
|
||||||
/.idea/* text eol=crlf
|
|
||||||
|
|
||||||
# Gradle wrapper
|
|
||||||
*.jar binary
|
|
||||||
|
|
||||||
# Images
|
|
||||||
*.webp binary
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.ico binary
|
|
||||||
*.gz binary
|
|
||||||
*.zip binary
|
|
||||||
*.7z binary
|
|
||||||
*.ttf binary
|
|
||||||
*.eot binary
|
|
||||||
*.woff binary
|
|
||||||
*.pyc binary
|
|
||||||
*.swp binary
|
|
31
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Bugs
|
||||||
|
* Include version (Setting > About > Version)
|
||||||
|
* If not latest, try updating, it may have already been solved
|
||||||
|
* Dev 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), include results and device names, OS, modifications (root, Xposed)
|
||||||
|
* **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||||
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
|
* For multipart issues **use list** like this:
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
* Don't put together too many unrelated requests into one issue
|
||||||
|
|
||||||
|
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
||||||
|
|
||||||
|
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
||||||
|
|
||||||
|
# Feature requests
|
||||||
|
|
||||||
|
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
||||||
|
* Include screenshot (if needed)
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
File `app/src/main/res/values/strings.xml` should be copied over to appropriate directories and then translated.
|
||||||
|
Consult [Android.com](http://developer.android.com/training/basics/supporting-devices/languages.html#CreateDirs)
|
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
ko_fi: inorichi
|
|
35
.github/ISSUE_TEMPLATE.md
vendored
@ -1,34 +1,7 @@
|
|||||||
**PLEASE READ THIS**
|
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
||||||
|
|
||||||
I acknowledge that:
|
Remove line above and describe your issue here. Fill out version below. Use Preview.
|
||||||
|
|
||||||
- I have updated:
|
|
||||||
- To the latest version of the app (stable is v0.14.3)
|
|
||||||
- All extensions
|
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
|
||||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
|
||||||
- I will fill out the title and the information in this template
|
|
||||||
|
|
||||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
Version: r000 or v0.0.0
|
||||||
|
(other relevant info like OS)
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Device information
|
|
||||||
* Tachiyomi version: ?
|
|
||||||
* Android version: ?
|
|
||||||
* Device: ?
|
|
||||||
|
|
||||||
## Steps to reproduce
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
|
|
||||||
## Issue/Request
|
|
||||||
?
|
|
||||||
|
|
||||||
## Other details
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
|
|
||||||
|
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
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
|
|
||||||
url: https://tachiyomi.org/help/
|
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
|
106
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -1,106 +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: "0.14.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: 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 **[0.14.3](https://github.com/tachiyomiorg/tachiyomi/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
|
|
39
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -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 **[0.14.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
10
.github/mergify.yml
vendored
@ -1,10 +0,0 @@
|
|||||||
#pull_request_rules:
|
|
||||||
# - name: Automatically merge translations
|
|
||||||
# conditions:
|
|
||||||
# - "author = weblate"
|
|
||||||
# - "-conflict"
|
|
||||||
# - "current-day-of-week = Sat"
|
|
||||||
# - "created-at < 1 day ago"
|
|
||||||
# actions:
|
|
||||||
# merge:
|
|
||||||
# method: squash
|
|
12
.github/pull_request_template.md
vendored
@ -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 |
|
|
||||||
| ------- | ------- |
|
|
||||||
|  |  |
|
|
||||||
-->
|
|
BIN
.github/readme-images/app-icon.png
vendored
Before Width: | Height: | Size: 1.1 KiB |
12
.github/renovate.json
vendored
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
],
|
|
||||||
"schedule": ["every sunday"],
|
|
||||||
"ignoreDeps": [
|
|
||||||
"androidx.core:core-splashscreen",
|
|
||||||
"com.android.tools:r8",
|
|
||||||
"com.google.guava:guava",
|
|
||||||
"com.github.commandiron:WheelPickerCompose"
|
|
||||||
]
|
|
||||||
}
|
|
39
.github/workflows/build_pull_request.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: PR build check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- 'i18n/src/main/res/**/strings.xml'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build app
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/wrapper-validation-action@v1
|
|
||||||
|
|
||||||
- name: Dependency Review
|
|
||||||
uses: actions/dependency-review-action@v3
|
|
||||||
|
|
||||||
- name: Set up JDK 11
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
distribution: adopt
|
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
|
||||||
uses: gradle/gradle-command-action@v2
|
|
||||||
with:
|
|
||||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
|
106
.github/workflows/build_push.yml
vendored
@ -1,106 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build 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
|
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
|
||||||
uses: gradle/gradle-command-action@v2
|
|
||||||
with:
|
|
||||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
|
||||||
|
|
||||||
- name: Get tag name
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Sign APK
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
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
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.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 tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.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 tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.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 tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.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 tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.VERSION_TAG }}
|
|
||||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
|
||||||
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: |
|
|
||||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
.github/workflows/issue_moderator.yml
vendored
@ -1,35 +0,0 @@
|
|||||||
name: Issue moderator
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
moderate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Moderate issues
|
|
||||||
uses: tachiyomiorg/issue-moderator-action@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
auto-close-rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
|
||||||
"message": "The acknowledgment section was not removed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
19
.github/workflows/lock.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
name: Lock threads
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Daily
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
# Manual trigger
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@v4
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
issue-inactive-days: '2'
|
|
||||||
pr-inactive-days: '2'
|
|
9
.gitignore
vendored
@ -2,15 +2,8 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/build
|
||||||
.idea/
|
.idea/
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# Built files
|
|
||||||
*/build
|
*/build
|
||||||
/build
|
|
||||||
*.apk
|
|
||||||
app/**/output.json
|
|
||||||
|
|
||||||
# Unnecessary file
|
|
||||||
*.swp
|
|
28
.travis.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
language: android
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- platform-tools
|
||||||
|
- tools
|
||||||
|
|
||||||
|
# The BuildTools version used by your project
|
||||||
|
- build-tools-23.0.3
|
||||||
|
- android-23
|
||||||
|
- extra-android-m2repository
|
||||||
|
- extra-google-m2repository
|
||||||
|
- extra-android-support
|
||||||
|
- extra-google-google_play_services
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- chmod +x gradlew
|
||||||
|
#Build, and run tests
|
||||||
|
script: "./gradlew clean buildDebug"
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle/caches/
|
||||||
|
- $HOME/.gradle/wrapper/
|
||||||
|
env:
|
||||||
|
- GRADLE_OPTS="-XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m"
|
@ -1,126 +0,0 @@
|
|||||||
# Contributor Covenant 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.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community 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
|
|
||||||
|
|
||||||
Examples of unacceptable behavior 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
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
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).
|
|
@ -1,50 +0,0 @@
|
|||||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Thanks for your interest in contributing to Tachiyomi!
|
|
||||||
|
|
||||||
|
|
||||||
# Code contributions
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/help/contribution/#translation) for more details.
|
|
||||||
|
|
||||||
|
|
||||||
# Forks
|
|
||||||
|
|
||||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
|
||||||
|
|
||||||
When creating a fork, remember to:
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
- 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:
|
|
||||||
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/standard/google-services.json) with your own
|
|
||||||
- If you want to use ACRA crash reporting, replace the `ACRA_URI` endpoint in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) with your own
|
|
26
LICENSE
@ -174,3 +174,29 @@
|
|||||||
of your accepting any such warranty or additional liability.
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
85
README.md
@ -1,77 +1,24 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
| Build | Download | Auto Update |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|----------|-------------|
|
||||||
|  | [](https://github.com/tachiyomiorg/tachiyomi/releases) | [](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) |
|
||||||
|
|
||||||
|
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
# Tachiyomi
|
**Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.**
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
|
||||||
|
|
||||||
## Features
|
Tachiyomi is a free and open source manga reader for Android.
|
||||||
|
|
||||||
Features include:
|
Keep in mind it's still a beta, so expect it to crash sometimes.
|
||||||
* Online reading from a variety of sources
|
|
||||||
* Local reading of downloaded content
|
# Features
|
||||||
* 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
|
* Online and offline reading
|
||||||
|
* Configurable reader with multiple viewers and settings
|
||||||
|
* MyAnimeList support
|
||||||
|
* Resume from the next unread chapter
|
||||||
|
* Chapter filtering
|
||||||
|
* Schedule searching for updates
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
|
||||||
* Schedule updating your library for new chapters
|
|
||||||
* Create backups locally to read offline or to your desired cloud service
|
|
||||||
|
|
||||||
## Download
|
|
||||||
Get the app from our [releases page](https://github.com/tachiyomiorg/tachiyomi/releases).
|
|
||||||
|
|
||||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/tachiyomi-preview/releases).
|
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
|
||||||
|
|
||||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
|
||||||
|
|
||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/tachiyomiorg/tachiyomi/releases) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<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 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)
|
|
||||||
* Don't group unrelated requests into one issue
|
|
||||||
|
|
||||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
|
||||||
|
|
||||||
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Feature Requests</summary>
|
|
||||||
|
|
||||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
|
|
||||||
Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Contributing</summary>
|
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Code of Conduct</summary>
|
|
||||||
|
|
||||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
|
||||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
2
app/.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
.idea
|
204
app/build.gradle
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
// 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
|
||||||
|
getCommitCount = {
|
||||||
|
return 'git rev-list --count HEAD'.execute().text.trim()
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
getGitSha = {
|
||||||
|
return 'git rev-parse --short HEAD'.execute().text.trim()
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildTime = {
|
||||||
|
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||||
|
df.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||||
|
return df.format(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def includeUpdater() {
|
||||||
|
return hasProperty("include_updater")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 23
|
||||||
|
buildToolsVersion "23.0.3"
|
||||||
|
publishNonDefault true
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "eu.kanade.tachiyomi"
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 23
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
versionCode 9
|
||||||
|
versionName "0.2.2-1"
|
||||||
|
|
||||||
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
|
buildConfigField "boolean", "INCLUDE_UPDATER", "${includeUpdater()}"
|
||||||
|
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
versionNameSuffix ".${getCommitCount()}"
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
|
exclude 'LICENSE.txt'
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/LICENSE.txt'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
checkReleaseBuilds false
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/32759529/androidhttpclient-not-found-when-running-robolectric
|
||||||
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
generateStubs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
final SUPPORT_LIBRARY_VERSION = '23.4.0'
|
||||||
|
final DAGGER_VERSION = '2.4'
|
||||||
|
final RETROFIT_VERSION = '2.0.2'
|
||||||
|
final NUCLEUS_VERSION = '3.0.0'
|
||||||
|
final STORIO_VERSION = '1.8.0'
|
||||||
|
final MOCKITO_VERSION = '1.10.19'
|
||||||
|
|
||||||
|
// Modified dependencies
|
||||||
|
compile 'com.github.inorichi:subsampling-scale-image-view:421fb81'
|
||||||
|
compile 'com.github.inorichi:ReactiveNetwork:69092ed'
|
||||||
|
|
||||||
|
// Android support library
|
||||||
|
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:preference-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:preference-v14:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
|
||||||
|
// ReactiveX
|
||||||
|
compile 'io.reactivex:rxandroid:1.2.0'
|
||||||
|
compile 'io.reactivex:rxjava:1.1.5'
|
||||||
|
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
||||||
|
|
||||||
|
// Network client
|
||||||
|
compile "com.squareup.okhttp3:okhttp:3.3.1"
|
||||||
|
|
||||||
|
// REST
|
||||||
|
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
|
||||||
|
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
|
||||||
|
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
|
||||||
|
|
||||||
|
// IO
|
||||||
|
compile 'com.squareup.okio:okio:1.8.0'
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
compile 'com.google.code.gson:gson:2.6.2'
|
||||||
|
|
||||||
|
// YAML
|
||||||
|
compile 'org.yaml:snakeyaml:1.17'
|
||||||
|
|
||||||
|
// JavaScript engine
|
||||||
|
compile 'com.squareup.duktape:duktape-android:0.9.5'
|
||||||
|
|
||||||
|
// Disk cache
|
||||||
|
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||||
|
|
||||||
|
// Parse HTML
|
||||||
|
compile 'org.jsoup:jsoup:1.9.2'
|
||||||
|
|
||||||
|
// Changelog
|
||||||
|
compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||||
|
|
||||||
|
// Database
|
||||||
|
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
||||||
|
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
||||||
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
|
|
||||||
|
// Model View Presenter
|
||||||
|
compile "info.android15.nucleus:nucleus:$NUCLEUS_VERSION"
|
||||||
|
compile "info.android15.nucleus:nucleus-support-v4:$NUCLEUS_VERSION"
|
||||||
|
compile "info.android15.nucleus:nucleus-support-v7:$NUCLEUS_VERSION"
|
||||||
|
|
||||||
|
// Dependency injection
|
||||||
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
provided 'org.glassfish:javax.annotation:10.0-b28'
|
||||||
|
|
||||||
|
// Image library
|
||||||
|
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||||
|
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
compile 'com.jakewharton.timber:timber:4.1.2'
|
||||||
|
|
||||||
|
// Crash reports
|
||||||
|
compile 'ch.acra:acra:4.8.5'
|
||||||
|
|
||||||
|
// UI
|
||||||
|
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
|
compile 'eu.davidea:flexible-adapter:4.2.0'
|
||||||
|
compile 'com.nononsenseapps:filepicker:2.5.2'
|
||||||
|
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
||||||
|
compile 'com.afollestad.material-dialogs:core:0.8.5.9'
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||||
|
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
|
||||||
|
testCompile('org.robolectric:robolectric:3.0') {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||||
|
}
|
||||||
|
|
||||||
|
kaptTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.0.2'
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
@ -1,360 +0,0 @@
|
|||||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
|
||||||
kotlin("android")
|
|
||||||
kotlin("plugin.serialization")
|
|
||||||
id("com.github.zellius.shortcut-helper")
|
|
||||||
id("com.squareup.sqldelight")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
|
||||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
|
||||||
|
|
||||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "eu.kanade.tachiyomi"
|
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
ndkVersion = AndroidConfig.ndk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
|
||||||
minSdk = AndroidConfig.minSdk
|
|
||||||
targetSdk = AndroidConfig.targetSdk
|
|
||||||
versionCode = 93
|
|
||||||
versionName = "0.14.3"
|
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
|
||||||
buildConfigField("boolean", "PREVIEW", "false")
|
|
||||||
|
|
||||||
// Please disable ACRA or use your own instance in forked versions of the project
|
|
||||||
buildConfigField("String", "ACRA_URI", "\"https://tachiyomi.kanade.eu/crash_report\"")
|
|
||||||
|
|
||||||
ndk {
|
|
||||||
abiFilters += SUPPORTED_ABIS
|
|
||||||
}
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
splits {
|
|
||||||
abi {
|
|
||||||
isEnable = true
|
|
||||||
reset()
|
|
||||||
include(*SUPPORTED_ABIS.toTypedArray())
|
|
||||||
isUniversalApk = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
named("debug") {
|
|
||||||
versionNameSuffix = "-${getCommitCount()}"
|
|
||||||
applicationIdSuffix = ".debug"
|
|
||||||
isPseudoLocalesEnabled = true
|
|
||||||
}
|
|
||||||
named("release") {
|
|
||||||
isShrinkResources = true
|
|
||||||
isMinifyEnabled = true
|
|
||||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
|
||||||
}
|
|
||||||
create("preview") {
|
|
||||||
initWith(getByName("release"))
|
|
||||||
buildConfigField("boolean", "PREVIEW", "true")
|
|
||||||
|
|
||||||
val debugType = getByName("debug")
|
|
||||||
signingConfig = debugType.signingConfig
|
|
||||||
versionNameSuffix = debugType.versionNameSuffix
|
|
||||||
applicationIdSuffix = debugType.applicationIdSuffix
|
|
||||||
matchingFallbacks.add("release")
|
|
||||||
}
|
|
||||||
create("benchmark") {
|
|
||||||
initWith(getByName("release"))
|
|
||||||
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
matchingFallbacks.add("release")
|
|
||||||
isDebuggable = false
|
|
||||||
versionNameSuffix = "-benchmark"
|
|
||||||
applicationIdSuffix = ".benchmark"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
getByName("preview").res.srcDirs("src/debug/res")
|
|
||||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
|
||||||
}
|
|
||||||
|
|
||||||
flavorDimensions.add("default")
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
create("standard") {
|
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "true")
|
|
||||||
dimension = "default"
|
|
||||||
}
|
|
||||||
create("dev") {
|
|
||||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
|
||||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "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",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
dependenciesInfo {
|
|
||||||
includeInApk = false
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding = true
|
|
||||||
compose = true
|
|
||||||
|
|
||||||
// Disable some unused things
|
|
||||||
aidl = false
|
|
||||||
renderScript = false
|
|
||||||
shaders = false
|
|
||||||
}
|
|
||||||
|
|
||||||
lint {
|
|
||||||
abortOnError = false
|
|
||||||
checkReleaseBuilds = false
|
|
||||||
}
|
|
||||||
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
|
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
sqldelight {
|
|
||||||
database("Database") {
|
|
||||||
packageName = "eu.kanade.tachiyomi"
|
|
||||||
dialect = "sqlite:3.24"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":i18n"))
|
|
||||||
implementation(project(":core"))
|
|
||||||
implementation(project(":source-api"))
|
|
||||||
|
|
||||||
coreLibraryDesugaring(libs.desugar)
|
|
||||||
|
|
||||||
// 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(libs.sqldelight.android.driver)
|
|
||||||
implementation(libs.sqldelight.coroutines)
|
|
||||||
implementation(libs.sqldelight.android.paging)
|
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
|
||||||
|
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
|
||||||
implementation(kotlinx.bundles.coroutines)
|
|
||||||
|
|
||||||
// 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.glance)
|
|
||||||
implementation(androidx.profileinstaller)
|
|
||||||
|
|
||||||
implementation(androidx.bundles.lifecycle)
|
|
||||||
|
|
||||||
// Job scheduling
|
|
||||||
implementation(androidx.bundles.workmanager)
|
|
||||||
|
|
||||||
// RX
|
|
||||||
implementation(libs.bundles.reactivex)
|
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
|
||||||
// Network client
|
|
||||||
implementation(libs.bundles.okhttp)
|
|
||||||
implementation(libs.okio)
|
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
|
||||||
implementation(libs.conscrypt.android)
|
|
||||||
|
|
||||||
// Data serialization (JSON, protobuf)
|
|
||||||
implementation(kotlinx.bundles.serialization)
|
|
||||||
|
|
||||||
// HTML parser
|
|
||||||
implementation(libs.jsoup)
|
|
||||||
|
|
||||||
// Disk
|
|
||||||
implementation(libs.disklrucache)
|
|
||||||
implementation(libs.unifile)
|
|
||||||
implementation(libs.junrar)
|
|
||||||
|
|
||||||
// Preferences
|
|
||||||
implementation(libs.preferencektx)
|
|
||||||
|
|
||||||
// Dependency injection
|
|
||||||
implementation(libs.injekt.core)
|
|
||||||
|
|
||||||
// Image loading
|
|
||||||
implementation(libs.bundles.coil)
|
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
|
||||||
exclude(module = "image-decoder")
|
|
||||||
}
|
|
||||||
implementation(libs.image.decoder)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
implementation(libs.logcat)
|
|
||||||
|
|
||||||
// Crash reports/analytics
|
|
||||||
implementation(libs.acra.http)
|
|
||||||
"standardImplementation"(libs.firebase.analytics)
|
|
||||||
|
|
||||||
// Shizuku
|
|
||||||
implementation(libs.bundles.shizuku)
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
testImplementation(libs.junit)
|
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
|
||||||
// debugImplementation(libs.leakcanary.android)
|
|
||||||
implementation(libs.leakcanary.plumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
withType<Test> {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withType<LintTask>().configureEach {
|
|
||||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
|
||||||
withType<KotlinCompile> {
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
|
|
||||||
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
|
||||||
)
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preBuild {
|
|
||||||
val ktlintTask = if (System.getenv("GITHUB_BASE_REF") == null) formatKotlin else lintKotlin
|
|
||||||
dependsOn(ktlintTask)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath(kotlinx.gradle)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
-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>(...);
|
|
||||||
}
|
|
146
app/proguard-rules.pro
vendored
@ -1,26 +1,30 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
# Keep common dependencies used in extensions
|
-keep class eu.kanade.tachiyomi.injection.** { *; }
|
||||||
-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 *; }
|
|
||||||
|
|
||||||
# From extensions-lib
|
# OkHttp
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
-keepattributes Signature
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.SpecificHostRateLimitInterceptorKt { public protected *; }
|
-keepattributes *Annotation*
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.NetworkHelper { public protected *; }
|
-keep class okhttp3.** { *; }
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.OkHttpExtensionsKt { public protected *; }
|
-keep interface okhttp3.** { *; }
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
-dontwarn okhttp3.**
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
-dontwarn okio.**
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
# Okio
|
||||||
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-dontwarn java.nio.file.*
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Glide specific rules #
|
||||||
|
# https://github.com/bumptech/glide
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||||
@ -36,34 +40,90 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
-dontnote rx.internal.util.PlatformDependent
|
# Retrofit 2.X
|
||||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
## https://square.github.io/retrofit/ ##
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
-dontwarn retrofit2.**
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keep class retrofit2.** { *; }
|
||||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
-keepattributes Signature
|
||||||
|
-keepattributes Exceptions
|
||||||
|
|
||||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
-keepclasseswithmembers class * {
|
||||||
-keepclassmembers class kotlinx.serialization.json.** {
|
@retrofit2.http.* <methods>;
|
||||||
*** Companion;
|
|
||||||
}
|
|
||||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep,includedescriptorclasses class eu.kanade.**$$serializer { *; }
|
# AppCombat
|
||||||
-keepclassmembers class eu.kanade.** {
|
-keep public class android.support.v7.widget.** { *; }
|
||||||
*** Companion;
|
-keep public class android.support.v7.internal.widget.** { *; }
|
||||||
}
|
-keep public class android.support.v7.internal.view.menu.** { *; }
|
||||||
-keepclasseswithmembers class eu.kanade.** {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
-keep public class * extends android.support.v4.view.ActionProvider {
|
||||||
|
public <init>(android.content.Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class kotlinx.serialization.**
|
## GSON 2.2.4 specific rules ##
|
||||||
-keepclassmembers class kotlinx.serialization.** {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
|
||||||
|
|
||||||
# XmlUtil
|
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
# removes such information by default, so configure it to keep all of it.
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
-keepattributes EnclosingMethod
|
||||||
|
|
||||||
|
# Gson specific classes
|
||||||
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
|
## ACRA 4.5.0 specific rules ##
|
||||||
|
|
||||||
|
# we need line numbers in our stack traces otherwise they are pretty useless
|
||||||
|
-renamesourcefileattribute SourceFile
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# ACRA needs "annotations" so add this...
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
# keep this class so that logging will show 'ACRA' and not a obfuscated name like 'a'.
|
||||||
|
# Note: if you are removing log messages elsewhere in this file then this isn't necessary
|
||||||
|
-keep class org.acra.ACRA {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this around for some enums that ACRA needs
|
||||||
|
-keep class org.acra.ReportingInteractionMode {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.sender.HttpSender$** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.ReportField {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void addCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void putCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void removeCustomData(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void handleSilentException(java.lang.Throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep the support library
|
||||||
|
-keep class org.acra.** { *; }
|
||||||
|
-keep interface org.acra.** { *; }
|
||||||
|
|
||||||
|
# SnakeYaml
|
||||||
|
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
||||||
|
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
||||||
|
-dontwarn org.yaml.snakeyaml.**
|
||||||
|
|
||||||
|
# Duktape
|
||||||
|
-keep class com.squareup.duktape.** { *; }
|
@ -1,47 +0,0 @@
|
|||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_library"
|
|
||||||
android:shortcutLongLabel="@string/label_library"
|
|
||||||
android:shortcutShortLabel="@string/label_library">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_new_releases_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_updated"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_updates"
|
|
||||||
android:shortcutShortLabel="@string/label_recent_updates">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_history_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_read"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_manga"
|
|
||||||
android:shortcutShortLabel="@string/label_recent_manga">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_explore_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_catalogues"
|
|
||||||
android:shortcutLongLabel="@string/browse"
|
|
||||||
android:shortcutShortLabel="@string/browse">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
@ -1,27 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108.0"
|
|
||||||
android:viewportHeight="108.0">
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillColor="#455A64"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
|
||||||
android:fillColor="#607D8B"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#CE2828"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-19.94,0a19.94,19.94 0,1 1,39.87 0a19.94,19.94 0,1 1,-39.87 0"
|
|
||||||
android:fillColor="#FFF"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.04,46.3L47.42,46.3C46.14,46.3 44.93,46.23 44.2,46.14L44.2,49.76C45,49.65 46.16,49.6 47.42,49.6L60.58,49.6C61.86,49.6 63.02,49.65 63.82,49.76L63.82,46.14C63.09,46.23 61.86,46.3 60.58,46.3L55.69,46.3L55.69,45.07C55.69,44.43 55.73,43.95 55.82,43.45L51.9,43.45C51.99,44 52.04,44.43 52.04,45.07L52.04,46.3ZM46.78,60.68C45.46,60.68 44.29,60.63 43.45,60.52L43.45,64.14C44.34,64.03 45.46,63.98 46.78,63.98L61.29,63.98C62.57,63.98 63.71,64.03 64.57,64.14L64.57,60.52C63.73,60.63 62.57,60.68 61.29,60.68L58.24,60.68C59.33,58.06 59.99,56.23 60.7,53.91C61.34,51.81 61.34,51.81 61.56,51.13L57.58,50.06C57.51,50.93 57.37,51.52 56.89,53.41C56.19,56.14 55.32,58.74 54.5,60.68L46.78,60.68ZM46.48,51.36C47.55,54.02 48.28,56.53 49.03,60.15L52.66,58.9C51.65,54.98 50.92,52.66 49.94,50.11L46.48,51.36Z"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
</vector>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
|
||||||
</adaptive-icon>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,263 +1,108 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
|
|
||||||
<!-- Storage -->
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<!-- For background jobs -->
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
|
||||||
<!-- 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
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:theme="@style/Theme.Tachiyomi" >
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
|
||||||
|
|
||||||
<!-- enable profiling by macrobenchmark -->
|
|
||||||
<profileable
|
|
||||||
android:shell="true"
|
|
||||||
tools:targetApi="q" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:theme="@style/Theme.BrandedLaunch">
|
||||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!--suppress AndroidDomInspection -->
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.shortcuts"
|
|
||||||
android:resource="@xml/shortcuts" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":error_handler"
|
android:name=".ui.manga.MangaActivity"
|
||||||
android:name=".crash.CrashActivity"
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
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">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.searchable"
|
|
||||||
android:resource="@xml/searchable" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:launchMode="singleTask"
|
android:theme="@style/Theme.Reader">
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
|
||||||
android:label="Anilist"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="anilist-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
android:name=".ui.setting.SettingsActivity"
|
||||||
android:label="MyAnimeList"
|
android:label="@string/label_settings"
|
||||||
android:exported="true">
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="myanimelist-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
android:name=".ui.category.CategoryActivity"
|
||||||
android:label="Shikimori"
|
android:label="@string/label_categories"
|
||||||
android:exported="true">
|
android:parentActivityName=".ui.main.MainActivity">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="shikimori-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
||||||
android:label="Bangumi"
|
android:label="@string/app_name"
|
||||||
android:exported="true">
|
android:theme="@style/FilePickerTheme">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="bangumi-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service android:name=".data.library.LibraryUpdateService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<service android:name=".data.download.DownloadService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<service android:name=".data.mangasync.UpdateMangaSyncService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".glance.UpdatesGridGlanceReceiver"
|
|
||||||
android:enabled="@bool/glance_appwidget_available"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/label_recent_updates">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateService"
|
android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.download.DownloadService"
|
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.updater.AppUpdateService"
|
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.library.LibraryUpdateAlarm">
|
||||||
android:exported="false" />
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.UPDATE_LIBRARY" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".extension.util.ExtensionInstallService"
|
<receiver
|
||||||
android:exported="false" />
|
android:name=".data.updater.UpdateDownloaderAlarm">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.CHECK_UPDATE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
|
||||||
android:enabled="false"
|
|
||||||
android:exported="false">
|
|
||||||
<meta-data
|
|
||||||
android:name="autoStoreLocales"
|
|
||||||
android:value="true" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.provider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
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" />
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.webkit.WebView.EnableSafeBrowsing"
|
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||||
android:value="false" />
|
android:value="GlideModule" />
|
||||||
<meta-data
|
|
||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
|
||||||
android:value="true" />
|
|
||||||
|
|
||||||
<!-- Disable advertising ID collection for Firebase -->
|
|
||||||
<meta-data
|
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
|
||||||
android:value="false" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
BIN
app/src/main/assets/fonts/PTSans-Narrow.ttf
Normal file
BIN
app/src/main/assets/fonts/PTSans-NarrowBold.ttf
Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
@ -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 eu.kanade.tachiyomi.core.preference.Preference
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
|
|
||||||
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,90 +0,0 @@
|
|||||||
package eu.kanade.data
|
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
|
||||||
import com.squareup.sqldelight.Query
|
|
||||||
import com.squareup.sqldelight.db.SqlDriver
|
|
||||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
|
||||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
|
||||||
import com.squareup.sqldelight.runtime.coroutines.mapToOne
|
|
||||||
import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull
|
|
||||||
import eu.kanade.tachiyomi.Database
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class AndroidDatabaseHandler(
|
|
||||||
val db: Database,
|
|
||||||
private val driver: SqlDriver,
|
|
||||||
val queryDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
|
||||||
val transactionDispatcher: CoroutineDispatcher = queryDispatcher,
|
|
||||||
) : DatabaseHandler {
|
|
||||||
|
|
||||||
val suspendingTransactionId = ThreadLocal<Int>()
|
|
||||||
|
|
||||||
override suspend fun <T> await(inTransaction: Boolean, block: suspend Database.() -> T): T {
|
|
||||||
return dispatch(inTransaction, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T : Any> awaitList(
|
|
||||||
inTransaction: Boolean,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): List<T> {
|
|
||||||
return dispatch(inTransaction) { block(db).executeAsList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T : Any> awaitOne(
|
|
||||||
inTransaction: Boolean,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): T {
|
|
||||||
return dispatch(inTransaction) { block(db).executeAsOne() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T : Any> awaitOneOrNull(
|
|
||||||
inTransaction: Boolean,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): T? {
|
|
||||||
return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>> {
|
|
||||||
return block(db).asFlow().mapToList(queryDispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T> {
|
|
||||||
return block(db).asFlow().mapToOne(queryDispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> subscribeToOneOrNull(block: Database.() -> Query<T>): Flow<T?> {
|
|
||||||
return block(db).asFlow().mapToOneOrNull(queryDispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> subscribeToPagingSource(
|
|
||||||
countQuery: Database.() -> Query<Long>,
|
|
||||||
queryProvider: Database.(Long, Long) -> Query<T>,
|
|
||||||
): PagingSource<Long, T> {
|
|
||||||
return QueryPagingSource(
|
|
||||||
handler = this,
|
|
||||||
countQuery = countQuery,
|
|
||||||
queryProvider = { limit, offset ->
|
|
||||||
queryProvider.invoke(db, limit, offset)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun <T> dispatch(inTransaction: Boolean, block: suspend Database.() -> T): T {
|
|
||||||
// Create a transaction if needed and run the calling block inside it.
|
|
||||||
if (inTransaction) {
|
|
||||||
return withTransaction { block(db) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're currently in the transaction thread, there's no need to dispatch our query.
|
|
||||||
if (driver.currentTransaction() != null) {
|
|
||||||
return block(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current database context and run the calling block.
|
|
||||||
val context = getCurrentDatabaseContext()
|
|
||||||
return withContext(context) { block(db) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.kanade.data
|
|
||||||
|
|
||||||
import com.squareup.sqldelight.ColumnAdapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
val dateAdapter = object : ColumnAdapter<Date, Long> {
|
|
||||||
override fun decode(databaseValue: Long): Date = Date(databaseValue)
|
|
||||||
override fun encode(value: Date): Long = value.time
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val listOfStringsSeparator = ", "
|
|
||||||
val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
|
|
||||||
override fun decode(databaseValue: String) =
|
|
||||||
if (databaseValue.isEmpty()) {
|
|
||||||
emptyList()
|
|
||||||
} else {
|
|
||||||
databaseValue.split(listOfStringsSeparator)
|
|
||||||
}
|
|
||||||
override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Long> {
|
|
||||||
private val enumValues by lazy { UpdateStrategy.values() }
|
|
||||||
|
|
||||||
override fun decode(databaseValue: Long): UpdateStrategy =
|
|
||||||
enumValues.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
|
|
||||||
|
|
||||||
override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.data
|
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
|
||||||
import com.squareup.sqldelight.Query
|
|
||||||
import eu.kanade.tachiyomi.Database
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface DatabaseHandler {
|
|
||||||
|
|
||||||
suspend fun <T> await(inTransaction: Boolean = false, block: suspend Database.() -> T): T
|
|
||||||
|
|
||||||
suspend fun <T : Any> awaitList(
|
|
||||||
inTransaction: Boolean = false,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): List<T>
|
|
||||||
|
|
||||||
suspend fun <T : Any> awaitOne(
|
|
||||||
inTransaction: Boolean = false,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): T
|
|
||||||
|
|
||||||
suspend fun <T : Any> awaitOneOrNull(
|
|
||||||
inTransaction: Boolean = false,
|
|
||||||
block: suspend Database.() -> Query<T>,
|
|
||||||
): T?
|
|
||||||
|
|
||||||
fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>>
|
|
||||||
|
|
||||||
fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T>
|
|
||||||
|
|
||||||
fun <T : Any> subscribeToOneOrNull(block: Database.() -> Query<T>): Flow<T?>
|
|
||||||
|
|
||||||
fun <T : Any> subscribeToPagingSource(
|
|
||||||
countQuery: Database.() -> Query<Long>,
|
|
||||||
queryProvider: Database.(Long, Long) -> Query<T>,
|
|
||||||
): PagingSource<Long, T>
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
package eu.kanade.data
|
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
|
||||||
import androidx.paging.PagingState
|
|
||||||
import com.squareup.sqldelight.Query
|
|
||||||
import eu.kanade.tachiyomi.Database
|
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
class QueryPagingSource<RowType : Any>(
|
|
||||||
val handler: DatabaseHandler,
|
|
||||||
val countQuery: Database.() -> Query<Long>,
|
|
||||||
val queryProvider: Database.(Long, Long) -> Query<RowType>,
|
|
||||||
) : PagingSource<Long, RowType>(), Query.Listener {
|
|
||||||
|
|
||||||
override val jumpingSupported: Boolean = true
|
|
||||||
|
|
||||||
private var currentQuery: Query<RowType>? by Delegates.observable(null) { _, old, new ->
|
|
||||||
old?.removeListener(this)
|
|
||||||
new?.addListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerInvalidatedCallback {
|
|
||||||
currentQuery?.removeListener(this)
|
|
||||||
currentQuery = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, RowType> {
|
|
||||||
try {
|
|
||||||
val key = params.key ?: 0L
|
|
||||||
val loadSize = params.loadSize
|
|
||||||
val count = handler.awaitOne { countQuery() }
|
|
||||||
|
|
||||||
val (offset, limit) = when (params) {
|
|
||||||
is LoadParams.Prepend -> key - loadSize to loadSize.toLong()
|
|
||||||
else -> key to loadSize.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
val data = handler.awaitList {
|
|
||||||
queryProvider(limit, offset)
|
|
||||||
.also { currentQuery = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
val (prevKey, nextKey) = when (params) {
|
|
||||||
is LoadParams.Append -> { offset - loadSize to offset + loadSize }
|
|
||||||
else -> { offset to offset + loadSize }
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadResult.Page(
|
|
||||||
data = data,
|
|
||||||
prevKey = if (offset <= 0L || prevKey < 0L) null else prevKey,
|
|
||||||
nextKey = if (offset + loadSize >= count) null else nextKey,
|
|
||||||
itemsBefore = maxOf(0L, offset).toInt(),
|
|
||||||
itemsAfter = maxOf(0L, count - (offset + loadSize)).toInt(),
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return LoadResult.Error(throwable = e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRefreshKey(state: PagingState<Long, RowType>): Long? {
|
|
||||||
return state.anchorPosition?.let { anchorPosition ->
|
|
||||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
|
||||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun queryResultsChanged() {
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package eu.kanade.data
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.asContextElement
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.util.concurrent.RejectedExecutionException
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.coroutines.ContinuationInterceptor
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the transaction dispatcher if we are on a transaction, or the database dispatchers.
|
|
||||||
*/
|
|
||||||
internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): CoroutineContext {
|
|
||||||
return coroutineContext[TransactionElement]?.transactionDispatcher ?: queryDispatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the specified suspending [block] in a database transaction. The transaction will be
|
|
||||||
* marked as successful unless an exception is thrown in the suspending [block] or the coroutine
|
|
||||||
* is cancelled.
|
|
||||||
*
|
|
||||||
* SQLDelight will only perform at most one transaction at a time, additional transactions are queued
|
|
||||||
* and executed on a first come, first serve order.
|
|
||||||
*
|
|
||||||
* Performing blocking database operations is not permitted in a coroutine scope other than the
|
|
||||||
* one received by the suspending block. It is recommended that all [Dao] function invoked within
|
|
||||||
* the [block] be suspending functions.
|
|
||||||
*
|
|
||||||
* The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor.
|
|
||||||
*/
|
|
||||||
internal suspend fun <T> AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T {
|
|
||||||
// Use inherited transaction context if available, this allows nested suspending transactions.
|
|
||||||
val transactionContext =
|
|
||||||
coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext()
|
|
||||||
return withContext(transactionContext) {
|
|
||||||
val transactionElement = coroutineContext[TransactionElement]!!
|
|
||||||
transactionElement.acquire()
|
|
||||||
try {
|
|
||||||
db.transactionWithResult {
|
|
||||||
runBlocking(transactionContext) {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
transactionElement.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a [CoroutineContext] for performing database operations within a coroutine transaction.
|
|
||||||
*
|
|
||||||
* The context is a combination of a dispatcher, a [TransactionElement] and a thread local element.
|
|
||||||
*
|
|
||||||
* * The dispatcher will dispatch coroutines to a single thread that is taken over from the SQLDelight
|
|
||||||
* query executor. If the coroutine context is switched, suspending DAO functions will be able to
|
|
||||||
* dispatch to the transaction thread.
|
|
||||||
*
|
|
||||||
* * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a
|
|
||||||
* switch of context, suspending DAO methods will be able to use the indicator to dispatch the
|
|
||||||
* database operation to the transaction thread.
|
|
||||||
*
|
|
||||||
* * The thread local element serves as a second indicator and marks threads that are used to
|
|
||||||
* execute coroutines within the coroutine transaction, more specifically it allows us to identify
|
|
||||||
* if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to
|
|
||||||
* this value, for now all we care is if its present or not.
|
|
||||||
*/
|
|
||||||
private suspend fun AndroidDatabaseHandler.createTransactionContext(): CoroutineContext {
|
|
||||||
val controlJob = Job()
|
|
||||||
// make sure to tie the control job to this context to avoid blocking the transaction if
|
|
||||||
// context get cancelled before we can even start using this job. Otherwise, the acquired
|
|
||||||
// transaction thread will forever wait for the controlJob to be cancelled.
|
|
||||||
// see b/148181325
|
|
||||||
coroutineContext[Job]?.invokeOnCompletion {
|
|
||||||
controlJob.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
val dispatcher = transactionDispatcher.acquireTransactionThread(controlJob)
|
|
||||||
val transactionElement = TransactionElement(controlJob, dispatcher)
|
|
||||||
val threadLocalElement =
|
|
||||||
suspendingTransactionId.asContextElement(System.identityHashCode(controlJob))
|
|
||||||
return dispatcher + transactionElement + threadLocalElement
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acquires a thread from the executor and returns a [ContinuationInterceptor] to dispatch
|
|
||||||
* coroutines to the acquired thread. The [controlJob] is used to control the release of the
|
|
||||||
* thread by cancelling the job.
|
|
||||||
*/
|
|
||||||
private suspend fun CoroutineDispatcher.acquireTransactionThread(
|
|
||||||
controlJob: Job,
|
|
||||||
): ContinuationInterceptor {
|
|
||||||
return suspendCancellableCoroutine { continuation ->
|
|
||||||
continuation.invokeOnCancellation {
|
|
||||||
// We got cancelled while waiting to acquire a thread, we can't stop our attempt to
|
|
||||||
// acquire a thread, but we can cancel the controlling job so once it gets acquired it
|
|
||||||
// is quickly released.
|
|
||||||
controlJob.cancel()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
dispatch(EmptyCoroutineContext) {
|
|
||||||
runBlocking {
|
|
||||||
// Thread acquired, resume coroutine
|
|
||||||
continuation.resume(coroutineContext[ContinuationInterceptor]!!)
|
|
||||||
controlJob.join()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: RejectedExecutionException) {
|
|
||||||
// Couldn't acquire a thread, cancel coroutine
|
|
||||||
continuation.cancel(
|
|
||||||
IllegalStateException(
|
|
||||||
"Unable to acquire a thread to perform the database transaction",
|
|
||||||
ex,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [CoroutineContext.Element] that indicates there is an on-going database transaction.
|
|
||||||
*/
|
|
||||||
private class TransactionElement(
|
|
||||||
private val transactionThreadControlJob: Job,
|
|
||||||
val transactionDispatcher: ContinuationInterceptor,
|
|
||||||
) : CoroutineContext.Element {
|
|
||||||
|
|
||||||
companion object Key : CoroutineContext.Key<TransactionElement>
|
|
||||||
|
|
||||||
override val key: CoroutineContext.Key<TransactionElement>
|
|
||||||
get() = TransactionElement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of transactions (including nested ones) started with this element.
|
|
||||||
* Call [acquire] to increase the count and [release] to decrease it. If the count reaches zero
|
|
||||||
* when [release] is invoked then the transaction job is cancelled and the transaction thread
|
|
||||||
* is released.
|
|
||||||
*/
|
|
||||||
private val referenceCount = AtomicInteger(0)
|
|
||||||
|
|
||||||
fun acquire() {
|
|
||||||
referenceCount.incrementAndGet()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun release() {
|
|
||||||
val count = referenceCount.decrementAndGet()
|
|
||||||
if (count < 0) {
|
|
||||||
throw IllegalStateException("Transaction was never started or was already released")
|
|
||||||
} else if (count == 0) {
|
|
||||||
// Cancel the job that controls the transaction thread, causing it to be released.
|
|
||||||
transactionThreadControlJob.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.data.category
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
|
|
||||||
val categoryMapper: (Long, String, Long, Long) -> Category = { id, name, order, flags ->
|
|
||||||
Category(
|
|
||||||
id = id,
|
|
||||||
name = name,
|
|
||||||
order = order,
|
|
||||||
flags = flags,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package eu.kanade.data.category
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.Database
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class CategoryRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : CategoryRepository {
|
|
||||||
|
|
||||||
override suspend fun get(id: Long): Category? {
|
|
||||||
return handler.awaitOneOrNull { categoriesQueries.getCategory(id, categoryMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAll(): List<Category> {
|
|
||||||
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllAsFlow(): Flow<List<Category>> {
|
|
||||||
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCategoriesByMangaId(mangaId: Long): List<Category> {
|
|
||||||
return handler.awaitList {
|
|
||||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
|
|
||||||
return handler.subscribeToList {
|
|
||||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insert(category: Category) {
|
|
||||||
handler.await {
|
|
||||||
categoriesQueries.insert(
|
|
||||||
name = category.name,
|
|
||||||
order = category.order,
|
|
||||||
flags = category.flags,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updatePartial(update: CategoryUpdate) {
|
|
||||||
handler.await {
|
|
||||||
updatePartialBlocking(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updatePartial(updates: List<CategoryUpdate>) {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
for (update in updates) {
|
|
||||||
updatePartialBlocking(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Database.updatePartialBlocking(update: CategoryUpdate) {
|
|
||||||
categoriesQueries.update(
|
|
||||||
name = update.name,
|
|
||||||
order = update.order,
|
|
||||||
flags = update.flags,
|
|
||||||
categoryId = update.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateAllFlags(flags: Long?) {
|
|
||||||
handler.await {
|
|
||||||
categoriesQueries.updateAllFlags(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(categoryId: Long) {
|
|
||||||
handler.await {
|
|
||||||
categoriesQueries.delete(
|
|
||||||
categoryId = categoryId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package eu.kanade.data.chapter
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
|
|
||||||
val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Chapter =
|
|
||||||
{ id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
|
|
||||||
Chapter(
|
|
||||||
id = id,
|
|
||||||
mangaId = mangaId,
|
|
||||||
read = read,
|
|
||||||
bookmark = bookmark,
|
|
||||||
lastPageRead = lastPageRead,
|
|
||||||
dateFetch = dateFetch,
|
|
||||||
sourceOrder = sourceOrder,
|
|
||||||
url = url,
|
|
||||||
name = name,
|
|
||||||
dateUpload = dateUpload,
|
|
||||||
chapterNumber = chapterNumber,
|
|
||||||
scanlator = scanlator,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package eu.kanade.data.chapter
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.system.toLong
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
class ChapterRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : ChapterRepository {
|
|
||||||
|
|
||||||
override suspend fun addAll(chapters: List<Chapter>): List<Chapter> {
|
|
||||||
return try {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
chapters.map { chapter ->
|
|
||||||
chaptersQueries.insert(
|
|
||||||
chapter.mangaId,
|
|
||||||
chapter.url,
|
|
||||||
chapter.name,
|
|
||||||
chapter.scanlator,
|
|
||||||
chapter.read,
|
|
||||||
chapter.bookmark,
|
|
||||||
chapter.lastPageRead,
|
|
||||||
chapter.chapterNumber,
|
|
||||||
chapter.sourceOrder,
|
|
||||||
chapter.dateFetch,
|
|
||||||
chapter.dateUpload,
|
|
||||||
)
|
|
||||||
val lastInsertId = chaptersQueries.selectLastInsertedRowId().executeAsOne()
|
|
||||||
chapter.copy(id = lastInsertId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(chapterUpdate: ChapterUpdate) {
|
|
||||||
partialUpdate(chapterUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateAll(chapterUpdates: List<ChapterUpdate>) {
|
|
||||||
partialUpdate(*chapterUpdates.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun partialUpdate(vararg chapterUpdates: ChapterUpdate) {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
chapterUpdates.forEach { chapterUpdate ->
|
|
||||||
chaptersQueries.update(
|
|
||||||
mangaId = chapterUpdate.mangaId,
|
|
||||||
url = chapterUpdate.url,
|
|
||||||
name = chapterUpdate.name,
|
|
||||||
scanlator = chapterUpdate.scanlator,
|
|
||||||
read = chapterUpdate.read?.toLong(),
|
|
||||||
bookmark = chapterUpdate.bookmark?.toLong(),
|
|
||||||
lastPageRead = chapterUpdate.lastPageRead,
|
|
||||||
chapterNumber = chapterUpdate.chapterNumber?.toDouble(),
|
|
||||||
sourceOrder = chapterUpdate.sourceOrder,
|
|
||||||
dateFetch = chapterUpdate.dateFetch,
|
|
||||||
dateUpload = chapterUpdate.dateUpload,
|
|
||||||
chapterId = chapterUpdate.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeChaptersWithIds(chapterIds: List<Long>) {
|
|
||||||
try {
|
|
||||||
handler.await { chaptersQueries.removeChaptersWithIds(chapterIds) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getChapterByMangaId(mangaId: Long): List<Chapter> {
|
|
||||||
return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter> {
|
|
||||||
return handler.awaitList { chaptersQueries.getBookmarkedChaptersByMangaId(mangaId, chapterMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getChapterById(id: Long): Chapter? {
|
|
||||||
return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, chapterMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> {
|
|
||||||
return handler.subscribeToList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? {
|
|
||||||
return handler.awaitOneOrNull { chaptersQueries.getChapterByUrlAndMangaId(url, mangaId, chapterMapper) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package eu.kanade.data.chapter
|
|
||||||
|
|
||||||
object CleanupChapterName {
|
|
||||||
|
|
||||||
fun await(chapterName: String, mangaTitle: String): String {
|
|
||||||
return chapterName
|
|
||||||
.trim()
|
|
||||||
.removePrefix(mangaTitle)
|
|
||||||
.trim(*CHAPTER_TRIM_CHARS)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val CHAPTER_TRIM_CHARS = arrayOf(
|
|
||||||
// Whitespace
|
|
||||||
' ',
|
|
||||||
'\u0009',
|
|
||||||
'\u000A',
|
|
||||||
'\u000B',
|
|
||||||
'\u000C',
|
|
||||||
'\u000D',
|
|
||||||
'\u0020',
|
|
||||||
'\u0085',
|
|
||||||
'\u00A0',
|
|
||||||
'\u1680',
|
|
||||||
'\u2000',
|
|
||||||
'\u2001',
|
|
||||||
'\u2002',
|
|
||||||
'\u2003',
|
|
||||||
'\u2004',
|
|
||||||
'\u2005',
|
|
||||||
'\u2006',
|
|
||||||
'\u2007',
|
|
||||||
'\u2008',
|
|
||||||
'\u2009',
|
|
||||||
'\u200A',
|
|
||||||
'\u2028',
|
|
||||||
'\u2029',
|
|
||||||
'\u202F',
|
|
||||||
'\u205F',
|
|
||||||
'\u3000',
|
|
||||||
|
|
||||||
// Separators
|
|
||||||
'-',
|
|
||||||
'_',
|
|
||||||
',',
|
|
||||||
':',
|
|
||||||
).toCharArray()
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.data.chapter
|
|
||||||
|
|
||||||
class NoChaptersException : Exception()
|
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.data.history
|
|
||||||
|
|
||||||
import eu.kanade.domain.history.model.History
|
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
|
||||||
import eu.kanade.domain.manga.model.MangaCover
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
val historyMapper: (Long, Long, Date?, Long) -> History = { id, chapterId, readAt, readDuration ->
|
|
||||||
History(
|
|
||||||
id = id,
|
|
||||||
chapterId = chapterId,
|
|
||||||
readAt = readAt,
|
|
||||||
readDuration = readDuration,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val historyWithRelationsMapper: (Long, Long, Long, String, String?, Long, Boolean, Long, Float, Date?, Long) -> HistoryWithRelations = {
|
|
||||||
historyId, mangaId, chapterId, title, thumbnailUrl, sourceId, isFavorite, coverLastModified, chapterNumber, readAt, readDuration ->
|
|
||||||
HistoryWithRelations(
|
|
||||||
id = historyId,
|
|
||||||
chapterId = chapterId,
|
|
||||||
mangaId = mangaId,
|
|
||||||
title = title,
|
|
||||||
chapterNumber = chapterNumber,
|
|
||||||
readAt = readAt,
|
|
||||||
readDuration = readDuration,
|
|
||||||
coverData = MangaCover(
|
|
||||||
mangaId = mangaId,
|
|
||||||
sourceId = sourceId,
|
|
||||||
isMangaFavorite = isFavorite,
|
|
||||||
url = thumbnailUrl,
|
|
||||||
lastModified = coverLastModified,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package eu.kanade.data.history
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.history.model.HistoryUpdate
|
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
class HistoryRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : HistoryRepository {
|
|
||||||
|
|
||||||
override fun getHistory(query: String): Flow<List<HistoryWithRelations>> {
|
|
||||||
return handler.subscribeToList {
|
|
||||||
historyViewQueries.history(query, historyWithRelationsMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getLastHistory(): HistoryWithRelations? {
|
|
||||||
return handler.awaitOneOrNull {
|
|
||||||
historyViewQueries.getLatestHistory(historyWithRelationsMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getTotalReadDuration(): Long {
|
|
||||||
return handler.awaitOne { historyQueries.getReadDuration() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun resetHistory(historyId: Long) {
|
|
||||||
try {
|
|
||||||
handler.await { historyQueries.resetHistoryById(historyId) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun resetHistoryByMangaId(mangaId: Long) {
|
|
||||||
try {
|
|
||||||
handler.await { historyQueries.resetHistoryByMangaId(mangaId) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteAllHistory(): Boolean {
|
|
||||||
return try {
|
|
||||||
handler.await { historyQueries.removeAllHistory() }
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun upsertHistory(historyUpdate: HistoryUpdate) {
|
|
||||||
try {
|
|
||||||
handler.await {
|
|
||||||
historyQueries.upsert(
|
|
||||||
historyUpdate.chapterId,
|
|
||||||
historyUpdate.readAt,
|
|
||||||
historyUpdate.sessionReadDuration,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.data.manga
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
|
||||||
|
|
||||||
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy) -> Manga =
|
|
||||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy ->
|
|
||||||
Manga(
|
|
||||||
id = id,
|
|
||||||
source = source,
|
|
||||||
favorite = favorite,
|
|
||||||
lastUpdate = lastUpdate ?: 0,
|
|
||||||
dateAdded = dateAdded,
|
|
||||||
viewerFlags = viewerFlags,
|
|
||||||
chapterFlags = chapterFlags,
|
|
||||||
coverLastModified = coverLastModified,
|
|
||||||
url = url,
|
|
||||||
title = title,
|
|
||||||
artist = artist,
|
|
||||||
author = author,
|
|
||||||
description = description,
|
|
||||||
genre = genre,
|
|
||||||
status = status,
|
|
||||||
thumbnailUrl = thumbnailUrl,
|
|
||||||
updateStrategy = updateStrategy,
|
|
||||||
initialized = initialized,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga =
|
|
||||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category ->
|
|
||||||
LibraryManga(
|
|
||||||
manga = mangaMapper(
|
|
||||||
id,
|
|
||||||
source,
|
|
||||||
url,
|
|
||||||
artist,
|
|
||||||
author,
|
|
||||||
description,
|
|
||||||
genre,
|
|
||||||
title,
|
|
||||||
status,
|
|
||||||
thumbnailUrl,
|
|
||||||
favorite,
|
|
||||||
lastUpdate,
|
|
||||||
nextUpdate,
|
|
||||||
initialized,
|
|
||||||
viewerFlags,
|
|
||||||
chapterFlags,
|
|
||||||
coverLastModified,
|
|
||||||
dateAdded,
|
|
||||||
updateStrategy,
|
|
||||||
),
|
|
||||||
category = category,
|
|
||||||
totalChapters = totalCount,
|
|
||||||
readCount = readCount,
|
|
||||||
bookmarkCount = bookmarkCount,
|
|
||||||
latestUpload = latestUpload,
|
|
||||||
chapterFetchedAt = chapterFetchedAt,
|
|
||||||
lastRead = lastRead,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
package eu.kanade.data.manga
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.data.listOfStringsAdapter
|
|
||||||
import eu.kanade.data.updateStrategyAdapter
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.model.MangaUpdate
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.system.toLong
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
class MangaRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : MangaRepository {
|
|
||||||
|
|
||||||
override suspend fun getMangaById(id: Long): Manga {
|
|
||||||
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
|
|
||||||
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
|
|
||||||
return handler.awaitOneOrNull(inTransaction = true) { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow<Manga?> {
|
|
||||||
return handler.subscribeToOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getFavorites(): List<Manga> {
|
|
||||||
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getLibraryManga(): List<LibraryManga> {
|
|
||||||
return handler.awaitList { libraryViewQueries.library(libraryManga) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>> {
|
|
||||||
return handler.subscribeToList { libraryViewQueries.library(libraryManga) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
|
|
||||||
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getDuplicateLibraryManga(title: String, sourceId: Long): Manga? {
|
|
||||||
return handler.awaitOneOrNull {
|
|
||||||
mangasQueries.getDuplicateLibraryManga(title, sourceId, mangaMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun resetViewerFlags(): Boolean {
|
|
||||||
return try {
|
|
||||||
handler.await { mangasQueries.resetViewerFlags() }
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>) {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId)
|
|
||||||
categoryIds.map { categoryId ->
|
|
||||||
mangas_categoriesQueries.insert(mangaId, categoryId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insert(manga: Manga): Long? {
|
|
||||||
return handler.awaitOneOrNull(inTransaction = true) {
|
|
||||||
mangasQueries.insert(
|
|
||||||
source = manga.source,
|
|
||||||
url = manga.url,
|
|
||||||
artist = manga.artist,
|
|
||||||
author = manga.author,
|
|
||||||
description = manga.description,
|
|
||||||
genre = manga.genre,
|
|
||||||
title = manga.title,
|
|
||||||
status = manga.status,
|
|
||||||
thumbnailUrl = manga.thumbnailUrl,
|
|
||||||
favorite = manga.favorite,
|
|
||||||
lastUpdate = manga.lastUpdate,
|
|
||||||
nextUpdate = null,
|
|
||||||
initialized = manga.initialized,
|
|
||||||
viewerFlags = manga.viewerFlags,
|
|
||||||
chapterFlags = manga.chapterFlags,
|
|
||||||
coverLastModified = manga.coverLastModified,
|
|
||||||
dateAdded = manga.dateAdded,
|
|
||||||
updateStrategy = manga.updateStrategy,
|
|
||||||
)
|
|
||||||
mangasQueries.selectLastInsertedRowId()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(update: MangaUpdate): Boolean {
|
|
||||||
return try {
|
|
||||||
partialUpdate(update)
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean {
|
|
||||||
return try {
|
|
||||||
partialUpdate(*mangaUpdates.toTypedArray())
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
mangaUpdates.forEach { value ->
|
|
||||||
mangasQueries.update(
|
|
||||||
source = value.source,
|
|
||||||
url = value.url,
|
|
||||||
artist = value.artist,
|
|
||||||
author = value.author,
|
|
||||||
description = value.description,
|
|
||||||
genre = value.genre?.let(listOfStringsAdapter::encode),
|
|
||||||
title = value.title,
|
|
||||||
status = value.status,
|
|
||||||
thumbnailUrl = value.thumbnailUrl,
|
|
||||||
favorite = value.favorite?.toLong(),
|
|
||||||
lastUpdate = value.lastUpdate,
|
|
||||||
initialized = value.initialized?.toLong(),
|
|
||||||
viewer = value.viewerFlags,
|
|
||||||
chapterFlags = value.chapterFlags,
|
|
||||||
coverLastModified = value.coverLastModified,
|
|
||||||
dateAdded = value.dateAdded,
|
|
||||||
mangaId = value.id,
|
|
||||||
updateStrategy = value.updateStrategy?.let(updateStrategyAdapter::encode),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
class NoResultsException : Exception()
|
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.source.model.SourceData
|
|
||||||
import eu.kanade.domain.source.repository.SourceDataRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class SourceDataRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : SourceDataRepository {
|
|
||||||
|
|
||||||
override fun subscribeAll(): Flow<List<SourceData>> {
|
|
||||||
return handler.subscribeToList { sourcesQueries.findAll(sourceDataMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSourceData(id: Long): SourceData? {
|
|
||||||
return handler.awaitOneOrNull { sourcesQueries.findOne(id, sourceDataMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun upsertSourceData(id: Long, lang: String, name: String) {
|
|
||||||
handler.await { sourcesQueries.upsert(id, lang, name) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.model.SourceData
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val sourceDataMapper: (Long, String, String) -> SourceData = { id, lang, name ->
|
|
||||||
SourceData(id, lang, name)
|
|
||||||
}
|
|
@ -1,62 +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.SManga
|
|
||||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
|
||||||
|
|
||||||
abstract class SourcePagingSource(
|
|
||||||
protected val source: CatalogueSource,
|
|
||||||
) : SourcePagingSourceType() {
|
|
||||||
|
|
||||||
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
|
||||||
|
|
||||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadResult.Page(
|
|
||||||
data = mangasPage.mangas,
|
|
||||||
prevKey = null,
|
|
||||||
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRefreshKey(state: PagingState<Long, SManga>): 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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
|
||||||
import eu.kanade.domain.source.model.SourceWithCount
|
|
||||||
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 kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
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 }
|
|
||||||
.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
|
|
||||||
return SourceSearchPagingSource(source, query, filterList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPopular(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourcePopularPagingSource(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLatest(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourceLatestPagingSource(source)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package eu.kanade.data.track
|
|
||||||
|
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
|
|
||||||
val trackMapper: (Long, Long, Long, Long, Long?, String, Double, Long, Long, Float, String, Long, Long) -> Track =
|
|
||||||
{ id, mangaId, syncId, remoteId, libraryId, title, lastChapterRead, totalChapters, status, score, remoteUrl, startDate, finishDate ->
|
|
||||||
Track(
|
|
||||||
id = id,
|
|
||||||
mangaId = mangaId,
|
|
||||||
syncId = syncId,
|
|
||||||
remoteId = remoteId,
|
|
||||||
libraryId = libraryId,
|
|
||||||
title = title,
|
|
||||||
lastChapterRead = lastChapterRead,
|
|
||||||
totalChapters = totalChapters,
|
|
||||||
status = status,
|
|
||||||
score = score,
|
|
||||||
remoteUrl = remoteUrl,
|
|
||||||
startDate = startDate,
|
|
||||||
finishDate = finishDate,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package eu.kanade.data.track
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class TrackRepositoryImpl(
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : TrackRepository {
|
|
||||||
|
|
||||||
override suspend fun getTrackById(id: Long): Track? {
|
|
||||||
return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, trackMapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getTracksByMangaId(mangaId: Long): List<Track> {
|
|
||||||
return handler.awaitList {
|
|
||||||
manga_syncQueries.getTracksByMangaId(mangaId, trackMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTracksAsFlow(): Flow<List<Track>> {
|
|
||||||
return handler.subscribeToList {
|
|
||||||
manga_syncQueries.getTracks(trackMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<Track>> {
|
|
||||||
return handler.subscribeToList {
|
|
||||||
manga_syncQueries.getTracksByMangaId(mangaId, trackMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(mangaId: Long, syncId: Long) {
|
|
||||||
handler.await {
|
|
||||||
manga_syncQueries.delete(
|
|
||||||
mangaId = mangaId,
|
|
||||||
syncId = syncId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insert(track: Track) {
|
|
||||||
insertValues(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insertAll(tracks: List<Track>) {
|
|
||||||
insertValues(*tracks.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun insertValues(vararg tracks: Track) {
|
|
||||||
handler.await(inTransaction = true) {
|
|
||||||
tracks.forEach { mangaTrack ->
|
|
||||||
manga_syncQueries.insert(
|
|
||||||
mangaId = mangaTrack.mangaId,
|
|
||||||
syncId = mangaTrack.syncId,
|
|
||||||
remoteId = mangaTrack.remoteId,
|
|
||||||
libraryId = mangaTrack.libraryId,
|
|
||||||
title = mangaTrack.title,
|
|
||||||
lastChapterRead = mangaTrack.lastChapterRead,
|
|
||||||
totalChapters = mangaTrack.totalChapters,
|
|
||||||
status = mangaTrack.status,
|
|
||||||
score = mangaTrack.score,
|
|
||||||
remoteUrl = mangaTrack.remoteUrl,
|
|
||||||
startDate = mangaTrack.startDate,
|
|
||||||
finishDate = mangaTrack.finishDate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package eu.kanade.data.updates
|
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.MangaCover
|
|
||||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
|
||||||
|
|
||||||
val updateWithRelationMapper: (Long, String, Long, String, String?, Boolean, Boolean, Long, Boolean, String?, Long, Long, Long) -> UpdatesWithRelations = {
|
|
||||||
mangaId, mangaTitle, chapterId, chapterName, scanlator, read, bookmark, sourceId, favorite, thumbnailUrl, coverLastModified, _, dateFetch ->
|
|
||||||
UpdatesWithRelations(
|
|
||||||
mangaId = mangaId,
|
|
||||||
mangaTitle = mangaTitle,
|
|
||||||
chapterId = chapterId,
|
|
||||||
chapterName = chapterName,
|
|
||||||
scanlator = scanlator,
|
|
||||||
read = read,
|
|
||||||
bookmark = bookmark,
|
|
||||||
sourceId = sourceId,
|
|
||||||
dateFetch = dateFetch,
|
|
||||||
coverData = MangaCover(
|
|
||||||
mangaId = mangaId,
|
|
||||||
sourceId = sourceId,
|
|
||||||
isMangaFavorite = favorite,
|
|
||||||
url = thumbnailUrl,
|
|
||||||
lastModified = coverLastModified,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.data.updates
|
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
|
||||||
import eu.kanade.domain.updates.repository.UpdatesRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class UpdatesRepositoryImpl(
|
|
||||||
val databaseHandler: DatabaseHandler,
|
|
||||||
) : UpdatesRepository {
|
|
||||||
|
|
||||||
override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
|
|
||||||
return databaseHandler.subscribeToList {
|
|
||||||
updatesViewQueries.updates(after, updateWithRelationMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package eu.kanade.domain
|
|
||||||
|
|
||||||
import eu.kanade.data.category.CategoryRepositoryImpl
|
|
||||||
import eu.kanade.data.chapter.ChapterRepositoryImpl
|
|
||||||
import eu.kanade.data.history.HistoryRepositoryImpl
|
|
||||||
import eu.kanade.data.manga.MangaRepositoryImpl
|
|
||||||
import eu.kanade.data.source.SourceDataRepositoryImpl
|
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
|
||||||
import eu.kanade.data.track.TrackRepositoryImpl
|
|
||||||
import eu.kanade.data.updates.UpdatesRepositoryImpl
|
|
||||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
|
||||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
|
||||||
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.category.repository.CategoryRepository
|
|
||||||
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.ShouldUpdateDbChapter
|
|
||||||
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.chapter.repository.ChapterRepository
|
|
||||||
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.GetHistory
|
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
|
||||||
import eu.kanade.domain.history.interactor.GetTotalReadDuration
|
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistory
|
|
||||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
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.manga.repository.MangaRepository
|
|
||||||
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.SourceDataRepository
|
|
||||||
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 eu.kanade.domain.track.repository.TrackRepository
|
|
||||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
|
||||||
import eu.kanade.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()) }
|
|
||||||
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()) }
|
|
||||||
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,16 +0,0 @@
|
|||||||
package eu.kanade.domain.backup.service
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.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,30 +0,0 @@
|
|||||||
package eu.kanade.domain.base
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
|
||||||
|
|
||||||
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() = preferenceStore.getEnum(
|
|
||||||
"extension_installer",
|
|
||||||
if (DeviceUtil.isMiui) PreferenceValues.ExtensionInstaller.LEGACY else PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.anyWithName
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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()
|
|
||||||
if (categories.anyWithName(name)) {
|
|
||||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
Result.InternalError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
object NameAlreadyExistsError : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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,26 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class GetCategories(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Category>> {
|
|
||||||
return categoryRepository.getAllAsFlow()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(mangaId: Long): Flow<List<Category>> {
|
|
||||||
return categoryRepository.getCategoriesByMangaIdAsFlow(mangaId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(): List<Category> {
|
|
||||||
return categoryRepository.getAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long): List<Category> {
|
|
||||||
return categoryRepository.getCategoriesByMangaId(mangaId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.model.anyWithName
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
class RenameCategory(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
|
||||||
val categories = categoryRepository.getAll()
|
|
||||||
if (categories.anyWithName(name)) {
|
|
||||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
object NameAlreadyExistsError : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import logcat.LogPriority
|
|
||||||
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.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
|
|
||||||
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,34 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
|
|
||||||
class SetDisplayModeForCategory(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, display: LibraryDisplayMode) {
|
|
||||||
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 eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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,34 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.LibrarySort
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
|
|
||||||
class SetSortModeForCategory(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
|
||||||
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 eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
|
|
||||||
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,21 +0,0 @@
|
|||||||
package eu.kanade.domain.category.model
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
data class Category(
|
|
||||||
val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val order: Long,
|
|
||||||
val flags: Long,
|
|
||||||
) : Serializable {
|
|
||||||
|
|
||||||
val isSystemCategory: Boolean = id == UNCATEGORIZED_ID
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val UNCATEGORIZED_ID = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun List<Category>.anyWithName(name: String): Boolean {
|
|
||||||
return any { name == it.name }
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package eu.kanade.domain.category.model
|
|
||||||
|
|
||||||
data class CategoryUpdate(
|
|
||||||
val id: Long,
|
|
||||||
val name: String? = null,
|
|
||||||
val order: Long? = null,
|
|
||||||
val flags: Long? = null,
|
|
||||||
)
|
|
@ -1,28 +0,0 @@
|
|||||||
package eu.kanade.domain.category.repository
|
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface CategoryRepository {
|
|
||||||
|
|
||||||
suspend fun get(id: Long): Category?
|
|
||||||
|
|
||||||
suspend fun getAll(): List<Category>
|
|
||||||
|
|
||||||
fun getAllAsFlow(): Flow<List<Category>>
|
|
||||||
|
|
||||||
suspend fun getCategoriesByMangaId(mangaId: Long): List<Category>
|
|
||||||
|
|
||||||
fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>>
|
|
||||||
|
|
||||||
suspend fun insert(category: Category)
|
|
||||||
|
|
||||||
suspend fun updatePartial(update: CategoryUpdate)
|
|
||||||
|
|
||||||
suspend fun updatePartial(updates: List<CategoryUpdate>)
|
|
||||||
|
|
||||||
suspend fun updateAllFlags(flags: Long?)
|
|
||||||
|
|
||||||
suspend fun delete(categoryId: Long)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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 eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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,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 eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
|
|
||||||
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,80 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
class SetReadStatus(
|
|
||||||
private val downloadPreferences: DownloadPreferences,
|
|
||||||
private val deleteDownload: DeleteDownload,
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(manga: Manga, read: Boolean) =
|
|
||||||
await(manga.id, read)
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
object NoChapters : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
|
|
||||||
class ShouldUpdateDbChapter {
|
|
||||||
|
|
||||||
fun await(dbChapter: Chapter, sourceChapter: Chapter): Boolean {
|
|
||||||
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
|
|
||||||
dbChapter.dateUpload != sourceChapter.dateUpload ||
|
|
||||||
dbChapter.chapterNumber != sourceChapter.chapterNumber ||
|
|
||||||
dbChapter.sourceOrder != sourceChapter.sourceOrder
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.data.chapter.CleanupChapterName
|
|
||||||
import eu.kanade.data.chapter.NoChaptersException
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
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 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 = CleanupChapterName.await(sChapter.name, 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, manga.title, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
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 eu.kanade.domain.chapter.model.ChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
|
|
||||||
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,76 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
|
||||||
|
|
||||||
data class Chapter(
|
|
||||||
val id: Long,
|
|
||||||
val mangaId: Long,
|
|
||||||
val read: Boolean,
|
|
||||||
val bookmark: Boolean,
|
|
||||||
val lastPageRead: Long,
|
|
||||||
val dateFetch: Long,
|
|
||||||
val sourceOrder: Long,
|
|
||||||
val url: String,
|
|
||||||
val name: String,
|
|
||||||
val dateUpload: Long,
|
|
||||||
val chapterNumber: Float,
|
|
||||||
val scanlator: String?,
|
|
||||||
) {
|
|
||||||
val isRecognizedNumber: Boolean
|
|
||||||
get() = chapterNumber >= 0f
|
|
||||||
|
|
||||||
fun toSChapter(): SChapter {
|
|
||||||
return SChapter.create().also {
|
|
||||||
it.url = url
|
|
||||||
it.name = name
|
|
||||||
it.date_upload = dateUpload
|
|
||||||
it.chapter_number = chapterNumber
|
|
||||||
it.scanlator = scanlator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun 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 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun create() = Chapter(
|
|
||||||
id = -1,
|
|
||||||
mangaId = -1,
|
|
||||||
read = false,
|
|
||||||
bookmark = false,
|
|
||||||
lastPageRead = 0,
|
|
||||||
dateFetch = 0,
|
|
||||||
sourceOrder = 0,
|
|
||||||
url = "",
|
|
||||||
name = "",
|
|
||||||
dateUpload = -1,
|
|
||||||
chapterNumber = -1f,
|
|
||||||
scanlator = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove when all deps are migrated
|
|
||||||
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()
|
|
||||||
}
|
|