mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Compare commits
345 Commits
Author | SHA1 | Date | |
---|---|---|---|
b8a98ef5e4 | |||
c8fa90f473 | |||
b47ee8857b | |||
131dfa62c4 | |||
6a5af438dd | |||
ccc0a61158 | |||
e990ad25eb | |||
98a4d1e763 | |||
f762598c5c | |||
ec56c27071 | |||
eb0e0a1952 | |||
01a837fde6 | |||
b9488645d4 | |||
7a94b477cb | |||
99710b45d1 | |||
3813743e3d | |||
9bb2334b69 | |||
7e73ede47a | |||
b0106aa420 | |||
33e5fea96c | |||
f0a1dcd120 | |||
26d5a87bef | |||
52ae208df3 | |||
34aaa7fb0a | |||
a8c784355c | |||
0aed93becf | |||
1ea0804209 | |||
a52fbb012a | |||
2dc47352f8 | |||
e95a5be21d | |||
abd69d4f91 | |||
d749e309f8 | |||
e4d075fb91 | |||
71c6c71081 | |||
d2b14bcfc4 | |||
2c04c81bd1 | |||
dd66c83c50 | |||
9e51d82154 | |||
bdc441a5be | |||
2dcb73700b | |||
ee01686ae4 | |||
9a55cf880e | |||
6742cdeb8b | |||
c37377bffa | |||
76147a9be7 | |||
bf22e69250 | |||
49693934cf | |||
e6a63ee5b2 | |||
08dc57fd02 | |||
cede590696 | |||
c401915fb5 | |||
2a202bd510 | |||
dcd8ed08fc | |||
d3ebedeef2 | |||
d2e2ebbe45 | |||
0cef05dd89 | |||
a443dc3040 | |||
4e6cc013e5 | |||
0c65d54d89 | |||
ccd0e0cdfe | |||
9278ca3f5e | |||
d7a70b962b | |||
fff0f841fa | |||
8ba426350f | |||
8ef548032f | |||
5452e29840 | |||
148f8e6d11 | |||
13a5662a84 | |||
6713a7ae3c | |||
226ad13061 | |||
88ee86b7ef | |||
4bc2288806 | |||
a928d9fa0b | |||
d8f4e6b45f | |||
5ef5087406 | |||
135c371d88 | |||
966c196f4a | |||
dc43e41896 | |||
4809d06d04 | |||
9f7fda0bc5 | |||
1f67695713 | |||
66ef1a8206 | |||
beaffc3870 | |||
8536ecb611 | |||
d7a89b0f8c | |||
943081e80d | |||
3f007a1edd | |||
e33cacf6a4 | |||
23fe848a35 | |||
fa5d2276c0 | |||
d353a3457d | |||
b363b9fc1a | |||
1920568057 | |||
763da19c9d | |||
1813dbbf59 | |||
339169b624 | |||
93960315d9 | |||
479eb1ba71 | |||
962d8e5fd2 | |||
fefd4c0b26 | |||
40639c0933 | |||
7401673ac1 | |||
41f759dafe | |||
73dcc7bcb1 | |||
d8b1f60581 | |||
16fc58bd16 | |||
68df2f4ce7 | |||
367932de69 | |||
3da08cbcbf | |||
68efc0c42f | |||
f5b01b0ca2 | |||
5733429682 | |||
c6d29fc19b | |||
d9a12d79b0 | |||
963cf4c996 | |||
0ef073669a | |||
ed1123feb0 | |||
c2114bbd4f | |||
fedb1d2590 | |||
eef39b75a6 | |||
eca593ac36 | |||
a1917b8c81 | |||
ec6dba12bd | |||
da0671ad62 | |||
04d83e9a6a | |||
2cb7624953 | |||
e6ace844b6 | |||
f2f6628693 | |||
1c33032721 | |||
b3f5f13c39 | |||
a5339969c9 | |||
1681437206 | |||
2eaf083eee | |||
3645d19135 | |||
3b4b1185e2 | |||
406c5bde11 | |||
75d1913aaf | |||
eb254d9c56 | |||
4e633b8936 | |||
e99a27e382 | |||
c8a6a2653f | |||
0ea0eba4f0 | |||
7f88b56d8b | |||
789421c7a0 | |||
d44503cb19 | |||
0258422527 | |||
47327d840d | |||
d0f1a33744 | |||
c54b8e62d7 | |||
3dc738f28c | |||
ca75400467 | |||
65091c05c9 | |||
52e846f3b6 | |||
ce22b2c29a | |||
361b0284fa | |||
b8947a1c50 | |||
8d1effa0e8 | |||
096a9f4cbf | |||
18712b166f | |||
4605e14729 | |||
a768280d82 | |||
cbb8f25645 | |||
24ff7ff67c | |||
eee0bd6cf4 | |||
f176a5179a | |||
381ba86e3c | |||
f4be8e28ca | |||
e17605f8d9 | |||
e06a488af8 | |||
6dc8bfed8d | |||
f642f23366 | |||
c041410f61 | |||
ff5f13eafe | |||
749b240897 | |||
9207762ade | |||
0a22950ad3 | |||
0fcd404b4f | |||
150ea29a70 | |||
f9baff0e90 | |||
6ad3fcb91d | |||
395ca3630c | |||
e9fbdb660b | |||
526e029ebb | |||
763c4522b7 | |||
32e3ac63ed | |||
90f31aab38 | |||
3d44feaf2c | |||
f5a44245e9 | |||
7e7eb9f39f | |||
6c9b982104 | |||
a106104027 | |||
7753161332 | |||
ec9d592cf1 | |||
31015504f4 | |||
bd40ec527d | |||
3899938b25 | |||
ca7373c28b | |||
390bdfa93d | |||
ac2df87954 | |||
0b73f8b1ef | |||
812fcec9f0 | |||
ec7297f8c2 | |||
0fdb19c07d | |||
3bc07b4753 | |||
0b8b13d0bb | |||
4c5bf9bc8e | |||
9f53109414 | |||
bccb1229c8 | |||
40ab3fe0a6 | |||
4a99118cce | |||
58ba29fa16 | |||
54cfb2acdf | |||
0bf14fd31c | |||
7dd9a0211b | |||
3d43473bf8 | |||
2194c4ba28 | |||
744a4f8f47 | |||
67d91f7b69 | |||
bf5065d16b | |||
3e837f8781 | |||
77d378ccd1 | |||
1a542bae71 | |||
e3ed12b5d2 | |||
759795940b | |||
a23d5ab734 | |||
6ee69ef430 | |||
3edf17d322 | |||
8c2b2f99bc | |||
73dc51b3f6 | |||
9a082d4df1 | |||
f430b6f853 | |||
78a352541a | |||
a0f5633094 | |||
0af81c7d05 | |||
52e82b3548 | |||
f05b99ec1f | |||
194897bf3c | |||
7cf26363c8 | |||
3d1dec4c05 | |||
af1935d2e4 | |||
3c4bc17065 | |||
333d1c1ad9 | |||
4e027cec71 | |||
39ae84301a | |||
3bf14623ad | |||
ac8f2923e5 | |||
e9d3b75e2b | |||
e6bc181e7a | |||
a2ece82197 | |||
259946cf0a | |||
4fdb4f14a8 | |||
914f5e569b | |||
9b4ffd1cd5 | |||
ed87dd89a1 | |||
3b41a78e76 | |||
0fccbbc0ca | |||
067627b51a | |||
09816ed5b6 | |||
b457cdb0c2 | |||
5fd1dec347 | |||
647391ef73 | |||
ed029c52ae | |||
102a372df9 | |||
d4ffb09a8b | |||
6ba052d2af | |||
57b63f43f5 | |||
2fb0969c75 | |||
3357e878a5 | |||
471d5d62d5 | |||
e810b343cf | |||
620be2617a | |||
035038a0b6 | |||
b8ffb87f01 | |||
39e1e11f99 | |||
5f9df78ab0 | |||
a00d11701f | |||
1cf74a5396 | |||
8cd27a199d | |||
772929b5c6 | |||
c4ca3606ad | |||
9e830f1c55 | |||
ee8c71c14a | |||
39bd823651 | |||
a9d16fad34 | |||
1da169319d | |||
2bb1eea2be | |||
9cb45b92e1 | |||
4a8d5098da | |||
d875d5ef74 | |||
fc4e290c49 | |||
ccc198e081 | |||
9f0ed77423 | |||
bb3e616890 | |||
573f3a392a | |||
830a834ea6 | |||
e4ea5d0344 | |||
97aed045e6 | |||
46b01c6134 | |||
e208fa4020 | |||
6b71264482 | |||
5723c184b1 | |||
530daeaa3a | |||
dd1b5c7ea7 | |||
3cffc6890d | |||
04d44f19f5 | |||
d46a742a43 | |||
a94fd24fa9 | |||
8a4c4c346a | |||
dc54299e24 | |||
436253dd63 | |||
29feee0095 | |||
63f3180dff | |||
6b3b98cf57 | |||
1442e2b53e | |||
521ebf0678 | |||
a20874f6a1 | |||
40776bdc8d | |||
f853158e6b | |||
c9035b5df9 | |||
150132f4dd | |||
fb97ac47bc | |||
5b53b90495 | |||
c6513d4450 | |||
ce6848b3c0 | |||
d86d861e4b | |||
3b45fcdb21 | |||
3d1250f2f8 | |||
eaf1ef831a | |||
b4c7992726 | |||
03baa21185 | |||
694de99a3f | |||
8383f4fb7b | |||
eb3fff6c51 | |||
4ff3f0bcba | |||
788ea052fc | |||
08ba805bbd | |||
dbd14c6dac | |||
5977fca6b9 | |||
fcf596d36b | |||
dabca5f09e | |||
018dbce57e | |||
bd45cc2024 | |||
abf47deee3 | |||
ce0090f0ca | |||
6cd34614f6 |
4
.github/CONTRIBUTING.md
vendored
Executable file → Normal file
4
.github/CONTRIBUTING.md
vendored
Executable file → Normal file
@ -14,9 +14,9 @@
|
||||
* Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions#readme, not here
|
||||
|
||||
# Bugs
|
||||
* Include version (Setting > About > Version)
|
||||
* Include version (More > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Dev version is equal to the number of commits as seen in the main page
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
|
2
.github/ISSUE_TEMPLATE.md
vendored
Executable file → Normal file
2
.github/ISSUE_TEMPLATE.md
vendored
Executable file → Normal file
@ -2,7 +2,7 @@
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: "🐞 Bug report"
|
||||
about: Report a bug
|
||||
title: "[Bug] Write short description here"
|
||||
title: "[Bug] <Write short description here>"
|
||||
labels: "bug"
|
||||
---
|
||||
|
||||
@ -9,7 +9,7 @@ labels: "bug"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: "🌟 Feature request"
|
||||
about: Suggest a feature to improve Tachiyomi
|
||||
title: "[Feature Request] Write short description here"
|
||||
title: "[Feature Request] <Write short description here>"
|
||||
labels: "feature"
|
||||
---
|
||||
|
||||
@ -9,7 +9,7 @@ labels: "feature"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
BIN
.github/readme-images/screens.png
vendored
BIN
.github/readme-images/screens.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.2 MiB |
25
.github/workflows/PullRequestTester.yml
vendored
25
.github/workflows/PullRequestTester.yml
vendored
@ -1,25 +0,0 @@
|
||||
name: Pull Request Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
apk:
|
||||
name: Generate APK
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Get NDK
|
||||
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570"
|
||||
- name: Build Release APK
|
||||
run: bash ./gradlew assembleDebug --stacktrace
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: TachiyomiSY-${{ github.sha }}.apk
|
||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
@ -1,31 +0,0 @@
|
||||
name: Remote Dispatch Action Initiator
|
||||
|
||||
on:
|
||||
push:
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
ping-pong:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
- name: TAG - Bump version and push tag
|
||||
uses: anothrNick/github-tag-action@1.17.2
|
||||
if: github.event.action != 'pong'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WITH_V: true
|
||||
RELEASE_BRANCHES: master
|
||||
- name: PING - Dispatch initiating repository event
|
||||
if: github.event.action != 'pong'
|
||||
run: |
|
||||
curl -X POST https://api.github.com/repos/jobobby04/TachiyomiSYPreview/dispatches \
|
||||
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||
- name: ACK - Acknowledge pong from remote repository
|
||||
if: github.event.action == 'pong'
|
||||
run: |
|
||||
echo "PONG received from '${{ github.event.client_payload.repository }}'"
|
31
.github/workflows/issue_closer.yml
vendored
31
.github/workflows/issue_closer.yml
vendored
@ -4,10 +4,31 @@ jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autoclose issue
|
||||
uses: arkon/issue-closer-action@v1.0
|
||||
- name: Autoclose when created in wrong repo
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-close-message: "@${issue.user.login} this issue was automatically closed because it was not filled in correctly or the acknowledgment section was not removed."
|
||||
issue-title-pattern: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||
issue-body-pattern: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
||||
type: title
|
||||
regex: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because it was not opened in the correct repo, as the template mentioned."
|
||||
- name: Autoclose when no short description provided
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: title
|
||||
regex: ".*<Write short description here>*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because you did not fill out the description in the title."
|
||||
- name: Autoclose when body acknowledgement section not removed
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: body
|
||||
regex: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because the acknowledgment section was not removed."
|
||||
- name: Autoclose when body requested information not filled out
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: body
|
||||
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
7
.gitignore
vendored
Executable file → Normal file
7
.gitignore
vendored
Executable file → Normal file
@ -5,8 +5,6 @@
|
||||
.idea/
|
||||
*iml
|
||||
*.iml
|
||||
/mainframer
|
||||
/.mainframer
|
||||
|
||||
# Built files
|
||||
*/build
|
||||
@ -16,8 +14,3 @@ app/**/output.json
|
||||
|
||||
# Hebrew assets are copied on build
|
||||
app/src/main/res/values-iw/
|
||||
|
||||
TODO.md
|
||||
CHANGELOG.md
|
||||
/captures
|
||||
build.sh
|
||||
|
20
.travis/build.sh
Executable file
20
.travis/build.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
git fetch --unshallow #required for commit count
|
||||
|
||||
if [ -z "$TRAVIS_TAG" ]; then
|
||||
./gradlew clean assembleStandardDebug
|
||||
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
export ARTIFACT="tachiyomi-r${COMMIT_COUNT}.apk"
|
||||
|
||||
mv app/build/outputs/apk/standard/debug/app-standard-debug.apk $ARTIFACT
|
||||
else
|
||||
./gradlew clean assembleStandardRelease
|
||||
|
||||
TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
|
||||
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
||||
|
||||
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
||||
${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:STORE_PASS --key-pass env:KEY_PASS --out $ARTIFACT app-aligned.apk
|
||||
fi
|
15
.travis/deploy.sh
Executable file
15
.travis/deploy.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
pattern="tachiyomi-r*"
|
||||
files=( $pattern )
|
||||
export ARTIFACT="${files[0]}"
|
||||
|
||||
if [ -z "$ARTIFACT" ]; then
|
||||
echo "Artifact not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export SSHOPTIONS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${DEPLOY_KEY}"
|
||||
|
||||
scp $SSHOPTIONS $ARTIFACT $DEPLOY_USER@$DEPLOY_HOST:builds/
|
||||
ssh $SSHOPTIONS $DEPLOY_USER@$DEPLOY_HOST ln -sf $ARTIFACT builds/latest
|
BIN
.travis/secrets.tar.enc
Normal file
BIN
.travis/secrets.tar.enc
Normal file
Binary file not shown.
28
PREVIEW_RELEASE_NOTES.md
Normal file
28
PREVIEW_RELEASE_NOTES.md
Normal file
@ -0,0 +1,28 @@
|
||||
### r1810
|
||||
- Background jobs were migrated to a new system. You may need to toggle the settings to ensure they
|
||||
run properly. This includes app updates, library updates, and automatic backups.
|
||||
|
||||
### r1340
|
||||
- A new screen for managing extensions was added. If you previously installed extensions from FDroid,
|
||||
you will have to uninstall all of them first (tap on the extension then uninstall), otherwise you won't be able
|
||||
to update them due to signature mismatch. You won't lose anything in this process as the extensions themselves
|
||||
don't store anything.
|
||||
|
||||
### r959
|
||||
- The download manager has been rewritten and it's possible some of your downloads
|
||||
aren't recognized anymore. You may have to check your downloads folder and manually delete those.
|
||||
- You can now download to any folder in your SD card.
|
||||
- The download directory setting has been reset.
|
||||
|
||||
### r857
|
||||
- **Important!** Delete after read has been updated.
|
||||
This means the value has been reset set to disabled.
|
||||
This can be changed in Settings > Downloads
|
||||
|
||||
### r736
|
||||
- **Important!** Now chapters follow the order of the sources. **It's required that you update your entire library
|
||||
before reading in order for them to be synced.** Old behavior can be restored for a manga in the overflow menu of the chapters tab.
|
||||
|
||||
### r724
|
||||
- Kissmanga covers may not load anymore. The only workaround is to update the details of the manga
|
||||
from the info tab, or clearing the database (the latter won't fix covers from library manga).
|
88
README.md
88
README.md
@ -1,3 +1,87 @@
|
||||
I haven't started a readme
|
||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
||||
|-------|----------|---------|------------|---------|
|
||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) | [](http://tachiyomi.kanade.eu/latest) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
||||
|
||||
Automated Preview Builds(with updater): https://github.com/jobobby04/TachiyomiSYPreview/releases
|
||||
|
||||
# Tachiyomi
|
||||
Tachiyomi is a free and open source manga reader for Android 5.0 and above.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
Features include:
|
||||
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
||||
* Local reading of downloaded manga
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Categories to organize your library
|
||||
* Light and dark themes
|
||||
* Schedule updating your library for new chapters
|
||||
* Create backups locally to read offline or to your desired cloud service
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](http://tachiyomi.kanade.eu/latest).
|
||||
|
||||
## Issues, Feature Requests and Contributing
|
||||
|
||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
||||
|
||||
<details><summary>Issues</summary>
|
||||
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Bugs</summary>
|
||||
|
||||
* Include version (More > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
* For large logs use http://pastebin.com/ (or similar)
|
||||
* Don't group unrelated requests into one issue
|
||||
|
||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
||||
|
||||
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Feature Requests</summary>
|
||||
|
||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||
* Include screenshot (if needed)
|
||||
|
||||
Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
|
||||
</details>
|
||||
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2015 Javier Tomás
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The developer of this application does not have any affiliation with the content providers available.
|
||||
|
2
app/.gitignore
vendored
Executable file → Normal file
2
app/.gitignore
vendored
Executable file → Normal file
@ -2,5 +2,3 @@
|
||||
*iml
|
||||
*.iml
|
||||
custom.gradle
|
||||
google-services.json
|
||||
output.json
|
212
app/build.gradle
Executable file → Normal file
212
app/build.gradle
Executable file → Normal file
@ -1,15 +1,13 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
//noinspection GradleDependency
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||
// Realm (EH)
|
||||
apply plugin: 'realm-android'
|
||||
|
||||
shortcutHelper.filePath = './shortcuts.xml'
|
||||
|
||||
@ -34,22 +32,21 @@ ext {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
publishNonDefault true
|
||||
compileSdkVersion AndroidConfig.compileSdk
|
||||
buildToolsVersion AndroidConfig.buildTools
|
||||
|
||||
defaultConfig {
|
||||
applicationId "eu.kanade.tachiyomi.sy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
applicationId "eu.kanade.tachiyomi"
|
||||
minSdkVersion AndroidConfig.minSdk
|
||||
targetSdkVersion AndroidConfig.targetSdk
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 1
|
||||
versionName "0.9.2.7"
|
||||
versionCode 46
|
||||
versionName "0.10.0"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
@ -58,28 +55,23 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
viewBinding {
|
||||
enabled = true
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
versionNameSuffix "-${getCommitCount()}"
|
||||
applicationIdSuffix ".debug"
|
||||
ext.enableCrashlytics = false
|
||||
}
|
||||
releaseTest {
|
||||
applicationIdSuffix ".rt"
|
||||
// minifyEnabled true
|
||||
// shrinkResources true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
postprocessing {
|
||||
obfuscate false
|
||||
optimizeCode true
|
||||
removeUnusedCode false
|
||||
removeUnusedResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,41 +82,42 @@ android {
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||
dimension "default"
|
||||
}
|
||||
fdroid {
|
||||
dimension "default"
|
||||
}
|
||||
dev {
|
||||
resConfigs "en", "xxhdpi"
|
||||
dimension "default"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/*.kotlin_module'
|
||||
}
|
||||
|
||||
// Compatibility for two RxJava versions (EXH)
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
disable 'ExtraTranslation'
|
||||
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,30 +127,34 @@ androidExtensions {
|
||||
|
||||
dependencies {
|
||||
|
||||
// Modified dependencies
|
||||
implementation 'com.github.inorichi:subsampling-scale-image-view:ac0dae7'
|
||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
// Android support library
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
// AndroidX libraries
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation 'androidx.webkit:webkit:1.3.0-rc01'
|
||||
|
||||
final lifecycle_version = '2.2.0'
|
||||
final lifecycle_version = '2.3.0-alpha06'
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
|
||||
// UI library
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
// Job scheduling
|
||||
final work_version = '2.4.0'
|
||||
implementation "androidx.work:work-runtime:$work_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.4.1'
|
||||
// UI library
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
||||
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.4.4'
|
||||
|
||||
// ReactiveX
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
@ -166,10 +163,14 @@ dependencies {
|
||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||
|
||||
// Network client
|
||||
final okhttp_version = '4.7.2'
|
||||
final okhttp_version = '4.8.0'
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||
implementation 'com.squareup.okio:okio:2.6.0'
|
||||
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
|
||||
implementation 'com.squareup.okio:okio:2.7.0'
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||
|
||||
// REST
|
||||
final retrofit_version = '2.9.0'
|
||||
@ -182,34 +183,23 @@ dependencies {
|
||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||
|
||||
// JavaScript engine
|
||||
implementation 'com.squareup.duktape:duktape-android:1.2.0' // Stuck on 1.2.0 to fix MangaPlus extension
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
|
||||
// Disk
|
||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||
implementation 'com.github.inorichi:unifile:e9ee588'
|
||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
// HTML parser
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
// Job scheduling
|
||||
final work_version = '2.3.4'
|
||||
implementation "androidx.work:work-runtime:$work_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
// [EXH] Android 7 SSL Workaround
|
||||
implementation 'com.google.android.gms:play-services-safetynet:17.0.0'
|
||||
|
||||
// Changelog
|
||||
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||
|
||||
// Database
|
||||
implementation 'androidx.sqlite:sqlite:2.1.0'
|
||||
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
|
||||
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
|
||||
implementation 'io.requery:sqlite-android:3.31.0'
|
||||
implementation 'io.requery:sqlite-android:3.32.2'
|
||||
|
||||
// Preferences
|
||||
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
||||
|
||||
// Model View Presenter
|
||||
@ -221,17 +211,18 @@ dependencies {
|
||||
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
||||
|
||||
// Image library
|
||||
final glide_version = '4.10.0'
|
||||
final glide_version = '4.11.0'
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
|
||||
implementation 'com.github.tachiyomiorg:subsampling-scale-image-view:bff2806'
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
// Crash reports
|
||||
final acra_version = '5.5.0'
|
||||
implementation "ch.acra:acra-http:$acra_version"
|
||||
implementation 'ch.acra:acra-http:5.7.0'
|
||||
|
||||
// Sort
|
||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||
@ -258,16 +249,21 @@ dependencies {
|
||||
implementation("com.bluelinelabs:conductor-support:2.1.5") {
|
||||
exclude group: "com.android.support"
|
||||
}
|
||||
implementation 'com.github.inorichi:conductor-support-preference:a32c357'
|
||||
implementation 'com.github.tachiyomiorg:conductor-support-preference:1.1.1'
|
||||
|
||||
// FlowBinding
|
||||
final flowbinding_version = '0.11.1'
|
||||
final flowbinding_version = '0.12.0'
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-android:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
||||
|
||||
// Licenses
|
||||
final aboutlibraries_version = '8.3.0'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$aboutlibraries_version"
|
||||
implementation "com.mikepenz:aboutlibraries:$aboutlibraries_version"
|
||||
|
||||
// Tests
|
||||
testImplementation 'junit:junit:4.13'
|
||||
testImplementation 'org.assertj:assertj-core:3.12.2'
|
||||
@ -279,64 +275,18 @@ dependencies {
|
||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
final coroutines_version = '1.3.7'
|
||||
// Do not update until we bump to Kotlin 1.4, see https://github.com/Kotlin/kotlinx.coroutines/issues/2049
|
||||
final coroutines_version = '1.3.6'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version"
|
||||
|
||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||
|
||||
// Text distance (EH)
|
||||
implementation 'info.debatty:java-string-similarity:1.2.1'
|
||||
|
||||
// Pin lock view (EH)
|
||||
implementation 'com.github.jawnnypoo:pinlockview:2.2.0'
|
||||
|
||||
// Reprint (EH)
|
||||
implementation 'com.github.ajalt.reprint:core:3.2.1@aar'
|
||||
implementation 'com.github.ajalt.reprint:rxjava:3.2.1@aar' // optional: the RxJava 1 interface
|
||||
|
||||
// Swirl (EH)
|
||||
implementation 'com.mattprecious.swirl:swirl:1.2.0'
|
||||
|
||||
// RxJava 2 interop for Realm (EH)
|
||||
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
|
||||
|
||||
// Firebase (EH)
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||
|
||||
// Better logging (EH)
|
||||
implementation 'com.elvishew:xlog:1.6.1'
|
||||
|
||||
// Time utils (EH)
|
||||
def typed_time_version = '1.0.2'
|
||||
implementation "com.github.kizitonwose.time:time:$typed_time_version"
|
||||
implementation "com.github.kizitonwose.time:time-android:$typed_time_version"
|
||||
|
||||
// Debug utils (EH)
|
||||
debugImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseTestImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
testImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
|
||||
// Humanize (EH)
|
||||
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
||||
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
||||
final def markwon_version = '4.1.0'
|
||||
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
|
||||
implementation "io.noties.markwon:ext-tables:$markwon_version"
|
||||
implementation "io.noties.markwon:html:$markwon_version"
|
||||
implementation "io.noties.markwon:image:$markwon_version"
|
||||
implementation "io.noties.markwon:linkify:$markwon_version"
|
||||
|
||||
implementation 'com.google.guava:guava:27.0.1-android'
|
||||
// Debug tool; see https://fbflipper.com/
|
||||
// debugImplementation 'com.facebook.flipper:flipper:0.50.0'
|
||||
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
@ -355,7 +305,7 @@ repositories {
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
||||
tasks.withType(AbstractKotlinCompile).all {
|
||||
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
|
||||
kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.Experimental"]
|
||||
}
|
||||
|
||||
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||
@ -369,6 +319,4 @@ preBuild.dependsOn(ktlintFormat, copyResources)
|
||||
|
||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
// Firebase (EH)
|
||||
apply plugin: 'io.fabric'
|
||||
}
|
||||
|
195
app/proguard-rules.pro
vendored
195
app/proguard-rules.pro
vendored
@ -11,13 +11,6 @@
|
||||
-keep class com.github.salomonbrys.kotson.** { *; }
|
||||
-keep class com.squareup.duktape.** { *; }
|
||||
|
||||
# === Keep EH classes
|
||||
-keep class exh.** { *; }
|
||||
-keep class xyz.nulldev.** { *; }
|
||||
|
||||
# === Keep RxAndroid, https://github.com/ReactiveX/RxAndroid/issues/350
|
||||
-keep class rx.android.** { *; }
|
||||
|
||||
# Design library
|
||||
-dontwarn com.google.android.material.**
|
||||
-keep class com.google.android.material.** { *; }
|
||||
@ -30,6 +23,20 @@
|
||||
<init>();
|
||||
}
|
||||
|
||||
# OkHttp
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
|
||||
# Glide specific rules #
|
||||
# https://github.com/bumptech/glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# RxJava 1.1.0
|
||||
-dontwarn sun.misc.**
|
||||
@ -47,186 +54,20 @@
|
||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||
}
|
||||
|
||||
-dontnote rx.internal.util.PlatformDependent
|
||||
# ReactiveNetwork
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
||||
|
||||
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
-dontwarn io.reactivex.functions.Function
|
||||
-dontwarn rx.internal.util.**
|
||||
-dontwarn sun.misc.Unsafe
|
||||
## GSON ##
|
||||
|
||||
# === Okhttp: https://github.com/square/okhttp/blob/3637fc56f70f87da696847defd311dbfb28e87b5/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
||||
# JSR 305 annotations are for embedding nullability information.
|
||||
-dontwarn javax.annotation.**
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
|
||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||
|
||||
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
||||
-dontwarn okio.**
|
||||
|
||||
# === GSON: https://raw.githubusercontent.com/google/gson/master/examples/android-proguard-example/proguard.cfg
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# For using GSON @Expose annotation
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc.**
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# == Nucleus
|
||||
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||
<init>();
|
||||
}
|
||||
|
||||
# TODO Changeloglib? Does it need proguard?
|
||||
|
||||
# === Injekt
|
||||
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
||||
-keep class uy.kohesive.injekt.** { *; }
|
||||
|
||||
|
||||
# === Glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||
|
||||
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
|
||||
-dontwarn jp.co.cyberagent.android.gpuimage.**
|
||||
|
||||
# === Conductor
|
||||
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
# === RxBinding
|
||||
-dontwarn com.google.auto.value.AutoValue
|
||||
|
||||
# === Crashlytics
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.crashlytics.** { *; }
|
||||
-dontwarn com.crashlytics.**
|
||||
|
||||
# === Humanize + Guava: https://github.com/google/guava/wiki/UsingProGuardWithGuava
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-keep class org.ocpsoft.prettytime.i18n.**
|
||||
|
||||
# Note: We intentionally don't add the flags we'd need to make Enums work.
|
||||
# That's because the Proguard configuration required to make it work on
|
||||
# optimized code would preclude lots of optimization, like converting enums
|
||||
# into ints.
|
||||
|
||||
# Throwables uses internal APIs for lazy stack trace resolution
|
||||
-dontnote sun.misc.SharedSecrets
|
||||
-keep class sun.misc.SharedSecrets {
|
||||
*** getJavaLangAccess(...);
|
||||
}
|
||||
-dontnote sun.misc.JavaLangAccess
|
||||
-keep class sun.misc.JavaLangAccess {
|
||||
*** getStackTraceElement(...);
|
||||
*** getStackTraceDepth(...);
|
||||
}
|
||||
|
||||
# FinalizableReferenceQueue calls this reflectively
|
||||
# Proguard is intelligent enough to spot the use of reflection onto this, so we
|
||||
# only need to keep the names, and allow it to be stripped out if
|
||||
# FinalizableReferenceQueue is unused.
|
||||
-keepnames class com.google.common.base.internal.Finalizer {
|
||||
*** startFinalizer(...);
|
||||
}
|
||||
# However, it cannot "spot" that this method needs to be kept IF the class is.
|
||||
-keepclassmembers class com.google.common.base.internal.Finalizer {
|
||||
*** startFinalizer(...);
|
||||
}
|
||||
-keepnames class com.google.common.base.FinalizableReference {
|
||||
void finalizeReferent();
|
||||
}
|
||||
-keepclassmembers class com.google.common.base.FinalizableReference {
|
||||
void finalizeReferent();
|
||||
}
|
||||
|
||||
# Striped64, LittleEndianByteArray, UnsignedBytes, AbstractFuture
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
# Striped64 appears to make some assumptions about object layout that
|
||||
# really might not be safe. This should be investigated.
|
||||
-keepclassmembers class com.google.common.cache.Striped64 {
|
||||
*** base;
|
||||
*** busy;
|
||||
}
|
||||
-keepclassmembers class com.google.common.cache.Striped64$Cell {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
-dontwarn java.lang.SafeVarargs
|
||||
|
||||
-keep class java.lang.Throwable {
|
||||
*** addSuppressed(...);
|
||||
}
|
||||
|
||||
# Futures.getChecked, in both of its variants, is incompatible with proguard.
|
||||
|
||||
# Used by AtomicReferenceFieldUpdater and sun.misc.Unsafe
|
||||
-keepclassmembers class com.google.common.util.concurrent.AbstractFuture** {
|
||||
*** waiters;
|
||||
*** value;
|
||||
*** listeners;
|
||||
*** thread;
|
||||
*** next;
|
||||
}
|
||||
-keepclassmembers class com.google.common.util.concurrent.AtomicDouble {
|
||||
*** value;
|
||||
}
|
||||
-keepclassmembers class com.google.common.util.concurrent.AggregateFutureState {
|
||||
*** remaining;
|
||||
*** seenExceptions;
|
||||
}
|
||||
|
||||
# Since Unsafe is using the field offsets of these inner classes, we don't want
|
||||
# to have class merging or similar tricks applied to these classes and their
|
||||
# fields. It's safe to allow obfuscation, since the by-name references are
|
||||
# already preserved in the -keep statement above.
|
||||
-keep,allowshrinking,allowobfuscation class com.google.common.util.concurrent.AbstractFuture** {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
# Futures.getChecked (which often won't work with Proguard anyway) uses this. It
|
||||
# has a fallback, but again, don't use Futures.getChecked on Android regardless.
|
||||
-dontwarn java.lang.ClassValue
|
||||
|
||||
# MoreExecutors references AppEngine
|
||||
-dontnote com.google.appengine.api.ThreadManager
|
||||
-keep class com.google.appengine.api.ThreadManager {
|
||||
static *** currentRequestThreadFactory(...);
|
||||
}
|
||||
-dontnote com.google.apphosting.api.ApiProxy
|
||||
-keep class com.google.apphosting.api.ApiProxy {
|
||||
static *** getCurrentEnvironment (...);
|
||||
}
|
||||
|
@ -16,82 +16,19 @@
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#607D8B"/>
|
||||
<path
|
||||
android:name="path_3"
|
||||
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||
android:fillColor="#000"
|
||||
android:fillType="evenOdd"/>
|
||||
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:fillType="evenOdd"
|
||||
android:fillColor="#000"/>
|
||||
<path
|
||||
android:name="path_4"
|
||||
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||
android:fillColor="#CE2828"
|
||||
android:fillType="evenOdd"/>
|
||||
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:fillType="evenOdd"
|
||||
android:fillColor="#CE2828"/>
|
||||
<path
|
||||
android:name="path_5"
|
||||
android:pathData="M 54 54.5 M 34.06 54.5 C 33.964 50.23 35.243 46.04 37.707 42.551 C 40.171 39.062 43.692 36.455 47.748 35.117 C 51.805 33.779 56.185 33.779 60.242 35.117 C 64.298 36.455 67.819 39.062 70.283 42.551 C 72.747 46.04 74.026 50.23 73.93 54.5 C 74.026 58.77 72.747 62.96 70.283 66.449 C 67.819 69.938 64.298 72.545 60.242 73.883 C 56.185 75.221 51.805 75.221 47.748 73.883 C 43.692 72.545 40.171 69.938 37.707 66.449 C 35.243 62.96 33.964 58.77 34.06 54.5"
|
||||
android:fillColor="#FFF"
|
||||
android:fillType="evenOdd"/>
|
||||
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:fillType="evenOdd"
|
||||
android:fillColor="#FFF"/>
|
||||
<path
|
||||
android:name="path_6"
|
||||
android:pathData="M 54.174 36.266 C 64.147 36.266 72.234 44.397 72.234 54.426 C 72.234 64.459 64.147 72.593 54.174 72.593 C 44.197 72.593 36.113 64.459 36.113 54.426 C 36.113 44.397 44.197 36.266 54.174 36.266 Z"
|
||||
android:fillColor="#ffcc4d"
|
||||
android:strokeColor="#ffcc4d"
|
||||
android:strokeWidth="4.628571428571428"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_7"
|
||||
android:pathData="M 48.774 45.158 C 49.988 45.158 50.973 46.452 50.973 48.05 C 50.973 49.65 49.988 50.946 48.774 50.946 C 47.559 50.946 46.576 49.65 46.576 48.05 C 46.576 46.452 47.559 45.158 48.774 45.158 Z"
|
||||
android:fillColor="#674600"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="0.1"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_8"
|
||||
android:pathData="M 62.02 45.158 C 63.235 45.158 64.219 46.452 64.219 48.05 C 64.219 49.65 63.235 50.946 62.02 50.946 C 60.805 50.946 59.821 49.65 59.821 48.05 C 59.821 46.452 60.805 45.158 62.02 45.158 Z"
|
||||
android:fillColor="#674600"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="0.1"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_9"
|
||||
android:pathData="M 44.687 42.102 C 44.687 42.102 48.404 40.049 53.119 42.102"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_10"
|
||||
android:pathData="M 58.035 42.102 C 58.035 42.102 61.751 40.049 66.465 42.102"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_11"
|
||||
android:pathData="M 49.191 56.685 C 49.191 56.685 51.703 59.685 56.992 59.685"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_12"
|
||||
android:pathData="M 34.072 67.511 C 34.072 67.511 34.524 66.248 37.476 63.634 C 37.476 63.634 39.143 61.991 37.876 58.114 C 37.876 58.114 37.534 56.06 39.681 57.255 C 39.681 57.255 44.129 60.46 40.871 65 C 40.871 65 40.241 66.117 41.652 65.78 L 53.939 63.103 C 53.939 63.103 55.275 62.752 55.535 64.163 C 55.535 64.163 55.696 65.372 54.641 65.672 L 47.742 67.378 C 47.742 67.378 50.72 69.597 47.742 70.825 C 47.742 70.825 50.294 72.779 47.336 74.268 C 47.336 74.268 49.276 76.015 46.407 77.019 C 46.407 77.019 42.525 78.474 39.209 77.831 C 35.509 77.103 31.947 72.114 34.072 67.511 Z"
|
||||
android:fillColor="#f4900c"
|
||||
android:strokeColor="#f4900c"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.1"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_13"
|
||||
android:pathData="M 56.182 67.511 C 56.182 67.511 56.633 66.248 59.585 63.634 C 59.585 63.634 61.253 61.991 59.985 58.114 C 59.985 58.114 59.642 56.06 61.789 57.255 C 61.789 57.255 66.239 60.46 62.98 65 C 62.98 65 62.351 66.117 63.761 65.78 L 76.066 63.103 C 76.066 63.103 77.386 62.752 77.641 64.163 C 77.641 64.163 77.812 65.372 76.746 65.672 L 69.85 67.378 C 69.85 67.378 72.829 69.597 69.85 70.825 C 69.85 70.825 72.404 72.779 69.446 74.268 C 69.446 74.268 71.387 76.015 68.516 77.019 C 68.516 77.019 64.633 78.474 61.317 77.831 C 57.618 77.103 54.057 72.114 56.182 67.511 Z"
|
||||
android:fillColor="#f4900c"
|
||||
android:strokeColor="#f4900c"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.1"
|
||||
android:fillType="evenOdd"/>
|
||||
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:fillType="evenOdd"
|
||||
android:fillColor="#000"/>
|
||||
</vector>
|
||||
|
@ -1,6 +0,0 @@
|
||||
<vector android:height="100dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 -5,5z"/>
|
||||
</vector>
|
155
app/src/main/AndroidManifest.xml
Executable file → Normal file
155
app/src/main/AndroidManifest.xml
Executable file → Normal file
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.kanade.tachiyomi">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@ -13,14 +12,6 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<!-- Lock vibrate -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -33,7 +24,6 @@
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/Theme.Tachiyomi.Light"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
@ -49,11 +39,6 @@
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.main.ForceCloseActivity"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkActivity"
|
||||
android:launchMode="singleTask"
|
||||
@ -133,13 +118,6 @@
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
android:theme="@style/Theme.MaterialComponents" />
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||
android:theme="@style/Theme.MaterialComponents" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@ -174,139 +152,6 @@
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- EH -->
|
||||
<service
|
||||
android:name="exh.eh.EHentaiUpdateWorker"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name="exh.ui.intercept.InterceptActivity"
|
||||
android:label="TachiyomiEH"
|
||||
android:theme="@style/Theme.EHActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<!-- EH -->
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- EXH -->
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- nhentai -->
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Perv Eden -->
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Hentai Cafe -->
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPattern="/.*/.*"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPattern="/.*/.*"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Tsumino -->
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Hitomi.la -->
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Pururin.io -->
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- HBrowse -->
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:pathPrefix="/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:pathPrefix="/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||
android:theme="@style/Theme.EHActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
0
app/src/main/ic_launcher-web.png
Executable file → Normal file
0
app/src/main/ic_launcher-web.png
Executable file → Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
183
app/src/main/java/eu/kanade/tachiyomi/App.kt
Executable file → Normal file
183
app/src/main/java/eu/kanade/tachiyomi/App.kt
Executable file → Normal file
@ -3,79 +3,61 @@ package eu.kanade.tachiyomi
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDex
|
||||
import com.elvishew.xlog.LogConfiguration
|
||||
import com.elvishew.xlog.LogLevel
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.elvishew.xlog.printer.AndroidPrinter
|
||||
import com.elvishew.xlog.printer.Printer
|
||||
import com.elvishew.xlog.printer.file.FilePrinter
|
||||
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
|
||||
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.kizitonwose.time.days
|
||||
import com.ms_square.debugoverlay.DebugOverlay
|
||||
import com.ms_square.debugoverlay.modules.FpsModule
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.main.ForceCloseActivity
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.debug.DebugToggles
|
||||
import exh.log.CrashlyticsPrinter
|
||||
import exh.log.EHDebugModeOverlay
|
||||
import exh.log.EHLogLevel
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.io.File
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.net.ssl.SSLContext
|
||||
import kotlin.concurrent.thread
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.security.Security
|
||||
import org.acra.ACRA
|
||||
import org.acra.annotation.AcraCore
|
||||
import org.acra.annotation.AcraHttpSender
|
||||
import org.acra.sender.HttpSender
|
||||
import org.conscrypt.Conscrypt
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.InjektScope
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
|
||||
@AcraCore(
|
||||
buildConfigClass = BuildConfig::class,
|
||||
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
|
||||
)
|
||||
@AcraHttpSender(
|
||||
uri = "https://tachiyomi.kanade.eu/crash_report",
|
||||
httpMethod = HttpSender.Method.PUT
|
||||
)
|
||||
open class App : Application(), LifecycleObserver {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||
setupExhLogging() // EXH logging
|
||||
|
||||
workaroundAndroid7BrokenSSL()
|
||||
// Debug tool; see https://fbflipper.com/
|
||||
// SoLoader.init(this, false)
|
||||
// if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
|
||||
// val client = AndroidFlipperClient.getInstance(this)
|
||||
// client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
|
||||
// client.addPlugin(DatabasesFlipperPlugin(this))
|
||||
// client.start()
|
||||
// }
|
||||
|
||||
// Enforce WebView availability
|
||||
if (!WebViewUtil.supportsWebView(this)) {
|
||||
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||
ForceCloseActivity.closeApp(this)
|
||||
// TLS 1.3 support for Android < 10
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
}
|
||||
|
||||
Injekt = InjektScope(DefaultRegistrar())
|
||||
Injekt.importModule(AppModule(this))
|
||||
|
||||
setupAcra()
|
||||
setupNotificationChannels()
|
||||
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
|
||||
// Reprint.initialize(this) //Setup fingerprint (EH)
|
||||
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
||||
setupDebugOverlay()
|
||||
}
|
||||
|
||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||
|
||||
@ -92,26 +74,6 @@ open class App : Application(), LifecycleObserver {
|
||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
||||
}
|
||||
|
||||
private fun workaroundAndroid7BrokenSSL() {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N ||
|
||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
|
||||
) {
|
||||
try {
|
||||
SSLContext.getInstance("TLSv1.2")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
}
|
||||
|
||||
try {
|
||||
ProviderInstaller.installIfNeeded(applicationContext)
|
||||
} catch (e: GooglePlayServicesRepairableException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
@Suppress("unused")
|
||||
fun onAppBackgrounded() {
|
||||
@ -121,96 +83,11 @@ open class App : Application(), LifecycleObserver {
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun setupAcra() {
|
||||
ACRA.init(this)
|
||||
}
|
||||
|
||||
protected open fun setupNotificationChannels() {
|
||||
Notifications.createChannels(this)
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun deleteOldMetadataRealm() {
|
||||
Realm.init(this)
|
||||
val config = RealmConfiguration.Builder()
|
||||
.name("gallery-metadata.realm")
|
||||
.schemaVersion(3)
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
.build()
|
||||
Realm.deleteRealm(config)
|
||||
|
||||
// Delete old paper db files
|
||||
listOf(
|
||||
File(filesDir, "gallery-ex"),
|
||||
File(filesDir, "gallery-perveden"),
|
||||
File(filesDir, "gallery-nhentai")
|
||||
).forEach {
|
||||
if (it.exists()) {
|
||||
thread {
|
||||
it.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun setupExhLogging() {
|
||||
EHLogLevel.init(this)
|
||||
|
||||
val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) {
|
||||
LogLevel.ALL
|
||||
} else {
|
||||
LogLevel.WARN
|
||||
}
|
||||
|
||||
val logConfig = LogConfiguration.Builder()
|
||||
.logLevel(logLevel)
|
||||
.t()
|
||||
.st(2)
|
||||
.nb()
|
||||
.build()
|
||||
|
||||
val printers = mutableListOf<Printer>(AndroidPrinter())
|
||||
|
||||
val logFolder = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
getString(R.string.app_name),
|
||||
"logs"
|
||||
)
|
||||
|
||||
printers += FilePrinter
|
||||
.Builder(logFolder.absolutePath)
|
||||
.fileNameGenerator(object : DateFileNameGenerator() {
|
||||
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}"
|
||||
}
|
||||
})
|
||||
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue))
|
||||
.backupStrategy(NeverBackupStrategy())
|
||||
.build()
|
||||
|
||||
// Install Crashlytics in prod
|
||||
if (!BuildConfig.DEBUG) {
|
||||
printers += CrashlyticsPrinter(LogLevel.ERROR)
|
||||
}
|
||||
|
||||
XLog.init(
|
||||
logConfig,
|
||||
*printers.toTypedArray()
|
||||
)
|
||||
|
||||
XLog.d("Application booting...")
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun setupDebugOverlay() {
|
||||
try {
|
||||
DebugOverlay.Builder(this)
|
||||
.modules(FpsModule(), EHDebugModeOverlay(this))
|
||||
.bgColor(Color.parseColor("#7F000000"))
|
||||
.notification(false)
|
||||
.allowSystemLayer(false)
|
||||
.build()
|
||||
.install()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Crashes if app is in background
|
||||
XLog.e("Failed to initialize debug overlay, app in background?", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Executable file → Normal file
6
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Executable file → Normal file
@ -11,8 +11,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import exh.eh.EHentaiUpdateHelper
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
@ -46,10 +44,6 @@ class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
addSingletonFactory { Gson() }
|
||||
|
||||
addSingletonFactory { EHentaiUpdateHelper(app) }
|
||||
|
||||
addSingletonFactory { Markwon.create(app) }
|
||||
|
||||
// Asynchronously init expensive components for a faster cold start
|
||||
|
||||
GlobalScope.launch { get<PreferencesHelper>() }
|
||||
|
@ -10,8 +10,6 @@ import java.io.File
|
||||
|
||||
object Migrations {
|
||||
|
||||
// TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS
|
||||
|
||||
/**
|
||||
* Performs a migration when the application is updated.
|
||||
*
|
||||
|
@ -7,6 +7,7 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
@ -106,7 +107,7 @@ class BackupCreateService : Service() {
|
||||
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
|
||||
backupManager = BackupManager(this)
|
||||
|
||||
val backupFileUri = Uri.parse(backupManager.createBackup(uri, backupFlags, false))
|
||||
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
|
||||
val unifile = UniFile.fromUri(this, backupFileUri)
|
||||
notifier.showBackupComplete(unifile)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.backup
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
@ -18,7 +18,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
override fun doWork(): Result {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
val backupManager = BackupManager(context)
|
||||
val uri = Uri.parse(preferences.backupsDirectory().get())
|
||||
val uri = preferences.backupsDirectory().get().toUri()
|
||||
val flags = BackupCreateService.BACKUP_ALL
|
||||
return try {
|
||||
backupManager.createBackup(uri, flags, true)
|
||||
|
38
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Executable file → Normal file
38
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Executable file → Normal file
@ -49,9 +49,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import kotlin.math.max
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
@ -299,23 +297,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
* @param manga manga that needs updating
|
||||
* @return [Observable] that contains manga
|
||||
*/
|
||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return (
|
||||
if (source is EHentai) {
|
||||
source.fetchChapterList(manga, throttleManager::throttle)
|
||||
} else {
|
||||
source.fetchChapterList(manga)
|
||||
}
|
||||
).map {
|
||||
if (it.last().chapter_number == -99F) {
|
||||
chapters.forEach { chapter ->
|
||||
chapter.name = "Chapter ${chapter.chapter_number} restored by dummy source"
|
||||
}
|
||||
syncChaptersWithSource(databaseHelper, chapters, manga, source)
|
||||
} else {
|
||||
syncChaptersWithSource(databaseHelper, it, manga, source)
|
||||
}
|
||||
}
|
||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return source.fetchChapterList(manga)
|
||||
.map { syncChaptersWithSource(databaseHelper, it, manga, source) }
|
||||
.doOnNext { pair ->
|
||||
if (pair.first.isNotEmpty()) {
|
||||
chapters.forEach { it.manga_id = manga.id }
|
||||
@ -341,7 +325,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
for (dbCategory in dbCategories) {
|
||||
// If the category is already in the db, assign the id to the file's category
|
||||
// and do nothing
|
||||
if (category.nameLower == dbCategory.nameLower) {
|
||||
if (category.name == dbCategory.name) {
|
||||
category.id = dbCategory.id
|
||||
found = true
|
||||
break
|
||||
@ -366,10 +350,10 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
*/
|
||||
internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
|
||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
||||
val mangaCategoriesToUpdate = ArrayList<MangaCategory>()
|
||||
val mangaCategoriesToUpdate = mutableListOf<MangaCategory>()
|
||||
for (backupCategoryStr in categories) {
|
||||
for (dbCategory in dbCategories) {
|
||||
if (backupCategoryStr.toLowerCase() == dbCategory.nameLower) {
|
||||
if (backupCategoryStr == dbCategory.name) {
|
||||
mangaCategoriesToUpdate.add(MangaCategory.create(manga, dbCategory))
|
||||
break
|
||||
}
|
||||
@ -378,9 +362,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
// Update database
|
||||
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
||||
val mangaAsList = ArrayList<Manga>()
|
||||
mangaAsList.add(manga)
|
||||
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
||||
databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
|
||||
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
@ -392,7 +374,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
*/
|
||||
internal fun restoreHistoryForManga(history: List<DHistory>) {
|
||||
// List containing history to be updated
|
||||
val historyToBeUpdated = ArrayList<History>()
|
||||
val historyToBeUpdated = mutableListOf<History>()
|
||||
for ((url, lastRead) in history) {
|
||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
||||
// Check if history already in database and update
|
||||
@ -426,9 +408,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
// Get tracks from database
|
||||
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
||||
val trackToUpdate = ArrayList<Track>()
|
||||
val trackToUpdate = mutableListOf<Track>()
|
||||
|
||||
for (track in tracks) {
|
||||
tracks.forEach { track ->
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged) {
|
||||
var isInDatabase = false
|
||||
|
@ -34,8 +34,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.EXHMigrations
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@ -102,8 +100,6 @@ class BackupRestoreService : Service() {
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
private val throttleManager = EHentaiThrottleManager()
|
||||
|
||||
/**
|
||||
* The progress of a backup restore
|
||||
*/
|
||||
@ -114,10 +110,6 @@ class BackupRestoreService : Service() {
|
||||
*/
|
||||
private var restoreAmount = 0
|
||||
|
||||
private var skippedAmount = 0
|
||||
|
||||
private var totalAmount = 0
|
||||
|
||||
/**
|
||||
* Mapping of source ID to source name from backup data
|
||||
*/
|
||||
@ -177,8 +169,6 @@ class BackupRestoreService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||
|
||||
throttleManager.resetThrottle()
|
||||
|
||||
// Cancel any previous job if needed.
|
||||
job?.cancel()
|
||||
val handler = CoroutineExceptionHandler { _, exception ->
|
||||
@ -220,25 +210,12 @@ class BackupRestoreService : Service() {
|
||||
|
||||
val mangasJson = json.get(MANGAS).asJsonArray
|
||||
|
||||
val validManga = mangasJson.filter {
|
||||
var manga = backupManager.parser.fromJson<MangaImpl>(it.asJsonObject.get(MANGA))
|
||||
// EXH -->
|
||||
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||
val sourced = backupManager.sourceManager.get(manga.source) != null
|
||||
if (!sourced) {
|
||||
restoreAmount -= 1
|
||||
}
|
||||
sourced
|
||||
}
|
||||
|
||||
totalAmount = mangasJson.size()
|
||||
restoreAmount = validManga.count() + 1 // +1 for categories
|
||||
skippedAmount = mangasJson.size() - validManga.count()
|
||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
||||
restoreProgress = 0
|
||||
errors.clear()
|
||||
|
||||
// Restore categories
|
||||
restoreCategories(json.get(CATEGORIES))
|
||||
json.get(CATEGORIES)?.let { restoreCategories(it) }
|
||||
|
||||
// Store source mapping for error messages
|
||||
sourceMapping = BackupRestoreValidator.getSourceMapping(json)
|
||||
@ -271,7 +248,7 @@ class BackupRestoreService : Service() {
|
||||
}
|
||||
|
||||
private fun restoreManga(mangaJson: JsonObject) {
|
||||
var manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||
val manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||
mangaJson.get(CHAPTERS)
|
||||
?: JsonArray()
|
||||
@ -289,22 +266,13 @@ class BackupRestoreService : Service() {
|
||||
?: JsonArray()
|
||||
)
|
||||
|
||||
// EXH -->
|
||||
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||
// <-- EXH
|
||||
|
||||
try {
|
||||
val source = backupManager.sourceManager.get(manga.source)
|
||||
if (source != null) {
|
||||
restoreMangaData(manga, source, chapters, categories, history, tracks)
|
||||
} else {
|
||||
val message = if (manga.source in sourceMapping) {
|
||||
getString(R.string.source_not_found_name, sourceMapping[manga.source])
|
||||
} else {
|
||||
getString(R.string.source_not_found)
|
||||
}
|
||||
|
||||
errors.add(Date() to "${manga.title} - $message")
|
||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found_name, sourceName)}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
errors.add(Date() to "${manga.title} - ${e.message}")
|
||||
@ -427,7 +395,7 @@ class BackupRestoreService : Service() {
|
||||
* @return [Observable] that contains manga
|
||||
*/
|
||||
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters, throttleManager)
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||
|
47
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
vendored
Executable file → Normal file
47
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
vendored
Executable file → Normal file
@ -6,17 +6,11 @@ import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import com.jakewharton.disklrucache.DiskLruCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import okhttp3.Response
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
@ -43,32 +37,21 @@ class ChapterCache(private val context: Context) {
|
||||
|
||||
/** The number of values per cache entry. Must be positive. */
|
||||
const val PARAMETER_VALUE_COUNT = 1
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
/** The maximum number of bytes this cache should use to store. */
|
||||
const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024
|
||||
}
|
||||
|
||||
/** Google Json class used for parsing JSON files. */
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
// --> EH
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
// <-- EH
|
||||
|
||||
/** Cache class used for cache management. */
|
||||
// --> EH
|
||||
private var diskCache = setupDiskCache(prefs.eh_cacheSize().get().toLong())
|
||||
|
||||
init {
|
||||
prefs.eh_cacheSize().asFlow()
|
||||
.onEach {
|
||||
// Save old cache for destruction later
|
||||
val oldCache = diskCache
|
||||
diskCache = setupDiskCache(it.toLong())
|
||||
oldCache.close()
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
// <-- EH
|
||||
private val diskCache = DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
PARAMETER_CACHE_SIZE
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns directory of cache.
|
||||
@ -88,18 +71,6 @@ class ChapterCache(private val context: Context) {
|
||||
val readableSize: String
|
||||
get() = Formatter.formatFileSize(context, realSize)
|
||||
|
||||
// --> EH
|
||||
// Cache size is in MB
|
||||
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||
return DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
cacheSize * 1024 * 1024
|
||||
)
|
||||
}
|
||||
// <-- EH
|
||||
|
||||
/**
|
||||
* Remove file from cache.
|
||||
*
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Executable file → Normal file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
Executable file → Normal file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
Executable file → Normal file
@ -21,22 +21,13 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||
import exh.metadata.sql.mappers.SearchMetadataTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTagTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTitleTypeMapping
|
||||
import exh.metadata.sql.models.SearchMetadata
|
||||
import exh.metadata.sql.models.SearchTag
|
||||
import exh.metadata.sql.models.SearchTitle
|
||||
import exh.metadata.sql.queries.SearchMetadataQueries
|
||||
import exh.metadata.sql.queries.SearchTagQueries
|
||||
import exh.metadata.sql.queries.SearchTitleQueries
|
||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
|
||||
/**
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries, /* EXH --> */ SearchMetadataQueries, SearchTagQueries, SearchTitleQueries /* EXH <-- */ {
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
@ -51,14 +42,7 @@ open class DatabaseHelper(context: Context) :
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
// EXH -->
|
||||
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||
// EXH <--
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
|
||||
fun lowLevel() = db.lowLevel()
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
Executable file → Normal file
64
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Executable file → Normal file
64
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Executable file → Normal file
@ -7,11 +7,7 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
import exh.metadata.sql.tables.SearchTagTable
|
||||
import exh.metadata.sql.tables.SearchTitleTable
|
||||
|
||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
|
||||
@ -24,7 +20,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = 2 // [SY]
|
||||
const val DATABASE_VERSION = 11
|
||||
}
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||
@ -34,14 +30,6 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(CategoryTable.createTableQuery)
|
||||
execSQL(MangaCategoryTable.createTableQuery)
|
||||
execSQL(HistoryTable.createTableQuery)
|
||||
// EXH -->
|
||||
execSQL(SearchMetadataTable.createTableQuery)
|
||||
execSQL(SearchTagTable.createTableQuery)
|
||||
execSQL(SearchTitleTable.createTableQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
execSQL(MergedTable.createTableQuery)
|
||||
// AZ <--
|
||||
|
||||
// DB indexes
|
||||
execSQL(MangaTable.createUrlIndexQuery)
|
||||
@ -49,23 +37,51 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
// EXH -->
|
||||
db.execSQL(SearchMetadataTable.createUploaderIndexQuery)
|
||||
db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
|
||||
db.execSQL(SearchTagTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTagTable.createNamespaceNameIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
execSQL(MergedTable.createIndexQuery)
|
||||
// AZ <--
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||
|
||||
// Fix kissmanga covers after supporting cloudflare
|
||||
db.execSQL(
|
||||
"""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
|
||||
)
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
db.execSQL(HistoryTable.createTableQuery)
|
||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(ChapterTable.bookmarkUpdateQuery)
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.execSQL(ChapterTable.addScanlator)
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
db.execSQL(TrackTable.addTrackingUrl)
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
db.execSQL(TrackTable.addLibraryId)
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
db.execSQL("DROP INDEX IF EXISTS mangas_favorite_index")
|
||||
db.execSQL(MangaTable.createLibraryIndexQuery)
|
||||
db.execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
db.execSQL(TrackTable.addStartDate)
|
||||
db.execSQL(TrackTable.addFinishDate)
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
db.execSQL(MangaTable.addCoverLastModified)
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
db.execSQL(MangaTable.addDateAdded)
|
||||
db.execSQL(MangaTable.backfillDateAdded)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
Executable file → Normal file
6
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
Executable file → Normal file
6
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
Executable file → Normal file
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
||||
@ -41,8 +40,6 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
|
||||
put(COL_NAME, obj.name)
|
||||
put(COL_ORDER, obj.order)
|
||||
put(COL_FLAGS, obj.flags)
|
||||
val orderString = obj.mangaOrder.joinToString("/")
|
||||
put(COL_MANGA_ORDER, orderString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,9 +50,6 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
|
||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
||||
|
||||
val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER))
|
||||
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Executable file → Normal file
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
||||
@ -47,7 +48,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
||||
override fun mapToContentValues(obj: Manga) = ContentValues(17).apply {
|
||||
put(COL_ID, obj.id)
|
||||
put(COL_SOURCE, obj.source)
|
||||
put(COL_URL, obj.url)
|
||||
@ -64,6 +65,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||
put(COL_VIEWER, obj.viewer)
|
||||
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
||||
put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified)
|
||||
put(COL_DATE_ADDED, obj.date_added)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +87,7 @@ interface BaseMangaGetResolver {
|
||||
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
|
||||
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
|
||||
}
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Executable file → Normal file
@ -12,11 +12,6 @@ interface Category : Serializable {
|
||||
|
||||
var flags: Int
|
||||
|
||||
var mangaOrder: List<Long>
|
||||
|
||||
val nameLower: String
|
||||
get() = name.toLowerCase()
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(name: String): Category = CategoryImpl().apply {
|
||||
|
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Executable file → Normal file
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Executable file → Normal file
@ -10,14 +10,11 @@ class CategoryImpl : Category {
|
||||
|
||||
override var flags: Int = 0
|
||||
|
||||
override var mangaOrder: List<Long> = emptyList()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val category = other as Category
|
||||
|
||||
return name == category.name
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Executable file → Normal file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Executable file → Normal file
@ -31,10 +31,11 @@ class ChapterImpl : Chapter {
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val chapter = other as Chapter
|
||||
return url == chapter.url
|
||||
if (url != chapter.url) return false
|
||||
return id == chapter.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return url.hashCode()
|
||||
return url.hashCode() + id.hashCode()
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
Executable file → Normal file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Executable file → Normal file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Executable file → Normal file
@ -12,6 +12,8 @@ interface Manga : SManga {
|
||||
|
||||
var last_update: Long
|
||||
|
||||
var date_added: Long
|
||||
|
||||
var viewer: Int
|
||||
|
||||
var chapter_flags: Int
|
||||
@ -22,10 +24,6 @@ interface Manga : SManga {
|
||||
setFlags(order, SORT_MASK)
|
||||
}
|
||||
|
||||
private fun setFlags(flag: Int, mask: Int) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
return chapter_flags and SORT_MASK == SORT_DESC
|
||||
}
|
||||
@ -34,6 +32,10 @@ interface Manga : SManga {
|
||||
return genre?.split(", ")?.map { it.trim() }
|
||||
}
|
||||
|
||||
private fun setFlags(flag: Int, mask: Int) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
// Used to display the chapter's title one way or another
|
||||
var displayMode: Int
|
||||
get() = chapter_flags and DISPLAY_MASK
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
Executable file → Normal file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Executable file → Normal file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Executable file → Normal file
@ -8,7 +8,7 @@ open class MangaImpl : Manga {
|
||||
|
||||
override lateinit var url: String
|
||||
|
||||
override var title: String = ""
|
||||
override lateinit var title: String
|
||||
|
||||
override var artist: String? = null
|
||||
|
||||
@ -26,6 +26,8 @@ open class MangaImpl : Manga {
|
||||
|
||||
override var last_update: Long = 0
|
||||
|
||||
override var date_added: Long = 0
|
||||
|
||||
override var initialized: Boolean = false
|
||||
|
||||
override var viewer: Int = 0
|
||||
@ -39,11 +41,11 @@ open class MangaImpl : Manga {
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val manga = other as Manga
|
||||
|
||||
return url == manga.url
|
||||
if (url != manga.url) return false
|
||||
return id == manga.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return url.hashCode()
|
||||
return url.hashCode() + id.hashCode()
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
Executable file → Normal file
26
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
Executable file → Normal file
26
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
Executable file → Normal file
@ -15,24 +15,13 @@ import java.util.Date
|
||||
|
||||
interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
|
||||
|
||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||
fun getChapters(manga: Manga) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedChaptersQuery(mangaId))
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
@ -82,17 +71,6 @@ interface ChapterQueries : DbProvider {
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapters(url: String) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||
|
||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
||||
|
21
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
Executable file → Normal file
21
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
Executable file → Normal file
@ -22,28 +22,11 @@ interface HistoryQueries : DbProvider {
|
||||
* Returns history of recent manga containing last read chapter
|
||||
* @param date recent date range
|
||||
*/
|
||||
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
|
||||
fun getRecentManga(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasQuery(offset, search))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* Returns history of recent manga containing last read chapter in 25s
|
||||
* @param date recent date range
|
||||
* @offset offset the db by
|
||||
*/
|
||||
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasLimitQuery(limit, search))
|
||||
.query(getRecentMangasQuery())
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
|
8
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
Executable file → Normal file
8
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
Executable file → Normal file
@ -26,12 +26,8 @@ interface MangaCategoryQueries : DbProvider {
|
||||
|
||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||
db.inTransaction {
|
||||
mangas.chunked(100) { chunk ->
|
||||
deleteOldMangasCategories(chunk).executeAsBlocking()
|
||||
}
|
||||
mangasCategories.chunked(100) { chunk ->
|
||||
insertMangasCategories(chunk).executeAsBlocking()
|
||||
}
|
||||
deleteOldMangasCategories(mangas).executeAsBlocking()
|
||||
insertMangasCategories(mangasCategories).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Executable file → Normal file
60
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Executable file → Normal file
@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
|
||||
interface MangaQueries : DbProvider {
|
||||
|
||||
@ -75,15 +74,6 @@ interface MangaQueries : DbProvider {
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getMergedMangas(id: Long) = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedMangaQuery(id))
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||
|
||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||
@ -169,54 +159,4 @@ interface MangaQueries : DbProvider {
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getMangaWithMetadata() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getFavoriteMangaWithMetadata() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getIdsOfFavoriteMangaWithMetadata() = db.get()
|
||||
.cursor()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.${MangaTable.COL_ID} FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
63
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Executable file → Normal file
63
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Executable file → Normal file
@ -5,33 +5,6 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
|
||||
|
||||
/**
|
||||
* Query to get the manga merged into a merged manga
|
||||
*/
|
||||
fun getMergedMangaQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
) AS M
|
||||
JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the chapters of all manga in a merged manga
|
||||
*/
|
||||
fun getMergedChaptersQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Chapter.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
) AS M
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the manga from the library, with their categories and unread count.
|
||||
@ -65,7 +38,9 @@ fun getRecentsQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
@ -76,7 +51,7 @@ fun getRecentsQuery() =
|
||||
* and are read after the given time period
|
||||
* @return return limit is 25
|
||||
*/
|
||||
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
|
||||
fun getRecentMangasQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
@ -91,36 +66,8 @@ fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT 25 OFFSET $offset
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recently read chapters of manga from the library up to a date.
|
||||
* The max_last_read table contains the most recent chapters grouped by manga
|
||||
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
||||
* and are read after the given time period
|
||||
*/
|
||||
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
|
||||
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT $limit
|
||||
LIMIT 25
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() =
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
Executable file → Normal file
@ -1,32 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.database.resolvers
|
||||
|
||||
import android.content.ContentValues
|
||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
|
||||
// [EXH]
|
||||
class MangaUrlPutResolver : PutResolver<Manga>() {
|
||||
|
||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||
val updateQuery = mapToUpdateQuery(manga)
|
||||
val contentValues = mapToContentValues(manga)
|
||||
|
||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_URL, manga.url)
|
||||
}
|
||||
}
|
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
Executable file → Normal file
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
Executable file → Normal file
@ -12,18 +12,12 @@ object CategoryTable {
|
||||
|
||||
const val COL_FLAGS = "flags"
|
||||
|
||||
const val COL_MANGA_ORDER = "manga_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_ORDER INTEGER NOT NULL,
|
||||
$COL_FLAGS INTEGER NOT NULL,
|
||||
$COL_MANGA_ORDER TEXT NOT NULL
|
||||
$COL_FLAGS INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
val addMangaOrder: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT"
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
Executable file → Normal file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Executable file → Normal file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Executable file → Normal file
@ -28,6 +28,8 @@ object MangaTable {
|
||||
|
||||
const val COL_LAST_UPDATE = "last_update"
|
||||
|
||||
const val COL_DATE_ADDED = "date_added"
|
||||
|
||||
const val COL_INITIALIZED = "initialized"
|
||||
|
||||
const val COL_VIEWER = "viewer"
|
||||
@ -58,7 +60,8 @@ object MangaTable {
|
||||
$COL_INITIALIZED BOOLEAN NOT NULL,
|
||||
$COL_VIEWER INTEGER NOT NULL,
|
||||
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
||||
$COL_COVER_LAST_MODIFIED LONG NOT NULL
|
||||
$COL_COVER_LAST_MODIFIED LONG NOT NULL,
|
||||
$COL_DATE_ADDED LONG NOT NULL
|
||||
)"""
|
||||
|
||||
val createUrlIndexQuery: String
|
||||
@ -70,4 +73,17 @@ object MangaTable {
|
||||
|
||||
val addCoverLastModified: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_COVER_LAST_MODIFIED LONG NOT NULL DEFAULT 0"
|
||||
|
||||
val addDateAdded: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_DATE_ADDED LONG NOT NULL DEFAULT 0"
|
||||
|
||||
/**
|
||||
* Used with addDateAdded to populate it with the oldest chapter fetch date.
|
||||
*/
|
||||
val backfillDateAdded: String
|
||||
get() = "UPDATE $TABLE SET $COL_DATE_ADDED = " +
|
||||
"(SELECT MIN(${ChapterTable.COL_DATE_FETCH}) " +
|
||||
"FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " +
|
||||
"ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " +
|
||||
"GROUP BY $TABLE.$COL_ID)"
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object MergedTable {
|
||||
|
||||
const val TABLE = "merged"
|
||||
|
||||
const val COL_MERGE_ID = "mergeID"
|
||||
|
||||
const val COL_MANGA_ID = "mangaID"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_MERGE_ID INTEGER NOT NULL,
|
||||
$COL_MANGA_ID INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
val createIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_MERGE_ID}_index ON $TABLE($COL_MERGE_ID)"
|
||||
}
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -59,7 +59,7 @@ class DownloadCache(
|
||||
*/
|
||||
private fun getDirectoryFromPreference(): UniFile {
|
||||
val dir = preferences.downloadsDirectory().get()
|
||||
return UniFile.fromUri(context, Uri.parse(dir))
|
||||
return UniFile.fromUri(context, dir.toUri())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +81,7 @@ class DownloadCache(
|
||||
if (sourceDir != null) {
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
|
||||
if (mangaDir != null) {
|
||||
return provider.getChapterDirName(chapter) in mangaDir.files
|
||||
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files }
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -128,7 +128,7 @@ class DownloadCache(
|
||||
.orEmpty()
|
||||
.associate { it.name to SourceDirectory(it) }
|
||||
.mapNotNullKeys { entry ->
|
||||
onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id
|
||||
onlineSources.find { provider.getSourceDirName(it).toLowerCase() == entry.key?.toLowerCase() }?.id
|
||||
}
|
||||
|
||||
rootDir.files = sourceDirs
|
||||
@ -191,9 +191,10 @@ class DownloadCache(
|
||||
fun removeChapter(chapter: Chapter, manga: Manga) {
|
||||
val sourceDir = rootDir.files[manga.source] ?: return
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||
val chapterDirName = provider.getChapterDirName(chapter)
|
||||
if (chapterDirName in mangaDir.files) {
|
||||
mangaDir.files -= chapterDirName
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,10 +208,11 @@ class DownloadCache(
|
||||
fun removeChapters(chapters: List<Chapter>, manga: Manga) {
|
||||
val sourceDir = rootDir.files[manga.source] ?: return
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||
for (chapter in chapters) {
|
||||
val chapterDirName = provider.getChapterDirName(chapter)
|
||||
if (chapterDirName in mangaDir.files) {
|
||||
mangaDir.files -= chapterDirName
|
||||
chapters.forEach { chapter ->
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Executable file → Normal file
13
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Executable file → Normal file
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
@ -152,7 +153,7 @@ class DownloadManager(private val context: Context) {
|
||||
.filter { "image" in it.type.orEmpty() }
|
||||
|
||||
if (files.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
throw Exception(context.getString(R.string.page_list_empty_error))
|
||||
}
|
||||
|
||||
files.sortedBy { it.name }
|
||||
@ -250,16 +251,20 @@ class DownloadManager(private val context: Context) {
|
||||
* @param newChapter the target chapter with the new name.
|
||||
*/
|
||||
fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
||||
val oldName = provider.getChapterDirName(oldChapter)
|
||||
val oldNames = provider.getValidChapterDirNames(oldChapter)
|
||||
val newName = provider.getChapterDirName(newChapter)
|
||||
val mangaDir = provider.getMangaDir(manga, source)
|
||||
|
||||
val oldFolder = mangaDir.findFile(oldName)
|
||||
// Assume there's only 1 version of the chapter name formats present
|
||||
val oldFolder = oldNames.asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
|
||||
if (oldFolder?.renameTo(newName) == true) {
|
||||
cache.removeChapter(oldChapter, manga)
|
||||
cache.addChapter(newName, mangaDir, manga)
|
||||
} else {
|
||||
Timber.e("Could not rename downloaded chapter: %s.", oldName)
|
||||
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
54
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Executable file → Normal file
54
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Executable file → Normal file
@ -13,8 +13,8 @@ import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.regex.Pattern
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
@ -23,11 +23,15 @@ import uy.kohesive.injekt.api.get
|
||||
*/
|
||||
internal class DownloadNotifier(private val context: Context) {
|
||||
|
||||
private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER) {
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||
}
|
||||
|
||||
private val preferences by lazy { Injekt.get<PreferencesHelper>() }
|
||||
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_COMPLETE) {
|
||||
setAutoCancel(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of download. Used for correct notification icon.
|
||||
@ -56,7 +60,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
/**
|
||||
* Clear old actions if they exist.
|
||||
*/
|
||||
private fun clearActions() = with(notificationBuilder) {
|
||||
private fun NotificationCompat.Builder.clearActions() {
|
||||
if (mActions.isNotEmpty()) {
|
||||
mActions.clear()
|
||||
}
|
||||
@ -76,8 +80,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
* @param download download object containing download information.
|
||||
*/
|
||||
fun onProgressChange(download: Download) {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
with(progressNotificationBuilder) {
|
||||
// Check if first call.
|
||||
if (!isDownloading) {
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
@ -110,16 +113,14 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
|
||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||
}
|
||||
|
||||
// Displays the progress bar on notification
|
||||
notificationBuilder.show()
|
||||
progressNotificationBuilder.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification when download is paused.
|
||||
*/
|
||||
fun onDownloadPaused() {
|
||||
with(notificationBuilder) {
|
||||
with(progressNotificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.chapter_paused))
|
||||
setContentText(context.getString(R.string.download_notifier_download_paused))
|
||||
setSmallIcon(R.drawable.ic_pause_24dp)
|
||||
@ -141,21 +142,40 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
NotificationReceiver.clearDownloadsPendingBroadcast(context)
|
||||
)
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
notificationBuilder.show()
|
||||
progressNotificationBuilder.show()
|
||||
|
||||
// Reset initial values
|
||||
isDownloading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* This function shows a notification to inform download tasks are done.
|
||||
*/
|
||||
fun downloadFinished() {
|
||||
// Create notification
|
||||
with(completeNotificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||
setContentText(context.getString(R.string.download_notifier_download_finish))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
clearActions()
|
||||
setAutoCancel(true)
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
completeNotificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_COMPLETE)
|
||||
|
||||
// Reset states to default
|
||||
errorThrown = false
|
||||
isDownloading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the downloader receives a warning.
|
||||
*
|
||||
* @param reason the text to show.
|
||||
*/
|
||||
fun onWarning(reason: String) {
|
||||
with(notificationBuilder) {
|
||||
with(completeNotificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||
setContentText(reason)
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
@ -164,7 +184,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
notificationBuilder.show()
|
||||
completeNotificationBuilder.show()
|
||||
|
||||
// Reset download information
|
||||
isDownloading = false
|
||||
@ -179,7 +199,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
*/
|
||||
fun onError(error: String? = null, chapter: String? = null) {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
with(completeNotificationBuilder) {
|
||||
setContentTitle(
|
||||
chapter
|
||||
?: context.getString(R.string.download_notifier_downloader_title)
|
||||
@ -191,7 +211,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
notificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||
completeNotificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||
|
||||
// Reset download information
|
||||
errorThrown = true
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
@ -22,7 +23,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
/**
|
||||
* Preferences used to store the list of chapters to delete.
|
||||
*/
|
||||
private val prefs = context.getSharedPreferences("chapters_to_delete", Context.MODE_PRIVATE)
|
||||
private val preferences = context.getSharedPreferences("chapters_to_delete", Context.MODE_PRIVATE)
|
||||
|
||||
/**
|
||||
* Last added chapter, used to avoid decoding from the preference too often.
|
||||
@ -49,7 +50,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
// Last entry matches the manga, reuse it to avoid decoding json from preferences
|
||||
lastEntry.copy(chapters = newChapters)
|
||||
} else {
|
||||
val existingEntry = prefs.getString(manga.id!!.toString(), null)
|
||||
val existingEntry = preferences.getString(manga.id!!.toString(), null)
|
||||
if (existingEntry != null) {
|
||||
// Existing entry found on preferences, decode json and add the new chapter
|
||||
val savedEntry = gson.fromJson<Entry>(existingEntry)
|
||||
@ -69,7 +70,9 @@ class DownloadPendingDeleter(context: Context) {
|
||||
|
||||
// Save current state
|
||||
val json = gson.toJson(newEntry)
|
||||
prefs.edit().putString(newEntry.manga.id.toString(), json).apply()
|
||||
preferences.edit {
|
||||
putString(newEntry.manga.id.toString(), json)
|
||||
}
|
||||
lastAddedEntry = newEntry
|
||||
}
|
||||
|
||||
@ -82,7 +85,9 @@ class DownloadPendingDeleter(context: Context) {
|
||||
@Synchronized
|
||||
fun getPendingChapters(): Map<Manga, List<Chapter>> {
|
||||
val entries = decodeAll()
|
||||
prefs.edit().clear().apply()
|
||||
preferences.edit {
|
||||
clear()
|
||||
}
|
||||
lastAddedEntry = null
|
||||
|
||||
return entries.associate { entry ->
|
||||
@ -94,7 +99,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
* Decodes all the chapters from preferences.
|
||||
*/
|
||||
private fun decodeAll(): List<Entry> {
|
||||
return prefs.all.values.mapNotNull { rawEntry ->
|
||||
return preferences.all.values.mapNotNull { rawEntry ->
|
||||
try {
|
||||
(rawEntry as? String)?.let { gson.fromJson<Entry>(it) }
|
||||
} catch (e: Exception) {
|
||||
@ -130,7 +135,8 @@ class DownloadPendingDeleter(context: Context) {
|
||||
private data class ChapterEntry(
|
||||
val id: Long,
|
||||
val url: String,
|
||||
val name: String
|
||||
val name: String,
|
||||
val scanlator: String?
|
||||
)
|
||||
|
||||
/**
|
||||
@ -154,7 +160,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
* Returns a chapter entry from a chapter model.
|
||||
*/
|
||||
private fun Chapter.toEntry(): ChapterEntry {
|
||||
return ChapterEntry(id!!, url, name)
|
||||
return ChapterEntry(id!!, url, name, scanlator)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,6 +180,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
it.id = id
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Executable file → Normal file
37
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
@ -32,14 +32,14 @@ class DownloadProvider(private val context: Context) {
|
||||
* The root directory for downloads.
|
||||
*/
|
||||
private var downloadsDir = preferences.downloadsDirectory().get().let {
|
||||
val dir = UniFile.fromUri(context, Uri.parse(it))
|
||||
val dir = UniFile.fromUri(context, it.toUri())
|
||||
DiskUtil.createNoMediaFile(dir, context)
|
||||
dir
|
||||
}
|
||||
|
||||
init {
|
||||
preferences.downloadsDirectory().asFlow()
|
||||
.onEach { downloadsDir = UniFile.fromUri(context, Uri.parse(it)) }
|
||||
.onEach { downloadsDir = UniFile.fromUri(context, it.toUri()) }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
@ -88,7 +88,9 @@ class DownloadProvider(private val context: Context) {
|
||||
*/
|
||||
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
|
||||
val mangaDir = findMangaDir(manga, source)
|
||||
return mangaDir?.findFile(getChapterDirName(chapter))
|
||||
return getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir?.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +102,11 @@ class DownloadProvider(private val context: Context) {
|
||||
*/
|
||||
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): List<UniFile> {
|
||||
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
||||
return chapters.mapNotNull { mangaDir.findFile(getChapterDirName(it)) }
|
||||
return chapters.mapNotNull { chapter ->
|
||||
getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,6 +133,25 @@ class DownloadProvider(private val context: Context) {
|
||||
* @param chapter the chapter to query.
|
||||
*/
|
||||
fun getChapterDirName(chapter: Chapter): String {
|
||||
return DiskUtil.buildValidFilename(chapter.name)
|
||||
return DiskUtil.buildValidFilename(
|
||||
when {
|
||||
chapter.scanlator != null -> "${chapter.scanlator}_${chapter.name}"
|
||||
else -> chapter.name
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid downloaded chapter directory names.
|
||||
*
|
||||
* @param chapter the chapter to query.
|
||||
*/
|
||||
fun getValidChapterDirNames(chapter: Chapter): List<String> {
|
||||
return listOf(
|
||||
getChapterDirName(chapter),
|
||||
|
||||
// Legacy chapter directory name used in v0.9.2 and before
|
||||
DiskUtil.buildValidFilename(chapter.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
9
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Executable file → Normal file
9
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Executable file → Normal file
@ -4,6 +4,7 @@ import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkInfo.State.CONNECTED
|
||||
import android.net.NetworkInfo.State.DISCONNECTED
|
||||
import android.os.Build
|
||||
@ -143,7 +144,7 @@ class DownloadService : Service() {
|
||||
private fun onNetworkStateChanged(connectivity: Connectivity) {
|
||||
when (connectivity.state) {
|
||||
CONNECTED -> {
|
||||
if (preferences.downloadOnlyOverWifi() && connectivityManager.isActiveNetworkMetered) {
|
||||
if (preferences.downloadOnlyOverWifi() && connectivityManager.activeNetworkInfo?.type != ConnectivityManager.TYPE_WIFI) {
|
||||
downloadManager.stopDownloads(getString(R.string.download_notifier_text_only_wifi))
|
||||
} else {
|
||||
val started = downloadManager.startDownloads()
|
||||
@ -175,19 +176,19 @@ class DownloadService : Service() {
|
||||
/**
|
||||
* Releases the wake lock if it's held.
|
||||
*/
|
||||
fun PowerManager.WakeLock.releaseIfNeeded() {
|
||||
private fun PowerManager.WakeLock.releaseIfNeeded() {
|
||||
if (isHeld) release()
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the wake lock if it's not held.
|
||||
*/
|
||||
fun PowerManager.WakeLock.acquireIfNeeded() {
|
||||
private fun PowerManager.WakeLock.acquireIfNeeded() {
|
||||
if (!isHeld) acquire()
|
||||
}
|
||||
|
||||
private fun getPlaceholderNotification(): Notification {
|
||||
return notification(Notifications.CHANNEL_DOWNLOADER) {
|
||||
return notification(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||
setContentTitle(getString(R.string.download_notifier_downloader_title))
|
||||
}
|
||||
}
|
||||
|
15
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Executable file → Normal file
15
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Executable file → Normal file
@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -42,9 +43,9 @@ class DownloadStore(
|
||||
* @param downloads the list of downloads to add.
|
||||
*/
|
||||
fun addAll(downloads: List<Download>) {
|
||||
val editor = preferences.edit()
|
||||
downloads.forEach { editor.putString(getKey(it), serialize(it)) }
|
||||
editor.apply()
|
||||
preferences.edit {
|
||||
downloads.forEach { putString(getKey(it), serialize(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,14 +54,18 @@ class DownloadStore(
|
||||
* @param download the download to remove.
|
||||
*/
|
||||
fun remove(download: Download) {
|
||||
preferences.edit().remove(getKey(download)).apply()
|
||||
preferences.edit {
|
||||
remove(getKey(download))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the downloads from the store.
|
||||
*/
|
||||
fun clear() {
|
||||
preferences.edit().clear().apply()
|
||||
preferences.edit {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
26
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Executable file → Normal file
26
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Executable file → Normal file
@ -5,6 +5,7 @@ import android.webkit.MimeTypeMap
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -116,6 +117,8 @@ class Downloader(
|
||||
val pending = queue.filter { it.status != Download.DOWNLOADED }
|
||||
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
||||
|
||||
notifier.paused = false
|
||||
|
||||
downloadsRelay.call(pending)
|
||||
return pending.isNotEmpty()
|
||||
}
|
||||
@ -136,7 +139,7 @@ class Downloader(
|
||||
notifier.paused = false
|
||||
notifier.onDownloadPaused()
|
||||
} else {
|
||||
notifier.dismiss()
|
||||
notifier.downloadFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,7 +194,7 @@ class Downloader(
|
||||
},
|
||||
5
|
||||
)
|
||||
.onBackpressureBuffer()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
@ -228,13 +231,9 @@ class Downloader(
|
||||
val wasEmpty = queue.isEmpty()
|
||||
// Called in background thread, the operation can be slow with SAF.
|
||||
val chaptersWithoutDir = async {
|
||||
val mangaDir = provider.findMangaDir(manga, source)
|
||||
|
||||
chapters
|
||||
// Avoid downloading chapters with the same name.
|
||||
.distinctBy { it.name }
|
||||
// Filter out those already downloaded.
|
||||
.filter { mangaDir?.findFile(provider.getChapterDirName(it)) == null }
|
||||
.filter { provider.findChapterDir(it, manga, source) == null }
|
||||
// Add chapters to queue from the start.
|
||||
.sortedByDescending { it.source_order }
|
||||
}
|
||||
@ -269,6 +268,13 @@ class Downloader(
|
||||
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||
|
||||
if (DiskUtil.getAvailableStorageSpace(mangaDir) < MIN_DISK_SPACE) {
|
||||
download.status = Download.ERROR
|
||||
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
|
||||
return@defer Observable.just(download)
|
||||
}
|
||||
|
||||
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
|
||||
|
||||
val pageListObservable = if (download.pages == null) {
|
||||
@ -276,7 +282,7 @@ class Downloader(
|
||||
download.source.fetchPageList(download.chapter)
|
||||
.doOnNext { pages ->
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
throw Exception(context.getString(R.string.page_list_empty_error))
|
||||
}
|
||||
download.pages = pages
|
||||
}
|
||||
@ -300,6 +306,7 @@ class Downloader(
|
||||
// Start downloading images, consider we can have downloaded images already
|
||||
// Concurrently do 5 pages at a time
|
||||
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5)
|
||||
.onBackpressureLatest()
|
||||
// Do when page is downloaded.
|
||||
.doOnNext { notifier.onProgressChange(download) }
|
||||
.toList()
|
||||
@ -485,5 +492,8 @@ class Downloader(
|
||||
|
||||
companion object {
|
||||
const val TMP_DIR_SUFFIX = "_tmp"
|
||||
|
||||
// Arbitrary minimum required space to start a download: 50 MB
|
||||
const val MIN_DISK_SPACE = 50 * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Executable file → Normal file
@ -104,10 +104,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
)
|
||||
)
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
|
||||
val errorLogIntent = NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||
|
||||
setContentIntent(errorLogIntent)
|
||||
addAction(
|
||||
R.drawable.nnf_ic_file_folder,
|
||||
context.getString(R.string.action_open_log),
|
||||
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||
errorLogIntent
|
||||
)
|
||||
}
|
||||
.build()
|
||||
|
42
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Executable file → Normal file
42
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Executable file → Normal file
@ -27,9 +27,7 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
@ -255,9 +253,9 @@ class LibraryUpdateService(
|
||||
// Initialize the variables holding the progress of the updates.
|
||||
val count = AtomicInteger(0)
|
||||
// List containing new updates
|
||||
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
|
||||
val newUpdates = mutableListOf<Pair<LibraryManga, Array<Chapter>>>()
|
||||
// List containing failed updates
|
||||
val failedUpdates = ArrayList<Pair<Manga, String?>>()
|
||||
val failedUpdates = mutableListOf<Pair<Manga, String?>>()
|
||||
// Boolean to determine if DownloadManager has downloads
|
||||
var hasDownloads = false
|
||||
|
||||
@ -267,25 +265,20 @@ class LibraryUpdateService(
|
||||
.doOnNext { notifier.showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
||||
// Update the chapters of the manga
|
||||
.concatMap { manga ->
|
||||
if (manga.source in LIBRARY_UPDATE_EXCLUDED_SOURCES) {
|
||||
// Ignore EXH manga, updating chapters for every manga will get you banned
|
||||
Observable.empty()
|
||||
} else {
|
||||
updateManga(manga)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
failedUpdates.add(Pair(manga, it.message))
|
||||
Pair(emptyList(), emptyList())
|
||||
updateManga(manga)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
failedUpdates.add(Pair(manga, it.message))
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
.filter { pair -> pair.first.isNotEmpty() }
|
||||
.doOnNext {
|
||||
if (manga.shouldDownloadNewChapters(db, preferences)) {
|
||||
downloadChapters(manga, it.first)
|
||||
hasDownloads = true
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
.filter { pair -> pair.first.isNotEmpty() }
|
||||
.doOnNext {
|
||||
if (manga.shouldDownloadNewChapters(db, preferences)) {
|
||||
downloadChapters(manga, it.first)
|
||||
hasDownloads = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert to the manga that contains new chapters.
|
||||
.map {
|
||||
Pair(
|
||||
@ -337,7 +330,7 @@ class LibraryUpdateService(
|
||||
* @return a pair of the inserted and removed chapters.
|
||||
*/
|
||||
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
val source = sourceManager.get(manga.source) ?: return Observable.empty()
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
// Update manga details metadata in the background
|
||||
if (preferences.autoUpdateMetadata()) {
|
||||
@ -436,7 +429,8 @@ class LibraryUpdateService(
|
||||
|
||||
destFile.bufferedWriter().use { out ->
|
||||
errors.forEach { (manga, error) ->
|
||||
out.write("${manga.title}: $error\n")
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
out.write("${manga.title} ($source): $error\n")
|
||||
}
|
||||
}
|
||||
return destFile
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Executable file → Normal file
@ -30,9 +30,12 @@ object Notifications {
|
||||
/**
|
||||
* Notification channel and ids used by the downloader.
|
||||
*/
|
||||
const val CHANNEL_DOWNLOADER = "downloader_channel"
|
||||
private const val GROUP_DOWNLOADER = "group_downloader"
|
||||
const val CHANNEL_DOWNLOADER_PROGRESS = "downloader_progress_channel"
|
||||
const val ID_DOWNLOAD_CHAPTER = -201
|
||||
const val CHANNEL_DOWNLOADER_COMPLETE = "downloader_complete_channel"
|
||||
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
||||
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
||||
|
||||
/**
|
||||
* Notification channel and ids used by the library updater.
|
||||
@ -50,7 +53,7 @@ object Notifications {
|
||||
/**
|
||||
* Notification channel and ids used by the backup/restore system.
|
||||
*/
|
||||
private const val GROUP_BACK_RESTORE = "group_backup_restore"
|
||||
private const val GROUP_BACKUP_RESTORE = "group_backup_restore"
|
||||
const val CHANNEL_BACKUP_RESTORE_PROGRESS = "backup_restore_progress_channel"
|
||||
const val ID_BACKUP_PROGRESS = -501
|
||||
const val ID_RESTORE_PROGRESS = -503
|
||||
@ -59,6 +62,7 @@ object Notifications {
|
||||
const val ID_RESTORE_COMPLETE = -504
|
||||
|
||||
private val deprecatedChannels = listOf(
|
||||
"downloader_channel",
|
||||
"backup_restore_complete_channel"
|
||||
)
|
||||
|
||||
@ -70,10 +74,12 @@ object Notifications {
|
||||
fun createChannels(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val backupRestoreGroup = NotificationChannelGroup(GROUP_BACK_RESTORE, context.getString(R.string.channel_backup_restore))
|
||||
context.notificationManager.createNotificationChannelGroup(backupRestoreGroup)
|
||||
listOf(
|
||||
NotificationChannelGroup(GROUP_BACKUP_RESTORE, context.getString(R.string.group_backup_restore)),
|
||||
NotificationChannelGroup(GROUP_DOWNLOADER, context.getString(R.string.group_downloader))
|
||||
).forEach(context.notificationManager::createNotificationChannelGroup)
|
||||
|
||||
val channels = listOf(
|
||||
listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
@ -85,9 +91,17 @@ object Notifications {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
|
||||
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
@ -99,26 +113,23 @@ object Notifications {
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_backup_restore_progress),
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_BACK_RESTORE
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_backup_restore_complete),
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
group = GROUP_BACK_RESTORE
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
setShowBadge(false)
|
||||
setSound(null, null)
|
||||
}
|
||||
)
|
||||
context.notificationManager.createNotificationChannels(channels)
|
||||
).forEach(context.notificationManager::createNotificationChannel)
|
||||
|
||||
// Delete old notification channels
|
||||
deprecatedChannels.forEach {
|
||||
context.notificationManager.deleteNotificationChannel(it)
|
||||
}
|
||||
deprecatedChannels.forEach(context.notificationManager::deleteNotificationChannel)
|
||||
}
|
||||
}
|
||||
|
100
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Executable file → Normal file
100
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Executable file → Normal file
@ -21,6 +21,8 @@ object PreferenceKeys {
|
||||
|
||||
const val showPageNumber = "pref_show_page_number_key"
|
||||
|
||||
const val showReadingMode = "pref_show_reading_mode"
|
||||
|
||||
const val trueColor = "pref_true_color_key"
|
||||
|
||||
const val fullscreen = "fullscreen"
|
||||
@ -53,6 +55,8 @@ object PreferenceKeys {
|
||||
|
||||
const val readWithTapping = "reader_tap"
|
||||
|
||||
const val readWithTappingInverted = "reader_tapping_inverted"
|
||||
|
||||
const val readWithLongTap = "reader_long_tap"
|
||||
|
||||
const val readWithVolumeKeys = "reader_volume_keys"
|
||||
@ -65,20 +69,20 @@ object PreferenceKeys {
|
||||
|
||||
const val landscapeColumns = "pref_library_columns_landscape_key"
|
||||
|
||||
const val jumpToChapters = "jump_to_chapters"
|
||||
|
||||
const val updateOnlyNonCompleted = "pref_update_only_non_completed_key"
|
||||
|
||||
const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
|
||||
|
||||
const val lastUsedCatalogueSource = "last_catalogue_source"
|
||||
const val lastUsedSource = "last_catalogue_source"
|
||||
|
||||
const val lastUsedCategory = "last_used_category"
|
||||
|
||||
const val catalogueViewSetting = "pref_display_catalogue_view_setting"
|
||||
const val sourceDisplayMode = "pref_display_mode_catalogue"
|
||||
|
||||
const val enabledLanguages = "source_languages"
|
||||
|
||||
const val sourcesSort = "sources_sort"
|
||||
|
||||
const val backupDirectory = "backup_directory"
|
||||
|
||||
const val downloadsDirectory = "download_directory"
|
||||
@ -109,10 +113,6 @@ object PreferenceKeys {
|
||||
|
||||
const val filterCompleted = "pref_filter_completed_key"
|
||||
|
||||
const val filterTracked = "pref_filter_tracked_key"
|
||||
|
||||
const val filterLewd = "pref_filter_lewd_key"
|
||||
|
||||
const val librarySortingMode = "library_sorting_mode"
|
||||
|
||||
const val automaticExtUpdates = "automatic_ext_updates"
|
||||
@ -137,7 +137,7 @@ object PreferenceKeys {
|
||||
|
||||
const val downloadNewCategories = "download_new_categories"
|
||||
|
||||
const val libraryViewSetting = "pref_display_library_view_setting"
|
||||
const val libraryDisplayMode = "pref_display_mode_library"
|
||||
|
||||
const val lang = "app_language"
|
||||
|
||||
@ -153,93 +153,17 @@ object PreferenceKeys {
|
||||
|
||||
const val unreadBadge = "display_unread_badge"
|
||||
|
||||
const val skipPreMigration = "skip_pre_migration"
|
||||
const val categoryTabs = "display_category_tabs"
|
||||
|
||||
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
||||
|
||||
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
|
||||
|
||||
const val enableDoh = "enable_doh"
|
||||
|
||||
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
|
||||
|
||||
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||
|
||||
fun trackToken(syncId: Int) = "track_token_$syncId"
|
||||
|
||||
const val eh_lock_hash = "lock_hash"
|
||||
|
||||
const val eh_lock_salt = "lock_salt"
|
||||
|
||||
const val eh_lock_length = "lock_length"
|
||||
|
||||
const val eh_lock_finger = "lock_finger"
|
||||
|
||||
const val eh_lock_manually = "eh_lock_manually"
|
||||
|
||||
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
|
||||
|
||||
const val eh_showSyncIntro = "eh_show_sync_intro"
|
||||
|
||||
const val eh_readOnlySync = "eh_sync_read_only"
|
||||
|
||||
const val eh_lenientSync = "eh_lenient_sync"
|
||||
|
||||
const val eh_useOrigImages = "eh_useOrigImages"
|
||||
|
||||
const val eh_ehSettingsProfile = "eh_ehSettingsProfile"
|
||||
|
||||
const val eh_exhSettingsProfile = "eh_exhSettingsProfile"
|
||||
|
||||
const val eh_settingsKey = "eh_settingsKey"
|
||||
|
||||
const val eh_sessionCookie = "eh_sessionCookie"
|
||||
|
||||
const val eh_hathPerksCookie = "eh_hathPerksCookie"
|
||||
|
||||
const val eh_enableExHentai = "enable_exhentai"
|
||||
|
||||
const val eh_ts_aspNetCookie = "eh_ts_aspNetCookie"
|
||||
|
||||
const val eh_showSettingsUploadWarning = "eh_showSettingsUploadWarning2"
|
||||
|
||||
const val eh_expandFilters = "eh_expand_filters"
|
||||
|
||||
const val eh_readerThreads = "eh_reader_threads"
|
||||
|
||||
const val eh_readerInstantRetry = "eh_reader_instant_retry"
|
||||
|
||||
const val eh_utilAutoscrollInterval = "eh_util_autoscroll_interval"
|
||||
|
||||
const val eh_cacheSize = "eh_cache_size"
|
||||
|
||||
const val eh_preserveReadingPosition = "eh_preserve_reading_position"
|
||||
|
||||
const val eh_autoSolveCaptchas = "eh_autosolve_captchas"
|
||||
|
||||
const val eh_delegateSources = "eh_delegate_sources"
|
||||
|
||||
const val eh_logLevel = "eh_log_level"
|
||||
|
||||
const val eh_enableSourceBlacklist = "eh_enable_source_blacklist"
|
||||
|
||||
const val eh_autoUpdateFrequency = "eh_auto_update_frequency"
|
||||
|
||||
const val eh_autoUpdateRestrictions = "eh_auto_update_restrictions"
|
||||
|
||||
const val eh_autoUpdateStats = "eh_auto_update_stats"
|
||||
|
||||
const val eh_aggressivePageLoading = "eh_aggressive_page_loading"
|
||||
|
||||
const val eh_hl_useHighQualityThumbs = "eh_hl_hq_thumbs"
|
||||
|
||||
const val eh_preload_size = "eh_preload_size"
|
||||
|
||||
const val eh_tag_filtering_value = "eh_tag_filtering_value"
|
||||
|
||||
const val eh_tag_watching_value = "eh_tag_watching_value"
|
||||
|
||||
const val eh_is_hentai_enabled = "eh_is_hentai_enabled"
|
||||
|
||||
const val eh_use_new_manga_interface = "eh_use_new_manga_interface"
|
||||
|
||||
const val eh_use_auto_webtoon = "eh_use_auto_webtoon"
|
||||
}
|
||||
|
@ -5,14 +5,36 @@ package eu.kanade.tachiyomi.data.preference
|
||||
*/
|
||||
object PreferenceValues {
|
||||
|
||||
const val THEME_MODE_LIGHT = "light"
|
||||
const val THEME_MODE_DARK = "dark"
|
||||
const val THEME_MODE_SYSTEM = "system"
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class ThemeMode {
|
||||
light,
|
||||
dark,
|
||||
system,
|
||||
}
|
||||
|
||||
const val THEME_LIGHT_DEFAULT = "default"
|
||||
const val THEME_LIGHT_BLUE = "blue"
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class LightThemeVariant {
|
||||
default,
|
||||
blue,
|
||||
}
|
||||
|
||||
const val THEME_DARK_DEFAULT = "default"
|
||||
const val THEME_DARK_BLUE = "blue"
|
||||
const val THEME_DARK_AMOLED = "amoled"
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class DarkThemeVariant {
|
||||
default,
|
||||
blue,
|
||||
amoled,
|
||||
}
|
||||
|
||||
enum class DisplayMode {
|
||||
COMPACT_GRID,
|
||||
COMFORTABLE_GRID,
|
||||
LIST,
|
||||
}
|
||||
|
||||
enum class TappingInvertMode {
|
||||
NONE,
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
BOTH
|
||||
}
|
||||
}
|
||||
|
196
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
Executable file → Normal file
196
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
Executable file → Normal file
@ -1,16 +1,15 @@
|
||||
package eu.kanade.tachiyomi.data.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.f2prateek.rx.preferences.Preference as RxPreference
|
||||
import com.f2prateek.rx.preferences.RxSharedPreferences
|
||||
import com.tfcporciuncula.flow.FlowSharedPreferences
|
||||
import com.tfcporciuncula.flow.Preference
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import java.io.File
|
||||
@ -21,8 +20,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
fun <T> RxPreference<T>.getOrDefault(): T = get() ?: defaultValue()!!
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
||||
block(get())
|
||||
@ -30,28 +27,31 @@ fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
||||
.onEach { block(it) }
|
||||
}
|
||||
|
||||
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
|
||||
set(get() + item)
|
||||
}
|
||||
|
||||
operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
|
||||
set(get() - item)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class PreferencesHelper(val context: Context) {
|
||||
|
||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val rxPrefs = RxSharedPreferences.create(prefs)
|
||||
val flowPrefs = FlowSharedPreferences(prefs)
|
||||
private val flowPrefs = FlowSharedPreferences(prefs)
|
||||
|
||||
private val defaultDownloadsDir = Uri.fromFile(
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"downloads"
|
||||
)
|
||||
)
|
||||
private val defaultDownloadsDir = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"downloads"
|
||||
).toUri()
|
||||
|
||||
private val defaultBackupDir = Uri.fromFile(
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"backup"
|
||||
)
|
||||
)
|
||||
private val defaultBackupDir = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"backup"
|
||||
).toUri()
|
||||
|
||||
fun startScreen() = prefs.getInt(Keys.startScreen, 1)
|
||||
|
||||
@ -73,11 +73,11 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun clear() = prefs.edit().clear().apply()
|
||||
|
||||
fun themeMode() = flowPrefs.getString(Keys.themeMode, Values.THEME_MODE_SYSTEM)
|
||||
fun themeMode() = flowPrefs.getEnum(Keys.themeMode, Values.ThemeMode.system)
|
||||
|
||||
fun themeLight() = flowPrefs.getString(Keys.themeLight, Values.THEME_LIGHT_DEFAULT)
|
||||
fun themeLight() = flowPrefs.getEnum(Keys.themeLight, Values.LightThemeVariant.default)
|
||||
|
||||
fun themeDark() = flowPrefs.getString(Keys.themeDark, Values.THEME_DARK_DEFAULT)
|
||||
fun themeDark() = flowPrefs.getEnum(Keys.themeDark, Values.DarkThemeVariant.default)
|
||||
|
||||
fun rotation() = flowPrefs.getInt(Keys.rotation, 1)
|
||||
|
||||
@ -87,6 +87,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
|
||||
|
||||
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
|
||||
|
||||
fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false)
|
||||
|
||||
fun fullscreen() = flowPrefs.getBoolean(Keys.fullscreen, true)
|
||||
@ -105,7 +107,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun colorFilterMode() = flowPrefs.getInt(Keys.colorFilterMode, 0)
|
||||
|
||||
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 1)
|
||||
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 2)
|
||||
|
||||
fun imageScaleType() = flowPrefs.getInt(Keys.imageScaleType, 1)
|
||||
|
||||
@ -123,31 +125,33 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun readWithTapping() = flowPrefs.getBoolean(Keys.readWithTapping, true)
|
||||
|
||||
fun readWithTappingInverted() = flowPrefs.getEnum(Keys.readWithTappingInverted, Values.TappingInvertMode.NONE)
|
||||
|
||||
fun readWithLongTap() = flowPrefs.getBoolean(Keys.readWithLongTap, true)
|
||||
|
||||
fun readWithVolumeKeys() = flowPrefs.getBoolean(Keys.readWithVolumeKeys, false)
|
||||
|
||||
fun readWithVolumeKeysInverted() = flowPrefs.getBoolean(Keys.readWithVolumeKeysInverted, false)
|
||||
|
||||
fun portraitColumns() = rxPrefs.getInteger(Keys.portraitColumns, 0)
|
||||
fun portraitColumns() = flowPrefs.getInt(Keys.portraitColumns, 0)
|
||||
|
||||
fun landscapeColumns() = rxPrefs.getInteger(Keys.landscapeColumns, 0)
|
||||
fun landscapeColumns() = flowPrefs.getInt(Keys.landscapeColumns, 0)
|
||||
|
||||
fun jumpToChapters() = prefs.getBoolean(Keys.jumpToChapters, false)
|
||||
|
||||
fun updateOnlyNonCompleted() = prefs.getBoolean(Keys.updateOnlyNonCompleted, false)
|
||||
|
||||
fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
|
||||
|
||||
fun lastUsedCatalogueSource() = rxPrefs.getLong(Keys.lastUsedCatalogueSource, -1)
|
||||
fun lastUsedSource() = flowPrefs.getLong(Keys.lastUsedSource, -1)
|
||||
|
||||
fun lastUsedCategory() = flowPrefs.getInt(Keys.lastUsedCategory, 0)
|
||||
|
||||
fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
|
||||
|
||||
fun catalogueViewSetting() = flowPrefs.getInt(Keys.catalogueViewSetting, 0)
|
||||
fun sourceDisplayMode() = flowPrefs.getEnum(Keys.sourceDisplayMode, DisplayMode.COMPACT_GRID)
|
||||
|
||||
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("all", "en", Locale.getDefault().language))
|
||||
|
||||
fun sourceSorting() = flowPrefs.getInt(Keys.sourcesSort, 0)
|
||||
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
|
||||
|
||||
fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
|
||||
|
||||
@ -191,7 +195,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
|
||||
|
||||
fun libraryViewSetting() = flowPrefs.getInt(Keys.libraryViewSetting, 0)
|
||||
fun libraryDisplayMode() = flowPrefs.getEnum(Keys.libraryDisplayMode, DisplayMode.COMPACT_GRID)
|
||||
|
||||
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
|
||||
|
||||
@ -199,16 +203,13 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun unreadBadge() = flowPrefs.getBoolean(Keys.unreadBadge, true)
|
||||
|
||||
// J2K converted from boolean to integer
|
||||
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, 0)
|
||||
fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true)
|
||||
|
||||
fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, 0)
|
||||
fun filterDownloaded() = flowPrefs.getBoolean(Keys.filterDownloaded, false)
|
||||
|
||||
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0)
|
||||
fun filterUnread() = flowPrefs.getBoolean(Keys.filterUnread, false)
|
||||
|
||||
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0)
|
||||
|
||||
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0)
|
||||
fun filterCompleted() = flowPrefs.getBoolean(Keys.filterCompleted, false)
|
||||
|
||||
fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
|
||||
|
||||
@ -222,9 +223,9 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false)
|
||||
|
||||
fun hiddenCatalogues() = flowPrefs.getStringSet("hidden_catalogues", mutableSetOf())
|
||||
fun disabledSources() = flowPrefs.getStringSet("hidden_catalogues", emptySet())
|
||||
|
||||
fun pinnedCatalogues() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
|
||||
fun pinnedSources() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
|
||||
|
||||
fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false)
|
||||
|
||||
@ -242,114 +243,5 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
|
||||
|
||||
// --> AZ J2K CHERRYPICKING
|
||||
|
||||
fun defaultMangaOrder() = flowPrefs.getString("default_manga_order", "")
|
||||
|
||||
fun migrationSources() = flowPrefs.getString("migrate_sources", "")
|
||||
|
||||
fun smartMigration() = flowPrefs.getBoolean("smart_migrate", false)
|
||||
|
||||
fun useSourceWithMost() = flowPrefs.getBoolean("use_source_with_most", false)
|
||||
|
||||
fun skipPreMigration() = flowPrefs.getBoolean(Keys.skipPreMigration, false)
|
||||
|
||||
fun upgradeFilters() {
|
||||
val filterDl = flowPrefs.getBoolean(Keys.filterDownloaded, false).get()
|
||||
val filterUn = flowPrefs.getBoolean(Keys.filterUnread, false).get()
|
||||
val filterCm = flowPrefs.getBoolean(Keys.filterCompleted, false).get()
|
||||
filterDownloaded().set(if (filterDl) 1 else 0)
|
||||
filterUnread().set(if (filterUn) 1 else 0)
|
||||
filterCompleted().set(if (filterCm) 1 else 0)
|
||||
}
|
||||
|
||||
// <--
|
||||
|
||||
// --> EH
|
||||
fun eh_isHentaiEnabled() = flowPrefs.getBoolean(Keys.eh_is_hentai_enabled, true)
|
||||
|
||||
fun enableExhentai() = flowPrefs.getBoolean(Keys.eh_enableExHentai, false)
|
||||
|
||||
fun secureEXH() = flowPrefs.getBoolean("secure_exh", true)
|
||||
|
||||
fun imageQuality() = flowPrefs.getString("ehentai_quality", "auto")
|
||||
|
||||
fun useHentaiAtHome() = flowPrefs.getBoolean("enable_hah", true)
|
||||
|
||||
fun useJapaneseTitle() = flowPrefs.getBoolean("use_jp_title", false)
|
||||
|
||||
fun eh_useOriginalImages() = flowPrefs.getBoolean(Keys.eh_useOrigImages, false)
|
||||
|
||||
fun ehTagFilterValue() = flowPrefs.getInt(Keys.eh_tag_filtering_value, 0)
|
||||
|
||||
fun ehTagWatchingValue() = flowPrefs.getInt(Keys.eh_tag_watching_value, 0)
|
||||
|
||||
fun ehSearchSize() = flowPrefs.getString("ex_search_size", "rc_0")
|
||||
|
||||
fun thumbnailRows() = flowPrefs.getString("ex_thumb_rows", "tr_2")
|
||||
|
||||
fun hasPerformedURLMigration() = flowPrefs.getBoolean("performed_url_migration", false)
|
||||
|
||||
// EH Cookies
|
||||
fun memberIdVal() = flowPrefs.getString("eh_ipb_member_id", "")
|
||||
|
||||
fun passHashVal() = flowPrefs.getString("eh_ipb_pass_hash", "")
|
||||
fun igneousVal() = flowPrefs.getString("eh_igneous", "")
|
||||
fun eh_ehSettingsProfile() = flowPrefs.getInt(Keys.eh_ehSettingsProfile, -1)
|
||||
fun eh_exhSettingsProfile() = flowPrefs.getInt(Keys.eh_exhSettingsProfile, -1)
|
||||
fun eh_settingsKey() = flowPrefs.getString(Keys.eh_settingsKey, "")
|
||||
fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
|
||||
fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
|
||||
|
||||
fun eh_nh_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
|
||||
|
||||
fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
|
||||
|
||||
fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
|
||||
|
||||
fun eh_lenientSync() = flowPrefs.getBoolean(Keys.eh_lenientSync, false)
|
||||
|
||||
fun eh_ts_aspNetCookie() = flowPrefs.getString(Keys.eh_ts_aspNetCookie, "")
|
||||
|
||||
fun eh_showSettingsUploadWarning() = flowPrefs.getBoolean(Keys.eh_showSettingsUploadWarning, true)
|
||||
|
||||
fun eh_expandFilters() = flowPrefs.getBoolean(Keys.eh_expandFilters, false)
|
||||
|
||||
fun eh_readerThreads() = flowPrefs.getInt(Keys.eh_readerThreads, 2)
|
||||
|
||||
fun eh_readerInstantRetry() = flowPrefs.getBoolean(Keys.eh_readerInstantRetry, true)
|
||||
|
||||
fun eh_utilAutoscrollInterval() = flowPrefs.getFloat(Keys.eh_utilAutoscrollInterval, 3f)
|
||||
|
||||
fun eh_cacheSize() = flowPrefs.getString(Keys.eh_cacheSize, "75")
|
||||
|
||||
fun eh_preserveReadingPosition() = flowPrefs.getBoolean(Keys.eh_preserveReadingPosition, false)
|
||||
|
||||
fun eh_autoSolveCaptchas() = flowPrefs.getBoolean(Keys.eh_autoSolveCaptchas, false)
|
||||
|
||||
fun eh_delegateSources() = flowPrefs.getBoolean(Keys.eh_delegateSources, true)
|
||||
|
||||
fun eh_lastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
|
||||
|
||||
fun eh_savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
|
||||
|
||||
fun eh_logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
|
||||
|
||||
fun eh_enableSourceBlacklist() = flowPrefs.getBoolean(Keys.eh_enableSourceBlacklist, true)
|
||||
|
||||
fun eh_autoUpdateFrequency() = flowPrefs.getInt(Keys.eh_autoUpdateFrequency, 1)
|
||||
|
||||
fun eh_autoUpdateRequirements() = prefs.getStringSet(Keys.eh_autoUpdateRestrictions, emptySet())
|
||||
|
||||
fun eh_autoUpdateStats() = flowPrefs.getString(Keys.eh_autoUpdateStats, "")
|
||||
|
||||
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
|
||||
|
||||
fun eh_hl_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_hl_useHighQualityThumbs, false)
|
||||
|
||||
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
|
||||
|
||||
fun eh_useNewMangaInterface() = flowPrefs.getBoolean(Keys.eh_use_new_manga_interface, true)
|
||||
|
||||
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
|
||||
fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false)
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt
Executable file → Normal file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user