mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-27 19:11:01 +00:00
Compare commits
442 Commits
v2.1.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59784de727 | ||
|
|
42f23e4345 | ||
|
|
adb304f138 | ||
|
|
3bbf9efe63 | ||
|
|
b454a2e3d9 | ||
|
|
23e6323f92 | ||
|
|
b0dbd7a348 | ||
|
|
0f9bf3c5b1 | ||
|
|
4035aee1f9 | ||
|
|
f707f8cc33 | ||
|
|
fa7126d80d | ||
|
|
7d5f69888a | ||
|
|
51841cf05f | ||
|
|
6d2c01ff2b | ||
|
|
0bd4755814 | ||
|
|
927ba5ac86 | ||
|
|
808d4e6bf5 | ||
|
|
a39db5ea93 | ||
|
|
ca2409ef91 | ||
|
|
7b1f1a1357 | ||
|
|
9471683501 | ||
|
|
deeefb8e35 | ||
|
|
c777888fdb | ||
|
|
ce50627989 | ||
|
|
9f84845ada | ||
|
|
6a8e422a30 | ||
|
|
39d6f0fbd6 | ||
|
|
c240664fda | ||
|
|
a0f6320eee | ||
|
|
3434aa9744 | ||
|
|
3e84cfe09a | ||
|
|
22dccaa24b | ||
|
|
ffe921a223 | ||
|
|
1fd91b9ec6 | ||
|
|
cf10229574 | ||
|
|
5c2ae57d77 | ||
|
|
353452dd21 | ||
|
|
92fa0c117d | ||
|
|
9f4cd0ba0d | ||
|
|
385198e69a | ||
|
|
89fe3b82a3 | ||
|
|
af1bc944d8 | ||
|
|
a0b22e8d56 | ||
|
|
c47d1afa1a | ||
|
|
12a5b602e9 | ||
|
|
25b85569fe | ||
|
|
b373a52218 | ||
|
|
b0e46cd904 | ||
|
|
89a54b4509 | ||
|
|
56aefef693 | ||
|
|
a8ad018c44 | ||
|
|
726f461ff6 | ||
|
|
9c0861a8e4 | ||
|
|
ca0162fb9c | ||
|
|
cfb8c3c733 | ||
|
|
fea448f850 | ||
|
|
c033bb0445 | ||
|
|
bb110be9ab | ||
|
|
fd39c4f391 | ||
|
|
9a3f9c6de2 | ||
|
|
cf9799da7c | ||
|
|
c054e2f2ac | ||
|
|
8177dfdcef | ||
|
|
813b64980d | ||
|
|
fda809bc8a | ||
|
|
5d1b220105 | ||
|
|
de21365c90 | ||
|
|
a24d1515b3 | ||
|
|
a3d6f841c6 | ||
|
|
b770bca6ba | ||
|
|
eaefbc13f9 | ||
|
|
7fcc23c5bf | ||
|
|
29364bf30a | ||
|
|
a9b4916dd8 | ||
|
|
d4ab0ad57d | ||
|
|
e3f8096749 | ||
|
|
94aae33d10 | ||
|
|
96e29a8c59 | ||
|
|
34a9a55d4f | ||
|
|
cf93f6d657 | ||
|
|
91bcacc978 | ||
|
|
e79a824a04 | ||
|
|
e00bbb2d8e | ||
|
|
12c77604f1 | ||
|
|
9a1ec8567c | ||
|
|
5dbc01dba3 | ||
|
|
c5abfa15e0 | ||
|
|
b69e466853 | ||
|
|
9e371778b7 | ||
|
|
ff036165df | ||
|
|
4ed74b664b | ||
|
|
ddd59643c5 | ||
|
|
6122eb3669 | ||
|
|
1b5149f143 | ||
|
|
b654824eb7 | ||
|
|
4d2a08c258 | ||
|
|
19697f4f39 | ||
|
|
41eea667e5 | ||
|
|
f0040b8392 | ||
|
|
291f61551a | ||
|
|
6e8bd08828 | ||
|
|
e915dd619d | ||
|
|
8fb6357fb5 | ||
|
|
07662a91f4 | ||
|
|
37c618cb28 | ||
|
|
5536f3b994 | ||
|
|
bdbbe62570 | ||
|
|
4838e69aea | ||
|
|
408737d510 | ||
|
|
a0f05928e0 | ||
|
|
a35887d4ac | ||
|
|
dbce7c5b29 | ||
|
|
1028ac66cb | ||
|
|
eb5e2623a0 | ||
|
|
867a4f36b3 | ||
|
|
913d74b285 | ||
|
|
eb2eae7d6c | ||
|
|
8e5e548e16 | ||
|
|
af1a481bdb | ||
|
|
92089067f1 | ||
|
|
d04ced94ea | ||
|
|
14115ada4c | ||
|
|
7f36eba709 | ||
|
|
7504bb9081 | ||
|
|
64df08f91c | ||
|
|
98f4d4f30b | ||
|
|
a9b03c45c6 | ||
|
|
3af7926d20 | ||
|
|
e0cd43c63c | ||
|
|
2742f58af5 | ||
|
|
49175a962a | ||
|
|
46d8248ffd | ||
|
|
4ba1408f0f | ||
|
|
95fa5dcd9b | ||
|
|
a2ca16355a | ||
|
|
7ac679f927 | ||
|
|
e2eae6250b | ||
|
|
2855093f5f | ||
|
|
e50a65571f | ||
|
|
acef7c3d5e | ||
|
|
18778f3c5a | ||
|
|
03dae8c1b0 | ||
|
|
c862c072b5 | ||
|
|
251f1e89cf | ||
|
|
3632055081 | ||
|
|
bd64454c15 | ||
|
|
31afbd547e | ||
|
|
8da0092561 | ||
|
|
36c64951c7 | ||
|
|
120e63ea8a | ||
|
|
ad82faba3f | ||
|
|
4c4bbe3214 | ||
|
|
ecbc7efebc | ||
|
|
89b6f28b9f | ||
|
|
8a1097cd35 | ||
|
|
47d74de7ce | ||
|
|
f3c89b3ac5 | ||
|
|
a2ecc5e30e | ||
|
|
db50975174 | ||
|
|
ab14c4815f | ||
|
|
7ad586c994 | ||
|
|
db979de829 | ||
|
|
5218d5cd28 | ||
|
|
9e4684e61c | ||
|
|
9b408e7520 | ||
|
|
10bd7d0918 | ||
|
|
5279b0cd65 | ||
|
|
d181dcf837 | ||
|
|
49dc9d55b5 | ||
|
|
852e9d0d29 | ||
|
|
7a1ed4f83e | ||
|
|
2673b7d9bc | ||
|
|
1587aff433 | ||
|
|
26b3f50fe0 | ||
|
|
286297aa38 | ||
|
|
99a805826d | ||
|
|
be1711b51e | ||
|
|
51beea1dc8 | ||
|
|
297e9dd756 | ||
|
|
03b8e7dab6 | ||
|
|
dbe837be28 | ||
|
|
945d5886ea | ||
|
|
93fa29829f | ||
|
|
a9f8d223e9 | ||
|
|
d2876d04f5 | ||
|
|
fcd5c621de | ||
|
|
5fb0204376 | ||
|
|
790ab1a343 | ||
|
|
86ed721796 | ||
|
|
2837cad762 | ||
|
|
500de4e45e | ||
|
|
533148069f | ||
|
|
42b0a3b62b | ||
|
|
c720aed4fc | ||
|
|
00dad2ad48 | ||
|
|
ce62a9c645 | ||
|
|
1e4e2fd701 | ||
|
|
103be31a43 | ||
|
|
63fa3c829e | ||
|
|
ab5c623e53 | ||
|
|
5e307bb796 | ||
|
|
a5567ef909 | ||
|
|
da22347267 | ||
|
|
a401ab89f3 | ||
|
|
6e6429db82 | ||
|
|
05fc97a933 | ||
|
|
b9eb9d82f1 | ||
|
|
976acd4af2 | ||
|
|
c5cbe408c1 | ||
|
|
1316d5a698 | ||
|
|
89aaef8355 | ||
|
|
94e3dff909 | ||
|
|
6240c61a11 | ||
|
|
f226614980 | ||
|
|
0ab283b254 | ||
|
|
449485f06a | ||
|
|
60752e83ed | ||
|
|
dfd8b597cd | ||
|
|
e256fb1560 | ||
|
|
2f7c6e734e | ||
|
|
efe5f546a2 | ||
|
|
a8bd9ef97b | ||
|
|
e93ca5d86e | ||
|
|
7f943d34ac | ||
|
|
8a922bd083 | ||
|
|
d5c87c46aa | ||
|
|
f128dee3e4 | ||
|
|
9de129a35b | ||
|
|
6d6b0b975a | ||
|
|
bff8983b23 | ||
|
|
55e156579b | ||
|
|
a251dd4ffb | ||
|
|
526098f2bf | ||
|
|
6ccdc10208 | ||
|
|
ce355c108e | ||
|
|
612936476d | ||
|
|
7ba117ec25 | ||
|
|
8ea6bf85b8 | ||
|
|
70c87b4067 | ||
|
|
4628282715 | ||
|
|
6c14a2eccf | ||
|
|
8944941d80 | ||
|
|
78da98bd1d | ||
|
|
57833be7df | ||
|
|
506a0576df | ||
|
|
458f4d1ff9 | ||
|
|
21b9d51a35 | ||
|
|
82922b9792 | ||
|
|
160f783c6d | ||
|
|
ba7c665a9d | ||
|
|
b2f01a24b2 | ||
|
|
84ae520f93 | ||
|
|
4f61f4cf2c | ||
|
|
eba774618e | ||
|
|
a74b9e985d | ||
|
|
0c2e2db1dc | ||
|
|
98b227876b | ||
|
|
1fe50d2cca | ||
|
|
420c0348f9 | ||
|
|
9be81aa4a9 | ||
|
|
64c8f4225c | ||
|
|
a7c9604c43 | ||
|
|
68cc81e56c | ||
|
|
c9a64b1638 | ||
|
|
ee7cff0fea | ||
|
|
4c35f9a0cf | ||
|
|
9d9c4f026d | ||
|
|
b4c7ea5f26 | ||
|
|
093bee94c6 | ||
|
|
fb99429dd7 | ||
|
|
a73c4cd678 | ||
|
|
1694a1cb24 | ||
|
|
aaf9bdd00c | ||
|
|
129adc5825 | ||
|
|
07e7456ed8 | ||
|
|
7168e08587 | ||
|
|
efb3b27317 | ||
|
|
2c3247c194 | ||
|
|
d37ebf8cdd | ||
|
|
a73b049fd4 | ||
|
|
b3de2f805f | ||
|
|
0bec4f4d61 | ||
|
|
4be4a0968d | ||
|
|
97b957a0ab | ||
|
|
a22083dfcd | ||
|
|
9dbc3db1b8 | ||
|
|
7af71ba217 | ||
|
|
80f3523f2e | ||
|
|
0afad1d9ae | ||
|
|
915c6c1dfe | ||
|
|
e319aeb342 | ||
|
|
ac20426689 | ||
|
|
d778cd4350 | ||
|
|
83c07467a9 | ||
|
|
0225b28fea | ||
|
|
1e2a740dae | ||
|
|
22e687b9d8 | ||
|
|
f088b90964 | ||
|
|
ab199a3502 | ||
|
|
2493935349 | ||
|
|
c9699ba1fc | ||
|
|
051012be2d | ||
|
|
a38f1a2b2b | ||
|
|
0b66275995 | ||
|
|
7879fe4355 | ||
|
|
4aa88244ed | ||
|
|
bfe4d69b8a | ||
|
|
69fead70d1 | ||
|
|
92c663cd38 | ||
|
|
b829ed26f3 | ||
|
|
3d4834507d | ||
|
|
0758241e06 | ||
|
|
b093b5f979 | ||
|
|
d86481a0f7 | ||
|
|
de1788950f | ||
|
|
1e7b546b75 | ||
|
|
d53781e75a | ||
|
|
aa1830f12c | ||
|
|
17a87aa8c8 | ||
|
|
2662017fb7 | ||
|
|
f2b9cc3b3e | ||
|
|
2912ee5f73 | ||
|
|
9bb4c702f3 | ||
|
|
c4630f9243 | ||
|
|
57581d6f54 | ||
|
|
b5cfb5d567 | ||
|
|
04306a981f | ||
|
|
95409f7eda | ||
|
|
8741d820ad | ||
|
|
6020636a66 | ||
|
|
d15ffe7708 | ||
|
|
f77269e468 | ||
|
|
a2e44da99d | ||
|
|
8d7b86a667 | ||
|
|
0c6fad91ca | ||
|
|
c0f3fed142 | ||
|
|
21f5d503cd | ||
|
|
18b4f858d9 | ||
|
|
a101cac503 | ||
|
|
3f4c1953f8 | ||
|
|
a599b5b632 | ||
|
|
9fad3597bb | ||
|
|
f4b9889d67 | ||
|
|
d27b4f6905 | ||
|
|
855c7e623a | ||
|
|
a7e7bd0230 | ||
|
|
1d46897086 | ||
|
|
7af0721a75 | ||
|
|
884d738de9 | ||
|
|
ef71ca8a76 | ||
|
|
d4df12505b | ||
|
|
670d9b3913 | ||
|
|
19df5355ba | ||
|
|
8ca1c3be1f | ||
|
|
ed19aac553 | ||
|
|
7a67fbb980 | ||
|
|
d80b250650 | ||
|
|
300f2c2fb0 | ||
|
|
c2f108bf44 | ||
|
|
15abcd77d0 | ||
|
|
462f82e3fb | ||
|
|
eade3ce341 | ||
|
|
b4487159ed | ||
|
|
d12022266e | ||
|
|
13074a0f72 | ||
|
|
dc2c0c1027 | ||
|
|
97ed84127e | ||
|
|
d3f097f675 | ||
|
|
402e0576c8 | ||
|
|
aa8d41eecf | ||
|
|
54b53dbe56 | ||
|
|
2214c47c0c | ||
|
|
ed6275b0e8 | ||
|
|
97d062ffc2 | ||
|
|
c57c33c088 | ||
|
|
025d31102e | ||
|
|
cd96b6ab06 | ||
|
|
9358f86d43 | ||
|
|
7e51e067cd | ||
|
|
10ae66d1d0 | ||
|
|
cbdd1a2538 | ||
|
|
829292399b | ||
|
|
f7c7b8050f | ||
|
|
a3be59bd02 | ||
|
|
49e90a27b8 | ||
|
|
b559a13bab | ||
|
|
4d682f014d | ||
|
|
1a346e6b5a | ||
|
|
37a53748a2 | ||
|
|
9c2c932e75 | ||
|
|
c853d5bdf8 | ||
|
|
1f98e349dc | ||
|
|
883c14bf0d | ||
|
|
178abf0f83 | ||
|
|
c242fedfee | ||
|
|
922ccdfb27 | ||
|
|
965adddf8d | ||
|
|
8020b32541 | ||
|
|
33d798727c | ||
|
|
a0949c4e36 | ||
|
|
f00a0367cf | ||
|
|
eb5b83564f | ||
|
|
6b9dce10cf | ||
|
|
5d789bf96c | ||
|
|
17431734fb | ||
|
|
63c80fa526 | ||
|
|
2b79d437fc | ||
|
|
1e6041f99e | ||
|
|
fad4032a78 | ||
|
|
36ad006021 | ||
|
|
f6db690454 | ||
|
|
26575cfa0d | ||
|
|
73be639397 | ||
|
|
0ebd067bc2 | ||
|
|
49b3c33fbc | ||
|
|
4a5eab13c9 | ||
|
|
00ce6ce755 | ||
|
|
0aa95889aa | ||
|
|
3fdec074c6 | ||
|
|
29c6863b00 | ||
|
|
97eacb58a6 | ||
|
|
67bb28d027 | ||
|
|
513ed31b08 | ||
|
|
da5d95c7e5 | ||
|
|
7b9450807b | ||
|
|
8bc3631964 | ||
|
|
ab038983e5 | ||
|
|
79cff1ec9d | ||
|
|
4893cd0b03 | ||
|
|
b8fbeed785 | ||
|
|
8a9668bc79 | ||
|
|
d02d542207 | ||
|
|
4c797c5eb1 | ||
|
|
3fa2690277 | ||
|
|
67d482bad6 | ||
|
|
60981ba224 | ||
|
|
cb8ebfccb6 | ||
|
|
e5f0b71cf0 | ||
|
|
4218d81c49 | ||
|
|
9fa422ebf3 | ||
|
|
3ec488675f | ||
|
|
78b7d07500 |
93
.github/workflows/beta.yml
vendored
93
.github/workflows/beta.yml
vendored
@@ -15,46 +15,109 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
||||||
|
- name: Download last SHA artifact
|
||||||
|
uses: dawidd6/action-download-artifact@v3
|
||||||
|
with:
|
||||||
|
workflow: beta.yml
|
||||||
|
name: last-sha
|
||||||
|
path: .
|
||||||
|
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Get Commits Since Last Run
|
||||||
|
run: |
|
||||||
|
if [ -f last_sha.txt ]; then
|
||||||
|
LAST_SHA=$(cat last_sha.txt)
|
||||||
|
else
|
||||||
|
# Fallback to first commit if no previous SHA available
|
||||||
|
LAST_SHA=$(git rev-list --max-parents=0 HEAD)
|
||||||
|
fi
|
||||||
|
echo "Commits since $LAST_SHA:"
|
||||||
|
# Accumulate commit logs in a shell variable
|
||||||
|
COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an")
|
||||||
|
# URL-encode the newline characters for GitHub Actions
|
||||||
|
COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}"
|
||||||
|
COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}"
|
||||||
|
COMMIT_LOGS="${COMMIT_LOGS//$'\r'/'%0D'}"
|
||||||
|
# Append the encoded commit logs to the COMMIT_LOG environment variable
|
||||||
|
echo "COMMIT_LOG=${COMMIT_LOGS}" >> $GITHUB_ENV
|
||||||
|
# Debugging: Print the variable to check its content
|
||||||
|
echo "$COMMIT_LOGS"
|
||||||
|
shell: /usr/bin/bash -e {0}
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
|
||||||
|
- name: Save Current SHA for Next Run
|
||||||
|
run: echo ${{ github.sha }} > last_sha.txt
|
||||||
|
|
||||||
- name: Set variables
|
- name: Set variables
|
||||||
run: |
|
run: |
|
||||||
VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"')
|
VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"')
|
||||||
SHA=${{ github.sha }}
|
SHA=${{ github.sha }}
|
||||||
VERSION="$VER.${SHA:0:7}"
|
VERSION="$VER+${SHA:0:7}"
|
||||||
echo "Version $VERSION"
|
echo "Version $VERSION"
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup JDK 17
|
- name: Setup JDK 17
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: 17
|
java-version: 17
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
|
||||||
- name: Decode Keystore File
|
- name: Decode Keystore File
|
||||||
run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore
|
run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore
|
||||||
|
|
||||||
- name: List files in the directory
|
- name: List files in the directory
|
||||||
run: ls -l
|
run: ls -l
|
||||||
|
|
||||||
- name: Make gradlew executable
|
- name: Make gradlew executable
|
||||||
run: chmod +x ./gradlew
|
run: chmod +x ./gradlew
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew assembleDebug -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
|
run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Upload a Build Artifact
|
- name: Upload a Build Artifact
|
||||||
uses: actions/upload-artifact@v3.0.0
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: Dantotsu
|
name: Dantotsu
|
||||||
path: "app/build/outputs/apk/debug/app-debug.apk"
|
path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
|
||||||
|
|
||||||
- name: Upload APK to Discord
|
- name: Upload APK to Discord and Telegram
|
||||||
|
if: ${{ github.repository == 'rebelonion/Dantotsu' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
contentbody=$( jq -Rsa . <<< "${{ github.event.head_commit.message }}" )
|
#Discord
|
||||||
curl -F "payload_json={\"content\":\" Debug-Build: <@719439449423085569> **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }}
|
commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/')
|
||||||
|
# Truncate commit messages if they are too long
|
||||||
|
max_length=1900 # Adjust this value as needed
|
||||||
|
if [ ${#commit_messages} -gt $max_length ]; then
|
||||||
|
commit_messages="${commit_messages:0:$max_length}... (truncated)"
|
||||||
|
fi
|
||||||
|
contentbody=$( jq -nc --arg msg "Alpha-Build: <@714249925248024617> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' )
|
||||||
|
curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|
||||||
|
#Telegram
|
||||||
|
curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \
|
||||||
|
-F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \
|
||||||
|
-F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \
|
||||||
|
https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument
|
||||||
|
|
||||||
|
env:
|
||||||
|
COMMIT_LOG: ${{ env.COMMIT_LOG }}
|
||||||
|
VERSION: ${{ env.VERSION }}
|
||||||
|
|
||||||
|
- name: Upload Current SHA as Artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: last-sha
|
||||||
|
path: last_sha.txt
|
||||||
|
|
||||||
|
|
||||||
- name: Delete Old Pre-Releases
|
- name: Delete Old Pre-Releases
|
||||||
id: delete-pre-releases
|
id: delete-pre-releases
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,6 +8,9 @@ local.properties
|
|||||||
# Log/OS Files
|
# Log/OS Files
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
apikey.properties
|
||||||
|
|
||||||
# Android Studio generated files and folders
|
# Android Studio generated files and folders
|
||||||
captures/
|
captures/
|
||||||
.externalNativeBuild/
|
.externalNativeBuild/
|
||||||
|
|||||||
4
app/.gitignore
vendored
4
app/.gitignore
vendored
@@ -1,4 +1,8 @@
|
|||||||
/build
|
/build
|
||||||
/debug
|
/debug
|
||||||
/debug/output-metadata.json
|
/debug/output-metadata.json
|
||||||
|
/alpha
|
||||||
|
/alpha/output-metadata.json
|
||||||
|
/google/*
|
||||||
|
/fdroid/*
|
||||||
/release
|
/release
|
||||||
100
app/build.gradle
100
app/build.gradle
@@ -1,44 +1,66 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'com.google.gms.google-services'
|
|
||||||
id 'com.google.firebase.crashlytics'
|
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'kotlinx-serialization'
|
id 'kotlinx-serialization'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id 'com.google.devtools.ksp'
|
id 'com.google.devtools.ksp'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def gitCommitHash = providers.exec {
|
|
||||||
commandLine("git", "rev-parse", "--verify", "--short", "HEAD")
|
|
||||||
}.standardOutput.asText.get().trim()
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ani.dantotsu"
|
applicationId "ani.dantotsu"
|
||||||
minSdk 23
|
minSdk 21
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "2.0.0-beta01-iv1"
|
versionName "3.0.0"
|
||||||
|
versionCode 300000000
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions += "store"
|
||||||
|
productFlavors {
|
||||||
|
fdroid {
|
||||||
|
// F-Droid specific configuration
|
||||||
|
dimension "store"
|
||||||
|
versionNameSuffix "-fdroid"
|
||||||
|
}
|
||||||
|
google {
|
||||||
|
// Google Play specific configuration
|
||||||
|
dimension "store"
|
||||||
|
isDefault true
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
apply plugin: 'com.google.firebase.crashlytics'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
alpha {
|
||||||
|
applicationIdSuffix ".beta" // keep as beta by popular request
|
||||||
|
versionNameSuffix "-alpha01"
|
||||||
|
manifestPlaceholders.icon_placeholder = "@mipmap/ic_launcher_alpha"
|
||||||
|
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round"
|
||||||
|
debuggable System.getenv("CI") == null
|
||||||
|
isDefault true
|
||||||
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta", icon_placeholder_round: "@mipmap/ic_launcher_beta_round"]
|
versionNameSuffix "-beta01"
|
||||||
debuggable System.getenv("CI") == null
|
manifestPlaceholders.icon_placeholder = "@mipmap/ic_launcher_beta"
|
||||||
|
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_beta_round"
|
||||||
|
debuggable false
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher", icon_placeholder_round: "@mipmap/ic_launcher_round"]
|
manifestPlaceholders.icon_placeholder = "@mipmap/ic_launcher"
|
||||||
|
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_round"
|
||||||
debuggable false
|
debuggable false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-gson.pro', 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-gson.pro', 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
|
buildConfig true
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
@@ -52,9 +74,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
// FireBase
|
||||||
|
googleImplementation platform('com.google.firebase:firebase-bom:32.7.4')
|
||||||
|
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1'
|
||||||
|
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2'
|
||||||
// Core
|
// Core
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.browser:browser:1.7.0'
|
implementation 'androidx.browser:browser:1.8.0'
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
@@ -63,11 +90,11 @@ dependencies {
|
|||||||
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'com.google.code.gson:gson:2.10'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.github.Blatzar:NiceHttp:0.4.4'
|
implementation 'com.github.Blatzar:NiceHttp:0.4.4'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.webkit:webkit:1.9.0'
|
implementation 'androidx.webkit:webkit:1.10.0'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
ext.glide_version = '4.16.0'
|
ext.glide_version = '4.16.0'
|
||||||
@@ -77,13 +104,8 @@ dependencies {
|
|||||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
|
|
||||||
// FireBase
|
|
||||||
implementation platform('com.google.firebase:firebase-bom:32.2.3')
|
|
||||||
implementation 'com.google.firebase:firebase-analytics-ktx:21.5.0'
|
|
||||||
implementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.0'
|
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
ext.exo_version = '1.2.0'
|
ext.exo_version = '1.3.0'
|
||||||
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
||||||
implementation "androidx.media3:media3-ui:$exo_version"
|
implementation "androidx.media3:media3-ui:$exo_version"
|
||||||
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
||||||
@@ -96,14 +118,30 @@ dependencies {
|
|||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
implementation 'nl.joery.animatedbottombar:library:1.1.0'
|
//implementation 'nl.joery.animatedbottombar:library:1.1.0'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'com.github.rebelonion:AnimatedBottomBar:v1.1.0'
|
||||||
implementation 'com.flaviofaria:kenburnsview:1.0.7'
|
implementation 'com.flaviofaria:kenburnsview:1.0.7'
|
||||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||||
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
||||||
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
||||||
|
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc'
|
||||||
|
|
||||||
|
// Markwon
|
||||||
|
ext.markwon_version = '4.6.2'
|
||||||
|
implementation "io.noties.markwon:core:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:editor:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:ext-tables:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:ext-tasklist:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:html:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:image-glide:$markwon_version"
|
||||||
|
|
||||||
|
// Groupie
|
||||||
|
ext.groupie_version = '2.10.1'
|
||||||
|
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
|
||||||
|
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
|
||||||
|
|
||||||
// string matching
|
// string matching
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
@@ -115,13 +153,13 @@ dependencies {
|
|||||||
implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07'
|
implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07'
|
||||||
implementation 'com.squareup.logcat:logcat:0.1'
|
implementation 'com.squareup.logcat:logcat:0.1'
|
||||||
implementation 'com.github.inorichi.injekt:injekt-core:65b0440'
|
implementation 'com.github.inorichi.injekt:injekt-core:65b0440'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
|
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11'
|
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||||
implementation 'com.squareup.okio:okio:3.7.0'
|
implementation 'com.squareup.okio:okio:3.8.0'
|
||||||
implementation 'ch.acra:acra-http:5.11.3'
|
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12'
|
||||||
implementation 'org.jsoup:jsoup:1.15.4'
|
implementation 'org.jsoup:jsoup:1.16.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.3'
|
||||||
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
||||||
implementation 'com.github.tachiyomiorg:unifile:17bec43'
|
implementation 'com.github.tachiyomiorg:unifile:17bec43'
|
||||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||||
|
|||||||
@@ -43,6 +43,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "ani.dantotsu.alpha"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyCiXo_q4S2ofA5oCztsoLnlDqJi3GtTJjY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_info": {
|
"client_info": {
|
||||||
"mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf",
|
"mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf",
|
||||||
|
|||||||
19
app/proguard-rules.pro
vendored
19
app/proguard-rules.pro
vendored
@@ -43,6 +43,25 @@
|
|||||||
public static <1> INSTANCE;
|
public static <1> INSTANCE;
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
|
-keep class ani.dantotsu.** { *; }
|
||||||
|
-keep class ani.dantotsu.download.DownloadsManager { *; }
|
||||||
|
-keepattributes Signature
|
||||||
|
-keep class uy.kohesive.injekt.** { *; }
|
||||||
|
-keep class eu.kanade.tachiyomi.** { *; }
|
||||||
|
-keep class kotlin.** { *; }
|
||||||
|
-dontwarn kotlin.**
|
||||||
|
-keep class kotlinx.** { *; }
|
||||||
|
-keepclassmembers class uy.kohesive.injekt.api.FullTypeReference {
|
||||||
|
<init>(...);
|
||||||
|
}
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes EnclosingMethod
|
||||||
|
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||||
|
-keep class org.jsoup.** { *; }
|
||||||
|
-keepclassmembers class org.jsoup.nodes.Document { *; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
||||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||||
|
|||||||
376
app/src/alpha/res/drawable/anim_splash.xml
Normal file
376
app/src/alpha/res/drawable/anim_splash.xml
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt">
|
||||||
|
<aapt:attr name="android:drawable">
|
||||||
|
<vector
|
||||||
|
android:name="vector"
|
||||||
|
android:width="768dp"
|
||||||
|
android:height="768dp"
|
||||||
|
android:viewportWidth="768"
|
||||||
|
android:viewportHeight="768">
|
||||||
|
<group
|
||||||
|
android:name="wrapper"
|
||||||
|
android:pivotX="384"
|
||||||
|
android:pivotY="384">
|
||||||
|
<clip-path
|
||||||
|
android:name="clippath"
|
||||||
|
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z" />
|
||||||
|
<group android:name="group">
|
||||||
|
<group android:name="group_1">
|
||||||
|
<path
|
||||||
|
android:name="path"
|
||||||
|
android:fillColor="#ED0021"
|
||||||
|
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
<group
|
||||||
|
android:name="group_12"
|
||||||
|
android:pivotX="384"
|
||||||
|
android:pivotY="384">
|
||||||
|
<path
|
||||||
|
android:name="path_2"
|
||||||
|
android:fillColor="#D40037"
|
||||||
|
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group android:name="group_2">
|
||||||
|
<group android:name="group_7">
|
||||||
|
<group android:name="group_10">
|
||||||
|
<group
|
||||||
|
android:name="group_11"
|
||||||
|
android:pivotX="94"
|
||||||
|
android:pivotY="440"
|
||||||
|
android:rotation="-90">
|
||||||
|
<path
|
||||||
|
android:name="path_1"
|
||||||
|
android:fillColor="#A70060"
|
||||||
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
<clip-path
|
||||||
|
android:name="mask_2"
|
||||||
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_13"
|
||||||
|
android:pivotX="384"
|
||||||
|
android:pivotY="384">
|
||||||
|
<clip-path
|
||||||
|
android:name="mask_1"
|
||||||
|
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z" />
|
||||||
|
<group
|
||||||
|
android:name="group_9"
|
||||||
|
android:pivotX="94"
|
||||||
|
android:pivotY="440"
|
||||||
|
android:rotation="-90">
|
||||||
|
<path
|
||||||
|
android:name="path_3"
|
||||||
|
android:fillColor="#BF005E"
|
||||||
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_6"
|
||||||
|
android:pivotX="94"
|
||||||
|
android:pivotY="440"
|
||||||
|
android:rotation="-5"
|
||||||
|
android:scaleX="1.2"
|
||||||
|
android:scaleY="1.2" />
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_8"
|
||||||
|
android:pivotX="94"
|
||||||
|
android:pivotY="440"
|
||||||
|
android:rotation="-90">
|
||||||
|
<group
|
||||||
|
android:name="group_14"
|
||||||
|
android:pivotX="94"
|
||||||
|
android:pivotY="440">
|
||||||
|
<path
|
||||||
|
android:name="path_4"
|
||||||
|
android:fillColor="#C70051"
|
||||||
|
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_3"
|
||||||
|
android:translateX="-360">
|
||||||
|
<path
|
||||||
|
android:name="path_6"
|
||||||
|
android:fillColor="#251528"
|
||||||
|
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_4"
|
||||||
|
android:pivotX="384"
|
||||||
|
android:pivotY="384"
|
||||||
|
android:scaleX="1.5"
|
||||||
|
android:scaleY="1.5">
|
||||||
|
<path
|
||||||
|
android:name="path_5"
|
||||||
|
android:fillColor="#251528"
|
||||||
|
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
<group
|
||||||
|
android:name="group_5"
|
||||||
|
android:pivotX="384"
|
||||||
|
android:pivotY="384"
|
||||||
|
android:rotation="-15"
|
||||||
|
android:scaleX="3"
|
||||||
|
android:scaleY="3">
|
||||||
|
<path
|
||||||
|
android:name="path_7"
|
||||||
|
android:fillAlpha="0"
|
||||||
|
android:fillColor="#FFD8DF"
|
||||||
|
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
</aapt:attr>
|
||||||
|
<target android:name="wrapper">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="500"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="500"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_6">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="-10"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="1.2"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="1.2"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_3">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="400"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="translateX"
|
||||||
|
android:startOffset="250"
|
||||||
|
android:valueFrom="-360"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_4">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:startOffset="400"
|
||||||
|
android:valueFrom="1.5"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:startOffset="400"
|
||||||
|
android:valueFrom="1.5"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="path_7">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="fillAlpha"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_5">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="-45"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="3"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="3"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_8">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="100"
|
||||||
|
android:valueFrom="-90"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_9">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="100"
|
||||||
|
android:valueFrom="-90"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_11">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="100"
|
||||||
|
android:valueFrom="-90"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_12">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_13">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleX"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="group_14">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="200"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="350"
|
||||||
|
android:valueFrom="5"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="100"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:startOffset="250"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="5"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
</animated-vector>
|
||||||
4
app/src/alpha/res/values/strings.xml
Normal file
4
app/src/alpha/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Dantotsu α</string>
|
||||||
|
</resources>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<animated-vector
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt">
|
xmlns:aapt="http://schemas.android.com/aapt">
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector
|
||||||
@@ -14,23 +13,23 @@
|
|||||||
android:pivotY="384">
|
android:pivotY="384">
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="clippath"
|
android:name="clippath"
|
||||||
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z"/>
|
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z" />
|
||||||
<group android:name="group">
|
<group android:name="group">
|
||||||
<group android:name="group_1">
|
<group android:name="group_1">
|
||||||
<path
|
<path
|
||||||
android:name="path"
|
android:name="path"
|
||||||
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
|
|
||||||
android:fillColor="#6901fd"
|
android:fillColor="#6901fd"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
<group
|
<group
|
||||||
android:name="group_12"
|
android:name="group_12"
|
||||||
android:pivotX="384"
|
android:pivotX="384"
|
||||||
android:pivotY="384">
|
android:pivotY="384">
|
||||||
<path
|
<path
|
||||||
android:name="path_2"
|
android:name="path_2"
|
||||||
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
|
|
||||||
android:fillColor="#4800e5"
|
android:fillColor="#4800e5"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group android:name="group_2">
|
<group android:name="group_2">
|
||||||
@@ -43,12 +42,12 @@
|
|||||||
android:rotation="-90">
|
android:rotation="-90">
|
||||||
<path
|
<path
|
||||||
android:name="path_1"
|
android:name="path_1"
|
||||||
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
|
||||||
android:fillColor="#2000bd"
|
android:fillColor="#2000bd"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="mask_2"
|
android:name="mask_2"
|
||||||
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"/>
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
android:pivotY="384">
|
android:pivotY="384">
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="mask_1"
|
android:name="mask_1"
|
||||||
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"/>
|
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z" />
|
||||||
<group
|
<group
|
||||||
android:name="group_9"
|
android:name="group_9"
|
||||||
android:pivotX="94"
|
android:pivotX="94"
|
||||||
@@ -65,18 +64,18 @@
|
|||||||
android:rotation="-90">
|
android:rotation="-90">
|
||||||
<path
|
<path
|
||||||
android:name="path_3"
|
android:name="path_3"
|
||||||
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
|
||||||
android:fillColor="#1e00d1"
|
android:fillColor="#1e00d1"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
android:name="group_6"
|
android:name="group_6"
|
||||||
android:pivotX="94"
|
android:pivotX="94"
|
||||||
android:pivotY="440"
|
android:pivotY="440"
|
||||||
|
android:rotation="-5"
|
||||||
android:scaleX="1.2"
|
android:scaleX="1.2"
|
||||||
android:scaleY="1.2"
|
android:scaleY="1.2" />
|
||||||
android:rotation="-5"/>
|
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
android:name="group_8"
|
android:name="group_8"
|
||||||
@@ -89,9 +88,9 @@
|
|||||||
android:pivotY="440">
|
android:pivotY="440">
|
||||||
<path
|
<path
|
||||||
android:name="path_4"
|
android:name="path_4"
|
||||||
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
|
|
||||||
android:fillColor="#2900da"
|
android:fillColor="#2900da"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -100,9 +99,9 @@
|
|||||||
android:translateX="-360">
|
android:translateX="-360">
|
||||||
<path
|
<path
|
||||||
android:name="path_6"
|
android:name="path_6"
|
||||||
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
|
|
||||||
android:fillColor="#1f1f30"
|
android:fillColor="#1f1f30"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
android:name="group_4"
|
android:name="group_4"
|
||||||
@@ -112,23 +111,23 @@
|
|||||||
android:scaleY="1.5">
|
android:scaleY="1.5">
|
||||||
<path
|
<path
|
||||||
android:name="path_5"
|
android:name="path_5"
|
||||||
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
|
|
||||||
android:fillColor="#1f1f30"
|
android:fillColor="#1f1f30"
|
||||||
android:strokeWidth="1"/>
|
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
android:name="group_5"
|
android:name="group_5"
|
||||||
android:pivotX="384"
|
android:pivotX="384"
|
||||||
android:pivotY="384"
|
android:pivotY="384"
|
||||||
|
android:rotation="-15"
|
||||||
android:scaleX="3"
|
android:scaleX="3"
|
||||||
android:scaleY="3"
|
android:scaleY="3">
|
||||||
android:rotation="-15">
|
|
||||||
<path
|
<path
|
||||||
android:name="path_7"
|
android:name="path_7"
|
||||||
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
|
|
||||||
android:fillColor="#efe7ff"
|
|
||||||
android:fillAlpha="0"
|
android:fillAlpha="0"
|
||||||
android:strokeWidth="1"/>
|
android:fillColor="#efe7ff"
|
||||||
|
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
|
||||||
|
android:strokeWidth="1" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -138,19 +137,19 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="500"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:duration="500"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:propertyName="scaleY"
|
|
||||||
android:duration="500"
|
android:duration="500"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
@@ -158,177 +157,177 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="-10"
|
android:valueFrom="-10"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="300"
|
|
||||||
android:valueFrom="1.2"
|
android:valueFrom="1.2"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleY"
|
android:propertyName="scaleY"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="300"
|
|
||||||
android:valueFrom="1.2"
|
android:valueFrom="1.2"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_3">
|
<target android:name="group_3">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="400"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
android:propertyName="translateX"
|
android:propertyName="translateX"
|
||||||
android:startOffset="250"
|
android:startOffset="250"
|
||||||
android:duration="400"
|
|
||||||
android:valueFrom="-360"
|
android:valueFrom="-360"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_4">
|
<target android:name="group_4">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:startOffset="400"
|
android:startOffset="400"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="1.5"
|
android:valueFrom="1.5"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleY"
|
android:propertyName="scaleY"
|
||||||
android:startOffset="400"
|
android:startOffset="400"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="1.5"
|
android:valueFrom="1.5"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="path_7">
|
<target android:name="path_7">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="fillAlpha"
|
android:propertyName="fillAlpha"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_5">
|
<target android:name="group_5">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="-45"
|
android:valueFrom="-45"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="3"
|
android:valueFrom="3"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleY"
|
android:propertyName="scaleY"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="3"
|
android:valueFrom="3"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_8">
|
<target android:name="group_8">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="100"
|
android:startOffset="100"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="-90"
|
android:valueFrom="-90"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_9">
|
<target android:name="group_9">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="100"
|
android:startOffset="100"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="-90"
|
android:valueFrom="-90"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:propertyName="scaleY"
|
|
||||||
android:duration="350"
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
|
android:propertyName="scaleY"
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_11">
|
<target android:name="group_11">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="350"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="100"
|
android:startOffset="100"
|
||||||
android:duration="350"
|
|
||||||
android:valueFrom="-90"
|
android:valueFrom="-90"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
<target android:name="group_12">
|
<target android:name="group_12">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:propertyName="scaleY"
|
|
||||||
android:duration="550"
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
@@ -336,19 +335,19 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
android:propertyName="scaleX"
|
android:propertyName="scaleX"
|
||||||
android:duration="550"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:propertyName="scaleY"
|
|
||||||
android:duration="550"
|
android:duration="550"
|
||||||
|
android:interpolator="@android:anim/overshoot_interpolator"
|
||||||
|
android:propertyName="scaleY"
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="1"
|
android:valueTo="1"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/overshoot_interpolator"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
@@ -356,21 +355,21 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="200"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="350"
|
android:startOffset="350"
|
||||||
android:duration="200"
|
|
||||||
android:valueFrom="5"
|
android:valueFrom="5"
|
||||||
android:valueTo="0"
|
android:valueTo="0"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="100"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||||
android:propertyName="rotation"
|
android:propertyName="rotation"
|
||||||
android:startOffset="250"
|
android:startOffset="250"
|
||||||
android:duration="100"
|
|
||||||
android:valueFrom="0"
|
android:valueFrom="0"
|
||||||
android:valueTo="5"
|
android:valueTo="5"
|
||||||
android:valueType="floatType"
|
android:valueType="floatType" />
|
||||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ani.dantotsu.connections.crashlytics
|
||||||
|
|
||||||
|
class CrashlyticsFactory {
|
||||||
|
companion object {
|
||||||
|
fun createCrashlytics(): CrashlyticsInterface {
|
||||||
|
return CrashlyticsStub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
app/src/fdroid/java/ani/dantotsu/others/AppUpdater.kt
Normal file
9
app/src/fdroid/java/ani/dantotsu/others/AppUpdater.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package ani.dantotsu.others
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
|
||||||
|
object AppUpdater {
|
||||||
|
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ani.dantotsu.connections.crashlytics
|
||||||
|
|
||||||
|
class CrashlyticsFactory {
|
||||||
|
companion object {
|
||||||
|
fun createCrashlytics(): CrashlyticsInterface {
|
||||||
|
return FirebaseCrashlytics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ani.dantotsu.connections.crashlytics
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.firebase.FirebaseApp
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||||
|
import com.google.firebase.ktx.Firebase
|
||||||
|
import com.google.firebase.ktx.app
|
||||||
|
|
||||||
|
class FirebaseCrashlytics : CrashlyticsInterface {
|
||||||
|
override fun initialize(context: Context) {
|
||||||
|
FirebaseApp.initializeApp(context)
|
||||||
|
}
|
||||||
|
override fun logException(e: Throwable) {
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun log(message: String) {
|
||||||
|
FirebaseCrashlytics.getInstance().log(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserId(id: String) {
|
||||||
|
Firebase.crashlytics.setUserId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCustomKey(key: String, value: String) {
|
||||||
|
FirebaseCrashlytics.getInstance().setCustomKey(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCrashlyticsCollectionEnabled(enabled: Boolean) {
|
||||||
|
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,12 +11,21 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.BuildConfig
|
||||||
import io.noties.markwon.Markwon
|
import ani.dantotsu.Mapper
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.buildMarkwon
|
||||||
|
import ani.dantotsu.client
|
||||||
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.logError
|
||||||
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.tryWithSuspend
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -24,9 +33,8 @@ import kotlinx.serialization.SerialName
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
object AppUpdater {
|
object AppUpdater {
|
||||||
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
||||||
@@ -38,9 +46,10 @@ object AppUpdater {
|
|||||||
.parsed<JsonArray>().map {
|
.parsed<JsonArray>().map {
|
||||||
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
|
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
|
||||||
}
|
}
|
||||||
val r = res.filter { it.prerelease }.maxByOrNull {
|
val r = res.filter { it.prerelease }.filter { !it.tagName.contains("fdroid") }
|
||||||
it.timeStamp()
|
.maxByOrNull {
|
||||||
} ?: throw Exception("No Pre Release Found")
|
it.timeStamp()
|
||||||
|
} ?: throw Exception("No Pre Release Found")
|
||||||
val v = r.tagName.substringAfter("v", "")
|
val v = r.tagName.substringAfter("v", "")
|
||||||
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
|
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
|
||||||
} else {
|
} else {
|
||||||
@@ -49,8 +58,8 @@ object AppUpdater {
|
|||||||
res to res.substringAfter("# ").substringBefore("\n")
|
res to res.substringAfter("# ").substringBefore("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger("Git Version : $version")
|
Logger.log("Git Version : $version")
|
||||||
val dontShow = loadData("dont_ask_for_update_$version") ?: false
|
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
|
||||||
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
|
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
|
||||||
CustomBottomDialog.newInstance().apply {
|
CustomBottomDialog.newInstance().apply {
|
||||||
setTitleText(
|
setTitleText(
|
||||||
@@ -60,8 +69,7 @@ object AppUpdater {
|
|||||||
)
|
)
|
||||||
addView(
|
addView(
|
||||||
TextView(activity).apply {
|
TextView(activity).apply {
|
||||||
val markWon = Markwon.builder(activity)
|
val markWon = buildMarkwon(activity, false)
|
||||||
.usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
|
|
||||||
markWon.setMarkdown(this, md)
|
markWon.setMarkdown(this, md)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -71,7 +79,7 @@ object AppUpdater {
|
|||||||
false
|
false
|
||||||
) { isChecked ->
|
) { isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
saveData("dont_ask_for_update_$version", true)
|
PrefManager.setCustomVal("dont_ask_for_update_$version", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
|
setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
|
||||||
@@ -160,21 +168,8 @@ object AppUpdater {
|
|||||||
DownloadManager.EXTRA_DOWNLOAD_ID, id
|
DownloadManager.EXTRA_DOWNLOAD_ID, id
|
||||||
) ?: id
|
) ?: id
|
||||||
|
|
||||||
val query = DownloadManager.Query()
|
downloadManager.getUriForDownloadedFile(downloadId)?.let {
|
||||||
query.setFilterById(downloadId)
|
openApk(this@downloadUpdate, it)
|
||||||
val c = downloadManager.query(query)
|
|
||||||
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
val columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
|
||||||
if (DownloadManager.STATUS_SUCCESSFUL == c
|
|
||||||
.getInt(columnIndex)
|
|
||||||
) {
|
|
||||||
c.getColumnIndex(DownloadManager.COLUMN_MEDIAPROVIDER_URI)
|
|
||||||
val uri = Uri.parse(
|
|
||||||
c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
|
||||||
)
|
|
||||||
openApk(this@downloadUpdate, uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
@@ -186,19 +181,14 @@ object AppUpdater {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openApk(context: Context, uri: Uri) {
|
private fun openApk(context: Context, uri: Uri) {
|
||||||
try {
|
try {
|
||||||
uri.path?.let {
|
uri.path?.let {
|
||||||
val contentUri = FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
BuildConfig.APPLICATION_ID + ".provider",
|
|
||||||
File(it)
|
|
||||||
)
|
|
||||||
val installIntent = Intent(Intent.ACTION_VIEW).apply {
|
val installIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||||
data = contentUri
|
data = uri
|
||||||
}
|
}
|
||||||
context.startActivity(installIntent)
|
context.startActivity(installIntent)
|
||||||
}
|
}
|
||||||
@@ -10,12 +10,13 @@
|
|||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
|
<uses-permission
|
||||||
|
android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
|
||||||
tools:ignore="LeanbackUsesWifi" />
|
tools:ignore="LeanbackUsesWifi" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
android:name="android.appwidget.provider"
|
android:name="android.appwidget.provider"
|
||||||
android:resource="@xml/currently_airing_widget_info" />
|
android:resource="@xml/currently_airing_widget_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name=".subcriptions.NotificationClickReceiver" />
|
<receiver android:name=".notifications.IncognitoNotificationClickReceiver" />
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -103,7 +104,26 @@
|
|||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.ExtensionsActivity"
|
android:name=".settings.ExtensionsActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateHidden"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".profile.ProfileActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateHidden"
|
||||||
|
android:parentActivityName=".MainActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".profile.FollowActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateHidden"
|
||||||
|
android:parentActivityName=".MainActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".profile.activity.FeedActivity"
|
||||||
|
android:label="Inbox Activity"
|
||||||
|
android:parentActivityName=".MainActivity" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".profile.activity.NotificationActivity"
|
||||||
|
android:label="Inbox Activity"
|
||||||
|
android:parentActivityName=".MainActivity" >
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".others.imagesearch.ImageSearchActivity"
|
android:name=".others.imagesearch.ImageSearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
@@ -116,6 +136,8 @@
|
|||||||
android:name=".media.CalendarActivity"
|
android:name=".media.CalendarActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity android:name=".media.user.ListActivity" />
|
<activity android:name=".media.user.ListActivity" />
|
||||||
|
<activity android:name=".profile.SingleStatActivity"
|
||||||
|
android:parentActivityName=".profile.ProfileActivity"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".media.manga.mangareader.MangaReaderActivity"
|
android:name=".media.manga.mangareader.MangaReaderActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
@@ -126,7 +148,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".media.MediaDetailsActivity"
|
android:name=".media.MediaDetailsActivity"
|
||||||
android:parentActivityName=".MainActivity"
|
android:parentActivityName=".MainActivity"
|
||||||
android:theme="@style/Theme.Dantotsu.NeverCutout" />
|
android:theme="@style/Theme.Dantotsu.NeverCutout"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateHidden"/>
|
||||||
<activity android:name=".media.CharacterDetailsActivity" />
|
<activity android:name=".media.CharacterDetailsActivity" />
|
||||||
<activity android:name=".home.NoInternet" />
|
<activity android:name=".home.NoInternet" />
|
||||||
<activity
|
<activity
|
||||||
@@ -208,6 +231,7 @@
|
|||||||
<data android:host="discord.dantotsu.com" />
|
<data android:host="discord.dantotsu.com" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".connections.anilist.UrlMedia"
|
android:name=".connections.anilist.UrlMedia"
|
||||||
android:configChanges="orientation|screenSize|layoutDirection"
|
android:configChanges="orientation|screenSize|layoutDirection"
|
||||||
@@ -238,6 +262,17 @@
|
|||||||
<data android:host="myanimelist.net" />
|
<data android:host="myanimelist.net" />
|
||||||
<data android:pathPrefix="/anime" />
|
<data android:pathPrefix="/anime" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter android:label="@string/view_profile_in_dantotsu">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="anilist.co" />
|
||||||
|
<data android:pathPrefix="/user" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -253,6 +288,15 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
<data android:pathPattern=".*\\.ani" />
|
||||||
|
<data android:pathPattern=".*\\.sani" />
|
||||||
|
<data android:host="*" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
||||||
@@ -263,14 +307,22 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
<receiver
|
<receiver android:name=".notifications.AlarmPermissionStateReceiver"
|
||||||
android:name=".subcriptions.AlarmReceiver"
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.BootCompletedReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="Aani.dantotsu.ACTION_ALARM" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver android:name=".notifications.anilist.AnilistNotificationReceiver"/>
|
||||||
|
<receiver android:name=".notifications.comment.CommentNotificationReceiver"/>
|
||||||
|
<receiver android:name=".notifications.subscription.SubscriptionNotificationReceiver"/>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="preloaded_fonts"
|
android:name="preloaded_fonts"
|
||||||
@@ -289,13 +341,13 @@
|
|||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".widgets.CurrentlyAiringRemoteViewsService"
|
android:name=".widgets.CurrentlyAiringRemoteViewsService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS"
|
android:exported="true"
|
||||||
android:exported="true" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
<service
|
<service
|
||||||
android:name=".download.video.ExoplayerDownloadService"
|
android:name=".download.video.ExoplayerDownloadService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync">
|
android:foregroundServiceType="dataSync">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@@ -317,19 +369,22 @@
|
|||||||
android:name=".download.novel.NovelDownloaderService"
|
android:name=".download.novel.NovelDownloaderService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
<service android:name=".download.anime.AnimeDownloaderService"
|
<service
|
||||||
|
android:name=".download.anime.AnimeDownloaderService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
<service
|
<service
|
||||||
android:name=".connections.discord.DiscordService"
|
android:name=".connections.discord.DiscordService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
<service android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
|
<service
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
|
||||||
android:exported="true"/>
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
<meta-data
|
||||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
|
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
BIN
app/src/main/ic_launcher_alpha-playstore.png
Normal file
BIN
app/src/main/ic_launcher_alpha-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -8,16 +8,20 @@ import androidx.multidex.MultiDex
|
|||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
||||||
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
|
import ani.dantotsu.notifications.TaskScheduler
|
||||||
import ani.dantotsu.others.DisabledReports
|
import ani.dantotsu.others.DisabledReports
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.parsers.NovelSources
|
import ani.dantotsu.parsers.NovelSources
|
||||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||||
import ani.dantotsu.settings.SettingsActivity
|
import ani.dantotsu.settings.SettingsActivity
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.util.FinalExceptionHandler
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
|
||||||
import com.google.firebase.ktx.Firebase
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
@@ -28,7 +32,6 @@ import kotlinx.coroutines.launch
|
|||||||
import logcat.AndroidLogcatLogger
|
import logcat.AndroidLogcatLogger
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import logcat.LogcatLogger
|
import logcat.LogcatLogger
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@@ -51,36 +54,37 @@ class App : MultiDexApplication() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
|
PrefManager.init(this)
|
||||||
|
Injekt.importModule(AppModule(this))
|
||||||
|
Injekt.importModule(PreferenceModule(this))
|
||||||
|
|
||||||
|
val crashlytics = Injekt.get<CrashlyticsInterface>()
|
||||||
|
crashlytics.initialize(this)
|
||||||
|
|
||||||
|
val useMaterialYou: Boolean = PrefManager.getVal(PrefName.UseMaterialYou)
|
||||||
if (useMaterialYou) {
|
if (useMaterialYou) {
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
//TODO: HarmonizedColors
|
|
||||||
}
|
}
|
||||||
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
||||||
|
|
||||||
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
||||||
getSharedPreferences(
|
(PrefManager.getVal(PrefName.SharedUserID) as Boolean).let {
|
||||||
getString(R.string.preference_file_key),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).getBoolean("shared_user_id", true).let {
|
|
||||||
if (!it) return@let
|
if (!it) return@let
|
||||||
val dUsername = getSharedPreferences(
|
val dUsername = PrefManager.getVal(PrefName.DiscordUserName, null as String?)
|
||||||
getString(R.string.preference_file_key),
|
val aUsername = PrefManager.getVal(PrefName.AnilistUserName, null as String?)
|
||||||
Context.MODE_PRIVATE
|
if (dUsername != null) {
|
||||||
).getString("discord_username", null)
|
crashlytics.setCustomKey("dUsername", dUsername)
|
||||||
val aUsername = getSharedPreferences(
|
}
|
||||||
getString(R.string.preference_file_key),
|
if (aUsername != null) {
|
||||||
Context.MODE_PRIVATE
|
crashlytics.setCustomKey("aUsername", aUsername)
|
||||||
).getString("anilist_username", null)
|
|
||||||
if (dUsername != null || aUsername != null) {
|
|
||||||
Firebase.crashlytics.setUserId("$dUsername - $aUsername")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FirebaseCrashlytics.getInstance().setCustomKey("device Info", SettingsActivity.getDeviceInfo())
|
crashlytics.setCustomKey("device Info", SettingsActivity.getDeviceInfo())
|
||||||
|
|
||||||
Injekt.importModule(AppModule(this))
|
Logger.init(this)
|
||||||
Injekt.importModule(PreferenceModule(this))
|
Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler())
|
||||||
|
Logger.log("App: Logging started")
|
||||||
|
|
||||||
initializeNetwork(baseContext)
|
initializeNetwork(baseContext)
|
||||||
|
|
||||||
@@ -96,29 +100,36 @@ class App : MultiDexApplication() {
|
|||||||
val animeScope = CoroutineScope(Dispatchers.Default)
|
val animeScope = CoroutineScope(Dispatchers.Default)
|
||||||
animeScope.launch {
|
animeScope.launch {
|
||||||
animeExtensionManager.findAvailableExtensions()
|
animeExtensionManager.findAvailableExtensions()
|
||||||
logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
||||||
AnimeSources.init(animeExtensionManager.installedExtensionsFlow, this@App)
|
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
val mangaScope = CoroutineScope(Dispatchers.Default)
|
val mangaScope = CoroutineScope(Dispatchers.Default)
|
||||||
mangaScope.launch {
|
mangaScope.launch {
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
mangaExtensionManager.findAvailableExtensions()
|
||||||
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
||||||
MangaSources.init(mangaExtensionManager.installedExtensionsFlow, this@App)
|
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
val novelScope = CoroutineScope(Dispatchers.Default)
|
val novelScope = CoroutineScope(Dispatchers.Default)
|
||||||
novelScope.launch {
|
novelScope.launch {
|
||||||
novelExtensionManager.findAvailableExtensions()
|
novelExtensionManager.findAvailableExtensions()
|
||||||
logger("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
||||||
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
}
|
val commentsScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
commentsScope.launch {
|
||||||
|
CommentsAPI.fetchAuthToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
|
||||||
|
TaskScheduler.create(this, useAlarmManager).scheduleAllTasks(this)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupNotificationChannels() {
|
private fun setupNotificationChannels() {
|
||||||
try {
|
try {
|
||||||
Notifications.createChannels(this)
|
Notifications.createChannels(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
Logger.log("Failed to modify notification channels")
|
||||||
|
Logger.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,88 @@
|
|||||||
package ani.dantotsu
|
package ani.dantotsu
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.DatePickerDialog
|
import android.app.DatePickerDialog
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources.getSystem
|
import android.content.res.Resources.getSystem
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities.*
|
import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_LOWPAN
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_USB
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_VPN
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.os.SystemClock
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import android.text.InputFilter
|
import android.text.InputFilter
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.view.GestureDetector
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewAnimationUtils
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import android.view.animation.*
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import android.widget.*
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationSet
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.view.animation.ScaleAnimation
|
||||||
|
import android.view.animation.TranslateAnimation
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.DatePicker
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.math.MathUtils.clamp
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.*
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -45,30 +90,67 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import ani.dantotsu.BuildConfig.APPLICATION_ID
|
import ani.dantotsu.BuildConfig.APPLICATION_ID
|
||||||
import ani.dantotsu.connections.anilist.Genre
|
import ani.dantotsu.connections.anilist.Genre
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.databinding.ItemCountDownBinding
|
import ani.dantotsu.databinding.ItemCountDownBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.notifications.IncognitoNotificationClickReceiver
|
||||||
|
import ani.dantotsu.others.SpoilerPlugin
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.subcriptions.NotificationClickReceiver
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
||||||
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestBuilder
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
|
||||||
|
import com.bumptech.glide.load.resource.gif.GifDrawable
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.internal.ViewUtils
|
import com.google.android.material.internal.ViewUtils
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import kotlinx.coroutines.*
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.MarkwonConfiguration
|
||||||
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
|
||||||
|
import io.noties.markwon.ext.tables.TablePlugin
|
||||||
|
import io.noties.markwon.ext.tasklist.TaskListPlugin
|
||||||
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
|
import io.noties.markwon.html.TagHandlerNoOp
|
||||||
|
import io.noties.markwon.image.AsyncDrawable
|
||||||
|
import io.noties.markwon.image.glide.GlideImagesPlugin
|
||||||
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
import java.io.*
|
import uy.kohesive.injekt.Injekt
|
||||||
import java.lang.Runnable
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
import kotlin.math.*
|
import java.util.TimeZone
|
||||||
|
import java.util.Timer
|
||||||
|
import java.util.TimerTask
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.math.log2
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
var statusBarHeight = 0
|
var statusBarHeight = 0
|
||||||
@@ -100,75 +182,31 @@ fun currActivity(): Activity? {
|
|||||||
var loadMedia: Int? = null
|
var loadMedia: Int? = null
|
||||||
var loadIsMAL = false
|
var loadIsMAL = false
|
||||||
|
|
||||||
fun logger(e: Any?, print: Boolean = true) {
|
|
||||||
if (print)
|
|
||||||
println(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveData(fileName: String, data: Any?, context: Context? = null) {
|
|
||||||
tryWith {
|
|
||||||
val a = context ?: currContext()
|
|
||||||
if (a != null) {
|
|
||||||
val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE)
|
|
||||||
val os = ObjectOutputStream(fos)
|
|
||||||
os.writeObject(data)
|
|
||||||
os.close()
|
|
||||||
fos.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T> loadData(fileName: String, context: Context? = null, toast: Boolean = true): T? {
|
|
||||||
val a = context ?: currContext()
|
|
||||||
try {
|
|
||||||
if (a?.fileList() != null)
|
|
||||||
if (fileName in a.fileList()) {
|
|
||||||
val fileIS: FileInputStream = a.openFileInput(fileName)
|
|
||||||
val objIS = ObjectInputStream(fileIS)
|
|
||||||
val data = objIS.readObject() as T
|
|
||||||
objIS.close()
|
|
||||||
fileIS.close()
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (toast) snackString(a?.getString(R.string.error_loading_data, fileName))
|
|
||||||
//try to delete the file
|
|
||||||
try {
|
|
||||||
a?.deleteFile(fileName)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
FirebaseCrashlytics.getInstance().log("Failed to delete file $fileName")
|
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
|
||||||
}
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initActivity(a: Activity) {
|
fun initActivity(a: Activity) {
|
||||||
val window = a.window
|
val window = a.window
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings", toast = false)
|
val darkMode = PrefManager.getVal<Int>(PrefName.DarkMode)
|
||||||
?: UserInterfaceSettings().apply {
|
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
|
||||||
saveData("ui_settings", this)
|
darkMode.apply {
|
||||||
}
|
|
||||||
uiSettings.darkMode.apply {
|
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
when (this) {
|
when (this) {
|
||||||
true -> AppCompatDelegate.MODE_NIGHT_YES
|
2 -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
false -> AppCompatDelegate.MODE_NIGHT_NO
|
1 -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (uiSettings.immersiveMode) {
|
if (immersiveMode) {
|
||||||
if (navBarHeight == 0) {
|
if (navBarHeight == 0) {
|
||||||
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
||||||
?.apply {
|
?.apply {
|
||||||
navBarHeight = this.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
navBarHeight = this.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.hideStatusBar()
|
WindowInsetsControllerCompat(
|
||||||
|
window,
|
||||||
|
window.decorView
|
||||||
|
).hide(WindowInsetsCompat.Type.statusBars())
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && statusBarHeight == 0 && a.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && statusBarHeight == 0 && a.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
window.decorView.rootWindowInsets?.displayCutout?.apply {
|
window.decorView.rootWindowInsets?.displayCutout?.apply {
|
||||||
if (boundingRects.size > 0) {
|
if (boundingRects.size > 0) {
|
||||||
@@ -181,43 +219,73 @@ fun initActivity(a: Activity) {
|
|||||||
val windowInsets =
|
val windowInsets =
|
||||||
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
||||||
if (windowInsets != null) {
|
if (windowInsets != null) {
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
statusBarHeight = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top
|
||||||
statusBarHeight = insets.top
|
navBarHeight =
|
||||||
navBarHeight = insets.bottom
|
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (a !is MainActivity) a.setNavigationTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun Activity.hideSystemBars() {
|
fun Activity.hideSystemBars() {
|
||||||
window.decorView.systemUiVisibility = (
|
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
controller.systemBarsBehavior =
|
||||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
}
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
fun Activity.hideSystemBarsExtendView() {
|
||||||
fun Activity.hideStatusBar() {
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
hideSystemBars()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.showSystemBars() {
|
||||||
|
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||||
|
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||||
|
controller.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.showSystemBarsRetractView() {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||||
|
showSystemBars()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.setNavigationTheme() {
|
||||||
|
val tv = TypedValue()
|
||||||
|
theme.resolveAttribute(android.R.attr.colorBackground, tv, true)
|
||||||
|
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && tv.isColorType)
|
||||||
|
|| (tv.type >= TypedValue.TYPE_FIRST_COLOR_INT && tv.type <= TypedValue.TYPE_LAST_COLOR_INT)
|
||||||
|
) {
|
||||||
|
window.navigationBarColor = tv.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
|
open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
val window = dialog?.window
|
dialog?.window?.let { window ->
|
||||||
val decorView: View = window?.decorView ?: return
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
|
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
|
||||||
if (this.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
|
if (immersiveMode) {
|
||||||
val behavior = BottomSheetBehavior.from(requireView().parent as View)
|
WindowInsetsControllerCompat(
|
||||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
window, window.decorView
|
||||||
|
).hide(WindowInsetsCompat.Type.statusBars())
|
||||||
|
}
|
||||||
|
if (this.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
val behavior = BottomSheetBehavior.from(requireView().parent as View)
|
||||||
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
}
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
val theme = requireContext().theme
|
||||||
|
theme.resolveAttribute(
|
||||||
|
com.google.android.material.R.attr.colorSurface,
|
||||||
|
typedValue,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
window.navigationBarColor = typedValue.data
|
||||||
}
|
}
|
||||||
val typedValue = TypedValue()
|
|
||||||
val theme = requireContext().theme
|
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurfaceInverse, typedValue, true)
|
|
||||||
window.navigationBarColor = typedValue.data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun show(manager: FragmentManager, tag: String?) {
|
override fun show(manager: FragmentManager, tag: String?) {
|
||||||
@@ -231,21 +299,35 @@ fun isOnline(context: Context): Boolean {
|
|||||||
val connectivityManager =
|
val connectivityManager =
|
||||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
return tryWith {
|
return tryWith {
|
||||||
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
return@tryWith if (cap != null) {
|
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
when {
|
return@tryWith if (cap != null) {
|
||||||
cap.hasTransport(TRANSPORT_BLUETOOTH) ||
|
when {
|
||||||
cap.hasTransport(TRANSPORT_CELLULAR) ||
|
cap.hasTransport(TRANSPORT_BLUETOOTH) ||
|
||||||
cap.hasTransport(TRANSPORT_ETHERNET) ||
|
cap.hasTransport(TRANSPORT_CELLULAR) ||
|
||||||
cap.hasTransport(TRANSPORT_LOWPAN) ||
|
cap.hasTransport(TRANSPORT_ETHERNET) ||
|
||||||
cap.hasTransport(TRANSPORT_USB) ||
|
cap.hasTransport(TRANSPORT_LOWPAN) ||
|
||||||
cap.hasTransport(TRANSPORT_VPN) ||
|
cap.hasTransport(TRANSPORT_USB) ||
|
||||||
cap.hasTransport(TRANSPORT_WIFI) ||
|
cap.hasTransport(TRANSPORT_VPN) ||
|
||||||
cap.hasTransport(TRANSPORT_WIFI_AWARE) -> true
|
cap.hasTransport(TRANSPORT_WIFI) ||
|
||||||
|
cap.hasTransport(TRANSPORT_WIFI_AWARE) -> true
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} else false
|
} else false
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
return@tryWith connectivityManager.activeNetworkInfo?.run {
|
||||||
|
type == ConnectivityManager.TYPE_BLUETOOTH ||
|
||||||
|
type == ConnectivityManager.TYPE_ETHERNET ||
|
||||||
|
type == ConnectivityManager.TYPE_MOBILE ||
|
||||||
|
type == ConnectivityManager.TYPE_MOBILE_DUN ||
|
||||||
|
type == ConnectivityManager.TYPE_MOBILE_HIPRI ||
|
||||||
|
type == ConnectivityManager.TYPE_WIFI ||
|
||||||
|
type == ConnectivityManager.TYPE_WIMAX ||
|
||||||
|
type == ConnectivityManager.TYPE_VPN
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +389,7 @@ class InputFilterMinMax(
|
|||||||
val input = (dest.toString() + source.toString()).toDouble()
|
val input = (dest.toString() + source.toString()).toDouble()
|
||||||
if (isInRange(min, max, input)) return null
|
if (isInRange(min, max, input)) return null
|
||||||
} catch (nfe: NumberFormatException) {
|
} catch (nfe: NumberFormatException) {
|
||||||
logger(nfe.stackTraceToString())
|
Logger.log(nfe)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -325,20 +407,20 @@ class InputFilterMinMax(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) :
|
class ZoomOutPageTransformer() :
|
||||||
ViewPager2.PageTransformer {
|
ViewPager2.PageTransformer {
|
||||||
override fun transformPage(view: View, position: Float) {
|
override fun transformPage(view: View, position: Float) {
|
||||||
if (position == 0.0f && uiSettings.layoutAnimations) {
|
if (position == 0.0f && PrefManager.getVal(PrefName.LayoutAnimations)) {
|
||||||
setAnimation(
|
setAnimation(
|
||||||
view.context,
|
view.context,
|
||||||
view,
|
view,
|
||||||
uiSettings,
|
|
||||||
300,
|
300,
|
||||||
floatArrayOf(1.3f, 1f, 1.3f, 1f),
|
floatArrayOf(1.3f, 1f, 1.3f, 1f),
|
||||||
0.5f to 0f
|
0.5f to 0f
|
||||||
)
|
)
|
||||||
ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f)
|
ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f)
|
||||||
.setDuration((200 * uiSettings.animationSpeed).toLong()).start()
|
.setDuration((200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong())
|
||||||
|
.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,12 +428,11 @@ class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) :
|
|||||||
fun setAnimation(
|
fun setAnimation(
|
||||||
context: Context,
|
context: Context,
|
||||||
viewToAnimate: View,
|
viewToAnimate: View,
|
||||||
uiSettings: UserInterfaceSettings,
|
|
||||||
duration: Long = 150,
|
duration: Long = 150,
|
||||||
list: FloatArray = floatArrayOf(0.0f, 1.0f, 0.0f, 1.0f),
|
list: FloatArray = floatArrayOf(0.0f, 1.0f, 0.0f, 1.0f),
|
||||||
pivot: Pair<Float, Float> = 0.5f to 0.5f
|
pivot: Pair<Float, Float> = 0.5f to 0.5f
|
||||||
) {
|
) {
|
||||||
if (uiSettings.layoutAnimations) {
|
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
|
||||||
val anim = ScaleAnimation(
|
val anim = ScaleAnimation(
|
||||||
list[0],
|
list[0],
|
||||||
list[1],
|
list[1],
|
||||||
@@ -362,7 +443,7 @@ fun setAnimation(
|
|||||||
Animation.RELATIVE_TO_SELF,
|
Animation.RELATIVE_TO_SELF,
|
||||||
pivot.second
|
pivot.second
|
||||||
)
|
)
|
||||||
anim.duration = (duration * uiSettings.animationSpeed).toLong()
|
anim.duration = (duration * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong()
|
||||||
anim.setInterpolator(context, R.anim.over_shoot)
|
anim.setInterpolator(context, R.anim.over_shoot)
|
||||||
viewToAnimate.startAnimation(anim)
|
viewToAnimate.startAnimation(anim)
|
||||||
}
|
}
|
||||||
@@ -479,6 +560,7 @@ fun ImageView.loadImage(url: String?, size: Int = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ImageView.loadImage(file: FileUrl?, size: Int = 0) {
|
fun ImageView.loadImage(file: FileUrl?, size: Int = 0) {
|
||||||
|
file?.url = PrefManager.getVal<String>(PrefName.ImageUrl).ifEmpty { file?.url ?: "" }
|
||||||
if (file?.url?.isNotEmpty() == true) {
|
if (file?.url?.isNotEmpty() == true) {
|
||||||
tryWith {
|
tryWith {
|
||||||
val glideUrl = GlideUrl(file.url) { file.headers }
|
val glideUrl = GlideUrl(file.url) { file.headers }
|
||||||
@@ -609,13 +691,28 @@ fun View.circularReveal(ex: Int, ey: Int, subX: Boolean, time: Long) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openLinkInBrowser(link: String?) {
|
fun openLinkInBrowser(link: String?) {
|
||||||
tryWith {
|
link?.let {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
try {
|
||||||
currContext()?.startActivity(intent)
|
val emptyBrowserIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||||
|
data = Uri.fromParts("http", "", null)
|
||||||
|
}
|
||||||
|
val sendIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||||
|
data = Uri.parse(link)
|
||||||
|
selector = emptyBrowserIntent
|
||||||
|
}
|
||||||
|
currContext()!!.startActivity(sendIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
snackString("No browser found")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) {
|
fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Activity) {
|
||||||
FileProvider.getUriForFile(
|
FileProvider.getUriForFile(
|
||||||
context,
|
context,
|
||||||
"$APPLICATION_ID.provider",
|
"$APPLICATION_ID.provider",
|
||||||
@@ -627,6 +724,110 @@ fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun savePrefsToDownloads(
|
||||||
|
title: String,
|
||||||
|
serialized: String,
|
||||||
|
context: Activity,
|
||||||
|
password: CharArray? = null
|
||||||
|
) {
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"$APPLICATION_ID.provider",
|
||||||
|
if (password != null) {
|
||||||
|
savePrefs(
|
||||||
|
serialized,
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
|
||||||
|
title,
|
||||||
|
context,
|
||||||
|
password
|
||||||
|
) ?: return
|
||||||
|
} else {
|
||||||
|
savePrefs(
|
||||||
|
serialized,
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
|
||||||
|
title,
|
||||||
|
context
|
||||||
|
) ?: return
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun savePrefs(serialized: String, path: String, title: String, context: Context): File? {
|
||||||
|
var file = File(path, "$title.ani")
|
||||||
|
var counter = 1
|
||||||
|
while (file.exists()) {
|
||||||
|
file = File(path, "${title}_${counter}.ani")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
file.writeText(serialized)
|
||||||
|
scanFile(file.absolutePath, context)
|
||||||
|
toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath)))
|
||||||
|
file
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackString("Failed to save settings: ${e.localizedMessage}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun savePrefs(
|
||||||
|
serialized: String,
|
||||||
|
path: String,
|
||||||
|
title: String,
|
||||||
|
context: Context,
|
||||||
|
password: CharArray
|
||||||
|
): File? {
|
||||||
|
var file = File(path, "$title.sani")
|
||||||
|
var counter = 1
|
||||||
|
while (file.exists()) {
|
||||||
|
file = File(path, "${title}_${counter}.sani")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
val salt = generateSalt()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val encryptedData = PreferenceKeystore.encryptWithPassword(password, serialized, salt)
|
||||||
|
|
||||||
|
// Combine salt and encrypted data
|
||||||
|
val dataToSave = salt + encryptedData
|
||||||
|
|
||||||
|
file.writeBytes(dataToSave)
|
||||||
|
scanFile(file.absolutePath, context)
|
||||||
|
toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath)))
|
||||||
|
file
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackString("Failed to save settings: ${e.localizedMessage}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadsPermission(activity: AppCompatActivity): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) return true
|
||||||
|
val permissions = arrayOf(
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
val requiredPermissions = permissions.filter {
|
||||||
|
ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
return if (requiredPermissions.isNotEmpty()) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
requiredPermissions,
|
||||||
|
DOWNLOADS_PERMISSION_REQUEST_CODE
|
||||||
|
)
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val DOWNLOADS_PERMISSION_REQUEST_CODE = 100
|
||||||
|
|
||||||
fun shareImage(title: String, bitmap: Bitmap, context: Context) {
|
fun shareImage(title: String, bitmap: Bitmap, context: Context) {
|
||||||
|
|
||||||
val contentUri = FileProvider.getUriForFile(
|
val contentUri = FileProvider.getUriForFile(
|
||||||
@@ -659,7 +860,7 @@ fun saveImage(image: Bitmap, path: String, imageFileName: String): File? {
|
|||||||
|
|
||||||
private fun scanFile(path: String, context: Context) {
|
private fun scanFile(path: String, context: Context) {
|
||||||
MediaScannerConnection.scanFile(context, arrayOf(path), null) { p, _ ->
|
MediaScannerConnection.scanFile(context, arrayOf(path), null) { p, _ ->
|
||||||
logger("Finished scanning $p")
|
Logger.log("Finished scanning $p")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,7 +892,9 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
|
|||||||
val clipboard = getSystemService(activity, ClipboardManager::class.java)
|
val clipboard = getSystemService(activity, ClipboardManager::class.java)
|
||||||
val clip = ClipData.newPlainText("label", string)
|
val clip = ClipData.newPlainText("label", string)
|
||||||
clipboard?.setPrimaryClip(clip)
|
clipboard?.setPrimaryClip(clip)
|
||||||
if (toast) snackString(activity.getString(R.string.copied_text, string))
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||||
|
if (toast) snackString(activity.getString(R.string.copied_text, string))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
@@ -743,10 +946,11 @@ fun MutableMap<String, Genre>.checkGenreTime(genre: String): Boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
|
fun setSlideIn() = AnimationSet(false).apply {
|
||||||
if (uiSettings.layoutAnimations) {
|
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
|
||||||
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
|
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
|
||||||
animation.duration = (500 * uiSettings.animationSpeed).toLong()
|
val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed)
|
||||||
|
animation.duration = (500 * animationSpeed).toLong()
|
||||||
animation.interpolator = AccelerateDecelerateInterpolator()
|
animation.interpolator = AccelerateDecelerateInterpolator()
|
||||||
addAnimation(animation)
|
addAnimation(animation)
|
||||||
|
|
||||||
@@ -757,16 +961,17 @@ fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
|
|||||||
Animation.RELATIVE_TO_SELF, 0f
|
Animation.RELATIVE_TO_SELF, 0f
|
||||||
)
|
)
|
||||||
|
|
||||||
animation.duration = (750 * uiSettings.animationSpeed).toLong()
|
animation.duration = (750 * animationSpeed).toLong()
|
||||||
animation.interpolator = OvershootInterpolator(1.1f)
|
animation.interpolator = OvershootInterpolator(1.1f)
|
||||||
addAnimation(animation)
|
addAnimation(animation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
|
fun setSlideUp() = AnimationSet(false).apply {
|
||||||
if (uiSettings.layoutAnimations) {
|
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
|
||||||
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
|
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
|
||||||
animation.duration = (500 * uiSettings.animationSpeed).toLong()
|
val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed)
|
||||||
|
animation.duration = (500 * animationSpeed).toLong()
|
||||||
animation.interpolator = AccelerateDecelerateInterpolator()
|
animation.interpolator = AccelerateDecelerateInterpolator()
|
||||||
addAnimation(animation)
|
addAnimation(animation)
|
||||||
|
|
||||||
@@ -777,7 +982,7 @@ fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
|
|||||||
Animation.RELATIVE_TO_SELF, 0f
|
Animation.RELATIVE_TO_SELF, 0f
|
||||||
)
|
)
|
||||||
|
|
||||||
animation.duration = (750 * uiSettings.animationSpeed).toLong()
|
animation.duration = (750 * animationSpeed).toLong()
|
||||||
animation.interpolator = OvershootInterpolator(1.1f)
|
animation.interpolator = OvershootInterpolator(1.1f)
|
||||||
addAnimation(animation)
|
addAnimation(animation)
|
||||||
}
|
}
|
||||||
@@ -797,7 +1002,7 @@ class EmptyAdapter(private val count: Int) : RecyclerView.Adapter<RecyclerView.V
|
|||||||
|
|
||||||
fun toast(string: String?) {
|
fun toast(string: String?) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
logger(string)
|
Logger.log(string)
|
||||||
MainScope().launch {
|
MainScope().launch {
|
||||||
Toast.makeText(currActivity()?.application ?: return@launch, string, Toast.LENGTH_SHORT)
|
Toast.makeText(currActivity()?.application ?: return@launch, string, Toast.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
@@ -805,16 +1010,16 @@ fun toast(string: String?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun snackString(s: String?, activity: Activity? = null, clipboard: String? = null) {
|
fun snackString(s: String?, activity: Activity? = null, clipboard: String? = null): Snackbar? {
|
||||||
try { //I have no idea why this sometimes crashes for some people...
|
try { //I have no idea why this sometimes crashes for some people...
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
(activity ?: currActivity())?.apply {
|
(activity ?: currActivity())?.apply {
|
||||||
|
val snackBar = Snackbar.make(
|
||||||
|
window.decorView.findViewById(android.R.id.content),
|
||||||
|
s,
|
||||||
|
Snackbar.LENGTH_SHORT
|
||||||
|
)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val snackBar = Snackbar.make(
|
|
||||||
window.decorView.findViewById(android.R.id.content),
|
|
||||||
s,
|
|
||||||
Snackbar.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
snackBar.view.apply {
|
snackBar.view.apply {
|
||||||
updateLayoutParams<FrameLayout.LayoutParams> {
|
updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
||||||
@@ -834,13 +1039,15 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
|
|||||||
}
|
}
|
||||||
snackBar.show()
|
snackBar.show()
|
||||||
}
|
}
|
||||||
|
return snackBar
|
||||||
}
|
}
|
||||||
logger(s)
|
Logger.log(s)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger(e.stackTraceToString())
|
Logger.log(e)
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
open class NoPaddingArrayAdapter<T>(context: Context, layoutId: Int, items: List<T>) :
|
open class NoPaddingArrayAdapter<T>(context: Context, layoutId: Int, items: List<T>) :
|
||||||
@@ -964,10 +1171,9 @@ const val INCOGNITO_CHANNEL_ID = 26
|
|||||||
fun incognitoNotification(context: Context) {
|
fun incognitoNotification(context: Context) {
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val incognito = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
.getBoolean("incognito", false)
|
|
||||||
if (incognito) {
|
if (incognito) {
|
||||||
val intent = Intent(context, NotificationClickReceiver::class.java)
|
val intent = Intent(context, IncognitoNotificationClickReceiver::class.java)
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
context, 0, intent,
|
context, 0, intent,
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
@@ -985,6 +1191,28 @@ fun incognitoNotification(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasNotificationPermission(context: Context): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openSettings(context: Context, channelId: String?): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val intent = Intent(
|
||||||
|
if (channelId != null) Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
|
||||||
|
else Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||||
|
).apply {
|
||||||
|
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||||
|
putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun View.pop() {
|
suspend fun View.pop() {
|
||||||
currActivity()?.runOnUiThread {
|
currActivity()?.runOnUiThread {
|
||||||
ObjectAnimator.ofFloat(this@pop, "scaleX", 1f, 1.25f).setDuration(120).start()
|
ObjectAnimator.ofFloat(this@pop, "scaleX", 1f, 1.25f).setDuration(120).start()
|
||||||
@@ -997,3 +1225,100 @@ suspend fun View.pop() {
|
|||||||
}
|
}
|
||||||
delay(100)
|
delay(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun blurImage(imageView: ImageView, banner: String?) {
|
||||||
|
if (banner != null) {
|
||||||
|
val radius = PrefManager.getVal<Float>(PrefName.BlurRadius).toInt()
|
||||||
|
val sampling = PrefManager.getVal<Float>(PrefName.BlurSampling).toInt()
|
||||||
|
if (PrefManager.getVal(PrefName.BlurBanners)) {
|
||||||
|
val context = imageView.context
|
||||||
|
if (!(context as Activity).isDestroyed) {
|
||||||
|
val url = PrefManager.getVal<String>(PrefName.ImageUrl).ifEmpty { banner }
|
||||||
|
Glide.with(context as Context)
|
||||||
|
.load(GlideUrl(url))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
||||||
|
.apply(RequestOptions.bitmapTransform(BlurTransformation(radius, sampling)))
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imageView.loadImage(banner)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imageView.setImageResource(R.drawable.linear_gradient_bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the markwon instance with all the plugins
|
||||||
|
* @return the markwon instance
|
||||||
|
*/
|
||||||
|
fun buildMarkwon(
|
||||||
|
activity: Context,
|
||||||
|
userInputContent: Boolean = true,
|
||||||
|
fragment: Fragment? = null
|
||||||
|
): Markwon {
|
||||||
|
val glideContext = fragment?.let { Glide.with(it) } ?: Glide.with(activity)
|
||||||
|
val markwon = Markwon.builder(activity)
|
||||||
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
|
builder.linkResolver { _, link ->
|
||||||
|
copyToClipboard(link, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(TablePlugin.create(activity))
|
||||||
|
.usePlugin(TaskListPlugin.create(activity))
|
||||||
|
.usePlugin(SpoilerPlugin())
|
||||||
|
.usePlugin(HtmlPlugin.create { plugin ->
|
||||||
|
if (userInputContent) {
|
||||||
|
plugin.addHandler(
|
||||||
|
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
|
||||||
|
|
||||||
|
private val requestManager: RequestManager = glideContext.apply {
|
||||||
|
addDefaultRequestListener(object : RequestListener<Any> {
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Any,
|
||||||
|
model: Any,
|
||||||
|
target: Target<Any>,
|
||||||
|
dataSource: DataSource,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
if (resource is GifDrawable) {
|
||||||
|
resource.start()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Any>,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Logger.log("Image failed to load: $model")
|
||||||
|
Logger.log(e as Exception)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> {
|
||||||
|
Logger.log("Loading image: ${drawable.destination}")
|
||||||
|
return requestManager.load(drawable.destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel(target: Target<*>) {
|
||||||
|
Logger.log("Cancelling image load")
|
||||||
|
requestManager.clear(target)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build()
|
||||||
|
return markwon
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ package ani.dantotsu
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -12,7 +14,8 @@ import android.os.Bundle
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AnticipateInterpolator
|
import android.view.animation.AnticipateInterpolator
|
||||||
@@ -26,6 +29,8 @@ import androidx.core.animation.doOnEnd
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.core.view.updateMargins
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -33,6 +38,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.offline.Download
|
import androidx.media3.exoplayer.offline.Download
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
||||||
import ani.dantotsu.databinding.ActivityMainBinding
|
import ani.dantotsu.databinding.ActivityMainBinding
|
||||||
@@ -44,20 +50,27 @@ import ani.dantotsu.home.LoginFragment
|
|||||||
import ani.dantotsu.home.MangaFragment
|
import ani.dantotsu.home.MangaFragment
|
||||||
import ani.dantotsu.home.NoInternet
|
import ani.dantotsu.home.NoInternet
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
|
||||||
|
import ani.dantotsu.notifications.comment.CommentNotificationWorker
|
||||||
import ani.dantotsu.others.CustomBottomDialog
|
import ani.dantotsu.others.CustomBottomDialog
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.others.SharedPreferenceBooleanLiveData
|
import ani.dantotsu.profile.activity.FeedActivity
|
||||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
import ani.dantotsu.profile.activity.NotificationActivity
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
import ani.dantotsu.settings.saving.PrefManager.asLiveBool
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData
|
||||||
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
||||||
|
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -73,22 +86,81 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
private var load = false
|
private var load = false
|
||||||
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("InternalInsetResource", "DiscouragedApi")
|
@SuppressLint("InternalInsetResource", "DiscouragedApi")
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
LangSet.setLocale(this)
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
//get FRAGMENT_CLASS_NAME from intent
|
//get FRAGMENT_CLASS_NAME from intent
|
||||||
val FRAGMENT_CLASS_NAME = intent.getStringExtra("FRAGMENT_CLASS_NAME")
|
val fragment = intent.getStringExtra("FRAGMENT_CLASS_NAME")
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|
||||||
|
androidx.work.WorkManager.getInstance(this)
|
||||||
|
.enqueue(OneTimeWorkRequest.Companion.from(CommentNotificationWorker::class.java))
|
||||||
|
|
||||||
|
androidx.work.WorkManager.getInstance(this)
|
||||||
|
.enqueue(OneTimeWorkRequest.Companion.from(AnilistNotificationWorker::class.java))
|
||||||
|
|
||||||
|
val action = intent.action
|
||||||
|
val type = intent.type
|
||||||
|
if (Intent.ACTION_VIEW == action && type != null) {
|
||||||
|
val uri: Uri? = intent.data
|
||||||
|
try {
|
||||||
|
if (uri == null) {
|
||||||
|
throw Exception("Uri is null")
|
||||||
|
}
|
||||||
|
val jsonString =
|
||||||
|
contentResolver.openInputStream(uri)?.readBytes()
|
||||||
|
?: throw Exception("Error reading file")
|
||||||
|
val name =
|
||||||
|
DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
|
||||||
|
//.sani is encrypted, .ani is not
|
||||||
|
if (name.endsWith(".sani")) {
|
||||||
|
passwordAlertDialog { password ->
|
||||||
|
if (password != null) {
|
||||||
|
val salt = jsonString.copyOfRange(0, 16)
|
||||||
|
val encrypted = jsonString.copyOfRange(16, jsonString.size)
|
||||||
|
val decryptedJson = try {
|
||||||
|
PreferenceKeystore.decryptWithPassword(
|
||||||
|
password,
|
||||||
|
encrypted,
|
||||||
|
salt
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast("Incorrect password")
|
||||||
|
return@passwordAlertDialog
|
||||||
|
}
|
||||||
|
if (PreferencePackager.unpack(decryptedJson)) {
|
||||||
|
val intent = Intent(this, this.javaClass)
|
||||||
|
this.finish()
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name.endsWith(".ani")) {
|
||||||
|
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
||||||
|
if (PreferencePackager.unpack(decryptedJson)) {
|
||||||
|
val intent = Intent(this, this.javaClass)
|
||||||
|
this.finish()
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Invalid file type")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
toast("Error importing settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
|
||||||
@@ -98,12 +170,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
backgroundDrawable.setColor(semiTransparentColor)
|
backgroundDrawable.setColor(semiTransparentColor)
|
||||||
_bottomBar.background = backgroundDrawable
|
_bottomBar.background = backgroundDrawable
|
||||||
}
|
}
|
||||||
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||||
val colorOverflow = sharedPreferences.getBoolean("colorOverflow", false)
|
|
||||||
if (!colorOverflow) {
|
|
||||||
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val offset = try {
|
val offset = try {
|
||||||
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||||
@@ -114,11 +181,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val layoutParams = binding.incognito.layoutParams as ViewGroup.MarginLayoutParams
|
val layoutParams = binding.incognito.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
layoutParams.topMargin = 11 * offset / 12
|
layoutParams.topMargin = 11 * offset / 12
|
||||||
binding.incognito.layoutParams = layoutParams
|
binding.incognito.layoutParams = layoutParams
|
||||||
incognitoLiveData = SharedPreferenceBooleanLiveData(
|
incognitoLiveData = PrefManager.getLiveVal(
|
||||||
sharedPreferences,
|
PrefName.Incognito,
|
||||||
"incognito",
|
|
||||||
false
|
false
|
||||||
)
|
).asLiveBool()
|
||||||
incognitoLiveData.observe(this) {
|
incognitoLiveData.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
val slideDownAnim = ObjectAnimator.ofFloat(
|
val slideDownAnim = ObjectAnimator.ofFloat(
|
||||||
@@ -154,15 +220,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
doubleBackToExitPressedOnce = true
|
doubleBackToExitPressedOnce = true
|
||||||
snackString(this@MainActivity.getString(R.string.back_to_exit))
|
snackString(this@MainActivity.getString(R.string.back_to_exit)).apply {
|
||||||
Handler(Looper.getMainLooper()).postDelayed(
|
this?.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||||
{ doubleBackToExitPressedOnce = false },
|
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||||
2000
|
super.onDismissed(transientBottomBar, event)
|
||||||
)
|
doubleBackToExitPressedOnce = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences: SourcePreferences = Injekt.get()
|
val preferences: SourcePreferences = Injekt.get()
|
||||||
if (preferences.animeExtensionUpdatesCount().get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0) {
|
if (preferences.animeExtensionUpdatesCount()
|
||||||
|
.get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0
|
||||||
|
) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this,
|
this,
|
||||||
"You have extension updates available!",
|
"You have extension updates available!",
|
||||||
@@ -213,24 +284,52 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
binding.root.doOnAttach {
|
binding.root.doOnAttach {
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
uiSettings = loadData("ui_settings") ?: uiSettings
|
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent)
|
||||||
selectedOption = if (FRAGMENT_CLASS_NAME != null) {
|
selectedOption = if (fragment != null) {
|
||||||
when (FRAGMENT_CLASS_NAME) {
|
when (fragment) {
|
||||||
AnimeFragment::class.java.name -> 0
|
AnimeFragment::class.java.name -> 0
|
||||||
HomeFragment::class.java.name -> 1
|
HomeFragment::class.java.name -> 1
|
||||||
MangaFragment::class.java.name -> 2
|
MangaFragment::class.java.name -> 2
|
||||||
else -> 1
|
else -> 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uiSettings.defaultStartUpTab
|
PrefManager.getVal(PrefName.DefaultStartUpTab)
|
||||||
}
|
}
|
||||||
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val offlineMode = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("offlineMode", false)
|
intent.extras?.let { extras ->
|
||||||
|
val fragmentToLoad = extras.getString("FRAGMENT_TO_LOAD")
|
||||||
|
val mediaId = extras.getInt("mediaId", -1)
|
||||||
|
val commentId = extras.getInt("commentId", -1)
|
||||||
|
val activityId = extras.getInt("activityId", -1)
|
||||||
|
|
||||||
|
if (fragmentToLoad != null && mediaId != -1 && commentId != -1) {
|
||||||
|
val detailIntent = Intent(this, MediaDetailsActivity::class.java).apply {
|
||||||
|
putExtra("FRAGMENT_TO_LOAD", fragmentToLoad)
|
||||||
|
putExtra("mediaId", mediaId)
|
||||||
|
putExtra("commentId", commentId)
|
||||||
|
}
|
||||||
|
startActivity(detailIntent)
|
||||||
|
} else if (fragmentToLoad == "FEED" && activityId != -1) {
|
||||||
|
val feedIntent = Intent(this, FeedActivity::class.java).apply {
|
||||||
|
putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS")
|
||||||
|
putExtra("activityId", activityId)
|
||||||
|
|
||||||
|
}
|
||||||
|
startActivity(feedIntent)
|
||||||
|
} else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) {
|
||||||
|
Logger.log("MainActivity, onCreate: $activityId")
|
||||||
|
val notificationIntent = Intent(this, NotificationActivity::class.java).apply {
|
||||||
|
putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS")
|
||||||
|
putExtra("activityId", activityId)
|
||||||
|
}
|
||||||
|
startActivity(notificationIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val offlineMode: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||||
if (!isOnline(this)) {
|
if (!isOnline(this)) {
|
||||||
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
||||||
startActivity(Intent(this, NoInternet::class.java))
|
startActivity(Intent(this, NoInternet::class.java))
|
||||||
@@ -251,7 +350,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
mainViewPager.isUserInputEnabled = false
|
mainViewPager.isUserInputEnabled = false
|
||||||
mainViewPager.adapter =
|
mainViewPager.adapter =
|
||||||
ViewPagerAdapter(supportFragmentManager, lifecycle)
|
ViewPagerAdapter(supportFragmentManager, lifecycle)
|
||||||
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
mainViewPager.setPageTransformer(ZoomOutPageTransformer())
|
||||||
navbar.setOnTabSelectListener(object :
|
navbar.setOnTabSelectListener(object :
|
||||||
AnimatedBottomBar.OnTabSelectListener {
|
AnimatedBottomBar.OnTabSelectListener {
|
||||||
override fun onTabSelected(
|
override fun onTabSelected(
|
||||||
@@ -265,12 +364,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
mainViewPager.setCurrentItem(newIndex, false)
|
mainViewPager.setCurrentItem(newIndex, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
navbar.selectTabAt(selectedOption)
|
if (mainViewPager.getCurrentItem() != selectedOption) {
|
||||||
mainViewPager.post {
|
navbar.selectTabAt(selectedOption)
|
||||||
mainViewPager.setCurrentItem(
|
mainViewPager.post {
|
||||||
selectedOption,
|
mainViewPager.setCurrentItem(
|
||||||
false
|
selectedOption,
|
||||||
)
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.mainProgressBar.visibility = View.GONE
|
binding.mainProgressBar.visibility = View.GONE
|
||||||
@@ -298,14 +399,27 @@ class MainActivity : AppCompatActivity() {
|
|||||||
snackString(this@MainActivity.getString(R.string.anilist_not_found))
|
snackString(this@MainActivity.getString(R.string.anilist_not_found))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delay(500)
|
val username = intent.extras?.getString("username")
|
||||||
startSubscription()
|
if (username != null) {
|
||||||
|
val nameInt = username.toIntOrNull()
|
||||||
|
if (nameInt != null) {
|
||||||
|
startActivity(
|
||||||
|
Intent(this@MainActivity, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", nameInt)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startActivity(
|
||||||
|
Intent(this@MainActivity, ProfileActivity::class.java)
|
||||||
|
.putExtra("username", username)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
load = true
|
load = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
if (loadData<Boolean>("allow_opening_links", this) != true) {
|
if (!(PrefManager.getVal(PrefName.AllowOpeningLinks) as Boolean)) {
|
||||||
CustomBottomDialog.newInstance().apply {
|
CustomBottomDialog.newInstance().apply {
|
||||||
title = "Allow Dantotsu to automatically open Anilist & MAL Links?"
|
title = "Allow Dantotsu to automatically open Anilist & MAL Links?"
|
||||||
val md = "Open settings & click +Add Links & select Anilist & Mal urls"
|
val md = "Open settings & click +Add Links & select Anilist & Mal urls"
|
||||||
@@ -317,45 +431,93 @@ class MainActivity : AppCompatActivity() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setNegativeButton(this@MainActivity.getString(R.string.no)) {
|
setNegativeButton(this@MainActivity.getString(R.string.no)) {
|
||||||
saveData("allow_opening_links", true, this@MainActivity)
|
PrefManager.setVal(PrefName.AllowOpeningLinks, true)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositiveButton(this@MainActivity.getString(R.string.yes)) {
|
setPositiveButton(this@MainActivity.getString(R.string.yes)) {
|
||||||
saveData("allow_opening_links", true, this@MainActivity)
|
PrefManager.setVal(PrefName.AllowOpeningLinks, true)
|
||||||
tryWith(true) {
|
tryWith(true) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
|
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
|
||||||
.setData(Uri.parse("package:$packageName"))
|
.setData(Uri.parse("package:$packageName"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show(supportFragmentManager, "dialog")
|
}.show(supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: Remove this
|
lifecycleScope.launch(Dispatchers.IO) { //simple cleanup
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
val index = Helper.downloadManager(this@MainActivity).downloadIndex
|
val index = Helper.downloadManager(this@MainActivity).downloadIndex
|
||||||
val downloadCursor = index.getDownloads()
|
val downloadCursor = index.getDownloads()
|
||||||
while (downloadCursor.moveToNext()) {
|
while (downloadCursor.moveToNext()) {
|
||||||
val download = downloadCursor.download
|
val download = downloadCursor.download
|
||||||
Log.e("Downloader", download.request.uri.toString())
|
if (download.state == Download.STATE_FAILED) {
|
||||||
Log.e("Downloader", download.request.id.toString())
|
|
||||||
Log.e("Downloader", download.request.mimeType.toString())
|
|
||||||
Log.e("Downloader", download.request.data.size.toString())
|
|
||||||
Log.e("Downloader", download.bytesDownloaded.toString())
|
|
||||||
Log.e("Downloader", download.state.toString())
|
|
||||||
Log.e("Downloader", download.failureReason.toString())
|
|
||||||
|
|
||||||
if (download.state == Download.STATE_FAILED) { //simple cleanup
|
|
||||||
Helper.downloadManager(this@MainActivity).removeDownload(download.request.id)
|
Helper.downloadManager(this@MainActivity).removeDownload(download.request.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRestart() {
|
||||||
|
super.onRestart()
|
||||||
|
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Int.toPx get() = TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
val params : ViewGroup.MarginLayoutParams =
|
||||||
|
binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||||
|
params.updateMargins(bottom = 8.toPx)
|
||||||
|
else
|
||||||
|
params.updateMargins(bottom = 32.toPx)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
|
||||||
|
val password = CharArray(16).apply { fill('0') }
|
||||||
|
|
||||||
|
// Inflate the dialog layout
|
||||||
|
val dialogView =
|
||||||
|
LayoutInflater.from(this).inflate(R.layout.dialog_user_agent, null)
|
||||||
|
dialogView.findViewById<TextInputEditText>(R.id.userAgentTextBox)?.hint = "Password"
|
||||||
|
val subtitleTextView = dialogView.findViewById<TextView>(R.id.subtitle)
|
||||||
|
subtitleTextView?.visibility = View.VISIBLE
|
||||||
|
subtitleTextView?.text = "Enter your password to decrypt the file"
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
|
||||||
|
.setTitle("Enter Password")
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton("OK", null)
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
password.fill('0')
|
||||||
|
dialog.dismiss()
|
||||||
|
callback(null)
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.window?.setDimAmount(0.8f)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
// Override the positive button here
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val editText = dialog.findViewById<TextInputEditText>(R.id.userAgentTextBox)
|
||||||
|
if (editText?.text?.isNotBlank() == true) {
|
||||||
|
editText.text?.toString()?.trim()?.toCharArray(password)
|
||||||
|
dialog.dismiss()
|
||||||
|
callback(password)
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//ViewPager
|
//ViewPager
|
||||||
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Build
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import ani.dantotsu.others.webview.CloudFlare
|
import ani.dantotsu.others.webview.CloudFlare
|
||||||
import ani.dantotsu.others.webview.WebViewBottomDialog
|
import ani.dantotsu.others.webview.WebViewBottomDialog
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.ResponseParser
|
import com.lagradost.nicehttp.ResponseParser
|
||||||
import com.lagradost.nicehttp.addGenericDns
|
import com.lagradost.nicehttp.addGenericDns
|
||||||
@@ -104,6 +105,7 @@ fun logError(e: Throwable, post: Boolean = true, snackbar: Boolean = true) {
|
|||||||
toast(e.localizedMessage)
|
toast(e.localizedMessage)
|
||||||
}
|
}
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
Logger.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> tryWith(post: Boolean = false, snackbar: Boolean = true, call: () -> T): T? {
|
fun <T> tryWith(post: Boolean = false, snackbar: Boolean = true, call: () -> T): T? {
|
||||||
@@ -134,7 +136,7 @@ suspend fun <T> tryWithSuspend(
|
|||||||
* A url, which can also have headers
|
* A url, which can also have headers
|
||||||
* **/
|
* **/
|
||||||
data class FileUrl(
|
data class FileUrl(
|
||||||
val url: String,
|
var url: String,
|
||||||
val headers: Map<String, String> = mapOf()
|
val headers: Map<String, String> = mapOf()
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package ani.dantotsu.aniyomi.anime.custom
|
|||||||
|
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||||
@@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
|
|||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
|
||||||
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
|
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
|
||||||
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
|
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -36,7 +35,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { DownloadsManager(app) }
|
addSingletonFactory { DownloadsManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app, get()) }
|
addSingletonFactory { NetworkHelper(app) }
|
||||||
|
|
||||||
addSingletonFactory { AnimeExtensionManager(app) }
|
addSingletonFactory { AnimeExtensionManager(app) }
|
||||||
addSingletonFactory { MangaExtensionManager(app) }
|
addSingletonFactory { MangaExtensionManager(app) }
|
||||||
@@ -45,9 +44,6 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
|
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
|
||||||
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
|
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
|
||||||
|
|
||||||
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
addSingleton(sharedPreferences)
|
|
||||||
|
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
Json {
|
Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -57,6 +53,10 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { StandaloneDatabaseProvider(app) }
|
addSingletonFactory { StandaloneDatabaseProvider(app) }
|
||||||
|
|
||||||
|
addSingletonFactory<CrashlyticsInterface> {
|
||||||
|
ani.dantotsu.connections.crashlytics.CrashlyticsFactory.createCrashlytics()
|
||||||
|
}
|
||||||
|
|
||||||
addSingletonFactory { MangaCache() }
|
addSingletonFactory { MangaCache() }
|
||||||
|
|
||||||
ContextCompat.getMainExecutor(app).execute {
|
ContextCompat.getMainExecutor(app).execute {
|
||||||
@@ -72,13 +72,6 @@ class PreferenceModule(val application: Application) : InjektModule {
|
|||||||
AndroidPreferenceStore(application)
|
AndroidPreferenceStore(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSingletonFactory {
|
|
||||||
NetworkPreferences(
|
|
||||||
preferenceStore = get(),
|
|
||||||
verboseLogging = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
SourcePreferences(get())
|
SourcePreferences(get())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
fun updateProgress(media: Media, number: String) {
|
fun updateProgress(media: Media, number: String) {
|
||||||
val incognito = currContext()?.getSharedPreferences("Dantotsu", 0)
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
?.getBoolean("incognito", false) ?: false
|
|
||||||
if (!incognito) {
|
if (!incognito) {
|
||||||
if (Anilist.userid != null) {
|
if (Anilist.userid != null) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val a = number.toFloatOrNull()?.toInt()
|
val a = number.toFloatOrNull()?.toInt()
|
||||||
if ((a ?: 0) > (media.userProgress ?: 0)) {
|
if ((a ?: 0) > (media.userProgress ?: -1)) {
|
||||||
Anilist.mutation.editList(
|
Anilist.mutation.editList(
|
||||||
media.id,
|
media.id,
|
||||||
a,
|
a,
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ package ani.dantotsu.connections.anilist
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.client
|
import ani.dantotsu.client
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import java.io.File
|
import ani.dantotsu.util.Logger
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
object Anilist {
|
object Anilist {
|
||||||
@@ -24,10 +29,13 @@ object Anilist {
|
|||||||
var bg: String? = null
|
var bg: String? = null
|
||||||
var episodesWatched: Int? = null
|
var episodesWatched: Int? = null
|
||||||
var chapterRead: Int? = null
|
var chapterRead: Int? = null
|
||||||
|
var unreadNotificationCount: Int = 0
|
||||||
|
|
||||||
var genres: ArrayList<String>? = null
|
var genres: ArrayList<String>? = null
|
||||||
var tags: Map<Boolean, List<String>>? = null
|
var tags: Map<Boolean, List<String>>? = null
|
||||||
|
|
||||||
|
var rateLimitReset: Long = 0
|
||||||
|
|
||||||
val sortBy = listOf(
|
val sortBy = listOf(
|
||||||
"SCORE_DESC",
|
"SCORE_DESC",
|
||||||
"POPULARITY_DESC",
|
"POPULARITY_DESC",
|
||||||
@@ -94,15 +102,12 @@ object Anilist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSavedToken(context: Context): Boolean {
|
fun getSavedToken(): Boolean {
|
||||||
if ("anilistToken" in context.fileList()) {
|
token = PrefManager.getVal(PrefName.AnilistToken, null as String?)
|
||||||
token = File(context.filesDir, "anilistToken").readText()
|
return !token.isNullOrEmpty()
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeSavedToken(context: Context) {
|
fun removeSavedToken() {
|
||||||
token = null
|
token = null
|
||||||
username = null
|
username = null
|
||||||
adult = false
|
adult = false
|
||||||
@@ -111,9 +116,7 @@ object Anilist {
|
|||||||
bg = null
|
bg = null
|
||||||
episodesWatched = null
|
episodesWatched = null
|
||||||
chapterRead = null
|
chapterRead = null
|
||||||
if ("anilistToken" in context.fileList()) {
|
PrefManager.removeVal(PrefName.AnilistToken)
|
||||||
File(context.filesDir, "anilistToken").delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <reified T : Any> executeQuery(
|
suspend inline fun <reified T : Any> executeQuery(
|
||||||
@@ -124,7 +127,12 @@ object Anilist {
|
|||||||
show: Boolean = false,
|
show: Boolean = false,
|
||||||
cache: Int? = null
|
cache: Int? = null
|
||||||
): T? {
|
): T? {
|
||||||
return tryWithSuspend {
|
return try {
|
||||||
|
if (show) Logger.log("Anilist Query: $query")
|
||||||
|
if (rateLimitReset > System.currentTimeMillis() / 1000) {
|
||||||
|
toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
|
||||||
|
throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
|
||||||
|
}
|
||||||
val data = mapOf(
|
val data = mapOf(
|
||||||
"query" to query,
|
"query" to query,
|
||||||
"variables" to variables
|
"variables" to variables
|
||||||
@@ -143,10 +151,26 @@ object Anilist {
|
|||||||
data = data,
|
data = data,
|
||||||
cacheTime = cache ?: 10
|
cacheTime = cache ?: 10
|
||||||
)
|
)
|
||||||
if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down))
|
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
|
||||||
if (show) println("Response : ${json.text}")
|
Logger.log("Remaining requests: $remaining")
|
||||||
|
if (json.code == 429) {
|
||||||
|
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
|
||||||
|
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0
|
||||||
|
if (retry > 0) {
|
||||||
|
rateLimitReset = passedLimitReset
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("Rate limited. Try after $retry seconds")
|
||||||
|
throw Exception("Rate limited after $retry seconds")
|
||||||
|
}
|
||||||
|
if (!json.text.startsWith("{")) {throw Exception(currContext()?.getString(R.string.anilist_down))}
|
||||||
|
if (show) Logger.log("Anilist Response: ${json.text}")
|
||||||
json.parsed()
|
json.parsed()
|
||||||
} else null
|
} else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (show) snackString("Error fetching Anilist data: ${e.message}")
|
||||||
|
Logger.log("Anilist Query Error: ${e.message}")
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ class AnilistMutations {
|
|||||||
executeQuery<JsonObject>(query, variables)
|
executeQuery<JsonObject>(query, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun toggleFav(type: FavType, id: Int): Boolean {
|
||||||
|
val filter = when (type) {
|
||||||
|
FavType.ANIME -> "animeId"
|
||||||
|
FavType.MANGA -> "mangaId"
|
||||||
|
FavType.CHARACTER -> "characterId"
|
||||||
|
FavType.STAFF -> "staffId"
|
||||||
|
FavType.STUDIO -> "studioId"
|
||||||
|
}
|
||||||
|
val query = """mutation{ToggleFavourite($filter:$id){anime{pageInfo{total}}}}"""
|
||||||
|
val result = executeQuery<JsonObject>(query)
|
||||||
|
return result?.get("errors") == null && result != null
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FavType {
|
||||||
|
ANIME, MANGA, CHARACTER, STAFF, STUDIO
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun editList(
|
suspend fun editList(
|
||||||
mediaID: Int,
|
mediaID: Int,
|
||||||
progress: Int? = null,
|
progress: Int? = null,
|
||||||
|
|||||||
@@ -1,54 +1,61 @@
|
|||||||
package ani.dantotsu.connections.anilist
|
package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
import android.app.Activity
|
import android.util.Base64
|
||||||
import android.content.Context
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.checkGenreTime
|
import ani.dantotsu.checkGenreTime
|
||||||
import ani.dantotsu.checkId
|
import ani.dantotsu.checkId
|
||||||
import ani.dantotsu.connections.anilist.Anilist.authorRoles
|
import ani.dantotsu.connections.anilist.Anilist.authorRoles
|
||||||
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
||||||
|
import ani.dantotsu.connections.anilist.api.FeedResponse
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
|
import ani.dantotsu.connections.anilist.api.NotificationResponse
|
||||||
import ani.dantotsu.connections.anilist.api.Page
|
import ani.dantotsu.connections.anilist.api.Page
|
||||||
import ani.dantotsu.connections.anilist.api.Query
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
|
import ani.dantotsu.connections.anilist.api.ToggleLike
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.media.Author
|
import ani.dantotsu.media.Author
|
||||||
import ani.dantotsu.media.Character
|
import ani.dantotsu.media.Character
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.Studio
|
import ani.dantotsu.media.Studio
|
||||||
import ani.dantotsu.others.MalScraper
|
import ani.dantotsu.others.MalScraper
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
import java.io.Serializable
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
class AnilistQueries {
|
class AnilistQueries {
|
||||||
|
|
||||||
suspend fun getUserData(): Boolean {
|
suspend fun getUserData(): Boolean {
|
||||||
val response: Query.Viewer?
|
val response: Query.Viewer?
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
response =
|
response =
|
||||||
executeQuery("""{Viewer{name options{displayAdultContent}avatar{medium}bannerImage id mediaListOptions{rowOrder animeList{sectionOrder customLists}mangaList{sectionOrder customLists}}statistics{anime{episodesWatched}manga{chaptersRead}}}}""")
|
executeQuery("""{Viewer{name options{displayAdultContent}avatar{medium}bannerImage id mediaListOptions{rowOrder animeList{sectionOrder customLists}mangaList{sectionOrder customLists}}statistics{anime{episodesWatched}manga{chaptersRead}}unreadNotificationCount}}""")
|
||||||
}.also { println("time : $it") }
|
}.also { println("time : $it") }
|
||||||
val user = response?.data?.user ?: return false
|
val user = response?.data?.user ?: return false
|
||||||
|
|
||||||
currContext()?.let {
|
PrefManager.setVal(PrefName.AnilistUserName, user.name)
|
||||||
it.getSharedPreferences(it.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
|
|
||||||
.edit()
|
|
||||||
.putString("anilist_username", user.name)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
Anilist.userid = user.id
|
Anilist.userid = user.id
|
||||||
|
PrefManager.setVal(PrefName.AnilistUserId, user.id.toString())
|
||||||
Anilist.username = user.name
|
Anilist.username = user.name
|
||||||
Anilist.bg = user.bannerImage
|
Anilist.bg = user.bannerImage
|
||||||
Anilist.avatar = user.avatar?.medium
|
Anilist.avatar = user.avatar?.medium
|
||||||
Anilist.episodesWatched = user.statistics?.anime?.episodesWatched
|
Anilist.episodesWatched = user.statistics?.anime?.episodesWatched
|
||||||
Anilist.chapterRead = user.statistics?.manga?.chaptersRead
|
Anilist.chapterRead = user.statistics?.manga?.chaptersRead
|
||||||
Anilist.adult = user.options?.displayAdultContent ?: false
|
Anilist.adult = user.options?.displayAdultContent ?: false
|
||||||
|
Anilist.unreadNotificationCount = user.unreadNotificationCount ?: 0
|
||||||
|
val unread = PrefManager.getVal<Int>(PrefName.UnreadCommentNotifications)
|
||||||
|
Anilist.unreadNotificationCount += unread
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +72,7 @@ class AnilistQueries {
|
|||||||
media.cameFromContinue = false
|
media.cameFromContinue = false
|
||||||
|
|
||||||
val query =
|
val query =
|
||||||
"""{Media(id:${media.id}){id mediaListEntry{id status score(format:POINT_100) progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer { site id } synonyms tags { name rank isMediaSpoiler } characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role node{id image{medium}name{userPreferred}}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100) status} episodes chapters nextAiringEpisode{episode} popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview: staff(perPage: 8, sort: [RELEVANCE, ID]) {edges{role node{id name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100) status} episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}}"""
|
"""{Media(id:${media.id}){id mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}}"""
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val anilist = async {
|
val anilist = async {
|
||||||
var response = executeQuery<Query.Media>(query, force = true, show = true)
|
var response = executeQuery<Query.Media>(query, force = true, show = true)
|
||||||
@@ -122,6 +129,30 @@ class AnilistQueries {
|
|||||||
name = i.node?.name?.userPreferred,
|
name = i.node?.name?.userPreferred,
|
||||||
image = i.node?.image?.medium,
|
image = i.node?.image?.medium,
|
||||||
banner = media.banner ?: media.cover,
|
banner = media.banner ?: media.cover,
|
||||||
|
isFav = i.node?.isFavourite ?: false,
|
||||||
|
role = when (i.role.toString()) {
|
||||||
|
"MAIN" -> currContext()?.getString(R.string.main_role)
|
||||||
|
?: "MAIN"
|
||||||
|
|
||||||
|
"SUPPORTING" -> currContext()?.getString(R.string.supporting_role)
|
||||||
|
?: "SUPPORTING"
|
||||||
|
|
||||||
|
else -> i.role.toString()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fetchedMedia.staff != null) {
|
||||||
|
media.staff = arrayListOf()
|
||||||
|
fetchedMedia.staff?.edges?.forEach { i ->
|
||||||
|
i.node?.apply {
|
||||||
|
media.staff?.add(
|
||||||
|
Author(
|
||||||
|
id = id,
|
||||||
|
name = i.node?.name?.userPreferred,
|
||||||
|
image = i.node?.image?.medium,
|
||||||
role = when (i.role.toString()) {
|
role = when (i.role.toString()) {
|
||||||
"MAIN" -> currContext()?.getString(R.string.main_role)
|
"MAIN" -> currContext()?.getString(R.string.main_role)
|
||||||
?: "MAIN"
|
?: "MAIN"
|
||||||
@@ -212,8 +243,10 @@ class AnilistQueries {
|
|||||||
|
|
||||||
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
||||||
media.anime.author = Author(
|
media.anime.author = Author(
|
||||||
it.id.toString(),
|
it.id,
|
||||||
it.name?.userPreferred ?: "N/A"
|
it.name?.userPreferred ?: "N/A",
|
||||||
|
it.image?.medium,
|
||||||
|
"AUTHOR"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,8 +265,10 @@ class AnilistQueries {
|
|||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
||||||
media.manga.author = Author(
|
media.manga.author = Author(
|
||||||
it.id.toString(),
|
it.id,
|
||||||
it.name?.userPreferred ?: "N/A"
|
it.name?.userPreferred ?: "N/A",
|
||||||
|
it.image?.medium,
|
||||||
|
"AUTHOR"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,6 +285,7 @@ class AnilistQueries {
|
|||||||
} else {
|
} else {
|
||||||
if (currContext()?.let { isOnline(it) } == true) {
|
if (currContext()?.let { isOnline(it) } == true) {
|
||||||
snackString(currContext()?.getString(R.string.error_getting_data))
|
snackString(currContext()?.getString(R.string.error_getting_data))
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,15 +299,82 @@ class AnilistQueries {
|
|||||||
return media
|
return media
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun userMediaDetails(media: Media): Media {
|
||||||
|
val query =
|
||||||
|
"""{Media(id:${media.id}){id mediaListEntry{id status progress private repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite idMal}}"""
|
||||||
|
runBlocking {
|
||||||
|
val anilist = async {
|
||||||
|
var response = executeQuery<Query.Media>(query, force = true, show = true)
|
||||||
|
if (response != null) {
|
||||||
|
fun parse() {
|
||||||
|
val fetchedMedia = response?.data?.media ?: return
|
||||||
|
|
||||||
|
if (fetchedMedia.mediaListEntry != null) {
|
||||||
|
fetchedMedia.mediaListEntry?.apply {
|
||||||
|
media.userProgress = progress
|
||||||
|
media.isListPrivate = private ?: false
|
||||||
|
media.userListId = id
|
||||||
|
media.userStatus = status?.toString()
|
||||||
|
media.inCustomListsOf = customLists?.toMutableMap()
|
||||||
|
media.userRepeat = repeat ?: 0
|
||||||
|
media.userUpdatedAt = updatedAt?.toString()?.toLong()?.times(1000)
|
||||||
|
media.userCompletedAt = completedAt ?: FuzzyDate()
|
||||||
|
media.userStartedAt = startedAt ?: FuzzyDate()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
media.isListPrivate = false
|
||||||
|
media.userStatus = null
|
||||||
|
media.userListId = null
|
||||||
|
media.userProgress = null
|
||||||
|
media.userRepeat = 0
|
||||||
|
media.userUpdatedAt = null
|
||||||
|
media.userCompletedAt = FuzzyDate()
|
||||||
|
media.userStartedAt = FuzzyDate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data?.media != null) parse()
|
||||||
|
else {
|
||||||
|
response = executeQuery(query, force = true, useToken = false)
|
||||||
|
if (response?.data?.media != null) parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
awaitAll(anilist)
|
||||||
|
}
|
||||||
|
return media
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun continueMedia(type: String, planned: Boolean = false): ArrayList<Media> {
|
suspend fun continueMedia(type: String, planned: Boolean = false): ArrayList<Media> {
|
||||||
val returnArray = arrayListOf<Media>()
|
val returnArray = arrayListOf<Media>()
|
||||||
val map = mutableMapOf<Int, Media>()
|
val map = mutableMapOf<Int, Media>()
|
||||||
val statuses = if (!planned) arrayOf("CURRENT", "REPEATING") else arrayOf("PLANNING")
|
val query = if (planned) {
|
||||||
suspend fun repeat(status: String) {
|
"""{ planned: ${continueMediaQuery(type, "PLANNING")} }"""
|
||||||
val response =
|
} else {
|
||||||
executeQuery<Query.MediaListCollection>(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """)
|
"""{
|
||||||
|
current: ${continueMediaQuery(type, "CURRENT")},
|
||||||
|
repeating: ${continueMediaQuery(type, "REPEATING")}
|
||||||
|
}"""
|
||||||
|
}
|
||||||
|
|
||||||
response?.data?.mediaListCollection?.lists?.forEach { li ->
|
val response = executeQuery<Query.CombinedMediaListResponse>(query)
|
||||||
|
if (planned) {
|
||||||
|
response?.data?.planned?.lists?.forEach { li ->
|
||||||
|
li.entries?.reversed()?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
m.cameFromContinue = true
|
||||||
|
map[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response?.data?.current?.lists?.forEach { li ->
|
||||||
|
li.entries?.reversed()?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
m.cameFromContinue = true
|
||||||
|
map[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response?.data?.repeating?.lists?.forEach { li ->
|
||||||
li.entries?.reversed()?.forEach {
|
li.entries?.reversed()?.forEach {
|
||||||
val m = Media(it)
|
val m = Media(it)
|
||||||
m.cameFromContinue = true
|
m.cameFromContinue = true
|
||||||
@@ -279,11 +382,17 @@ class AnilistQueries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (type != "ANIME") {
|
||||||
statuses.forEach { repeat(it) }
|
returnArray.addAll(map.values)
|
||||||
val set = loadData<MutableSet<Int>>("continue_$type")
|
return returnArray
|
||||||
if (set != null) {
|
}
|
||||||
set.reversed().forEach {
|
val list = PrefManager.getNullableCustomVal(
|
||||||
|
"continueAnimeList",
|
||||||
|
listOf<Int>(),
|
||||||
|
List::class.java
|
||||||
|
) as List<Int>
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
list.reversed().forEach {
|
||||||
if (map.containsKey(it)) returnArray.add(map[it]!!)
|
if (map.containsKey(it)) returnArray.add(map[it]!!)
|
||||||
}
|
}
|
||||||
for (i in map) {
|
for (i in map) {
|
||||||
@@ -293,13 +402,16 @@ class AnilistQueries {
|
|||||||
return returnArray
|
return returnArray
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun favMedia(anime: Boolean): ArrayList<Media> {
|
private fun continueMediaQuery(type: String, status: String): String {
|
||||||
|
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } """
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun favMedia(anime: Boolean, id: Int? = Anilist.userid): ArrayList<Media> {
|
||||||
var hasNextPage = true
|
var hasNextPage = true
|
||||||
var page = 0
|
var page = 0
|
||||||
|
|
||||||
suspend fun getNextPage(page: Int): List<Media> {
|
suspend fun getNextPage(page: Int): List<Media> {
|
||||||
val response =
|
val response = executeQuery<Query.User>("""{${favMediaQuery(anime, page, id)}}""")
|
||||||
executeQuery<Query.User>("""{User(id:${Anilist.userid}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}}""")
|
|
||||||
val favourites = response?.data?.user?.favourites
|
val favourites = response?.data?.user?.favourites
|
||||||
val apiMediaList = if (anime) favourites?.anime else favourites?.manga
|
val apiMediaList = if (anime) favourites?.anime else favourites?.manga
|
||||||
hasNextPage = apiMediaList?.pageInfo?.hasNextPage ?: false
|
hasNextPage = apiMediaList?.pageInfo?.hasNextPage ?: false
|
||||||
@@ -318,9 +430,12 @@ class AnilistQueries {
|
|||||||
return responseArray
|
return responseArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun favMediaQuery(anime: Boolean, page: Int, id: Int? = Anilist.userid): String {
|
||||||
|
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}"""
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun recommendations(): ArrayList<Media> {
|
suspend fun recommendations(): ArrayList<Media> {
|
||||||
val response =
|
val response = executeQuery<Query.Page>("""{${recommendationQuery()}}""")
|
||||||
executeQuery<Query.Page>(""" { Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } } """)
|
|
||||||
val map = mutableMapOf<Int, Media>()
|
val map = mutableMapOf<Int, Media>()
|
||||||
response?.data?.page?.apply {
|
response?.data?.page?.apply {
|
||||||
recommendations?.onEach {
|
recommendations?.onEach {
|
||||||
@@ -336,7 +451,7 @@ class AnilistQueries {
|
|||||||
val types = arrayOf("ANIME", "MANGA")
|
val types = arrayOf("ANIME", "MANGA")
|
||||||
suspend fun repeat(type: String) {
|
suspend fun repeat(type: String) {
|
||||||
val res =
|
val res =
|
||||||
executeQuery<Query.MediaListCollection>(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING , sort: MEDIA_POPULARITY_DESC ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """)
|
executeQuery<Query.MediaListCollection>("""{${recommendationPlannedQuery(type)}}""")
|
||||||
res?.data?.mediaListCollection?.lists?.forEach { li ->
|
res?.data?.mediaListCollection?.lists?.forEach { li ->
|
||||||
li.entries?.forEach {
|
li.entries?.forEach {
|
||||||
val m = Media(it)
|
val m = Media(it)
|
||||||
@@ -354,9 +469,204 @@ class AnilistQueries {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recommendationQuery(): String {
|
||||||
|
return """ Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } """
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recommendationPlannedQuery(type: String): String {
|
||||||
|
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING${if (type == "ANIME") ", sort: MEDIA_POPULARITY_DESC" else ""} ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } }"""
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun initHomePage(): Map<String, ArrayList<Media>> {
|
||||||
|
val toShow: List<Boolean> =
|
||||||
|
PrefManager.getVal(PrefName.HomeLayoutShow) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations
|
||||||
|
var query = """{"""
|
||||||
|
if (toShow.getOrNull(0) == true) query += """currentAnime: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"ANIME",
|
||||||
|
"CURRENT"
|
||||||
|
)
|
||||||
|
}, repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}"""
|
||||||
|
if (toShow.getOrNull(1) == true) query += """favoriteAnime: ${favMediaQuery(true, 1)}"""
|
||||||
|
if (toShow.getOrNull(2) == true) query += """plannedAnime: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"ANIME",
|
||||||
|
"PLANNING"
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
if (toShow.getOrNull(3) == true) query += """currentManga: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"MANGA",
|
||||||
|
"CURRENT"
|
||||||
|
)
|
||||||
|
}, repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}"""
|
||||||
|
if (toShow.getOrNull(4) == true) query += """favoriteManga: ${favMediaQuery(false, 1)}"""
|
||||||
|
if (toShow.getOrNull(5) == true) query += """plannedManga: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"MANGA",
|
||||||
|
"PLANNING"
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
if (toShow.getOrNull(6) == true) query += """recommendationQuery: ${recommendationQuery()}, recommendationPlannedQueryAnime: ${
|
||||||
|
recommendationPlannedQuery(
|
||||||
|
"ANIME"
|
||||||
|
)
|
||||||
|
}, recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}"""
|
||||||
|
query += """}""".trimEnd(',')
|
||||||
|
|
||||||
|
val response = executeQuery<Query.HomePageMedia>(query, show = true)
|
||||||
|
Logger.log(response.toString())
|
||||||
|
val returnMap = mutableMapOf<String, ArrayList<Media>>()
|
||||||
|
fun current(type: String) {
|
||||||
|
val subMap = mutableMapOf<Int, Media>()
|
||||||
|
val returnArray = arrayListOf<Media>()
|
||||||
|
val current =
|
||||||
|
if (type == "Anime") response?.data?.currentAnime else response?.data?.currentManga
|
||||||
|
val repeating =
|
||||||
|
if (type == "Anime") response?.data?.repeatingAnime else response?.data?.repeatingManga
|
||||||
|
current?.lists?.forEach { li ->
|
||||||
|
li.entries?.reversed()?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
m.cameFromContinue = true
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repeating?.lists?.forEach { li ->
|
||||||
|
li.entries?.reversed()?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
m.cameFromContinue = true
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type != "Anime") {
|
||||||
|
returnArray.addAll(subMap.values)
|
||||||
|
returnMap["current$type"] = returnArray
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val list = PrefManager.getNullableCustomVal(
|
||||||
|
"continueAnimeList",
|
||||||
|
listOf<Int>(),
|
||||||
|
List::class.java
|
||||||
|
) as List<Int>
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
list.reversed().forEach {
|
||||||
|
if (subMap.containsKey(it)) returnArray.add(subMap[it]!!)
|
||||||
|
}
|
||||||
|
for (i in subMap) {
|
||||||
|
if (i.value !in returnArray) returnArray.add(i.value)
|
||||||
|
}
|
||||||
|
} else returnArray.addAll(subMap.values)
|
||||||
|
returnMap["current$type"] = returnArray
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun planned(type: String) {
|
||||||
|
val subMap = mutableMapOf<Int, Media>()
|
||||||
|
val returnArray = arrayListOf<Media>()
|
||||||
|
val current =
|
||||||
|
if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga
|
||||||
|
current?.lists?.forEach { li ->
|
||||||
|
li.entries?.reversed()?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
m.cameFromContinue = true
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val list = PrefManager.getNullableCustomVal(
|
||||||
|
"continueAnimeList",
|
||||||
|
listOf<Int>(),
|
||||||
|
List::class.java
|
||||||
|
) as List<Int>
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
list.reversed().forEach {
|
||||||
|
if (subMap.containsKey(it)) returnArray.add(subMap[it]!!)
|
||||||
|
}
|
||||||
|
for (i in subMap) {
|
||||||
|
if (i.value !in returnArray) returnArray.add(i.value)
|
||||||
|
}
|
||||||
|
} else returnArray.addAll(subMap.values)
|
||||||
|
returnMap["planned$type"] = returnArray
|
||||||
|
}
|
||||||
|
|
||||||
|
fun favorite(type: String) {
|
||||||
|
val favourites =
|
||||||
|
if (type == "Anime") response?.data?.favoriteAnime?.favourites else response?.data?.favoriteManga?.favourites
|
||||||
|
val apiMediaList = if (type == "Anime") favourites?.anime else favourites?.manga
|
||||||
|
val returnArray = arrayListOf<Media>()
|
||||||
|
apiMediaList?.edges?.forEach {
|
||||||
|
it.node?.let { i ->
|
||||||
|
returnArray.add(Media(i).apply { isFav = true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnMap["favorite$type"] = returnArray
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toShow.getOrNull(0) == true) {
|
||||||
|
current("Anime")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(1) == true) {
|
||||||
|
favorite("Anime")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(2) == true) {
|
||||||
|
planned("Anime")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(3) == true) {
|
||||||
|
current("Manga")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(4) == true) {
|
||||||
|
favorite("Manga")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(5) == true) {
|
||||||
|
planned("Manga")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(6) == true) {
|
||||||
|
val subMap = mutableMapOf<Int, Media>()
|
||||||
|
response?.data?.recommendationQuery?.apply {
|
||||||
|
recommendations?.onEach {
|
||||||
|
val json = it.mediaRecommendation
|
||||||
|
if (json != null) {
|
||||||
|
val m = Media(json)
|
||||||
|
m.relation = json.type?.toString()
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response?.data?.recommendationPlannedQueryAnime?.apply {
|
||||||
|
lists?.forEach { li ->
|
||||||
|
li.entries?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
if (m.status == "RELEASING" || m.status == "FINISHED") {
|
||||||
|
m.relation = it.media?.type?.toString()
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response?.data?.recommendationPlannedQueryManga?.apply {
|
||||||
|
lists?.forEach { li ->
|
||||||
|
li.entries?.forEach {
|
||||||
|
val m = Media(it)
|
||||||
|
if (m.status == "RELEASING" || m.status == "FINISHED") {
|
||||||
|
m.relation = it.media?.type?.toString()
|
||||||
|
subMap[m.id] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val list = ArrayList(subMap.values.toList())
|
||||||
|
list.sortByDescending { it.meanScore }
|
||||||
|
returnMap["recommendations"] = list
|
||||||
|
}
|
||||||
|
return returnMap
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun bannerImage(type: String): String? {
|
private suspend fun bannerImage(type: String): String? {
|
||||||
var image = loadData<BannerImage>("banner_$type")
|
val image = BannerImage(
|
||||||
if (image == null || image.checkTime()) {
|
PrefManager.getCustomVal("banner_${type}_url", ""),
|
||||||
|
PrefManager.getCustomVal("banner_${type}_time", 0L)
|
||||||
|
)
|
||||||
|
if (image.url.isNullOrEmpty() || image.checkTime()) {
|
||||||
val response =
|
val response =
|
||||||
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: ${Anilist.userid}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """)
|
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: ${Anilist.userid}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """)
|
||||||
val random = response?.data?.mediaListCollection?.lists?.mapNotNull {
|
val random = response?.data?.mediaListCollection?.lists?.mapNotNull {
|
||||||
@@ -366,13 +676,9 @@ class AnilistQueries {
|
|||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
}?.flatten()?.randomOrNull() ?: return null
|
}?.flatten()?.randomOrNull() ?: return null
|
||||||
|
PrefManager.setCustomVal("banner_${type}_url", random)
|
||||||
image = BannerImage(
|
PrefManager.setCustomVal("banner_${type}_time", System.currentTimeMillis())
|
||||||
random,
|
return random
|
||||||
System.currentTimeMillis()
|
|
||||||
)
|
|
||||||
saveData("banner_$type", image)
|
|
||||||
return image.url
|
|
||||||
} else return image.url
|
} else return image.url
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +695,7 @@ class AnilistQueries {
|
|||||||
sortOrder: String? = null
|
sortOrder: String? = null
|
||||||
): MutableMap<String, ArrayList<Media>> {
|
): MutableMap<String, ArrayList<Media>> {
|
||||||
val response =
|
val response =
|
||||||
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: $userId, type: ${if (anime) "ANIME" else "MANGA"}) { lists { name isCustomList entries { status progress private score(format:POINT_100) updatedAt media { id idMal isAdult type status chapters episodes nextAiringEpisode {episode} bannerImage meanScore isFavourite format coverImage{large} startDate{year month day} title {english romaji userPreferred } } } } user { id mediaListOptions { rowOrder animeList { sectionOrder } mangaList { sectionOrder } } } } }""")
|
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: $userId, type: ${if (anime) "ANIME" else "MANGA"}) { lists { name isCustomList entries { status progress private score(format:POINT_100) updatedAt media { id idMal isAdult type status chapters episodes nextAiringEpisode {episode} bannerImage genres meanScore isFavourite format coverImage{large} startDate{year month day} title {english romaji userPreferred } } } } user { id mediaListOptions { rowOrder animeList { sectionOrder } mangaList { sectionOrder } } } } }""")
|
||||||
val sorted = mutableMapOf<String, ArrayList<Media>>()
|
val sorted = mutableMapOf<String, ArrayList<Media>>()
|
||||||
val unsorted = mutableMapOf<String, ArrayList<Media>>()
|
val unsorted = mutableMapOf<String, ArrayList<Media>>()
|
||||||
val all = arrayListOf<Media>()
|
val all = arrayListOf<Media>()
|
||||||
@@ -417,13 +723,19 @@ class AnilistQueries {
|
|||||||
if (!sorted.containsKey(it.key)) sorted[it.key] = it.value
|
if (!sorted.containsKey(it.key)) sorted[it.key] = it.value
|
||||||
}
|
}
|
||||||
|
|
||||||
sorted["Favourites"] = favMedia(anime)
|
sorted["Favourites"] = favMedia(anime, userId)
|
||||||
sorted["Favourites"]?.sortWith(compareBy { it.userFavOrder })
|
sorted["Favourites"]?.sortWith(compareBy { it.userFavOrder })
|
||||||
|
//favMedia doesn't fill userProgress, so we need to fill it manually by searching :(
|
||||||
|
sorted["Favourites"]?.forEach { fav ->
|
||||||
|
all.find { it.id == fav.id }?.let {
|
||||||
|
fav.userProgress = it.userProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sorted["All"] = all
|
sorted["All"] = all
|
||||||
val listsort = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val listSort: String = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder)
|
||||||
?.getString("sort_order", "score")
|
else PrefManager.getVal(PrefName.MangaListSortOrder)
|
||||||
val sort = listsort ?: sortOrder ?: options?.rowOrder
|
val sort = listSort ?: sortOrder ?: options?.rowOrder
|
||||||
for (i in sorted.keys) {
|
for (i in sorted.keys) {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
"score" -> sorted[i]?.sortWith { b, a ->
|
"score" -> sorted[i]?.sortWith { b, a ->
|
||||||
@@ -444,11 +756,19 @@ class AnilistQueries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun getGenresAndTags(activity: Activity): Boolean {
|
suspend fun getGenresAndTags(): Boolean {
|
||||||
var genres: ArrayList<String>? = loadData("genres_list", activity)
|
var genres: ArrayList<String>? = PrefManager.getVal<Set<String>>(PrefName.GenresList)
|
||||||
var tags: Map<Boolean, List<String>>? = loadData("tags_map", activity)
|
.toMutableList() as ArrayList<String>?
|
||||||
|
val adultTags = PrefManager.getVal<Set<String>>(PrefName.TagsListIsAdult).toMutableList()
|
||||||
|
val nonAdultTags =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.TagsListNonAdult).toMutableList()
|
||||||
|
var tags = if (adultTags.isEmpty() || nonAdultTags.isEmpty()) null else
|
||||||
|
mapOf(
|
||||||
|
true to adultTags.sortedBy { it },
|
||||||
|
false to nonAdultTags.sortedBy { it }
|
||||||
|
)
|
||||||
|
|
||||||
if (genres == null) {
|
if (genres.isNullOrEmpty()) {
|
||||||
executeQuery<Query.GenreCollection>(
|
executeQuery<Query.GenreCollection>(
|
||||||
"""{GenreCollection}""",
|
"""{GenreCollection}""",
|
||||||
force = true,
|
force = true,
|
||||||
@@ -458,7 +778,7 @@ class AnilistQueries {
|
|||||||
forEach {
|
forEach {
|
||||||
genres?.add(it)
|
genres?.add(it)
|
||||||
}
|
}
|
||||||
saveData("genres_list", genres!!)
|
PrefManager.setVal(PrefName.GenresList, genres?.toSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tags == null) {
|
if (tags == null) {
|
||||||
@@ -476,11 +796,12 @@ class AnilistQueries {
|
|||||||
true to adult,
|
true to adult,
|
||||||
false to good
|
false to good
|
||||||
)
|
)
|
||||||
saveData("tags_map", tags)
|
PrefManager.setVal(PrefName.TagsListIsAdult, adult.toSet())
|
||||||
|
PrefManager.setVal(PrefName.TagsListNonAdult, good.toSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (genres != null && tags != null) {
|
return if (!genres.isNullOrEmpty() && tags != null) {
|
||||||
Anilist.genres = genres
|
Anilist.genres = genres?.sortedBy { it }?.toMutableList() as ArrayList<String>
|
||||||
Anilist.tags = tags
|
Anilist.tags = tags
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
@@ -496,8 +817,37 @@ class AnilistQueries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <K, V : Serializable> saveSerializableMap(prefKey: String, map: Map<K, V>) {
|
||||||
|
val byteStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
ObjectOutputStream(byteStream).use { outputStream ->
|
||||||
|
outputStream.writeObject(map)
|
||||||
|
}
|
||||||
|
val serializedMap = Base64.encodeToString(byteStream.toByteArray(), Base64.DEFAULT)
|
||||||
|
PrefManager.setCustomVal(prefKey, serializedMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun <K, V : Serializable> loadSerializableMap(prefKey: String): Map<K, V>? {
|
||||||
|
try {
|
||||||
|
val serializedMap = PrefManager.getCustomVal(prefKey, "")
|
||||||
|
if (serializedMap.isEmpty()) return null
|
||||||
|
|
||||||
|
val bytes = Base64.decode(serializedMap, Base64.DEFAULT)
|
||||||
|
val byteArrayStream = ByteArrayInputStream(bytes)
|
||||||
|
|
||||||
|
return ObjectInputStream(byteArrayStream).use { inputStream ->
|
||||||
|
inputStream.readObject() as? Map<K, V>
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getGenreThumbnail(genre: String): Genre? {
|
private suspend fun getGenreThumbnail(genre: String): Genre? {
|
||||||
val genres = loadData<MutableMap<String, Genre>>("genre_thumb") ?: mutableMapOf()
|
val genres: MutableMap<String, Genre> =
|
||||||
|
loadSerializableMap<String, Genre>("genre_thumb")?.toMutableMap()
|
||||||
|
?: mutableMapOf()
|
||||||
if (genres.checkGenreTime(genre)) {
|
if (genres.checkGenreTime(genre)) {
|
||||||
try {
|
try {
|
||||||
val genreQuery =
|
val genreQuery =
|
||||||
@@ -510,7 +860,7 @@ class AnilistQueries {
|
|||||||
it.bannerImage!!,
|
it.bannerImage!!,
|
||||||
System.currentTimeMillis()
|
System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
saveData("genre_thumb", genres)
|
saveSerializableMap("genre_thumb", genres)
|
||||||
return genres[genre]
|
return genres[genre]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,7 +1015,7 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
|||||||
page = pageInfo.currentPage.toString().toIntOrNull() ?: 0,
|
page = pageInfo.currentPage.toString().toIntOrNull() ?: 0,
|
||||||
hasNextPage = pageInfo.hasNextPage == true,
|
hasNextPage = pageInfo.hasNextPage == true,
|
||||||
)
|
)
|
||||||
} else snackString(currContext()?.getString(R.string.empty_response))
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +1073,7 @@ Page(page:$page,perPage:50) {
|
|||||||
if (smaller) {
|
if (smaller) {
|
||||||
val response = execute()?.airingSchedules ?: return null
|
val response = execute()?.airingSchedules ?: return null
|
||||||
val idArr = mutableListOf<Int>()
|
val idArr = mutableListOf<Int>()
|
||||||
val listOnly = loadData("recently_list_only") ?: false
|
val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly)
|
||||||
return response.mapNotNull { i ->
|
return response.mapNotNull { i ->
|
||||||
i.media?.let {
|
i.media?.let {
|
||||||
if (!idArr.contains(it.id))
|
if (!idArr.contains(it.id))
|
||||||
@@ -978,4 +1328,116 @@ Page(page:$page,perPage:50) {
|
|||||||
return author
|
return author
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun toggleFollow(id: Int): Query.ToggleFollow? {
|
||||||
|
return executeQuery<Query.ToggleFollow>(
|
||||||
|
"""mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun toggleLike(id: Int, type: String): ToggleLike? {
|
||||||
|
return executeQuery<ToggleLike>(
|
||||||
|
"""mutation Like{ToggleLikeV2(id:$id,type:$type){__typename}}"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
|
||||||
|
return executeQuery<Query.UserProfileResponse>(
|
||||||
|
"""{followerPage:Page{followers(userId:$id){id}pageInfo{total}}followingPage:Page{following(userId:$id){id}pageInfo{total}}user:User(id:$id){id name about(asHtml:true)avatar{medium large}bannerImage isFollowing isFollower isBlocked favourites{anime{nodes{id coverImage{extraLarge large medium color}}}manga{nodes{id coverImage{extraLarge large medium color}}}characters{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}staff{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}studios{nodes{id name isFavourite}}}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}manga{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}}siteUrl}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserProfile(username: String): Query.UserProfileResponse? {
|
||||||
|
val id = getUserId(username) ?: return null
|
||||||
|
return getUserProfile(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserId(username: String): Int? {
|
||||||
|
return executeQuery<Query.User>(
|
||||||
|
"""{User(name:"$username"){id}}""",
|
||||||
|
force = true
|
||||||
|
)?.data?.user?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserStatistics(id: Int, sort: String = "ID"): Query.StatisticsResponse? {
|
||||||
|
return executeQuery<Query.StatisticsResponse>(
|
||||||
|
"""{User(id:$id){id name mediaListOptions{scoreFormat}statistics{anime{...UserStatistics}manga{...UserStatistics}}}}fragment UserStatistics on UserStatistics{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead formats(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds format}statuses(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds status}scores(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds score}lengths(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds length}releaseYears(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds releaseYear}startYears(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds startYear}genres(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds genre}tags(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds tag{id name}}countries(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds country}voiceActors(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds voiceActor{id name{first middle last full native alternative userPreferred}}characterIds}staff(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds staff{id name{first middle last full native alternative userPreferred}}}studios(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds studio{id name isAnimationStudio}}}""",
|
||||||
|
force = true,
|
||||||
|
show = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun userFavMediaQuery(anime: Boolean, page: Int, id: Int): String {
|
||||||
|
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun userFollowing(id: Int): Query.Following? {
|
||||||
|
return executeQuery<Query.Following>(
|
||||||
|
"""{Page {following(userId:${id},sort:[USERNAME]){id name avatar{large medium}bannerImage}}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun userFollowers(id: Int): Query.Follower? {
|
||||||
|
return executeQuery<Query.Follower>(
|
||||||
|
"""{Page {followers(userId:${id},sort:[USERNAME]){id name avatar{large medium}bannerImage}}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun initProfilePage(id: Int): Query.ProfilePageMedia? {
|
||||||
|
return executeQuery<Query.ProfilePageMedia>(
|
||||||
|
"""{
|
||||||
|
favoriteAnime:${userFavMediaQuery(true, 1, id)}
|
||||||
|
favoriteManga:${userFavMediaQuery(false, 1, id)}
|
||||||
|
animeMediaList:${bannerImageQuery("ANIME", id)}
|
||||||
|
mangaMediaList:${bannerImageQuery("MANGA", id)}
|
||||||
|
}""".trimIndent(), force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bannerImageQuery(type: String, id: Int?): String {
|
||||||
|
return """MediaListCollection(userId: ${id}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } }"""
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getNotifications(id: Int, page: Int = 1, resetNotification: Boolean = true): NotificationResponse? {
|
||||||
|
val reset = if (resetNotification) "true" else "false"
|
||||||
|
val res = executeQuery<NotificationResponse>(
|
||||||
|
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
if (res != null && resetNotification) {
|
||||||
|
val commentNotifications = PrefManager.getVal(PrefName.UnreadCommentNotifications, 0)
|
||||||
|
res.data.user.unreadNotificationCount += commentNotifications
|
||||||
|
PrefManager.setVal(PrefName.UnreadCommentNotifications, 0)
|
||||||
|
Anilist.unreadNotificationCount = 0
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFeed(userId: Int?, global: Boolean = false, page: Int = 1, activityId: Int? = null): FeedResponse? {
|
||||||
|
val filter = if (activityId != null) "id:$activityId,"
|
||||||
|
else if (userId != null) "userId:$userId,"
|
||||||
|
else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
|
||||||
|
else "isFollowing:true,type_not:MESSAGE,"
|
||||||
|
return executeQuery<FeedResponse>(
|
||||||
|
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isUserFav(favType: AnilistMutations.FavType, id: Int): Boolean { //anilist isFavourite is broken, so we need to check it manually
|
||||||
|
val res = getUserProfile(Anilist.userid?: return false)
|
||||||
|
return when (favType) {
|
||||||
|
AnilistMutations.FavType.ANIME -> res?.data?.user?.favourites?.anime?.nodes?.any { it.id == id } ?: false
|
||||||
|
AnilistMutations.FavType.MANGA -> res?.data?.user?.favourites?.manga?.nodes?.any { it.id == id } ?: false
|
||||||
|
AnilistMutations.FavType.CHARACTER -> res?.data?.user?.favourites?.characters?.nodes?.any { it.id == id } ?: false
|
||||||
|
AnilistMutations.FavType.STAFF -> res?.data?.user?.favourites?.staff?.nodes?.any { it.id == id } ?: false
|
||||||
|
AnilistMutations.FavType.STUDIO -> res?.data?.user?.favourites?.studios?.nodes?.any { it.id == id } ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ITEMS_PER_PAGE = 25
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,26 +5,25 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import ani.dantotsu.BuildConfig
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.discord.Discord
|
import ani.dantotsu.connections.discord.Discord
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.others.AppUpdater
|
import ani.dantotsu.others.AppUpdater
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
suspend fun getUserId(context: Context, block: () -> Unit) {
|
suspend fun getUserId(context: Context, block: () -> Unit) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val sharedPref = context.getSharedPreferences(
|
val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
|
||||||
context.getString(R.string.preference_file_key),
|
val userid = PrefManager.getVal(PrefName.DiscordId, null as String?)
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
val token = sharedPref.getString("discord_token", null)
|
|
||||||
val userid = sharedPref.getString("discord_id", null)
|
|
||||||
if (userid == null && token != null) {
|
if (userid == null && token != null) {
|
||||||
/*if (!Discord.getUserData())
|
/*if (!Discord.getUserData())
|
||||||
snackString(context.getString(R.string.error_loading_discord_user_data))*/
|
snackString(context.getString(R.string.error_loading_discord_user_data))*/
|
||||||
@@ -99,12 +98,26 @@ class AnilistHomeViewModel : ViewModel() {
|
|||||||
fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation
|
fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation
|
||||||
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
|
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
|
||||||
|
|
||||||
|
suspend fun initHomePage() {
|
||||||
|
val res = Anilist.query.initHomePage()
|
||||||
|
Logger.log("AnilistHomeViewModel : res=$res")
|
||||||
|
res["currentAnime"]?.let { animeContinue.postValue(it) }
|
||||||
|
res["favoriteAnime"]?.let { animeFav.postValue(it) }
|
||||||
|
res["plannedAnime"]?.let { animePlanned.postValue(it) }
|
||||||
|
res["currentManga"]?.let { mangaContinue.postValue(it) }
|
||||||
|
res["favoriteManga"]?.let { mangaFav.postValue(it) }
|
||||||
|
res["plannedManga"]?.let { mangaPlanned.postValue(it) }
|
||||||
|
res["recommendations"]?.let { recommendation.postValue(it) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun loadMain(context: FragmentActivity) {
|
suspend fun loadMain(context: FragmentActivity) {
|
||||||
Anilist.getSavedToken(context)
|
Anilist.getSavedToken()
|
||||||
MAL.getSavedToken(context)
|
MAL.getSavedToken(context)
|
||||||
Discord.getSavedToken(context)
|
Discord.getSavedToken(context)
|
||||||
if (loadData<Boolean>("check_update") != false) AppUpdater.check(context)
|
if (!BuildConfig.FLAVOR.contains("fdroid")) {
|
||||||
genres.postValue(Anilist.query.getGenresAndTags(context))
|
if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context)
|
||||||
|
}
|
||||||
|
genres.postValue(Anilist.query.getGenresAndTags())
|
||||||
}
|
}
|
||||||
|
|
||||||
val empty = MutableLiveData<Boolean>(null)
|
val empty = MutableLiveData<Boolean>(null)
|
||||||
@@ -320,4 +333,64 @@ class GenresViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val mangaFav: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
|
fun getMangaFav(): LiveData<ArrayList<Media>> = mangaFav
|
||||||
|
|
||||||
|
private val animeFav: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
|
fun getAnimeFav(): LiveData<ArrayList<Media>> = animeFav
|
||||||
|
|
||||||
|
private val listImages: MutableLiveData<ArrayList<String?>> =
|
||||||
|
MutableLiveData<ArrayList<String?>>(arrayListOf())
|
||||||
|
|
||||||
|
fun getListImages(): LiveData<ArrayList<String?>> = listImages
|
||||||
|
|
||||||
|
suspend fun setData(id: Int) {
|
||||||
|
val res = Anilist.query.initProfilePage(id)
|
||||||
|
val mangaList = res?.data?.favoriteManga?.favourites?.manga?.edges?.mapNotNull {
|
||||||
|
it.node?.let { i ->
|
||||||
|
Media(i).apply { isFav = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mangaFav.postValue(ArrayList(mangaList ?: arrayListOf()))
|
||||||
|
val animeList = res?.data?.favoriteAnime?.favourites?.anime?.edges?.mapNotNull {
|
||||||
|
it.node?.let { i ->
|
||||||
|
Media(i).apply { isFav = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animeFav.postValue(ArrayList(animeList ?: arrayListOf()))
|
||||||
|
|
||||||
|
val bannerImages = arrayListOf<String?>(null, null)
|
||||||
|
val animeRandom = res?.data?.animeMediaList?.lists?.mapNotNull {
|
||||||
|
it.entries?.mapNotNull { entry ->
|
||||||
|
val imageUrl = entry.media?.bannerImage
|
||||||
|
if (imageUrl != null && imageUrl != "null") imageUrl
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
}?.flatten()?.randomOrNull()
|
||||||
|
bannerImages[0] = animeRandom
|
||||||
|
val mangaRandom = res?.data?.mangaMediaList?.lists?.mapNotNull {
|
||||||
|
it.entries?.mapNotNull { entry ->
|
||||||
|
val imageUrl = entry.media?.bannerImage
|
||||||
|
if (imageUrl != null && imageUrl != "null") imageUrl
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
}?.flatten()?.randomOrNull()
|
||||||
|
bannerImages[1] = mangaRandom
|
||||||
|
listImages.postValue(bannerImages)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
mangaFav.postValue(mangaFav.value)
|
||||||
|
animeFav.postValue(animeFav.value)
|
||||||
|
listImages.postValue(listImages.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,25 @@
|
|||||||
package ani.dantotsu.connections.anilist
|
package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
val data: Uri? = intent?.data
|
val data: Uri? = intent?.data
|
||||||
logger(data.toString())
|
|
||||||
try {
|
try {
|
||||||
Anilist.token =
|
Anilist.token =
|
||||||
Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
|
Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
|
||||||
val filename = "anilistToken"
|
PrefManager.setVal(PrefName.AnilistToken, Anilist.token ?: "")
|
||||||
this.openFileOutput(filename, Context.MODE_PRIVATE).use {
|
|
||||||
it.write(Anilist.token!!.toByteArray())
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,27 +5,31 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import ani.dantotsu.loadMedia
|
import ani.dantotsu.loadMedia
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class UrlMedia : Activity() {
|
class UrlMedia : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
|
val data: Uri? = intent?.data
|
||||||
var isMAL = false
|
val type = data?.pathSegments?.getOrNull(0)
|
||||||
var continueMedia = true
|
if (type != "user") {
|
||||||
if (id == 0) {
|
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
|
||||||
continueMedia = false
|
var isMAL = false
|
||||||
val data: Uri? = intent?.data
|
var continueMedia = true
|
||||||
isMAL = data?.host != "anilist.co"
|
if (id == 0) {
|
||||||
id = data?.pathSegments?.getOrNull(1)?.toIntOrNull()
|
continueMedia = false
|
||||||
} else loadMedia = id
|
isMAL = data?.host != "anilist.co"
|
||||||
startMainActivity(
|
id = data?.pathSegments?.getOrNull(1)?.toIntOrNull()
|
||||||
this,
|
} else loadMedia = id
|
||||||
bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia)
|
startMainActivity(
|
||||||
)
|
this,
|
||||||
|
bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val username = data.pathSegments?.getOrNull(1)
|
||||||
|
startMainActivity(this, bundleOf("username" to username))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ data class Character(
|
|||||||
|
|
||||||
// Notes for site moderators
|
// Notes for site moderators
|
||||||
@SerialName("modNotes") var modNotes: String?,
|
@SerialName("modNotes") var modNotes: String?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CharacterConnection(
|
data class CharacterConnection(
|
||||||
@@ -56,7 +56,7 @@ data class CharacterConnection(
|
|||||||
|
|
||||||
// The pagination information
|
// The pagination information
|
||||||
// @SerialName("pageInfo") var pageInfo: PageInfo?,
|
// @SerialName("pageInfo") var pageInfo: PageInfo?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CharacterEdge(
|
data class CharacterEdge(
|
||||||
@@ -82,7 +82,7 @@ data class CharacterEdge(
|
|||||||
|
|
||||||
// The order the character should be displayed from the users favourites
|
// The order the character should be displayed from the users favourites
|
||||||
@SerialName("favouriteOrder") var favouriteOrder: Int?,
|
@SerialName("favouriteOrder") var favouriteOrder: Int?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CharacterName(
|
data class CharacterName(
|
||||||
@@ -109,7 +109,7 @@ data class CharacterName(
|
|||||||
|
|
||||||
// The currently authenticated users preferred name language. Default romaji for non-authenticated
|
// The currently authenticated users preferred name language. Default romaji for non-authenticated
|
||||||
@SerialName("userPreferred") var userPreferred: String?,
|
@SerialName("userPreferred") var userPreferred: String?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CharacterImage(
|
data class CharacterImage(
|
||||||
@@ -118,4 +118,4 @@ data class CharacterImage(
|
|||||||
|
|
||||||
// The character's image of media at medium size
|
// The character's image of media at medium size
|
||||||
@SerialName("medium") var medium: String?,
|
@SerialName("medium") var medium: String?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
@@ -105,41 +105,584 @@ class Query {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CombinedMediaListResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data?
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("current") val current: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("planned") val planned: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("repeating") val repeating: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HomePageMedia(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data?
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("currentAnime") val currentAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("repeatingAnime") val repeatingAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?,
|
||||||
|
@SerialName("plannedAnime") val plannedAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("currentManga") val currentManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("repeatingManga") val repeatingManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User?,
|
||||||
|
@SerialName("plannedManga") val plannedManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("recommendationQuery") val recommendationQuery: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
|
@SerialName("recommendationPlannedQueryAnime") val recommendationPlannedQueryAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("recommendationPlannedQueryManga") val recommendationPlannedQueryManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ProfilePageMedia(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data?
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?,
|
||||||
|
@SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User?,
|
||||||
|
@SerialName("animeMediaList") val animeMediaList: ani.dantotsu.connections.anilist.api.MediaListCollection?,
|
||||||
|
@SerialName("mangaMediaList") val mangaMediaList: ani.dantotsu.connections.anilist.api.MediaListCollection?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToggleFollow(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data?
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("ToggleFollow")
|
||||||
|
val toggleFollow: FollowData
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GenreCollection(
|
data class GenreCollection(
|
||||||
@SerialName("data")
|
@SerialName("data")
|
||||||
val data: Data
|
val data: Data
|
||||||
) {
|
) : java.io.Serializable {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Data(
|
data class Data(
|
||||||
@SerialName("GenreCollection")
|
@SerialName("GenreCollection")
|
||||||
val genreCollection: List<String>?
|
val genreCollection: List<String>?
|
||||||
)
|
) : java.io.Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MediaTagCollection(
|
data class MediaTagCollection(
|
||||||
@SerialName("data")
|
@SerialName("data")
|
||||||
val data: Data
|
val data: Data
|
||||||
) {
|
) : java.io.Serializable {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Data(
|
data class Data(
|
||||||
@SerialName("MediaTagCollection")
|
@SerialName("MediaTagCollection")
|
||||||
val mediaTagCollection: List<MediaTag>?
|
val mediaTagCollection: List<MediaTag>?
|
||||||
)
|
) : java.io.Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class User(
|
data class User(
|
||||||
@SerialName("data")
|
@SerialName("data")
|
||||||
val data: Data
|
val data: Data
|
||||||
) {
|
) : java.io.Serializable {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Data(
|
data class Data(
|
||||||
@SerialName("User")
|
@SerialName("User")
|
||||||
val user: ani.dantotsu.connections.anilist.api.User?
|
val user: ani.dantotsu.connections.anilist.api.User?
|
||||||
)
|
) : java.io.Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserProfileResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("followerPage")
|
||||||
|
val followerPage: UserProfilePage?,
|
||||||
|
@SerialName("followingPage")
|
||||||
|
val followingPage: UserProfilePage?,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: UserProfile?
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserProfilePage(
|
||||||
|
@SerialName("pageInfo")
|
||||||
|
val pageInfo: PageInfo,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Following(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("Page")
|
||||||
|
val page: FollowingPage?
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Follower(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("Page")
|
||||||
|
val page: FollowerPage?
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FollowerPage(
|
||||||
|
@SerialName("followers")
|
||||||
|
val followers: List<ani.dantotsu.connections.anilist.api.User>?
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FollowingPage(
|
||||||
|
@SerialName("following")
|
||||||
|
val following: List<ani.dantotsu.connections.anilist.api.User>?
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserProfile(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerialName("about")
|
||||||
|
val about: String?,
|
||||||
|
@SerialName("avatar")
|
||||||
|
val avatar: UserAvatar?,
|
||||||
|
@SerialName("bannerImage")
|
||||||
|
val bannerImage: String?,
|
||||||
|
@SerialName("isFollowing")
|
||||||
|
var isFollowing: Boolean,
|
||||||
|
@SerialName("isFollower")
|
||||||
|
val isFollower: Boolean,
|
||||||
|
@SerialName("isBlocked")
|
||||||
|
val isBlocked: Boolean,
|
||||||
|
@SerialName("favourites")
|
||||||
|
val favourites: UserFavourites?,
|
||||||
|
@SerialName("statistics")
|
||||||
|
val statistics: NNUserStatisticTypes,
|
||||||
|
@SerialName("siteUrl")
|
||||||
|
val siteUrl: String,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NNUserStatisticTypes(
|
||||||
|
@SerialName("anime") var anime: NNUserStatistics,
|
||||||
|
@SerialName("manga") var manga: NNUserStatistics
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NNUserStatistics(
|
||||||
|
@SerialName("count") var count: Int,
|
||||||
|
@SerialName("meanScore") var meanScore: Float,
|
||||||
|
@SerialName("standardDeviation") var standardDeviation: Float,
|
||||||
|
@SerialName("minutesWatched") var minutesWatched: Int,
|
||||||
|
@SerialName("episodesWatched") var episodesWatched: Int,
|
||||||
|
@SerialName("chaptersRead") var chaptersRead: Int,
|
||||||
|
@SerialName("volumesRead") var volumesRead: Int,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserFavourites(
|
||||||
|
@SerialName("anime")
|
||||||
|
val anime: UserMediaFavouritesCollection,
|
||||||
|
@SerialName("manga")
|
||||||
|
val manga: UserMediaFavouritesCollection,
|
||||||
|
@SerialName("characters")
|
||||||
|
val characters: UserCharacterFavouritesCollection,
|
||||||
|
@SerialName("staff")
|
||||||
|
val staff: UserStaffFavouritesCollection,
|
||||||
|
@SerialName("studios")
|
||||||
|
val studios: UserStudioFavouritesCollection,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserMediaFavouritesCollection(
|
||||||
|
@SerialName("nodes")
|
||||||
|
val nodes: List<UserMediaImageFavorite>,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserMediaImageFavorite(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("coverImage")
|
||||||
|
val coverImage: MediaCoverImage
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserCharacterFavouritesCollection(
|
||||||
|
@SerialName("nodes")
|
||||||
|
val nodes: List<UserCharacterImageFavorite>,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserCharacterImageFavorite(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: CharacterName,
|
||||||
|
@SerialName("image")
|
||||||
|
val image: CharacterImage,
|
||||||
|
@SerialName("isFavourite")
|
||||||
|
val isFavourite: Boolean
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserStaffFavouritesCollection(
|
||||||
|
@SerialName("nodes")
|
||||||
|
val nodes: List<UserCharacterImageFavorite>, //downstream it's the same as character
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserStudioFavouritesCollection(
|
||||||
|
@SerialName("nodes")
|
||||||
|
val nodes: List<UserStudioFavorite>,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserStudioFavorite(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String,
|
||||||
|
): java.io.Serializable
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
// Statistics
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
): java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("User")
|
||||||
|
val user: StatisticsUser?
|
||||||
|
): java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsUser(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerialName("mediaListOptions")
|
||||||
|
val mediaListOptions: MediaListOptions,
|
||||||
|
@SerialName("statistics")
|
||||||
|
val statistics: StatisticsTypes
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsTypes(
|
||||||
|
@SerialName("anime")
|
||||||
|
val anime: Statistics,
|
||||||
|
@SerialName("manga")
|
||||||
|
val manga: Statistics
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Statistics(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("standardDeviation")
|
||||||
|
val standardDeviation: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("episodesWatched")
|
||||||
|
val episodesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("volumesRead")
|
||||||
|
val volumesRead: Int,
|
||||||
|
@SerialName("formats")
|
||||||
|
val formats: List<StatisticsFormat>,
|
||||||
|
@SerialName("statuses")
|
||||||
|
val statuses: List<StatisticsStatus>,
|
||||||
|
@SerialName("scores")
|
||||||
|
val scores: List<StatisticsScore>,
|
||||||
|
@SerialName("lengths")
|
||||||
|
val lengths: List<StatisticsLength>,
|
||||||
|
@SerialName("releaseYears")
|
||||||
|
val releaseYears: List<StatisticsReleaseYear>,
|
||||||
|
@SerialName("startYears")
|
||||||
|
val startYears: List<StatisticsStartYear>,
|
||||||
|
@SerialName("genres")
|
||||||
|
val genres: List<StatisticsGenre>,
|
||||||
|
@SerialName("tags")
|
||||||
|
val tags: List<StatisticsTag>,
|
||||||
|
@SerialName("countries")
|
||||||
|
val countries: List<StatisticsCountry>,
|
||||||
|
@SerialName("voiceActors")
|
||||||
|
val voiceActors: List<StatisticsVoiceActor>,
|
||||||
|
@SerialName("staff")
|
||||||
|
val staff: List<StatisticsStaff>,
|
||||||
|
@SerialName("studios")
|
||||||
|
val studios: List<StatisticsStudio>
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsFormat(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("format")
|
||||||
|
val format: String
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsStatus(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("status")
|
||||||
|
val status: String
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsScore(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("score")
|
||||||
|
val score: Int
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsLength(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("length")
|
||||||
|
val length: String? //can be null for manga
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsReleaseYear(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("releaseYear")
|
||||||
|
val releaseYear: Int
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsStartYear(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("startYear")
|
||||||
|
val startYear: Int
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsGenre(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("genre")
|
||||||
|
val genre: String
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsTag(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("tag")
|
||||||
|
val tag: Tag
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Tag(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsCountry(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("country")
|
||||||
|
val country: String
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsVoiceActor(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("voiceActor")
|
||||||
|
val voiceActor: VoiceActor,
|
||||||
|
@SerialName("characterIds")
|
||||||
|
val characterIds: List<Int>
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VoiceActor(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: StaffName
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StaffName(
|
||||||
|
@SerialName("first")
|
||||||
|
val first: String?,
|
||||||
|
@SerialName("middle")
|
||||||
|
val middle: String?,
|
||||||
|
@SerialName("last")
|
||||||
|
val last: String?,
|
||||||
|
@SerialName("full")
|
||||||
|
val full: String?,
|
||||||
|
@SerialName("native")
|
||||||
|
val native: String?,
|
||||||
|
@SerialName("alternative")
|
||||||
|
val alternative: List<String>?,
|
||||||
|
@SerialName("userPreferred")
|
||||||
|
val userPreferred: String?
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsStaff(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("staff")
|
||||||
|
val staff: VoiceActor
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatisticsStudio(
|
||||||
|
@SerialName("count")
|
||||||
|
val count: Int,
|
||||||
|
@SerialName("meanScore")
|
||||||
|
val meanScore: Float,
|
||||||
|
@SerialName("minutesWatched")
|
||||||
|
val minutesWatched: Int,
|
||||||
|
@SerialName("chaptersRead")
|
||||||
|
val chaptersRead: Int,
|
||||||
|
@SerialName("mediaIds")
|
||||||
|
val mediaIds: List<Int>,
|
||||||
|
@SerialName("studio")
|
||||||
|
val studio: StatStudio
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StatStudio(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerialName("isAnimationStudio")
|
||||||
|
val isAnimationStudio: Boolean
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//data class WhaData(
|
//data class WhaData(
|
||||||
@@ -169,7 +712,7 @@ class Query {
|
|||||||
// // Activity reply query
|
// // Activity reply query
|
||||||
// val ActivityReply: ActivityReply?,
|
// val ActivityReply: ActivityReply?,
|
||||||
|
|
||||||
// // Comment query
|
// // CommentNotificationWorker query
|
||||||
// val ThreadComment: List<ThreadComment>?,
|
// val ThreadComment: List<ThreadComment>?,
|
||||||
|
|
||||||
// // Notification query
|
// // Notification query
|
||||||
|
|||||||
114
app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt
Normal file
114
app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package ani.dantotsu.connections.anilist.api
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FeedResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("Page")
|
||||||
|
val page: ActivityPage
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ActivityPage(
|
||||||
|
@SerialName("activities")
|
||||||
|
val activities: List<Activity>
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Activity(
|
||||||
|
@SerialName("__typename")
|
||||||
|
val typename: String,
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("recipientId")
|
||||||
|
val recipientId: Int?,
|
||||||
|
@SerialName("messengerId")
|
||||||
|
val messengerId: Int?,
|
||||||
|
@SerialName("userId")
|
||||||
|
val userId: Int?,
|
||||||
|
@SerialName("type")
|
||||||
|
val type: String,
|
||||||
|
@SerialName("replyCount")
|
||||||
|
val replyCount: Int,
|
||||||
|
@SerialName("status")
|
||||||
|
val status: String?,
|
||||||
|
@SerialName("progress")
|
||||||
|
val progress: String?,
|
||||||
|
@SerialName("text")
|
||||||
|
val text: String?,
|
||||||
|
@SerialName("message")
|
||||||
|
val message: String?,
|
||||||
|
@SerialName("siteUrl")
|
||||||
|
val siteUrl: String?,
|
||||||
|
@SerialName("isLocked")
|
||||||
|
val isLocked: Boolean,
|
||||||
|
@SerialName("isSubscribed")
|
||||||
|
val isSubscribed: Boolean,
|
||||||
|
@SerialName("likeCount")
|
||||||
|
var likeCount: Int?,
|
||||||
|
@SerialName("isLiked")
|
||||||
|
var isLiked: Boolean?,
|
||||||
|
@SerialName("isPinned")
|
||||||
|
val isPinned: Boolean?,
|
||||||
|
@SerialName("isPrivate")
|
||||||
|
val isPrivate: Boolean?,
|
||||||
|
@SerialName("createdAt")
|
||||||
|
val createdAt: Int,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: User?,
|
||||||
|
@SerialName("recipient")
|
||||||
|
val recipient: User?,
|
||||||
|
@SerialName("messenger")
|
||||||
|
val messenger: User?,
|
||||||
|
@SerialName("media")
|
||||||
|
val media: Media?,
|
||||||
|
@SerialName("replies")
|
||||||
|
val replies: List<ActivityReply>?,
|
||||||
|
@SerialName("likes")
|
||||||
|
val likes: List<User>?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ActivityReply(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("userId")
|
||||||
|
val userId: Int,
|
||||||
|
@SerialName("text")
|
||||||
|
val text: String,
|
||||||
|
@SerialName("likeCount")
|
||||||
|
val likeCount: Int,
|
||||||
|
@SerialName("isLiked")
|
||||||
|
val isLiked: Boolean,
|
||||||
|
@SerialName("createdAt")
|
||||||
|
val createdAt: Int,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: User,
|
||||||
|
@SerialName("likes")
|
||||||
|
val likes: List<User>?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToggleLike(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("ToggleLikeV2")
|
||||||
|
val toggleLike: LikeData
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LikeData(
|
||||||
|
@SerialName("__typename")
|
||||||
|
val typename: String
|
||||||
|
) : java.io.Serializable
|
||||||
@@ -251,7 +251,7 @@ data class MediaCoverImage(
|
|||||||
|
|
||||||
// Average #hex color of cover image
|
// Average #hex color of cover image
|
||||||
@SerialName("color") var color: String?,
|
@SerialName("color") var color: String?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MediaList(
|
data class MediaList(
|
||||||
@@ -490,7 +490,7 @@ data class MediaExternalLink(
|
|||||||
|
|
||||||
// isDisabled: Boolean
|
// isDisabled: Boolean
|
||||||
@SerialName("notes") var notes: String?,
|
@SerialName("notes") var notes: String?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class ExternalLinkType {
|
enum class ExternalLinkType {
|
||||||
@@ -512,7 +512,13 @@ data class MediaListCollection(
|
|||||||
// If there is another chunk
|
// If there is another chunk
|
||||||
@SerialName("hasNextChunk") var hasNextChunk: Boolean?,
|
@SerialName("hasNextChunk") var hasNextChunk: Boolean?,
|
||||||
|
|
||||||
)
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FollowData(
|
||||||
|
@SerialName("id") var id: Int,
|
||||||
|
@SerialName("isFollowing") var isFollowing: Boolean,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MediaListGroup(
|
data class MediaListGroup(
|
||||||
@@ -526,4 +532,4 @@ data class MediaListGroup(
|
|||||||
@SerialName("isSplitCompletedList") var isSplitCompletedList: Boolean?,
|
@SerialName("isSplitCompletedList") var isSplitCompletedList: Boolean?,
|
||||||
|
|
||||||
@SerialName("status") var status: MediaListStatus?,
|
@SerialName("status") var status: MediaListStatus?,
|
||||||
)
|
) : java.io.Serializable
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package ani.dantotsu.connections.anilist.api
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
enum class NotificationType(val value: String) {
|
||||||
|
ACTIVITY_MESSAGE("ACTIVITY_MESSAGE"),
|
||||||
|
ACTIVITY_REPLY("ACTIVITY_REPLY"),
|
||||||
|
FOLLOWING("FOLLOWING"),
|
||||||
|
ACTIVITY_MENTION("ACTIVITY_MENTION"),
|
||||||
|
THREAD_COMMENT_MENTION("THREAD_COMMENT_MENTION"),
|
||||||
|
THREAD_SUBSCRIBED("THREAD_SUBSCRIBED"),
|
||||||
|
THREAD_COMMENT_REPLY("THREAD_COMMENT_REPLY"),
|
||||||
|
AIRING("AIRING"),
|
||||||
|
ACTIVITY_LIKE("ACTIVITY_LIKE"),
|
||||||
|
ACTIVITY_REPLY_LIKE("ACTIVITY_REPLY_LIKE"),
|
||||||
|
THREAD_LIKE("THREAD_LIKE"),
|
||||||
|
THREAD_COMMENT_LIKE("THREAD_COMMENT_LIKE"),
|
||||||
|
ACTIVITY_REPLY_SUBSCRIBED("ACTIVITY_REPLY_SUBSCRIBED"),
|
||||||
|
RELATED_MEDIA_ADDITION("RELATED_MEDIA_ADDITION"),
|
||||||
|
MEDIA_DATA_CHANGE("MEDIA_DATA_CHANGE"),
|
||||||
|
MEDIA_MERGE("MEDIA_MERGE"),
|
||||||
|
MEDIA_DELETION("MEDIA_DELETION"),
|
||||||
|
//custom
|
||||||
|
COMMENT_REPLY("COMMENT_REPLY"),
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NotificationResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data,
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("User")
|
||||||
|
val user: NotificationUser,
|
||||||
|
@SerialName("Page")
|
||||||
|
val page: NotificationPage,
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NotificationUser(
|
||||||
|
@SerialName("unreadNotificationCount")
|
||||||
|
var unreadNotificationCount: Int,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NotificationPage(
|
||||||
|
@SerialName("pageInfo")
|
||||||
|
val pageInfo: PageInfo,
|
||||||
|
@SerialName("notifications")
|
||||||
|
val notifications: List<Notification>,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Notification(
|
||||||
|
@SerialName("__typename")
|
||||||
|
val typename: String,
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("userId")
|
||||||
|
val userId: Int? = null,
|
||||||
|
@SerialName("CommentId")
|
||||||
|
val commentId: Int?,
|
||||||
|
@SerialName("type")
|
||||||
|
val notificationType: String,
|
||||||
|
@SerialName("activityId")
|
||||||
|
val activityId: Int? = null,
|
||||||
|
@SerialName("animeId")
|
||||||
|
val mediaId: Int? = null,
|
||||||
|
@SerialName("episode")
|
||||||
|
val episode: Int? = null,
|
||||||
|
@SerialName("contexts")
|
||||||
|
val contexts: List<String>? = null,
|
||||||
|
@SerialName("context")
|
||||||
|
val context: String? = null,
|
||||||
|
@SerialName("reason")
|
||||||
|
val reason: String? = null,
|
||||||
|
@SerialName("deletedMediaTitle")
|
||||||
|
val deletedMediaTitle: String? = null,
|
||||||
|
@SerialName("deletedMediaTitles")
|
||||||
|
val deletedMediaTitles: List<String>? = null,
|
||||||
|
@SerialName("createdAt")
|
||||||
|
val createdAt: Int,
|
||||||
|
@SerialName("media")
|
||||||
|
val media: ani.dantotsu.connections.anilist.api.Media? = null,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: ani.dantotsu.connections.anilist.api.User? = null,
|
||||||
|
@SerialName("message")
|
||||||
|
val message: MessageActivity? = null,
|
||||||
|
@SerialName("activity")
|
||||||
|
val activity: ActivityUnion? = null,
|
||||||
|
@SerialName("Thread")
|
||||||
|
val thread: Thread? = null,
|
||||||
|
@SerialName("comment")
|
||||||
|
val comment: ThreadComment? = null,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MessageActivity(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ActivityUnion(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Thread(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThreadComment(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int?,
|
||||||
|
) : java.io.Serializable
|
||||||
@@ -15,7 +15,7 @@ data class Staff(
|
|||||||
@SerialName("languageV2") var languageV2: String?,
|
@SerialName("languageV2") var languageV2: String?,
|
||||||
|
|
||||||
// The staff images
|
// The staff images
|
||||||
// @SerialName("image") var image: StaffImage?,
|
@SerialName("image") var image: StaffImage?,
|
||||||
|
|
||||||
// A general description of the staff member
|
// A general description of the staff member
|
||||||
@SerialName("description") var description: String?,
|
@SerialName("description") var description: String?,
|
||||||
@@ -93,7 +93,14 @@ data class StaffConnection(
|
|||||||
// The pagination information
|
// The pagination information
|
||||||
// @SerialName("pageInfo") var pageInfo: PageInfo?,
|
// @SerialName("pageInfo") var pageInfo: PageInfo?,
|
||||||
)
|
)
|
||||||
|
@Serializable
|
||||||
|
data class StaffImage(
|
||||||
|
// The character's image of media at its largest size
|
||||||
|
@SerialName("large") var large: String?,
|
||||||
|
|
||||||
|
// The character's image of media at medium size
|
||||||
|
@SerialName("medium") var medium: String?,
|
||||||
|
) : java.io.Serializable
|
||||||
@Serializable
|
@Serializable
|
||||||
data class StaffEdge(
|
data class StaffEdge(
|
||||||
var role: String?,
|
var role: String?,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ data class User(
|
|||||||
@SerialName("statistics") var statistics: UserStatisticTypes?,
|
@SerialName("statistics") var statistics: UserStatisticTypes?,
|
||||||
|
|
||||||
// The number of unread notifications the user has
|
// The number of unread notifications the user has
|
||||||
// @SerialName("unreadNotificationCount") var unreadNotificationCount: Int?,
|
@SerialName("unreadNotificationCount") var unreadNotificationCount: Int?,
|
||||||
|
|
||||||
// The url for the user page on the AniList website
|
// The url for the user page on the AniList website
|
||||||
// @SerialName("siteUrl") var siteUrl: String?,
|
// @SerialName("siteUrl") var siteUrl: String?,
|
||||||
@@ -111,7 +111,7 @@ data class UserAvatar(
|
|||||||
|
|
||||||
// The avatar of user at medium size
|
// The avatar of user at medium size
|
||||||
@SerialName("medium") var medium: String?,
|
@SerialName("medium") var medium: String?,
|
||||||
)
|
): java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserStatisticTypes(
|
data class UserStatisticTypes(
|
||||||
@@ -164,7 +164,7 @@ data class Favourites(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class MediaListOptions(
|
data class MediaListOptions(
|
||||||
// The score format the user is using for media lists
|
// The score format the user is using for media lists
|
||||||
// @SerialName("scoreFormat") var scoreFormat: ScoreFormat?,
|
@SerialName("scoreFormat") var scoreFormat: String?,
|
||||||
|
|
||||||
// The default order list rows should be displayed in
|
// The default order list rows should be displayed in
|
||||||
@SerialName("rowOrder") var rowOrder: String?,
|
@SerialName("rowOrder") var rowOrder: String?,
|
||||||
|
|||||||
@@ -0,0 +1,554 @@
|
|||||||
|
package ani.dantotsu.connections.comments
|
||||||
|
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import com.lagradost.nicehttp.NiceResponse
|
||||||
|
import com.lagradost.nicehttp.Requests
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okio.IOException
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object CommentsAPI {
|
||||||
|
val address: String = "https://1224665.xyz:443"
|
||||||
|
var authToken: String? = null
|
||||||
|
var userId: String? = null
|
||||||
|
var isBanned: Boolean = false
|
||||||
|
var isAdmin: Boolean = false
|
||||||
|
var isMod: Boolean = false
|
||||||
|
var totalVotes: Int = 0
|
||||||
|
|
||||||
|
suspend fun getCommentsForId(id: Int, page: Int = 1, tag: Int?, sort: String?): CommentResponse? {
|
||||||
|
var url = "$address/comments/$id/$page"
|
||||||
|
val request = requestBuilder()
|
||||||
|
tag?.let {
|
||||||
|
url += "?tag=$it"
|
||||||
|
}
|
||||||
|
sort?.let {
|
||||||
|
url += if (tag != null) "&sort=$it" else "?sort=$it"
|
||||||
|
}
|
||||||
|
val json = try {
|
||||||
|
request.get(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to fetch comments")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!json.text.startsWith("{")) return null
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res && json.code != 404) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<CommentResponse>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRepliesFromId(id: Int, page: Int = 1): CommentResponse? {
|
||||||
|
val url = "$address/comments/parent/$id/$page"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.get(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to fetch comments")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!json.text.startsWith("{")) return null
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res && json.code != 404) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<CommentResponse>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSingleComment(id: Int): Comment? {
|
||||||
|
val url = "$address/comments/$id"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.get(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to fetch comment")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!json.text.startsWith("{")) return null
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res && json.code != 404) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<Comment>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun vote(commentId: Int, voteType: Int): Boolean {
|
||||||
|
val url = "$address/comments/vote/$commentId/$voteType"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.post(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to vote")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun comment(mediaId: Int, parentCommentId: Int?, content: String, tag: Int?): Comment? {
|
||||||
|
val url = "$address/comments"
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("user_id", userId ?: return null)
|
||||||
|
.add("media_id", mediaId.toString())
|
||||||
|
.add("content", content)
|
||||||
|
if (tag != null) {
|
||||||
|
body.add("tag", tag.toString())
|
||||||
|
}
|
||||||
|
parentCommentId?.let {
|
||||||
|
body.add("parent_comment_id", it.toString())
|
||||||
|
}
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.post(url, requestBody = body.build())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to comment")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<ReturnedComment>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackString("Failed to parse comment")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Comment(
|
||||||
|
parsed.id,
|
||||||
|
parsed.userId,
|
||||||
|
parsed.mediaId,
|
||||||
|
parsed.parentCommentId,
|
||||||
|
parsed.content,
|
||||||
|
parsed.timestamp,
|
||||||
|
parsed.deleted,
|
||||||
|
parsed.tag,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
Anilist.username ?: "",
|
||||||
|
Anilist.avatar,
|
||||||
|
totalVotes = totalVotes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteComment(commentId: Int): Boolean {
|
||||||
|
val url = "$address/comments/$commentId"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.delete(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to delete comment")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun editComment(commentId: Int, content: String): Boolean {
|
||||||
|
val url = "$address/comments/$commentId"
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("content", content)
|
||||||
|
.build()
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.put(url, requestBody = body)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to edit comment")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun banUser(userId: String): Boolean {
|
||||||
|
val url = "$address/ban/$userId"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.post(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to ban user")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun reportComment(
|
||||||
|
commentId: Int,
|
||||||
|
username: String,
|
||||||
|
mediaTitle: String,
|
||||||
|
reportedId: String
|
||||||
|
): Boolean {
|
||||||
|
val url = "$address/report/$commentId"
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("username", username)
|
||||||
|
.add("mediaName", mediaTitle)
|
||||||
|
.add("reporter", Anilist.username ?: "unknown")
|
||||||
|
.add("reportedId", reportedId)
|
||||||
|
.build()
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.post(url, requestBody = body)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to report comment")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getNotifications(client: OkHttpClient): NotificationResponse? {
|
||||||
|
val url = "$address/notification/reply"
|
||||||
|
val request = requestBuilder(client)
|
||||||
|
val json = try {
|
||||||
|
request.get(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!json.text.startsWith("{")) return null
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<NotificationResponse>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getUserDetails(client: OkHttpClient? = null): User? {
|
||||||
|
val url = "$address/user"
|
||||||
|
val request = if (client != null) requestBuilder(client) else requestBuilder()
|
||||||
|
val json = try {
|
||||||
|
request.get(url)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (json.code == 200) {
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<UserResponse>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
isBanned = parsed.user.isBanned ?: false
|
||||||
|
isAdmin = parsed.user.isAdmin ?: false
|
||||||
|
isMod = parsed.user.isMod ?: false
|
||||||
|
totalVotes = parsed.user.totalVotes
|
||||||
|
return parsed.user
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchAuthToken(client: OkHttpClient? = null) {
|
||||||
|
if (authToken != null) return
|
||||||
|
val MAX_RETRIES = 5
|
||||||
|
val tokenLifetime: Long = 1000 * 60 * 60 * 24 * 6 // 6 days
|
||||||
|
val tokenExpiry = PrefManager.getVal<Long>(PrefName.CommentTokenExpiry)
|
||||||
|
if (tokenExpiry < System.currentTimeMillis() + tokenLifetime) {
|
||||||
|
val commentResponse =
|
||||||
|
PrefManager.getNullableVal<AuthResponse>(PrefName.CommentAuthResponse, null)
|
||||||
|
if (commentResponse != null) {
|
||||||
|
authToken = commentResponse.authToken
|
||||||
|
userId = commentResponse.user.id
|
||||||
|
isBanned = commentResponse.user.isBanned ?: false
|
||||||
|
isAdmin = commentResponse.user.isAdmin ?: false
|
||||||
|
isMod = commentResponse.user.isMod ?: false
|
||||||
|
totalVotes = commentResponse.user.totalVotes
|
||||||
|
if (getUserDetails(client) != null) return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
val url = "$address/authenticate"
|
||||||
|
val token = PrefManager.getVal(PrefName.AnilistToken, null as String?) ?: return
|
||||||
|
repeat(MAX_RETRIES) {
|
||||||
|
try {
|
||||||
|
val json = authRequest(token, url, client)
|
||||||
|
if (json.code == 200) {
|
||||||
|
if (!json.text.startsWith("{")) throw IOException("Invalid response")
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<AuthResponse>(json.text)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackString("Failed to login to comments API: ${e.printStackTrace()}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
PrefManager.setVal(PrefName.CommentAuthResponse, parsed)
|
||||||
|
PrefManager.setVal(
|
||||||
|
PrefName.CommentTokenExpiry,
|
||||||
|
System.currentTimeMillis() + tokenLifetime
|
||||||
|
)
|
||||||
|
authToken = parsed.authToken
|
||||||
|
userId = parsed.user.id
|
||||||
|
isBanned = parsed.user.isBanned ?: false
|
||||||
|
isAdmin = parsed.user.isAdmin ?: false
|
||||||
|
isMod = parsed.user.isMod ?: false
|
||||||
|
totalVotes = parsed.user.totalVotes
|
||||||
|
return
|
||||||
|
} else if (json.code != 429) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
snackString("Failed to login to comments API")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kotlinx.coroutines.delay(60000)
|
||||||
|
}
|
||||||
|
snackString("Failed to login after multiple attempts")
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun authRequest(
|
||||||
|
token: String,
|
||||||
|
url: String,
|
||||||
|
client: OkHttpClient? = null
|
||||||
|
): NiceResponse {
|
||||||
|
val body: FormBody = FormBody.Builder()
|
||||||
|
.add("token", token)
|
||||||
|
.build()
|
||||||
|
val request = if (client != null) requestBuilder(client) else requestBuilder()
|
||||||
|
return request.post(url, requestBody = body)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun headerBuilder(): Map<String, String> {
|
||||||
|
val map = mutableMapOf(
|
||||||
|
"appauth" to "6*45Qp%W2RS@t38jkXoSKY588Ynj%n"
|
||||||
|
)
|
||||||
|
if (authToken != null) {
|
||||||
|
map["Authorization"] = authToken!!
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests {
|
||||||
|
return Requests(
|
||||||
|
client,
|
||||||
|
headerBuilder()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun errorReason(code: Int, reason: String? = null) {
|
||||||
|
val error = when (code) {
|
||||||
|
429 -> "Rate limited. :("
|
||||||
|
else -> "Failed to connect"
|
||||||
|
}
|
||||||
|
val parsed = try {
|
||||||
|
Json.decodeFromString<ErrorResponse>(reason!!)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val message = parsed?.message ?: reason ?: error
|
||||||
|
val fullMessage = if(code == 500) message else "$code: $message"
|
||||||
|
|
||||||
|
toast(fullMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ErrorResponse(
|
||||||
|
@SerialName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NotificationResponse(
|
||||||
|
@SerialName("notifications")
|
||||||
|
val notifications: List<Notification>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Notification(
|
||||||
|
@SerialName("username")
|
||||||
|
val username: String,
|
||||||
|
@SerialName("media_id")
|
||||||
|
val mediaId: Int,
|
||||||
|
@SerialName("comment_id")
|
||||||
|
val commentId: Int,
|
||||||
|
@SerialName("type")
|
||||||
|
val type: Int? = null,
|
||||||
|
@SerialName("content")
|
||||||
|
val content: String? = null,
|
||||||
|
@SerialName("notification_id")
|
||||||
|
val notificationId: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AuthResponse(
|
||||||
|
@SerialName("authToken")
|
||||||
|
val authToken: String,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: User
|
||||||
|
) : java.io.Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID: Long = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserResponse(
|
||||||
|
@SerialName("user")
|
||||||
|
val user: User
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class User(
|
||||||
|
@SerialName("user_id")
|
||||||
|
val id: String,
|
||||||
|
@SerialName("username")
|
||||||
|
val username: String,
|
||||||
|
@SerialName("profile_picture_url")
|
||||||
|
val profilePictureUrl: String? = null,
|
||||||
|
@SerialName("is_banned")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isBanned: Boolean? = null,
|
||||||
|
@SerialName("is_mod")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isAdmin: Boolean? = null,
|
||||||
|
@SerialName("is_admin")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isMod: Boolean? = null,
|
||||||
|
@SerialName("total_votes")
|
||||||
|
val totalVotes: Int,
|
||||||
|
@SerialName("warnings")
|
||||||
|
val warnings: Int
|
||||||
|
) : java.io.Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID: Long = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CommentResponse(
|
||||||
|
@SerialName("comments")
|
||||||
|
val comments: List<Comment>,
|
||||||
|
@SerialName("totalPages")
|
||||||
|
val totalPages: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Comment(
|
||||||
|
@SerialName("comment_id")
|
||||||
|
val commentId: Int,
|
||||||
|
@SerialName("user_id")
|
||||||
|
val userId: String,
|
||||||
|
@SerialName("media_id")
|
||||||
|
val mediaId: Int,
|
||||||
|
@SerialName("parent_comment_id")
|
||||||
|
val parentCommentId: Int?,
|
||||||
|
@SerialName("content")
|
||||||
|
var content: String,
|
||||||
|
@SerialName("timestamp")
|
||||||
|
var timestamp: String,
|
||||||
|
@SerialName("deleted")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val deleted: Boolean?,
|
||||||
|
@SerialName("tag")
|
||||||
|
val tag: Int?,
|
||||||
|
@SerialName("upvotes")
|
||||||
|
var upvotes: Int,
|
||||||
|
@SerialName("downvotes")
|
||||||
|
var downvotes: Int,
|
||||||
|
@SerialName("user_vote_type")
|
||||||
|
var userVoteType: Int?,
|
||||||
|
@SerialName("username")
|
||||||
|
val username: String,
|
||||||
|
@SerialName("profile_picture_url")
|
||||||
|
val profilePictureUrl: String?,
|
||||||
|
@SerialName("is_mod")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isMod: Boolean? = null,
|
||||||
|
@SerialName("is_admin")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isAdmin: Boolean? = null,
|
||||||
|
@SerialName("reply_count")
|
||||||
|
val replyCount: Int? = null,
|
||||||
|
@SerialName("total_votes")
|
||||||
|
val totalVotes: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReturnedComment(
|
||||||
|
@SerialName("id")
|
||||||
|
var id: Int,
|
||||||
|
@SerialName("comment_id")
|
||||||
|
var commentId: Int?,
|
||||||
|
@SerialName("user_id")
|
||||||
|
val userId: String,
|
||||||
|
@SerialName("media_id")
|
||||||
|
val mediaId: Int,
|
||||||
|
@SerialName("parent_comment_id")
|
||||||
|
val parentCommentId: Int? = null,
|
||||||
|
@SerialName("content")
|
||||||
|
val content: String,
|
||||||
|
@SerialName("timestamp")
|
||||||
|
val timestamp: String,
|
||||||
|
@SerialName("deleted")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val deleted: Boolean?,
|
||||||
|
@SerialName("tag")
|
||||||
|
val tag: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
object NumericBooleanSerializer : KSerializer<Boolean> {
|
||||||
|
override val descriptor: SerialDescriptor =
|
||||||
|
PrimitiveSerialDescriptor("NumericBoolean", PrimitiveKind.INT)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Boolean) {
|
||||||
|
encoder.encodeInt(if (value) 1 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Boolean {
|
||||||
|
return decoder.decodeInt() != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ani.dantotsu.connections.crashlytics
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
interface CrashlyticsInterface {
|
||||||
|
fun initialize(context: Context)
|
||||||
|
fun logException(e: Throwable)
|
||||||
|
fun log(message: String)
|
||||||
|
fun setUserId(id: String)
|
||||||
|
fun setCustomKey(key: String, value: String)
|
||||||
|
fun setCrashlyticsCollectionEnabled(enabled: Boolean)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package ani.dantotsu.connections.crashlytics
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
|
||||||
|
class CrashlyticsStub : CrashlyticsInterface {
|
||||||
|
override fun initialize(context: Context) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
override fun logException(e: Throwable) {
|
||||||
|
Logger.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun log(message: String) {
|
||||||
|
Logger.log(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserId(id: String) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCustomKey(key: String, value: String) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCrashlyticsCollectionEnabled(enabled: Boolean) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@ package ani.dantotsu.connections.discord
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.edit
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.others.CustomBottomDialog
|
import ani.dantotsu.others.CustomBottomDialog
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
@@ -18,37 +19,20 @@ object Discord {
|
|||||||
var userid: String? = null
|
var userid: String? = null
|
||||||
var avatar: String? = null
|
var avatar: String? = null
|
||||||
|
|
||||||
const val TOKEN = "discord_token"
|
|
||||||
|
|
||||||
fun getSavedToken(context: Context): Boolean {
|
fun getSavedToken(context: Context): Boolean {
|
||||||
val sharedPref = context.getSharedPreferences(
|
token = PrefManager.getVal(
|
||||||
context.getString(R.string.preference_file_key),
|
PrefName.DiscordToken, null as String?
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
)
|
||||||
token = sharedPref.getString(TOKEN, null)
|
|
||||||
return token != null
|
return token != null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToken(context: Context, token: String) {
|
fun saveToken(context: Context, token: String) {
|
||||||
val sharedPref = context.getSharedPreferences(
|
PrefManager.setVal(PrefName.DiscordToken, token)
|
||||||
context.getString(R.string.preference_file_key),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
sharedPref.edit {
|
|
||||||
putString(TOKEN, token)
|
|
||||||
commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeSavedToken(context: Context) {
|
fun removeSavedToken(context: Context) {
|
||||||
val sharedPref = context.getSharedPreferences(
|
PrefManager.removeVal(PrefName.DiscordToken)
|
||||||
context.getString(R.string.preference_file_key),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
sharedPref.edit {
|
|
||||||
remove(TOKEN)
|
|
||||||
commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
tryWith(true) {
|
tryWith(true) {
|
||||||
val dir = File(context.filesDir?.parentFile, "app_webview")
|
val dir = File(context.filesDir?.parentFile, "app_webview")
|
||||||
@@ -86,19 +70,5 @@ object Discord {
|
|||||||
|
|
||||||
const val application_Id = "1163925779692912771"
|
const val application_Id = "1163925779692912771"
|
||||||
const val small_Image: String =
|
const val small_Image: String =
|
||||||
"mp:attachments/1167176318266380288/1176997397797277856/logo-best_of_both.png"
|
"mp:external/GJEe4hKzr8w56IW6ZKQz43HFVEo8pOtA_C-dJiWwxKo/https/cdn.discordapp.com/app-icons/1163925779692912771/f6b42d41dfdf0b56fcc79d4a12d2ac66.png"
|
||||||
/*fun defaultRPC(): RPC? {
|
|
||||||
return token?.let {
|
|
||||||
RPC(it, Dispatchers.IO).apply {
|
|
||||||
applicationId = application_Id
|
|
||||||
smallImage = RPC.Link(
|
|
||||||
"Dantotsu",
|
|
||||||
small_Image
|
|
||||||
)
|
|
||||||
buttons.add(RPC.Link("Stream on Dantotsu", "https://github.com/rebelonion/Dantotsu/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,6 @@ import android.os.Environment
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
@@ -24,6 +23,9 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.connections.discord.serializers.Presence
|
import ani.dantotsu.connections.discord.serializers.Presence
|
||||||
import ani.dantotsu.connections.discord.serializers.User
|
import ani.dantotsu.connections.discord.serializers.User
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
@@ -149,19 +151,11 @@ class DiscordService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveProfile(response: String) {
|
fun saveProfile(response: String) {
|
||||||
val sharedPref = baseContext.getSharedPreferences(
|
|
||||||
baseContext.getString(R.string.preference_file_key),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
val user = json.decodeFromString<User.Response>(response).d.user
|
val user = json.decodeFromString<User.Response>(response).d.user
|
||||||
log("User data: $user")
|
log("User data: $user")
|
||||||
with(sharedPref.edit()) {
|
PrefManager.setVal(PrefName.DiscordUserName, user.username)
|
||||||
putString("discord_username", user.username)
|
PrefManager.setVal(PrefName.DiscordId, user.id)
|
||||||
putString("discord_id", user.id)
|
PrefManager.setVal(PrefName.DiscordAvatar, user.avatar)
|
||||||
putString("discord_avatar", user.avatar)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(p0: Intent?): IBinder? = null
|
override fun onBind(p0: Intent?): IBinder? = null
|
||||||
@@ -280,7 +274,7 @@ class DiscordService : Service() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.message?.let { Log.d("WebSocket", "onFailure() $it") }
|
t.message?.let { Logger.log("onFailure() $it") }
|
||||||
log("WebSocket: Error, onFailure() reason: ${t.message}")
|
log("WebSocket: Error, onFailure() reason: ${t.message}")
|
||||||
client = OkHttpClient()
|
client = OkHttpClient()
|
||||||
client.newWebSocket(
|
client.newWebSocket(
|
||||||
@@ -295,7 +289,7 @@ class DiscordService : Service() {
|
|||||||
|
|
||||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
super.onClosing(webSocket, code, reason)
|
super.onClosing(webSocket, code, reason)
|
||||||
Log.d("WebSocket", "onClosing() $code $reason")
|
Logger.log("onClosing() $code $reason")
|
||||||
if (::heartbeatThread.isInitialized && !heartbeatThread.isInterrupted) {
|
if (::heartbeatThread.isInitialized && !heartbeatThread.isInterrupted) {
|
||||||
heartbeatThread.interrupt()
|
heartbeatThread.interrupt()
|
||||||
}
|
}
|
||||||
@@ -303,7 +297,7 @@ class DiscordService : Service() {
|
|||||||
|
|
||||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
super.onClosed(webSocket, code, reason)
|
super.onClosed(webSocket, code, reason)
|
||||||
Log.d("WebSocket", "onClosed() $code $reason")
|
Logger.log("onClosed() $code $reason")
|
||||||
if (code >= 4000) {
|
if (code >= 4000) {
|
||||||
log("WebSocket: Error, code: $code reason: $reason")
|
log("WebSocket: Error, code: $code reason: $reason")
|
||||||
client = OkHttpClient()
|
client = OkHttpClient()
|
||||||
@@ -318,17 +312,13 @@ class DiscordService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getToken(context: Context): String {
|
fun getToken(context: Context): String {
|
||||||
val sharedPref = context.getSharedPreferences(
|
val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
|
||||||
context.getString(R.string.preference_file_key),
|
return if (token == null) {
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
val token = sharedPref.getString(Discord.TOKEN, null)
|
|
||||||
if (token == null) {
|
|
||||||
log("WebSocket: Token not found")
|
log("WebSocket: Token not found")
|
||||||
errorNotification("Could not set the presence", "token not found")
|
errorNotification("Could not set the presence", "token not found")
|
||||||
return ""
|
""
|
||||||
} else {
|
} else {
|
||||||
return token
|
token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,52 +382,7 @@ class DiscordService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun log(string: String) {
|
fun log(string: String) {
|
||||||
Log.d("WebSocket_Discord", string)
|
//Logger.log(string)
|
||||||
//log += "${SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().time)} $string\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveLogToFile() {
|
|
||||||
val fileName = "log_${System.currentTimeMillis()}.txt"
|
|
||||||
|
|
||||||
// ContentValues to store file metadata
|
|
||||||
val values = ContentValues().apply {
|
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inserting the file in the MediaStore
|
|
||||||
val resolver = baseContext.contentResolver
|
|
||||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
|
||||||
} else {
|
|
||||||
val directory =
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
val file = File(directory, fileName)
|
|
||||||
|
|
||||||
// Make sure the Downloads directory exists
|
|
||||||
if (!directory.exists()) {
|
|
||||||
directory.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use FileProvider to get the URI for the file
|
|
||||||
val authority =
|
|
||||||
"${baseContext.packageName}.provider" // Adjust with your app's package name
|
|
||||||
Uri.fromFile(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writing to the file
|
|
||||||
uri?.let {
|
|
||||||
resolver.openOutputStream(it).use { outputStream ->
|
|
||||||
OutputStreamWriter(outputStream).use { writer ->
|
|
||||||
writer.write(log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: run {
|
|
||||||
log("Error saving log file")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.discord.Discord.saveToken
|
import ani.dantotsu.connections.discord.Discord.saveToken
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ class Login : AppCompatActivity() {
|
|||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
val process = getProcessName()
|
val process = getProcessName()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package ani.dantotsu.connections.discord
|
|||||||
|
|
||||||
import ani.dantotsu.connections.discord.serializers.Activity
|
import ani.dantotsu.connections.discord.serializers.Activity
|
||||||
import ani.dantotsu.connections.discord.serializers.Presence
|
import ani.dantotsu.connections.discord.serializers.Presence
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -81,7 +83,7 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
|||||||
),
|
),
|
||||||
afk = true,
|
afk = true,
|
||||||
since = data.startTimestamp,
|
since = data.startTimestamp,
|
||||||
status = data.status
|
status = PrefManager.getVal(PrefName.DiscordStatus)
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.client
|
import ani.dantotsu.client
|
||||||
import ani.dantotsu.connections.mal.MAL.clientId
|
import ani.dantotsu.connections.mal.MAL.clientId
|
||||||
import ani.dantotsu.connections.mal.MAL.saveResponse
|
import ani.dantotsu.connections.mal.MAL.saveResponse
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
@@ -21,12 +21,12 @@ import kotlinx.coroutines.launch
|
|||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
try {
|
try {
|
||||||
val data: Uri = intent?.data
|
val data: Uri = intent?.data
|
||||||
?: throw Exception(getString(R.string.mal_login_uri_not_found))
|
?: throw Exception(getString(R.string.mal_login_uri_not_found))
|
||||||
val codeChallenge: String = loadData("malCodeChallenge", this)
|
val codeChallenge = PrefManager.getVal(PrefName.MALCodeChallenge, null as String?)
|
||||||
?: throw Exception(getString(R.string.mal_login_code_challenge_not_found))
|
?: throw Exception(getString(R.string.mal_login_code_challenge_not_found))
|
||||||
val code = data.getQueryParameter("code")
|
val code = data.getQueryParameter("code")
|
||||||
?: throw Exception(getString(R.string.mal_login_code_not_present))
|
?: throw Exception(getString(R.string.mal_login_code_not_present))
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.client
|
import ani.dantotsu.client
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.io.File
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
object MAL {
|
object MAL {
|
||||||
@@ -34,7 +33,7 @@ object MAL {
|
|||||||
.replace("/", "_")
|
.replace("/", "_")
|
||||||
.replace("\n", "")
|
.replace("\n", "")
|
||||||
|
|
||||||
saveData("malCodeChallenge", codeChallenge, context)
|
PrefManager.setVal(PrefName.MALCodeChallenge, codeChallenge)
|
||||||
val request =
|
val request =
|
||||||
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$clientId&code_challenge=$codeChallenge"
|
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$clientId&code_challenge=$codeChallenge"
|
||||||
try {
|
try {
|
||||||
@@ -47,11 +46,9 @@ object MAL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val MAL_TOKEN = "malToken"
|
|
||||||
|
|
||||||
private suspend fun refreshToken(): ResponseToken? {
|
private suspend fun refreshToken(): ResponseToken? {
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
val token = loadData<ResponseToken>(MAL_TOKEN)
|
val token = PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
|
||||||
?: throw Exception(currContext()?.getString(R.string.refresh_token_load_failed))
|
?: throw Exception(currContext()?.getString(R.string.refresh_token_load_failed))
|
||||||
val res = client.post(
|
val res = client.post(
|
||||||
"https://myanimelist.net/v1/oauth2/token",
|
"https://myanimelist.net/v1/oauth2/token",
|
||||||
@@ -69,8 +66,9 @@ object MAL {
|
|||||||
|
|
||||||
suspend fun getSavedToken(context: FragmentActivity): Boolean {
|
suspend fun getSavedToken(context: FragmentActivity): Boolean {
|
||||||
return tryWithSuspend(false) {
|
return tryWithSuspend(false) {
|
||||||
var res: ResponseToken = loadData(MAL_TOKEN, context)
|
var res: ResponseToken =
|
||||||
?: return@tryWithSuspend false
|
PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
|
||||||
|
?: return@tryWithSuspend false
|
||||||
if (System.currentTimeMillis() > res.expiresIn)
|
if (System.currentTimeMillis() > res.expiresIn)
|
||||||
res = refreshToken()
|
res = refreshToken()
|
||||||
?: throw Exception(currContext()?.getString(R.string.refreshing_token_failed))
|
?: throw Exception(currContext()?.getString(R.string.refreshing_token_failed))
|
||||||
@@ -84,14 +82,12 @@ object MAL {
|
|||||||
username = null
|
username = null
|
||||||
userid = null
|
userid = null
|
||||||
avatar = null
|
avatar = null
|
||||||
if (MAL_TOKEN in context.fileList()) {
|
PrefManager.removeVal(PrefName.MALToken)
|
||||||
File(context.filesDir, MAL_TOKEN).delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveResponse(res: ResponseToken) {
|
fun saveResponse(res: ResponseToken) {
|
||||||
res.expiresIn += System.currentTimeMillis()
|
res.expiresIn += System.currentTimeMillis()
|
||||||
saveData(MAL_TOKEN, res)
|
PrefManager.setVal(PrefName.MALToken, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -100,6 +96,10 @@ object MAL {
|
|||||||
@SerialName("expires_in") var expiresIn: Long,
|
@SerialName("expires_in") var expiresIn: Long,
|
||||||
@SerialName("access_token") val accessToken: String,
|
@SerialName("access_token") val accessToken: String,
|
||||||
@SerialName("refresh_token") val refreshToken: String,
|
@SerialName("refresh_token") val refreshToken: String,
|
||||||
) : java.io.Serializable
|
) : java.io.Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package ani.dantotsu.download
|
package ani.dantotsu.download
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class DownloadsManager(private val context: Context) {
|
class DownloadsManager(private val context: Context) {
|
||||||
private val prefs: SharedPreferences =
|
|
||||||
context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE)
|
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private val downloadsList = loadDownloads().toMutableList()
|
private val downloadsList = loadDownloads().toMutableList()
|
||||||
|
|
||||||
@@ -24,11 +23,11 @@ class DownloadsManager(private val context: Context) {
|
|||||||
|
|
||||||
private fun saveDownloads() {
|
private fun saveDownloads() {
|
||||||
val jsonString = gson.toJson(downloadsList)
|
val jsonString = gson.toJson(downloadsList)
|
||||||
prefs.edit().putString("downloads_key", jsonString).apply()
|
PrefManager.setVal(PrefName.DownloadsKeys, jsonString)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDownloads(): List<DownloadedType> {
|
private fun loadDownloads(): List<DownloadedType> {
|
||||||
val jsonString = prefs.getString("downloads_key", null)
|
val jsonString = PrefManager.getVal(PrefName.DownloadsKeys, null as String?)
|
||||||
return if (jsonString != null) {
|
return if (jsonString != null) {
|
||||||
val type = object : TypeToken<List<DownloadedType>>() {}.type
|
val type = object : TypeToken<List<DownloadedType>>() {}.type
|
||||||
gson.fromJson(jsonString, type)
|
gson.fromJson(jsonString, type)
|
||||||
@@ -75,9 +74,11 @@ class DownloadsManager(private val context: Context) {
|
|||||||
DownloadedType.Type.MANGA -> {
|
DownloadedType.Type.MANGA -> {
|
||||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA }
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA }
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadedType.Type.ANIME -> {
|
DownloadedType.Type.ANIME -> {
|
||||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME }
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME }
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadedType.Type.NOVEL -> {
|
DownloadedType.Type.NOVEL -> {
|
||||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL }
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL }
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,12 @@ class DownloadsManager(private val context: Context) {
|
|||||||
const val mangaLocation = "Dantotsu/Manga"
|
const val mangaLocation = "Dantotsu/Manga"
|
||||||
const val animeLocation = "Dantotsu/Anime"
|
const val animeLocation = "Dantotsu/Anime"
|
||||||
|
|
||||||
fun getDirectory(context: Context, type: DownloadedType.Type, title: String, chapter: String? = null): File {
|
fun getDirectory(
|
||||||
|
context: Context,
|
||||||
|
type: DownloadedType.Type,
|
||||||
|
title: String,
|
||||||
|
chapter: String? = null
|
||||||
|
): File {
|
||||||
return if (type == DownloadedType.Type.MANGA) {
|
return if (type == DownloadedType.Type.MANGA) {
|
||||||
if (chapter != null) {
|
if (chapter != null) {
|
||||||
File(
|
File(
|
||||||
|
|||||||
@@ -21,19 +21,20 @@ import androidx.media3.exoplayer.offline.DownloadManager
|
|||||||
import androidx.media3.exoplayer.offline.DownloadService
|
import androidx.media3.exoplayer.offline.DownloadService
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.download.video.ExoplayerDownloadService
|
import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||||
import ani.dantotsu.download.video.Helper
|
import ani.dantotsu.download.video.Helper
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.SubtitleDownloader
|
import ani.dantotsu.media.SubtitleDownloader
|
||||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
@@ -53,6 +54,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -84,7 +86,7 @@ class AnimeDownloaderService : Service() {
|
|||||||
builder =
|
builder =
|
||||||
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
|
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
|
||||||
setContentTitle("Anime Download Progress")
|
setContentTitle("Anime Download Progress")
|
||||||
setSmallIcon(R.drawable.ic_round_download_24)
|
setSmallIcon(R.drawable.ic_download_24)
|
||||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
}
|
}
|
||||||
@@ -224,8 +226,6 @@ class AnimeDownloaderService : Service() {
|
|||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastDownloadStarted(task.episode)
|
|
||||||
|
|
||||||
currActivity()?.let {
|
currActivity()?.let {
|
||||||
Helper.downloadVideo(
|
Helper.downloadVideo(
|
||||||
it,
|
it,
|
||||||
@@ -250,7 +250,7 @@ class AnimeDownloaderService : Service() {
|
|||||||
hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
|
hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
|
||||||
|
|
||||||
if (!downloadStarted) {
|
if (!downloadStarted) {
|
||||||
logger("Download failed to start")
|
Logger.log("Download failed to start")
|
||||||
builder.setContentText("${task.title} - ${task.episode} Download failed to start")
|
builder.setContentText("${task.title} - ${task.episode} Download failed to start")
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
snackString("${task.title} - ${task.episode} Download failed to start")
|
snackString("${task.title} - ${task.episode} Download failed to start")
|
||||||
@@ -264,11 +264,11 @@ class AnimeDownloaderService : Service() {
|
|||||||
val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
|
val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
|
||||||
if (download != null) {
|
if (download != null) {
|
||||||
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) {
|
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) {
|
||||||
logger("Download failed")
|
Logger.log("Download failed")
|
||||||
builder.setContentText("${task.title} - ${task.episode} Download failed")
|
builder.setContentText("${task.title} - ${task.episode} Download failed")
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
snackString("${task.title} - ${task.episode} Download failed")
|
snackString("${task.title} - ${task.episode} Download failed")
|
||||||
logger("Download failed: ${download.failureReason}")
|
Logger.log("Download failed: ${download.failureReason}")
|
||||||
downloadsManager.removeDownload(
|
downloadsManager.removeDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
task.title,
|
task.title,
|
||||||
@@ -276,7 +276,7 @@ class AnimeDownloaderService : Service() {
|
|||||||
DownloadedType.Type.ANIME,
|
DownloadedType.Type.ANIME,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FirebaseCrashlytics.getInstance().recordException(
|
Injekt.get<CrashlyticsInterface>().logException(
|
||||||
Exception(
|
Exception(
|
||||||
"Anime Download failed:" +
|
"Anime Download failed:" +
|
||||||
" ${download.failureReason}" +
|
" ${download.failureReason}" +
|
||||||
@@ -290,14 +290,11 @@ class AnimeDownloaderService : Service() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) {
|
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) {
|
||||||
logger("Download completed")
|
Logger.log("Download completed")
|
||||||
builder.setContentText("${task.title} - ${task.episode} Download completed")
|
builder.setContentText("${task.title} - ${task.episode} Download completed")
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
snackString("${task.title} - ${task.episode} Download completed")
|
snackString("${task.title} - ${task.episode} Download completed")
|
||||||
getSharedPreferences(
|
PrefManager.getAnimeDownloadPreferences().edit().putString(
|
||||||
getString(R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).edit().putString(
|
|
||||||
task.getTaskName(),
|
task.getTaskName(),
|
||||||
task.video.file.url
|
task.video.file.url
|
||||||
).apply()
|
).apply()
|
||||||
@@ -313,7 +310,7 @@ class AnimeDownloaderService : Service() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) {
|
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) {
|
||||||
logger("Download stopped")
|
Logger.log("Download stopped")
|
||||||
builder.setContentText("${task.title} - ${task.episode} Download stopped")
|
builder.setContentText("${task.title} - ${task.episode} Download stopped")
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
snackString("${task.title} - ${task.episode} Download stopped")
|
snackString("${task.title} - ${task.episode} Download stopped")
|
||||||
@@ -332,10 +329,10 @@ class AnimeDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e.message?.contains("Coroutine was cancelled") == false) { //wut
|
if (e.message?.contains("Coroutine was cancelled") == false) { //wut
|
||||||
logger("Exception while downloading file: ${e.message}")
|
Logger.log("Exception while downloading file: ${e.message}")
|
||||||
snackString("Exception while downloading file: ${e.message}")
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
}
|
}
|
||||||
broadcastDownloadFailed(task.episode)
|
broadcastDownloadFailed(task.episode)
|
||||||
}
|
}
|
||||||
@@ -359,15 +356,13 @@ class AnimeDownloaderService : Service() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
private fun saveMediaInfo(task: AnimeDownloadTask) {
|
private fun saveMediaInfo(task: AnimeDownloadTask) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
launchIO {
|
||||||
val directory = File(
|
val directory = File(
|
||||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"${DownloadsManager.animeLocation}/${task.title}"
|
"${DownloadsManager.animeLocation}/${task.title}"
|
||||||
)
|
)
|
||||||
val episodeDirectory = File(directory, task.episode)
|
val episodeDirectory = File(directory, task.episode)
|
||||||
if (!directory.exists()) directory.mkdirs()
|
|
||||||
if (!episodeDirectory.exists()) episodeDirectory.mkdirs()
|
if (!episodeDirectory.exists()) episodeDirectory.mkdirs()
|
||||||
|
|
||||||
val file = File(directory, "media.json")
|
val file = File(directory, "media.json")
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
|
||||||
|
|
||||||
class OfflineAnimeAdapter(
|
class OfflineAnimeAdapter(
|
||||||
@@ -22,8 +24,7 @@ class OfflineAnimeAdapter(
|
|||||||
private val inflater: LayoutInflater =
|
private val inflater: LayoutInflater =
|
||||||
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
private var originalItems: List<OfflineAnimeModel> = items
|
private var originalItems: List<OfflineAnimeModel> = items
|
||||||
private var style =
|
private var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
||||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return items.size
|
return items.size
|
||||||
@@ -105,8 +106,7 @@ class OfflineAnimeAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun notifyNewGrid() {
|
fun notifyNewGrid() {
|
||||||
style =
|
style = PrefManager.getVal(PrefName.OfflineView)
|
||||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
package ani.dantotsu.download.anime
|
package ani.dantotsu.download.anime
|
||||||
|
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
@@ -16,7 +13,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AlphaAnimation
|
import android.view.animation.AlphaAnimation
|
||||||
import android.view.animation.LayoutAnimationController
|
import android.view.animation.LayoutAnimationController
|
||||||
import android.view.animation.OvershootInterpolator
|
|
||||||
import android.widget.AbsListView
|
import android.widget.AbsListView
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.GridView
|
import android.widget.GridView
|
||||||
@@ -25,34 +21,30 @@ import android.widget.TextView
|
|||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.util.Pair
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.logger
|
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
@@ -64,8 +56,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
||||||
|
|
||||||
@@ -73,9 +63,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
private var downloads: List<OfflineAnimeModel> = listOf()
|
private var downloads: List<OfflineAnimeModel> = listOf()
|
||||||
private lateinit var gridView: GridView
|
private lateinit var gridView: GridView
|
||||||
private lateinit var adapter: OfflineAnimeAdapter
|
private lateinit var adapter: OfflineAnimeAdapter
|
||||||
private lateinit var total : TextView
|
private lateinit var total: TextView
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -101,15 +89,12 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineANIME)
|
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineANIME)
|
||||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
if (!uiSettings.immersiveMode) {
|
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
|
||||||
view.rootView.fitsSystemWindows = true
|
view.rootView.fitsSystemWindows = true
|
||||||
}
|
}
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
?.getBoolean("colorOverflow", false) ?: false
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
|
||||||
if (!colorOverflow) {
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
||||||
searchView.addTextChangedListener(object : TextWatcher {
|
searchView.addTextChangedListener(object : TextWatcher {
|
||||||
@@ -123,8 +108,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
onSearchQuery(s.toString())
|
onSearchQuery(s.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
||||||
?.getInt("offline_view", 0)
|
|
||||||
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
||||||
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
||||||
var selected = when (style) {
|
var selected = when (style) {
|
||||||
@@ -143,8 +127,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
layoutList.setOnClickListener {
|
layoutList.setOnClickListener {
|
||||||
selected(it as ImageView)
|
selected(it as ImageView)
|
||||||
style = 0
|
style = 0
|
||||||
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
PrefManager.setVal(PrefName.OfflineView, style)
|
||||||
?.putInt("offline_view", style!!)?.apply()
|
|
||||||
gridView.visibility = View.GONE
|
gridView.visibility = View.GONE
|
||||||
gridView = view.findViewById(R.id.gridView)
|
gridView = view.findViewById(R.id.gridView)
|
||||||
adapter.notifyNewGrid()
|
adapter.notifyNewGrid()
|
||||||
@@ -154,15 +137,15 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
layoutcompact.setOnClickListener {
|
layoutcompact.setOnClickListener {
|
||||||
selected(it as ImageView)
|
selected(it as ImageView)
|
||||||
style = 1
|
style = 1
|
||||||
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
PrefManager.setVal(PrefName.OfflineView, style)
|
||||||
?.putInt("offline_view", style!!)?.apply()
|
|
||||||
gridView.visibility = View.GONE
|
gridView.visibility = View.GONE
|
||||||
gridView = view.findViewById(R.id.gridView1)
|
gridView = view.findViewById(R.id.gridView1)
|
||||||
adapter.notifyNewGrid()
|
adapter.notifyNewGrid()
|
||||||
grid()
|
grid()
|
||||||
}
|
}
|
||||||
|
|
||||||
gridView = if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
|
gridView =
|
||||||
|
if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
|
||||||
total = view.findViewById(R.id.total)
|
total = view.findViewById(R.id.total)
|
||||||
grid()
|
grid()
|
||||||
return view
|
return view
|
||||||
@@ -195,13 +178,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
requireActivity(),
|
requireActivity(),
|
||||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
.putExtra("download", true),
|
.putExtra("download", true),
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
null
|
||||||
requireActivity(),
|
|
||||||
Pair.create(
|
|
||||||
requireActivity().findViewById<ImageView>(R.id.itemCompactImage),
|
|
||||||
ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage))
|
|
||||||
),
|
|
||||||
).toBundle()
|
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
snackString("no media found")
|
snackString("no media found")
|
||||||
@@ -220,11 +197,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
||||||
builder.setPositiveButton("Yes") { _, _ ->
|
builder.setPositiveButton("Yes") { _, _ ->
|
||||||
downloadManager.removeMedia(item.title, type)
|
downloadManager.removeMedia(item.title, type)
|
||||||
val mediaIds = requireContext().getSharedPreferences(
|
val mediaIds =
|
||||||
getString(R.string.anime_downloads),
|
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
|
||||||
Context.MODE_PRIVATE
|
?: emptySet()
|
||||||
)
|
|
||||||
?.all?.filter { it.key.contains(item.title) }?.values ?: emptySet()
|
|
||||||
if (mediaIds.isEmpty()) {
|
if (mediaIds.isEmpty()) {
|
||||||
snackString("No media found") // if this happens, terrible things have happened
|
snackString("No media found") // if this happens, terrible things have happened
|
||||||
}
|
}
|
||||||
@@ -252,41 +227,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
var height = statusBarHeight
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
|
|
||||||
if (displayCutout != null) {
|
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
|
||||||
height = max(
|
|
||||||
statusBarHeight,
|
|
||||||
min(
|
|
||||||
displayCutout.boundingRects[0].width(),
|
|
||||||
displayCutout.boundingRects[0].height()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||||
scrollTop.translationY =
|
|
||||||
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
|
||||||
val visible = false
|
|
||||||
|
|
||||||
fun animate() {
|
|
||||||
val start = if (visible) 0f else 1f
|
|
||||||
val end = if (!visible) 0f else 1f
|
|
||||||
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
|
|
||||||
duration = 300
|
|
||||||
interpolator = OvershootInterpolator(2f)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
|
|
||||||
duration = 300
|
|
||||||
interpolator = OvershootInterpolator(2f)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollTop.setOnClickListener {
|
scrollTop.setOnClickListener {
|
||||||
gridView.smoothScrollToPositionFromTop(0, 0)
|
gridView.smoothScrollToPositionFromTop(0, 0)
|
||||||
}
|
}
|
||||||
@@ -306,7 +247,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
totalItemCount: Int
|
totalItemCount: Int
|
||||||
) {
|
) {
|
||||||
val first = view.getChildAt(0)
|
val first = view.getChildAt(0)
|
||||||
val visibility = first != null && first.top < -height
|
val visibility = first != null && first.top < 0
|
||||||
|
scrollTop.translationY =
|
||||||
|
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||||
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -340,8 +283,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
||||||
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
||||||
for (title in animeTitles) {
|
for (title in animeTitles) {
|
||||||
val _downloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
|
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
|
||||||
val download = _downloads.first()
|
val download = tDownloads.first()
|
||||||
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
||||||
newAnimeDownloads += offlineAnimeModel
|
newAnimeDownloads += offlineAnimeModel
|
||||||
}
|
}
|
||||||
@@ -349,12 +292,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||||
val type = if (downloadedType.type == DownloadedType.Type.ANIME) {
|
val type = when (downloadedType.type) {
|
||||||
"Anime"
|
DownloadedType.Type.MANGA -> "Manga"
|
||||||
} else if (downloadedType.type == DownloadedType.Type.MANGA) {
|
DownloadedType.Type.ANIME -> "Anime"
|
||||||
"Manga"
|
else -> "Novel"
|
||||||
} else {
|
|
||||||
"Novel"
|
|
||||||
}
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
@@ -377,20 +318,18 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
val mediaJson = media.readText()
|
val mediaJson = media.readText()
|
||||||
gson.fromJson(mediaJson, Media::class.java)
|
gson.fromJson(mediaJson, Media::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
Logger.log(e)
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
||||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
val type = when (downloadedType.type) {
|
||||||
"Manga"
|
DownloadedType.Type.MANGA -> "Manga"
|
||||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
DownloadedType.Type.ANIME -> "Anime"
|
||||||
"Anime"
|
else -> "Novel"
|
||||||
} else {
|
|
||||||
"Novel"
|
|
||||||
}
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
@@ -398,8 +337,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
)
|
)
|
||||||
//load media.json and convert to media class with gson
|
//load media.json and convert to media class with gson
|
||||||
try {
|
try {
|
||||||
val media = File(directory, "media.json")
|
|
||||||
val mediaJson = media.readText()
|
|
||||||
val mediaModel = getMedia(downloadedType)!!
|
val mediaModel = getMedia(downloadedType)!!
|
||||||
val cover = File(directory, "cover.jpg")
|
val cover = File(directory, "cover.jpg")
|
||||||
val coverUri: Uri? = if (cover.exists()) {
|
val coverUri: Uri? = if (cover.exists()) {
|
||||||
@@ -437,9 +374,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
bannerUri
|
bannerUri
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
Logger.log(e)
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
return OfflineAnimeModel(
|
return OfflineAnimeModel(
|
||||||
"unknown",
|
"unknown",
|
||||||
"0",
|
"0",
|
||||||
@@ -448,8 +385,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
"??",
|
"??",
|
||||||
"movie",
|
"movie",
|
||||||
"hmm",
|
"hmm",
|
||||||
false,
|
isOngoing = false,
|
||||||
false,
|
isUserScored = false,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.manga.ImageData
|
import ani.dantotsu.media.manga.ImageData
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
||||||
@@ -29,7 +30,6 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROG
|
|||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
@@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -47,6 +48,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -76,7 +78,7 @@ class MangaDownloaderService : Service() {
|
|||||||
notificationManager = NotificationManagerCompat.from(this)
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
builder = NotificationCompat.Builder(this, CHANNEL_DOWNLOADER_PROGRESS).apply {
|
builder = NotificationCompat.Builder(this, CHANNEL_DOWNLOADER_PROGRESS).apply {
|
||||||
setContentTitle("Manga Download Progress")
|
setContentTitle("Manga Download Progress")
|
||||||
setSmallIcon(R.drawable.ic_round_download_24)
|
setSmallIcon(R.drawable.ic_download_24)
|
||||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
@@ -251,9 +253,9 @@ class MangaDownloaderService : Service() {
|
|||||||
snackString("${task.title} - ${task.chapter} Download finished")
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Exception while downloading file: ${e.message}")
|
Logger.log("Exception while downloading file: ${e.message}")
|
||||||
snackString("Exception while downloading file: ${e.message}")
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
broadcastDownloadFailed(task.chapter)
|
broadcastDownloadFailed(task.chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,12 +285,13 @@ class MangaDownloaderService : Service() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Exception while saving image: ${e.message}")
|
println("Exception while saving image: ${e.message}")
|
||||||
snackString("Exception while saving image: ${e.message}")
|
snackString("Exception while saving image: ${e.message}")
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun saveMediaInfo(task: DownloadTask) {
|
private fun saveMediaInfo(task: DownloadTask) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
launchIO {
|
||||||
val directory = File(
|
val directory = File(
|
||||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/Manga/${task.title}"
|
"Dantotsu/Manga/${task.title}"
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
|
||||||
|
|
||||||
class OfflineMangaAdapter(
|
class OfflineMangaAdapter(
|
||||||
@@ -21,8 +23,7 @@ class OfflineMangaAdapter(
|
|||||||
private val inflater: LayoutInflater =
|
private val inflater: LayoutInflater =
|
||||||
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
private var originalItems: List<OfflineMangaModel> = items
|
private var originalItems: List<OfflineMangaModel> = items
|
||||||
private var style =
|
private var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
||||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return items.size
|
return items.size
|
||||||
@@ -104,8 +105,7 @@ class OfflineMangaAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun notifyNewGrid() {
|
fun notifyNewGrid() {
|
||||||
style =
|
style = PrefManager.getVal(PrefName.OfflineView)
|
||||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package ani.dantotsu.download.manga
|
package ani.dantotsu.download.manga
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
@@ -15,7 +12,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AlphaAnimation
|
import android.view.animation.AlphaAnimation
|
||||||
import android.view.animation.LayoutAnimationController
|
import android.view.animation.LayoutAnimationController
|
||||||
import android.view.animation.OvershootInterpolator
|
|
||||||
import android.widget.AbsListView
|
import android.widget.AbsListView
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.GridView
|
import android.widget.GridView
|
||||||
@@ -23,33 +19,29 @@ import android.widget.ImageView
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.util.Pair
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.logger
|
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
@@ -57,8 +49,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
||||||
|
|
||||||
@@ -67,8 +57,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
private lateinit var gridView: GridView
|
private lateinit var gridView: GridView
|
||||||
private lateinit var adapter: OfflineMangaAdapter
|
private lateinit var adapter: OfflineMangaAdapter
|
||||||
private lateinit var total: TextView
|
private lateinit var total: TextView
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -94,15 +82,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineMANGA)
|
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineMANGA)
|
||||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
if (!uiSettings.immersiveMode) {
|
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
|
||||||
view.rootView.fitsSystemWindows = true
|
view.rootView.fitsSystemWindows = true
|
||||||
}
|
}
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
?.getBoolean("colorOverflow", false) ?: false
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
|
||||||
if (!colorOverflow) {
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
||||||
searchView.addTextChangedListener(object : TextWatcher {
|
searchView.addTextChangedListener(object : TextWatcher {
|
||||||
@@ -116,8 +101,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
onSearchQuery(s.toString())
|
onSearchQuery(s.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
||||||
?.getInt("offline_view", 0)
|
|
||||||
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
||||||
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
||||||
var selected = when (style) {
|
var selected = when (style) {
|
||||||
@@ -136,8 +120,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
layoutList.setOnClickListener {
|
layoutList.setOnClickListener {
|
||||||
selected(it as ImageView)
|
selected(it as ImageView)
|
||||||
style = 0
|
style = 0
|
||||||
requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit()
|
PrefManager.setVal(PrefName.OfflineView, style)
|
||||||
.putInt("offline_view", style!!).apply()
|
|
||||||
gridView.visibility = View.GONE
|
gridView.visibility = View.GONE
|
||||||
gridView = view.findViewById(R.id.gridView)
|
gridView = view.findViewById(R.id.gridView)
|
||||||
adapter.notifyNewGrid()
|
adapter.notifyNewGrid()
|
||||||
@@ -148,8 +131,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
layoutcompact.setOnClickListener {
|
layoutcompact.setOnClickListener {
|
||||||
selected(it as ImageView)
|
selected(it as ImageView)
|
||||||
style = 1
|
style = 1
|
||||||
requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit()
|
PrefManager.setVal(PrefName.OfflineView, style)
|
||||||
.putInt("offline_view", style!!).apply()
|
|
||||||
gridView.visibility = View.GONE
|
gridView.visibility = View.GONE
|
||||||
gridView = view.findViewById(R.id.gridView1)
|
gridView = view.findViewById(R.id.gridView1)
|
||||||
adapter.notifyNewGrid()
|
adapter.notifyNewGrid()
|
||||||
@@ -180,19 +162,13 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
|
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
|
||||||
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
|
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
|
||||||
media?.let {
|
media?.let {
|
||||||
|
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
.putExtra("media", getMedia(it))
|
.putExtra("media", getMedia(it))
|
||||||
.putExtra("download", true),
|
.putExtra("download", true),
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
null
|
||||||
requireActivity(),
|
|
||||||
Pair.create(
|
|
||||||
gridView.getChildAt(position)
|
|
||||||
.findViewById<ImageView>(R.id.itemCompactImage),
|
|
||||||
ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage))
|
|
||||||
)
|
|
||||||
).toBundle()
|
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
snackString("no media found")
|
snackString("no media found")
|
||||||
@@ -236,41 +212,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
initActivity(requireActivity())
|
initActivity(requireActivity())
|
||||||
var height = statusBarHeight
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
|
|
||||||
if (displayCutout != null) {
|
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
|
||||||
height = max(
|
|
||||||
statusBarHeight,
|
|
||||||
min(
|
|
||||||
displayCutout.boundingRects[0].width(),
|
|
||||||
displayCutout.boundingRects[0].height()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||||
scrollTop.translationY =
|
|
||||||
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
|
||||||
val visible = false
|
|
||||||
|
|
||||||
fun animate() {
|
|
||||||
val start = if (visible) 0f else 1f
|
|
||||||
val end = if (!visible) 0f else 1f
|
|
||||||
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
|
|
||||||
duration = 300
|
|
||||||
interpolator = OvershootInterpolator(2f)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
|
|
||||||
duration = 300
|
|
||||||
interpolator = OvershootInterpolator(2f)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollTop.setOnClickListener {
|
scrollTop.setOnClickListener {
|
||||||
gridView.smoothScrollToPositionFromTop(0, 0)
|
gridView.smoothScrollToPositionFromTop(0, 0)
|
||||||
}
|
}
|
||||||
@@ -290,8 +233,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
totalItemCount: Int
|
totalItemCount: Int
|
||||||
) {
|
) {
|
||||||
val first = view.getChildAt(0)
|
val first = view.getChildAt(0)
|
||||||
val visibility = first != null && first.top < -height
|
val visibility = first != null && first.top < 0
|
||||||
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
||||||
|
scrollTop.translationY =
|
||||||
|
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -324,8 +269,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
||||||
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
for (title in mangaTitles) {
|
for (title in mangaTitles) {
|
||||||
val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
|
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
|
||||||
val download = _downloads.first()
|
val download = tDownloads.first()
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
newMangaDownloads += offlineMangaModel
|
newMangaDownloads += offlineMangaModel
|
||||||
}
|
}
|
||||||
@@ -333,8 +278,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
||||||
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
for (title in novelTitles) {
|
for (title in novelTitles) {
|
||||||
val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
|
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
|
||||||
val download = _downloads.first()
|
val download = tDownloads.first()
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
newNovelDownloads += offlineMangaModel
|
newNovelDownloads += offlineMangaModel
|
||||||
}
|
}
|
||||||
@@ -343,12 +288,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
val type = when (downloadedType.type) {
|
||||||
"Manga"
|
DownloadedType.Type.MANGA -> "Manga"
|
||||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
DownloadedType.Type.ANIME -> "Anime"
|
||||||
"Anime"
|
else -> "Novel"
|
||||||
} else {
|
|
||||||
"Novel"
|
|
||||||
}
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
@@ -365,20 +308,18 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
val mediaJson = media.readText()
|
val mediaJson = media.readText()
|
||||||
gson.fromJson(mediaJson, Media::class.java)
|
gson.fromJson(mediaJson, Media::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
Logger.log(e)
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
||||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
val type = when (downloadedType.type) {
|
||||||
"Manga"
|
DownloadedType.Type.MANGA -> "Manga"
|
||||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
DownloadedType.Type.ANIME -> "Anime"
|
||||||
"Anime"
|
else -> "Novel"
|
||||||
} else {
|
|
||||||
"Novel"
|
|
||||||
}
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
@@ -386,8 +327,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
)
|
)
|
||||||
//load media.json and convert to media class with gson
|
//load media.json and convert to media class with gson
|
||||||
try {
|
try {
|
||||||
val media = File(directory, "media.json")
|
|
||||||
val mediaJson = media.readText()
|
|
||||||
val mediaModel = getMedia(downloadedType)!!
|
val mediaModel = getMedia(downloadedType)!!
|
||||||
val cover = File(directory, "cover.jpg")
|
val cover = File(directory, "cover.jpg")
|
||||||
val coverUri: Uri? = if (cover.exists()) {
|
val coverUri: Uri? = if (cover.exists()) {
|
||||||
@@ -419,9 +358,9 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
bannerUri
|
bannerUri
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
Logger.log(e)
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
return OfflineMangaModel(
|
return OfflineMangaModel(
|
||||||
"unknown",
|
"unknown",
|
||||||
"0",
|
"0",
|
||||||
@@ -429,8 +368,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
"??",
|
"??",
|
||||||
"movie",
|
"movie",
|
||||||
"hmm",
|
"hmm",
|
||||||
false,
|
isOngoing = false,
|
||||||
false,
|
isUserScored = false,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.novel.NovelReadFragment
|
import ani.dantotsu.media.novel.NovelReadFragment
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
@@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -42,6 +43,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -75,7 +77,7 @@ class NovelDownloaderService : Service() {
|
|||||||
builder =
|
builder =
|
||||||
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
|
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
|
||||||
setContentTitle("Novel Download Progress")
|
setContentTitle("Novel Download Progress")
|
||||||
setSmallIcon(R.drawable.ic_round_download_24)
|
setSmallIcon(R.drawable.ic_download_24)
|
||||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
@@ -186,15 +188,15 @@ class NovelDownloaderService : Service() {
|
|||||||
val contentType = response.header("Content-Type")
|
val contentType = response.header("Content-Type")
|
||||||
val contentDisposition = response.header("Content-Disposition")
|
val contentDisposition = response.header("Content-Disposition")
|
||||||
|
|
||||||
logger("Content-Type: $contentType")
|
Logger.log("Content-Type: $contentType")
|
||||||
logger("Content-Disposition: $contentDisposition")
|
Logger.log("Content-Disposition: $contentDisposition")
|
||||||
|
|
||||||
// Return true if the Content-Type or Content-Disposition indicates an EPUB file
|
// Return true if the Content-Type or Content-Disposition indicates an EPUB file
|
||||||
contentType == "application/epub+zip" ||
|
contentType == "application/epub+zip" ||
|
||||||
(contentDisposition?.contains(".epub") == true)
|
(contentDisposition?.contains(".epub") == true)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Error checking file type: ${e.message}")
|
Logger.log("Error checking file type: ${e.message}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,12 +227,12 @@ class NovelDownloaderService : Service() {
|
|||||||
|
|
||||||
if (!isEpubFile(task.downloadLink)) {
|
if (!isEpubFile(task.downloadLink)) {
|
||||||
if (isAlreadyDownloaded(task.originalLink)) {
|
if (isAlreadyDownloaded(task.originalLink)) {
|
||||||
logger("Already downloaded")
|
Logger.log("Already downloaded")
|
||||||
broadcastDownloadFinished(task.originalLink)
|
broadcastDownloadFinished(task.originalLink)
|
||||||
snackString("Already downloaded")
|
snackString("Already downloaded")
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
logger("Download link is not an .epub file")
|
Logger.log("Download link is not an .epub file")
|
||||||
broadcastDownloadFailed(task.originalLink)
|
broadcastDownloadFailed(task.originalLink)
|
||||||
snackString("Download link is not an .epub file")
|
snackString("Download link is not an .epub file")
|
||||||
return@withContext
|
return@withContext
|
||||||
@@ -301,7 +303,7 @@ class NovelDownloaderService : Service() {
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val progress =
|
val progress =
|
||||||
(downloadedBytes * 100 / totalBytes).toInt()
|
(downloadedBytes * 100 / totalBytes).toInt()
|
||||||
logger("Download progress: $progress")
|
Logger.log("Download progress: $progress")
|
||||||
broadcastDownloadProgress(task.originalLink, progress)
|
broadcastDownloadProgress(task.originalLink, progress)
|
||||||
}
|
}
|
||||||
lastBroadcastUpdate = downloadedBytes
|
lastBroadcastUpdate = downloadedBytes
|
||||||
@@ -316,7 +318,7 @@ class NovelDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Exception while downloading .epub inside request: ${e.message}")
|
Logger.log("Exception while downloading .epub inside request: ${e.message}")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,15 +342,16 @@ class NovelDownloaderService : Service() {
|
|||||||
snackString("${task.title} - ${task.chapter} Download finished")
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Exception while downloading .epub: ${e.message}")
|
Logger.log("Exception while downloading .epub: ${e.message}")
|
||||||
snackString("Exception while downloading .epub: ${e.message}")
|
snackString("Exception while downloading .epub: ${e.message}")
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
broadcastDownloadFailed(task.originalLink)
|
broadcastDownloadFailed(task.originalLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun saveMediaInfo(task: DownloadTask) {
|
private fun saveMediaInfo(task: DownloadTask) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
launchIO {
|
||||||
val directory = File(
|
val directory = File(
|
||||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/Novel/${task.title}"
|
"Dantotsu/Novel/${task.title}"
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import android.util.Log
|
|||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.ContextCompat.getString
|
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
import androidx.media3.common.TrackSelectionParameters
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
@@ -31,7 +29,6 @@ import androidx.media3.exoplayer.offline.DownloadHelper
|
|||||||
import androidx.media3.exoplayer.offline.DownloadManager
|
import androidx.media3.exoplayer.offline.DownloadManager
|
||||||
import androidx.media3.exoplayer.offline.DownloadService
|
import androidx.media3.exoplayer.offline.DownloadService
|
||||||
import androidx.media3.exoplayer.scheduler.Requirements
|
import androidx.media3.exoplayer.scheduler.Requirements
|
||||||
import androidx.media3.ui.TrackSelectionDialogBuilder
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.defaultHeaders
|
import ani.dantotsu.defaultHeaders
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
@@ -45,6 +42,8 @@ import ani.dantotsu.parsers.Subtitle
|
|||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@@ -159,15 +158,15 @@ object Helper {
|
|||||||
finalException: Exception?
|
finalException: Exception?
|
||||||
) {
|
) {
|
||||||
if (download.state == Download.STATE_COMPLETED) {
|
if (download.state == Download.STATE_COMPLETED) {
|
||||||
Log.e("Downloader", "Download Completed")
|
Logger.log("Download Completed")
|
||||||
} else if (download.state == Download.STATE_FAILED) {
|
} else if (download.state == Download.STATE_FAILED) {
|
||||||
Log.e("Downloader", "Download Failed")
|
Logger.log("Download Failed")
|
||||||
} else if (download.state == Download.STATE_STOPPED) {
|
} else if (download.state == Download.STATE_STOPPED) {
|
||||||
Log.e("Downloader", "Download Stopped")
|
Logger.log("Download Stopped")
|
||||||
} else if (download.state == Download.STATE_QUEUED) {
|
} else if (download.state == Download.STATE_QUEUED) {
|
||||||
Log.e("Downloader", "Download Queued")
|
Logger.log("Download Queued")
|
||||||
} else if (download.state == Download.STATE_DOWNLOADING) {
|
} else if (download.state == Download.STATE_DOWNLOADING) {
|
||||||
Log.e("Downloader", "Download Downloading")
|
Logger.log("Download Downloading")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,19 +230,13 @@ object Helper {
|
|||||||
DownloadService.sendRemoveDownload(
|
DownloadService.sendRemoveDownload(
|
||||||
context,
|
context,
|
||||||
ExoplayerDownloadService::class.java,
|
ExoplayerDownloadService::class.java,
|
||||||
context.getSharedPreferences(
|
PrefManager.getAnimeDownloadPreferences().getString(
|
||||||
getString(context, R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).getString(
|
|
||||||
animeDownloadTask.getTaskName(),
|
animeDownloadTask.getTaskName(),
|
||||||
""
|
""
|
||||||
) ?: "",
|
) ?: "",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
context.getSharedPreferences(
|
PrefManager.getAnimeDownloadPreferences().edit()
|
||||||
getString(context, R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).edit()
|
|
||||||
.remove(animeDownloadTask.getTaskName())
|
.remove(animeDownloadTask.getTaskName())
|
||||||
.apply()
|
.apply()
|
||||||
downloadsManger.removeDownload(
|
downloadsManger.removeDownload(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ani.dantotsu.home
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -28,13 +27,13 @@ import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
|
|||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
import ani.dantotsu.connections.anilist.getUserId
|
import ani.dantotsu.connections.anilist.getUserId
|
||||||
import ani.dantotsu.databinding.FragmentAnimeBinding
|
import ani.dantotsu.databinding.FragmentAnimeBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.ProgressAdapter
|
import ani.dantotsu.media.ProgressAdapter
|
||||||
import ani.dantotsu.media.SearchActivity
|
import ani.dantotsu.media.SearchActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -50,9 +49,6 @@ class AnimeFragment : Fragment() {
|
|||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private lateinit var animePageAdapter: AnimePageAdapter
|
private lateinit var animePageAdapter: AnimePageAdapter
|
||||||
|
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
val model: AnilistAnimeViewModel by activityViewModels()
|
val model: AnilistAnimeViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@@ -217,7 +213,7 @@ class AnimeFragment : Fragment() {
|
|||||||
if (it != null) {
|
if (it != null) {
|
||||||
animePageAdapter.updateTrending(
|
animePageAdapter.updateTrending(
|
||||||
MediaAdaptor(
|
MediaAdaptor(
|
||||||
if (uiSettings.smallView) 3 else 2,
|
if (PrefManager.getVal(PrefName.SmallView)) 3 else 2,
|
||||||
it,
|
it,
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
viewPager = animePageAdapter.trendingViewPager
|
viewPager = animePageAdapter.trendingViewPager
|
||||||
@@ -268,8 +264,11 @@ class AnimeFragment : Fragment() {
|
|||||||
model.loaded = true
|
model.loaded = true
|
||||||
model.loadTrending(1)
|
model.loadTrending(1)
|
||||||
model.loadUpdated()
|
model.loadUpdated()
|
||||||
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
model.loadPopular(
|
||||||
.getBoolean("popular_list", false))
|
"ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
|
||||||
|
PrefName.PopularAnimeList
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
_binding?.animeRefresh?.isRefreshing = false
|
_binding?.animeRefresh?.isRefreshing = false
|
||||||
@@ -284,7 +283,9 @@ class AnimeFragment : Fragment() {
|
|||||||
binding.root.requestApplyInsets()
|
binding.root.requestApplyInsets()
|
||||||
binding.root.requestLayout()
|
binding.root.requestLayout()
|
||||||
}
|
}
|
||||||
|
if (this::animePageAdapter.isInitialized && _binding != null) {
|
||||||
|
animePageAdapter.updateNotificationCount()
|
||||||
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -22,18 +21,19 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemAnimePageBinding
|
import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.CalendarActivity
|
import ani.dantotsu.media.CalendarActivity
|
||||||
import ani.dantotsu.media.GenreActivity
|
import ani.dantotsu.media.GenreActivity
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.SearchActivity
|
import ani.dantotsu.media.SearchActivity
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.setSlideIn
|
import ani.dantotsu.setSlideIn
|
||||||
import ani.dantotsu.setSlideUp
|
import ani.dantotsu.setSlideUp
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
@@ -44,8 +44,6 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
private var trendHandler: Handler? = null
|
private var trendHandler: Handler? = null
|
||||||
private lateinit var trendRun: Runnable
|
private lateinit var trendRun: Runnable
|
||||||
var trendingViewPager: ViewPager2? = null
|
var trendingViewPager: ViewPager2? = null
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
@@ -68,17 +66,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
val color = typedValue.data
|
val color = typedValue.data
|
||||||
|
|
||||||
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
|
||||||
?.getBoolean("colorOverflow", false) ?: false
|
|
||||||
if (!colorOverflow) {
|
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
|
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
|
||||||
|
|
||||||
if (uiSettings.smallView) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
if (PrefManager.getVal(PrefName.SmallView)) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = (-108f).px
|
bottomMargin = (-108f).px
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +95,17 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME)
|
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME)
|
||||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
binding.animeUserAvatar.setOnLongClickListener { view ->
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
view.context,
|
||||||
|
Intent(view.context, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", Anilist.userid),null
|
||||||
|
)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.animeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
binding.animePreviousSeason,
|
binding.animePreviousSeason,
|
||||||
@@ -133,14 +137,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
binding.animeIncludeList.visibility =
|
binding.animeIncludeList.visibility =
|
||||||
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
binding.animeIncludeList.isChecked = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
binding.animeIncludeList.isChecked = PrefManager.getVal(PrefName.PopularAnimeList)
|
||||||
?.getBoolean("popular_list", true) ?: true
|
|
||||||
|
|
||||||
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
||||||
onIncludeListClick.invoke(isChecked)
|
onIncludeListClick.invoke(isChecked)
|
||||||
|
|
||||||
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
PrefManager.setVal(PrefName.PopularAnimeList, isChecked)
|
||||||
?.putBoolean("popular_list", isChecked)?.apply()
|
|
||||||
}
|
}
|
||||||
if (ready.value == false)
|
if (ready.value == false)
|
||||||
ready.postValue(true)
|
ready.postValue(true)
|
||||||
@@ -179,12 +181,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
)
|
)
|
||||||
|
|
||||||
binding.animeTrendingViewPager.layoutAnimation =
|
binding.animeTrendingViewPager.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
binding.animeTitleContainer.startAnimation(setSlideUp(uiSettings))
|
binding.animeTitleContainer.startAnimation(setSlideUp())
|
||||||
binding.animeListContainer.layoutAnimation =
|
binding.animeListContainer.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
binding.animeSeasonsCont.layoutAnimation =
|
binding.animeSeasonsCont.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRecent(adaptor: MediaAdaptor) {
|
fun updateRecent(adaptor: MediaAdaptor) {
|
||||||
@@ -199,11 +201,11 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
|
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.animeRecently.visibility = View.VISIBLE
|
binding.animeRecently.visibility = View.VISIBLE
|
||||||
binding.animeRecently.startAnimation(setSlideUp(uiSettings))
|
binding.animeRecently.startAnimation(setSlideUp())
|
||||||
binding.animeUpdatedRecyclerView.layoutAnimation =
|
binding.animeUpdatedRecyclerView.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
binding.animePopular.visibility = View.VISIBLE
|
binding.animePopular.visibility = View.VISIBLE
|
||||||
binding.animePopular.startAnimation(setSlideUp(uiSettings))
|
binding.animePopular.startAnimation(setSlideUp())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAvatar() {
|
fun updateAvatar() {
|
||||||
@@ -213,6 +215,14 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateNotificationCount() {
|
||||||
|
if (this::binding.isInitialized) {
|
||||||
|
binding.animeNotificationCount.visibility =
|
||||||
|
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inner class AnimePageViewHolder(val binding: ItemAnimePageBinding) :
|
inner class AnimePageViewHolder(val binding: ItemAnimePageBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,23 +21,25 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
|
import ani.dantotsu.blurImage
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
||||||
import ani.dantotsu.connections.anilist.getUserId
|
import ani.dantotsu.connections.anilist.getUserId
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.FragmentHomeBinding
|
import ani.dantotsu.databinding.FragmentHomeBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.user.ListActivity
|
import ani.dantotsu.media.user.ListActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.setSlideIn
|
import ani.dantotsu.setSlideIn
|
||||||
import ani.dantotsu.setSlideUp
|
import ani.dantotsu.setSlideUp
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -70,16 +72,17 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val scope = lifecycleScope
|
val scope = lifecycleScope
|
||||||
var uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
fun load() {
|
fun load() {
|
||||||
if (activity != null && _binding != null) lifecycleScope.launch(Dispatchers.Main) {
|
if (activity != null && _binding != null) lifecycleScope.launch(Dispatchers.Main) {
|
||||||
binding.homeUserName.text = Anilist.username
|
binding.homeUserName.text = Anilist.username
|
||||||
binding.homeUserEpisodesWatched.text = Anilist.episodesWatched.toString()
|
binding.homeUserEpisodesWatched.text = Anilist.episodesWatched.toString()
|
||||||
binding.homeUserChaptersRead.text = Anilist.chapterRead.toString()
|
binding.homeUserChaptersRead.text = Anilist.chapterRead.toString()
|
||||||
binding.homeUserAvatar.loadImage(Anilist.avatar)
|
binding.homeUserAvatar.loadImage(Anilist.avatar)
|
||||||
if (!uiSettings.bannerAnimations) binding.homeUserBg.pause()
|
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause()
|
||||||
binding.homeUserBg.loadImage(Anilist.bg)
|
blurImage(binding.homeUserBg, Anilist.bg)
|
||||||
binding.homeUserDataProgressBar.visibility = View.GONE
|
binding.homeUserDataProgressBar.visibility = View.GONE
|
||||||
|
binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
|
||||||
binding.homeAnimeList.setOnClickListener {
|
binding.homeAnimeList.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
@@ -98,14 +101,14 @@ class HomeFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
|
binding.homeUserAvatarContainer.startAnimation(setSlideUp())
|
||||||
binding.homeUserDataContainer.visibility = View.VISIBLE
|
binding.homeUserDataContainer.visibility = View.VISIBLE
|
||||||
binding.homeUserDataContainer.layoutAnimation =
|
binding.homeUserDataContainer.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideUp(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideUp(), 0.25f)
|
||||||
binding.homeAnimeList.visibility = View.VISIBLE
|
binding.homeAnimeList.visibility = View.VISIBLE
|
||||||
binding.homeMangaList.visibility = View.VISIBLE
|
binding.homeMangaList.visibility = View.VISIBLE
|
||||||
binding.homeListContainer.layoutAnimation =
|
binding.homeListContainer.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
snackString(currContext()?.getString(R.string.please_reload))
|
snackString(currContext()?.getString(R.string.please_reload))
|
||||||
@@ -119,6 +122,13 @@ class HomeFragment : Fragment() {
|
|||||||
"dialog"
|
"dialog"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
binding.homeUserAvatarContainer.setOnLongClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
requireContext(), Intent(requireContext(), ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", Anilist.userid),null
|
||||||
|
)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
@@ -127,18 +137,21 @@ class HomeFragment : Fragment() {
|
|||||||
binding.homeTopContainer.updatePadding(top = statusBarHeight)
|
binding.homeTopContainer.updatePadding(top = statusBarHeight)
|
||||||
|
|
||||||
var reached = false
|
var reached = false
|
||||||
val duration = (uiSettings.animationSpeed * 200).toLong()
|
val duration = ((PrefManager.getVal(PrefName.AnimationSpeed) as Float) * 200).toLong()
|
||||||
binding.homeScroll.setOnScrollChangeListener { _, _, _, _, _ ->
|
|
||||||
if (!binding.homeScroll.canScrollVertically(1)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
reached = true
|
binding.homeScroll.setOnScrollChangeListener { _, _, _, _, _ ->
|
||||||
bottomBar.animate().translationZ(0f).setDuration(duration).start()
|
if (!binding.homeScroll.canScrollVertically(1)) {
|
||||||
ObjectAnimator.ofFloat(bottomBar, "elevation", 4f, 0f).setDuration(duration)
|
reached = true
|
||||||
.start()
|
bottomBar.animate().translationZ(0f).setDuration(duration).start()
|
||||||
} else {
|
ObjectAnimator.ofFloat(bottomBar, "elevation", 4f, 0f).setDuration(duration)
|
||||||
if (reached) {
|
|
||||||
bottomBar.animate().translationZ(12f).setDuration(duration).start()
|
|
||||||
ObjectAnimator.ofFloat(bottomBar, "elevation", 0f, 4f).setDuration(duration)
|
|
||||||
.start()
|
.start()
|
||||||
|
} else {
|
||||||
|
if (reached) {
|
||||||
|
bottomBar.animate().translationZ(12f).setDuration(duration).start()
|
||||||
|
ObjectAnimator.ofFloat(bottomBar, "elevation", 0f, 4f).setDuration(duration)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,13 +219,13 @@ class HomeFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
recyclerView.visibility = View.VISIBLE
|
recyclerView.visibility = View.VISIBLE
|
||||||
recyclerView.layoutAnimation =
|
recyclerView.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
empty.visibility = View.VISIBLE
|
empty.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
title.visibility = View.VISIBLE
|
title.visibility = View.VISIBLE
|
||||||
title.startAnimation(setSlideUp(uiSettings))
|
title.startAnimation(setSlideUp())
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,25 +308,26 @@ class HomeFragment : Fragment() {
|
|||||||
binding.homeRecommended
|
binding.homeRecommended
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
|
binding.homeUserAvatarContainer.startAnimation(setSlideUp())
|
||||||
|
|
||||||
model.empty.observe(viewLifecycleOwner) {
|
model.empty.observe(viewLifecycleOwner) {
|
||||||
binding.homeDantotsuContainer.visibility = if (it == true) View.VISIBLE else View.GONE
|
binding.homeDantotsuContainer.visibility = if (it == true) View.VISIBLE else View.GONE
|
||||||
(binding.homeDantotsuIcon.drawable as Animatable).start()
|
(binding.homeDantotsuIcon.drawable as Animatable).start()
|
||||||
binding.homeDantotsuContainer.startAnimation(setSlideUp(uiSettings))
|
binding.homeDantotsuContainer.startAnimation(setSlideUp())
|
||||||
binding.homeDantotsuIcon.setSafeOnClickListener {
|
binding.homeDantotsuIcon.setSafeOnClickListener {
|
||||||
(binding.homeDantotsuIcon.drawable as Animatable).start()
|
(binding.homeDantotsuIcon.drawable as Animatable).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val array = arrayOf(
|
val array = arrayOf(
|
||||||
Runnable { runBlocking { model.setAnimeContinue() } },
|
"AnimeContinue",
|
||||||
Runnable { runBlocking { model.setAnimeFav() } },
|
"AnimeFav",
|
||||||
Runnable { runBlocking { model.setAnimePlanned() } },
|
"AnimePlanned",
|
||||||
Runnable { runBlocking { model.setMangaContinue() } },
|
"MangaContinue",
|
||||||
Runnable { runBlocking { model.setMangaFav() } },
|
"MangaFav",
|
||||||
Runnable { runBlocking { model.setMangaPlanned() } },
|
"MangaPlanned",
|
||||||
Runnable { runBlocking { model.setRecommendation() } }
|
"Recommendation"
|
||||||
)
|
)
|
||||||
|
|
||||||
val containers = arrayOf(
|
val containers = arrayOf(
|
||||||
@@ -330,8 +344,6 @@ class HomeFragment : Fragment() {
|
|||||||
live.observe(viewLifecycleOwner) {
|
live.observe(viewLifecycleOwner) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
uiSettings =
|
|
||||||
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
//Get userData First
|
//Get userData First
|
||||||
getUserId(requireContext()) {
|
getUserId(requireContext()) {
|
||||||
@@ -340,9 +352,13 @@ class HomeFragment : Fragment() {
|
|||||||
model.loaded = true
|
model.loaded = true
|
||||||
model.setListImages()
|
model.setListImages()
|
||||||
var empty = true
|
var empty = true
|
||||||
|
val homeLayoutShow: List<Boolean> =
|
||||||
|
PrefManager.getVal(PrefName.HomeLayoutShow)
|
||||||
|
runBlocking {
|
||||||
|
model.initHomePage()
|
||||||
|
}
|
||||||
(array.indices).forEach { i ->
|
(array.indices).forEach { i ->
|
||||||
if (uiSettings.homeLayoutShow[i]) {
|
if (homeLayoutShow.elementAt(i)) {
|
||||||
array[i].run()
|
|
||||||
empty = false
|
empty = false
|
||||||
} else withContext(Dispatchers.Main) {
|
} else withContext(Dispatchers.Main) {
|
||||||
containers[i].visibility = View.GONE
|
containers[i].visibility = View.GONE
|
||||||
@@ -356,9 +372,12 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
|
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
|
||||||
|
if (_binding != null) {
|
||||||
|
binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.FragmentLoginBinding
|
import ani.dantotsu.databinding.FragmentLoginBinding
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
||||||
|
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
@@ -29,5 +39,99 @@ class LoginFragment : Fragment() {
|
|||||||
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
|
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
|
||||||
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
|
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
|
||||||
binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
|
binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
|
||||||
|
|
||||||
|
val openDocumentLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
val jsonString =
|
||||||
|
requireActivity().contentResolver.openInputStream(uri)?.readBytes()
|
||||||
|
?: throw Exception("Error reading file")
|
||||||
|
val name =
|
||||||
|
DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings"
|
||||||
|
//.sani is encrypted, .ani is not
|
||||||
|
if (name.endsWith(".sani")) {
|
||||||
|
passwordAlertDialog { password ->
|
||||||
|
if (password != null) {
|
||||||
|
val salt = jsonString.copyOfRange(0, 16)
|
||||||
|
val encrypted = jsonString.copyOfRange(16, jsonString.size)
|
||||||
|
val decryptedJson = try {
|
||||||
|
PreferenceKeystore.decryptWithPassword(
|
||||||
|
password,
|
||||||
|
encrypted,
|
||||||
|
salt
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast("Incorrect password")
|
||||||
|
return@passwordAlertDialog
|
||||||
|
}
|
||||||
|
if (PreferencePackager.unpack(decryptedJson))
|
||||||
|
restartApp()
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name.endsWith(".ani")) {
|
||||||
|
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
||||||
|
if (PreferencePackager.unpack(decryptedJson))
|
||||||
|
restartApp()
|
||||||
|
} else {
|
||||||
|
toast("Invalid file type")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
toast("Error importing settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.importSettingsButton.setOnClickListener {
|
||||||
|
openDocumentLauncher.launch(arrayOf("*/*"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
|
||||||
|
val password = CharArray(16).apply { fill('0') }
|
||||||
|
|
||||||
|
// Inflate the dialog layout
|
||||||
|
val dialogView =
|
||||||
|
LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null)
|
||||||
|
dialogView.findViewById<TextInputEditText>(R.id.userAgentTextBox)?.hint = "Password"
|
||||||
|
val subtitleTextView = dialogView.findViewById<TextView>(R.id.subtitle)
|
||||||
|
subtitleTextView?.visibility = View.VISIBLE
|
||||||
|
subtitleTextView?.text = "Enter your password to decrypt the file"
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup)
|
||||||
|
.setTitle("Enter Password")
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton("OK", null)
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
password.fill('0')
|
||||||
|
dialog.dismiss()
|
||||||
|
callback(null)
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.window?.setDimAmount(0.8f)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
// Override the positive button here
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val editText = dialog.findViewById<TextInputEditText>(R.id.userAgentTextBox)
|
||||||
|
if (editText?.text?.isNotBlank() == true) {
|
||||||
|
editText.text?.toString()?.trim()?.toCharArray(password)
|
||||||
|
dialog.dismiss()
|
||||||
|
callback(password)
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restartApp() {
|
||||||
|
val intent = Intent(requireActivity(), requireActivity().javaClass)
|
||||||
|
requireActivity().finish()
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package ani.dantotsu.home
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -26,12 +25,12 @@ import ani.dantotsu.connections.anilist.AnilistMangaViewModel
|
|||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
import ani.dantotsu.connections.anilist.getUserId
|
import ani.dantotsu.connections.anilist.getUserId
|
||||||
import ani.dantotsu.databinding.FragmentMangaBinding
|
import ani.dantotsu.databinding.FragmentMangaBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.ProgressAdapter
|
import ani.dantotsu.media.ProgressAdapter
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -46,9 +45,6 @@ class MangaFragment : Fragment() {
|
|||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private lateinit var mangaPageAdapter: MangaPageAdapter
|
private lateinit var mangaPageAdapter: MangaPageAdapter
|
||||||
|
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
val model: AnilistMangaViewModel by activityViewModels()
|
val model: AnilistMangaViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@@ -175,7 +171,7 @@ class MangaFragment : Fragment() {
|
|||||||
if (it != null) {
|
if (it != null) {
|
||||||
mangaPageAdapter.updateTrending(
|
mangaPageAdapter.updateTrending(
|
||||||
MediaAdaptor(
|
MediaAdaptor(
|
||||||
if (uiSettings.smallView) 3 else 2,
|
if (PrefManager.getVal(PrefName.SmallView)) 3 else 2,
|
||||||
it,
|
it,
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
viewPager = mangaPageAdapter.trendingViewPager
|
viewPager = mangaPageAdapter.trendingViewPager
|
||||||
@@ -242,8 +238,11 @@ class MangaFragment : Fragment() {
|
|||||||
model.loaded = true
|
model.loaded = true
|
||||||
model.loadTrending()
|
model.loadTrending()
|
||||||
model.loadTrendingNovel()
|
model.loadTrendingNovel()
|
||||||
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
model.loadPopular(
|
||||||
.getBoolean("popular_list", false) )
|
"MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
|
||||||
|
PrefName.PopularMangaList
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
_binding?.mangaRefresh?.isRefreshing = false
|
_binding?.mangaRefresh?.isRefreshing = false
|
||||||
@@ -259,6 +258,9 @@ class MangaFragment : Fragment() {
|
|||||||
binding.root.requestApplyInsets()
|
binding.root.requestApplyInsets()
|
||||||
binding.root.requestLayout()
|
binding.root.requestLayout()
|
||||||
}
|
}
|
||||||
|
if (this::mangaPageAdapter.isInitialized && _binding != null) {
|
||||||
|
mangaPageAdapter.updateNotificationCount()
|
||||||
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -22,17 +21,18 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemMangaPageBinding
|
import ani.dantotsu.databinding.ItemMangaPageBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.GenreActivity
|
import ani.dantotsu.media.GenreActivity
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.SearchActivity
|
import ani.dantotsu.media.SearchActivity
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.setSlideIn
|
import ani.dantotsu.setSlideIn
|
||||||
import ani.dantotsu.setSlideUp
|
import ani.dantotsu.setSlideUp
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
@@ -43,8 +43,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
private var trendHandler: Handler? = null
|
private var trendHandler: Handler? = null
|
||||||
private lateinit var trendRun: Runnable
|
private lateinit var trendRun: Runnable
|
||||||
var trendingViewPager: ViewPager2? = null
|
var trendingViewPager: ViewPager2? = null
|
||||||
private var uiSettings: UserInterfaceSettings =
|
|
||||||
loadData("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
@@ -67,22 +65,18 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
val color = typedValue.data
|
val color = typedValue.data
|
||||||
|
|
||||||
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
?.getBoolean("colorOverflow", false) ?: false
|
|
||||||
if (!colorOverflow) {
|
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
|
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
|
||||||
|
|
||||||
if (uiSettings.smallView) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
if (PrefManager.getVal(PrefName.SmallView)) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = (-108f).px
|
bottomMargin = (-108f).px
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAvatar()
|
updateAvatar()
|
||||||
|
binding.mangaNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
binding.mangaSearchBar.hint = "MANGA"
|
binding.mangaSearchBar.hint = "MANGA"
|
||||||
binding.mangaSearchBarText.setOnClickListener {
|
binding.mangaSearchBarText.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
@@ -97,6 +91,14 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.MANGA)
|
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.MANGA)
|
||||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
binding.mangaUserAvatar.setOnLongClickListener { view ->
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
view.context,
|
||||||
|
Intent(view.context, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", Anilist.userid),null
|
||||||
|
)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
binding.mangaSearchBar.setEndIconOnClickListener {
|
binding.mangaSearchBar.setEndIconOnClickListener {
|
||||||
binding.mangaSearchBarText.performClick()
|
binding.mangaSearchBarText.performClick()
|
||||||
@@ -126,14 +128,12 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
binding.mangaIncludeList.visibility =
|
binding.mangaIncludeList.visibility =
|
||||||
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
binding.mangaIncludeList.isChecked = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
binding.mangaIncludeList.isChecked = PrefManager.getVal(PrefName.PopularMangaList)
|
||||||
?.getBoolean("popular_list", true) ?: true
|
|
||||||
|
|
||||||
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
||||||
onIncludeListClick.invoke(isChecked)
|
onIncludeListClick.invoke(isChecked)
|
||||||
|
|
||||||
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
PrefManager.setVal(PrefName.PopularMangaList, isChecked)
|
||||||
?.putBoolean("popular_list", isChecked)?.apply()
|
|
||||||
}
|
}
|
||||||
if (ready.value == false)
|
if (ready.value == false)
|
||||||
ready.postValue(true)
|
ready.postValue(true)
|
||||||
@@ -155,8 +155,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||||
trendHandler = Handler(Looper.getMainLooper())
|
trendHandler = Handler(Looper.getMainLooper())
|
||||||
trendRun = Runnable {
|
trendRun = Runnable {
|
||||||
binding.mangaTrendingViewPager.currentItem =
|
binding.mangaTrendingViewPager.currentItem += 1
|
||||||
binding.mangaTrendingViewPager.currentItem + 1
|
|
||||||
}
|
}
|
||||||
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
|
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
|
||||||
object : ViewPager2.OnPageChangeCallback() {
|
object : ViewPager2.OnPageChangeCallback() {
|
||||||
@@ -169,10 +168,10 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
)
|
)
|
||||||
|
|
||||||
binding.mangaTrendingViewPager.layoutAnimation =
|
binding.mangaTrendingViewPager.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
binding.mangaTitleContainer.startAnimation(setSlideUp(uiSettings))
|
binding.mangaTitleContainer.startAnimation(setSlideUp())
|
||||||
binding.mangaListContainer.layoutAnimation =
|
binding.mangaListContainer.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNovel(adaptor: MediaAdaptor) {
|
fun updateNovel(adaptor: MediaAdaptor) {
|
||||||
@@ -187,11 +186,11 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
|
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.mangaNovel.visibility = View.VISIBLE
|
binding.mangaNovel.visibility = View.VISIBLE
|
||||||
binding.mangaNovel.startAnimation(setSlideUp(uiSettings))
|
binding.mangaNovel.startAnimation(setSlideUp())
|
||||||
binding.mangaNovelRecyclerView.layoutAnimation =
|
binding.mangaNovelRecyclerView.layoutAnimation =
|
||||||
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||||
binding.mangaPopular.visibility = View.VISIBLE
|
binding.mangaPopular.visibility = View.VISIBLE
|
||||||
binding.mangaPopular.startAnimation(setSlideUp(uiSettings))
|
binding.mangaPopular.startAnimation(setSlideUp())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAvatar() {
|
fun updateAvatar() {
|
||||||
@@ -201,6 +200,14 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateNotificationCount() {
|
||||||
|
if (this::binding.isInitialized) {
|
||||||
|
binding.mangaNotificationCount.visibility =
|
||||||
|
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||||
|
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inner class MangaPageViewHolder(val binding: ItemMangaPageBinding) :
|
inner class MangaPageViewHolder(val binding: ItemMangaPageBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -23,43 +22,36 @@ import ani.dantotsu.databinding.ActivityNoInternetBinding
|
|||||||
import ani.dantotsu.download.anime.OfflineAnimeFragment
|
import ani.dantotsu.download.anime.OfflineAnimeFragment
|
||||||
import ani.dantotsu.download.manga.OfflineMangaFragment
|
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.offline.OfflineFragment
|
import ani.dantotsu.offline.OfflineFragment
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.selectedOption
|
import ani.dantotsu.selectedOption
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
class NoInternet : AppCompatActivity() {
|
class NoInternet : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityNoInternetBinding
|
private lateinit var binding: ActivityNoInternetBinding
|
||||||
lateinit var bottomBar: AnimatedBottomBar
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
val bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
|
||||||
val backgroundDrawable = _bottomBar.background as GradientDrawable
|
val backgroundDrawable = bottomBar.background as GradientDrawable
|
||||||
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
|
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
|
||||||
backgroundDrawable.setColor(semiTransparentColor)
|
backgroundDrawable.setColor(semiTransparentColor)
|
||||||
_bottomBar.background = backgroundDrawable
|
bottomBar.background = backgroundDrawable
|
||||||
}
|
}
|
||||||
val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||||
.getBoolean("colorOverflow", false)
|
|
||||||
if (!colorOverflow) {
|
|
||||||
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var doubleBackToExitPressedOnce = false
|
var doubleBackToExitPressedOnce = false
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
@@ -76,8 +68,7 @@ class NoInternet : AppCompatActivity() {
|
|||||||
|
|
||||||
binding.root.doOnAttach {
|
binding.root.doOnAttach {
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
uiSettings = loadData("ui_settings") ?: uiSettings
|
selectedOption = PrefManager.getVal(PrefName.DefaultStartUpTab)
|
||||||
selectedOption = uiSettings.defaultStartUpTab
|
|
||||||
|
|
||||||
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
@@ -89,7 +80,7 @@ class NoInternet : AppCompatActivity() {
|
|||||||
val mainViewPager = binding.viewpager
|
val mainViewPager = binding.viewpager
|
||||||
mainViewPager.isUserInputEnabled = false
|
mainViewPager.isUserInputEnabled = false
|
||||||
mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
|
mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
|
||||||
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
mainViewPager.setPageTransformer(ZoomOutPageTransformer())
|
||||||
navbar.setOnTabSelectListener(object :
|
navbar.setOnTabSelectListener(object :
|
||||||
AnimatedBottomBar.OnTabSelectListener {
|
AnimatedBottomBar.OnTabSelectListener {
|
||||||
override fun onTabSelected(
|
override fun onTabSelected(
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package ani.dantotsu.media
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class Author(
|
data class Author(
|
||||||
val id: String,
|
val id: Int,
|
||||||
val name: String,
|
val name: String?,
|
||||||
|
val image: String?,
|
||||||
|
val role: String?,
|
||||||
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import ani.dantotsu.Refresh
|
|||||||
import ani.dantotsu.databinding.ActivityAuthorBinding
|
import ani.dantotsu.databinding.ActivityAuthorBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
@@ -36,7 +35,7 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|||||||
60
app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt
Normal file
60
app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.util.Pair
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.databinding.ItemCharacterBinding
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.setAnimation
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class AuthorAdapter(
|
||||||
|
private val authorList: ArrayList<Author>
|
||||||
|
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
|
||||||
|
val binding =
|
||||||
|
ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return AuthorViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) {
|
||||||
|
val binding = holder.binding
|
||||||
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
|
val author = authorList[position]
|
||||||
|
binding.itemCompactRelation.text = author.role
|
||||||
|
binding.itemCompactImage.loadImage(author.image)
|
||||||
|
binding.itemCompactTitle.text = author.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = authorList.size
|
||||||
|
inner class AuthorViewHolder(val binding: ItemCharacterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
val author = authorList[bindingAdapterPosition]
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
itemView.context,
|
||||||
|
Intent(
|
||||||
|
itemView.context,
|
||||||
|
AuthorActivity::class.java
|
||||||
|
).putExtra("author", author as Serializable),
|
||||||
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
|
itemView.context as Activity,
|
||||||
|
Pair.create(
|
||||||
|
binding.itemCompactImage,
|
||||||
|
ViewCompat.getTransitionName(binding.itemCompactImage)!!
|
||||||
|
),
|
||||||
|
).toBundle()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,11 +16,10 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityListBinding
|
import ani.dantotsu.databinding.ActivityListBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.media.user.ListViewPagerAdapter
|
import ani.dantotsu.media.user.ListViewPagerAdapter
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
@@ -38,7 +37,7 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
@@ -67,8 +66,7 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
binding.listTitle.setTextColor(primaryTextColor)
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
||||||
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
|
||||||
if (!uiSettings.immersiveMode) {
|
|
||||||
this.window.statusBarColor =
|
this.window.statusBarColor =
|
||||||
ContextCompat.getColor(this, R.color.nav_bg_inv)
|
ContextCompat.getColor(this, R.color.nav_bg_inv)
|
||||||
binding.root.fitsSystemWindows = true
|
binding.root.fitsSystemWindows = true
|
||||||
@@ -82,14 +80,13 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
topMargin = statusBarHeight
|
topMargin = statusBarHeight
|
||||||
bottomMargin = navBarHeight
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
binding.listTitle.setText(R.string.release_calendar)
|
binding.listTitle.setText(R.string.release_calendar)
|
||||||
binding.listSort.visibility = View.GONE
|
binding.listSort.visibility = View.GONE
|
||||||
|
binding.random.visibility = View.GONE
|
||||||
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
this@CalendarActivity.selectedTabIdx = tab?.position ?: 1
|
this@CalendarActivity.selectedTabIdx = tab?.position ?: 1
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ data class Character(
|
|||||||
val image: String?,
|
val image: String?,
|
||||||
val banner: String?,
|
val banner: String?,
|
||||||
val role: String,
|
val role: String,
|
||||||
|
var isFav: Boolean,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
var age: String? = null,
|
var age: String? = null,
|
||||||
var gender: String? = null,
|
var gender: String? = null,
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import androidx.core.util.Pair
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.databinding.ItemCharacterBinding
|
import ani.dantotsu.databinding.ItemCharacterBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class CharacterAdapter(
|
class CharacterAdapter(
|
||||||
@@ -26,13 +24,10 @@ class CharacterAdapter(
|
|||||||
return CharacterViewHolder(binding)
|
return CharacterViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uiSettings =
|
|
||||||
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(binding.root.context, holder.binding.root, uiSettings)
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
val character = characterList[position]
|
val character = characterList[position]
|
||||||
binding.itemCompactRelation.text = character.role + " "
|
binding.itemCompactRelation.text = character.role + " "
|
||||||
binding.itemCompactImage.loadImage(character.image)
|
binding.itemCompactImage.loadImage(character.image)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -15,21 +16,25 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistMutations
|
||||||
import ani.dantotsu.databinding.ActivityCharacterBinding
|
import ani.dantotsu.databinding.ActivityCharacterBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
||||||
@@ -38,22 +43,21 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
private val model: OtherDetailsViewModel by viewModels()
|
private val model: OtherDetailsViewModel by viewModels()
|
||||||
private lateinit var character: Character
|
private lateinit var character: Character
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor =
|
||||||
ContextCompat.getColor(this, R.color.status)
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
|
|
||||||
val banner =
|
val banner =
|
||||||
if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen
|
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen
|
||||||
|
|
||||||
banner.updateLayoutParams { height += statusBarHeight }
|
banner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
@@ -77,7 +81,39 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
character.image
|
character.image
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val link = "https://anilist.co/character/${character.id}"
|
||||||
|
binding.characterShare.setOnClickListener {
|
||||||
|
val i = Intent(Intent.ACTION_SEND)
|
||||||
|
i.type = "text/plain"
|
||||||
|
i.putExtra(Intent.EXTRA_TEXT, link)
|
||||||
|
startActivity(Intent.createChooser(i, character.name))
|
||||||
|
}
|
||||||
|
binding.characterShare.setOnLongClickListener {
|
||||||
|
openLinkInBrowser(link)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
character.isFav = Anilist.query.isUserFav(AnilistMutations.FavType.CHARACTER, character.id)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (character.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.characterFav.setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (Anilist.mutation.toggleFav(AnilistMutations.FavType.CHARACTER, character.id)) {
|
||||||
|
character.isFav = !character.isFav
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (character.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("Failed to toggle favorite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
model.getCharacter().observe(this) {
|
model.getCharacter().observe(this) {
|
||||||
if (it != null && !loaded) {
|
if (it != null && !loaded) {
|
||||||
character = it
|
character = it
|
||||||
@@ -136,18 +172,16 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
|
|
||||||
binding.characterCover.visibility =
|
binding.characterCover.visibility =
|
||||||
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
|
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
|
||||||
if (percentage >= percent && !isCollapsed) {
|
if (percentage >= percent && !isCollapsed) {
|
||||||
isCollapsed = true
|
isCollapsed = true
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
ContextCompat.getColor(this, R.color.nav_bg)
|
ContextCompat.getColor(this, R.color.nav_bg)
|
||||||
binding.characterAppBar.setBackgroundResource(R.color.nav_bg)
|
|
||||||
}
|
}
|
||||||
if (percentage <= percent && isCollapsed) {
|
if (percentage <= percent && isCollapsed) {
|
||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
ContextCompat.getColor(this, R.color.status)
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
binding.characterAppBar.setBackgroundResource(R.color.bg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
|
|||||||
binding.characterDesc.isTextSelectable
|
binding.characterDesc.isTextSelectable
|
||||||
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
.usePlugin(SpoilerPlugin()).build()
|
.usePlugin(SpoilerPlugin()).build()
|
||||||
markWon.setMarkdown(binding.characterDesc, desc)
|
markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.anilist.GenresViewModel
|
import ani.dantotsu.connections.anilist.GenresViewModel
|
||||||
import ani.dantotsu.databinding.ActivityGenreBinding
|
import ani.dantotsu.databinding.ActivityGenreBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -27,7 +27,7 @@ class GenreActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityGenreBinding.inflate(layoutInflater)
|
binding = ActivityGenreBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
@@ -54,7 +54,9 @@ class GenreActivity : AppCompatActivity() {
|
|||||||
GridLayoutManager(this, (screenWidth / 156f).toInt())
|
GridLayoutManager(this, (screenWidth / 156f).toInt())
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) {
|
model.loadGenres(
|
||||||
|
Anilist.genres ?: loadLocalGenres() ?: arrayListOf()
|
||||||
|
) {
|
||||||
MainScope().launch {
|
MainScope().launch {
|
||||||
adapter.addGenre(it)
|
adapter.addGenre(it)
|
||||||
}
|
}
|
||||||
@@ -62,4 +64,15 @@ class GenreActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadLocalGenres(): ArrayList<String>? {
|
||||||
|
val genres = PrefManager.getVal<Set<String>>(PrefName.GenresList)
|
||||||
|
.toMutableList()
|
||||||
|
return if (genres.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
//sort alphabetically
|
||||||
|
genres.sort().let { genres as ArrayList<String> }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,7 @@ data class Media(
|
|||||||
var endDate: FuzzyDate? = null,
|
var endDate: FuzzyDate? = null,
|
||||||
|
|
||||||
var characters: ArrayList<Character>? = null,
|
var characters: ArrayList<Character>? = null,
|
||||||
|
var staff: ArrayList<Author>? = null,
|
||||||
var prequel: Media? = null,
|
var prequel: Media? = null,
|
||||||
var sequel: Media? = null,
|
var sequel: Media? = null,
|
||||||
var relations: ArrayList<Media>? = null,
|
var relations: ArrayList<Media>? = null,
|
||||||
@@ -108,6 +109,7 @@ data class Media(
|
|||||||
this.userScore = mediaList.score?.toInt() ?: 0
|
this.userScore = mediaList.score?.toInt() ?: 0
|
||||||
this.userStatus = mediaList.status?.toString()
|
this.userStatus = mediaList.status?.toString()
|
||||||
this.userUpdatedAt = mediaList.updatedAt?.toLong()
|
this.userUpdatedAt = mediaList.updatedAt?.toLong()
|
||||||
|
this.genres = mediaList.media?.genres?.toMutableList() as? ArrayList<String>? ?: arrayListOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(mediaEdge: MediaEdge) : this(mediaEdge.node!!) {
|
constructor(mediaEdge: MediaEdge) : this(mediaEdge.node!!) {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import ani.dantotsu.databinding.ItemMediaCompactBinding
|
|||||||
import ani.dantotsu.databinding.ItemMediaLargeBinding
|
import ani.dantotsu.databinding.ItemMediaLargeBinding
|
||||||
import ani.dantotsu.databinding.ItemMediaPageBinding
|
import ani.dantotsu.databinding.ItemMediaPageBinding
|
||||||
import ani.dantotsu.databinding.ItemMediaPageSmallBinding
|
import ani.dantotsu.databinding.ItemMediaPageSmallBinding
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
@@ -42,11 +43,9 @@ class MediaAdaptor(
|
|||||||
private val activity: FragmentActivity,
|
private val activity: FragmentActivity,
|
||||||
private val matchParent: Boolean = false,
|
private val matchParent: Boolean = false,
|
||||||
private val viewPager: ViewPager2? = null,
|
private val viewPager: ViewPager2? = null,
|
||||||
|
private val fav: Boolean = false,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private val uiSettings =
|
|
||||||
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
0 -> MediaViewHolder(
|
0 -> MediaViewHolder(
|
||||||
@@ -91,7 +90,7 @@ class MediaAdaptor(
|
|||||||
when (type) {
|
when (type) {
|
||||||
0 -> {
|
0 -> {
|
||||||
val b = (holder as MediaViewHolder).binding
|
val b = (holder as MediaViewHolder).binding
|
||||||
setAnimation(activity, b.root, uiSettings)
|
setAnimation(activity, b.root)
|
||||||
val media = mediaList?.getOrNull(position)
|
val media = mediaList?.getOrNull(position)
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
@@ -130,16 +129,17 @@ class MediaAdaptor(
|
|||||||
)
|
)
|
||||||
b.itemCompactTotal.text = " | ${media.manga.totalChapters ?: "~"}"
|
b.itemCompactTotal.text = " | ${media.manga.totalChapters ?: "~"}"
|
||||||
}
|
}
|
||||||
|
b.itemCompactProgressContainer.visibility = if (fav) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
val b = (holder as MediaLargeViewHolder).binding
|
val b = (holder as MediaLargeViewHolder).binding
|
||||||
setAnimation(activity, b.root, uiSettings)
|
setAnimation(activity, b.root)
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
b.itemCompactBanner.loadImage(media.banner ?: media.cover)
|
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||||
b.itemCompactOngoing.visibility =
|
b.itemCompactOngoing.visibility =
|
||||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
@@ -178,23 +178,16 @@ class MediaAdaptor(
|
|||||||
val b = (holder as MediaPageViewHolder).binding
|
val b = (holder as MediaPageViewHolder).binding
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
|
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
if (uiSettings.bannerAnimations)
|
if (bannerAnimations)
|
||||||
b.itemCompactBanner.setTransitionGenerator(
|
b.itemCompactBanner.setTransitionGenerator(
|
||||||
RandomTransitionGenerator(
|
RandomTransitionGenerator(
|
||||||
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
|
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed)) as Float)).toLong(),
|
||||||
AccelerateDecelerateInterpolator()
|
AccelerateDecelerateInterpolator()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val banner =
|
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||||
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
|
||||||
val context = b.itemCompactBanner.context
|
|
||||||
if (!(context as Activity).isDestroyed)
|
|
||||||
Glide.with(context as Context)
|
|
||||||
.load(GlideUrl(media.banner ?: media.cover))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
|
||||||
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
|
||||||
.into(banner)
|
|
||||||
b.itemCompactOngoing.visibility =
|
b.itemCompactOngoing.visibility =
|
||||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
@@ -234,23 +227,16 @@ class MediaAdaptor(
|
|||||||
val b = (holder as MediaPageSmallViewHolder).binding
|
val b = (holder as MediaPageSmallViewHolder).binding
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
|
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
if (uiSettings.bannerAnimations)
|
if (bannerAnimations)
|
||||||
b.itemCompactBanner.setTransitionGenerator(
|
b.itemCompactBanner.setTransitionGenerator(
|
||||||
RandomTransitionGenerator(
|
RandomTransitionGenerator(
|
||||||
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
|
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(),
|
||||||
AccelerateDecelerateInterpolator()
|
AccelerateDecelerateInterpolator()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val banner =
|
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||||
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
|
||||||
val context = b.itemCompactBanner.context
|
|
||||||
if (!(context as Activity).isDestroyed)
|
|
||||||
Glide.with(context as Context)
|
|
||||||
.load(GlideUrl(media.banner ?: media.cover))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
|
||||||
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
|
||||||
.into(banner)
|
|
||||||
b.itemCompactOngoing.visibility =
|
b.itemCompactOngoing.visibility =
|
||||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
@@ -396,10 +382,8 @@ class MediaAdaptor(
|
|||||||
if (itemCompactImage != null) {
|
if (itemCompactImage != null) {
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
activity,
|
activity,
|
||||||
Pair.create(
|
itemCompactImage,
|
||||||
itemCompactImage,
|
ViewCompat.getTransitionName(itemCompactImage)!!
|
||||||
ViewCompat.getTransitionName(activity.findViewById(R.id.itemCompactImage))!!
|
|
||||||
),
|
|
||||||
).toBundle()
|
).toBundle()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package ani.dantotsu.media
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@@ -11,7 +12,9 @@ import android.view.GestureDetector
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -19,6 +22,7 @@ import androidx.appcompat.content.res.AppCompatResources
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.text.bold
|
import androidx.core.text.bold
|
||||||
import androidx.core.text.color
|
import androidx.core.text.color
|
||||||
|
import androidx.core.view.marginBottom
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
@@ -26,46 +30,47 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import ani.dantotsu.CustomBottomNavBar
|
|
||||||
import ani.dantotsu.GesturesListener
|
import ani.dantotsu.GesturesListener
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.ZoomOutPageTransformer
|
import ani.dantotsu.ZoomOutPageTransformer
|
||||||
|
import ani.dantotsu.blurImage
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.copyToClipboard
|
import ani.dantotsu.copyToClipboard
|
||||||
import ani.dantotsu.databinding.ActivityMediaBinding
|
import ani.dantotsu.databinding.ActivityMediaBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||||
|
import ani.dantotsu.media.comments.CommentsFragment
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment
|
import ani.dantotsu.media.manga.MangaReadFragment
|
||||||
import ani.dantotsu.media.novel.NovelReadFragment
|
import ani.dantotsu.media.novel.NovelReadFragment
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.others.AndroidBug5497Workaround
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
||||||
|
|
||||||
private lateinit var binding: ActivityMediaBinding
|
lateinit var binding: ActivityMediaBinding
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
private val model: MediaDetailsViewModel by viewModels()
|
private val model: MediaDetailsViewModel by viewModels()
|
||||||
private lateinit var tabLayout: NavigationBarView
|
lateinit var tabLayout: TripleNavAdapter
|
||||||
private lateinit var uiSettings: UserInterfaceSettings
|
|
||||||
var selected = 0
|
var selected = 0
|
||||||
var anime = true
|
var anime = true
|
||||||
private var adult = false
|
private var adult = false
|
||||||
@@ -74,6 +79,15 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia()
|
var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia()
|
||||||
|
val id = intent.getIntExtra("mediaId", -1)
|
||||||
|
if (id != -1) {
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
media =
|
||||||
|
Anilist.query.getMedia(id, false) ?: emptyMedia()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (media.name == "No media found") {
|
if (media.name == "No media found") {
|
||||||
snackString(media.name)
|
snackString(media.name)
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
@@ -87,21 +101,31 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||||
|
|
||||||
|
val isVertical = resources.configuration.orientation
|
||||||
//Ui init
|
//Ui init
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||||
|
val oldMargin = binding.mediaViewPager.marginBottom
|
||||||
|
AndroidBug5497Workaround.assistActivity(this) {
|
||||||
|
if (it) {
|
||||||
|
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = 0
|
||||||
|
}
|
||||||
|
binding.mediaTabContainer.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = oldMargin
|
||||||
|
}
|
||||||
|
binding.mediaTabContainer.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
binding.incognito.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
binding.incognito.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
binding.mediaCollapsing.minimumHeight = statusBarHeight
|
binding.mediaCollapsing.minimumHeight = statusBarHeight
|
||||||
|
|
||||||
if (binding.mediaTab is CustomBottomNavBar) binding.mediaTab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
bottomMargin = navBarHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.mediaTitle.isSelected = true
|
binding.mediaTitle.isSelected = true
|
||||||
|
|
||||||
mMaxScrollSize = binding.mediaAppBar.totalScrollRange
|
mMaxScrollSize = binding.mediaAppBar.totalScrollRange
|
||||||
@@ -111,20 +135,21 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiSettings.bannerAnimations) {
|
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
|
||||||
|
if (bannerAnimations) {
|
||||||
val adi = AccelerateDecelerateInterpolator()
|
val adi = AccelerateDecelerateInterpolator()
|
||||||
val generator = RandomTransitionGenerator(
|
val generator = RandomTransitionGenerator(
|
||||||
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
|
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(),
|
||||||
adi
|
adi
|
||||||
)
|
)
|
||||||
binding.mediaBanner.setTransitionGenerator(generator)
|
binding.mediaBanner.setTransitionGenerator(generator)
|
||||||
}
|
}
|
||||||
val banner =
|
val banner =
|
||||||
if (uiSettings.bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
|
if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
|
||||||
val viewPager = binding.mediaViewPager
|
val viewPager = binding.mediaViewPager
|
||||||
tabLayout = binding.mediaTab as NavigationBarView
|
//tabLayout = binding.mediaTab as AnimatedBottomBar
|
||||||
viewPager.isUserInputEnabled = false
|
viewPager.isUserInputEnabled = false
|
||||||
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
viewPager.setPageTransformer(ZoomOutPageTransformer())
|
||||||
|
|
||||||
|
|
||||||
val isDownload = intent.getBooleanExtra("download", false)
|
val isDownload = intent.getBooleanExtra("download", false)
|
||||||
@@ -138,10 +163,11 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
media.cover
|
media.cover
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
banner.loadImage(media.banner ?: media.cover, 400)
|
|
||||||
|
blurImage(banner, media.banner ?: media.cover)
|
||||||
val gestureDetector = GestureDetector(this, object : GesturesListener() {
|
val gestureDetector = GestureDetector(this, object : GesturesListener() {
|
||||||
override fun onDoubleClick(event: MotionEvent) {
|
override fun onDoubleClick(event: MotionEvent) {
|
||||||
if (!uiSettings.bannerAnimations)
|
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean))
|
||||||
snackString(getString(R.string.enable_banner_animations))
|
snackString(getString(R.string.enable_banner_animations))
|
||||||
else {
|
else {
|
||||||
binding.mediaBanner.restart()
|
binding.mediaBanner.restart()
|
||||||
@@ -159,11 +185,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true }
|
banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true }
|
||||||
if (this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
.getBoolean("incognito", false)) {
|
|
||||||
binding.mediaTitle.text = " ${media.userPreferredName}"
|
binding.mediaTitle.text = " ${media.userPreferredName}"
|
||||||
binding.incognito.visibility = View.VISIBLE
|
binding.incognito.visibility = View.VISIBLE
|
||||||
}else {
|
} else {
|
||||||
binding.mediaTitle.text = media.userPreferredName
|
binding.mediaTitle.text = media.userPreferredName
|
||||||
}
|
}
|
||||||
binding.mediaTitle.setOnLongClickListener {
|
binding.mediaTitle.setOnLongClickListener {
|
||||||
@@ -284,7 +309,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
} else snackString(getString(R.string.please_login_anilist))
|
} else snackString(getString(R.string.please_login_anilist))
|
||||||
}
|
}
|
||||||
binding.mediaAddToList.setOnLongClickListener {
|
binding.mediaAddToList.setOnLongClickListener {
|
||||||
saveData("${media.id}_progressDialog", true)
|
PrefManager.setCustomVal(
|
||||||
|
"${media.id}_progressDialog",
|
||||||
|
true,
|
||||||
|
)
|
||||||
snackString(getString(R.string.auto_update_reset))
|
snackString(getString(R.string.auto_update_reset))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -314,49 +342,54 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
progress()
|
progress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tabLayout = TripleNavAdapter(
|
||||||
|
binding.mediaTab1,
|
||||||
|
binding.mediaTab2,
|
||||||
|
binding.mediaTab3,
|
||||||
|
media.anime != null,
|
||||||
|
media.format ?: "",
|
||||||
|
isVertical == 1
|
||||||
|
)
|
||||||
adult = media.isAdult
|
adult = media.isAdult
|
||||||
tabLayout.menu.clear()
|
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
viewPager.adapter =
|
viewPager.adapter =
|
||||||
ViewPagerAdapter(supportFragmentManager, lifecycle, SupportedMedia.ANIME)
|
ViewPagerAdapter(supportFragmentManager, lifecycle, SupportedMedia.ANIME, media, intent.getIntExtra("commentId", -1))
|
||||||
tabLayout.inflateMenu(R.menu.anime_menu_detail)
|
|
||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
viewPager.adapter = ViewPagerAdapter(
|
viewPager.adapter = ViewPagerAdapter(
|
||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
lifecycle,
|
lifecycle,
|
||||||
if (media.format == "NOVEL") SupportedMedia.NOVEL else SupportedMedia.MANGA
|
if (media.format == "NOVEL") SupportedMedia.NOVEL else SupportedMedia.MANGA,
|
||||||
|
media,
|
||||||
|
intent.getIntExtra("commentId", -1)
|
||||||
)
|
)
|
||||||
if (media.format == "NOVEL") {
|
|
||||||
tabLayout.inflateMenu(R.menu.novel_menu_detail)
|
|
||||||
} else {
|
|
||||||
tabLayout.inflateMenu(R.menu.manga_menu_detail)
|
|
||||||
}
|
|
||||||
anime = false
|
anime = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
selected = media.selected!!.window
|
selected = media.selected!!.window
|
||||||
binding.mediaTitle.translationX = -screenWidth
|
binding.mediaTitle.translationX = -screenWidth
|
||||||
tabLayout.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
tabLayout.setOnItemSelectedListener { item ->
|
tabLayout.selectionListener = { selected, newId ->
|
||||||
selectFromID(item.itemId)
|
binding.commentInputLayout.visibility = if (selected == 2) View.VISIBLE else View.GONE
|
||||||
|
this.selected = selected
|
||||||
|
selectFromID(newId)
|
||||||
viewPager.setCurrentItem(selected, false)
|
viewPager.setCurrentItem(selected, false)
|
||||||
val sel = model.loadSelected(media, isDownload)
|
val sel = model.loadSelected(media, isDownload)
|
||||||
sel.window = selected
|
sel.window = selected
|
||||||
model.saveSelected(media.id, sel, this)
|
model.saveSelected(media.id, sel)
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
tabLayout.selectTab(selected)
|
||||||
|
selectFromID(tabLayout.selected)
|
||||||
tabLayout.selectedItemId = idFromSelect()
|
|
||||||
viewPager.setCurrentItem(selected, false)
|
viewPager.setCurrentItem(selected, false)
|
||||||
|
|
||||||
if (model.continueMedia == null && media.cameFromContinue) {
|
if (model.continueMedia == null && media.cameFromContinue) {
|
||||||
model.continueMedia = loadData("continue_media") ?: true
|
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
|
||||||
selected = 1
|
selected = 1
|
||||||
}
|
}
|
||||||
|
val frag = intent.getStringExtra("FRAGMENT_TO_LOAD")
|
||||||
|
if (frag != null) {
|
||||||
|
selected = 2
|
||||||
|
}
|
||||||
|
|
||||||
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) }
|
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) }
|
||||||
live.observe(this) {
|
live.observe(this) {
|
||||||
@@ -369,7 +402,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun selectFromID(id: Int) {
|
private fun selectFromID(id: Int) {
|
||||||
when (id) {
|
when (id) {
|
||||||
R.id.info -> {
|
R.id.info -> {
|
||||||
@@ -379,6 +411,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
R.id.watch, R.id.read -> {
|
R.id.watch, R.id.read -> {
|
||||||
selected = 1
|
selected = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.comment -> {
|
||||||
|
selected = 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,16 +422,20 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
if (anime) when (selected) {
|
if (anime) when (selected) {
|
||||||
0 -> return R.id.info
|
0 -> return R.id.info
|
||||||
1 -> return R.id.watch
|
1 -> return R.id.watch
|
||||||
|
2 -> return R.id.comment
|
||||||
}
|
}
|
||||||
else when (selected) {
|
else when (selected) {
|
||||||
0 -> return R.id.info
|
0 -> return R.id.info
|
||||||
1 -> return R.id.read
|
1 -> return R.id.read
|
||||||
|
2 -> return R.id.comment
|
||||||
}
|
}
|
||||||
return R.id.info
|
return R.id.info
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
tabLayout.selectedItemId = idFromSelect()
|
if (this::tabLayout.isInitialized) {
|
||||||
|
tabLayout.selectTab(selected)
|
||||||
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,19 +447,30 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
private class ViewPagerAdapter(
|
private class ViewPagerAdapter(
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
lifecycle: Lifecycle,
|
lifecycle: Lifecycle,
|
||||||
private val media: SupportedMedia
|
private val mediaType: SupportedMedia,
|
||||||
|
private val media: Media,
|
||||||
|
private val commentId: Int
|
||||||
) :
|
) :
|
||||||
FragmentStateAdapter(fragmentManager, lifecycle) {
|
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
|
|
||||||
override fun getItemCount(): Int = 2
|
override fun getItemCount(): Int = 3
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = when (position) {
|
override fun createFragment(position: Int): Fragment = when (position) {
|
||||||
0 -> MediaInfoFragment()
|
0 -> MediaInfoFragment()
|
||||||
1 -> when (media) {
|
1 -> when (mediaType) {
|
||||||
SupportedMedia.ANIME -> AnimeWatchFragment()
|
SupportedMedia.ANIME -> AnimeWatchFragment()
|
||||||
SupportedMedia.MANGA -> MangaReadFragment()
|
SupportedMedia.MANGA -> MangaReadFragment()
|
||||||
SupportedMedia.NOVEL -> NovelReadFragment()
|
SupportedMedia.NOVEL -> NovelReadFragment()
|
||||||
}
|
}
|
||||||
|
2 -> {
|
||||||
|
val fragment = CommentsFragment()
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putInt("mediaId", media.id)
|
||||||
|
bundle.putString("mediaName", media.mainName())
|
||||||
|
if (commentId != -1) bundle.putInt("commentId", commentId)
|
||||||
|
fragment.arguments = bundle
|
||||||
|
fragment
|
||||||
|
}
|
||||||
|
|
||||||
else -> MediaInfoFragment()
|
else -> MediaInfoFragment()
|
||||||
}
|
}
|
||||||
@@ -437,7 +488,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
binding.mediaCover.visibility =
|
binding.mediaCover.visibility =
|
||||||
if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE
|
if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
val duration = (200 * uiSettings.animationSpeed).toLong()
|
val duration = (200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong()
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
this@MediaDetailsActivity.theme.resolveAttribute(
|
this@MediaDetailsActivity.theme.resolveAttribute(
|
||||||
com.google.android.material.R.attr.colorSecondary,
|
com.google.android.material.R.attr.colorSecondary,
|
||||||
@@ -467,7 +518,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
.start()
|
.start()
|
||||||
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f)
|
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f)
|
||||||
.setDuration(duration).start()
|
.setDuration(duration).start()
|
||||||
if (uiSettings.bannerAnimations) binding.mediaBanner.resume()
|
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.mediaBanner.resume()
|
||||||
}
|
}
|
||||||
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(
|
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(
|
||||||
false
|
false
|
||||||
@@ -483,6 +534,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
private val c1: Int,
|
private val c1: Int,
|
||||||
private val c2: Int,
|
private val c2: Int,
|
||||||
var clicked: Boolean,
|
var clicked: Boolean,
|
||||||
|
needsInitialClick: Boolean = false,
|
||||||
callback: suspend (Boolean) -> (Unit)
|
callback: suspend (Boolean) -> (Unit)
|
||||||
) {
|
) {
|
||||||
private var disabled = false
|
private var disabled = false
|
||||||
@@ -491,9 +543,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
enabled(true)
|
enabled(true)
|
||||||
scope.launch {
|
if (needsInitialClick) {
|
||||||
delay(100) //TODO: a listener would be better
|
scope.launch {
|
||||||
clicked()
|
clicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
image.setOnClickListener {
|
image.setOnClickListener {
|
||||||
if (pressable && !disabled) {
|
if (pressable && !disabled) {
|
||||||
@@ -549,5 +602,4 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
companion object {
|
companion object {
|
||||||
var mediaSingleton: Media? = null
|
var mediaSingleton: Media? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
@@ -11,8 +9,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.logger
|
|
||||||
import ani.dantotsu.media.anime.Episode
|
import ani.dantotsu.media.anime.Episode
|
||||||
import ani.dantotsu.media.anime.SelectorDialogFragment
|
import ani.dantotsu.media.anime.SelectorDialogFragment
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
@@ -28,35 +25,32 @@ import ani.dantotsu.parsers.NovelSources
|
|||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class MediaDetailsViewModel : ViewModel() {
|
class MediaDetailsViewModel : ViewModel() {
|
||||||
val scrolledToTop = MutableLiveData(true)
|
val scrolledToTop = MutableLiveData(true)
|
||||||
|
|
||||||
fun saveSelected(id: Int, data: Selected, activity: Activity? = null) {
|
fun saveSelected(id: Int, data: Selected) {
|
||||||
saveData("$id-select", data, activity)
|
PrefManager.setCustomVal("Selected-$id", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadSelected(media: Media, isDownload: Boolean = false): Selected {
|
fun loadSelected(media: Media, isDownload: Boolean = false): Selected {
|
||||||
val sharedPreferences = Injekt.get<SharedPreferences>()
|
val data =
|
||||||
val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
|
PrefManager.getNullableCustomVal("Selected-${media.id}", null, Selected::class.java)
|
||||||
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
|
?: Selected().let {
|
||||||
true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
|
it.sourceIndex = 0
|
||||||
else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0)
|
it.preferDub = PrefManager.getVal(PrefName.SettingsPreferDub)
|
||||||
}
|
saveSelected(media.id, it)
|
||||||
it.preferDub = loadData("settings_prefer_dub") ?: false
|
it
|
||||||
saveSelected(media.id, it)
|
}
|
||||||
it
|
|
||||||
}
|
|
||||||
if (isDownload) {
|
if (isDownload) {
|
||||||
data.sourceIndex = if (media.anime != null) {
|
data.sourceIndex = if (media.anime != null) {
|
||||||
AnimeSources.list.size - 1
|
AnimeSources.list.size - 1
|
||||||
@@ -229,7 +223,7 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setEpisode(ep: Episode?, who: String) {
|
fun setEpisode(ep: Episode?, who: String) {
|
||||||
logger("set episode ${ep?.number} - $who", false)
|
Logger.log("set episode ${ep?.number} - $who")
|
||||||
episode.postValue(ep)
|
episode.postValue(ep)
|
||||||
MainScope().launch(Dispatchers.Main) {
|
MainScope().launch(Dispatchers.Main) {
|
||||||
episode.value = null
|
episode.value = null
|
||||||
@@ -276,7 +270,7 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
mangaChapters
|
mangaChapters
|
||||||
|
|
||||||
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
|
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
|
||||||
logger("Loading Manga Chapters : $mangaLoaded")
|
Logger.log("Loading Manga Chapters : $mangaLoaded")
|
||||||
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
|
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
|
||||||
mangaLoaded[i] =
|
mangaLoaded[i] =
|
||||||
mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package ani.dantotsu.media
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
@@ -26,6 +26,8 @@ import ani.dantotsu.*
|
|||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.GenresViewModel
|
import ani.dantotsu.connections.anilist.GenresViewModel
|
||||||
import ani.dantotsu.databinding.*
|
import ani.dantotsu.databinding.*
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -60,7 +62,7 @@ class MediaInfoFragment : Fragment() {
|
|||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val model: MediaDetailsViewModel by activityViewModels()
|
val model: MediaDetailsViewModel by activityViewModels()
|
||||||
val offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("offlineMode", false) || !isOnline(requireContext())
|
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||||
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||||
binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE
|
binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE
|
||||||
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
|
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
|
||||||
@@ -72,6 +74,8 @@ class MediaInfoFragment : Fragment() {
|
|||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
if (media != null && !loaded) {
|
if (media != null && !loaded) {
|
||||||
loaded = true
|
loaded = true
|
||||||
|
|
||||||
|
|
||||||
binding.mediaInfoProgressBar.visibility = View.GONE
|
binding.mediaInfoProgressBar.visibility = View.GONE
|
||||||
binding.mediaInfoContainer.visibility = View.VISIBLE
|
binding.mediaInfoContainer.visibility = View.VISIBLE
|
||||||
binding.mediaInfoName.text = "\t\t\t" + (media.name ?: media.nameRomaji)
|
binding.mediaInfoName.text = "\t\t\t" + (media.name ?: media.nameRomaji)
|
||||||
@@ -94,8 +98,31 @@ class MediaInfoFragment : Fragment() {
|
|||||||
binding.mediaInfoStart.text = media.startDate?.toString() ?: "??"
|
binding.mediaInfoStart.text = media.startDate?.toString() ?: "??"
|
||||||
binding.mediaInfoEnd.text = media.endDate?.toString() ?: "??"
|
binding.mediaInfoEnd.text = media.endDate?.toString() ?: "??"
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
binding.mediaInfoDuration.text =
|
val episodeDuration = media.anime.episodeDuration
|
||||||
if (media.anime.episodeDuration != null) media.anime.episodeDuration.toString() else "??"
|
|
||||||
|
binding.mediaInfoDuration.text = when {
|
||||||
|
episodeDuration != null -> {
|
||||||
|
val hours = episodeDuration / 60
|
||||||
|
val minutes = episodeDuration % 60
|
||||||
|
|
||||||
|
val formattedDuration = buildString {
|
||||||
|
if (hours > 0) {
|
||||||
|
append("$hours hour")
|
||||||
|
if (hours > 1) append("s")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes > 0) {
|
||||||
|
if (hours > 0) append(", ")
|
||||||
|
append("$minutes min")
|
||||||
|
if (minutes > 1) append("s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> "??"
|
||||||
|
}
|
||||||
binding.mediaInfoDurationContainer.visibility = View.VISIBLE
|
binding.mediaInfoDurationContainer.visibility = View.VISIBLE
|
||||||
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
|
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
|
||||||
binding.mediaInfoSeason.text =
|
binding.mediaInfoSeason.text =
|
||||||
@@ -384,23 +411,6 @@ class MediaInfoFragment : Fragment() {
|
|||||||
parent.addView(bind.root)
|
parent.addView(bind.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!media.characters.isNullOrEmpty() && !offline) {
|
|
||||||
val bind = ItemTitleRecyclerBinding.inflate(
|
|
||||||
LayoutInflater.from(context),
|
|
||||||
parent,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
bind.itemTitle.setText(R.string.characters)
|
|
||||||
bind.itemRecycler.adapter =
|
|
||||||
CharacterAdapter(media.characters!!)
|
|
||||||
bind.itemRecycler.layoutManager = LinearLayoutManager(
|
|
||||||
requireContext(),
|
|
||||||
LinearLayoutManager.HORIZONTAL,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
parent.addView(bind.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!media.relations.isNullOrEmpty() && !offline) {
|
if (!media.relations.isNullOrEmpty() && !offline) {
|
||||||
if (media.sequel != null || media.prequel != null) {
|
if (media.sequel != null || media.prequel != null) {
|
||||||
val bind = ItemQuelsBinding.inflate(
|
val bind = ItemQuelsBinding.inflate(
|
||||||
@@ -463,8 +473,39 @@ class MediaInfoFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
parent.addView(bindi.root)
|
parent.addView(bindi.root)
|
||||||
}
|
}
|
||||||
|
if (!media.characters.isNullOrEmpty() && !offline) {
|
||||||
if (!media.recommendations.isNullOrEmpty() && !offline ) {
|
val bind = ItemTitleRecyclerBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
bind.itemTitle.setText(R.string.characters)
|
||||||
|
bind.itemRecycler.adapter =
|
||||||
|
CharacterAdapter(media.characters!!)
|
||||||
|
bind.itemRecycler.layoutManager = LinearLayoutManager(
|
||||||
|
requireContext(),
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
parent.addView(bind.root)
|
||||||
|
}
|
||||||
|
if (!media.staff.isNullOrEmpty() && !offline) {
|
||||||
|
val bind = ItemTitleRecyclerBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
bind.itemTitle.setText(R.string.staff)
|
||||||
|
bind.itemRecycler.adapter =
|
||||||
|
AuthorAdapter(media.staff!!)
|
||||||
|
bind.itemRecycler.layoutManager = LinearLayoutManager(
|
||||||
|
requireContext(),
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
parent.addView(bind.root)
|
||||||
|
}
|
||||||
|
if (!media.recommendations.isNullOrEmpty() && !offline) {
|
||||||
val bind = ItemTitleRecyclerBinding.inflate(
|
val bind = ItemTitleRecyclerBinding.inflate(
|
||||||
LayoutInflater.from(context),
|
LayoutInflater.from(context),
|
||||||
parent,
|
parent,
|
||||||
|
|||||||
@@ -254,20 +254,28 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.mediaListDelete.setOnClickListener {
|
binding.mediaListDelete.setOnClickListener {
|
||||||
val id = media!!.userListId
|
var id = media!!.userListId
|
||||||
if (id != null) {
|
scope.launch {
|
||||||
scope.launch {
|
withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
if (id != null) {
|
||||||
Anilist.mutation.deleteList(id)
|
Anilist.mutation.deleteList(id!!)
|
||||||
MAL.query.deleteList(media?.anime != null, media?.idMAL)
|
MAL.query.deleteList(media?.anime != null, media?.idMAL)
|
||||||
|
} else {
|
||||||
|
val profile = Anilist.query.userMediaDetails(media!!)
|
||||||
|
profile.userListId?.let { listId ->
|
||||||
|
id = listId
|
||||||
|
Anilist.mutation.deleteList(listId)
|
||||||
|
MAL.query.deleteList(media?.anime != null, media?.idMAL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Refresh.all()
|
|
||||||
snackString(getString(R.string.deleted_from_list))
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
Refresh.all()
|
||||||
|
snackString(getString(R.string.deleted_from_list))
|
||||||
|
dismissAllowingStateLoss()
|
||||||
} else {
|
} else {
|
||||||
snackString(getString(R.string.no_list_id))
|
snackString(getString(R.string.no_list_id))
|
||||||
Refresh.all()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,40 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||||
val scope = viewLifecycleOwner.lifecycleScope
|
val scope = viewLifecycleOwner.lifecycleScope
|
||||||
|
binding.mediaListDelete.setOnClickListener {
|
||||||
|
var id = media.userListId
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (id != null) {
|
||||||
|
try {
|
||||||
|
Anilist.mutation.deleteList(id!!)
|
||||||
|
MAL.query.deleteList(media.anime != null, media.idMAL)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
snackString("Failed to delete because of... ${e.message}")
|
||||||
|
}
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val profile = Anilist.query.userMediaDetails(media)
|
||||||
|
profile.userListId?.let { listId ->
|
||||||
|
id = listId
|
||||||
|
Anilist.mutation.deleteList(listId)
|
||||||
|
MAL.query.deleteList(media.anime != null, media.idMAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (id != null) {
|
||||||
|
Refresh.all()
|
||||||
|
snackString(getString(R.string.deleted_from_list))
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
} else {
|
||||||
|
snackString(getString(R.string.no_list_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.mediaListProgressBar.visibility = View.GONE
|
binding.mediaListProgressBar.visibility = View.GONE
|
||||||
binding.mediaListLayout.visibility = View.VISIBLE
|
binding.mediaListLayout.visibility = View.VISIBLE
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.anilist.AnilistSearch
|
import ani.dantotsu.connections.anilist.AnilistSearch
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
import ani.dantotsu.databinding.ActivitySearchBinding
|
import ani.dantotsu.databinding.ActivitySearchBinding
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -33,13 +34,14 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
private lateinit var mediaAdaptor: MediaAdaptor
|
private lateinit var mediaAdaptor: MediaAdaptor
|
||||||
private lateinit var progressAdapter: ProgressAdapter
|
private lateinit var progressAdapter: ProgressAdapter
|
||||||
private lateinit var concatAdapter: ConcatAdapter
|
private lateinit var concatAdapter: ConcatAdapter
|
||||||
|
private lateinit var headerAdaptor: SearchAdapter
|
||||||
|
|
||||||
lateinit var result: SearchResults
|
lateinit var result: SearchResults
|
||||||
lateinit var updateChips: (() -> Unit)
|
lateinit var updateChips: (() -> Unit)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivitySearchBinding.inflate(layoutInflater)
|
binding = ActivitySearchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
@@ -51,7 +53,7 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
bottom = navBarHeight + 80f.px
|
bottom = navBarHeight + 80f.px
|
||||||
)
|
)
|
||||||
|
|
||||||
style = loadData<Int>("searchStyle") ?: 0
|
style = PrefManager.getVal(PrefName.SearchStyle)
|
||||||
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
||||||
if (!listOnly!!) listOnly = null
|
if (!listOnly!!) listOnly = null
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
progressAdapter = ProgressAdapter(searched = model.searched)
|
progressAdapter = ProgressAdapter(searched = model.searched)
|
||||||
mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true)
|
mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true)
|
||||||
val headerAdaptor = SearchAdapter(this)
|
headerAdaptor = SearchAdapter(this, model.searchResults.type)
|
||||||
|
|
||||||
val gridSize = (screenWidth / 120f).toInt()
|
val gridSize = (screenWidth / 120f).toInt()
|
||||||
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
||||||
@@ -154,9 +156,18 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun emptyMediaAdapter() {
|
||||||
|
searchTimer.cancel()
|
||||||
|
searchTimer.purge()
|
||||||
|
mediaAdaptor.notifyItemRangeRemoved(0, model.searchResults.results.size)
|
||||||
|
model.searchResults.results.clear()
|
||||||
|
progressAdapter.bar?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
private var searchTimer = Timer()
|
private var searchTimer = Timer()
|
||||||
private var loading = false
|
private var loading = false
|
||||||
fun search() {
|
fun search() {
|
||||||
|
headerAdaptor.setHistoryVisibility(false)
|
||||||
val size = model.searchResults.results.size
|
val size = model.searchResults.results.size
|
||||||
model.searchResults.results.clear()
|
model.searchResults.results.clear()
|
||||||
binding.searchRecyclerView.post {
|
binding.searchRecyclerView.post {
|
||||||
@@ -188,6 +199,9 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
var state: Parcelable? = null
|
var state: Parcelable? = null
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
if (this::headerAdaptor.isInitialized) {
|
||||||
|
headerAdaptor.addHistory()
|
||||||
|
}
|
||||||
super.onPause()
|
super.onPause()
|
||||||
state = binding.searchRecyclerView.layoutManager?.onSaveInstanceState()
|
state = binding.searchRecyclerView.layoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
@@ -9,29 +9,40 @@ import android.view.LayoutInflater
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
|
import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
|
||||||
import ani.dantotsu.App.Companion.context
|
import ani.dantotsu.App.Companion.context
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox.*
|
import com.google.android.material.checkbox.MaterialCheckBox.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class SearchAdapter(private val activity: SearchActivity) :
|
class SearchAdapter(private val activity: SearchActivity, private val type: String) :
|
||||||
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
||||||
private val itemViewType = 6969
|
private val itemViewType = 6969
|
||||||
var search: Runnable? = null
|
var search: Runnable? = null
|
||||||
var requestFocus: Runnable? = null
|
var requestFocus: Runnable? = null
|
||||||
private var textWatcher: TextWatcher? = null
|
private var textWatcher: TextWatcher? = null
|
||||||
|
private lateinit var searchHistoryAdapter: SearchHistoryAdapter
|
||||||
|
private lateinit var binding: ItemSearchHeaderBinding
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
@@ -41,8 +52,13 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
binding = holder.binding
|
||||||
|
|
||||||
|
searchHistoryAdapter = SearchHistoryAdapter(type) {
|
||||||
|
binding.searchBarText.setText(it)
|
||||||
|
}
|
||||||
|
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
|
||||||
|
binding.searchHistoryList.adapter = searchHistoryAdapter
|
||||||
|
|
||||||
val imm: InputMethodManager =
|
val imm: InputMethodManager =
|
||||||
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
@@ -60,8 +76,7 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.searchBar.hint = activity.result.type
|
binding.searchBar.hint = activity.result.type
|
||||||
if (currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
?.getBoolean("incognito", false ) == true){
|
|
||||||
val startIconDrawableRes = R.drawable.ic_incognito_24
|
val startIconDrawableRes = R.drawable.ic_incognito_24
|
||||||
val startIconDrawable: Drawable? =
|
val startIconDrawable: Drawable? =
|
||||||
context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) }
|
context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) }
|
||||||
@@ -80,13 +95,16 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
binding.searchChipRecycler.adapter = SearchChipAdapter(activity).also {
|
binding.searchChipRecycler.adapter = SearchChipAdapter(activity).also {
|
||||||
activity.updateChips = { it.update() }
|
activity.updateChips = { it.update() }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchChipRecycler.layoutManager =
|
binding.searchChipRecycler.layoutManager =
|
||||||
LinearLayoutManager(binding.root.context, HORIZONTAL, false)
|
LinearLayoutManager(binding.root.context, HORIZONTAL, false)
|
||||||
|
|
||||||
binding.searchFilter.setOnClickListener {
|
binding.searchFilter.setOnClickListener {
|
||||||
SearchFilterBottomDialog.newInstance().show(activity.supportFragmentManager, "dialog")
|
SearchFilterBottomDialog.newInstance().show(activity.supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
binding.searchByImage.setOnClickListener {
|
||||||
|
activity.startActivity(Intent(activity, ImageSearchActivity::class.java))
|
||||||
|
}
|
||||||
fun searchTitle() {
|
fun searchTitle() {
|
||||||
activity.result.apply {
|
activity.result.apply {
|
||||||
search =
|
search =
|
||||||
@@ -94,6 +112,9 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
onList = listOnly
|
onList = listOnly
|
||||||
isAdult = adult
|
isAdult = adult
|
||||||
}
|
}
|
||||||
|
if (binding.searchBarText.text.toString().equals("hentai", true)) {
|
||||||
|
openLinkInBrowser("https://www.youtube.com/watch?v=GgJrEOo0QoA")
|
||||||
|
}
|
||||||
activity.search()
|
activity.search()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +124,18 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
searchTitle()
|
if (s.toString().isBlank()) {
|
||||||
|
activity.emptyMediaAdapter()
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
delay(200)
|
||||||
|
activity.runOnUiThread {
|
||||||
|
setHistoryVisibility(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHistoryVisibility(false)
|
||||||
|
searchTitle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.searchBarText.addTextChangedListener(textWatcher)
|
binding.searchBarText.addTextChangedListener(textWatcher)
|
||||||
@@ -126,14 +158,14 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
it.alpha = 1f
|
it.alpha = 1f
|
||||||
binding.searchResultList.alpha = 0.33f
|
binding.searchResultList.alpha = 0.33f
|
||||||
activity.style = 0
|
activity.style = 0
|
||||||
saveData("searchStyle", 0)
|
PrefManager.setVal(PrefName.SearchStyle, 0)
|
||||||
activity.recycler()
|
activity.recycler()
|
||||||
}
|
}
|
||||||
binding.searchResultList.setOnClickListener {
|
binding.searchResultList.setOnClickListener {
|
||||||
it.alpha = 1f
|
it.alpha = 1f
|
||||||
binding.searchResultGrid.alpha = 0.33f
|
binding.searchResultGrid.alpha = 0.33f
|
||||||
activity.style = 1
|
activity.style = 1
|
||||||
saveData("searchStyle", 1)
|
PrefManager.setVal(PrefName.SearchStyle, 1)
|
||||||
activity.recycler()
|
activity.recycler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +208,43 @@ class SearchAdapter(private val activity: SearchActivity) :
|
|||||||
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setHistoryVisibility(visible: Boolean) {
|
||||||
|
if (visible) {
|
||||||
|
binding.searchResultLayout.startAnimation(fadeOutAnimation())
|
||||||
|
binding.searchHistoryList.startAnimation(fadeInAnimation())
|
||||||
|
binding.searchResultLayout.visibility = View.GONE
|
||||||
|
binding.searchHistoryList.visibility = View.VISIBLE
|
||||||
|
binding.searchByImage.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
if (binding.searchResultLayout.visibility != View.VISIBLE) {
|
||||||
|
binding.searchResultLayout.startAnimation(fadeInAnimation())
|
||||||
|
binding.searchHistoryList.startAnimation(fadeOutAnimation())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchResultLayout.visibility = View.VISIBLE
|
||||||
|
binding.searchHistoryList.visibility = View.GONE
|
||||||
|
binding.searchByImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fadeInAnimation(): Animation {
|
||||||
|
return AlphaAnimation(0f, 1f).apply {
|
||||||
|
duration = 150
|
||||||
|
fillAfter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fadeOutAnimation(): Animation {
|
||||||
|
return AlphaAnimation(1f, 0f).apply {
|
||||||
|
duration = 150
|
||||||
|
fillAfter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun addHistory() {
|
||||||
|
searchHistoryAdapter.add(binding.searchBarText.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.databinding.BottomSheetSearchFilterBinding
|
import ani.dantotsu.databinding.BottomSheetSearchFilterBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetSearchFilterBinding? = null
|
private var _binding: BottomSheetSearchFilterBinding? = null
|
||||||
@@ -103,7 +104,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
(1970 until 2025).map { it.toString() }.reversed().toTypedArray()
|
(1970 until Calendar.getInstance().get(Calendar.YEAR) + 2).map { it.toString() }
|
||||||
|
.reversed().toTypedArray()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
100
app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt
Normal file
100
app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.ItemSearchHistoryBinding
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager.asLiveStringSet
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.settings.saving.SharedPreferenceStringSetLiveData
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class SearchHistoryAdapter(private val type: String, private val searchClicked: (String) -> Unit) :
|
||||||
|
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
|
||||||
|
DIFF_CALLBACK_INSTALLED
|
||||||
|
) {
|
||||||
|
private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null
|
||||||
|
private var searchHistory: MutableSet<String>? = null
|
||||||
|
private var historyType: PrefName = when (type.lowercase(Locale.ROOT)) {
|
||||||
|
"anime" -> PrefName.AnimeSearchHistory
|
||||||
|
"manga" -> PrefName.MangaSearchHistory
|
||||||
|
else -> throw IllegalArgumentException("Invalid type")
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
searchHistoryLiveData =
|
||||||
|
PrefManager.getLiveVal(historyType, mutableSetOf<String>()).asLiveStringSet()
|
||||||
|
searchHistoryLiveData?.observeForever {
|
||||||
|
searchHistory = it.toMutableSet()
|
||||||
|
submitList(searchHistory?.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(item: String) {
|
||||||
|
searchHistory?.remove(item)
|
||||||
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
|
submitList(searchHistory?.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(item: String) {
|
||||||
|
if (searchHistory?.contains(item) == true || item.isBlank()) return
|
||||||
|
if (PrefManager.getVal(PrefName.Incognito)) return
|
||||||
|
searchHistory?.add(item)
|
||||||
|
submitList(searchHistory?.toList())
|
||||||
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): SearchHistoryAdapter.SearchHistoryViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_search_history, parent, false)
|
||||||
|
return SearchHistoryViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: SearchHistoryAdapter.SearchHistoryViewHolder,
|
||||||
|
position: Int
|
||||||
|
) {
|
||||||
|
val item = getItem(position)
|
||||||
|
holder.binding.searchHistoryTextView.text = item
|
||||||
|
holder.binding.closeTextView.setOnClickListener {
|
||||||
|
val currentPosition = holder.bindingAdapterPosition
|
||||||
|
if (currentPosition >= itemCount || currentPosition < 0) return@setOnClickListener
|
||||||
|
remove(getItem(currentPosition))
|
||||||
|
}
|
||||||
|
holder.binding.searchHistoryTextView.setOnClickListener {
|
||||||
|
val currentPosition = holder.bindingAdapterPosition
|
||||||
|
if (currentPosition >= itemCount || currentPosition < 0) return@setOnClickListener
|
||||||
|
searchClicked(getItem(currentPosition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SearchHistoryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val binding = ItemSearchHistoryBinding.bind(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_INSTALLED = object : DiffUtil.ItemCallback<String>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: String,
|
||||||
|
newItem: String
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: String,
|
||||||
|
newItem: String
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ import ani.dantotsu.Refresh
|
|||||||
import ani.dantotsu.databinding.ActivityStudioBinding
|
import ani.dantotsu.databinding.ActivityStudioBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
@@ -36,7 +35,7 @@ class StudioActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityStudioBinding.inflate(layoutInflater)
|
binding = ActivityStudioBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|||||||
@@ -45,9 +45,18 @@ class SubtitleDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//actually downloads lol
|
//actually downloads lol
|
||||||
suspend fun downloadSubtitle(context: Context, url: String, downloadedType: DownloadedType) {
|
suspend fun downloadSubtitle(
|
||||||
|
context: Context,
|
||||||
|
url: String,
|
||||||
|
downloadedType: DownloadedType
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
val directory = DownloadsManager.getDirectory(context, downloadedType.type, downloadedType.title, downloadedType.chapter)
|
val directory = DownloadsManager.getDirectory(
|
||||||
|
context,
|
||||||
|
downloadedType.type,
|
||||||
|
downloadedType.title,
|
||||||
|
downloadedType.chapter
|
||||||
|
)
|
||||||
if (!directory.exists()) { //just in case
|
if (!directory.exists()) { //just in case
|
||||||
directory.mkdirs()
|
directory.mkdirs()
|
||||||
}
|
}
|
||||||
|
|||||||
136
app/src/main/java/ani/dantotsu/media/TripleNavAdapter.kt
Normal file
136
app/src/main/java/ani/dantotsu/media/TripleNavAdapter.kt
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
|
class TripleNavAdapter(
|
||||||
|
private val nav1: AnimatedBottomBar,
|
||||||
|
private val nav2: AnimatedBottomBar,
|
||||||
|
private val nav3: AnimatedBottomBar,
|
||||||
|
anime: Boolean,
|
||||||
|
format: String,
|
||||||
|
private val isScreenVertical: Boolean = false
|
||||||
|
) {
|
||||||
|
var selected: Int = 0
|
||||||
|
var selectionListener: ((Int, Int) -> Unit)? = null
|
||||||
|
init {
|
||||||
|
nav1.tabs.clear()
|
||||||
|
nav2.tabs.clear()
|
||||||
|
nav3.tabs.clear()
|
||||||
|
val infoTab = nav1.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info)
|
||||||
|
val watchTab = if (anime) {
|
||||||
|
nav2.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch)
|
||||||
|
} else if (format == "NOVEL") {
|
||||||
|
nav2.createTab(R.drawable.ic_round_book_24, R.string.read, R.id.read)
|
||||||
|
} else {
|
||||||
|
nav2.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read)
|
||||||
|
}
|
||||||
|
val commentTab = nav3.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
|
||||||
|
nav1.addTab(infoTab)
|
||||||
|
nav1.visibility = ViewGroup.VISIBLE
|
||||||
|
if (isScreenVertical) {
|
||||||
|
nav2.visibility = ViewGroup.GONE
|
||||||
|
nav3.visibility = ViewGroup.GONE
|
||||||
|
nav1.addTab(watchTab)
|
||||||
|
nav1.addTab(commentTab)
|
||||||
|
nav1.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
nav2.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
nav3.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nav1.indicatorColor = Color.TRANSPARENT
|
||||||
|
nav2.indicatorColor = Color.TRANSPARENT
|
||||||
|
nav3.indicatorColor = Color.TRANSPARENT
|
||||||
|
nav2.visibility = ViewGroup.VISIBLE
|
||||||
|
nav3.visibility = ViewGroup.VISIBLE
|
||||||
|
nav2.addTab(watchTab)
|
||||||
|
nav3.addTab(commentTab)
|
||||||
|
nav2.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||||
|
override fun onTabSelected(
|
||||||
|
lastIndex: Int,
|
||||||
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
|
newIndex: Int,
|
||||||
|
newTab: AnimatedBottomBar.Tab
|
||||||
|
) {
|
||||||
|
selected = 1
|
||||||
|
deselectOthers(selected)
|
||||||
|
selectionListener?.invoke(selected, newTab.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
nav3.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||||
|
override fun onTabSelected(
|
||||||
|
lastIndex: Int,
|
||||||
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
|
newIndex: Int,
|
||||||
|
newTab: AnimatedBottomBar.Tab
|
||||||
|
) {
|
||||||
|
selected = 2
|
||||||
|
deselectOthers(selected)
|
||||||
|
selectionListener?.invoke(selected, newTab.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
nav1.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||||
|
override fun onTabSelected(
|
||||||
|
lastIndex: Int,
|
||||||
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
|
newIndex: Int,
|
||||||
|
newTab: AnimatedBottomBar.Tab
|
||||||
|
) {
|
||||||
|
if (!isScreenVertical) {
|
||||||
|
selected = 0
|
||||||
|
deselectOthers(selected)
|
||||||
|
} else selected = newIndex
|
||||||
|
selectionListener?.invoke(selected, newTab.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deselectOthers(selected: Int) {
|
||||||
|
if (selected == 0) {
|
||||||
|
nav2.clearSelection()
|
||||||
|
nav3.clearSelection()
|
||||||
|
}
|
||||||
|
if (selected == 1) {
|
||||||
|
nav1.clearSelection()
|
||||||
|
nav3.clearSelection()
|
||||||
|
}
|
||||||
|
if (selected == 2) {
|
||||||
|
nav1.clearSelection()
|
||||||
|
nav2.clearSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectTab(tab: Int) {
|
||||||
|
selected = tab
|
||||||
|
if (!isScreenVertical) {
|
||||||
|
when (tab) {
|
||||||
|
0 -> nav1.selectTabAt(0)
|
||||||
|
1 -> nav2.selectTabAt(0)
|
||||||
|
2 -> nav3.selectTabAt(0)
|
||||||
|
}
|
||||||
|
deselectOthers(selected)
|
||||||
|
} else {
|
||||||
|
nav1.selectTabAt(selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVisibility(visibility: Int) {
|
||||||
|
if (isScreenVertical) {
|
||||||
|
nav1.visibility = visibility
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nav1.visibility = visibility
|
||||||
|
nav2.visibility = visibility
|
||||||
|
nav3.visibility = visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,66 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
import java.util.regex.Matcher
|
import java.util.regex.Matcher
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class AnimeNameAdapter {
|
class AnimeNameAdapter {
|
||||||
companion object {
|
companion object {
|
||||||
const val episodeRegex =
|
const val episodeRegex =
|
||||||
"(episode|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*"
|
"(episode|episodio|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*"
|
||||||
const val failedEpisodeNumberRegex =
|
const val failedEpisodeNumberRegex =
|
||||||
"(?<!part\\s)\\b(\\d+)\\b"
|
"(?<!part\\s)\\b(\\d+)\\b"
|
||||||
const val seasonRegex = "\\s+(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
|
const val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
|
||||||
|
const val subdubRegex = "^(soft)?[\\s-]*(sub|dub|mixed)(bed|s)?\\s*$"
|
||||||
|
|
||||||
|
fun setSubDub(text: String, typeToSetTo: SubDubType): String? {
|
||||||
|
val subdubPattern: Pattern = Pattern.compile(subdubRegex, Pattern.CASE_INSENSITIVE)
|
||||||
|
val subdubMatcher: Matcher = subdubPattern.matcher(text)
|
||||||
|
|
||||||
|
return if (subdubMatcher.find()) {
|
||||||
|
val soft = subdubMatcher.group(1)
|
||||||
|
val subdub = subdubMatcher.group(2)
|
||||||
|
val bed = subdubMatcher.group(3) ?: ""
|
||||||
|
|
||||||
|
val toggled = when (typeToSetTo) {
|
||||||
|
SubDubType.SUB -> "sub"
|
||||||
|
SubDubType.DUB -> "dub"
|
||||||
|
SubDubType.NULL -> ""
|
||||||
|
}
|
||||||
|
val toggledCasePreserved =
|
||||||
|
if (subdub?.get(0)?.isUpperCase() == true || soft?.get(0)
|
||||||
|
?.isUpperCase() == true
|
||||||
|
) toggled.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(
|
||||||
|
Locale.ROOT
|
||||||
|
) else it.toString()
|
||||||
|
} else toggled
|
||||||
|
|
||||||
|
subdubMatcher.replaceFirst(toggledCasePreserved + bed)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSubDub(text: String): SubDubType {
|
||||||
|
val subdubPattern: Pattern = Pattern.compile(subdubRegex, Pattern.CASE_INSENSITIVE)
|
||||||
|
val subdubMatcher: Matcher = subdubPattern.matcher(text)
|
||||||
|
|
||||||
|
return if (subdubMatcher.find()) {
|
||||||
|
val subdub = subdubMatcher.group(2)?.lowercase(Locale.ROOT)
|
||||||
|
when (subdub) {
|
||||||
|
"sub" -> SubDubType.SUB
|
||||||
|
"dub" -> SubDubType.DUB
|
||||||
|
else -> SubDubType.NULL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SubDubType.NULL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SubDubType {
|
||||||
|
SUB, DUB, NULL
|
||||||
|
}
|
||||||
|
|
||||||
fun findSeasonNumber(text: String): Int? {
|
fun findSeasonNumber(text: String): Int? {
|
||||||
val seasonPattern: Pattern = Pattern.compile(seasonRegex, Pattern.CASE_INSENSITIVE)
|
val seasonPattern: Pattern = Pattern.compile(seasonRegex, Pattern.CASE_INSENSITIVE)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import ani.dantotsu.settings.FAQActivity
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
@@ -27,10 +27,11 @@ import ani.dantotsu.others.webview.CookieCatcher
|
|||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.DynamicAnimeParser
|
import ani.dantotsu.parsers.DynamicAnimeParser
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -58,15 +59,21 @@ class AnimeWatchAdapter(
|
|||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
_binding = binding
|
_binding = binding
|
||||||
|
|
||||||
|
binding.faqbutton.setOnClickListener {
|
||||||
|
startActivity(
|
||||||
|
fragment.requireContext(),
|
||||||
|
Intent(fragment.requireContext(), FAQActivity::class.java),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
//Youtube
|
//Youtube
|
||||||
if (media.anime!!.youtube != null && fragment.uiSettings.showYtButton) {
|
if (media.anime?.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) {
|
||||||
binding.animeSourceYT.visibility = View.VISIBLE
|
binding.animeSourceYT.visibility = View.VISIBLE
|
||||||
binding.animeSourceYT.setOnClickListener {
|
binding.animeSourceYT.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))
|
||||||
fragment.requireContext().startActivity(intent)
|
fragment.requireContext().startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceDubbed.isChecked = media.selected!!.preferDub
|
binding.animeSourceDubbed.isChecked = media.selected!!.preferDub
|
||||||
binding.animeSourceDubbedText.text =
|
binding.animeSourceDubbedText.text =
|
||||||
if (media.selected!!.preferDub) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(
|
if (media.selected!!.preferDub) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(
|
||||||
@@ -90,11 +97,9 @@ class AnimeWatchAdapter(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
|
val offline = if (!isOnline(binding.root.context) || PrefManager.getVal(
|
||||||
"Dantotsu",
|
PrefName.OfflineMode
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
)
|
||||||
?.getBoolean("offlineMode", false) == true
|
|
||||||
) View.GONE else View.VISIBLE
|
) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
binding.animeSourceNameContainer.visibility = offline
|
binding.animeSourceNameContainer.visibility = offline
|
||||||
@@ -113,7 +118,7 @@ class AnimeWatchAdapter(
|
|||||||
binding.animeSourceTitle.text = showUserText
|
binding.animeSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
binding.animeSourceDubbedCont.visibility =
|
binding.animeSourceDubbedCont.visibility =
|
||||||
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,7 @@ class AnimeWatchAdapter(
|
|||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
binding.animeSourceDubbedCont.visibility =
|
binding.animeSourceDubbedCont.visibility =
|
||||||
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||||
source = i
|
source = i
|
||||||
setLanguageList(0, i)
|
setLanguageList(0, i)
|
||||||
}
|
}
|
||||||
@@ -154,7 +159,7 @@ class AnimeWatchAdapter(
|
|||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
binding.animeSourceDubbedCont.visibility =
|
binding.animeSourceDubbedCont.visibility =
|
||||||
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||||
setLanguageList(i, source)
|
setLanguageList(i, source)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
@@ -180,7 +185,8 @@ class AnimeWatchAdapter(
|
|||||||
R.drawable.ic_round_notifications_none_24,
|
R.drawable.ic_round_notifications_none_24,
|
||||||
R.color.bg_opp,
|
R.color.bg_opp,
|
||||||
R.color.violet_400,
|
R.color.violet_400,
|
||||||
fragment.subscribed
|
fragment.subscribed,
|
||||||
|
true
|
||||||
) {
|
) {
|
||||||
fragment.onNotificationPressed(it, binding.animeSource.text.toString())
|
fragment.onNotificationPressed(it, binding.animeSource.text.toString())
|
||||||
}
|
}
|
||||||
@@ -188,7 +194,7 @@ class AnimeWatchAdapter(
|
|||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
|
|
||||||
binding.animeSourceSubscribe.setOnLongClickListener {
|
binding.animeSourceSubscribe.setOnLongClickListener {
|
||||||
openSettings(fragment.requireContext(), getChannelId(true, media.id))
|
openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Nested Button
|
//Nested Button
|
||||||
@@ -199,7 +205,8 @@ class AnimeWatchAdapter(
|
|||||||
var refresh = false
|
var refresh = false
|
||||||
var run = false
|
var run = false
|
||||||
var reversed = media.selected!!.recyclerReversed
|
var reversed = media.selected!!.recyclerReversed
|
||||||
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
|
var style =
|
||||||
|
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView)
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
dialogBinding.animeSourceTop.setOnClickListener {
|
dialogBinding.animeSourceTop.setOnClickListener {
|
||||||
@@ -257,8 +264,15 @@ class AnimeWatchAdapter(
|
|||||||
val url = sourceHttp?.baseUrl
|
val url = sourceHttp?.baseUrl
|
||||||
url?.let {
|
url?.let {
|
||||||
refresh = true
|
refresh = true
|
||||||
|
val headersMap = try {
|
||||||
|
sourceHttp.headers.toMultimap()
|
||||||
|
.mapValues { it.value.getOrNull(0) ?: "" }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
||||||
.putExtra("url", url)
|
.putExtra("url", url)
|
||||||
|
.putExtra("headers", headersMap as HashMap<String, String>)
|
||||||
startActivity(fragment.requireContext(), intent, null)
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,7 +371,9 @@ class AnimeWatchAdapter(
|
|||||||
val episodes = media.anime.episodes!!.keys.toTypedArray()
|
val episodes = media.anime.episodes!!.keys.toTypedArray()
|
||||||
|
|
||||||
val anilistEp = (media.userProgress ?: 0).plus(1)
|
val anilistEp = (media.userProgress ?: 0).plus(1)
|
||||||
val appEp = loadData<String>("${media.id}_current_ep")?.toIntOrNull() ?: 1
|
val appEp =
|
||||||
|
PrefManager.getCustomVal<String?>("${media.id}_current_ep", "")?.toIntOrNull()
|
||||||
|
?: 1
|
||||||
|
|
||||||
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
||||||
if (episodes.contains(continueEp)) {
|
if (episodes.contains(continueEp)) {
|
||||||
@@ -369,7 +385,10 @@ class AnimeWatchAdapter(
|
|||||||
media.id,
|
media.id,
|
||||||
continueEp
|
continueEp
|
||||||
)
|
)
|
||||||
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > fragment.playerSettings.watchPercentage) {
|
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal<Float>(
|
||||||
|
PrefName.WatchPercentage
|
||||||
|
)
|
||||||
|
) {
|
||||||
val e = episodes.indexOf(continueEp)
|
val e = episodes.indexOf(continueEp)
|
||||||
if (e != -1 && e + 1 < episodes.size) {
|
if (e != -1 && e + 1 < episodes.size) {
|
||||||
continueEp = episodes[e + 1]
|
continueEp = episodes[e + 1]
|
||||||
@@ -396,7 +415,10 @@ class AnimeWatchAdapter(
|
|||||||
fragment.onEpisodeClick(continueEp)
|
fragment.onEpisodeClick(continueEp)
|
||||||
}
|
}
|
||||||
if (fragment.continueEp) {
|
if (fragment.continueEp) {
|
||||||
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < fragment.playerSettings.watchPercentage) {
|
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal<Float>(
|
||||||
|
PrefName.WatchPercentage
|
||||||
|
)
|
||||||
|
) {
|
||||||
binding.animeSourceContinue.performClick()
|
binding.animeSourceContinue.performClick()
|
||||||
fragment.continueEp = false
|
fragment.continueEp = false
|
||||||
}
|
}
|
||||||
@@ -406,13 +428,17 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceProgressBar.visibility = View.GONE
|
binding.animeSourceProgressBar.visibility = View.GONE
|
||||||
if (media.anime.episodes!!.isNotEmpty())
|
if (media.anime.episodes!!.isNotEmpty()) {
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.animeSourceNotFound.visibility = View.GONE
|
||||||
else
|
binding.faqbutton.visibility = View.GONE}
|
||||||
|
else {
|
||||||
binding.animeSourceNotFound.visibility = View.VISIBLE
|
binding.animeSourceNotFound.visibility = View.VISIBLE
|
||||||
|
binding.faqbutton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.animeSourceContinue.visibility = View.GONE
|
binding.animeSourceContinue.visibility = View.GONE
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.animeSourceNotFound.visibility = View.GONE
|
||||||
|
binding.faqbutton.visibility = View.GONE
|
||||||
clearChips()
|
clearChips()
|
||||||
binding.animeSourceProgressBar.visibility = View.VISIBLE
|
binding.animeSourceProgressBar.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.media3.common.util.UnstableApi
|
|||||||
import androidx.media3.exoplayer.offline.DownloadService
|
import androidx.media3.exoplayer.offline.DownloadService
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
@@ -39,16 +40,12 @@ import ani.dantotsu.others.LanguageMapper
|
|||||||
import ani.dantotsu.parsers.AnimeParser
|
import ani.dantotsu.parsers.AnimeParser
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.HAnimeSources
|
import ani.dantotsu.parsers.HAnimeSources
|
||||||
import ani.dantotsu.settings.PlayerSettings
|
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
|
||||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
import ani.dantotsu.subcriptions.Notifications
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigationrail.NavigationRailView
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -84,8 +81,6 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
var continueEp: Boolean = false
|
var continueEp: Boolean = false
|
||||||
var loaded = false
|
var loaded = false
|
||||||
|
|
||||||
lateinit var playerSettings: PlayerSettings
|
|
||||||
lateinit var uiSettings: UserInterfaceSettings
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -118,12 +113,6 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
var maxGridSize = (screenWidth / 100f).roundToInt()
|
var maxGridSize = (screenWidth / 100f).roundToInt()
|
||||||
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
||||||
|
|
||||||
playerSettings =
|
|
||||||
loadData("player_settings", toast = false)
|
|
||||||
?: PlayerSettings().apply { saveData("player_settings", this) }
|
|
||||||
uiSettings = loadData("ui_settings", toast = false)
|
|
||||||
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
|
||||||
|
|
||||||
val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize)
|
val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize)
|
||||||
|
|
||||||
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||||
@@ -144,6 +133,23 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
|
|
||||||
binding.animeSourceRecycler.layoutManager = gridLayoutManager
|
binding.animeSourceRecycler.layoutManager = gridLayoutManager
|
||||||
|
|
||||||
|
binding.ScrollTop.setOnClickListener {
|
||||||
|
binding.animeSourceRecycler.scrollToPosition(10)
|
||||||
|
binding.animeSourceRecycler.smoothScrollToPosition(0)
|
||||||
|
}
|
||||||
|
binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val position = gridLayoutManager.findFirstVisibleItemPosition()
|
||||||
|
if (position > 2) {
|
||||||
|
binding.ScrollTop.translationY = -navBarHeight.toFloat()
|
||||||
|
binding.ScrollTop.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.ScrollTop.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
model.scrolledToTop.observe(viewLifecycleOwner) {
|
model.scrolledToTop.observe(viewLifecycleOwner) {
|
||||||
if (it) binding.animeSourceRecycler.scrollToPosition(0)
|
if (it) binding.animeSourceRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
@@ -155,7 +161,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
|
|
||||||
subscribed =
|
subscribed =
|
||||||
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
SubscriptionHelper.getSubscriptions().containsKey(media.id)
|
||||||
|
|
||||||
style = media.selected!!.recyclerStyle
|
style = media.selected!!.recyclerStyle
|
||||||
reverse = media.selected!!.recyclerReversed
|
reverse = media.selected!!.recyclerReversed
|
||||||
@@ -172,7 +178,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
|
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
|
||||||
episodeAdapter =
|
episodeAdapter =
|
||||||
EpisodeAdapter(
|
EpisodeAdapter(
|
||||||
style ?: uiSettings.animeDefaultView,
|
style ?: PrefManager.getVal(PrefName.AnimeDefaultView),
|
||||||
media,
|
media,
|
||||||
this,
|
this,
|
||||||
offlineMode = offlineMode
|
offlineMode = offlineMode
|
||||||
@@ -273,7 +279,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
model.watchSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
model.watchSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
||||||
selected.sourceIndex = i
|
selected.sourceIndex = i
|
||||||
selected.server = null
|
selected.server = null
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
return model.watchSources?.get(i)!!
|
return model.watchSources?.get(i)!!
|
||||||
}
|
}
|
||||||
@@ -281,7 +287,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
fun onLangChange(i: Int) {
|
fun onLangChange(i: Int) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
selected.langIndex = i
|
selected.langIndex = i
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +295,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
|
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
|
||||||
selected.preferDub = checked
|
selected.preferDub = checked
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
model.forceLoadEpisode(
|
model.forceLoadEpisode(
|
||||||
@@ -308,7 +314,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
reverse = rev
|
reverse = rev
|
||||||
media.selected!!.recyclerStyle = style
|
media.selected!!.recyclerStyle = style
|
||||||
media.selected!!.recyclerReversed = reverse
|
media.selected!!.recyclerReversed = reverse
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,23 +322,14 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
media.selected!!.chip = i
|
media.selected!!.chip = i
|
||||||
start = s
|
start = s
|
||||||
end = e
|
end = e
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscribed = false
|
var subscribed = false
|
||||||
fun onNotificationPressed(subscribed: Boolean, source: String) {
|
fun onNotificationPressed(subscribed: Boolean, source: String) {
|
||||||
this.subscribed = subscribed
|
this.subscribed = subscribed
|
||||||
saveSubscription(requireContext(), media, subscribed)
|
saveSubscription(media, subscribed)
|
||||||
if (!subscribed)
|
|
||||||
Notifications.deleteChannel(requireContext(), getChannelId(true, media.id))
|
|
||||||
else
|
|
||||||
Notifications.createChannel(
|
|
||||||
requireContext(),
|
|
||||||
ANIME_GROUP,
|
|
||||||
getChannelId(true, media.id),
|
|
||||||
media.userPreferredName
|
|
||||||
)
|
|
||||||
snackString(
|
snackString(
|
||||||
if (subscribed) getString(R.string.subscribed_notification, source)
|
if (subscribed) getString(R.string.subscribed_notification, source)
|
||||||
else getString(R.string.unsubscribed_notification)
|
else getString(R.string.unsubscribed_notification)
|
||||||
@@ -348,11 +345,9 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||||
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||||
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||||
try {
|
|
||||||
activity.findViewById<CustomBottomNavBar>(R.id.mediaTab).visibility = visibility
|
activity.tabLayout.setVisibility(visibility)
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
activity.findViewById<NavigationRailView>(R.id.mediaTab).visibility = visibility
|
|
||||||
}
|
|
||||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
if (show) View.GONE else View.VISIBLE
|
if (show) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
@@ -364,12 +359,10 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
if (allSettings.size > 1) {
|
if (allSettings.size > 1) {
|
||||||
val names =
|
val names =
|
||||||
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
|
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
|
||||||
var selectedIndex = 0
|
|
||||||
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
||||||
.setTitle("Select a Source")
|
.setTitle("Select a Source")
|
||||||
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
|
.setSingleChoiceItems(names, -1) { dialog, which ->
|
||||||
selectedIndex = which
|
selectedSetting = allSettings[which]
|
||||||
selectedSetting = allSettings[selectedIndex]
|
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
|
||||||
@@ -419,7 +412,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
|
|
||||||
fun onEpisodeClick(i: String) {
|
fun onEpisodeClick(i: String) {
|
||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
|
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,17 +451,11 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
||||||
val id = requireContext().getSharedPreferences(
|
val id = PrefManager.getAnimeDownloadPreferences().getString(
|
||||||
ContextCompat.getString(requireContext(), R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).getString(
|
|
||||||
taskName,
|
taskName,
|
||||||
""
|
""
|
||||||
) ?: ""
|
) ?: ""
|
||||||
requireContext().getSharedPreferences(
|
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
|
||||||
ContextCompat.getString(requireContext(), R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).edit().remove(taskName).apply()
|
|
||||||
DownloadService.sendRemoveDownload(
|
DownloadService.sendRemoveDownload(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
ExoplayerDownloadService::class.java,
|
ExoplayerDownloadService::class.java,
|
||||||
@@ -520,7 +507,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
selected.latest =
|
selected.latest =
|
||||||
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
||||||
|
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
headerAdapter.handleEpisodes()
|
headerAdapter.handleEpisodes()
|
||||||
val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
|
val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
|
||||||
episodeAdapter.offlineMode = isDownloaded
|
episodeAdapter.offlineMode = isDownloaded
|
||||||
@@ -536,7 +523,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
arr = (arr.reversed() as? ArrayList<Episode>) ?: arr
|
arr = (arr.reversed() as? ArrayList<Episode>) ?: arr
|
||||||
}
|
}
|
||||||
episodeAdapter.arr = arr
|
episodeAdapter.arr = arr
|
||||||
episodeAdapter.updateType(style ?: uiSettings.animeDefaultView)
|
episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView))
|
||||||
episodeAdapter.notifyItemRangeInserted(0, arr.size)
|
episodeAdapter.notifyItemRangeInserted(0, arr.size)
|
||||||
for (download in downloadManager.animeDownloadedTypes) {
|
for (download in downloadManager.animeDownloadedTypes) {
|
||||||
if (download.title == media.mainName()) {
|
if (download.title == media.mainName()) {
|
||||||
@@ -559,6 +546,8 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
binding.mediaInfoProgressBar.visibility = progress
|
binding.mediaInfoProgressBar.visibility = progress
|
||||||
binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
||||||
|
|
||||||
|
requireActivity().setNavigationTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package ani.dantotsu.media.anime
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.LinearInterpolator
|
import android.view.animation.LinearInterpolator
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.offline.DownloadIndex
|
import androidx.media3.exoplayer.offline.DownloadIndex
|
||||||
@@ -22,6 +20,7 @@ import ani.dantotsu.databinding.ItemEpisodeListBinding
|
|||||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||||
import ani.dantotsu.download.video.Helper
|
import ani.dantotsu.download.video.Helper
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -30,8 +29,8 @@ import kotlin.math.ln
|
|||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) {
|
fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) {
|
||||||
val curr = loadData<Long>("${mediaId}_${ep}")
|
val curr = PrefManager.getNullableCustomVal("${mediaId}_${ep}", null, Long::class.java)
|
||||||
val max = loadData<Long>("${mediaId}_${ep}_max")
|
val max = PrefManager.getNullableCustomVal("${mediaId}_${ep}_max", null, Long::class.java)
|
||||||
if (curr != null && max != null) {
|
if (curr != null && max != null) {
|
||||||
cont.visibility = View.VISIBLE
|
cont.visibility = View.VISIBLE
|
||||||
val div = curr.toFloat() / max.toFloat()
|
val div = curr.toFloat() / max.toFloat()
|
||||||
@@ -110,7 +109,7 @@ class EpisodeAdapter(
|
|||||||
when (holder) {
|
when (holder) {
|
||||||
is EpisodeListViewHolder -> {
|
is EpisodeListViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
|
|
||||||
val thumb =
|
val thumb =
|
||||||
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
||||||
@@ -129,7 +128,7 @@ class EpisodeAdapter(
|
|||||||
binding.itemEpisodeDesc.visibility =
|
binding.itemEpisodeDesc.visibility =
|
||||||
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
|
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
|
||||||
binding.itemEpisodeDesc.text = ep.desc ?: ""
|
binding.itemEpisodeDesc.text = ep.desc ?: ""
|
||||||
holder.bind(ep.number, ep.downloadProgress , ep.desc)
|
holder.bind(ep.number, ep.downloadProgress, ep.desc)
|
||||||
|
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) {
|
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) {
|
||||||
@@ -159,7 +158,7 @@ class EpisodeAdapter(
|
|||||||
|
|
||||||
is EpisodeGridViewHolder -> {
|
is EpisodeGridViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
|
|
||||||
val thumb =
|
val thumb =
|
||||||
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
||||||
@@ -202,7 +201,7 @@ class EpisodeAdapter(
|
|||||||
|
|
||||||
is EpisodeCompactViewHolder -> {
|
is EpisodeCompactViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeFillerView.visibility =
|
binding.itemEpisodeFillerView.visibility =
|
||||||
if (ep.filler) View.VISIBLE else View.GONE
|
if (ep.filler) View.VISIBLE else View.GONE
|
||||||
@@ -253,10 +252,7 @@ class EpisodeAdapter(
|
|||||||
media.mainName(),
|
media.mainName(),
|
||||||
episodeNumber
|
episodeNumber
|
||||||
)
|
)
|
||||||
val id = fragment.requireContext().getSharedPreferences(
|
val id = PrefManager.getAnimeDownloadPreferences().getString(
|
||||||
ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads),
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
).getString(
|
|
||||||
taskName,
|
taskName,
|
||||||
""
|
""
|
||||||
) ?: ""
|
) ?: ""
|
||||||
@@ -376,30 +372,31 @@ class EpisodeAdapter(
|
|||||||
if (activeDownloads.contains(episodeNumber)) {
|
if (activeDownloads.contains(episodeNumber)) {
|
||||||
// Show spinner
|
// Show spinner
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_sync)
|
binding.itemDownload.setImageResource(R.drawable.ic_sync)
|
||||||
startOrContinueRotation(episodeNumber)
|
startOrContinueRotation(episodeNumber) {
|
||||||
|
binding.itemDownload.rotation = 0f
|
||||||
|
}
|
||||||
binding.itemEpisodeDesc.visibility = View.GONE
|
binding.itemEpisodeDesc.visibility = View.GONE
|
||||||
} else if (downloadedEpisodes.contains(episodeNumber)) {
|
} else if (downloadedEpisodes.contains(episodeNumber)) {
|
||||||
binding.itemEpisodeDesc.visibility = View.GONE
|
binding.itemEpisodeDesc.visibility = View.GONE
|
||||||
binding.itemDownloadStatus.visibility = View.VISIBLE
|
binding.itemDownloadStatus.visibility = View.VISIBLE
|
||||||
// Show checkmark
|
// Show checkmark
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
|
binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
|
||||||
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
|
|
||||||
binding.itemDownload.postDelayed({
|
binding.itemDownload.postDelayed({
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
|
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
|
||||||
binding.itemDownload.rotation = 0f
|
binding.itemDownload.rotation = 0f
|
||||||
//binding.itemDownload.setColorFilter(typedValue2.data)
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} else {
|
} else {
|
||||||
binding.itemDownloadStatus.visibility = View.GONE
|
binding.itemDownloadStatus.visibility = View.GONE
|
||||||
binding.itemEpisodeDesc.visibility = if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
|
binding.itemEpisodeDesc.visibility =
|
||||||
|
if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
|
||||||
// Show download icon
|
// Show download icon
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
|
binding.itemDownload.setImageResource(R.drawable.ic_download_24)
|
||||||
binding.itemDownload.rotation = 0f
|
binding.itemDownload.rotation = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startOrContinueRotation(episodeNumber: String) {
|
private fun startOrContinueRotation(episodeNumber: String, resetRotation: () -> Unit) {
|
||||||
if (!isRotationCoroutineRunningFor(episodeNumber)) {
|
if (!isRotationCoroutineRunningFor(episodeNumber)) {
|
||||||
val scope = fragment.lifecycle.coroutineScope
|
val scope = fragment.lifecycle.coroutineScope
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -414,6 +411,7 @@ class EpisodeAdapter(
|
|||||||
}
|
}
|
||||||
// Remove chapter number from active coroutines set
|
// Remove chapter number from active coroutines set
|
||||||
activeCoroutines.remove(episodeNumber)
|
activeCoroutines.remove(episodeNumber)
|
||||||
|
resetRotation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -8,7 +9,6 @@ import android.graphics.Color
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -18,6 +18,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
||||||
import ani.dantotsu.databinding.ItemStreamBinding
|
import ani.dantotsu.databinding.ItemStreamBinding
|
||||||
import ani.dantotsu.databinding.ItemUrlBinding
|
import ani.dantotsu.databinding.ItemUrlBinding
|
||||||
@@ -28,11 +29,14 @@ import ani.dantotsu.others.Download.download
|
|||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +97,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
binding.selectorAutoText.text = selected
|
binding.selectorAutoText.text = selected
|
||||||
binding.selectorCancel.setOnClickListener {
|
binding.selectorCancel.setOnClickListener {
|
||||||
media!!.selected!!.server = null
|
media!!.selected!!.server = null
|
||||||
model.saveSelected(media!!.id, media!!.selected!!, requireActivity())
|
model.saveSelected(media!!.id, media!!.selected!!)
|
||||||
tryWith {
|
tryWith {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -142,11 +146,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
binding.selectorRecyclerView.adapter = null
|
binding.selectorRecyclerView.adapter = null
|
||||||
binding.selectorProgressBar.visibility = View.VISIBLE
|
binding.selectorProgressBar.visibility = View.VISIBLE
|
||||||
makeDefault = loadData("make_default") ?: true
|
makeDefault = PrefManager.getVal(PrefName.MakeDefault)
|
||||||
binding.selectorMakeDefault.isChecked = makeDefault
|
binding.selectorMakeDefault.isChecked = makeDefault
|
||||||
binding.selectorMakeDefault.setOnClickListener {
|
binding.selectorMakeDefault.setOnClickListener {
|
||||||
makeDefault = binding.selectorMakeDefault.isChecked
|
makeDefault = binding.selectorMakeDefault.isChecked
|
||||||
saveData("make_default", makeDefault)
|
PrefManager.setVal(PrefName.MakeDefault, makeDefault)
|
||||||
}
|
}
|
||||||
binding.selectorRecyclerView.layoutManager =
|
binding.selectorRecyclerView.layoutManager =
|
||||||
LinearLayoutManager(
|
LinearLayoutManager(
|
||||||
@@ -177,11 +181,23 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
model.loadEpisodeVideos(ep, media!!.selected!!.sourceIndex)
|
model.loadEpisodeVideos(ep, media!!.selected!!.sourceIndex)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.selectorProgressBar.visibility = View.GONE
|
binding.selectorProgressBar.visibility = View.GONE
|
||||||
|
if (adapter.itemCount == 0) {
|
||||||
|
snackString(getString(R.string.stream_selection_empty))
|
||||||
|
tryWith {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep)
|
media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep)
|
||||||
adapter.addAll(ep.extractors)
|
adapter.addAll(ep.extractors)
|
||||||
|
if (ep.extractors?.size == 0) {
|
||||||
|
snackString(getString(R.string.stream_selection_empty))
|
||||||
|
tryWith {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) {
|
if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) {
|
||||||
adapter.performClick(0)
|
adapter.performClick(0)
|
||||||
}
|
}
|
||||||
@@ -265,7 +281,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = 0
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = 0
|
||||||
startExoplayer(media!!)
|
startExoplayer(media!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,96 +316,89 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
extractor.server.name
|
extractor.server.name
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
|
||||||
position
|
position
|
||||||
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
if ((PrefManager.getVal(PrefName.DownloadManager) as Int) != 0) {
|
||||||
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
|
|
||||||
val selectedVideo =
|
|
||||||
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
|
|
||||||
|
|
||||||
val subtitles = extractor.subtitles
|
|
||||||
val subtitleNames = subtitles.map { it.language }
|
|
||||||
var subtitleToDownload: Subtitle? = null
|
|
||||||
if (subtitles.isNotEmpty()) {
|
|
||||||
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
|
|
||||||
.setTitle("Download Subtitle")
|
|
||||||
.setSingleChoiceItems(
|
|
||||||
subtitleNames.toTypedArray(),
|
|
||||||
-1
|
|
||||||
) { dialog, which ->
|
|
||||||
subtitleToDownload = subtitles[which]
|
|
||||||
}
|
|
||||||
.setPositiveButton("Download") { _, _ ->
|
|
||||||
dialog?.dismiss()
|
|
||||||
if (selectedVideo != null) {
|
|
||||||
Helper.startAnimeDownloadService(
|
|
||||||
currActivity()!!,
|
|
||||||
media!!.mainName(),
|
|
||||||
episode.number,
|
|
||||||
selectedVideo,
|
|
||||||
subtitleToDownload,
|
|
||||||
media,
|
|
||||||
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
snackString("No Video Selected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton("Skip") { dialog, _ ->
|
|
||||||
subtitleToDownload = null
|
|
||||||
if (selectedVideo != null) {
|
|
||||||
Helper.startAnimeDownloadService(
|
|
||||||
currActivity()!!,
|
|
||||||
media!!.mainName(),
|
|
||||||
episode.number,
|
|
||||||
selectedVideo,
|
|
||||||
subtitleToDownload,
|
|
||||||
media,
|
|
||||||
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
snackString("No Video Selected")
|
|
||||||
}
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.setNeutralButton("Cancel") { dialog, _ ->
|
|
||||||
subtitleToDownload = null
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
alertDialog.window?.setDimAmount(0.8f)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (selectedVideo != null) {
|
|
||||||
Helper.startAnimeDownloadService(
|
|
||||||
requireActivity(),
|
|
||||||
media!!.mainName(),
|
|
||||||
episode.number,
|
|
||||||
selectedVideo,
|
|
||||||
subtitleToDownload,
|
|
||||||
media,
|
|
||||||
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
snackString("No Video Selected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
binding.urlDownload.setOnLongClickListener {
|
|
||||||
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
|
||||||
if ((loadData<Int>("settings_download_manager") ?: 0) != 0) {
|
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor =
|
|
||||||
extractor.server.name
|
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
|
|
||||||
position
|
|
||||||
download(
|
download(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!,
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!,
|
||||||
media!!.userPreferredName
|
media!!.userPreferredName
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
snackString("No Download Manager Selected")
|
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
|
||||||
|
val selectedVideo =
|
||||||
|
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
|
||||||
|
val subtitles = extractor.subtitles
|
||||||
|
val subtitleNames = subtitles.map { it.language }
|
||||||
|
var subtitleToDownload: Subtitle? = null
|
||||||
|
val activity = currActivity()?:requireActivity()
|
||||||
|
if (subtitles.isNotEmpty()) {
|
||||||
|
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
|
||||||
|
.setTitle("Download Subtitle")
|
||||||
|
.setSingleChoiceItems(
|
||||||
|
subtitleNames.toTypedArray(),
|
||||||
|
-1
|
||||||
|
) { dialog, which ->
|
||||||
|
subtitleToDownload = subtitles[which]
|
||||||
|
}
|
||||||
|
.setPositiveButton("Download") { _, _ ->
|
||||||
|
dialog?.dismiss()
|
||||||
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
activity,
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
broadcastDownloadStarted(episode.number, activity)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton("Skip") { dialog, _ ->
|
||||||
|
subtitleToDownload = null
|
||||||
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
currActivity()!!,
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
broadcastDownloadStarted(episode.number, activity)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNeutralButton("Cancel") { dialog, _ ->
|
||||||
|
subtitleToDownload = null
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
alertDialog.window?.setDimAmount(0.8f)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
requireActivity(),
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
broadcastDownloadStarted(episode.number, activity)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
dismiss()
|
||||||
}
|
}
|
||||||
if (video.format == VideoType.CONTAINER) {
|
if (video.format == VideoType.CONTAINER) {
|
||||||
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
||||||
@@ -404,6 +413,13 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
binding.urlQuality.text = extractor.server.name
|
binding.urlQuality.text = extractor.server.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadStarted(episodeNumber: String, activity: Activity) {
|
||||||
|
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply {
|
||||||
|
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
|
||||||
|
}
|
||||||
|
activity.sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = extractor.videos.size
|
override fun getItemCount(): Int = extractor.videos.size
|
||||||
|
|
||||||
private inner class UrlViewHolder(val binding: ItemUrlBinding) :
|
private inner class UrlViewHolder(val binding: ItemUrlBinding) :
|
||||||
@@ -422,7 +438,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
if (makeDefault) {
|
if (makeDefault) {
|
||||||
media!!.selected!!.server = extractor.server.name
|
media!!.selected!!.server = extractor.server.name
|
||||||
media!!.selected!!.video = bindingAdapterPosition
|
media!!.selected!!.video = bindingAdapterPosition
|
||||||
model.saveSelected(media!!.id, media!!.selected!!, requireActivity())
|
model.saveSelected(media!!.id, media!!.selected!!)
|
||||||
}
|
}
|
||||||
startExoplayer(media!!)
|
startExoplayer(media!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ import ani.dantotsu.BottomSheetDialogFragment
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.BottomSheetSubtitlesBinding
|
import ani.dantotsu.databinding.BottomSheetSubtitlesBinding
|
||||||
import ani.dantotsu.databinding.ItemSubtitleTextBinding
|
import ani.dantotsu.databinding.ItemSubtitleTextBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
|
||||||
class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetSubtitlesBinding? = null
|
private var _binding: BottomSheetSubtitlesBinding? = null
|
||||||
@@ -69,7 +68,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
|||||||
binding.subtitleTitle.setText(R.string.none)
|
binding.subtitleTitle.setText(R.string.none)
|
||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
val mediaID: Int = media.id
|
val mediaID: Int = media.id
|
||||||
val selSubs: String? = loadData("subLang_${mediaID}", activity)
|
val selSubs = PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
|
||||||
if (episode.selectedSubtitle != null && selSubs != "None") {
|
if (episode.selectedSubtitle != null && selSubs != "None") {
|
||||||
binding.root.setCardBackgroundColor(TRANSPARENT)
|
binding.root.setCardBackgroundColor(TRANSPARENT)
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
|||||||
model.setEpisode(episode, "Subtitle")
|
model.setEpisode(episode, "Subtitle")
|
||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
val mediaID: Int = media.id
|
val mediaID: Int = media.id
|
||||||
saveData("subLang_${mediaID}", "None", activity)
|
PrefManager.setCustomVal("subLang_${mediaID}", "None")
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -108,7 +107,8 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
val mediaID: Int = media.id
|
val mediaID: Int = media.id
|
||||||
val selSubs: String? = loadData("subLang_${mediaID}", activity)
|
val selSubs: String? =
|
||||||
|
PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
|
||||||
if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) {
|
if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) {
|
||||||
binding.root.setCardBackgroundColor(TRANSPARENT)
|
binding.root.setCardBackgroundColor(TRANSPARENT)
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,10 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
|||||||
model.setEpisode(episode, "Subtitle")
|
model.setEpisode(episode, "Subtitle")
|
||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
val mediaID: Int = media.id
|
val mediaID: Int = media.id
|
||||||
saveData("subLang_${mediaID}", subtitles[position - 1].language, activity)
|
PrefManager.setCustomVal(
|
||||||
|
"subLang_${mediaID}",
|
||||||
|
subtitles[position - 1].language
|
||||||
|
)
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
394
app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt
Normal file
394
app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
package ani.dantotsu.media.comments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.comments.Comment
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
|
import ani.dantotsu.copyToClipboard
|
||||||
|
import ani.dantotsu.databinding.ItemCommentsBinding
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
|
import ani.dantotsu.setAnimation
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast
|
||||||
|
import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import com.xwray.groupie.Section
|
||||||
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.TimeZone
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
class CommentItem(val comment: Comment,
|
||||||
|
private val markwon: Markwon,
|
||||||
|
val parentSection: Section,
|
||||||
|
private val commentsFragment: CommentsFragment,
|
||||||
|
private val backgroundColor: Int,
|
||||||
|
val commentDepth: Int
|
||||||
|
) : BindableItem<ItemCommentsBinding>() {
|
||||||
|
lateinit var binding: ItemCommentsBinding
|
||||||
|
val adapter = GroupieAdapter()
|
||||||
|
private var subCommentIds: MutableList<Int> = mutableListOf()
|
||||||
|
val repliesSection = Section()
|
||||||
|
private var isEditing = false
|
||||||
|
var isReplying = false
|
||||||
|
private var repliesVisible = false
|
||||||
|
var MAX_DEPTH = 3
|
||||||
|
|
||||||
|
init {
|
||||||
|
adapter.add(repliesSection)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
|
||||||
|
binding = viewBinding
|
||||||
|
setAnimation(binding.root.context, binding.root)
|
||||||
|
viewBinding.commentRepliesList.layoutManager = LinearLayoutManager(commentsFragment.activity)
|
||||||
|
viewBinding.commentRepliesList.adapter = adapter
|
||||||
|
val isUserComment = CommentsAPI.userId == comment.userId
|
||||||
|
val levelColor = getAvatarColor(comment.totalVotes, backgroundColor)
|
||||||
|
markwon.setMarkdown(viewBinding.commentText, comment.content)
|
||||||
|
viewBinding.commentDelete.visibility = if (isUserComment || CommentsAPI.isAdmin || CommentsAPI.isMod) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.commentBanUser.visibility = if ((CommentsAPI.isAdmin || CommentsAPI.isMod) && !isUserComment) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.commentReport.visibility = if (!isUserComment) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.commentEdit.visibility = if (isUserComment) View.VISIBLE else View.GONE
|
||||||
|
if (comment.tag == null) {
|
||||||
|
viewBinding.commentUserTagLayout.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
viewBinding.commentUserTagLayout.visibility = View.VISIBLE
|
||||||
|
viewBinding.commentUserTag.text = comment.tag.toString()
|
||||||
|
}
|
||||||
|
replying(isReplying) //sets default text
|
||||||
|
editing(isEditing)
|
||||||
|
if ((comment.replyCount ?: 0) > 0) {
|
||||||
|
viewBinding.commentTotalReplies.visibility = View.VISIBLE
|
||||||
|
viewBinding.commentRepliesDivider.visibility = View.VISIBLE
|
||||||
|
viewBinding.commentTotalReplies.text = if(repliesVisible) "Hide Replies" else
|
||||||
|
"View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}"
|
||||||
|
} else {
|
||||||
|
viewBinding.commentTotalReplies.visibility = View.GONE
|
||||||
|
viewBinding.commentRepliesDivider.visibility = View.GONE
|
||||||
|
}
|
||||||
|
viewBinding.commentReply.visibility = View.VISIBLE
|
||||||
|
viewBinding.commentTotalReplies.setOnClickListener {
|
||||||
|
if (repliesVisible) {
|
||||||
|
repliesSection.clear()
|
||||||
|
removeSubCommentIds()
|
||||||
|
viewBinding.commentTotalReplies.text = "View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}"
|
||||||
|
repliesVisible = false
|
||||||
|
} else {
|
||||||
|
viewBinding.commentTotalReplies.text = "Hide Replies"
|
||||||
|
repliesSection.clear()
|
||||||
|
commentsFragment.viewReplyCallback(this)
|
||||||
|
repliesVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.commentUserName.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
commentsFragment.activity, Intent(commentsFragment.activity, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", comment.userId.toInt())
|
||||||
|
.putExtra("userLVL","[${levelColor.second}]"), null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewBinding.commentUserAvatar.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
commentsFragment.activity, Intent(commentsFragment.activity, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", comment.userId.toInt())
|
||||||
|
.putExtra("userLVL","[${levelColor.second}]"), null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewBinding.commentText.setOnLongClickListener {
|
||||||
|
copyToClipboard(comment.content)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.commentEdit.setOnClickListener {
|
||||||
|
editing(!isEditing)
|
||||||
|
commentsFragment.editCallback(this)
|
||||||
|
}
|
||||||
|
viewBinding.commentReply.setOnClickListener {
|
||||||
|
replying(!isReplying)
|
||||||
|
commentsFragment.replyTo(this, comment.username)
|
||||||
|
commentsFragment.replyCallback(this)
|
||||||
|
}
|
||||||
|
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.commentDelete.setOnClickListener {
|
||||||
|
dialogBuilder("Delete Comment", "Are you sure you want to delete this comment?") {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.deleteComment(comment.commentId)
|
||||||
|
if (success) {
|
||||||
|
snackString("Comment Deleted")
|
||||||
|
parentSection.remove(this@CommentItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewBinding.commentBanUser.setOnClickListener {
|
||||||
|
dialogBuilder("Ban User", "Are you sure you want to ban this user?") {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.banUser(comment.userId)
|
||||||
|
if (success) {
|
||||||
|
snackString("User Banned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewBinding.commentReport.setOnClickListener {
|
||||||
|
dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId)
|
||||||
|
if (success) {
|
||||||
|
snackString("Comment Reported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fill the icon if the user has liked the comment
|
||||||
|
setVoteButtons(viewBinding)
|
||||||
|
viewBinding.commentUpVote.setOnClickListener {
|
||||||
|
val voteType = if (comment.userVoteType == 1) 0 else 1
|
||||||
|
val previousVoteType = comment.userVoteType
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.vote(comment.commentId, voteType)
|
||||||
|
if (success) {
|
||||||
|
comment.userVoteType = voteType
|
||||||
|
|
||||||
|
if (previousVoteType == -1) {
|
||||||
|
comment.downvotes -= 1
|
||||||
|
}
|
||||||
|
comment.upvotes += if (voteType == 1) 1 else -1
|
||||||
|
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.commentDownVote.setOnClickListener {
|
||||||
|
val voteType = if (comment.userVoteType == -1) 0 else -1
|
||||||
|
val previousVoteType = comment.userVoteType
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.vote(comment.commentId, voteType)
|
||||||
|
if (success) {
|
||||||
|
comment.userVoteType = voteType
|
||||||
|
|
||||||
|
if (previousVoteType == 1) {
|
||||||
|
comment.upvotes -= 1
|
||||||
|
}
|
||||||
|
comment.downvotes += if (voteType == -1) 1 else -1
|
||||||
|
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewBinding.commentTotalVotes.text = (comment.upvotes - comment.downvotes).toString()
|
||||||
|
viewBinding.commentUserAvatar.setOnLongClickListener {
|
||||||
|
ImageViewDialog.newInstance(
|
||||||
|
commentsFragment.activity,
|
||||||
|
"${comment.username}'s [Cover]",
|
||||||
|
comment.profilePictureUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
comment.profilePictureUrl?.let { viewBinding.commentUserAvatar.loadImage(it) }
|
||||||
|
viewBinding.commentUserName.text = comment.username
|
||||||
|
viewBinding.commentUserLevel.text = "[${levelColor.second}]"
|
||||||
|
viewBinding.commentUserLevel.setTextColor(levelColor.first)
|
||||||
|
viewBinding.commentUserTime.text = formatTimestamp(comment.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayout(): Int {
|
||||||
|
return R.layout.item_comments
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsGif(): Boolean {
|
||||||
|
return comment.content.contains(".gif")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initializeViewBinding(view: View): ItemCommentsBinding {
|
||||||
|
return ItemCommentsBinding.bind(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replying(isReplying: Boolean) {
|
||||||
|
binding.commentReply.text = if (isReplying) commentsFragment.activity.getString(R.string.cancel) else "Reply"
|
||||||
|
this.isReplying = isReplying
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editing(isEditing: Boolean) {
|
||||||
|
binding.commentEdit.text = if (isEditing) commentsFragment.activity.getString(R.string.cancel) else commentsFragment.activity.getString(R.string.edit)
|
||||||
|
this.isEditing = isEditing
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerSubComment(id: Int) {
|
||||||
|
subCommentIds.add(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSubCommentIds(){
|
||||||
|
subCommentIds.forEach { id ->
|
||||||
|
val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList()
|
||||||
|
val commentToRemove = parentComments.find { it.comment.commentId == id }
|
||||||
|
commentToRemove?.let {
|
||||||
|
it.removeSubCommentIds()
|
||||||
|
parentSection.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subCommentIds.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setVoteButtons(viewBinding: ItemCommentsBinding) {
|
||||||
|
when (comment.userVoteType) {
|
||||||
|
1 -> {
|
||||||
|
viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_active_24)
|
||||||
|
viewBinding.commentUpVote.alpha = 1f
|
||||||
|
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
|
||||||
|
}
|
||||||
|
-1 -> {
|
||||||
|
viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
|
||||||
|
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_active_24)
|
||||||
|
viewBinding.commentDownVote.alpha = 1f
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
|
||||||
|
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTimestamp(timestamp: String): String {
|
||||||
|
return try {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||||
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
val parsedDate = dateFormat.parse(timestamp)
|
||||||
|
val currentDate = Date()
|
||||||
|
|
||||||
|
val diff = currentDate.time - (parsedDate?.time ?: 0)
|
||||||
|
|
||||||
|
val days = diff / (24 * 60 * 60 * 1000)
|
||||||
|
val hours = diff / (60 * 60 * 1000) % 24
|
||||||
|
val minutes = diff / (60 * 1000) % 60
|
||||||
|
|
||||||
|
return when {
|
||||||
|
days > 0 -> "${days}d"
|
||||||
|
hours > 0 -> "${hours}h"
|
||||||
|
minutes > 0 -> "${minutes}m"
|
||||||
|
else -> "now"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun timestampToMillis(timestamp: String): Long {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||||
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
val parsedDate = dateFormat.parse(timestamp)
|
||||||
|
return parsedDate?.time ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvatarColor(voteCount: Int, backgroundColor: Int): Pair<Int, Int> {
|
||||||
|
val level = if (voteCount < 0) 0 else sqrt(abs(voteCount.toDouble()) / 0.8).toInt()
|
||||||
|
val colorString = if (level > usernameColors.size - 1) usernameColors[usernameColors.size - 1] else usernameColors[level]
|
||||||
|
var color = Color.parseColor(colorString)
|
||||||
|
val ratio = getContrastRatio(color, backgroundColor)
|
||||||
|
if (ratio < 4.5) {
|
||||||
|
color = adjustColorForContrast(color, backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(color, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the dialog for yes/no confirmation
|
||||||
|
* no doesn't do anything, yes calls the callback
|
||||||
|
* @param title the title of the dialog
|
||||||
|
* @param message the message of the dialog
|
||||||
|
* @param callback the callback to call when the user clicks yes
|
||||||
|
*/
|
||||||
|
private fun dialogBuilder(title: String, message: String, callback: () -> Unit) {
|
||||||
|
val alertDialog = android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton("Yes") { dialog, _ ->
|
||||||
|
callback()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNegativeButton("No") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val dialog = alertDialog.show()
|
||||||
|
dialog?.window?.setDimAmount(0.8f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val usernameColors: Array<String> = arrayOf(
|
||||||
|
"#9932cc",
|
||||||
|
"#a020f0",
|
||||||
|
"#8b008b",
|
||||||
|
"#7b68ee",
|
||||||
|
"#da70d6",
|
||||||
|
"#dda0dd",
|
||||||
|
"#ffe4b5",
|
||||||
|
"#f0e68c",
|
||||||
|
"#ffb6c1",
|
||||||
|
"#fa8072",
|
||||||
|
"#b03060",
|
||||||
|
"#ff1493",
|
||||||
|
"#ff00ff",
|
||||||
|
"#ff69b4",
|
||||||
|
"#dc143c",
|
||||||
|
"#8b0000",
|
||||||
|
"#ff0000",
|
||||||
|
"#a0522d",
|
||||||
|
"#f4a460",
|
||||||
|
"#b8860b",
|
||||||
|
"#ffa500",
|
||||||
|
"#d2691e",
|
||||||
|
"#ff6347",
|
||||||
|
"#808000",
|
||||||
|
"#ffd700",
|
||||||
|
"#ffff54",
|
||||||
|
"#8fbc8f",
|
||||||
|
"#3cb371",
|
||||||
|
"#008000",
|
||||||
|
"#00fa9a",
|
||||||
|
"#98fb98",
|
||||||
|
"#00ff00",
|
||||||
|
"#adff2f",
|
||||||
|
"#32cd32",
|
||||||
|
"#556b2f",
|
||||||
|
"#9acd32",
|
||||||
|
"#7fffd4",
|
||||||
|
"#2f4f4f",
|
||||||
|
"#5f9ea0",
|
||||||
|
"#87ceeb",
|
||||||
|
"#00bfff",
|
||||||
|
"#00ffff",
|
||||||
|
"#1e90ff",
|
||||||
|
"#4682b4",
|
||||||
|
"#0000ff",
|
||||||
|
"#0000cd",
|
||||||
|
"#00008b",
|
||||||
|
"#191970",
|
||||||
|
"#ffffff",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,707 @@
|
|||||||
|
package ani.dantotsu.media.comments
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Context.INPUT_METHOD_SERVICE
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.core.animation.doOnEnd
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.buildMarkwon
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.comments.Comment
|
||||||
|
import ani.dantotsu.connections.comments.CommentResponse
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
|
import ani.dantotsu.databinding.FragmentCommentsBinding
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import com.xwray.groupie.Section
|
||||||
|
import io.noties.markwon.editor.MarkwonEditor
|
||||||
|
import io.noties.markwon.editor.MarkwonEditorTextWatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
class CommentsFragment : Fragment() {
|
||||||
|
lateinit var binding: FragmentCommentsBinding
|
||||||
|
lateinit var activity: MediaDetailsActivity
|
||||||
|
private var interactionState = InteractionState.NONE
|
||||||
|
private var commentWithInteraction: CommentItem? = null
|
||||||
|
private val section = Section()
|
||||||
|
private val adapter = GroupieAdapter()
|
||||||
|
private var tag: Int? = null
|
||||||
|
private var filterTag: Int? = null
|
||||||
|
private var mediaId: Int = -1
|
||||||
|
var mediaName: String = ""
|
||||||
|
private var backgroundColor: Int = 0
|
||||||
|
var pagesLoaded = 1
|
||||||
|
var totalPages = 1
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentCommentsBinding.inflate(inflater, container, false)
|
||||||
|
binding.commentsLayout.isNestedScrollingEnabled = true
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
activity = requireActivity() as MediaDetailsActivity
|
||||||
|
//get the media id from the intent
|
||||||
|
val mediaId = arguments?.getInt("mediaId") ?: -1
|
||||||
|
mediaName = arguments?.getString("mediaName") ?: "unknown"
|
||||||
|
if (mediaId == -1) {
|
||||||
|
snackString("Invalid Media ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mediaId = mediaId
|
||||||
|
backgroundColor = (binding.root.background as? ColorDrawable)?.color ?: 0
|
||||||
|
|
||||||
|
val markwon = buildMarkwon(activity, fragment = this@CommentsFragment)
|
||||||
|
|
||||||
|
activity.binding.commentUserAvatar.loadImage(Anilist.avatar)
|
||||||
|
val markwonEditor = MarkwonEditor.create(markwon)
|
||||||
|
activity.binding.commentInput.addTextChangedListener(
|
||||||
|
MarkwonEditorTextWatcher.withProcess(
|
||||||
|
markwonEditor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.commentsRefresh.setOnRefreshListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loadAndDisplayComments()
|
||||||
|
binding.commentsRefresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentsList.adapter = adapter
|
||||||
|
binding.commentsList.layoutManager = LinearLayoutManager(activity)
|
||||||
|
|
||||||
|
if (CommentsAPI.authToken != null) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val commentId = arguments?.getInt("commentId")
|
||||||
|
if (commentId != null && commentId > 0) {
|
||||||
|
loadSingleComment(commentId)
|
||||||
|
} else {
|
||||||
|
loadAndDisplayComments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Not logged in")
|
||||||
|
activity.binding.commentMessageContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentSort.setOnClickListener { sortView ->
|
||||||
|
fun sortComments(sortOrder: String) {
|
||||||
|
val groups = section.groups
|
||||||
|
when (sortOrder) {
|
||||||
|
"newest" -> groups.sortByDescending { CommentItem.timestampToMillis((it as CommentItem).comment.timestamp) }
|
||||||
|
"oldest" -> groups.sortBy { CommentItem.timestampToMillis((it as CommentItem).comment.timestamp) }
|
||||||
|
"highest_rated" -> groups.sortByDescending { (it as CommentItem).comment.upvotes - it.comment.downvotes }
|
||||||
|
"lowest_rated" -> groups.sortBy { (it as CommentItem).comment.upvotes - it.comment.downvotes }
|
||||||
|
}
|
||||||
|
section.update(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
val popup = PopupMenu(activity, sortView)
|
||||||
|
popup.setOnMenuItemClickListener { item ->
|
||||||
|
val sortOrder = when (item.itemId) {
|
||||||
|
R.id.comment_sort_newest -> "newest"
|
||||||
|
R.id.comment_sort_oldest -> "oldest"
|
||||||
|
R.id.comment_sort_highest_rated -> "highest_rated"
|
||||||
|
R.id.comment_sort_lowest_rated -> "lowest_rated"
|
||||||
|
else -> return@setOnMenuItemClickListener false
|
||||||
|
}
|
||||||
|
PrefManager.setVal(PrefName.CommentSortOrder, sortOrder)
|
||||||
|
if (totalPages > pagesLoaded) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loadAndDisplayComments()
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sortComments(sortOrder)
|
||||||
|
}
|
||||||
|
binding.commentsList.scrollToPosition(0)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
popup.inflate(R.menu.comments_sort_menu)
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentFilter.setOnClickListener {
|
||||||
|
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup)
|
||||||
|
.setTitle("Enter a chapter/episode number tag")
|
||||||
|
.setView(R.layout.dialog_edittext)
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
val editText =
|
||||||
|
(dialog as AlertDialog).findViewById<EditText>(R.id.dialogEditText)
|
||||||
|
val text = editText?.text.toString()
|
||||||
|
filterTag = text.toIntOrNull()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loadAndDisplayComments()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNeutralButton("Clear") { dialog, _ ->
|
||||||
|
filterTag = null
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loadAndDisplayComments()
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
filterTag = null
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val dialog = alertDialog.show()
|
||||||
|
dialog?.window?.setDimAmount(0.8f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFetching = false
|
||||||
|
binding.commentsList.setOnTouchListener(
|
||||||
|
object : View.OnTouchListener {
|
||||||
|
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||||
|
if (event?.action == MotionEvent.ACTION_UP) {
|
||||||
|
if (!binding.commentsList.canScrollVertically(1) && !isFetching &&
|
||||||
|
(binding.commentsList.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.commentsList.adapter!!.itemCount - 1)
|
||||||
|
) {
|
||||||
|
if (pagesLoaded < totalPages && totalPages > 1) {
|
||||||
|
binding.commentBottomRefresh.visibility = View.VISIBLE
|
||||||
|
loadMoreComments()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
kotlinx.coroutines.delay(1000)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.commentBottomRefresh.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//snackString("No more comments") fix spam?
|
||||||
|
Logger.log("No more comments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadMoreComments() {
|
||||||
|
isFetching = true
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val comments = fetchComments()
|
||||||
|
comments?.comments?.forEach { comment ->
|
||||||
|
updateUIWithComment(comment)
|
||||||
|
}
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
pagesLoaded++
|
||||||
|
isFetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchComments(): CommentResponse? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getCommentsForId(
|
||||||
|
mediaId,
|
||||||
|
pagesLoaded + 1,
|
||||||
|
filterTag,
|
||||||
|
PrefManager.getVal(PrefName.CommentSortOrder, "newest")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//adds additional comments to the section
|
||||||
|
private suspend fun updateUIWithComment(comment: Comment) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(
|
||||||
|
CommentItem(
|
||||||
|
comment,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.binding.commentInput.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: android.text.Editable?) {
|
||||||
|
if ((activity.binding.commentInput.text.length) > 300) {
|
||||||
|
activity.binding.commentInput.text.delete(
|
||||||
|
300,
|
||||||
|
activity.binding.commentInput.text.length
|
||||||
|
)
|
||||||
|
snackString("Comment cannot be longer than 300 characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.binding.commentInput.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (hasFocus) {
|
||||||
|
val targetWidth = activity.binding.commentInputLayout.width -
|
||||||
|
activity.binding.commentLabel.width -
|
||||||
|
activity.binding.commentSend.width -
|
||||||
|
activity.binding.commentUserAvatar.width - 12 + 16
|
||||||
|
val anim = ValueAnimator.ofInt(activity.binding.commentInput.width, targetWidth)
|
||||||
|
anim.addUpdateListener { valueAnimator ->
|
||||||
|
val layoutParams = activity.binding.commentInput.layoutParams
|
||||||
|
layoutParams.width = valueAnimator.animatedValue as Int
|
||||||
|
activity.binding.commentInput.layoutParams = layoutParams
|
||||||
|
}
|
||||||
|
anim.duration = 300
|
||||||
|
|
||||||
|
anim.start()
|
||||||
|
anim.doOnEnd {
|
||||||
|
activity.binding.commentLabel.visibility = View.VISIBLE
|
||||||
|
activity.binding.commentSend.visibility = View.VISIBLE
|
||||||
|
activity.binding.commentLabel.animate().translationX(0f).setDuration(300)
|
||||||
|
.start()
|
||||||
|
activity.binding.commentSend.animate().translationX(0f).setDuration(300).start()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.binding.commentLabel.setOnClickListener {
|
||||||
|
//alert dialog to enter a number, with a cancel and ok button
|
||||||
|
val alertDialog = android.app.AlertDialog.Builder(activity, R.style.MyPopup)
|
||||||
|
.setTitle("Enter a chapter/episode number tag")
|
||||||
|
.setView(R.layout.dialog_edittext)
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
val editText =
|
||||||
|
(dialog as AlertDialog).findViewById<EditText>(R.id.dialogEditText)
|
||||||
|
val text = editText?.text.toString()
|
||||||
|
tag = text.toIntOrNull()
|
||||||
|
if (tag == null) {
|
||||||
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
R.drawable.ic_label_off_24,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
R.drawable.ic_label_24,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNeutralButton("Clear") { dialog, _ ->
|
||||||
|
tag = null
|
||||||
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
R.drawable.ic_label_off_24,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
tag = null
|
||||||
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
R.drawable.ic_label_off_24,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val dialog = alertDialog.show()
|
||||||
|
dialog?.window?.setDimAmount(0.8f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.binding.commentSend.setOnClickListener {
|
||||||
|
if (CommentsAPI.isBanned) {
|
||||||
|
snackString("You are banned from commenting :(")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrefManager.getVal(PrefName.FirstComment)) {
|
||||||
|
showCommentRulesDialog()
|
||||||
|
} else {
|
||||||
|
processComment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
tag = null
|
||||||
|
section.groups.forEach {
|
||||||
|
if (it is CommentItem && it.containsGif()) {
|
||||||
|
it.notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class InteractionState {
|
||||||
|
NONE, EDIT, REPLY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and displays the comments
|
||||||
|
* Called when the activity is created
|
||||||
|
* Or when the user refreshes the comments
|
||||||
|
*/
|
||||||
|
private suspend fun loadAndDisplayComments() {
|
||||||
|
binding.commentsProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.commentsList.visibility = View.GONE
|
||||||
|
adapter.clear()
|
||||||
|
section.clear()
|
||||||
|
|
||||||
|
val comments = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getCommentsForId(
|
||||||
|
mediaId,
|
||||||
|
tag = filterTag,
|
||||||
|
sort = PrefManager.getVal(PrefName.CommentSortOrder, "newest")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedComments = sortComments(comments?.comments)
|
||||||
|
sortedComments.forEach {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(
|
||||||
|
CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
binding.commentsProgressBar.visibility = View.GONE
|
||||||
|
binding.commentsList.visibility = View.VISIBLE
|
||||||
|
adapter.add(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadSingleComment(commentId: Int) {
|
||||||
|
binding.commentsProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.commentsList.visibility = View.GONE
|
||||||
|
adapter.clear()
|
||||||
|
section.clear()
|
||||||
|
|
||||||
|
val comment = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getSingleComment(commentId)
|
||||||
|
}
|
||||||
|
if (comment != null) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(
|
||||||
|
CommentItem(
|
||||||
|
comment,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentsProgressBar.visibility = View.GONE
|
||||||
|
binding.commentsList.visibility = View.VISIBLE
|
||||||
|
adapter.add(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortComments(comments: List<Comment>?): List<Comment> {
|
||||||
|
if (comments == null) return emptyList()
|
||||||
|
return when (PrefManager.getVal(PrefName.CommentSortOrder, "newest")) {
|
||||||
|
"newest" -> comments.sortedByDescending { CommentItem.timestampToMillis(it.timestamp) }
|
||||||
|
"oldest" -> comments.sortedBy { CommentItem.timestampToMillis(it.timestamp) }
|
||||||
|
"highest_rated" -> comments.sortedByDescending { it.upvotes - it.downvotes }
|
||||||
|
"lowest_rated" -> comments.sortedBy { it.upvotes - it.downvotes }
|
||||||
|
else -> comments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the old state of the comment input
|
||||||
|
* @return the old state
|
||||||
|
*/
|
||||||
|
private fun resetOldState(): InteractionState {
|
||||||
|
val oldState = interactionState
|
||||||
|
interactionState = InteractionState.NONE
|
||||||
|
return when (oldState) {
|
||||||
|
InteractionState.EDIT -> {
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
activity.binding.commentInput.setText("")
|
||||||
|
val imm = activity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(activity.binding.commentInput.windowToken, 0)
|
||||||
|
commentWithInteraction?.editing(false)
|
||||||
|
InteractionState.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionState.REPLY -> {
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
activity.binding.commentInput.setText("")
|
||||||
|
val imm = activity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(activity.binding.commentInput.windowToken, 0)
|
||||||
|
commentWithInteraction?.replying(false)
|
||||||
|
InteractionState.REPLY
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
InteractionState.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the comment item to edit the comment
|
||||||
|
* Called every time the edit button is clicked
|
||||||
|
* @param comment the comment to edit
|
||||||
|
*/
|
||||||
|
fun editCallback(comment: CommentItem) {
|
||||||
|
if (resetOldState() == InteractionState.EDIT) return
|
||||||
|
commentWithInteraction = comment
|
||||||
|
activity.binding.commentInput.setText(comment.comment.content)
|
||||||
|
activity.binding.commentInput.requestFocus()
|
||||||
|
activity.binding.commentInput.setSelection(activity.binding.commentInput.text.length)
|
||||||
|
val imm = activity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(activity.binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
interactionState = InteractionState.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the comment item to reply to the comment
|
||||||
|
* Called every time the reply button is clicked
|
||||||
|
* @param comment the comment to reply to
|
||||||
|
*/
|
||||||
|
fun replyCallback(comment: CommentItem) {
|
||||||
|
if (resetOldState() == InteractionState.REPLY) return
|
||||||
|
commentWithInteraction = comment
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.VISIBLE
|
||||||
|
activity.binding.commentInput.requestFocus()
|
||||||
|
activity.binding.commentInput.setSelection(activity.binding.commentInput.text.length)
|
||||||
|
val imm = activity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(activity.binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
interactionState = InteractionState.REPLY
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun replyTo(comment: CommentItem, username: String) {
|
||||||
|
if (comment.isReplying) {
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.VISIBLE
|
||||||
|
activity.binding.commentReplyTo.text = "Replying to $username"
|
||||||
|
activity.binding.commentReplyToCancel.setOnClickListener {
|
||||||
|
comment.replying(false)
|
||||||
|
replyCallback(comment)
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activity.binding.commentReplyToContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the comment item to view the replies to the comment
|
||||||
|
* @param comment the comment to view the replies of
|
||||||
|
*/
|
||||||
|
fun viewReplyCallback(comment: CommentItem) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val replies = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getRepliesFromId(comment.comment.commentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
replies?.comments?.forEach {
|
||||||
|
val depth =
|
||||||
|
if (comment.commentDepth + 1 > comment.MAX_DEPTH) comment.commentDepth else comment.commentDepth + 1
|
||||||
|
val section =
|
||||||
|
if (comment.commentDepth + 1 > comment.MAX_DEPTH) comment.parentSection else comment.repliesSection
|
||||||
|
if (depth >= comment.MAX_DEPTH) comment.registerSubComment(it.commentId)
|
||||||
|
val newCommentItem = CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
depth
|
||||||
|
)
|
||||||
|
|
||||||
|
section.add(newCommentItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the comment rules dialog
|
||||||
|
* Called when the user tries to comment for the first time
|
||||||
|
*/
|
||||||
|
private fun showCommentRulesDialog() {
|
||||||
|
val alertDialog = android.app.AlertDialog.Builder(activity, R.style.MyPopup)
|
||||||
|
.setTitle("Commenting Rules")
|
||||||
|
.setMessage(
|
||||||
|
"I WILL BAN YOU WITHOUT HESITATION\n" +
|
||||||
|
"1. No racism\n" +
|
||||||
|
"2. No hate speech\n" +
|
||||||
|
"3. No spam\n" +
|
||||||
|
"4. No NSFW content\n" +
|
||||||
|
"6. ENGLISH ONLY\n" +
|
||||||
|
"7. No self promotion\n" +
|
||||||
|
"8. No impersonation\n" +
|
||||||
|
"9. No harassment\n" +
|
||||||
|
"10. No illegal content\n" +
|
||||||
|
"11. Anything you know you shouldn't comment\n"
|
||||||
|
)
|
||||||
|
.setPositiveButton("I Understand") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
PrefManager.setVal(PrefName.FirstComment, false)
|
||||||
|
processComment()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val dialog = alertDialog.show()
|
||||||
|
dialog?.window?.setDimAmount(0.8f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processComment() {
|
||||||
|
val commentText = activity.binding.commentInput.text.toString()
|
||||||
|
if (commentText.isEmpty()) {
|
||||||
|
snackString("Comment cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.binding.commentInput.text.clear()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (interactionState == InteractionState.EDIT) {
|
||||||
|
handleEditComment(commentText)
|
||||||
|
} else {
|
||||||
|
handleNewComment(commentText)
|
||||||
|
tag = null
|
||||||
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
R.drawable.ic_label_off_24,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resetOldState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleEditComment(commentText: String) {
|
||||||
|
val success = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.editComment(
|
||||||
|
commentWithInteraction?.comment?.commentId ?: return@withContext false, commentText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
updateCommentInSection(commentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCommentInSection(commentText: String) {
|
||||||
|
val groups = section.groups
|
||||||
|
groups.forEach { item ->
|
||||||
|
if (item is CommentItem && item.comment.commentId == commentWithInteraction?.comment?.commentId) {
|
||||||
|
updateCommentItem(item, commentText)
|
||||||
|
snackString("Comment edited")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCommentItem(item: CommentItem, commentText: String) {
|
||||||
|
item.comment.content = commentText
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
item.comment.timestamp = dateFormat.format(System.currentTimeMillis())
|
||||||
|
item.notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the new user-added comment
|
||||||
|
* @param commentText the text of the comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
private suspend fun handleNewComment(commentText: String) {
|
||||||
|
val success = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.comment(
|
||||||
|
mediaId,
|
||||||
|
if (interactionState == InteractionState.REPLY) commentWithInteraction?.comment?.commentId else null,
|
||||||
|
commentText,
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
}
|
||||||
|
success?.let {
|
||||||
|
if (interactionState == InteractionState.REPLY) {
|
||||||
|
if (commentWithInteraction == null) return@let
|
||||||
|
val section =
|
||||||
|
if (commentWithInteraction!!.commentDepth + 1 > commentWithInteraction!!.MAX_DEPTH) commentWithInteraction?.parentSection else commentWithInteraction?.repliesSection
|
||||||
|
val depth =
|
||||||
|
if (commentWithInteraction!!.commentDepth + 1 > commentWithInteraction!!.MAX_DEPTH) commentWithInteraction!!.commentDepth else commentWithInteraction!!.commentDepth + 1
|
||||||
|
if (depth >= commentWithInteraction!!.MAX_DEPTH) commentWithInteraction!!.registerSubComment(
|
||||||
|
it.commentId
|
||||||
|
)
|
||||||
|
section?.add(
|
||||||
|
if (commentWithInteraction!!.commentDepth + 1 > commentWithInteraction!!.MAX_DEPTH) 0 else section.itemCount,
|
||||||
|
CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
depth
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
section.add(
|
||||||
|
0,
|
||||||
|
CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(activity, fragment = this@CommentsFragment),
|
||||||
|
section,
|
||||||
|
this@CommentsFragment,
|
||||||
|
backgroundColor,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@@ -32,8 +32,8 @@ data class ImageData(
|
|||||||
try {
|
try {
|
||||||
// Fetch the image
|
// Fetch the image
|
||||||
val response = httpSource.getImage(page)
|
val response = httpSource.getImage(page)
|
||||||
logger("Response: ${response.code}")
|
Logger.log("Response: ${response.code}")
|
||||||
logger("Response: ${response.message}")
|
Logger.log("Response: ${response.message}")
|
||||||
|
|
||||||
// Convert the Response to an InputStream
|
// Convert the Response to an InputStream
|
||||||
val inputStream = response.body.byteStream()
|
val inputStream = response.body.byteStream()
|
||||||
@@ -47,7 +47,7 @@ data class ImageData(
|
|||||||
return@withContext bitmap
|
return@withContext bitmap
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Handle any exceptions
|
// Handle any exceptions
|
||||||
logger("An error occurred: ${e.message}")
|
Logger.log("An error occurred: ${e.message}")
|
||||||
snackString("An error occurred: ${e.message}")
|
snackString("An error occurred: ${e.message}")
|
||||||
return@withContext null
|
return@withContext null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ data class MangaChapter(
|
|||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
var sChapter: SChapter,
|
var sChapter: SChapter,
|
||||||
val scanlator: String? = null,
|
val scanlator: String? = null,
|
||||||
|
val date: Long? = null,
|
||||||
var progress: String? = ""
|
var progress: String? = ""
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(chapter: MangaChapter) : this(
|
constructor(chapter: MangaChapter) : this(
|
||||||
@@ -21,7 +22,8 @@ data class MangaChapter(
|
|||||||
chapter.title,
|
chapter.title,
|
||||||
chapter.description,
|
chapter.description,
|
||||||
chapter.sChapter,
|
chapter.sChapter,
|
||||||
chapter.scanlator
|
chapter.scanlator,
|
||||||
|
chapter.date
|
||||||
)
|
)
|
||||||
|
|
||||||
private val images = mutableListOf<MangaImage>()
|
private val images = mutableListOf<MangaImage>()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -16,6 +17,9 @@ import ani.dantotsu.databinding.ItemChapterListBinding
|
|||||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -154,25 +158,25 @@ class MangaChapterAdapter(
|
|||||||
if (activeDownloads.contains(chapterNumber)) {
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
// Show spinner
|
// Show spinner
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_sync)
|
binding.itemDownload.setImageResource(R.drawable.ic_sync)
|
||||||
startOrContinueRotation(chapterNumber)
|
startOrContinueRotation(chapterNumber) {
|
||||||
|
binding.itemDownload.rotation = 0f
|
||||||
|
}
|
||||||
} else if (downloadedChapters.contains(chapterNumber)) {
|
} else if (downloadedChapters.contains(chapterNumber)) {
|
||||||
// Show checkmark
|
// Show checkmark
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
|
binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
|
||||||
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
|
|
||||||
binding.itemDownload.postDelayed({
|
binding.itemDownload.postDelayed({
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
|
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
|
||||||
binding.itemDownload.rotation = 0f
|
binding.itemDownload.rotation = 0f
|
||||||
//binding.itemDownload.setColorFilter(typedValue2.data)
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} else {
|
} else {
|
||||||
// Show download icon
|
// Show download icon
|
||||||
binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
|
binding.itemDownload.setImageResource(R.drawable.ic_download_24)
|
||||||
binding.itemDownload.rotation = 0f
|
binding.itemDownload.rotation = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startOrContinueRotation(chapterNumber: String) {
|
private fun startOrContinueRotation(chapterNumber: String, resetRotation: () -> Unit) {
|
||||||
if (!isRotationCoroutineRunningFor(chapterNumber)) {
|
if (!isRotationCoroutineRunningFor(chapterNumber)) {
|
||||||
val scope = fragment.lifecycle.coroutineScope
|
val scope = fragment.lifecycle.coroutineScope
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -187,6 +191,7 @@ class MangaChapterAdapter(
|
|||||||
}
|
}
|
||||||
// Remove chapter number from active coroutines set
|
// Remove chapter number from active coroutines set
|
||||||
activeCoroutines.remove(chapterNumber)
|
activeCoroutines.remove(chapterNumber)
|
||||||
|
resetRotation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,11 +262,12 @@ class MangaChapterAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is ChapterCompactViewHolder -> {
|
is ChapterCompactViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt()
|
val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt()
|
||||||
binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: ep.number
|
binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: ep.number
|
||||||
@@ -287,8 +293,25 @@ class MangaChapterAdapter(
|
|||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
holder.bind(ep.number, ep.progress)
|
holder.bind(ep.number, ep.progress)
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
binding.itemChapterNumber.text = ep.number
|
binding.itemChapterNumber.text = ep.number
|
||||||
|
|
||||||
|
if (ep.date != null) {
|
||||||
|
binding.itemChapterDateLayout.visibility = View.VISIBLE
|
||||||
|
binding.itemChapterDate.text = formatDate(ep.date)
|
||||||
|
}
|
||||||
|
if (ep.scanlator != null) {
|
||||||
|
binding.itemChapterDateLayout.visibility = View.VISIBLE
|
||||||
|
binding.itemChapterScan.text = ep.scanlator.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(
|
||||||
|
Locale.ROOT
|
||||||
|
) else it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (formatDate(ep.date) == "" || ep.scanlator == null) {
|
||||||
|
binding.itemChapterDateDivider.visibility = View.GONE
|
||||||
|
} else binding.itemChapterDateDivider.visibility = View.VISIBLE
|
||||||
|
|
||||||
if (ep.progress.isNullOrEmpty()) {
|
if (ep.progress.isNullOrEmpty()) {
|
||||||
binding.itemChapterTitle.visibility = View.GONE
|
binding.itemChapterTitle.visibility = View.GONE
|
||||||
} else binding.itemChapterTitle.visibility = View.VISIBLE
|
} else binding.itemChapterTitle.visibility = View.VISIBLE
|
||||||
@@ -321,6 +344,33 @@ class MangaChapterAdapter(
|
|||||||
fun updateType(t: Int) {
|
fun updateType(t: Int) {
|
||||||
type = t
|
type = t
|
||||||
}
|
}
|
||||||
|
private fun formatDate(timestamp: Long?): String {
|
||||||
|
timestamp ?: return "" // Return empty string if timestamp is null
|
||||||
|
|
||||||
|
val targetDate = Date(timestamp)
|
||||||
|
|
||||||
|
if (targetDate < Date(946684800000L)) { // January 1, 2000 (who want dates before that?)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentDate = Date()
|
||||||
|
val difference = currentDate.time - targetDate.time
|
||||||
|
|
||||||
|
return when (val daysDifference = difference / (1000 * 60 * 60 * 24)) {
|
||||||
|
0L -> {
|
||||||
|
val hoursDifference = difference / (1000 * 60 * 60)
|
||||||
|
val minutesDifference = (difference / (1000 * 60)) % 60
|
||||||
|
|
||||||
|
when {
|
||||||
|
hoursDifference > 0 -> "$hoursDifference hour${if (hoursDifference > 1) "s" else ""} ago"
|
||||||
|
minutesDifference > 0 -> "$minutesDifference minute${if (minutesDifference > 1) "s" else ""} ago"
|
||||||
|
else -> "Just now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1L -> "1 day ago"
|
||||||
|
in 2..6 -> "$daysDifference days ago"
|
||||||
|
else -> SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(targetDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package ani.dantotsu.media.manga
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -13,6 +12,7 @@ import android.widget.ImageButton
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.NumberPicker
|
import android.widget.NumberPicker
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
@@ -28,9 +28,11 @@ import ani.dantotsu.others.webview.CookieCatcher
|
|||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.settings.FAQActivity
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
@@ -62,6 +64,12 @@ class MangaReadAdapter(
|
|||||||
_binding = binding
|
_binding = binding
|
||||||
binding.sourceTitle.setText(R.string.chaps)
|
binding.sourceTitle.setText(R.string.chaps)
|
||||||
|
|
||||||
|
//Fuck u launch
|
||||||
|
binding.faqbutton.setOnClickListener {
|
||||||
|
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
|
||||||
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
|
}
|
||||||
|
|
||||||
//Wrong Title
|
//Wrong Title
|
||||||
binding.animeSourceSearch.setOnClickListener {
|
binding.animeSourceSearch.setOnClickListener {
|
||||||
SourceSearchDialogFragment().show(
|
SourceSearchDialogFragment().show(
|
||||||
@@ -69,12 +77,9 @@ class MangaReadAdapter(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
|
val offline =
|
||||||
"Dantotsu",
|
if (!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||||
Context.MODE_PRIVATE
|
) View.GONE else View.VISIBLE
|
||||||
)
|
|
||||||
?.getBoolean("offlineMode", false) == true
|
|
||||||
) View.GONE else View.VISIBLE
|
|
||||||
|
|
||||||
binding.animeSourceNameContainer.visibility = offline
|
binding.animeSourceNameContainer.visibility = offline
|
||||||
binding.animeSourceSettings.visibility = offline
|
binding.animeSourceSettings.visibility = offline
|
||||||
@@ -144,7 +149,8 @@ class MangaReadAdapter(
|
|||||||
R.drawable.ic_round_notifications_none_24,
|
R.drawable.ic_round_notifications_none_24,
|
||||||
R.color.bg_opp,
|
R.color.bg_opp,
|
||||||
R.color.violet_400,
|
R.color.violet_400,
|
||||||
fragment.subscribed
|
fragment.subscribed,
|
||||||
|
true
|
||||||
) {
|
) {
|
||||||
fragment.onNotificationPressed(it, binding.animeSource.text.toString())
|
fragment.onNotificationPressed(it, binding.animeSource.text.toString())
|
||||||
}
|
}
|
||||||
@@ -152,7 +158,7 @@ class MangaReadAdapter(
|
|||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
|
|
||||||
binding.animeSourceSubscribe.setOnLongClickListener {
|
binding.animeSourceSubscribe.setOnLongClickListener {
|
||||||
openSettings(fragment.requireContext(), getChannelId(true, media.id))
|
openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeNestedButton.setOnClickListener {
|
binding.animeNestedButton.setOnClickListener {
|
||||||
@@ -163,7 +169,8 @@ class MangaReadAdapter(
|
|||||||
var refresh = false
|
var refresh = false
|
||||||
var run = false
|
var run = false
|
||||||
var reversed = media.selected!!.recyclerReversed
|
var reversed = media.selected!!.recyclerReversed
|
||||||
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.mangaDefaultView
|
var style =
|
||||||
|
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView)
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
dialogBinding.animeSourceTop.setOnClickListener {
|
dialogBinding.animeSourceTop.setOnClickListener {
|
||||||
@@ -246,17 +253,41 @@ class MangaReadAdapter(
|
|||||||
if (options.count() > 1) View.VISIBLE else View.GONE
|
if (options.count() > 1) View.VISIBLE else View.GONE
|
||||||
dialogBinding.scanlatorNo.text = "${options.count()}"
|
dialogBinding.scanlatorNo.text = "${options.count()}"
|
||||||
dialogBinding.animeScanlatorTop.setOnClickListener {
|
dialogBinding.animeScanlatorTop.setOnClickListener {
|
||||||
val dialogView2 =
|
val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
||||||
LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
val checkboxContainer = dialogView2.findViewById<LinearLayout>(R.id.checkboxContainer)
|
||||||
val checkboxContainer =
|
val tickAllButton = dialogView2.findViewById<ImageButton>(R.id.toggleButton)
|
||||||
dialogView2.findViewById<LinearLayout>(R.id.checkboxContainer)
|
|
||||||
|
// Function to get the right image resource for the toggle button
|
||||||
|
fun getToggleImageResource(container: ViewGroup): Int {
|
||||||
|
var allChecked = true
|
||||||
|
var allUnchecked = true
|
||||||
|
|
||||||
|
for (i in 0 until container.childCount) {
|
||||||
|
val checkBox = container.getChildAt(i) as CheckBox
|
||||||
|
if (!checkBox.isChecked) {
|
||||||
|
allChecked = false
|
||||||
|
} else {
|
||||||
|
allUnchecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
allChecked -> R.drawable.untick_all_boxes
|
||||||
|
allUnchecked -> R.drawable.tick_all_boxes
|
||||||
|
else -> R.drawable.invert_all_boxes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamically add checkboxes
|
// Dynamically add checkboxes
|
||||||
options.forEach { option ->
|
options.forEach { option ->
|
||||||
val checkBox = CheckBox(currContext()).apply {
|
val checkBox = CheckBox(currContext()).apply {
|
||||||
text = option
|
text = option
|
||||||
|
setOnCheckedChangeListener { _, _ ->
|
||||||
|
// Update image resource when you change a checkbox
|
||||||
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//set checked if it's already selected
|
|
||||||
|
// Set checked if its already selected
|
||||||
if (media.selected!!.scanlators != null) {
|
if (media.selected!!.scanlators != null) {
|
||||||
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
|
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
|
||||||
scanlatorSelectionListener?.onScanlatorsSelected()
|
scanlatorSelectionListener?.onScanlatorsSelected()
|
||||||
@@ -270,7 +301,6 @@ class MangaReadAdapter(
|
|||||||
val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
||||||
.setView(dialogView2)
|
.setView(dialogView2)
|
||||||
.setPositiveButton("OK") { _, _ ->
|
.setPositiveButton("OK") { _, _ ->
|
||||||
//add unchecked to hidden
|
|
||||||
hiddenScanlators.clear()
|
hiddenScanlators.clear()
|
||||||
for (i in 0 until checkboxContainer.childCount) {
|
for (i in 0 until checkboxContainer.childCount) {
|
||||||
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
||||||
@@ -284,6 +314,21 @@ class MangaReadAdapter(
|
|||||||
.setNegativeButton("Cancel", null)
|
.setNegativeButton("Cancel", null)
|
||||||
.show()
|
.show()
|
||||||
dialog.window?.setDimAmount(0.8f)
|
dialog.window?.setDimAmount(0.8f)
|
||||||
|
|
||||||
|
// Standard image resource
|
||||||
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
|
||||||
|
// Listens to ticked checkboxes and changes image resource accordingly
|
||||||
|
tickAllButton.setOnClickListener {
|
||||||
|
// Toggle checkboxes
|
||||||
|
for (i in 0 until checkboxContainer.childCount) {
|
||||||
|
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
||||||
|
checkBox.isChecked = !checkBox.isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update image resource
|
||||||
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup)
|
nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup)
|
||||||
@@ -392,7 +437,8 @@ class MangaReadAdapter(
|
|||||||
if (media.manga?.chapters != null) {
|
if (media.manga?.chapters != null) {
|
||||||
val chapters = media.manga.chapters!!.keys.toTypedArray()
|
val chapters = media.manga.chapters!!.keys.toTypedArray()
|
||||||
val anilistEp = (media.userProgress ?: 0).plus(1)
|
val anilistEp = (media.userProgress ?: 0).plus(1)
|
||||||
val appEp = loadData<String>("${media.id}_current_chp")?.toIntOrNull() ?: 1
|
val appEp = PrefManager.getNullableCustomVal("${media.id}_current_chp", null, String::class.java)
|
||||||
|
?.toIntOrNull() ?: 1
|
||||||
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
||||||
val filteredChapters = chapters.filter { chapterKey ->
|
val filteredChapters = chapters.filter { chapterKey ->
|
||||||
val chapter = media.manga.chapters!![chapterKey]!!
|
val chapter = media.manga.chapters!![chapterKey]!!
|
||||||
@@ -435,13 +481,17 @@ class MangaReadAdapter(
|
|||||||
binding.animeSourceContinue.visibility = View.GONE
|
binding.animeSourceContinue.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.animeSourceProgressBar.visibility = View.GONE
|
binding.animeSourceProgressBar.visibility = View.GONE
|
||||||
if (media.manga.chapters!!.isNotEmpty())
|
if (media.manga.chapters!!.isNotEmpty()) {
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.animeSourceNotFound.visibility = View.GONE
|
||||||
else
|
binding.faqbutton.visibility = View.GONE
|
||||||
|
} else {
|
||||||
binding.animeSourceNotFound.visibility = View.VISIBLE
|
binding.animeSourceNotFound.visibility = View.VISIBLE
|
||||||
|
binding.faqbutton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.animeSourceContinue.visibility = View.GONE
|
binding.animeSourceContinue.visibility = View.GONE
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.animeSourceNotFound.visibility = View.GONE
|
||||||
|
binding.faqbutton.visibility = View.GONE
|
||||||
clearChips()
|
clearChips()
|
||||||
binding.animeSourceProgressBar.visibility = View.VISIBLE
|
binding.animeSourceProgressBar.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
@@ -42,15 +43,12 @@ import ani.dantotsu.parsers.DynamicMangaParser
|
|||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaParser
|
import ani.dantotsu.parsers.MangaParser
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
|
||||||
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||||
import ani.dantotsu.subcriptions.Notifications
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigationrail.NavigationRailView
|
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -86,9 +84,6 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
var continueEp: Boolean = false
|
var continueEp: Boolean = false
|
||||||
var loaded = false
|
var loaded = false
|
||||||
|
|
||||||
val uiSettings = loadData("ui_settings", toast = false)
|
|
||||||
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -139,6 +134,23 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
|
|
||||||
binding.animeSourceRecycler.layoutManager = gridLayoutManager
|
binding.animeSourceRecycler.layoutManager = gridLayoutManager
|
||||||
|
|
||||||
|
binding.ScrollTop.setOnClickListener {
|
||||||
|
binding.animeSourceRecycler.scrollToPosition(10)
|
||||||
|
binding.animeSourceRecycler.smoothScrollToPosition(0)
|
||||||
|
}
|
||||||
|
binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val position = gridLayoutManager.findFirstVisibleItemPosition()
|
||||||
|
if (position > 2) {
|
||||||
|
binding.ScrollTop.translationY = -navBarHeight.toFloat()
|
||||||
|
binding.ScrollTop.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.ScrollTop.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
model.scrolledToTop.observe(viewLifecycleOwner) {
|
model.scrolledToTop.observe(viewLifecycleOwner) {
|
||||||
if (it) binding.animeSourceRecycler.scrollToPosition(0)
|
if (it) binding.animeSourceRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
@@ -154,7 +166,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
|
|
||||||
subscribed =
|
subscribed =
|
||||||
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
SubscriptionHelper.getSubscriptions().containsKey(media.id)
|
||||||
|
|
||||||
style = media.selected!!.recyclerStyle
|
style = media.selected!!.recyclerStyle
|
||||||
reverse = media.selected!!.recyclerReversed
|
reverse = media.selected!!.recyclerReversed
|
||||||
@@ -165,10 +177,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
||||||
headerAdapter.scanlatorSelectionListener = this
|
headerAdapter.scanlatorSelectionListener = this
|
||||||
chapterAdapter =
|
chapterAdapter =
|
||||||
MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
MangaChapterAdapter(
|
||||||
|
style ?: PrefManager.getVal(PrefName.MangaDefaultView), media, this
|
||||||
|
)
|
||||||
|
|
||||||
for (download in downloadManager.mangaDownloadedTypes) {
|
for (download in downloadManager.mangaDownloadedTypes) {
|
||||||
chapterAdapter.stopDownload(download.chapter)
|
if (download.title == media.mainName()) {
|
||||||
|
chapterAdapter.stopDownload(download.chapter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter =
|
binding.animeSourceRecycler.adapter =
|
||||||
@@ -284,7 +300,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
model.mangaReadSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
model.mangaReadSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
||||||
selected.sourceIndex = i
|
selected.sourceIndex = i
|
||||||
selected.server = null
|
selected.server = null
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
return model.mangaReadSources?.get(i)!!
|
return model.mangaReadSources?.get(i)!!
|
||||||
}
|
}
|
||||||
@@ -292,14 +308,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
fun onLangChange(i: Int) {
|
fun onLangChange(i: Int) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
selected.langIndex = i
|
selected.langIndex = i
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScanlatorChange(list: List<String>) {
|
fun onScanlatorChange(list: List<String>) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
selected.scanlators = list
|
selected.scanlators = list
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +328,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
reverse = rev
|
reverse = rev
|
||||||
media.selected!!.recyclerStyle = style
|
media.selected!!.recyclerStyle = style
|
||||||
media.selected!!.recyclerReversed = reverse
|
media.selected!!.recyclerReversed = reverse
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,23 +336,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
media.selected!!.chip = i
|
media.selected!!.chip = i
|
||||||
start = s
|
start = s
|
||||||
end = e
|
end = e
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscribed = false
|
var subscribed = false
|
||||||
fun onNotificationPressed(subscribed: Boolean, source: String) {
|
fun onNotificationPressed(subscribed: Boolean, source: String) {
|
||||||
this.subscribed = subscribed
|
this.subscribed = subscribed
|
||||||
saveSubscription(requireContext(), media, subscribed)
|
saveSubscription(media, subscribed)
|
||||||
if (!subscribed)
|
|
||||||
Notifications.deleteChannel(requireContext(), getChannelId(true, media.id))
|
|
||||||
else
|
|
||||||
Notifications.createChannel(
|
|
||||||
requireContext(),
|
|
||||||
MANGA_GROUP,
|
|
||||||
getChannelId(true, media.id),
|
|
||||||
media.userPreferredName
|
|
||||||
)
|
|
||||||
snackString(
|
snackString(
|
||||||
if (subscribed) getString(R.string.subscribed_notification, source)
|
if (subscribed) getString(R.string.subscribed_notification, source)
|
||||||
else getString(R.string.unsubscribed_notification)
|
else getString(R.string.unsubscribed_notification)
|
||||||
@@ -352,11 +359,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||||
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||||
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||||
try {
|
activity.tabLayout.setVisibility(visibility)
|
||||||
activity.findViewById<CustomBottomNavBar>(R.id.mediaTab).visibility = visibility
|
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
activity.findViewById<NavigationRailView>(R.id.mediaTab).visibility = visibility
|
|
||||||
}
|
|
||||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
if (show) View.GONE else View.VISIBLE
|
if (show) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
@@ -368,12 +371,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
if (allSettings.size > 1) {
|
if (allSettings.size > 1) {
|
||||||
val names =
|
val names =
|
||||||
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
|
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
|
||||||
var selectedIndex = 0
|
|
||||||
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
||||||
.setTitle("Select a Source")
|
.setTitle("Select a Source")
|
||||||
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
|
.setSingleChoiceItems(names, -1) { dialog, which ->
|
||||||
selectedIndex = which
|
selectedSetting = allSettings[which]
|
||||||
selectedSetting = allSettings[selectedIndex]
|
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
|
||||||
@@ -420,7 +421,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
media.manga?.chapters?.get(i)?.let {
|
media.manga?.chapters?.get(i)?.let {
|
||||||
media.manga?.selectedChapter = i
|
media.manga?.selectedChapter = i
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!)
|
||||||
ChapterLoaderDialog.newInstance(it, true)
|
ChapterLoaderDialog.newInstance(it, true)
|
||||||
.show(requireActivity().supportFragmentManager, "dialog")
|
.show(requireActivity().supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
@@ -558,7 +559,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
selected.latest =
|
selected.latest =
|
||||||
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
||||||
|
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected)
|
||||||
headerAdapter.handleChapters()
|
headerAdapter.handleChapters()
|
||||||
chapterAdapter.notifyItemRangeRemoved(0, chapterAdapter.arr.size)
|
chapterAdapter.notifyItemRangeRemoved(0, chapterAdapter.arr.size)
|
||||||
var arr: ArrayList<MangaChapter> = arrayListOf()
|
var arr: ArrayList<MangaChapter> = arrayListOf()
|
||||||
@@ -572,7 +573,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
arr = (arr.reversed() as? ArrayList<MangaChapter>) ?: arr
|
arr = (arr.reversed() as? ArrayList<MangaChapter>) ?: arr
|
||||||
}
|
}
|
||||||
chapterAdapter.arr = arr
|
chapterAdapter.arr = arr
|
||||||
chapterAdapter.updateType(style ?: uiSettings.mangaDefaultView)
|
chapterAdapter.updateType(style ?: PrefManager.getVal(PrefName.MangaDefaultView))
|
||||||
chapterAdapter.notifyItemRangeInserted(0, arr.size)
|
chapterAdapter.notifyItemRangeInserted(0, arr.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,6 +588,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
binding.mediaInfoProgressBar.visibility = progress
|
binding.mediaInfoProgressBar.visibility = progress
|
||||||
binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
||||||
|
|
||||||
|
requireActivity().setNavigationTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ abstract class BaseImageAdapter(
|
|||||||
val activity: MangaReaderActivity,
|
val activity: MangaReaderActivity,
|
||||||
chapter: MangaChapter
|
chapter: MangaChapter
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
val settings = activity.settings.default
|
val settings = activity.defaultSettings
|
||||||
val uiSettings = activity.uiSettings
|
|
||||||
val images = chapter.images()
|
val images = chapter.images()
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import ani.dantotsu.media.manga.MangaChapter
|
|||||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT
|
import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT
|
import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED
|
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
@@ -83,7 +85,7 @@ open class ImageAdapter(
|
|||||||
imageView.minScale = scale
|
imageView.minScale = scale
|
||||||
|
|
||||||
ObjectAnimator.ofFloat(parent, "alpha", 0f, 1f)
|
ObjectAnimator.ofFloat(parent, "alpha", 0f, 1f)
|
||||||
.setDuration((400 * uiSettings.animationSpeed).toLong())
|
.setDuration((400 * PrefManager.getVal<Float>(PrefName.AnimationSpeed)).toLong())
|
||||||
.start()
|
.start()
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.connections.discord.Discord
|
import ani.dantotsu.connections.discord.Discord
|
||||||
import ani.dantotsu.connections.discord.DiscordService
|
import ani.dantotsu.connections.discord.DiscordService
|
||||||
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
||||||
@@ -41,27 +42,29 @@ import ani.dantotsu.media.manga.MangaCache
|
|||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.media.manga.MangaNameAdapter
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaImage
|
import ani.dantotsu.parsers.MangaImage
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.settings.CurrentReaderSettings
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon
|
import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.*
|
import ani.dantotsu.settings.CurrentReaderSettings.Directions.*
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.*
|
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.*
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
||||||
import ani.dantotsu.settings.ReaderSettings
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
|
||||||
import com.google.firebase.ktx.Firebase
|
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
@@ -74,6 +77,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
private val model: MediaDetailsViewModel by viewModels()
|
private val model: MediaDetailsViewModel by viewModels()
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
|
|
||||||
|
var defaultSettings = CurrentReaderSettings()
|
||||||
|
|
||||||
private lateinit var media: Media
|
private lateinit var media: Media
|
||||||
private lateinit var chapter: MangaChapter
|
private lateinit var chapter: MangaChapter
|
||||||
private lateinit var chapters: MutableMap<String, MangaChapter>
|
private lateinit var chapters: MutableMap<String, MangaChapter>
|
||||||
@@ -83,14 +88,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var isContVisible = false
|
private var isContVisible = false
|
||||||
private var showProgressDialog = true
|
private var showProgressDialog = true
|
||||||
|
private var hidescrollbar = false
|
||||||
|
|
||||||
//private var progressDialog: AlertDialog.Builder? = null
|
|
||||||
private var maxChapterPage = 0L
|
private var maxChapterPage = 0L
|
||||||
private var currentChapterPage = 0L
|
private var currentChapterPage = 0L
|
||||||
|
|
||||||
lateinit var settings: ReaderSettings
|
|
||||||
lateinit var uiSettings: UserInterfaceSettings
|
|
||||||
|
|
||||||
private var notchHeight: Int? = null
|
private var notchHeight: Int? = null
|
||||||
|
|
||||||
private var imageAdapter: BaseImageAdapter? = null
|
private var imageAdapter: BaseImageAdapter? = null
|
||||||
@@ -98,10 +100,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
var sliding = false
|
var sliding = false
|
||||||
var isAnimating = false
|
var isAnimating = false
|
||||||
|
|
||||||
private var rpc: RPC? = null
|
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !settings.showSystemBars) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
|
||||||
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
||||||
if (displayCutout != null) {
|
if (displayCutout != null) {
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
@@ -122,8 +122,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideBars() {
|
private fun hideSystemBars() {
|
||||||
if (!settings.showSystemBars) hideSystemBars()
|
if (PrefManager.getVal<Boolean>(PrefName.ShowSystemBars))
|
||||||
|
showSystemBarsRetractView()
|
||||||
|
else
|
||||||
|
hideSystemBarsExtendView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -138,30 +141,36 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
LangSet.setLocale(this)
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
binding.mangaReaderBack.setOnClickListener {
|
binding.mangaReaderBack.setOnClickListener {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultSettings = loadReaderSettings("reader_settings") ?: defaultSettings
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
progress { finish() }
|
val chapter = (MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
|
?.minus(1L) ?: 0).toString()
|
||||||
|
if (chapter == "0.0" && PrefManager.getVal(PrefName.ChapterZeroReader)
|
||||||
|
// Not asking individually or incognito
|
||||||
|
&& !showProgressDialog && !PrefManager.getVal<Boolean>(PrefName.Incognito)
|
||||||
|
// Not ...opted out ...already? Somehow?
|
||||||
|
&& PrefManager.getCustomVal("${media.id}_save_progress", true)
|
||||||
|
// Allowing Doujin updates or not one
|
||||||
|
&& if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHReader) else true
|
||||||
|
) {
|
||||||
|
updateProgress(media, chapter)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
progress { finish() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = loadData("reader_settings", this)
|
controllerDuration = (PrefManager.getVal<Float>(PrefName.AnimationSpeed) * 200).toLong()
|
||||||
?: ReaderSettings().apply { saveData("reader_settings", this) }
|
|
||||||
uiSettings = loadData("ui_settings", this) ?: UserInterfaceSettings().apply {
|
|
||||||
saveData(
|
|
||||||
"ui_settings",
|
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
controllerDuration = (uiSettings.animationSpeed * 200).toLong()
|
|
||||||
|
|
||||||
hideBars()
|
hideSystemBars()
|
||||||
|
|
||||||
var pageSliderTimer = Timer()
|
var pageSliderTimer = Timer()
|
||||||
fun pageSliderHide() {
|
fun pageSliderHide() {
|
||||||
@@ -182,7 +191,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
binding.mangaReaderSlider.addOnChangeListener { _, value, fromUser ->
|
binding.mangaReaderSlider.addOnChangeListener { _, value, fromUser ->
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
sliding = true
|
sliding = true
|
||||||
if (settings.default.layout != PAGED)
|
if (defaultSettings.layout != PAGED)
|
||||||
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 }
|
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 }
|
||||||
?: 1))
|
?: 1))
|
||||||
else
|
else
|
||||||
@@ -205,34 +214,30 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
else model.getMedia().value ?: return
|
else model.getMedia().value ?: return
|
||||||
model.setMedia(media)
|
model.setMedia(media)
|
||||||
|
|
||||||
if (settings.autoDetectWebtoon && media.countryOfOrigin != "JP") applyWebtoon(settings.default)
|
if (PrefManager.getVal(PrefName.AutoDetectWebtoon) && media.countryOfOrigin != "JP") applyWebtoon(
|
||||||
settings.default = loadData("${media.id}_current_settings") ?: settings.default
|
defaultSettings
|
||||||
|
)
|
||||||
|
defaultSettings = loadReaderSettings("${media.id}_current_settings") ?: defaultSettings
|
||||||
|
|
||||||
chapters = media.manga?.chapters ?: return
|
chapters = media.manga?.chapters ?: return
|
||||||
chapter = chapters[media.manga!!.selectedChapter] ?: return
|
chapter = chapters[media.manga!!.selectedChapter] ?: return
|
||||||
|
|
||||||
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
||||||
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
|
binding.mangaReaderSource.visibility =
|
||||||
|
if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE
|
||||||
if (model.mangaReadSources!!.names.isEmpty()) {
|
if (model.mangaReadSources!!.names.isEmpty()) {
|
||||||
//try to reload sources
|
//try to reload sources
|
||||||
try {
|
try {
|
||||||
if (media.isAdult) {
|
val mangaSources = MangaSources
|
||||||
val mangaSources = MangaSources
|
val scope = lifecycleScope
|
||||||
val scope = lifecycleScope
|
scope.launch(Dispatchers.IO) {
|
||||||
scope.launch(Dispatchers.IO) {
|
mangaSources.init(
|
||||||
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow, this@MangaReaderActivity)
|
Injekt.get<MangaExtensionManager>().installedExtensionsFlow
|
||||||
}
|
)
|
||||||
model.mangaReadSources = mangaSources
|
|
||||||
} else {
|
|
||||||
val mangaSources = HMangaSources
|
|
||||||
val scope = lifecycleScope
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
|
|
||||||
}
|
|
||||||
model.mangaReadSources = mangaSources
|
|
||||||
}
|
}
|
||||||
|
model.mangaReadSources = mangaSources
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Firebase.crashlytics.recordException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,13 +260,18 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showProgressDialog =
|
showProgressDialog =
|
||||||
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
|
if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal(
|
||||||
?: true else false
|
"${media.id}_progressDialog",
|
||||||
|
true
|
||||||
|
) else false
|
||||||
|
|
||||||
//Chapter Change
|
//Chapter Change
|
||||||
fun change(index: Int) {
|
fun change(index: Int) {
|
||||||
mangaCache.clear()
|
mangaCache.clear()
|
||||||
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
|
PrefManager.setCustomVal(
|
||||||
|
"${media.id}_${chaptersArr[currentChapterIndex]}",
|
||||||
|
currentChapterPage
|
||||||
|
)
|
||||||
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!)
|
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!)
|
||||||
.show(supportFragmentManager, "dialog")
|
.show(supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
@@ -293,16 +303,26 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
binding.mangaReaderNextChapter.performClick()
|
binding.mangaReaderNextChapter.performClick()
|
||||||
}
|
}
|
||||||
binding.mangaReaderNextChapter.setOnClickListener {
|
binding.mangaReaderNextChapter.setOnClickListener {
|
||||||
if (chaptersArr.size > currentChapterIndex + 1) progress { change(currentChapterIndex + 1) }
|
if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||||
else snackString(getString(R.string.next_chapter_not_found))
|
if (currentChapterIndex > 0) change(currentChapterIndex - 1)
|
||||||
|
else snackString(getString(R.string.first_chapter))
|
||||||
|
} else {
|
||||||
|
if (chaptersArr.size > currentChapterIndex + 1) progress { change(currentChapterIndex + 1) }
|
||||||
|
else snackString(getString(R.string.next_chapter_not_found))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Prev Chapter
|
//Prev Chapter
|
||||||
binding.mangaReaderPrevChap.setOnClickListener {
|
binding.mangaReaderPrevChap.setOnClickListener {
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
binding.mangaReaderPreviousChapter.performClick()
|
||||||
}
|
}
|
||||||
binding.mangaReaderPreviousChapter.setOnClickListener {
|
binding.mangaReaderPreviousChapter.setOnClickListener {
|
||||||
if (currentChapterIndex > 0) change(currentChapterIndex - 1)
|
if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||||
else snackString(getString(R.string.first_chapter))
|
if (chaptersArr.size > currentChapterIndex + 1) progress { change(currentChapterIndex + 1) }
|
||||||
|
else snackString(getString(R.string.next_chapter_not_found))
|
||||||
|
} else {
|
||||||
|
if (currentChapterIndex > 0) change(currentChapterIndex - 1)
|
||||||
|
else snackString(getString(R.string.first_chapter))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.getMangaChapter().observe(this) { chap ->
|
model.getMangaChapter().observe(this) { chap ->
|
||||||
@@ -310,18 +330,25 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
chapter = chap
|
chapter = chap
|
||||||
media.manga!!.selectedChapter = chapter.number
|
media.manga!!.selectedChapter = chapter.number
|
||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
saveData("${media.id}_current_chp", chap.number, this)
|
PrefManager.setCustomVal("${media.id}_current_chp", chap.number)
|
||||||
currentChapterIndex = chaptersArr.indexOf(chap.number)
|
currentChapterIndex = chaptersArr.indexOf(chap.number)
|
||||||
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
||||||
binding.mangaReaderNextChap.text =
|
if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||||
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
binding.mangaReaderNextChap.text =
|
||||||
binding.mangaReaderPrevChap.text =
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
|
} else {
|
||||||
|
binding.mangaReaderNextChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
|
}
|
||||||
applySettings()
|
applySettings()
|
||||||
val context = this
|
val context = this
|
||||||
val incognito = context.getSharedPreferences("Dantotsu", 0)
|
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||||
?.getBoolean("incognito", false) ?: false
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
if (isOnline(context) && Discord.token != null && !incognito) {
|
if ((isOnline(context) && !offline) && Discord.token != null && !incognito) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val presence = RPC.createPresence(
|
val presence = RPC.createPresence(
|
||||||
RPC.Companion.RPCData(
|
RPC.Companion.RPCData(
|
||||||
@@ -369,7 +396,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
|
||||||
fun <T> dualPage(callback: () -> T): T? {
|
fun <T> dualPage(callback: () -> T): T? {
|
||||||
return when (settings.default.dualPageMode) {
|
return when (defaultSettings.dualPageMode) {
|
||||||
No -> null
|
No -> null
|
||||||
Automatic -> {
|
Automatic -> {
|
||||||
val orientation = resources.configuration.orientation
|
val orientation = resources.configuration.orientation
|
||||||
@@ -384,29 +411,29 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
fun applySettings() {
|
fun applySettings() {
|
||||||
|
|
||||||
saveData("${media.id}_current_settings", settings.default)
|
saveReaderSettings("${media.id}_current_settings", defaultSettings)
|
||||||
hideBars()
|
hideSystemBars()
|
||||||
|
|
||||||
//true colors
|
//true colors
|
||||||
SubsamplingScaleImageView.setPreferredBitmapConfig(
|
SubsamplingScaleImageView.setPreferredBitmapConfig(
|
||||||
if (settings.default.trueColors) Bitmap.Config.ARGB_8888
|
if (defaultSettings.trueColors) Bitmap.Config.ARGB_8888
|
||||||
else Bitmap.Config.RGB_565
|
else Bitmap.Config.RGB_565
|
||||||
)
|
)
|
||||||
|
|
||||||
//keep screen On
|
//keep screen On
|
||||||
if (settings.default.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
if (defaultSettings.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
binding.mangaReaderPager.unregisterOnPageChangeCallback(pageChangeCallback)
|
binding.mangaReaderPager.unregisterOnPageChangeCallback(pageChangeCallback)
|
||||||
|
|
||||||
currentChapterPage = loadData("${media.id}_${chapter.number}", this) ?: 1
|
currentChapterPage = PrefManager.getCustomVal("${media.id}_${chapter.number}", 1L)
|
||||||
|
|
||||||
val chapImages = chapter.images()
|
val chapImages = chapter.images()
|
||||||
|
|
||||||
maxChapterPage = 0
|
maxChapterPage = 0
|
||||||
if (chapImages.isNotEmpty()) {
|
if (chapImages.isNotEmpty()) {
|
||||||
maxChapterPage = chapImages.size.toLong()
|
maxChapterPage = chapImages.size.toLong()
|
||||||
saveData("${media.id}_${chapter.number}_max", maxChapterPage)
|
PrefManager.setCustomVal("${media.id}_${chapter.number}_max", maxChapterPage)
|
||||||
|
|
||||||
imageAdapter =
|
imageAdapter =
|
||||||
dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter)
|
dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter)
|
||||||
@@ -421,15 +448,19 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
binding.mangaReaderSlider.visibility = View.GONE
|
binding.mangaReaderSlider.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.mangaReaderPageNumber.text =
|
binding.mangaReaderPageNumber.text =
|
||||||
if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
|
if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPage = currentChapterPage.toInt()
|
val currentPage = currentChapterPage.toInt()
|
||||||
|
|
||||||
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) {
|
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) {
|
||||||
binding.mangaReaderSwipy.vertical = true
|
binding.mangaReaderSwipy.vertical = true
|
||||||
if (settings.default.direction == TOP_TO_BOTTOM) {
|
if (defaultSettings.direction == TOP_TO_BOTTOM) {
|
||||||
|
binding.mangaReaderNextChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
@@ -441,6 +472,10 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
binding.mangaReaderNextChapter.performClick()
|
binding.mangaReaderNextChapter.performClick()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
binding.mangaReaderNextChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
@@ -466,28 +501,27 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.mangaReaderSwipy.vertical = false
|
binding.mangaReaderSwipy.vertical = false
|
||||||
if (settings.default.direction == RIGHT_TO_LEFT) {
|
if (defaultSettings.direction == RIGHT_TO_LEFT) {
|
||||||
|
binding.mangaReaderNextChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onLeftSwiped = {
|
|
||||||
binding.mangaReaderNextChapter.performClick()
|
|
||||||
}
|
|
||||||
binding.mangaReaderSwipy.onRightSwiped = {
|
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
binding.mangaReaderNextChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onLeftSwiped = {
|
}
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
binding.mangaReaderSwipy.onLeftSwiped = {
|
||||||
}
|
binding.mangaReaderPreviousChapter.performClick()
|
||||||
binding.mangaReaderSwipy.onRightSwiped = {
|
|
||||||
binding.mangaReaderNextChapter.performClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
binding.mangaReaderSwipy.leftBeingSwiped = { value ->
|
binding.mangaReaderSwipy.leftBeingSwiped = { value ->
|
||||||
binding.LeftSwipeContainer.apply {
|
binding.LeftSwipeContainer.apply {
|
||||||
@@ -495,6 +529,9 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
translationX = -width.dp * (1 - min(value, 1f))
|
translationX = -width.dp * (1 - min(value, 1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.mangaReaderSwipy.onRightSwiped = {
|
||||||
|
binding.mangaReaderNextChapter.performClick()
|
||||||
|
}
|
||||||
binding.mangaReaderSwipy.rightBeingSwiped = { value ->
|
binding.mangaReaderSwipy.rightBeingSwiped = { value ->
|
||||||
binding.RightSwipeContainer.apply {
|
binding.RightSwipeContainer.apply {
|
||||||
alpha = value
|
alpha = value
|
||||||
@@ -503,11 +540,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.default.layout != PAGED) {
|
if (defaultSettings.layout != PAGED) {
|
||||||
|
|
||||||
binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE
|
binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE
|
||||||
binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled =
|
binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled =
|
||||||
settings.default.rotation
|
defaultSettings.rotation
|
||||||
|
|
||||||
val detector = GestureDetectorCompat(this, object : GesturesListener() {
|
val detector = GestureDetectorCompat(this, object : GesturesListener() {
|
||||||
override fun onLongPress(e: MotionEvent) {
|
override fun onLongPress(e: MotionEvent) {
|
||||||
@@ -530,7 +567,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
val page =
|
val page =
|
||||||
chapter.dualPages().getOrNull(pos) ?: return@dualPage false
|
chapter.dualPages().getOrNull(pos) ?: return@dualPage false
|
||||||
val nextPage = page.second
|
val nextPage = page.second
|
||||||
if (settings.default.direction != LEFT_TO_RIGHT && nextPage != null)
|
if (defaultSettings.direction != LEFT_TO_RIGHT && nextPage != null)
|
||||||
onImageLongClicked(pos * 2, nextPage, page.first, callback)
|
onImageLongClicked(pos * 2, nextPage, page.first, callback)
|
||||||
else
|
else
|
||||||
onImageLongClicked(pos * 2, page.first, nextPage, callback)
|
onImageLongClicked(pos * 2, page.first, nextPage, callback)
|
||||||
@@ -552,11 +589,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val manager = PreloadLinearLayoutManager(
|
val manager = PreloadLinearLayoutManager(
|
||||||
this,
|
this,
|
||||||
if (settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)
|
if (defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)
|
||||||
RecyclerView.VERTICAL
|
RecyclerView.VERTICAL
|
||||||
else
|
else
|
||||||
RecyclerView.HORIZONTAL,
|
RecyclerView.HORIZONTAL,
|
||||||
!(settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == LEFT_TO_RIGHT)
|
!(defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == LEFT_TO_RIGHT)
|
||||||
)
|
)
|
||||||
manager.preloadItemCount = 5
|
manager.preloadItemCount = 5
|
||||||
|
|
||||||
@@ -575,7 +612,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
settings.default.apply {
|
defaultSettings.apply {
|
||||||
if (
|
if (
|
||||||
((direction == TOP_TO_BOTTOM || direction == BOTTOM_TO_TOP)
|
((direction == TOP_TO_BOTTOM || direction == BOTTOM_TO_TOP)
|
||||||
&& (!v.canScrollVertically(-1) || !v.canScrollVertically(1)))
|
&& (!v.canScrollVertically(-1) || !v.canScrollVertically(1)))
|
||||||
@@ -594,25 +631,25 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
super.onScrolled(v, dx, dy)
|
super.onScrolled(v, dx, dy)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
|
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
|
||||||
updatePadding(0, 128f.px, 0, 128f.px)
|
updatePadding(0, 128f.px, 0, 128f.px)
|
||||||
else
|
else
|
||||||
updatePadding(128f.px, 0, 128f.px, 0)
|
updatePadding(128f.px, 0, 128f.px, 0)
|
||||||
|
|
||||||
snapHelper.attachToRecyclerView(
|
snapHelper.attachToRecyclerView(
|
||||||
if (settings.default.layout == CONTINUOUS_PAGED) this
|
if (defaultSettings.layout == CONTINUOUS_PAGED) this
|
||||||
else null
|
else null
|
||||||
)
|
)
|
||||||
|
|
||||||
onVolumeUp = {
|
onVolumeUp = {
|
||||||
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
|
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
|
||||||
smoothScrollBy(0, -500)
|
smoothScrollBy(0, -500)
|
||||||
else
|
else
|
||||||
smoothScrollBy(-500, 0)
|
smoothScrollBy(-500, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
onVolumeDown = {
|
onVolumeDown = {
|
||||||
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
|
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
|
||||||
smoothScrollBy(0, 500)
|
smoothScrollBy(0, 500)
|
||||||
else
|
else
|
||||||
smoothScrollBy(500, 0)
|
smoothScrollBy(500, 0)
|
||||||
@@ -627,11 +664,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
adapter = imageAdapter
|
adapter = imageAdapter
|
||||||
layoutDirection =
|
layoutDirection =
|
||||||
if (settings.default.direction == BOTTOM_TO_TOP || settings.default.direction == RIGHT_TO_LEFT)
|
if (defaultSettings.direction == BOTTOM_TO_TOP || defaultSettings.direction == RIGHT_TO_LEFT)
|
||||||
View.LAYOUT_DIRECTION_RTL
|
View.LAYOUT_DIRECTION_RTL
|
||||||
else View.LAYOUT_DIRECTION_LTR
|
else View.LAYOUT_DIRECTION_LTR
|
||||||
orientation =
|
orientation =
|
||||||
if (settings.default.direction == LEFT_TO_RIGHT || settings.default.direction == RIGHT_TO_LEFT)
|
if (defaultSettings.direction == LEFT_TO_RIGHT || defaultSettings.direction == RIGHT_TO_LEFT)
|
||||||
ViewPager2.ORIENTATION_HORIZONTAL
|
ViewPager2.ORIENTATION_HORIZONTAL
|
||||||
else ViewPager2.ORIENTATION_VERTICAL
|
else ViewPager2.ORIENTATION_VERTICAL
|
||||||
registerOnPageChangeCallback(pageChangeCallback)
|
registerOnPageChangeCallback(pageChangeCallback)
|
||||||
@@ -654,7 +691,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
return when (event.keyCode) {
|
return when (event.keyCode) {
|
||||||
KEYCODE_VOLUME_UP, KEYCODE_DPAD_UP, KEYCODE_PAGE_UP -> {
|
KEYCODE_VOLUME_UP, KEYCODE_DPAD_UP, KEYCODE_PAGE_UP -> {
|
||||||
if (event.keyCode == KEYCODE_VOLUME_UP)
|
if (event.keyCode == KEYCODE_VOLUME_UP)
|
||||||
if (!settings.default.volumeButtons)
|
if (!defaultSettings.volumeButtons)
|
||||||
return false
|
return false
|
||||||
if (event.action == ACTION_DOWN) {
|
if (event.action == ACTION_DOWN) {
|
||||||
onVolumeUp?.invoke()
|
onVolumeUp?.invoke()
|
||||||
@@ -664,7 +701,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> {
|
KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> {
|
||||||
if (event.keyCode == KEYCODE_VOLUME_DOWN)
|
if (event.keyCode == KEYCODE_VOLUME_DOWN)
|
||||||
if (!settings.default.volumeButtons)
|
if (!defaultSettings.volumeButtons)
|
||||||
return false
|
return false
|
||||||
if (event.action == ACTION_DOWN) {
|
if (event.action == ACTION_DOWN) {
|
||||||
onVolumeDown?.invoke()
|
onVolumeDown?.invoke()
|
||||||
@@ -711,14 +748,14 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
|
fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
|
||||||
var pressLocation = pressPos.CENTER
|
var pressLocation = pressPos.CENTER
|
||||||
if (!sliding) {
|
if (!sliding) {
|
||||||
if (event != null && settings.default.layout == PAGED) {
|
if (event != null && defaultSettings.layout == PAGED) {
|
||||||
if (event.action != MotionEvent.ACTION_UP) return
|
if (event.action != MotionEvent.ACTION_UP) return
|
||||||
val x = event.rawX.toInt()
|
val x = event.rawX.toInt()
|
||||||
val y = event.rawY.toInt()
|
val y = event.rawY.toInt()
|
||||||
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||||
//if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left
|
//if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left
|
||||||
if (screenWidth / 5 in (x + 1)..<y) {
|
if (screenWidth / 5 in x + 1..<y) {
|
||||||
pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) {
|
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||||
pressPos.RIGHT
|
pressPos.RIGHT
|
||||||
} else {
|
} else {
|
||||||
pressPos.LEFT
|
pressPos.LEFT
|
||||||
@@ -726,7 +763,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
//if in the last 1/5th of the screen width, right and lower than 1/5th of the screen height, right
|
//if in the last 1/5th of the screen width, right and lower than 1/5th of the screen height, right
|
||||||
else if (x > screenWidth - screenWidth / 5 && y > screenWidth / 5) {
|
else if (x > screenWidth - screenWidth / 5 && y > screenWidth / 5) {
|
||||||
pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) {
|
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||||
pressPos.LEFT
|
pressPos.LEFT
|
||||||
} else {
|
} else {
|
||||||
pressPos.RIGHT
|
pressPos.RIGHT
|
||||||
@@ -758,12 +795,44 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.showSystemBars) {
|
if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
|
||||||
hideBars()
|
hideSystemBars()
|
||||||
checkNotch()
|
checkNotch()
|
||||||
}
|
}
|
||||||
|
// Hide the scrollbar completely
|
||||||
|
if (defaultSettings.hideScrollBar) {
|
||||||
|
binding.mangaReaderSliderContainer.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
if (defaultSettings.horizontalScrollBar) {
|
||||||
|
binding.mangaReaderSliderContainer.updateLayoutParams {
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.mangaReaderSlider.apply {
|
||||||
|
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
}
|
||||||
|
rotation = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
binding.mangaReaderSliderContainer.updateLayoutParams {
|
||||||
|
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
width = 48f.px
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.mangaReaderSlider.apply {
|
||||||
|
updateLayoutParams {
|
||||||
|
width = binding.mangaReaderSliderContainer.height - 16f.px
|
||||||
|
}
|
||||||
|
rotation = 90f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.mangaReaderSliderContainer.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
//horizontal scrollbar
|
//horizontal scrollbar
|
||||||
if (settings.default.horizontalScrollBar) {
|
if (defaultSettings.horizontalScrollBar) {
|
||||||
binding.mangaReaderSliderContainer.updateLayoutParams {
|
binding.mangaReaderSliderContainer.updateLayoutParams {
|
||||||
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
@@ -790,7 +859,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.mangaReaderSlider.layoutDirection =
|
binding.mangaReaderSlider.layoutDirection =
|
||||||
if (settings.default.direction == RIGHT_TO_LEFT || settings.default.direction == BOTTOM_TO_TOP)
|
if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP)
|
||||||
View.LAYOUT_DIRECTION_RTL
|
View.LAYOUT_DIRECTION_RTL
|
||||||
else View.LAYOUT_DIRECTION_LTR
|
else View.LAYOUT_DIRECTION_LTR
|
||||||
shouldShow?.apply { isContVisible = !this }
|
shouldShow?.apply { isContVisible = !this }
|
||||||
@@ -828,9 +897,9 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
fun updatePageNumber(page: Long) {
|
fun updatePageNumber(page: Long) {
|
||||||
if (currentChapterPage != page) {
|
if (currentChapterPage != page) {
|
||||||
currentChapterPage = page
|
currentChapterPage = page
|
||||||
saveData("${media.id}_${chapter.number}", page, this)
|
PrefManager.setCustomVal("${media.id}_${chapter.number}", page)
|
||||||
binding.mangaReaderPageNumber.text =
|
binding.mangaReaderPageNumber.text =
|
||||||
if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
|
if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
|
||||||
if (!sliding) binding.mangaReaderSlider.apply {
|
if (!sliding) binding.mangaReaderSlider.apply {
|
||||||
value = clamp(currentChapterPage.toFloat(), 1f, valueTo)
|
value = clamp(currentChapterPage.toFloat(), 1f, valueTo)
|
||||||
}
|
}
|
||||||
@@ -851,31 +920,27 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
private fun progress(runnable: Runnable) {
|
private fun progress(runnable: Runnable) {
|
||||||
if (maxChapterPage - currentChapterPage <= 1 && Anilist.userid != null) {
|
if (maxChapterPage - currentChapterPage <= 1 && Anilist.userid != null) {
|
||||||
showProgressDialog =
|
showProgressDialog =
|
||||||
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
|
if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal(
|
||||||
?: true else false
|
"${media.id}_progressDialog",
|
||||||
if (showProgressDialog) {
|
true
|
||||||
|
)
|
||||||
|
else false
|
||||||
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
|
if (showProgressDialog && !incognito) {
|
||||||
|
|
||||||
val dialogView = layoutInflater.inflate(R.layout.item_custom_dialog, null)
|
val dialogView = layoutInflater.inflate(R.layout.item_custom_dialog, null)
|
||||||
val checkbox = dialogView.findViewById<CheckBox>(R.id.dialog_checkbox)
|
val checkbox = dialogView.findViewById<CheckBox>(R.id.dialog_checkbox)
|
||||||
checkbox.text = getString(R.string.dont_ask_again, media.userPreferredName)
|
checkbox.text = getString(R.string.dont_ask_again, media.userPreferredName)
|
||||||
checkbox.setOnCheckedChangeListener { _, isChecked ->
|
checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
saveData("${media.id}_progressDialog", !isChecked)
|
PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked)
|
||||||
showProgressDialog = !isChecked
|
showProgressDialog = !isChecked
|
||||||
}
|
}
|
||||||
val incognito =
|
|
||||||
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
|
||||||
?.getBoolean("incognito", false) ?: false
|
|
||||||
AlertDialog.Builder(this, R.style.MyPopup)
|
AlertDialog.Builder(this, R.style.MyPopup)
|
||||||
.setTitle(getString(R.string.title_update_progress))
|
.setTitle(getString(R.string.title_update_progress))
|
||||||
.apply {
|
|
||||||
if (incognito) {
|
|
||||||
setMessage(getString(R.string.incognito_will_not_update))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
|
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
|
||||||
saveData("${media.id}_save_progress", true)
|
PrefManager.setCustomVal("${media.id}_save_progress", true)
|
||||||
updateProgress(
|
updateProgress(
|
||||||
media,
|
media,
|
||||||
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
@@ -885,15 +950,19 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
.setNegativeButton(getString(R.string.no)) { dialog, _ ->
|
.setNegativeButton(getString(R.string.no)) { dialog, _ ->
|
||||||
saveData("${media.id}_save_progress", false)
|
PrefManager.setCustomVal("${media.id}_save_progress", false)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
.setOnCancelListener { hideBars() }
|
.setOnCancelListener { hideSystemBars() }
|
||||||
.create()
|
.create()
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
|
if (!incognito && PrefManager.getCustomVal(
|
||||||
|
"${media.id}_save_progress",
|
||||||
|
true
|
||||||
|
) && if (media.isAdult) PrefManager.getVal<Boolean>(PrefName.UpdateForHReader) else true
|
||||||
|
)
|
||||||
updateProgress(
|
updateProgress(
|
||||||
media,
|
media,
|
||||||
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
@@ -906,6 +975,51 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun <T> loadReaderSettings(
|
||||||
|
fileName: String,
|
||||||
|
context: Context? = null,
|
||||||
|
toast: Boolean = true
|
||||||
|
): T? {
|
||||||
|
val a = context ?: currContext()
|
||||||
|
try {
|
||||||
|
if (a?.fileList() != null)
|
||||||
|
if (fileName in a.fileList()) {
|
||||||
|
val fileIS: FileInputStream = a.openFileInput(fileName)
|
||||||
|
val objIS = ObjectInputStream(fileIS)
|
||||||
|
val data = objIS.readObject() as T
|
||||||
|
objIS.close()
|
||||||
|
fileIS.close()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (toast) snackString(a?.getString(R.string.error_loading_data, fileName))
|
||||||
|
//try to delete the file
|
||||||
|
try {
|
||||||
|
a?.deleteFile(fileName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Injekt.get<CrashlyticsInterface>().log("Failed to delete file $fileName")
|
||||||
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
|
}
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveReaderSettings(fileName: String, data: Any?, context: Context? = null) {
|
||||||
|
tryWith {
|
||||||
|
val a = context ?: currContext()
|
||||||
|
if (a != null) {
|
||||||
|
val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE)
|
||||||
|
val os = ObjectOutputStream(fos)
|
||||||
|
os.writeObject(data)
|
||||||
|
os.close()
|
||||||
|
fos.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getTransformation(mangaImage: MangaImage): BitmapTransformation? {
|
fun getTransformation(mangaImage: MangaImage): BitmapTransformation? {
|
||||||
return model.loadTransformation(mangaImage, media.selected!!.sourceIndex)
|
return model.loadTransformation(mangaImage, media.selected!!.sourceIndex)
|
||||||
}
|
}
|
||||||
@@ -916,7 +1030,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
img2: MangaImage?,
|
img2: MangaImage?,
|
||||||
callback: ((ImageViewDialog) -> Unit)? = null
|
callback: ((ImageViewDialog) -> Unit)? = null
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (!settings.default.longClickImage) return false
|
if (!defaultSettings.longClickImage) return false
|
||||||
val title = "(Page ${pos + 1}${if (img2 != null) "-${pos + 2}" else ""}) ${
|
val title = "(Page ${pos + 1}${if (img2 != null) "-${pos + 2}" else ""}) ${
|
||||||
chaptersTitleArr.getOrNull(currentChapterIndex)?.replace(" : ", " - ") ?: ""
|
chaptersTitleArr.getOrNull(currentChapterIndex)?.replace(" : ", " - ") ?: ""
|
||||||
} [${media.userPreferredName}]"
|
} [${media.userPreferredName}]"
|
||||||
@@ -930,8 +1044,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
val parserTransformation2 = getTransformation(img2)
|
val parserTransformation2 = getTransformation(img2)
|
||||||
if (parserTransformation2 != null) transforms2.add(parserTransformation2)
|
if (parserTransformation2 != null) transforms2.add(parserTransformation2)
|
||||||
}
|
}
|
||||||
val threshold = settings.default.cropBorderThreshold
|
val threshold = defaultSettings.cropBorderThreshold
|
||||||
if (settings.default.cropBorders) {
|
if (defaultSettings.cropBorders) {
|
||||||
transforms1.add(RemoveBordersTransformation(true, threshold))
|
transforms1.add(RemoveBordersTransformation(true, threshold))
|
||||||
transforms1.add(RemoveBordersTransformation(false, threshold))
|
transforms1.add(RemoveBordersTransformation(false, threshold))
|
||||||
if (img2 != null) {
|
if (img2 != null) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user