Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
222e111806 | |||
8489b0dd8b | |||
88ed634978 | |||
32188f9f65 | |||
05efc4ebeb | |||
65bfa083f2 | |||
b8a9998bbd | |||
d736bec003 | |||
348b23a9fd | |||
121b2ec829 | |||
1dd130df9e | |||
e17d87f357 | |||
de75561402 | |||
58085336a5 | |||
89ea0a271b | |||
e3f33e24f5 | |||
9fd1419142 | |||
cb06898430 | |||
39407407f2 | |||
a024218410 | |||
26815c7356 | |||
e0deeb8008 | |||
38d6ab80ce | |||
78e66fd8d3 | |||
26aa126ecb | |||
e4a65656e7 | |||
6018aa99e2 | |||
99fd2731f5 | |||
e34043f1fe | |||
3c3a1cd448 | |||
0a2df21c5b | |||
277be02682 | |||
1849715418 | |||
dc6d4f9917 | |||
a9d98e5048 | |||
653940613d | |||
23a2d816e4 | |||
8a3a9146db | |||
a605a4ec75 | |||
c83037eeab | |||
25c76f5612 | |||
0d449a9b1d | |||
62cb12a3f1 | |||
9ec4dc5758 | |||
f594f1994b | |||
96b85962e3 | |||
f77e0e2d00 | |||
ce60ac150b | |||
19afd8c9ca | |||
5067160132 | |||
c9906491fb | |||
e51013d2a4 | |||
1aa75f22d0 | |||
8c910f2a2c | |||
dfb3091e38 | |||
98bdef230a | |||
4b594fc11f | |||
71931cf697 | |||
87e3525f88 | |||
a9c7cbf2c4 | |||
e63a52b8e3 |
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
ko_fi: inorichi
|
|
2
.github/ISSUE_TEMPLATE.md
vendored
@ -5,7 +5,7 @@ I acknowledge that:
|
|||||||
- I have updated:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.15.3)
|
- To the latest version of the app (stable is v0.15.3)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have gone through the FAQ (https://tachiyomi.org/docs/faq/general) and troubleshooting guide (https://tachiyomi.org/docs/guides/troubleshooting/)
|
- I have gone through the FAQ (https://mihon.app/docs/faq/general) and troubleshooting guide (https://mihon.app/docs/guides/troubleshooting/)
|
||||||
- If this is an issue with an official extension, that I should be opening an issue in https://github.com/tachiyomiorg/extensions
|
- If this is an issue with an official extension, that I should be opening an issue in https://github.com/tachiyomiorg/extensions
|
||||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
- 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
|
- I will fill out the title and the information in this template
|
||||||
|
10
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ⚠️ Extension/source issue
|
- name: 🖥️ Mihon website
|
||||||
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
url: https://mihon.app/
|
||||||
about: Issues and requests for official extensions and sources should be opened in the 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/
|
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Guides, troubleshooting, and answers to common questions
|
||||||
|
16
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -1,5 +1,5 @@
|
|||||||
name: 🐞 Issue report
|
name: 🐞 Issue report
|
||||||
description: Report an issue in Tachiyomi
|
description: Report an issue in Mihon
|
||||||
labels: [Bug]
|
labels: [Bug]
|
||||||
body:
|
body:
|
||||||
|
|
||||||
@ -48,12 +48,12 @@ body:
|
|||||||
You can paste the crash logs in plain text or upload it as an attachment.
|
You can paste the crash logs in plain text or upload it as an attachment.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: tachiyomi-version
|
id: mihon-version
|
||||||
attributes:
|
attributes:
|
||||||
label: Tachiyomi version
|
label: Mihon version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Mihon version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.15.3"
|
Example: "0.16.1"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -94,11 +94,9 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[0.15.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
8
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -1,5 +1,5 @@
|
|||||||
name: ⭐ Feature request
|
name: ⭐ Feature request
|
||||||
description: Suggest a feature to improve Tachiyomi
|
description: Suggest a feature to improve Mihon
|
||||||
labels: [Feature request]
|
labels: [Feature request]
|
||||||
body:
|
body:
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ body:
|
|||||||
id: feature-description
|
id: feature-description
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe your suggested feature
|
label: Describe your suggested feature
|
||||||
description: How can Tachiyomi be improved?
|
description: How can Mihon be improved?
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example:
|
Example:
|
||||||
"It should work like this..."
|
"It should work like this..."
|
||||||
@ -31,9 +31,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[0.15.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
BIN
.github/assets/logo.png
vendored
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
.github/readme-images/app-icon.png
vendored
Before Width: | Height: | Size: 1.1 KiB |
60
.github/workflows/build_push.yml
vendored
@ -2,7 +2,7 @@ name: CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
|
||||||
@ -22,6 +22,10 @@ jobs:
|
|||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
run: |
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
@ -36,13 +40,13 @@ jobs:
|
|||||||
# Sign APK and create release for tags
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
- name: Get tag name
|
- name: Get tag name
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/standard/release
|
releaseDirectory: app/build/outputs/apk/standard/release
|
||||||
@ -52,36 +56,36 @@ jobs:
|
|||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
- name: Clean up build artifacts
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
|
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk mihon-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.VERSION_TAG }}
|
tag_name: ${{ env.VERSION_TAG }}
|
||||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
name: Mihon ${{ env.VERSION_TAG }}
|
||||||
body: |
|
body: |
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -94,23 +98,15 @@ jobs:
|
|||||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
||||||
| x86 | ${{ env.APK_X86_SHA }} |
|
| x86 | ${{ env.APK_X86_SHA }} |
|
||||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
||||||
|
|
||||||
|
If you are unsure which version to choose then go with mihon-${{ env.VERSION_TAG }}.apk
|
||||||
files: |
|
files: |
|
||||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
mihon-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
|
||||||
update-website:
|
|
||||||
needs: [build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
steps:
|
|
||||||
- name: Trigger Netlify build hook
|
|
||||||
run: curl -s -X POST -d {} "https://api.netlify.com/build_hooks/${TOKEN}"
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ secrets.NETLIFY_HOOK_RELEASE }}
|
|
||||||
|
2
.github/workflows/issue_moderator.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
|
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
|
||||||
"ignoreCase": true,
|
"ignoreCase": true,
|
||||||
"labels": ["Cloudflare protected"],
|
"labels": ["Cloudflare protected"],
|
||||||
"message": "Refer to the **Solving Cloudflare issues** section at https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
auto-close-ignore-label: do-not-autoclose
|
auto-close-ignore-label: do-not-autoclose
|
||||||
|
BIN
.idea/icon.png
generated
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 26 KiB |
@ -59,7 +59,8 @@ representative at an online or offline event.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community moderators via issues.
|
reported to the community moderators responsible for enforcement at
|
||||||
|
the [Mihon Discord server](https://discord.gg/mihon).
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community moderators are obligated to respect the privacy and security of the
|
All community moderators are obligated to respect the privacy and security of the
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing).
|
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/mihonapp/mihon#issues-feature-requests-and-contributing).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Thanks for your interest in contributing to Tachiyomi!
|
Thanks for your interest in contributing to Mihon!
|
||||||
|
|
||||||
|
|
||||||
# Code contributions
|
# Code contributions
|
||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
If you're interested in taking on [an open issue](https://github.com/mihonapp/mihon/issues), please comment on it so others are aware.
|
||||||
You do not need to ask for permission nor an assignment.
|
You do not need to ask for permission nor an assignment.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
@ -30,25 +30,24 @@ To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
|||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
No support is currently provided.
|
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details.
|
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
|
||||||
|
|
||||||
|
|
||||||
# Forks
|
# Forks
|
||||||
|
|
||||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/mihonapp/mihon/blob/main/LICENSE).
|
||||||
|
|
||||||
When creating a fork, remember to:
|
When creating a fork, remember to:
|
||||||
|
|
||||||
- To avoid confusion with the main app:
|
- To avoid confusion with the main app:
|
||||||
- Change the app name
|
- Change the app name
|
||||||
- Change the app icon
|
- Change the app icon
|
||||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
- Change or disable the [app update checker](https://github.com/mihonapp/mihon/blob/main/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||||
- To avoid installation conflicts:
|
- To avoid installation conflicts:
|
||||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/mihonapp/mihon/blob/main/app/build.gradle.kts)
|
||||||
- To avoid having your data polluting the main app's analytics and crash report services:
|
- 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 Firebase analytics, replace [`google-services.json`](https://github.com/mihonapp/mihon/blob/main/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
|
|
||||||
|
138
README.md
@ -1,91 +1,113 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute |
|
<div align="center">
|
||||||
|-------|--------|----------------|------------|
|
|
||||||
| [](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml) | [](https://github.com/tachiyomiorg/tachiyomi/releases) | [](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) |
|
|
||||||
|
|
||||||
# Tachiyomi
|
<a href="https://mihon.app">
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
<img src="./.github/assets/logo.png" alt="Mihon logo" title="Mihon logo" width="80"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
# Mihon [App](#)
|
||||||
|
|
||||||
|
### Full-featured reader
|
||||||
|
Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.
|
||||||
|
|
||||||
|
[](https://discord.gg/mihon)
|
||||||
|
[](https://github.com/mihonapp/mihon/releases)
|
||||||
|
|
||||||
|
[](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml)
|
||||||
|
[](/LICENSE)
|
||||||
|
[](https://hosted.weblate.org/engage/mihon/)
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
[](https://github.com/mihonapp/mihon/releases)
|
||||||
|
[](https://github.com/mihonapp/mihon-preview/releases)
|
||||||
|
|
||||||
|
*Requires Android 8.0 or higher.*
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Features include:
|
<div align="left">
|
||||||
* Online reading from a variety of sources
|
|
||||||
* Local reading of downloaded content
|
* Local reading of content.
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
|
||||||
* Categories to organize your library
|
* Categories to organize your library.
|
||||||
* Light and dark themes
|
* Light and dark themes.
|
||||||
* Schedule updating your library for new chapters
|
* Schedule updating your library for new chapters.
|
||||||
* Create backups locally to read offline or to your desired cloud service
|
* Create backups locally to read offline or to your desired cloud service.
|
||||||
|
* Plus much more...
|
||||||
|
|
||||||
## Download
|
</div>
|
||||||
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).
|
## Contributing
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
[Code of conduct](./CODE_OF_CONDUCT.md) · [Contributing guide](./CONTRIBUTING.md)
|
||||||
|
|
||||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
If you got any questions, [join our Discord server](https://discord.gg/mihon).
|
||||||
|
|
||||||
**Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/docs/faq/general), the [changelog](https://tachiyomi.org/changelogs/) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
<details align="center"><summary>Issues</summary><div align="left">
|
||||||
|
|
||||||
</details>
|
Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://mihon.app/changelogs/) and the already opened [issues](https://github.com/mihonapp/mihon/issues).
|
||||||
|
|
||||||
<details><summary>Bugs</summary>
|
</div></details>
|
||||||
|
|
||||||
* Include version (More → About → Version)
|
<details align="center"><summary>Bugs</summary><div align="left">
|
||||||
* If not latest, try updating, it may have already been solved
|
|
||||||
* Preview version is equal to the number of commits as seen on the main page
|
* Include version (**More → About → Version**).
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* If not latest, try updating, it may have already been solved.
|
||||||
* Include screenshot (if needed)
|
* Beta version is equal to the number of commits as seen on the main page.
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* 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
|
* Don't group unrelated requests into one issue
|
||||||
|
- **DO:** [#24](https://git.mihon.dev/tachiyomi/tachiyomi/issues/24), [#71](https://git.mihon.dev/tachiyomi/tachiyomi/issues/71)
|
||||||
|
- **DON'T:** [#75](https://git.mihon.dev/tachiyomi/tachiyomi/issues/75)
|
||||||
|
|
||||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
</div></details>
|
||||||
|
|
||||||
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
<details align="center"><summary>Feature requests</summary><div align="left">
|
||||||
|
|
||||||
</details>
|
* Write a detailed issue, explaining what it should do or how.
|
||||||
|
* Avoid writing just "like X app does";
|
||||||
<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)
|
* Include screenshot (if needed)
|
||||||
|
* Source requests are not accepted.
|
||||||
|
|
||||||
Source requests are not accepted.
|
</div></details>
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Contributing</summary>
|
### Repositories
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
[](https://github.com/mihonapp/website/)
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Code of Conduct</summary>
|
### Credits
|
||||||
|
|
||||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
Thank you to all the people who have contributed!
|
||||||
</details>
|
|
||||||
|
|
||||||
## FAQ
|
<a href="https://github.com/mihonapp/mihon/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=mihonapp/mihon" alt="Mihon app contributors" title="Mihon app contributors" width="800"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
### Disclaimer
|
||||||
|
|
||||||
## License
|
The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
|
||||||
|
|
||||||
Copyright 2015 Javier Tomás
|
### License
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
```
|
||||||
you may not use this file except in compliance with the License.
|
Copyright © 2015 Javier Tomás
|
||||||
You may obtain a copy of the License at
|
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
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## Disclaimer
|
Modifications Copyright © 2024 The Mihon Open Source Project
|
||||||
|
```
|
||||||
|
|
||||||
The developer of this application does not have any affiliation with the content providers available.
|
</div>
|
@ -8,6 +8,10 @@ plugins {
|
|||||||
id("com.github.zellius.shortcut-helper")
|
id("com.github.zellius.shortcut-helper")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||||
|
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||||
|
}
|
||||||
|
|
||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
@ -16,10 +20,10 @@ android {
|
|||||||
namespace = "eu.kanade.tachiyomi"
|
namespace = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "app.mihon"
|
||||||
|
|
||||||
versionCode = 119
|
versionCode = 3
|
||||||
versionName = "0.15.3"
|
versionName = "0.16.2"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -135,15 +139,15 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":i18n"))
|
implementation(projects.i18n)
|
||||||
implementation(project(":core"))
|
implementation(projects.core)
|
||||||
implementation(project(":core-metadata"))
|
implementation(projects.coreMetadata)
|
||||||
implementation(project(":source-api"))
|
implementation(projects.sourceApi)
|
||||||
implementation(project(":source-local"))
|
implementation(projects.sourceLocal)
|
||||||
implementation(project(":data"))
|
implementation(projects.data)
|
||||||
implementation(project(":domain"))
|
implementation(projects.domain)
|
||||||
implementation(project(":presentation-core"))
|
implementation(projects.presentationCore)
|
||||||
implementation(project(":presentation-widget"))
|
implementation(projects.presentationWidget)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
implementation(platform(compose.bom))
|
implementation(platform(compose.bom))
|
||||||
@ -238,6 +242,9 @@ dependencies {
|
|||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
|
||||||
|
// Crash reports/analytics
|
||||||
|
"standardImplementation"(libs.firebase.analytics)
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
|
|
||||||
|
4
app/proguard-rules.pro
vendored
@ -71,3 +71,7 @@
|
|||||||
|
|
||||||
# XmlUtil
|
# XmlUtil
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||||
|
|
||||||
|
# Firebase
|
||||||
|
-keep class com.google.firebase.installations.** { *; }
|
||||||
|
-keep interface com.google.firebase.installations.** { *; }
|
||||||
|
23
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="432"
|
||||||
|
android:viewportHeight="432">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,0h432v432h-432z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h432v432h-432z"
|
||||||
|
android:fillColor="#FAFAFA"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h432v432h-432z"
|
||||||
|
android:fillColor="#2E3943"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M322.13,215.5C322.13,272.66 274.64,319 216.07,319C157.49,319 110,272.66 110,215.5C110,158.34 157.49,112 216.07,112C274.64,112 322.13,158.34 322.13,215.5Z"
|
||||||
|
android:fillColor="#F2FAFF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M216.07,299.59C263.66,299.59 302.24,261.94 302.24,215.5C302.24,169.06 263.66,131.41 216.07,131.41C168.47,131.41 129.89,169.06 129.89,215.5C129.89,261.94 168.47,299.59 216.07,299.59ZM216.07,319C274.64,319 322.13,272.66 322.13,215.5C322.13,158.34 274.64,112 216.07,112C157.49,112 110,158.34 110,215.5C110,272.66 157.49,319 216.07,319Z"
|
||||||
|
android:fillColor="#7EBBED"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -1,27 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108.0"
|
android:viewportWidth="432"
|
||||||
android:viewportHeight="108.0">
|
android:viewportHeight="432">
|
||||||
<path
|
<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:pathData="M182.03,188.7L181.33,172.69C183.42,173.09 185.91,173.19 191.57,173.19C198.44,173.19 207.49,172.79 212.16,172.19C214.15,171.99 214.95,171.7 216.24,171L226.98,180.15C225.98,181.54 225.68,182.14 224.59,184.92C223.7,187.11 219.62,199.74 218.03,205.11C225.39,206.6 229.46,207.7 235.03,209.98C235.73,205.11 235.83,202.52 235.83,193.67C235.83,191.39 235.73,190.09 235.43,188.01L252.74,188.6C252.24,190.99 252.14,191.98 252.04,195.86C251.64,205.21 251.24,209.68 250.25,216.45C257.11,219.93 257.11,219.93 260.59,221.82C262.38,222.81 262.78,223.01 263.97,223.41L258.2,242.01C255.42,239.52 251.54,236.83 245.87,233.65C240.9,245.49 232.65,254.14 220.12,261C215.94,255.43 212.76,252.05 207.68,248.07C215.04,244.59 218.43,242.4 222.3,238.72C226.08,235.04 228.57,231.46 230.96,226.09C224.59,223.21 220.51,221.92 213.45,220.43C209.38,232.56 206.09,240.32 203.21,244.99C199.33,251.25 194.06,254.54 187.99,254.54C183.32,254.54 178.55,252.45 175.07,248.87C171.09,244.79 169,239.12 169,232.56C169,222.81 173.67,214.36 181.83,209.09C187.1,205.71 192.67,204.21 201.52,203.72C203.31,197.85 204.8,192.78 206.19,187.11C201.82,187.51 196.35,187.81 189.68,188.1C186.1,188.2 184.91,188.3 182.03,188.7ZM197.14,218.93C192.47,219.73 189.68,221.22 187.2,224.4C185.31,226.59 184.41,229.18 184.41,231.96C184.41,235.04 185.91,237.33 187.8,237.33C190.08,237.33 192.67,232.16 197.14,218.93Z"
|
||||||
android:fillColor="#000"/>
|
android:fillColor="#031019"/>
|
||||||
<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>
|
</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 |
@ -33,6 +33,11 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
|
<!-- Remove permission from Firebase dependency -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="com.google.android.gms.permission.AD_ID"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@ -45,7 +50,7 @@
|
|||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:preserveLegacyExternalStorage="true"
|
android:preserveLegacyExternalStorage="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Tachiyomi">
|
android:theme="@style/Theme.Tachiyomi">
|
||||||
|
|
||||||
@ -174,7 +179,7 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="tachiyomi" />
|
<data android:scheme="mihon" />
|
||||||
|
|
||||||
<data android:host="anilist-auth" />
|
<data android:host="anilist-auth" />
|
||||||
<data android:host="bangumi-auth" />
|
<data android:host="bangumi-auth" />
|
||||||
@ -231,6 +236,11 @@
|
|||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
android:name="android.webkit.WebView.MetricsOptOut"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
|
<!-- Disable advertising ID collection for Firebase -->
|
||||||
|
<meta-data
|
||||||
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
|
android:value="false" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -163,7 +163,7 @@ class SyncChaptersWithSource(
|
|||||||
var updatedToAdd = newChapters.map { toAddItem ->
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
chapter = chapter.copy(
|
chapter = chapter.copy(
|
||||||
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
||||||
|
@ -23,7 +23,7 @@ class GetExtensionSources(
|
|||||||
ExtensionSourceItem(
|
ExtensionSourceItem(
|
||||||
source = source,
|
source = source,
|
||||||
enabled = source.isEnabled(),
|
enabled = source.isEnabled(),
|
||||||
labelAsName = isMultiSource && isMultiLangSingleSource.not(),
|
labelAsName = isMultiSource && !isMultiLangSingleSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,9 @@ class GetExtensionsByType(
|
|||||||
extensionManager.availableExtensionsFlow,
|
extensionManager.availableExtensionsFlow,
|
||||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||||
val (updates, installed) = _installed
|
val (updates, installed) = _installed
|
||||||
.filter { (showNsfwSources || it.isNsfw.not()) }
|
.filter { (showNsfwSources || !it.isNsfw) }
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareBy<Extension.Installed> { it.isObsolete.not() }
|
compareBy<Extension.Installed> { !it.isObsolete }
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
)
|
)
|
||||||
.partition { it.hasUpdate }
|
.partition { it.hasUpdate }
|
||||||
@ -36,7 +36,7 @@ class GetExtensionsByType(
|
|||||||
.filter { extension ->
|
.filter { extension ->
|
||||||
_installed.none { it.pkgName == extension.pkgName } &&
|
_installed.none { it.pkgName == extension.pkgName } &&
|
||||||
_untrusted.none { it.pkgName == extension.pkgName } &&
|
_untrusted.none { it.pkgName == extension.pkgName } &&
|
||||||
(showNsfwSources || extension.isNsfw.not())
|
(showNsfwSources || !extension.isNsfw)
|
||||||
}
|
}
|
||||||
.flatMap { ext ->
|
.flatMap { ext ->
|
||||||
if (ext.sources.isEmpty()) {
|
if (ext.sources.isEmpty()) {
|
||||||
|
@ -34,15 +34,15 @@ class GetSourcesWithFavoriteCount(
|
|||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && !b.first.isStub -> -1
|
||||||
b.first.isStub && a.first.isStub.not() -> 1
|
b.first.isStub && !a.first.isStub -> 1
|
||||||
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetMigrateSorting.Mode.TOTAL -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && !b.first.isStub -> -1
|
||||||
b.first.isStub && a.first.isStub.not() -> 1
|
b.first.isStub && !a.first.isStub -> 1
|
||||||
else -> a.second.compareTo(b.second)
|
else -> a.second.compareTo(b.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,9 @@ class RefreshTracks(
|
|||||||
.map { (track, service) ->
|
.map { (track, service) ->
|
||||||
async {
|
async {
|
||||||
return@async try {
|
return@async try {
|
||||||
val updatedTrack = service!!.refresh(track.toDbTrack())
|
val updatedTrack = service!!.refresh(track.toDbTrack()).toDomainTrack()!!
|
||||||
insertTrack.await(updatedTrack.toDomainTrack()!!)
|
insertTrack.await(updatedTrack)
|
||||||
syncChapterProgressWithTrack.await(mangaId, track, service)
|
syncChapterProgressWithTrack.await(mangaId, updatedTrack, service)
|
||||||
null
|
null
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
service to e
|
service to e
|
||||||
|
@ -19,30 +19,28 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
|||||||
it.remote_id = remoteId
|
it.remote_id = remoteId
|
||||||
it.library_id = libraryId
|
it.library_id = libraryId
|
||||||
it.title = title
|
it.title = title
|
||||||
it.last_chapter_read = lastChapterRead.toFloat()
|
it.last_chapter_read = lastChapterRead
|
||||||
it.total_chapters = totalChapters.toInt()
|
it.total_chapters = totalChapters
|
||||||
it.status = status.toInt()
|
it.status = status
|
||||||
it.score = score.toFloat()
|
it.score = score
|
||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||||
val trackId = id ?: if (idRequired.not()) -1 else return null
|
val trackId = id ?: if (!idRequired) -1 else return null
|
||||||
return Track(
|
return Track(
|
||||||
id = trackId,
|
id = trackId,
|
||||||
mangaId = manga_id,
|
mangaId = manga_id,
|
||||||
trackerId = tracker_id.toLong(),
|
trackerId = tracker_id,
|
||||||
remoteId = remote_id,
|
remoteId = remote_id,
|
||||||
libraryId = library_id,
|
libraryId = library_id,
|
||||||
title = title,
|
title = title,
|
||||||
lastChapterRead = last_chapter_read.toDouble(),
|
lastChapterRead = last_chapter_read,
|
||||||
totalChapters = total_chapters.toLong(),
|
totalChapters = total_chapters,
|
||||||
status = status.toLong(),
|
status = status,
|
||||||
// Jank workaround due to precision issues while converting
|
score = score,
|
||||||
// See https://github.com/tachiyomiorg/tachiyomi/issues/10343
|
|
||||||
score = score.toString().toDouble(),
|
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
@ -19,9 +19,15 @@ class TrackPreferences(
|
|||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun trackAuthExpired(tracker: Tracker) = preferenceStore.getBoolean(
|
||||||
|
Preference.privateKey("pref_tracker_auth_expired_${tracker.id}"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||||
trackUsername(tracker).set(username)
|
trackUsername(tracker).set(username)
|
||||||
trackPassword(tracker).set(password)
|
trackPassword(tracker).set(password)
|
||||||
|
trackAuthExpired(tracker).set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.domain.ui
|
package eu.kanade.domain.ui
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
@ -16,10 +15,7 @@ class UiPreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun themeMode() = preferenceStore.getEnum(
|
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
||||||
"pref_theme_mode_key",
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT },
|
|
||||||
)
|
|
||||||
|
|
||||||
fun appTheme() = preferenceStore.getEnum(
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
"pref_app_theme",
|
"pref_app_theme",
|
||||||
|
@ -203,7 +203,13 @@ private fun ExtensionContent(
|
|||||||
items(
|
items(
|
||||||
items = items,
|
items = items,
|
||||||
contentType = { "item" },
|
contentType = { "item" },
|
||||||
key = { "extension-${it.hashCode()}" },
|
key = { item ->
|
||||||
|
when (item.extension) {
|
||||||
|
is Extension.Untrusted -> "extension-untrusted-${item.hashCode()}"
|
||||||
|
is Extension.Installed -> "extension-installed-${item.hashCode()}"
|
||||||
|
is Extension.Available -> "extension-available-${item.hashCode()}"
|
||||||
|
}
|
||||||
|
},
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -173,14 +172,9 @@ fun MangaCoverDialog(
|
|||||||
// Copy bitmap in case it came from memory cache
|
// Copy bitmap in case it came from memory cache
|
||||||
// Because SSIV needs to thoroughly read the image
|
// Because SSIV needs to thoroughly read the image
|
||||||
val copy = (drawable as? BitmapDrawable)?.let {
|
val copy = (drawable as? BitmapDrawable)?.let {
|
||||||
val config = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
Bitmap.Config.HARDWARE
|
|
||||||
} else {
|
|
||||||
Bitmap.Config.ARGB_8888
|
|
||||||
}
|
|
||||||
BitmapDrawable(
|
BitmapDrawable(
|
||||||
view.context.resources,
|
view.context.resources,
|
||||||
it.bitmap.copy(config, false),
|
it.bitmap.copy(Bitmap.Config.HARDWARE, false),
|
||||||
)
|
)
|
||||||
} ?: drawable
|
} ?: drawable
|
||||||
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
||||||
|
@ -21,7 +21,7 @@ fun LogoHeader() {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_tachi),
|
painter = painterResource(R.drawable.ic_mihon),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -56,7 +56,7 @@ internal class GuidesStep(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-started"
|
const val GETTING_STARTED_URL = "https://mihon.app/docs/guides/getting-started"
|
||||||
|
|
||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.presentation.more.settings.screen
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.webkit.WebStorage
|
import android.webkit.WebStorage
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
@ -84,60 +83,48 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||||
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
||||||
|
|
||||||
return buildList {
|
return listOf(
|
||||||
addAll(
|
Preference.PreferenceItem.TextPreference(
|
||||||
listOf(
|
title = stringResource(MR.strings.pref_dump_crash_logs),
|
||||||
Preference.PreferenceItem.TextPreference(
|
subtitle = stringResource(MR.strings.pref_dump_crash_logs_summary),
|
||||||
title = stringResource(MR.strings.pref_dump_crash_logs),
|
onClick = {
|
||||||
subtitle = stringResource(MR.strings.pref_dump_crash_logs_summary),
|
scope.launch {
|
||||||
onClick = {
|
CrashLogUtil(context).dumpLogs()
|
||||||
scope.launch {
|
}
|
||||||
CrashLogUtil(context).dumpLogs()
|
},
|
||||||
}
|
),
|
||||||
},
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
),
|
pref = networkPreferences.verboseLogging(),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
title = stringResource(MR.strings.pref_verbose_logging),
|
||||||
pref = networkPreferences.verboseLogging(),
|
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
|
||||||
title = stringResource(MR.strings.pref_verbose_logging),
|
onValueChanged = {
|
||||||
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
|
context.toast(MR.strings.requires_app_restart)
|
||||||
onValueChanged = {
|
true
|
||||||
context.toast(MR.strings.requires_app_restart)
|
},
|
||||||
true
|
),
|
||||||
},
|
Preference.PreferenceItem.TextPreference(
|
||||||
),
|
title = stringResource(MR.strings.pref_debug_info),
|
||||||
Preference.PreferenceItem.TextPreference(
|
onClick = { navigator.push(DebugInfoScreen()) },
|
||||||
title = stringResource(MR.strings.pref_debug_info),
|
),
|
||||||
onClick = { navigator.push(DebugInfoScreen()) },
|
Preference.PreferenceItem.TextPreference(
|
||||||
),
|
title = stringResource(MR.strings.pref_onboarding_guide),
|
||||||
Preference.PreferenceItem.TextPreference(
|
onClick = { navigator.push(OnboardingScreen()) },
|
||||||
title = stringResource(MR.strings.pref_onboarding_guide),
|
),
|
||||||
onClick = { navigator.push(OnboardingScreen()) },
|
Preference.PreferenceItem.TextPreference(
|
||||||
),
|
title = stringResource(MR.strings.pref_manage_notifications),
|
||||||
),
|
onClick = {
|
||||||
)
|
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||||
add(
|
}
|
||||||
Preference.PreferenceItem.TextPreference(
|
context.startActivity(intent)
|
||||||
title = stringResource(MR.strings.pref_manage_notifications),
|
},
|
||||||
onClick = {
|
),
|
||||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
getBackgroundActivityGroup(),
|
||||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
getDataGroup(),
|
||||||
}
|
getNetworkGroup(networkPreferences = networkPreferences),
|
||||||
context.startActivity(intent)
|
getLibraryGroup(),
|
||||||
},
|
getExtensionsGroup(basePreferences = basePreferences),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
addAll(
|
|
||||||
listOf(
|
|
||||||
getBackgroundActivityGroup(),
|
|
||||||
getDataGroup(),
|
|
||||||
getNetworkGroup(networkPreferences = networkPreferences),
|
|
||||||
getLibraryGroup(),
|
|
||||||
getExtensionsGroup(basePreferences = basePreferences),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -7,8 +7,11 @@ import android.net.Uri
|
|||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
@ -64,7 +67,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
object SettingsDataScreen : SearchableSettings {
|
object SettingsDataScreen : SearchableSettings {
|
||||||
|
|
||||||
val restorePreferenceKeyString = MR.strings.label_backup
|
val restorePreferenceKeyString = MR.strings.label_backup
|
||||||
const val HELP_URL = "https://tachiyomi.org/docs/faq/storage"
|
const val HELP_URL = "https://mihon.app/docs/faq/storage"
|
||||||
|
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
@Composable
|
@Composable
|
||||||
@ -189,9 +192,11 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
MultiChoiceSegmentedButtonRow(
|
MultiChoiceSegmentedButtonRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.height(intrinsicSize = IntrinsicSize.Min)
|
||||||
.padding(horizontal = PrefsHorizontalPadding),
|
.padding(horizontal = PrefsHorizontalPadding),
|
||||||
) {
|
) {
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
checked = false,
|
checked = false,
|
||||||
onCheckedChange = { navigator.push(CreateBackupScreen()) },
|
onCheckedChange = { navigator.push(CreateBackupScreen()) },
|
||||||
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
||||||
@ -199,6 +204,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
Text(stringResource(MR.strings.pref_create_backup))
|
Text(stringResource(MR.strings.pref_create_backup))
|
||||||
}
|
}
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
checked = false,
|
checked = false,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
if (!BackupRestoreJob.isRunning(context)) {
|
if (!BackupRestoreJob.isRunning(context)) {
|
||||||
|
@ -60,7 +60,6 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
pref = readerPref.trueColor(),
|
pref = readerPref.trueColor(),
|
||||||
title = stringResource(MR.strings.pref_true_color),
|
title = stringResource(MR.strings.pref_true_color),
|
||||||
subtitle = stringResource(MR.strings.pref_true_color_summary),
|
subtitle = stringResource(MR.strings.pref_true_color_summary),
|
||||||
enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O,
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.pageTransitions(),
|
pref = readerPref.pageTransitions(),
|
||||||
|
@ -71,7 +71,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun RowScope.AppBarAction() {
|
override fun RowScope.AppBarAction() {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
|
IconButton(onClick = { uriHandler.openUri("https://mihon.app/docs/guides/tracking") }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
contentDescription = stringResource(MR.strings.tracking_guide),
|
contentDescription = stringResource(MR.strings.tracking_guide),
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.about
|
package eu.kanade.presentation.more.settings.screen.about
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -27,10 +30,13 @@ import eu.kanade.presentation.util.LocalBackPress
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
|
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||||
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
@ -42,6 +48,7 @@ import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
|||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.icons.CustomIcons
|
import tachiyomi.presentation.core.icons.CustomIcons
|
||||||
|
import tachiyomi.presentation.core.icons.Discord
|
||||||
import tachiyomi.presentation.core.icons.Facebook
|
import tachiyomi.presentation.core.icons.Facebook
|
||||||
import tachiyomi.presentation.core.icons.Github
|
import tachiyomi.presentation.core.icons.Github
|
||||||
import tachiyomi.presentation.core.icons.Reddit
|
import tachiyomi.presentation.core.icons.Reddit
|
||||||
@ -91,6 +98,54 @@ object AboutScreen : Screen() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
item {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.check_for_updates),
|
||||||
|
widget = {
|
||||||
|
AnimatedVisibility(visible = isCheckingUpdates) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
strokeWidth = 3.dp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPreferenceClick = {
|
||||||
|
if (!isCheckingUpdates) {
|
||||||
|
scope.launch {
|
||||||
|
isCheckingUpdates = true
|
||||||
|
|
||||||
|
checkVersion(
|
||||||
|
context = context,
|
||||||
|
onAvailableUpdate = { result ->
|
||||||
|
val updateScreen = NewUpdateScreen(
|
||||||
|
versionName = result.release.version,
|
||||||
|
changelogInfo = result.release.info,
|
||||||
|
releaseLink = result.release.releaseLink,
|
||||||
|
downloadLink = result.release.getDownloadLink(),
|
||||||
|
)
|
||||||
|
navigator.push(updateScreen)
|
||||||
|
},
|
||||||
|
onFinish = {
|
||||||
|
isCheckingUpdates = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BuildConfig.DEBUG) {
|
||||||
|
item {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.whats_new),
|
||||||
|
onPreferenceClick = { uriHandler.openUri(RELEASE_URL) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(MR.strings.licenses),
|
title = stringResource(MR.strings.licenses),
|
||||||
@ -101,7 +156,7 @@ object AboutScreen : Screen() {
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(MR.strings.privacy_policy),
|
title = stringResource(MR.strings.privacy_policy),
|
||||||
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy/") },
|
onPreferenceClick = { uriHandler.openUri("https://mihon.app/privacy/") },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,27 +170,32 @@ object AboutScreen : Screen() {
|
|||||||
LinkIcon(
|
LinkIcon(
|
||||||
label = stringResource(MR.strings.website),
|
label = stringResource(MR.strings.website),
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
url = "https://tachiyomi.org",
|
url = "https://mihon.app",
|
||||||
|
)
|
||||||
|
LinkIcon(
|
||||||
|
label = "Discord",
|
||||||
|
icon = CustomIcons.Discord,
|
||||||
|
url = "https://discord.gg/mihon",
|
||||||
)
|
)
|
||||||
LinkIcon(
|
LinkIcon(
|
||||||
label = "X",
|
label = "X",
|
||||||
icon = CustomIcons.X,
|
icon = CustomIcons.X,
|
||||||
url = "https://x.com/tachiyomiorg",
|
url = "https://x.com/mihonapp",
|
||||||
)
|
)
|
||||||
LinkIcon(
|
LinkIcon(
|
||||||
label = "Facebook",
|
label = "Facebook",
|
||||||
icon = CustomIcons.Facebook,
|
icon = CustomIcons.Facebook,
|
||||||
url = "https://facebook.com/tachiyomiorg",
|
url = "https://facebook.com/mihonapp",
|
||||||
)
|
)
|
||||||
LinkIcon(
|
LinkIcon(
|
||||||
label = "Reddit",
|
label = "Reddit",
|
||||||
icon = CustomIcons.Reddit,
|
icon = CustomIcons.Reddit,
|
||||||
url = "https://www.reddit.com/r/Tachiyomi",
|
url = "https://www.reddit.com/r/mihonapp",
|
||||||
)
|
)
|
||||||
LinkIcon(
|
LinkIcon(
|
||||||
label = "GitHub",
|
label = "GitHub",
|
||||||
icon = CustomIcons.Github,
|
icon = CustomIcons.Github,
|
||||||
url = "https://github.com/tachiyomiorg",
|
url = "https://github.com/mihonapp",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,7 +247,7 @@ object AboutScreen : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuildConfig.PREVIEW -> {
|
BuildConfig.PREVIEW -> {
|
||||||
"Preview r${BuildConfig.COMMIT_COUNT}".let {
|
"Beta r${BuildConfig.COMMIT_COUNT}".let {
|
||||||
if (withBuildDate) {
|
if (withBuildDate) {
|
||||||
"$it (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
|
"$it (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
||||||
@ -13,18 +12,11 @@ import eu.kanade.domain.ui.model.ThemeMode
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
private val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
private val options = mapOf(
|
||||||
mapOf(
|
ThemeMode.SYSTEM to MR.strings.theme_system,
|
||||||
ThemeMode.SYSTEM to MR.strings.theme_system,
|
ThemeMode.LIGHT to MR.strings.theme_light,
|
||||||
ThemeMode.LIGHT to MR.strings.theme_light,
|
ThemeMode.DARK to MR.strings.theme_dark,
|
||||||
ThemeMode.DARK to MR.strings.theme_dark,
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mapOf(
|
|
||||||
ThemeMode.LIGHT to MR.strings.theme_light,
|
|
||||||
ThemeMode.DARK to MR.strings.theme_dark,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AppThemeModePreferenceWidget(
|
internal fun AppThemeModePreferenceWidget(
|
||||||
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class DisplayRefreshHost {
|
class DisplayRefreshHost {
|
||||||
@ -30,15 +31,15 @@ fun DisplayRefreshHost(
|
|||||||
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
||||||
LaunchedEffect(currentDisplayRefresh) {
|
LaunchedEffect(currentDisplayRefresh) {
|
||||||
if (currentDisplayRefresh) {
|
if (currentDisplayRefresh) {
|
||||||
delay(1500)
|
delay(1.5.seconds)
|
||||||
hostState.currentDisplayRefresh = false
|
hostState.currentDisplayRefresh = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDisplayRefresh) {
|
Canvas(
|
||||||
Canvas(
|
modifier = modifier.fillMaxSize(),
|
||||||
modifier = modifier.fillMaxSize(),
|
) {
|
||||||
) {
|
if (currentDisplayRefresh) {
|
||||||
drawRect(Color.Black)
|
drawRect(Color.Black)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ fun TrackInfoDialogHome(
|
|||||||
TrackInfoItem(
|
TrackInfoItem(
|
||||||
title = item.track.title,
|
title = item.track.title,
|
||||||
tracker = item.tracker,
|
tracker = item.tracker,
|
||||||
status = item.tracker.getStatus(item.track.status.toInt()),
|
status = item.tracker.getStatus(item.track.status),
|
||||||
onStatusClick = { onStatusClick(item) },
|
onStatusClick = { onStatusClick(item) },
|
||||||
chapters = "${item.track.lastChapterRead.toInt()}".let {
|
chapters = "${item.track.lastChapterRead.toInt()}".let {
|
||||||
val totalChapters = item.track.totalChapters
|
val totalChapters = item.track.totalChapters
|
||||||
|
@ -48,9 +48,9 @@ import tachiyomi.presentation.core.util.isScrolledToStart
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TrackStatusSelector(
|
fun TrackStatusSelector(
|
||||||
selection: Int,
|
selection: Long,
|
||||||
onSelectionChange: (Int) -> Unit,
|
onSelectionChange: (Long) -> Unit,
|
||||||
selections: Map<Int, StringResource?>,
|
selections: Map<Long, StringResource?>,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -236,12 +236,12 @@ private fun TrackStatusSelectorPreviews() {
|
|||||||
onSelectionChange = {},
|
onSelectionChange = {},
|
||||||
selections = persistentMapOf(
|
selections = persistentMapOf(
|
||||||
// Anilist values
|
// Anilist values
|
||||||
1 to MR.strings.reading,
|
1L to MR.strings.reading,
|
||||||
2 to MR.strings.plan_to_read,
|
2L to MR.strings.plan_to_read,
|
||||||
3 to MR.strings.completed,
|
3L to MR.strings.completed,
|
||||||
4 to MR.strings.on_hold,
|
4L to MR.strings.on_hold,
|
||||||
5 to MR.strings.dropped,
|
5L to MR.strings.dropped,
|
||||||
6 to MR.strings.repeating,
|
6L to MR.strings.repeating,
|
||||||
),
|
),
|
||||||
onConfirm = {},
|
onConfirm = {},
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
|
@ -301,7 +301,7 @@ private fun SearchResultItem(
|
|||||||
text = status,
|
text = status,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (trackSearch.score != -1f) {
|
if (trackSearch.score != -1.0) {
|
||||||
SearchResultItemDetails(
|
SearchResultItemDetails(
|
||||||
title = stringResource(MR.strings.score),
|
title = stringResource(MR.strings.score),
|
||||||
text = trackSearch.score.toString(),
|
text = trackSearch.score.toString(),
|
||||||
|
@ -62,14 +62,14 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
private fun randTrackSearch() = TrackSearch().let {
|
private fun randTrackSearch() = TrackSearch().let {
|
||||||
it.id = Random.nextLong()
|
it.id = Random.nextLong()
|
||||||
it.manga_id = Random.nextLong()
|
it.manga_id = Random.nextLong()
|
||||||
it.tracker_id = Random.nextInt()
|
it.tracker_id = Random.nextLong()
|
||||||
it.remote_id = Random.nextLong()
|
it.remote_id = Random.nextLong()
|
||||||
it.library_id = Random.nextLong()
|
it.library_id = Random.nextLong()
|
||||||
it.title = lorem((1..10).random()).joinToString()
|
it.title = lorem((1..10).random()).joinToString()
|
||||||
it.last_chapter_read = (0..100).random().toFloat()
|
it.last_chapter_read = (0..100).random().toDouble()
|
||||||
it.total_chapters = (100..1000).random()
|
it.total_chapters = (100L..1000L).random()
|
||||||
it.score = (0..10).random().toFloat()
|
it.score = (0..10).random().toDouble()
|
||||||
it.status = Random.nextInt()
|
it.status = Random.nextLong()
|
||||||
it.started_reading_date = 0L
|
it.started_reading_date = 0L
|
||||||
it.finished_reading_date = 0L
|
it.finished_reading_date = 0L
|
||||||
it.tracking_url = "https://example.com/tracker-example"
|
it.tracking_url = "https://example.com/tracker-example"
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -23,12 +21,7 @@ fun rememberRequestPackageInstallsPermissionState(initialValue: Boolean = false)
|
|||||||
DisposableEffect(lifecycleOwner.lifecycle) {
|
DisposableEffect(lifecycleOwner.lifecycle) {
|
||||||
val observer = object : DefaultLifecycleObserver {
|
val observer = object : DefaultLifecycleObserver {
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
installGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
installGranted = context.packageManager.canRequestPackageInstalls()
|
||||||
context.packageManager.canRequestPackageInstalls()
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
Settings.Secure.getInt(context.contentResolver, Settings.Secure.INSTALL_NON_MARKET_APPS) != 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
|
@ -175,7 +175,7 @@ fun WebViewScreenContent(
|
|||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri(
|
uriHandler.openUri(
|
||||||
"https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare",
|
"https://mihon.app/docs/guides/troubleshooting/#cloudflare",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -169,22 +169,19 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getPackageName(): String {
|
override fun getPackageName(): String {
|
||||||
// This causes freezes in Android 6/7 for some reason
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// Override the value passed as X-Requested-With in WebView requests
|
||||||
try {
|
val stackTrace = Looper.getMainLooper().thread.stackTrace
|
||||||
// Override the value passed as X-Requested-With in WebView requests
|
val chromiumElement = stackTrace.find {
|
||||||
val stackTrace = Looper.getMainLooper().thread.stackTrace
|
it.className.equals(
|
||||||
val chromiumElement = stackTrace.find {
|
"org.chromium.base.BuildInfo",
|
||||||
it.className.equals(
|
ignoreCase = true,
|
||||||
"org.chromium.base.BuildInfo",
|
)
|
||||||
ignoreCase = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
|
||||||
return WebViewUtil.SPOOF_PACKAGE_NAME
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
}
|
||||||
|
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
||||||
|
return WebViewUtil.SPOOF_PACKAGE_NAME
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
return super.getPackageName()
|
return super.getPackageName()
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
|
||||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.TriState
|
|
||||||
import tachiyomi.core.preference.getAndSet
|
|
||||||
import tachiyomi.core.preference.getEnum
|
|
||||||
import tachiyomi.core.preference.minusAssign
|
|
||||||
import tachiyomi.core.preference.plusAssign
|
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object Migrations {
|
object Migrations {
|
||||||
|
|
||||||
@ -37,18 +13,10 @@ object Migrations {
|
|||||||
*
|
*
|
||||||
* @return true if a migration is performed, false otherwise.
|
* @return true if a migration is performed, false otherwise.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("SameReturnValue")
|
||||||
fun upgrade(
|
fun upgrade(
|
||||||
context: Context,
|
context: Context,
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
basePreferences: BasePreferences,
|
|
||||||
uiPreferences: UiPreferences,
|
|
||||||
networkPreferences: NetworkPreferences,
|
|
||||||
sourcePreferences: SourcePreferences,
|
|
||||||
securityPreferences: SecurityPreferences,
|
|
||||||
libraryPreferences: LibraryPreferences,
|
|
||||||
readerPreferences: ReaderPreferences,
|
|
||||||
backupPreferences: BackupPreferences,
|
|
||||||
trackerManager: TrackerManager,
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val lastVersionCode = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0)
|
val lastVersionCode = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0)
|
||||||
val oldVersion = lastVersionCode.get()
|
val oldVersion = lastVersionCode.get()
|
||||||
@ -63,399 +31,8 @@ object Migrations {
|
|||||||
if (oldVersion == 0) {
|
if (oldVersion == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
|
|
||||||
if (oldVersion < 15) {
|
|
||||||
// Delete internal chapter cache dir.
|
|
||||||
File(context.cacheDir, "chapter_disk_cache").deleteRecursively()
|
|
||||||
}
|
|
||||||
if (oldVersion < 19) {
|
|
||||||
// Move covers to external files dir.
|
|
||||||
val oldDir = File(context.externalCacheDir, "cover_disk_cache")
|
|
||||||
if (oldDir.exists()) {
|
|
||||||
val destDir = context.getExternalFilesDir("covers")
|
|
||||||
if (destDir != null) {
|
|
||||||
oldDir.listFiles()?.forEach {
|
|
||||||
it.renameTo(File(destDir, it.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 26) {
|
|
||||||
// Delete external chapter cache dir.
|
|
||||||
val extCache = context.externalCacheDir
|
|
||||||
if (extCache != null) {
|
|
||||||
val chapterCache = File(extCache, "chapter_disk_cache")
|
|
||||||
if (chapterCache.exists()) {
|
|
||||||
chapterCache.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 44) {
|
|
||||||
// Reset sorting preference if using removed sort by source
|
|
||||||
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
|
|
||||||
|
|
||||||
if (oldSortingMode == 5) { // SOURCE = 5
|
|
||||||
prefs.edit {
|
|
||||||
putInt(libraryPreferences.sortingMode().key(), 0) // ALPHABETICAL = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 52) {
|
|
||||||
// Migrate library filters to tri-state versions
|
|
||||||
fun convertBooleanPrefToTriState(key: String): Int {
|
|
||||||
val oldPrefValue = prefs.getBoolean(key, false)
|
|
||||||
return if (oldPrefValue) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefs.edit {
|
|
||||||
putInt(
|
|
||||||
libraryPreferences.filterDownloaded().key(),
|
|
||||||
convertBooleanPrefToTriState("pref_filter_downloaded_key"),
|
|
||||||
)
|
|
||||||
remove("pref_filter_downloaded_key")
|
|
||||||
|
|
||||||
putInt(
|
|
||||||
libraryPreferences.filterUnread().key(),
|
|
||||||
convertBooleanPrefToTriState("pref_filter_unread_key"),
|
|
||||||
)
|
|
||||||
remove("pref_filter_unread_key")
|
|
||||||
|
|
||||||
putInt(
|
|
||||||
libraryPreferences.filterCompleted().key(),
|
|
||||||
convertBooleanPrefToTriState("pref_filter_completed_key"),
|
|
||||||
)
|
|
||||||
remove("pref_filter_completed_key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 54) {
|
|
||||||
// Force MAL log out due to login flow change
|
|
||||||
// v52: switched from scraping to WebView
|
|
||||||
// v53: switched from WebView to OAuth
|
|
||||||
if (trackerManager.myAnimeList.isLoggedIn) {
|
|
||||||
trackerManager.myAnimeList.logout()
|
|
||||||
context.toast(MR.strings.myanimelist_relogin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 57) {
|
|
||||||
// Migrate DNS over HTTPS setting
|
|
||||||
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
|
|
||||||
if (wasDohEnabled) {
|
|
||||||
prefs.edit {
|
|
||||||
putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE)
|
|
||||||
remove("enable_doh")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 59) {
|
|
||||||
// Reset rotation to Free after replacing Lock
|
|
||||||
if (prefs.contains("pref_rotation_type_key")) {
|
|
||||||
prefs.edit {
|
|
||||||
putInt("pref_rotation_type_key", 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 60) {
|
|
||||||
// Migrate Rotation and Viewer values to default values for viewer_flags
|
|
||||||
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
|
|
||||||
1 -> ReaderOrientation.FREE.flagValue
|
|
||||||
2 -> ReaderOrientation.PORTRAIT.flagValue
|
|
||||||
3 -> ReaderOrientation.LANDSCAPE.flagValue
|
|
||||||
4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue
|
|
||||||
5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue
|
|
||||||
else -> ReaderOrientation.FREE.flagValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading mode flag and prefValue is the same value
|
|
||||||
val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
|
|
||||||
|
|
||||||
prefs.edit {
|
|
||||||
putInt("pref_default_orientation_type_key", newOrientation)
|
|
||||||
remove("pref_rotation_type_key")
|
|
||||||
putInt("pref_default_reading_mode_key", newReadingMode)
|
|
||||||
remove("pref_default_viewer_key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 61) {
|
|
||||||
// Handle removed every 1 or 2 hour library updates
|
|
||||||
val updateInterval = libraryPreferences.autoUpdateInterval().get()
|
|
||||||
if (updateInterval == 1 || updateInterval == 2) {
|
|
||||||
libraryPreferences.autoUpdateInterval().set(3)
|
|
||||||
LibraryUpdateJob.setupTask(context, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 64) {
|
|
||||||
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
|
|
||||||
val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
|
|
||||||
|
|
||||||
val newSortingMode = when (oldSortingMode) {
|
|
||||||
0 -> "ALPHABETICAL"
|
|
||||||
1 -> "LAST_READ"
|
|
||||||
2 -> "LAST_CHECKED"
|
|
||||||
3 -> "UNREAD"
|
|
||||||
4 -> "TOTAL_CHAPTERS"
|
|
||||||
6 -> "LATEST_CHAPTER"
|
|
||||||
8 -> "DATE_FETCHED"
|
|
||||||
7 -> "DATE_ADDED"
|
|
||||||
else -> "ALPHABETICAL"
|
|
||||||
}
|
|
||||||
|
|
||||||
val newSortingDirection = when (oldSortingDirection) {
|
|
||||||
true -> "ASCENDING"
|
|
||||||
else -> "DESCENDING"
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.edit(commit = true) {
|
|
||||||
remove(libraryPreferences.sortingMode().key())
|
|
||||||
remove("library_sorting_ascending")
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.edit {
|
|
||||||
putString(libraryPreferences.sortingMode().key(), newSortingMode)
|
|
||||||
putString("library_sorting_ascending", newSortingDirection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 70) {
|
|
||||||
if (sourcePreferences.enabledLanguages().isSet()) {
|
|
||||||
sourcePreferences.enabledLanguages() += "all"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 71) {
|
|
||||||
// Handle removed every 3, 4, 6, and 8 hour library updates
|
|
||||||
val updateInterval = libraryPreferences.autoUpdateInterval().get()
|
|
||||||
if (updateInterval in listOf(3, 4, 6, 8)) {
|
|
||||||
libraryPreferences.autoUpdateInterval().set(12)
|
|
||||||
LibraryUpdateJob.setupTask(context, 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 72) {
|
|
||||||
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
|
|
||||||
if (!oldUpdateOngoingOnly) {
|
|
||||||
libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 75) {
|
|
||||||
val oldSecureScreen = prefs.getBoolean("secure_screen", false)
|
|
||||||
if (oldSecureScreen) {
|
|
||||||
securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
DeviceUtil.isMiui &&
|
|
||||||
basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER
|
|
||||||
) {
|
|
||||||
basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 77) {
|
|
||||||
val oldReaderTap = prefs.getBoolean("reader_tap", false)
|
|
||||||
if (!oldReaderTap) {
|
|
||||||
readerPreferences.navigationModePager().set(5)
|
|
||||||
readerPreferences.navigationModeWebtoon().set(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 81) {
|
|
||||||
// Handle renamed enum values
|
|
||||||
prefs.edit {
|
|
||||||
val newSortingMode = when (
|
|
||||||
val oldSortingMode = prefs.getString(
|
|
||||||
libraryPreferences.sortingMode().key(),
|
|
||||||
"ALPHABETICAL",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
|
|
||||||
"UNREAD" -> "UNREAD_COUNT"
|
|
||||||
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
|
|
||||||
else -> oldSortingMode
|
|
||||||
}
|
|
||||||
putString(libraryPreferences.sortingMode().key(), newSortingMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 82) {
|
|
||||||
prefs.edit {
|
|
||||||
val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit
|
|
||||||
val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
|
|
||||||
putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
|
|
||||||
remove("library_sorting_ascending")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 84) {
|
|
||||||
if (backupPreferences.backupInterval().get() == 0) {
|
|
||||||
backupPreferences.backupInterval().set(12)
|
|
||||||
BackupCreateJob.setupTask(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 85) {
|
|
||||||
val preferences = listOf(
|
|
||||||
libraryPreferences.filterChapterByRead(),
|
|
||||||
libraryPreferences.filterChapterByDownloaded(),
|
|
||||||
libraryPreferences.filterChapterByBookmarked(),
|
|
||||||
libraryPreferences.sortChapterBySourceOrNumber(),
|
|
||||||
libraryPreferences.displayChapterByNameOrNumber(),
|
|
||||||
libraryPreferences.sortChapterByAscendingOrDescending(),
|
|
||||||
)
|
|
||||||
|
|
||||||
prefs.edit {
|
|
||||||
preferences.forEach { preference ->
|
|
||||||
val key = preference.key()
|
|
||||||
val value = prefs.getInt(key, Int.MIN_VALUE)
|
|
||||||
if (value == Int.MIN_VALUE) return@forEach
|
|
||||||
remove(key)
|
|
||||||
putLong(key, value.toLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 86) {
|
|
||||||
if (uiPreferences.themeMode().isSet()) {
|
|
||||||
prefs.edit {
|
|
||||||
val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit
|
|
||||||
putString(uiPreferences.themeMode().key(), themeMode.uppercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 92) {
|
|
||||||
val trackingQueuePref = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
|
||||||
trackingQueuePref.all.forEach {
|
|
||||||
val (_, lastChapterRead) = it.value.toString().split(":")
|
|
||||||
trackingQueuePref.edit {
|
|
||||||
remove(it.key)
|
|
||||||
putFloat(it.key, lastChapterRead.toFloat())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 96) {
|
|
||||||
LibraryUpdateJob.cancelAllWorks(context)
|
|
||||||
LibraryUpdateJob.setupTask(context)
|
|
||||||
}
|
|
||||||
if (oldVersion < 97) {
|
|
||||||
// Removed background jobs
|
|
||||||
context.workManager.cancelAllWorkByTag("UpdateChecker")
|
|
||||||
context.workManager.cancelAllWorkByTag("ExtensionUpdate")
|
|
||||||
prefs.edit {
|
|
||||||
remove("automatic_ext_updates")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 99) {
|
|
||||||
val prefKeys = listOf(
|
|
||||||
"pref_filter_library_downloaded",
|
|
||||||
"pref_filter_library_unread",
|
|
||||||
"pref_filter_library_started",
|
|
||||||
"pref_filter_library_bookmarked",
|
|
||||||
"pref_filter_library_completed",
|
|
||||||
) + trackerManager.trackers.map { "pref_filter_library_tracked_${it.id}" }
|
|
||||||
|
|
||||||
prefKeys.forEach { key ->
|
|
||||||
val pref = preferenceStore.getInt(key, 0)
|
|
||||||
prefs.edit {
|
|
||||||
remove(key)
|
|
||||||
|
|
||||||
val newValue = when (pref.get()) {
|
|
||||||
1 -> TriState.ENABLED_IS
|
|
||||||
2 -> TriState.ENABLED_NOT
|
|
||||||
else -> TriState.DISABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 105) {
|
|
||||||
val pref = libraryPreferences.autoUpdateDeviceRestrictions()
|
|
||||||
if (pref.isSet() && "battery_not_low" in pref.get()) {
|
|
||||||
pref.getAndSet { it - "battery_not_low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 106) {
|
|
||||||
val pref = preferenceStore.getInt("relative_time", 7)
|
|
||||||
if (pref.get() == 0) {
|
|
||||||
uiPreferences.relativeTime().set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 113) {
|
|
||||||
val prefsToReplace = listOf(
|
|
||||||
"pref_download_only",
|
|
||||||
"incognito_mode",
|
|
||||||
"last_catalogue_source",
|
|
||||||
"trusted_signatures",
|
|
||||||
"last_app_closed",
|
|
||||||
"library_update_last_timestamp",
|
|
||||||
"library_unseen_updates_count",
|
|
||||||
"last_used_category",
|
|
||||||
"last_app_check",
|
|
||||||
"last_ext_check",
|
|
||||||
"last_version_code",
|
|
||||||
"storage_dir",
|
|
||||||
)
|
|
||||||
replacePreferences(
|
|
||||||
preferenceStore = preferenceStore,
|
|
||||||
filterPredicate = { it.key in prefsToReplace },
|
|
||||||
newKey = { Preference.appStateKey(it) },
|
|
||||||
)
|
|
||||||
|
|
||||||
// Deleting old download cache index files, but might as well clear it all out
|
|
||||||
context.cacheDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
if (oldVersion < 114) {
|
|
||||||
sourcePreferences.extensionRepos().getAndSet {
|
|
||||||
it.map { repo -> "https://raw.githubusercontent.com/$repo/repo" }.toSet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 116) {
|
|
||||||
replacePreferences(
|
|
||||||
preferenceStore = preferenceStore,
|
|
||||||
filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
|
|
||||||
newKey = { Preference.privateKey(it) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (oldVersion < 117) {
|
|
||||||
prefs.edit {
|
|
||||||
remove(Preference.appStateKey("trusted_signatures"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun replacePreferences(
|
|
||||||
preferenceStore: PreferenceStore,
|
|
||||||
filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
|
|
||||||
newKey: (String) -> String,
|
|
||||||
) {
|
|
||||||
preferenceStore.getAll()
|
|
||||||
.filter(filterPredicate)
|
|
||||||
.forEach { (key, value) ->
|
|
||||||
when (value) {
|
|
||||||
is Int -> {
|
|
||||||
preferenceStore.getInt(newKey(key)).set(value)
|
|
||||||
preferenceStore.getInt(key).delete()
|
|
||||||
}
|
|
||||||
is Long -> {
|
|
||||||
preferenceStore.getLong(newKey(key)).set(value)
|
|
||||||
preferenceStore.getLong(key).delete()
|
|
||||||
}
|
|
||||||
is Float -> {
|
|
||||||
preferenceStore.getFloat(newKey(key)).set(value)
|
|
||||||
preferenceStore.getFloat(key).delete()
|
|
||||||
}
|
|
||||||
is String -> {
|
|
||||||
preferenceStore.getString(newKey(key)).set(value)
|
|
||||||
preferenceStore.getString(key).delete()
|
|
||||||
}
|
|
||||||
is Boolean -> {
|
|
||||||
preferenceStore.getBoolean(newKey(key)).set(value)
|
|
||||||
preferenceStore.getBoolean(key).delete()
|
|
||||||
}
|
|
||||||
is Set<*> -> (value as? Set<String>)?.let {
|
|
||||||
preferenceStore.getStringSet(newKey(key)).set(value)
|
|
||||||
preferenceStore.getStringSet(key).delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -28,7 +28,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS,
|
Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS,
|
||||||
) {
|
) {
|
||||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_mihon)
|
||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
@ -38,7 +38,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE,
|
Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE,
|
||||||
) {
|
) {
|
||||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_mihon)
|
||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,10 @@ class MangaBackupCreator(
|
|||||||
// Entry for this manga
|
// Entry for this manga
|
||||||
val mangaObject = manga.toBackupManga()
|
val mangaObject = manga.toBackupManga()
|
||||||
|
|
||||||
|
mangaObject.excludedScanlators = handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
|
||||||
|
}
|
||||||
|
|
||||||
if (options.chapters) {
|
if (options.chapters) {
|
||||||
// Backup all the chapters
|
// Backup all the chapters
|
||||||
handler.awaitList {
|
handler.awaitList {
|
||||||
|
@ -38,6 +38,7 @@ data class BackupManga(
|
|||||||
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||||
@ProtoNumber(106) var lastModifiedAt: Long = 0,
|
@ProtoNumber(106) var lastModifiedAt: Long = 0,
|
||||||
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
|
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
|
||||||
|
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
||||||
) {
|
) {
|
||||||
fun getMangaImpl(): Manga {
|
fun getMangaImpl(): Manga {
|
||||||
return Manga.create().copy(
|
return Manga.create().copy(
|
||||||
|
@ -73,6 +73,7 @@ class MangaRestorer(
|
|||||||
backupCategories = backupCategories,
|
backupCategories = backupCategories,
|
||||||
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
||||||
tracks = backupManga.tracking,
|
tracks = backupManga.tracking,
|
||||||
|
excludedScanlators = backupManga.excludedScanlators,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,11 +265,13 @@ class MangaRestorer(
|
|||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<BackupTracking>,
|
tracks: List<BackupTracking>,
|
||||||
|
excludedScanlators: List<String>,
|
||||||
): Manga {
|
): Manga {
|
||||||
restoreCategories(manga, categories, backupCategories)
|
restoreCategories(manga, categories, backupCategories)
|
||||||
restoreChapters(manga, chapters)
|
restoreChapters(manga, chapters)
|
||||||
restoreTracking(manga, tracks)
|
restoreTracking(manga, tracks)
|
||||||
restoreHistory(history)
|
restoreHistory(history)
|
||||||
|
restoreExcludedScanlators(manga, excludedScanlators)
|
||||||
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
|
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
@ -401,4 +404,25 @@ class MangaRestorer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Track.forComparison() = this.copy(id = 0L, mangaId = 0L)
|
private fun Track.forComparison() = this.copy(id = 0L, mangaId = 0L)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the excluded scanlators for the manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga whose excluded scanlators have to be restored.
|
||||||
|
* @param excludedScanlators the excluded scanlators to restore.
|
||||||
|
*/
|
||||||
|
private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List<String>) {
|
||||||
|
if (excludedScanlators.isEmpty()) return
|
||||||
|
val existingExcludedScanlators = handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
|
||||||
|
}
|
||||||
|
val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators }
|
||||||
|
if (toInsert.isNotEmpty()) {
|
||||||
|
handler.await {
|
||||||
|
toInsert.forEach {
|
||||||
|
excluded_scanlatorsQueries.insert(manga.id, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.coil
|
package eu.kanade.tachiyomi.data.coil
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.decode.DecodeResult
|
import coil.decode.DecodeResult
|
||||||
@ -48,8 +47,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
|||||||
ImageUtil.findImageType(it)
|
ImageUtil.findImageType(it)
|
||||||
}
|
}
|
||||||
return when (type) {
|
return when (type) {
|
||||||
ImageUtil.ImageType.AVIF, ImageUtil.ImageType.JXL -> true
|
ImageUtil.ImageType.AVIF, ImageUtil.ImageType.JXL, ImageUtil.ImageType.HEIF -> true
|
||||||
ImageUtil.ImageType.HEIF -> Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var manga_id: Long
|
var manga_id: Long
|
||||||
|
|
||||||
var tracker_id: Int
|
var tracker_id: Long
|
||||||
|
|
||||||
var remote_id: Long
|
var remote_id: Long
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var title: String
|
var title: String
|
||||||
|
|
||||||
var last_chapter_read: Float
|
var last_chapter_read: Double
|
||||||
|
|
||||||
var total_chapters: Int
|
var total_chapters: Long
|
||||||
|
|
||||||
var score: Float
|
var score: Double
|
||||||
|
|
||||||
var status: Int
|
var status: Long
|
||||||
|
|
||||||
var started_reading_date: Long
|
var started_reading_date: Long
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(serviceId: Long): Track = TrackImpl().apply {
|
fun create(serviceId: Long): Track = TrackImpl().apply {
|
||||||
tracker_id = serviceId.toInt()
|
tracker_id = serviceId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
override var manga_id: Long = 0
|
override var manga_id: Long = 0
|
||||||
|
|
||||||
override var tracker_id: Int = 0
|
override var tracker_id: Long = 0
|
||||||
|
|
||||||
override var remote_id: Long = 0
|
override var remote_id: Long = 0
|
||||||
|
|
||||||
@ -14,13 +14,13 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
override lateinit var title: String
|
override lateinit var title: String
|
||||||
|
|
||||||
override var last_chapter_read: Float = 0F
|
override var last_chapter_read: Double = 0.0
|
||||||
|
|
||||||
override var total_chapters: Int = 0
|
override var total_chapters: Long = 0
|
||||||
|
|
||||||
override var score: Float = 0f
|
override var score: Double = 0.0
|
||||||
|
|
||||||
override var status: Int = 0
|
override var status: Long = 0
|
||||||
|
|
||||||
override var started_reading_date: Long = 0
|
override var started_reading_date: Long = 0
|
||||||
|
|
||||||
|
@ -315,13 +315,13 @@ class DownloadManager(
|
|||||||
val capitalizationChanged = oldFolder.name.equals(newName, ignoreCase = true)
|
val capitalizationChanged = oldFolder.name.equals(newName, ignoreCase = true)
|
||||||
if (capitalizationChanged) {
|
if (capitalizationChanged) {
|
||||||
val tempName = newName + Downloader.TMP_DIR_SUFFIX
|
val tempName = newName + Downloader.TMP_DIR_SUFFIX
|
||||||
if (oldFolder.renameTo(tempName).not()) {
|
if (!oldFolder.renameTo(tempName)) {
|
||||||
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldFolder.renameTo(newName).not()) {
|
if (!oldFolder.renameTo(newName)) {
|
||||||
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ class DownloadProvider(
|
|||||||
val newChapterName = sanitizeChapterName(chapterName)
|
val newChapterName = sanitizeChapterName(chapterName)
|
||||||
return DiskUtil.buildValidFilename(
|
return DiskUtil.buildValidFilename(
|
||||||
when {
|
when {
|
||||||
chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$newChapterName"
|
!chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$newChapterName"
|
||||||
else -> newChapterName
|
else -> newChapterName
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -413,7 +413,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private const val WORK_NAME_AUTO = "LibraryUpdate-auto"
|
private const val WORK_NAME_AUTO = "LibraryUpdate-auto"
|
||||||
private const val WORK_NAME_MANUAL = "LibraryUpdate-manual"
|
private const val WORK_NAME_MANUAL = "LibraryUpdate-manual"
|
||||||
|
|
||||||
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/docs/guides/troubleshooting/"
|
private const val ERROR_LOG_HELP_URL = "https://mihon.app/docs/guides/troubleshooting/"
|
||||||
|
|
||||||
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
|
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class LibraryUpdateNotifier(
|
|||||||
) {
|
) {
|
||||||
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
|
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
|
||||||
setContentText(context.stringResource(MR.strings.action_show_errors))
|
setContentText(context.stringResource(MR.strings.action_show_errors))
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_mihon)
|
||||||
|
|
||||||
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
|
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ class LibraryUpdateNotifier(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_mihon)
|
||||||
setLargeIcon(notificationBitmap)
|
setLargeIcon(notificationBitmap)
|
||||||
|
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
@ -229,7 +229,7 @@ class LibraryUpdateNotifier(
|
|||||||
setContentText(description)
|
setContentText(description)
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(description))
|
setStyle(NotificationCompat.BigTextStyle().bigText(description))
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_mihon)
|
||||||
|
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
setLargeIcon(icon)
|
setLargeIcon(icon)
|
||||||
@ -377,7 +377,7 @@ class LibraryUpdateNotifier(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val HELP_WARNING_URL =
|
const val HELP_WARNING_URL =
|
||||||
"https://tachiyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads"
|
"https://mihon.app/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
@ -354,20 +353,18 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
When programmatically dismissing this notification, the group notification is not automatically dismissed.
|
When programmatically dismissing this notification, the group notification is not automatically dismissed.
|
||||||
*/
|
*/
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
val groupKey = context.notificationManager.activeNotifications.find {
|
||||||
val groupKey = context.notificationManager.activeNotifications.find {
|
it.id == notificationId
|
||||||
it.id == notificationId
|
}?.groupKey
|
||||||
}?.groupKey
|
|
||||||
|
|
||||||
if (groupId != null && groupId != 0 && !groupKey.isNullOrEmpty()) {
|
if (groupId != null && groupId != 0 && !groupKey.isNullOrEmpty()) {
|
||||||
val notifications = context.notificationManager.activeNotifications.filter {
|
val notifications = context.notificationManager.activeNotifications.filter {
|
||||||
it.groupKey == groupKey
|
it.groupKey == groupKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifications.size == 2) {
|
if (notifications.size == 2) {
|
||||||
context.cancelNotification(groupId)
|
context.cancelNotification(groupId)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,8 @@ abstract class BaseTracker(
|
|||||||
return track.score
|
return track.score
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun indexToScore(index: Int): Float {
|
override fun indexToScore(index: Int): Double {
|
||||||
return index.toFloat()
|
return index.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@ -70,24 +70,24 @@ abstract class BaseTracker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setRemoteStatus(track: Track, status: Int) {
|
override suspend fun setRemoteStatus(track: Track, status: Long) {
|
||||||
track.status = status
|
track.status = status
|
||||||
if (track.status == getCompletionStatus() && track.total_chapters != 0) {
|
if (track.status == getCompletionStatus() && track.total_chapters != 0L) {
|
||||||
track.last_chapter_read = track.total_chapters.toFloat()
|
track.last_chapter_read = track.total_chapters.toDouble()
|
||||||
}
|
}
|
||||||
updateRemote(track)
|
updateRemote(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
override suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
||||||
if (
|
if (
|
||||||
track.last_chapter_read == 0f &&
|
track.last_chapter_read == 0.0 &&
|
||||||
track.last_chapter_read < chapterNumber &&
|
track.last_chapter_read < chapterNumber &&
|
||||||
track.status != getRereadingStatus()
|
track.status != getRereadingStatus()
|
||||||
) {
|
) {
|
||||||
track.status = getReadingStatus()
|
track.status = getReadingStatus()
|
||||||
}
|
}
|
||||||
track.last_chapter_read = chapterNumber.toFloat()
|
track.last_chapter_read = chapterNumber.toDouble()
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
|
if (track.total_chapters != 0L && track.last_chapter_read.toLong() == track.total_chapters) {
|
||||||
track.status = getCompletionStatus()
|
track.status = getCompletionStatus()
|
||||||
track.finished_reading_date = System.currentTimeMillis()
|
track.finished_reading_date = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
@ -27,22 +27,22 @@ interface Tracker {
|
|||||||
@DrawableRes
|
@DrawableRes
|
||||||
fun getLogo(): Int
|
fun getLogo(): Int
|
||||||
|
|
||||||
fun getStatusList(): List<Int>
|
fun getStatusList(): List<Long>
|
||||||
|
|
||||||
fun getStatus(status: Int): StringResource?
|
fun getStatus(status: Long): StringResource?
|
||||||
|
|
||||||
fun getReadingStatus(): Int
|
fun getReadingStatus(): Long
|
||||||
|
|
||||||
fun getRereadingStatus(): Int
|
fun getRereadingStatus(): Long
|
||||||
|
|
||||||
fun getCompletionStatus(): Int
|
fun getCompletionStatus(): Long
|
||||||
|
|
||||||
fun getScoreList(): ImmutableList<String>
|
fun getScoreList(): ImmutableList<String>
|
||||||
|
|
||||||
// TODO: Store all scores as 10 point in the future maybe?
|
// TODO: Store all scores as 10 point in the future maybe?
|
||||||
fun get10PointScore(track: DomainTrack): Double
|
fun get10PointScore(track: DomainTrack): Double
|
||||||
|
|
||||||
fun indexToScore(index: Int): Float
|
fun indexToScore(index: Int): Double
|
||||||
|
|
||||||
fun displayScore(track: DomainTrack): String
|
fun displayScore(track: DomainTrack): String
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ interface Tracker {
|
|||||||
// TODO: move this to an interactor, and update all trackers based on common data
|
// TODO: move this to an interactor, and update all trackers based on common data
|
||||||
suspend fun register(item: Track, mangaId: Long)
|
suspend fun register(item: Track, mangaId: Long)
|
||||||
|
|
||||||
suspend fun setRemoteStatus(track: Track, status: Int)
|
suspend fun setRemoteStatus(track: Track, status: Long)
|
||||||
|
|
||||||
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int)
|
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int)
|
||||||
|
|
||||||
|
@ -20,12 +20,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1L
|
||||||
const val COMPLETED = 2
|
const val COMPLETED = 2L
|
||||||
const val ON_HOLD = 3
|
const val ON_HOLD = 3L
|
||||||
const val DROPPED = 4
|
const val DROPPED = 4L
|
||||||
const val PLAN_TO_READ = 5
|
const val PLAN_TO_READ = 5L
|
||||||
const val REREADING = 6
|
const val REREADING = 6L
|
||||||
|
|
||||||
const val POINT_100 = "POINT_100"
|
const val POINT_100 = "POINT_100"
|
||||||
const val POINT_10 = "POINT_10"
|
const val POINT_10 = "POINT_10"
|
||||||
@ -58,11 +58,11 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
@ -72,11 +72,11 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = REREADING
|
override fun getRereadingStatus(): Long = REREADING
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> {
|
override fun getScoreList(): ImmutableList<String> {
|
||||||
return when (scorePreference.get()) {
|
return when (scorePreference.get()) {
|
||||||
@ -99,24 +99,24 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
return track.score / 10.0
|
return track.score / 10.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun indexToScore(index: Int): Float {
|
override fun indexToScore(index: Int): Double {
|
||||||
return when (scorePreference.get()) {
|
return when (scorePreference.get()) {
|
||||||
// 10 point
|
// 10 point
|
||||||
POINT_10 -> index * 10f
|
POINT_10 -> index * 10.0
|
||||||
// 100 point
|
// 100 point
|
||||||
POINT_100 -> index.toFloat()
|
POINT_100 -> index.toDouble()
|
||||||
// 5 stars
|
// 5 stars
|
||||||
POINT_5 -> when (index) {
|
POINT_5 -> when (index) {
|
||||||
0 -> 0f
|
0 -> 0.0
|
||||||
else -> index * 20f - 10f
|
else -> index * 20.0 - 10.0
|
||||||
}
|
}
|
||||||
// Smiley
|
// Smiley
|
||||||
POINT_3 -> when (index) {
|
POINT_3 -> when (index) {
|
||||||
0 -> 0f
|
0 -> 0.0
|
||||||
else -> index * 25f + 10f
|
else -> index * 25.0 + 10.0
|
||||||
}
|
}
|
||||||
// 10 point decimal
|
// 10 point decimal
|
||||||
POINT_10_DECIMAL -> index.toFloat()
|
POINT_10_DECIMAL -> index.toDouble()
|
||||||
else -> throw Exception("Unknown score type")
|
else -> throw Exception("Unknown score type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,12 +153,12 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
|
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
track.finished_reading_date = System.currentTimeMillis()
|
track.finished_reading_date = System.currentTimeMillis()
|
||||||
} else if (track.status != REREADING) {
|
} else if (track.status != REREADING) {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
if (track.last_chapter_read == 1F) {
|
if (track.last_chapter_read == 1.0) {
|
||||||
track.started_reading_date = System.currentTimeMillis()
|
track.started_reading_date = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,14 +185,14 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
|
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
val isRereading = track.status == REREADING
|
val isRereading = track.status == REREADING
|
||||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||||
}
|
}
|
||||||
|
|
||||||
update(track)
|
update(track)
|
||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0F
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import kotlinx.serialization.json.jsonArray
|
|||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.long
|
import kotlinx.serialization.json.long
|
||||||
|
import kotlinx.serialization.json.longOrNull
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -312,7 +313,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
struct["format"]!!.jsonPrimitive.content.replace("_", "-"),
|
struct["format"]!!.jsonPrimitive.content.replace("_", "-"),
|
||||||
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
|
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
|
||||||
parseDate(struct, "startDate"),
|
parseDate(struct, "startDate"),
|
||||||
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0,
|
struct["chapters"]!!.jsonPrimitive.longOrNull ?: 0,
|
||||||
struct["averageScore"]?.jsonPrimitive?.intOrNull ?: -1,
|
struct["averageScore"]?.jsonPrimitive?.intOrNull ?: -1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -363,7 +364,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId = "385"
|
private const val clientId = "16329"
|
||||||
private const val apiUrl = "https://graphql.anilist.co/"
|
private const val apiUrl = "https://graphql.anilist.co/"
|
||||||
private const val baseUrl = "https://anilist.co/api/v2/"
|
private const val baseUrl = "https://anilist.co/api/v2/"
|
||||||
private const val baseMangaUrl = "https://anilist.co/manga/"
|
private const val baseMangaUrl = "https://anilist.co/manga/"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.anilist
|
package eu.kanade.tachiyomi.data.track.anilist
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -40,6 +41,7 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
|
|||||||
// Add the authorization header to the original request.
|
// Add the authorization header to the original request.
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||||
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(authRequest)
|
return chain.proceed(authRequest)
|
||||||
|
@ -19,7 +19,7 @@ data class ALManga(
|
|||||||
val format: String,
|
val format: String,
|
||||||
val publishing_status: String,
|
val publishing_status: String,
|
||||||
val start_date_fuzzy: Long,
|
val start_date_fuzzy: Long,
|
||||||
val total_chapters: Int,
|
val total_chapters: Long,
|
||||||
val average_score: Int,
|
val average_score: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ data class ALManga(
|
|||||||
total_chapters = this@ALManga.total_chapters
|
total_chapters = this@ALManga.total_chapters
|
||||||
cover_url = image_url_lge
|
cover_url = image_url_lge
|
||||||
summary = description?.htmlDecode() ?: ""
|
summary = description?.htmlDecode() ?: ""
|
||||||
score = average_score.toFloat()
|
score = average_score.toDouble()
|
||||||
tracking_url = AnilistApi.mangaUrl(remote_id)
|
tracking_url = AnilistApi.mangaUrl(remote_id)
|
||||||
publishing_status = this@ALManga.publishing_status
|
publishing_status = this@ALManga.publishing_status
|
||||||
publishing_type = format
|
publishing_type = format
|
||||||
@ -58,10 +58,10 @@ data class ALUserManga(
|
|||||||
remote_id = manga.remote_id
|
remote_id = manga.remote_id
|
||||||
title = manga.title_user_pref
|
title = manga.title_user_pref
|
||||||
status = toTrackStatus()
|
status = toTrackStatus()
|
||||||
score = score_raw.toFloat()
|
score = score_raw.toDouble()
|
||||||
started_reading_date = start_date_fuzzy
|
started_reading_date = start_date_fuzzy
|
||||||
finished_reading_date = completed_date_fuzzy
|
finished_reading_date = completed_date_fuzzy
|
||||||
last_chapter_read = chapters_read.toFloat()
|
last_chapter_read = chapters_read.toDouble()
|
||||||
library_id = this@ALUserManga.library_id
|
library_id = this@ALUserManga.library_id
|
||||||
total_chapters = manga.total_chapters
|
total_chapters = manga.total_chapters
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
} else {
|
} else {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
@ -64,7 +64,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0F
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
update(track)
|
update(track)
|
||||||
}
|
}
|
||||||
@ -87,11 +87,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(240, 145, 153)
|
override fun getLogoColor() = Color.rgb(240, 145, 153)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
@ -100,11 +100,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Long = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override suspend fun login(username: String, password: String) = login(password)
|
override suspend fun login(username: String, password: String) = login(password)
|
||||||
|
|
||||||
@ -137,11 +137,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 3
|
const val READING = 3L
|
||||||
const val COMPLETED = 2
|
const val COMPLETED = 2L
|
||||||
const val ON_HOLD = 4
|
const val ON_HOLD = 4L
|
||||||
const val DROPPED = 5
|
const val DROPPED = 5L
|
||||||
const val PLAN_TO_READ = 1
|
const val PLAN_TO_READ = 1L
|
||||||
|
|
||||||
private val SCORE_LIST = IntRange(0, 10)
|
private val SCORE_LIST = IntRange(0, 10)
|
||||||
.map(Int::toString)
|
.map(Int::toString)
|
||||||
|
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.network.parseAs
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.floatOrNull
|
import kotlinx.serialization.json.doubleOrNull
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
@ -105,11 +105,11 @@ class BangumiApi(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
val totalChapters = if (obj["eps_count"] != null) {
|
val totalChapters = if (obj["eps_count"] != null) {
|
||||||
obj["eps_count"]!!.jsonPrimitive.int
|
obj["eps_count"]!!.jsonPrimitive.long
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
val rating = obj["rating"]?.jsonObject?.get("score")?.jsonPrimitive?.floatOrNull ?: -1f
|
val rating = obj["rating"]?.jsonObject?.get("score")?.jsonPrimitive?.doubleOrNull ?: -1.0
|
||||||
return TrackSearch.create(trackId).apply {
|
return TrackSearch.create(trackId).apply {
|
||||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["name_cn"]!!.jsonPrimitive.content
|
title = obj["name_cn"]!!.jsonPrimitive.content
|
||||||
@ -152,7 +152,7 @@ class BangumiApi(
|
|||||||
} else {
|
} else {
|
||||||
json.decodeFromString<Collection>(responseBody).let {
|
json.decodeFromString<Collection>(responseBody).let {
|
||||||
track.status = it.status?.id!!
|
track.status = it.status?.id!!
|
||||||
track.last_chapter_read = it.ep_status!!.toFloat()
|
track.last_chapter_read = it.ep_status!!.toDouble()
|
||||||
track.score = it.rating!!
|
track.score = it.rating!!
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
@ -182,14 +182,14 @@ class BangumiApi(
|
|||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId = "bgm10555cda0762e80ca"
|
private const val clientId = "bgm291665acbd06a4c28"
|
||||||
private const val clientSecret = "8fff394a8627b4c388cbf349ec865775"
|
private const val clientSecret = "43e5ce36b207de16e5d3cfd3e79118db"
|
||||||
|
|
||||||
private const val apiUrl = "https://api.bgm.tv"
|
private const val apiUrl = "https://api.bgm.tv"
|
||||||
private const val oauthUrl = "https://bgm.tv/oauth/access_token"
|
private const val oauthUrl = "https://bgm.tv/oauth/access_token"
|
||||||
private const val loginUrl = "https://bgm.tv/oauth/authorize"
|
private const val loginUrl = "https://bgm.tv/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://bangumi-auth"
|
private const val redirectUrl = "mihon://bangumi-auth"
|
||||||
|
|
||||||
fun authUrl(): Uri =
|
fun authUrl(): Uri =
|
||||||
loginUrl.toUri().buildUpon()
|
loginUrl.toUri().buildUpon()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.bangumi
|
package eu.kanade.tachiyomi.data.track.bangumi
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
@ -29,22 +30,23 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val authRequest = if (originalRequest.method == "GET") {
|
return originalRequest.newBuilder()
|
||||||
originalRequest.newBuilder()
|
.header(
|
||||||
.header("User-Agent", "Tachiyomi")
|
"User-Agent",
|
||||||
.url(
|
"antsylich/Mihon/v${BuildConfig.VERSION_NAME} (Android) (http://github.com/mihonapp/mihon)",
|
||||||
originalRequest.url.newBuilder()
|
)
|
||||||
.addQueryParameter("access_token", currAuth.access_token).build(),
|
.apply {
|
||||||
)
|
if (originalRequest.method == "GET") {
|
||||||
.build()
|
val newUrl = originalRequest.url.newBuilder()
|
||||||
} else {
|
.addQueryParameter("access_token", currAuth.access_token)
|
||||||
originalRequest.newBuilder()
|
.build()
|
||||||
.post(addToken(currAuth.access_token, originalRequest.body as FormBody))
|
url(newUrl)
|
||||||
.header("User-Agent", "Tachiyomi")
|
} else {
|
||||||
.build()
|
post(addToken(currAuth.access_token, originalRequest.body as FormBody))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return chain.proceed(authRequest)
|
.build()
|
||||||
|
.let(chain::proceed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newAuth(oauth: OAuth?) {
|
fun newAuth(oauth: OAuth?) {
|
||||||
|
@ -16,7 +16,7 @@ data class Collection(
|
|||||||
val comment: String? = "",
|
val comment: String? = "",
|
||||||
val ep_status: Int? = 0,
|
val ep_status: Int? = 0,
|
||||||
val lasttouch: Int? = 0,
|
val lasttouch: Int? = 0,
|
||||||
val rating: Float? = 0f,
|
val rating: Double? = 0.0,
|
||||||
val status: Status? = Status(),
|
val status: Status? = Status(),
|
||||||
val tag: List<String?>? = emptyList(),
|
val tag: List<String?>? = emptyList(),
|
||||||
val user: User? = User(),
|
val user: User? = User(),
|
||||||
@ -25,7 +25,7 @@ data class Collection(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Status(
|
data class Status(
|
||||||
val id: Int? = 0,
|
val id: Long? = 0,
|
||||||
val name: String? = "",
|
val name: String? = "",
|
||||||
val type: String? = "",
|
val type: String? = "",
|
||||||
)
|
)
|
||||||
|
@ -22,9 +22,9 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNREAD = 1
|
const val UNREAD = 1L
|
||||||
const val READING = 2
|
const val READING = 2L
|
||||||
const val COMPLETED = 3
|
const val COMPLETED = 3L
|
||||||
}
|
}
|
||||||
|
|
||||||
var authentications: OAuth? = null
|
var authentications: OAuth? = null
|
||||||
@ -38,20 +38,20 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(74, 198, 148)
|
override fun getLogoColor() = Color.rgb(74, 198, 148)
|
||||||
|
|
||||||
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
|
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
UNREAD -> MR.strings.unread
|
UNREAD -> MR.strings.unread
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Long = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
} else {
|
} else {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
|
@ -93,7 +93,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
* Ignores volumes.
|
* Ignores volumes.
|
||||||
* Volumes consisting of 1 file treated as chapter
|
* Volumes consisting of 1 file treated as chapter
|
||||||
*/
|
*/
|
||||||
private fun getTotalChapters(url: String): Int {
|
private fun getTotalChapters(url: String): Long {
|
||||||
val requestUrl = getApiVolumesUrl(url)
|
val requestUrl = getApiVolumesUrl(url)
|
||||||
try {
|
try {
|
||||||
val listVolumeDto = with(json) {
|
val listVolumeDto = with(json) {
|
||||||
@ -101,13 +101,13 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
.execute()
|
.execute()
|
||||||
.parseAs<List<VolumeDto>>()
|
.parseAs<List<VolumeDto>>()
|
||||||
}
|
}
|
||||||
var volumeNumber = 0
|
var volumeNumber = 0L
|
||||||
var maxChapterNumber = 0
|
var maxChapterNumber = 0L
|
||||||
for (volume in listVolumeDto) {
|
for (volume in listVolumeDto) {
|
||||||
if (volume.chapters.maxOf { it.number!!.toFloat() } == 0f) {
|
if (volume.chapters.maxOf { it.number!!.toFloat() } == 0f) {
|
||||||
volumeNumber++
|
volumeNumber++
|
||||||
} else if (maxChapterNumber < volume.chapters.maxOf { it.number!!.toFloat() }) {
|
} else if (maxChapterNumber < volume.chapters.maxOf { it.number!!.toFloat() }) {
|
||||||
maxChapterNumber = volume.chapters.maxOf { it.number!!.toFloat().toInt() }
|
maxChapterNumber = volume.chapters.maxOf { it.number!!.toFloat().toLong() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,17 +118,17 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLatestChapterRead(url: String): Float {
|
private fun getLatestChapterRead(url: String): Double {
|
||||||
val seriesId = getIdFromUrl(url)
|
val seriesId = getIdFromUrl(url)
|
||||||
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$seriesId"
|
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$seriesId"
|
||||||
try {
|
try {
|
||||||
with(json) {
|
with(json) {
|
||||||
authClient.newCall(GET(requestUrl)).execute().use {
|
authClient.newCall(GET(requestUrl)).execute().use {
|
||||||
if (it.code == 200) {
|
if (it.code == 200) {
|
||||||
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toDouble()
|
||||||
}
|
}
|
||||||
if (it.code == 204) {
|
if (it.code == 204) {
|
||||||
return 0F
|
return 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
|
) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
return 0F
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
||||||
|
@ -18,7 +18,7 @@ class KavitaInterceptor(private val kavita: Kavita) : Interceptor {
|
|||||||
// Add the authorization header to the original request.
|
// Add the authorization header to the original request.
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer $jwtToken")
|
.addHeader("Authorization", "Bearer $jwtToken")
|
||||||
.header("User-Agent", "Tachiyomi Kavita v${BuildConfig.VERSION_NAME}")
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(authRequest)
|
return chain.proceed(authRequest)
|
||||||
|
@ -19,11 +19,11 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1L
|
||||||
const val COMPLETED = 2
|
const val COMPLETED = 2L
|
||||||
const val ON_HOLD = 3
|
const val ON_HOLD = 3L
|
||||||
const val DROPPED = 4
|
const val DROPPED = 4L
|
||||||
const val PLAN_TO_READ = 5
|
const val PLAN_TO_READ = 5L
|
||||||
}
|
}
|
||||||
|
|
||||||
override val supportsReadingDates: Boolean = true
|
override val supportsReadingDates: Boolean = true
|
||||||
@ -38,11 +38,11 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
@ -51,19 +51,19 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Long = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> {
|
override fun getScoreList(): ImmutableList<String> {
|
||||||
val df = DecimalFormat("0.#")
|
val df = DecimalFormat("0.#")
|
||||||
return (listOf("0") + IntRange(2, 20).map { df.format(it / 2f) }).toImmutableList()
|
return (listOf("0") + IntRange(2, 20).map { df.format(it / 2f) }).toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun indexToScore(index: Int): Float {
|
override fun indexToScore(index: Int): Double {
|
||||||
return if (index > 0) (index + 1) / 2f else 0f
|
return if (index > 0) (index + 1) / 2.0 else 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun displayScore(track: DomainTrack): String {
|
override fun displayScore(track: DomainTrack): String {
|
||||||
@ -78,12 +78,12 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
track.finished_reading_date = System.currentTimeMillis()
|
track.finished_reading_date = System.currentTimeMillis()
|
||||||
} else {
|
} else {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
if (track.last_chapter_read == 1F) {
|
if (track.last_chapter_read == 1.0) {
|
||||||
track.started_reading_date = System.currentTimeMillis()
|
track.started_reading_date = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
update(track)
|
update(track)
|
||||||
} else {
|
} else {
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0F
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.kitsu
|
package eu.kanade.tachiyomi.data.track.kitsu
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -34,6 +35,7 @@ class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor {
|
|||||||
// Add the authorization header to the original request.
|
// Add the authorization header to the original request.
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||||
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.header("Accept", "application/vnd.api+json")
|
.header("Accept", "application/vnd.api+json")
|
||||||
.header("Content-Type", "application/vnd.api+json")
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
.build()
|
.build()
|
||||||
|
@ -8,10 +8,10 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.intOrNull
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.long
|
import kotlinx.serialization.json.long
|
||||||
|
import kotlinx.serialization.json.longOrNull
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -19,7 +19,7 @@ import java.util.Locale
|
|||||||
class KitsuSearchManga(obj: JsonObject) {
|
class KitsuSearchManga(obj: JsonObject) {
|
||||||
val id = obj["id"]!!.jsonPrimitive.long
|
val id = obj["id"]!!.jsonPrimitive.long
|
||||||
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
|
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
|
||||||
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull
|
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.longOrNull
|
||||||
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
|
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
|
||||||
val original = try {
|
val original = try {
|
||||||
obj["posterImage"]?.jsonObject?.get("original")?.jsonPrimitive?.content
|
obj["posterImage"]?.jsonObject?.get("original")?.jsonPrimitive?.content
|
||||||
@ -28,7 +28,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
private val synopsis = obj["synopsis"]?.jsonPrimitive?.contentOrNull
|
private val synopsis = obj["synopsis"]?.jsonPrimitive?.contentOrNull
|
||||||
private val rating = obj["averageRating"]?.jsonPrimitive?.contentOrNull?.toFloatOrNull()
|
private val rating = obj["averageRating"]?.jsonPrimitive?.contentOrNull?.toDoubleOrNull()
|
||||||
private var startDate = obj["startDate"]?.jsonPrimitive?.contentOrNull?.let {
|
private var startDate = obj["startDate"]?.jsonPrimitive?.contentOrNull?.let {
|
||||||
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||||
outputDf.format(Date(it.toLong() * 1000))
|
outputDf.format(Date(it.toLong() * 1000))
|
||||||
@ -43,7 +43,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
|||||||
cover_url = original ?: ""
|
cover_url = original ?: ""
|
||||||
summary = synopsis ?: ""
|
summary = synopsis ?: ""
|
||||||
tracking_url = KitsuApi.mangaUrl(remote_id)
|
tracking_url = KitsuApi.mangaUrl(remote_id)
|
||||||
score = rating ?: -1f
|
score = rating ?: -1.0
|
||||||
publishing_status = if (endDate == null) {
|
publishing_status = if (endDate == null) {
|
||||||
"Publishing"
|
"Publishing"
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +57,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
|||||||
class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
||||||
val id = manga["id"]!!.jsonPrimitive.int
|
val id = manga["id"]!!.jsonPrimitive.int
|
||||||
private val canonicalTitle = manga["attributes"]!!.jsonObject["canonicalTitle"]!!.jsonPrimitive.content
|
private val canonicalTitle = manga["attributes"]!!.jsonObject["canonicalTitle"]!!.jsonPrimitive.content
|
||||||
private val chapterCount = manga["attributes"]!!.jsonObject["chapterCount"]?.jsonPrimitive?.intOrNull
|
private val chapterCount = manga["attributes"]!!.jsonObject["chapterCount"]?.jsonPrimitive?.longOrNull
|
||||||
val type = manga["attributes"]!!.jsonObject["mangaType"]?.jsonPrimitive?.contentOrNull.orEmpty()
|
val type = manga["attributes"]!!.jsonObject["mangaType"]?.jsonPrimitive?.contentOrNull.orEmpty()
|
||||||
val original = manga["attributes"]!!.jsonObject["posterImage"]!!.jsonObject["original"]!!.jsonPrimitive.content
|
val original = manga["attributes"]!!.jsonObject["posterImage"]!!.jsonObject["original"]!!.jsonPrimitive.content
|
||||||
private val synopsis = manga["attributes"]!!.jsonObject["synopsis"]!!.jsonPrimitive.content
|
private val synopsis = manga["attributes"]!!.jsonObject["synopsis"]!!.jsonPrimitive.content
|
||||||
@ -82,8 +82,8 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
|||||||
started_reading_date = KitsuDateHelper.parse(startedAt)
|
started_reading_date = KitsuDateHelper.parse(startedAt)
|
||||||
finished_reading_date = KitsuDateHelper.parse(finishedAt)
|
finished_reading_date = KitsuDateHelper.parse(finishedAt)
|
||||||
status = toTrackStatus()
|
status = toTrackStatus()
|
||||||
score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f
|
score = ratingTwenty?.let { it.toInt() / 2.0 } ?: 0.0
|
||||||
last_chapter_read = progress.toFloat()
|
last_chapter_read = progress.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toTrackStatus() = when (status) {
|
private fun toTrackStatus() = when (status) {
|
||||||
|
@ -19,9 +19,9 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNREAD = 1
|
const val UNREAD = 1L
|
||||||
const val READING = 2
|
const val READING = 2L
|
||||||
const val COMPLETED = 3
|
const val COMPLETED = 3L
|
||||||
}
|
}
|
||||||
|
|
||||||
override val client: OkHttpClient =
|
override val client: OkHttpClient =
|
||||||
@ -35,20 +35,20 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
||||||
|
|
||||||
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
|
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
UNREAD -> MR.strings.unread
|
UNREAD -> MR.strings.unread
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Long = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
} else {
|
} else {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.komga
|
package eu.kanade.tachiyomi.data.track.komga
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -8,6 +9,7 @@ import eu.kanade.tachiyomi.network.parseAs
|
|||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import okhttp3.Headers
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -23,6 +25,12 @@ class KomgaApi(
|
|||||||
private val client: OkHttpClient,
|
private val client: OkHttpClient,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val headers: Headers by lazy {
|
||||||
|
Headers.Builder()
|
||||||
|
.add("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
suspend fun getTrackSearch(url: String): TrackSearch =
|
suspend fun getTrackSearch(url: String): TrackSearch =
|
||||||
@ -30,12 +38,12 @@ class KomgaApi(
|
|||||||
try {
|
try {
|
||||||
val track = with(json) {
|
val track = with(json) {
|
||||||
if (url.contains(READLIST_API)) {
|
if (url.contains(READLIST_API)) {
|
||||||
client.newCall(GET(url))
|
client.newCall(GET(url, headers))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<ReadListDto>()
|
.parseAs<ReadListDto>()
|
||||||
.toTrack()
|
.toTrack()
|
||||||
} else {
|
} else {
|
||||||
client.newCall(GET(url))
|
client.newCall(GET(url, headers))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<SeriesDto>()
|
.parseAs<SeriesDto>()
|
||||||
.toTrack()
|
.toTrack()
|
||||||
@ -43,7 +51,9 @@ class KomgaApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val progress = client
|
val progress = client
|
||||||
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
|
.newCall(
|
||||||
|
GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi", headers),
|
||||||
|
)
|
||||||
.awaitSuccess().let {
|
.awaitSuccess().let {
|
||||||
with(json) {
|
with(json) {
|
||||||
if (url.contains("/api/v1/series/")) {
|
if (url.contains("/api/v1/series/")) {
|
||||||
@ -57,7 +67,7 @@ class KomgaApi(
|
|||||||
track.apply {
|
track.apply {
|
||||||
cover_url = "$url/thumbnail"
|
cover_url = "$url/thumbnail"
|
||||||
tracking_url = url
|
tracking_url = url
|
||||||
total_chapters = progress.maxNumberSort.toInt()
|
total_chapters = progress.maxNumberSort.toLong()
|
||||||
status = when (progress.booksCount) {
|
status = when (progress.booksCount) {
|
||||||
progress.booksUnreadCount -> Komga.UNREAD
|
progress.booksUnreadCount -> Komga.UNREAD
|
||||||
progress.booksReadCount -> Komga.COMPLETED
|
progress.booksReadCount -> Komga.COMPLETED
|
||||||
@ -80,6 +90,7 @@ class KomgaApi(
|
|||||||
client.newCall(
|
client.newCall(
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url("${track.tracking_url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi")
|
.url("${track.tracking_url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi")
|
||||||
|
.headers(headers)
|
||||||
.put(payload.toRequestBody("application/json".toMediaType()))
|
.put(payload.toRequestBody("application/json".toMediaType()))
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
@ -65,7 +65,7 @@ data class ReadProgressUpdateDto(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ReadProgressUpdateV2Dto(
|
data class ReadProgressUpdateV2Dto(
|
||||||
val lastBookNumberSortRead: Float,
|
val lastBookNumberSortRead: Double,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -91,7 +91,7 @@ data class ReadProgressDto(
|
|||||||
booksReadCount,
|
booksReadCount,
|
||||||
booksUnreadCount,
|
booksUnreadCount,
|
||||||
booksInProgressCount,
|
booksInProgressCount,
|
||||||
lastReadContinuousIndex.toFloat(),
|
lastReadContinuousIndex.toDouble(),
|
||||||
booksCount.toFloat(),
|
booksCount.toFloat(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -102,6 +102,6 @@ data class ReadProgressV2Dto(
|
|||||||
val booksReadCount: Int,
|
val booksReadCount: Int,
|
||||||
val booksUnreadCount: Int,
|
val booksUnreadCount: Int,
|
||||||
val booksInProgressCount: Int,
|
val booksInProgressCount: Int,
|
||||||
val lastReadContinuousNumberSort: Float,
|
val lastReadContinuousNumberSort: Double,
|
||||||
val maxNumberSort: Float,
|
val maxNumberSort: Float,
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.ListItem
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Rating
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
@ -17,16 +19,22 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
|
class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING_LIST = 0
|
const val READING_LIST = 0L
|
||||||
const val WISH_LIST = 1
|
const val WISH_LIST = 1L
|
||||||
const val COMPLETE_LIST = 2
|
const val COMPLETE_LIST = 2L
|
||||||
const val UNFINISHED_LIST = 3
|
const val UNFINISHED_LIST = 3L
|
||||||
const val ON_HOLD_LIST = 4
|
const val ON_HOLD_LIST = 4L
|
||||||
|
|
||||||
private val SCORE_LIST = (
|
private val SCORE_LIST = (0..10)
|
||||||
(0..9)
|
.flatMap { decimal ->
|
||||||
.flatMap { i -> (0..9).map { j -> "$i.$j" } } + listOf("10.0")
|
when (decimal) {
|
||||||
)
|
0 -> listOf("-")
|
||||||
|
10 -> listOf("10.0")
|
||||||
|
else -> (0..9).map { fraction ->
|
||||||
|
"$decimal.$fraction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,11 +46,11 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
|
|
||||||
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
|
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
|
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING_LIST -> MR.strings.reading_list
|
READING_LIST -> MR.strings.reading_list
|
||||||
WISH_LIST -> MR.strings.wish_list
|
WISH_LIST -> MR.strings.wish_list
|
||||||
COMPLETE_LIST -> MR.strings.complete_list
|
COMPLETE_LIST -> MR.strings.complete_list
|
||||||
@ -51,15 +59,15 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING_LIST
|
override fun getReadingStatus(): Long = READING_LIST
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Long = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETE_LIST
|
override fun getCompletionStatus(): Long = COMPLETE_LIST
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
||||||
|
|
||||||
override fun indexToScore(index: Int): Float = SCORE_LIST[index].toFloat()
|
override fun indexToScore(index: Int): Double = if (index == 0) 0.0 else SCORE_LIST[index].toDouble()
|
||||||
|
|
||||||
override fun displayScore(track: DomainTrack): String = track.score.toString()
|
override fun displayScore(track: DomainTrack): String = track.score.toString()
|
||||||
|
|
||||||
@ -78,9 +86,9 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
return try {
|
return try {
|
||||||
val (series, rating) = api.getSeriesListItem(track)
|
val (series, rating) = api.getSeriesListItem(track)
|
||||||
series.copyTo(track)
|
track.copyFrom(series, rating)
|
||||||
rating?.copyTo(track) ?: track
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
track.score = 0.0
|
||||||
api.addSeriesToList(track, hasReadChapters)
|
api.addSeriesToList(track, hasReadChapters)
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
@ -95,8 +103,12 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
override suspend fun refresh(track: Track): Track {
|
||||||
val (series, rating) = api.getSeriesListItem(track)
|
val (series, rating) = api.getSeriesListItem(track)
|
||||||
series.copyTo(track)
|
return track.copyFrom(series, rating)
|
||||||
return rating?.copyTo(track) ?: track
|
}
|
||||||
|
|
||||||
|
private fun Track.copyFrom(item: ListItem, rating: Rating?): Track = apply {
|
||||||
|
item.copyTo(this)
|
||||||
|
score = rating?.rating ?: 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun login(username: String, password: String) {
|
override suspend fun login(username: String, password: String) {
|
||||||
@ -106,6 +118,6 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun restoreSession(): String? {
|
fun restoreSession(): String? {
|
||||||
return trackPreferences.trackPassword(this).get()
|
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ class MangaUpdatesApi(
|
|||||||
.let {
|
.let {
|
||||||
if (it.code == 200) {
|
if (it.code == 200) {
|
||||||
track.status = status
|
track.status = status
|
||||||
track.last_chapter_read = 1f
|
track.last_chapter_read = 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +133,8 @@ class MangaUpdatesApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateSeriesRating(track: Track) {
|
private suspend fun updateSeriesRating(track: Track) {
|
||||||
if (track.score != 0f) {
|
if (track.score < 0.0) return
|
||||||
|
if (track.score != 0.0) {
|
||||||
val body = buildJsonObject {
|
val body = buildJsonObject {
|
||||||
put("rating", track.score)
|
put("rating", track.score)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.mangaupdates
|
package eu.kanade.tachiyomi.data.track.mangaupdates
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -18,6 +19,7 @@ class MangaUpdatesInterceptor(
|
|||||||
// Add the authorization header to the original request.
|
// Add the authorization header to the original request.
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer $token")
|
.addHeader("Authorization", "Bearer $token")
|
||||||
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(authRequest)
|
return chain.proceed(authRequest)
|
||||||
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
|||||||
data class ListItem(
|
data class ListItem(
|
||||||
val series: Series? = null,
|
val series: Series? = null,
|
||||||
@SerialName("list_id")
|
@SerialName("list_id")
|
||||||
val listId: Int? = null,
|
val listId: Long? = null,
|
||||||
val status: Status? = null,
|
val status: Status? = null,
|
||||||
val priority: Int? = null,
|
val priority: Int? = null,
|
||||||
)
|
)
|
||||||
@ -17,6 +17,6 @@ data class ListItem(
|
|||||||
fun ListItem.copyTo(track: Track): Track {
|
fun ListItem.copyTo(track: Track): Track {
|
||||||
return track.apply {
|
return track.apply {
|
||||||
this.status = listId ?: READING_LIST
|
this.status = listId ?: READING_LIST
|
||||||
this.last_chapter_read = this@copyTo.status?.chapter?.toFloat() ?: 0f
|
this.last_chapter_read = this@copyTo.status?.chapter?.toDouble() ?: 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Rating(
|
data class Rating(
|
||||||
val rating: Float? = null,
|
val rating: Double? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Rating.copyTo(track: Track): Track {
|
fun Rating.copyTo(track: Track): Track {
|
||||||
return track.apply {
|
return track.apply {
|
||||||
this.score = rating ?: 0f
|
this.score = rating ?: 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ class TrackSearch : Track {
|
|||||||
|
|
||||||
override var manga_id: Long = 0
|
override var manga_id: Long = 0
|
||||||
|
|
||||||
override var tracker_id: Int = 0
|
override var tracker_id: Long = 0
|
||||||
|
|
||||||
override var remote_id: Long = 0
|
override var remote_id: Long = 0
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ class TrackSearch : Track {
|
|||||||
|
|
||||||
override lateinit var title: String
|
override lateinit var title: String
|
||||||
|
|
||||||
override var last_chapter_read: Float = 0F
|
override var last_chapter_read: Double = 0.0
|
||||||
|
|
||||||
override var total_chapters: Int = 0
|
override var total_chapters: Long = 0
|
||||||
|
|
||||||
override var score: Float = -1f
|
override var score: Double = -1.0
|
||||||
|
|
||||||
override var status: Int = 0
|
override var status: Long = 0
|
||||||
|
|
||||||
override var started_reading_date: Long = 0
|
override var started_reading_date: Long = 0
|
||||||
|
|
||||||
@ -55,14 +55,14 @@ class TrackSearch : Track {
|
|||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = manga_id.hashCode()
|
var result = manga_id.hashCode()
|
||||||
result = 31 * result + tracker_id
|
result = 31 * result + tracker_id.hashCode()
|
||||||
result = 31 * result + remote_id.hashCode()
|
result = 31 * result + remote_id.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(serviceId: Long): TrackSearch = TrackSearch().apply {
|
fun create(serviceId: Long): TrackSearch = TrackSearch().apply {
|
||||||
tracker_id = serviceId.toInt()
|
tracker_id = serviceId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1L
|
||||||
const val COMPLETED = 2
|
const val COMPLETED = 2L
|
||||||
const val ON_HOLD = 3
|
const val ON_HOLD = 3L
|
||||||
const val DROPPED = 4
|
const val DROPPED = 4L
|
||||||
const val PLAN_TO_READ = 6
|
const val PLAN_TO_READ = 6L
|
||||||
const val REREADING = 7
|
const val REREADING = 7L
|
||||||
|
|
||||||
private const val SEARCH_ID_PREFIX = "id:"
|
private const val SEARCH_ID_PREFIX = "id:"
|
||||||
private const val SEARCH_LIST_PREFIX = "my:"
|
private const val SEARCH_LIST_PREFIX = "my:"
|
||||||
@ -35,7 +35,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
|
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||||
private val api by lazy { MyAnimeListApi(id, client, interceptor) }
|
private val api by lazy { MyAnimeListApi(id, client, interceptor) }
|
||||||
|
|
||||||
override val supportsReadingDates: Boolean = true
|
override val supportsReadingDates: Boolean = true
|
||||||
@ -44,11 +44,11 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(46, 81, 162)
|
override fun getLogoColor() = Color.rgb(46, 81, 162)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
@ -58,11 +58,11 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = REREADING
|
override fun getRereadingStatus(): Long = REREADING
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
||||||
|
|
||||||
@ -77,12 +77,12 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
track.finished_reading_date = System.currentTimeMillis()
|
track.finished_reading_date = System.currentTimeMillis()
|
||||||
} else if (track.status != REREADING) {
|
} else if (track.status != REREADING) {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
if (track.last_chapter_read == 1F) {
|
if (track.last_chapter_read == 1.0) {
|
||||||
track.started_reading_date = System.currentTimeMillis()
|
track.started_reading_date = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,14 +104,14 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
|
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
val isRereading = track.status == REREADING
|
val isRereading = track.status == REREADING
|
||||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||||
}
|
}
|
||||||
|
|
||||||
update(track)
|
update(track)
|
||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0F
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +155,14 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
interceptor.setAuth(null)
|
interceptor.setAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIfAuthExpired(): Boolean {
|
||||||
|
return trackPreferences.trackAuthExpired(this).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAuthExpired() {
|
||||||
|
trackPreferences.trackAuthExpired(this).set(true)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveOAuth(oAuth: OAuth?) {
|
fun saveOAuth(oAuth: OAuth?) {
|
||||||
trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
|
trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ import kotlinx.serialization.json.Json
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.boolean
|
import kotlinx.serialization.json.boolean
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.float
|
import kotlinx.serialization.json.double
|
||||||
import kotlinx.serialization.json.floatOrNull
|
import kotlinx.serialization.json.doubleOrNull
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
@ -47,13 +47,13 @@ class MyAnimeListApi(
|
|||||||
suspend fun getAccessToken(authCode: String): OAuth {
|
suspend fun getAccessToken(authCode: String): OAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val formBody: RequestBody = FormBody.Builder()
|
val formBody: RequestBody = FormBody.Builder()
|
||||||
.add("client_id", clientId)
|
.add("client_id", CLIENT_ID)
|
||||||
.add("code", authCode)
|
.add("code", authCode)
|
||||||
.add("code_verifier", codeVerifier)
|
.add("code_verifier", codeVerifier)
|
||||||
.add("grant_type", "authorization_code")
|
.add("grant_type", "authorization_code")
|
||||||
.build()
|
.build()
|
||||||
with(json) {
|
with(json) {
|
||||||
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
|
client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs()
|
.parseAs()
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ class MyAnimeListApi(
|
|||||||
suspend fun getCurrentUser(): String {
|
suspend fun getCurrentUser(): String {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("$baseApiUrl/users/@me")
|
.url("$BASE_API_URL/users/@me")
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
with(json) {
|
with(json) {
|
||||||
@ -77,7 +77,7 @@ class MyAnimeListApi(
|
|||||||
|
|
||||||
suspend fun search(query: String): List<TrackSearch> {
|
suspend fun search(query: String): List<TrackSearch> {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
// MAL API throws a 400 when the query is over 64 characters...
|
// MAL API throws a 400 when the query is over 64 characters...
|
||||||
.appendQueryParameter("q", query.take(64))
|
.appendQueryParameter("q", query.take(64))
|
||||||
.appendQueryParameter("nsfw", "true")
|
.appendQueryParameter("nsfw", "true")
|
||||||
@ -102,7 +102,7 @@ class MyAnimeListApi(
|
|||||||
|
|
||||||
suspend fun getMangaDetails(id: Int): TrackSearch {
|
suspend fun getMangaDetails(id: Int): TrackSearch {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
.appendPath(id.toString())
|
.appendPath(id.toString())
|
||||||
.appendQueryParameter(
|
.appendQueryParameter(
|
||||||
"fields",
|
"fields",
|
||||||
@ -119,8 +119,8 @@ class MyAnimeListApi(
|
|||||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["title"]!!.jsonPrimitive.content
|
title = obj["title"]!!.jsonPrimitive.content
|
||||||
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
||||||
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
total_chapters = obj["num_chapters"]!!.jsonPrimitive.long
|
||||||
score = obj["mean"]?.jsonPrimitive?.floatOrNull ?: -1f
|
score = obj["mean"]?.jsonPrimitive?.doubleOrNull ?: -1.0
|
||||||
cover_url =
|
cover_url =
|
||||||
obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content
|
obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content
|
||||||
?: ""
|
?: ""
|
||||||
@ -178,7 +178,7 @@ class MyAnimeListApi(
|
|||||||
|
|
||||||
suspend fun findListItem(track: Track): Track? {
|
suspend fun findListItem(track: Track): Track? {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val uri = "$baseApiUrl/manga".toUri().buildUpon()
|
val uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
.appendPath(track.remote_id.toString())
|
.appendPath(track.remote_id.toString())
|
||||||
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
||||||
.build()
|
.build()
|
||||||
@ -187,7 +187,7 @@ class MyAnimeListApi(
|
|||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<JsonObject>()
|
.parseAs<JsonObject>()
|
||||||
.let { obj ->
|
.let { obj ->
|
||||||
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.long
|
||||||
obj.jsonObject["my_list_status"]?.jsonObject?.let {
|
obj.jsonObject["my_list_status"]?.jsonObject?.let {
|
||||||
parseMangaItem(it, track)
|
parseMangaItem(it, track)
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ class MyAnimeListApi(
|
|||||||
|
|
||||||
// Check next page if there's more
|
// Check next page if there's more
|
||||||
if (!obj["paging"]!!.jsonObject["next"]?.jsonPrimitive?.contentOrNull.isNullOrBlank()) {
|
if (!obj["paging"]!!.jsonObject["next"]?.jsonPrimitive?.contentOrNull.isNullOrBlank()) {
|
||||||
matches + findListItems(query, offset + listPaginationAmount)
|
matches + findListItems(query, offset + LIST_PAGINATION_AMOUNT)
|
||||||
} else {
|
} else {
|
||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
@ -225,9 +225,9 @@ class MyAnimeListApi(
|
|||||||
|
|
||||||
private suspend fun getListPage(offset: Int): JsonObject {
|
private suspend fun getListPage(offset: Int): JsonObject {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val urlBuilder = "$baseApiUrl/users/@me/mangalist".toUri().buildUpon()
|
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
||||||
.appendQueryParameter("fields", "list_status{start_date,finish_date}")
|
.appendQueryParameter("fields", "list_status{start_date,finish_date}")
|
||||||
.appendQueryParameter("limit", listPaginationAmount.toString())
|
.appendQueryParameter("limit", LIST_PAGINATION_AMOUNT.toString())
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
urlBuilder.appendQueryParameter("offset", offset.toString())
|
urlBuilder.appendQueryParameter("offset", offset.toString())
|
||||||
}
|
}
|
||||||
@ -249,8 +249,8 @@ class MyAnimeListApi(
|
|||||||
return track.apply {
|
return track.apply {
|
||||||
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
|
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
|
||||||
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]?.jsonPrimitive?.content)
|
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]?.jsonPrimitive?.content)
|
||||||
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.float
|
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.double
|
||||||
score = obj["score"]!!.jsonPrimitive.int.toFloat()
|
score = obj["score"]!!.jsonPrimitive.int.toDouble()
|
||||||
obj["start_date"]?.let {
|
obj["start_date"]?.let {
|
||||||
started_reading_date = parseDate(it.jsonPrimitive.content)
|
started_reading_date = parseDate(it.jsonPrimitive.content)
|
||||||
}
|
}
|
||||||
@ -277,30 +277,29 @@ class MyAnimeListApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Registered under arkon's MAL account
|
private const val CLIENT_ID = "c46c9e24640a64dad5be5ca7a1a53a0f"
|
||||||
private const val clientId = "8fd3313bc138e8b890551aa1de1a2589"
|
|
||||||
|
|
||||||
private const val baseOAuthUrl = "https://myanimelist.net/v1/oauth2"
|
private const val BASE_OAUTH_URL = "https://myanimelist.net/v1/oauth2"
|
||||||
private const val baseApiUrl = "https://api.myanimelist.net/v2"
|
private const val BASE_API_URL = "https://api.myanimelist.net/v2"
|
||||||
|
|
||||||
private const val listPaginationAmount = 250
|
private const val LIST_PAGINATION_AMOUNT = 250
|
||||||
|
|
||||||
private var codeVerifier: String = ""
|
private var codeVerifier: String = ""
|
||||||
|
|
||||||
fun authUrl(): Uri = "$baseOAuthUrl/authorize".toUri().buildUpon()
|
fun authUrl(): Uri = "$BASE_OAUTH_URL/authorize".toUri().buildUpon()
|
||||||
.appendQueryParameter("client_id", clientId)
|
.appendQueryParameter("client_id", CLIENT_ID)
|
||||||
.appendQueryParameter("code_challenge", getPkceChallengeCode())
|
.appendQueryParameter("code_challenge", getPkceChallengeCode())
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mangaUrl(id: Long): Uri = "$baseApiUrl/manga".toUri().buildUpon()
|
fun mangaUrl(id: Long): Uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
.appendPath(id.toString())
|
.appendPath(id.toString())
|
||||||
.appendPath("my_list_status")
|
.appendPath("my_list_status")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun refreshTokenRequest(oauth: OAuth): Request {
|
fun refreshTokenRequest(oauth: OAuth): Request {
|
||||||
val formBody: RequestBody = FormBody.Builder()
|
val formBody: RequestBody = FormBody.Builder()
|
||||||
.add("client_id", clientId)
|
.add("client_id", CLIENT_ID)
|
||||||
.add("refresh_token", oauth.refresh_token)
|
.add("refresh_token", oauth.refresh_token)
|
||||||
.add("grant_type", "refresh_token")
|
.add("grant_type", "refresh_token")
|
||||||
.build()
|
.build()
|
||||||
@ -312,7 +311,7 @@ class MyAnimeListApi(
|
|||||||
.add("Authorization", "Bearer ${oauth.access_token}")
|
.add("Authorization", "Bearer ${oauth.access_token}")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return POST("$baseOAuthUrl/token", body = formBody, headers = headers)
|
return POST("$BASE_OAUTH_URL/token", body = formBody, headers = headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPkceChallengeCode(): String {
|
private fun getPkceChallengeCode(): String {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.myanimelist
|
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
@ -7,33 +8,32 @@ import okhttp3.Response
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor {
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private var oauth: OAuth? = null
|
private var oauth: OAuth? = myanimelist.loadOAuth()
|
||||||
|
private val tokenExpired get() = myanimelist.getIfAuthExpired()
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
if (tokenExpired) {
|
||||||
|
throw MALTokenExpired()
|
||||||
|
}
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
if (token.isNullOrEmpty()) {
|
|
||||||
throw IOException("Not authenticated with MyAnimeList")
|
|
||||||
}
|
|
||||||
if (oauth == null) {
|
|
||||||
oauth = myanimelist.loadOAuth()
|
|
||||||
}
|
|
||||||
// Refresh access token if expired
|
// Refresh access token if expired
|
||||||
if (oauth != null && oauth!!.isExpired()) {
|
if (oauth != null && oauth!!.isExpired()) {
|
||||||
setAuth(refreshToken(chain))
|
setAuth(refreshToken(chain))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oauth == null) {
|
if (oauth == null) {
|
||||||
throw IOException("No authentication token")
|
throw IOException("MAL: User is not authenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the authorization header to the original request
|
// Add the authorization header to the original request
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||||
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = chain.proceed(authRequest)
|
val response = chain.proceed(authRequest)
|
||||||
@ -50,6 +50,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
|
|
||||||
val newRequest = originalRequest.newBuilder()
|
val newRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${newToken.access_token}")
|
.addHeader("Authorization", "Bearer ${newToken.access_token}")
|
||||||
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(newRequest)
|
return chain.proceed(newRequest)
|
||||||
@ -63,15 +64,16 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
* and the oauth object.
|
* and the oauth object.
|
||||||
*/
|
*/
|
||||||
fun setAuth(oauth: OAuth?) {
|
fun setAuth(oauth: OAuth?) {
|
||||||
token = oauth?.access_token
|
|
||||||
this.oauth = oauth
|
this.oauth = oauth
|
||||||
myanimelist.saveOAuth(oauth)
|
myanimelist.saveOAuth(oauth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshToken(chain: Interceptor.Chain): OAuth {
|
private fun refreshToken(chain: Interceptor.Chain): OAuth {
|
||||||
val newOauth = runCatching {
|
return runCatching {
|
||||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||||
|
if (oauthResponse.code == 401) {
|
||||||
|
myanimelist.setAuthExpired()
|
||||||
|
}
|
||||||
if (oauthResponse.isSuccessful) {
|
if (oauthResponse.isSuccessful) {
|
||||||
with(json) { oauthResponse.parseAs<OAuth>() }
|
with(json) { oauthResponse.parseAs<OAuth>() }
|
||||||
} else {
|
} else {
|
||||||
@ -79,11 +81,9 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.getOrNull()
|
||||||
if (newOauth.getOrNull() == null) {
|
?: throw MALTokenExpired()
|
||||||
throw IOException("Failed to refresh the access token")
|
|
||||||
}
|
|
||||||
|
|
||||||
return newOauth.getOrNull()!!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MALTokenExpired : IOException("MAL: Login has expired")
|
||||||
|
@ -18,12 +18,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
|||||||
class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1L
|
||||||
const val COMPLETED = 2
|
const val COMPLETED = 2L
|
||||||
const val ON_HOLD = 3
|
const val ON_HOLD = 3L
|
||||||
const val DROPPED = 4
|
const val DROPPED = 4L
|
||||||
const val PLAN_TO_READ = 5
|
const val PLAN_TO_READ = 5L
|
||||||
const val REREADING = 6
|
const val REREADING = 6L
|
||||||
|
|
||||||
private val SCORE_LIST = IntRange(0, 10)
|
private val SCORE_LIST = IntRange(0, 10)
|
||||||
.map(Int::toString)
|
.map(Int::toString)
|
||||||
@ -49,7 +49,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
if (didReadChapter) {
|
if (didReadChapter) {
|
||||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
} else if (track.status != REREADING) {
|
} else if (track.status != REREADING) {
|
||||||
track.status = READING
|
track.status = READING
|
||||||
@ -72,14 +72,14 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
|
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
val isRereading = track.status == REREADING
|
val isRereading = track.status == REREADING
|
||||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||||
}
|
}
|
||||||
|
|
||||||
update(track)
|
update(track)
|
||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0F
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,11 +101,11 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Long> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
READING -> MR.strings.reading
|
READING -> MR.strings.reading
|
||||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
COMPLETED -> MR.strings.completed
|
COMPLETED -> MR.strings.completed
|
||||||
@ -115,11 +115,11 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = READING
|
override fun getReadingStatus(): Long = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = REREADING
|
override fun getRereadingStatus(): Long = REREADING
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = COMPLETED
|
override fun getCompletionStatus(): Long = COMPLETED
|
||||||
|
|
||||||
override suspend fun login(username: String, password: String) = login(password)
|
override suspend fun login(username: String, password: String) = login(password)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import kotlinx.serialization.json.JsonArray
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.float
|
import kotlinx.serialization.json.double
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
@ -102,10 +102,10 @@ class ShikimoriApi(
|
|||||||
return TrackSearch.create(trackId).apply {
|
return TrackSearch.create(trackId).apply {
|
||||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["name"]!!.jsonPrimitive.content
|
title = obj["name"]!!.jsonPrimitive.content
|
||||||
total_chapters = obj["chapters"]!!.jsonPrimitive.int
|
total_chapters = obj["chapters"]!!.jsonPrimitive.long
|
||||||
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
||||||
summary = ""
|
summary = ""
|
||||||
score = obj["score"]!!.jsonPrimitive.float
|
score = obj["score"]!!.jsonPrimitive.double
|
||||||
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
||||||
publishing_status = obj["status"]!!.jsonPrimitive.content
|
publishing_status = obj["status"]!!.jsonPrimitive.content
|
||||||
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
||||||
@ -117,10 +117,10 @@ class ShikimoriApi(
|
|||||||
return Track.create(trackId).apply {
|
return Track.create(trackId).apply {
|
||||||
title = mangas["name"]!!.jsonPrimitive.content
|
title = mangas["name"]!!.jsonPrimitive.content
|
||||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||||
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
total_chapters = mangas["chapters"]!!.jsonPrimitive.long
|
||||||
library_id = obj["id"]!!.jsonPrimitive.long
|
library_id = obj["id"]!!.jsonPrimitive.long
|
||||||
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
last_chapter_read = obj["chapters"]!!.jsonPrimitive.double
|
||||||
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
score = obj["score"]!!.jsonPrimitive.int.toDouble()
|
||||||
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
||||||
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content
|
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content
|
||||||
}
|
}
|
||||||
@ -192,15 +192,15 @@ class ShikimoriApi(
|
|||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
private const val clientId = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA"
|
||||||
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
private const val clientSecret = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus"
|
||||||
|
|
||||||
private const val baseUrl = "https://shikimori.one"
|
private const val baseUrl = "https://shikimori.one"
|
||||||
private const val apiUrl = "$baseUrl/api"
|
private const val apiUrl = "$baseUrl/api"
|
||||||
private const val oauthUrl = "$baseUrl/oauth/token"
|
private const val oauthUrl = "$baseUrl/oauth/token"
|
||||||
private const val loginUrl = "$baseUrl/oauth/authorize"
|
private const val loginUrl = "$baseUrl/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
private const val redirectUrl = "mihon://shikimori-auth"
|
||||||
|
|
||||||
fun authUrl(): Uri = loginUrl.toUri().buildUpon()
|
fun authUrl(): Uri = loginUrl.toUri().buildUpon()
|
||||||
.appendQueryParameter("client_id", clientId)
|
.appendQueryParameter("client_id", clientId)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -33,7 +34,7 @@ class ShikimoriInterceptor(private val shikimori: Shikimori) : Interceptor {
|
|||||||
// Add the authorization header to the original request.
|
// Add the authorization header to the original request.
|
||||||
val authRequest = originalRequest.newBuilder()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||||
.header("User-Agent", "Tachiyomi")
|
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(authRequest)
|
return chain.proceed(authRequest)
|
||||||
|