mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-18 07:43:55 +00:00
Compare commits
173 Commits
v3.1.0-bet
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3f6d0ecee | ||
|
|
5124d6a2d8 | ||
|
|
e83a0fe7da | ||
|
|
61a8350043 | ||
|
|
baffbc845c | ||
|
|
afd9f6b884 | ||
|
|
7d0894cd92 | ||
|
|
dec2ed7959 | ||
|
|
e4630df3e0 | ||
|
|
6fd3515d2c | ||
|
|
6fa2f11db2 | ||
|
|
a5babea27c | ||
|
|
8a9b8cca7e | ||
|
|
7479f5f43b | ||
|
|
3ac9307329 | ||
|
|
f606bef2a5 | ||
|
|
f9f9767ecc | ||
|
|
31a67c8edb | ||
|
|
7fc69b4edd | ||
|
|
9dd59bb592 | ||
|
|
a8958a76cf | ||
|
|
482e867516 | ||
|
|
495322547e | ||
|
|
19740c82f9 | ||
|
|
3abfa821c7 | ||
|
|
ef70fb5238 | ||
|
|
4e8b6b5ff4 | ||
|
|
a90b7d5203 | ||
|
|
d422a1586f | ||
|
|
986d0fa4a8 | ||
|
|
66ed167bc8 | ||
|
|
1bb5f4d0ab | ||
|
|
f6d05ec375 | ||
|
|
c48028f3cd | ||
|
|
e41ab2ddac | ||
|
|
0779c0ca71 | ||
|
|
c229a5c717 | ||
|
|
7c4689dea6 | ||
|
|
eec8605069 | ||
|
|
0d365d55c5 | ||
|
|
7b8af6ea8a | ||
|
|
38d68a7976 | ||
|
|
30d6f48d23 | ||
|
|
88feb4d811 | ||
|
|
116de6324e | ||
|
|
b3e767d33d | ||
|
|
43dee6ee49 | ||
|
|
12c13be2aa | ||
|
|
6f1bb10dec | ||
|
|
f0a8d9bfd4 | ||
|
|
01a64c25fd | ||
|
|
b04a176870 | ||
|
|
eac4604b3d | ||
|
|
1686854632 | ||
|
|
8ad8637fce | ||
|
|
6d32900568 | ||
|
|
ce332d7ae5 | ||
|
|
0d22c92e3e | ||
|
|
0057363f4d | ||
|
|
d1400ff422 | ||
|
|
f13225e032 | ||
|
|
404d265a2d | ||
|
|
c2d69509fb | ||
|
|
d01e1c89e0 | ||
|
|
ff3372754a | ||
|
|
a4bd367f98 | ||
|
|
9fa326c571 | ||
|
|
3d4f5aaf4a | ||
|
|
90c6c08b48 | ||
|
|
d1e2ca8b5e | ||
|
|
56e557738c | ||
|
|
489dcc0b52 | ||
|
|
a993935433 | ||
|
|
5c1c639f53 | ||
|
|
02ec4a3605 | ||
|
|
441094ca17 | ||
|
|
b2d7af85c0 | ||
|
|
95b558118a | ||
|
|
b703337a16 | ||
|
|
3071f88681 | ||
|
|
c242770435 | ||
|
|
0fa2cf98d8 | ||
|
|
ffd9fecf26 | ||
|
|
7ec889a915 | ||
|
|
949ab7e87b | ||
|
|
cddad8edf1 | ||
|
|
4e76e8e6e7 | ||
|
|
a9331ffa32 | ||
|
|
545abf1f9a | ||
|
|
f191502a97 | ||
|
|
652ef219dd | ||
|
|
e8ca3f2222 | ||
|
|
bd1f3388f7 | ||
|
|
c37fefde73 | ||
|
|
74e88838f0 | ||
|
|
2cf73be675 | ||
|
|
b594258d28 | ||
|
|
f9ce897197 | ||
|
|
68b6fd030f | ||
|
|
f0536b3cad | ||
|
|
6a077fa48d | ||
|
|
ebb61d94dd | ||
|
|
a7cef9323e | ||
|
|
a8f7ff2a19 | ||
|
|
0214e6611b | ||
|
|
04b9b9e7ff | ||
|
|
7366aa1bf2 | ||
|
|
09c5d9ce91 | ||
|
|
79742f415b | ||
|
|
b09f26ed34 | ||
|
|
6eb654bf51 | ||
|
|
b109d50d89 | ||
|
|
f62bdf9360 | ||
|
|
46d16be835 | ||
|
|
c22fd6b66d | ||
|
|
ae95b61298 | ||
|
|
2180086573 | ||
|
|
665b558b1f | ||
|
|
37ba9341cc | ||
|
|
feb765448b | ||
|
|
a8ccf8d246 | ||
|
|
2f06ac6071 | ||
|
|
ed24e64b78 | ||
|
|
43fc9c17f5 | ||
|
|
83e7e4591d | ||
|
|
563a96cf98 | ||
|
|
124f4eb261 | ||
|
|
0052eba828 | ||
|
|
eda213a765 | ||
|
|
899af3ee1a | ||
|
|
124c8f5ed7 | ||
|
|
1670383619 | ||
|
|
903423b842 | ||
|
|
3ae59b8d22 | ||
|
|
d488d11573 | ||
|
|
6f685a4388 | ||
|
|
b644ba1866 | ||
|
|
74cab22eca | ||
|
|
ce7ae28e1e | ||
|
|
fdc1b31c44 | ||
|
|
1b4c8704ea | ||
|
|
5473ac8238 | ||
|
|
e52ea2628a | ||
|
|
0f8218482a | ||
|
|
8822ef6805 | ||
|
|
6ce41b8fbb | ||
|
|
11655bd38d | ||
|
|
ea75197120 | ||
|
|
6878d12b5c | ||
|
|
5800dcf3e7 | ||
|
|
b30047804a | ||
|
|
0b32636c1b | ||
|
|
43e560a893 | ||
|
|
b256f02f14 | ||
|
|
46c17dced1 | ||
|
|
72fe910c59 | ||
|
|
fb65cb601e | ||
|
|
5a78d68f67 | ||
|
|
f205463a51 | ||
|
|
2de8ffd367 | ||
|
|
f3f0daf7e7 | ||
|
|
2b4c9bf7a9 | ||
|
|
21e25fe7a7 | ||
|
|
7b36cd0d29 | ||
|
|
ce488ea536 | ||
|
|
7717974b9e | ||
|
|
37949c7e8e | ||
|
|
e7a60e07d8 | ||
|
|
7bce053202 | ||
|
|
a5304477c7 | ||
|
|
945018653e | ||
|
|
5e38b00c1f | ||
|
|
dec990c24c |
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 🧑💻 Dantotsu Help on Discord
|
||||||
|
url: https://discord.com/invite/4HPZ5nAWwM
|
||||||
|
about: Get support, ask questions, and join the community discussions.
|
||||||
|
|
||||||
|
- name: 📱 Dantotsu Help on Telegram
|
||||||
|
url: https://t.me/dantotsuapp
|
||||||
|
about: Connect with the community, ask questions, and get help directly on Telegram.
|
||||||
35
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: ❓ Question
|
||||||
|
description: Submit a question or query related to Dantotsu
|
||||||
|
labels: [question]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: question-details
|
||||||
|
attributes:
|
||||||
|
label: Question Details
|
||||||
|
description: Provide a detailed explanation of your question or query.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"How do I customize the settings in Dartotsu to optimize performance?"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: related-features
|
||||||
|
attributes:
|
||||||
|
label: Related Features (if applicable)
|
||||||
|
description: Mention any specific feature or section of Dantotsu related to your question.
|
||||||
|
placeholder: |
|
||||||
|
Example: "Settings > Performance"
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: submission-checklist
|
||||||
|
attributes:
|
||||||
|
label: Submission Checklist
|
||||||
|
description: Review the following items before submitting your question.
|
||||||
|
options:
|
||||||
|
- label: I have searched existing issues to see if this question has already been answered.
|
||||||
|
required: true
|
||||||
|
- label: I have provided a clear and concise question title.
|
||||||
|
required: true
|
||||||
|
- label: I have provided all relevant details to understand my question fully.
|
||||||
|
required: true
|
||||||
101
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
Normal file
101
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
name: 🐛 Issue Report
|
||||||
|
description: Report a bug or problem in Dantotsu
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: Outline the steps needed to trigger the issue.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
1. Navigate to the home screen.
|
||||||
|
2. Click on "Start."
|
||||||
|
3. Observe the error message.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-outcome
|
||||||
|
attributes:
|
||||||
|
label: Expected Outcome
|
||||||
|
description: Describe what you expected to happen.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"The application should have successfully loaded the dashboard..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual-outcome
|
||||||
|
attributes:
|
||||||
|
label: Actual Outcome
|
||||||
|
description: Detail what actually occurred when following the steps.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"The app crashed and displayed an error message instead..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: error-logs
|
||||||
|
attributes:
|
||||||
|
label: Error Logs (if applicable)
|
||||||
|
description: |
|
||||||
|
If the issue involves crashes, please attach relevant logs. Access them via **Settings → About → Log to file → Share**.
|
||||||
|
placeholder: |
|
||||||
|
Paste the logs here or upload as an attachment.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: dartotsu-version
|
||||||
|
attributes:
|
||||||
|
label: Dartotsu Version
|
||||||
|
description: Specify the version of Dartotsu in which the issue occurred.
|
||||||
|
placeholder: |
|
||||||
|
Example: "1.2.3"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os-version
|
||||||
|
attributes:
|
||||||
|
label: Operating System Version
|
||||||
|
description: Mention the OS version you are using.
|
||||||
|
placeholder: |
|
||||||
|
Example: "Android 12"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: device-info
|
||||||
|
attributes:
|
||||||
|
label: Device Information
|
||||||
|
description: Provide your device name and model.
|
||||||
|
placeholder: |
|
||||||
|
Example: "Samsung Galaxy S21"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional-information
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
placeholder: |
|
||||||
|
Include any other relevant details or attachments that may help diagnose the issue.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: submission-checklist
|
||||||
|
attributes:
|
||||||
|
label: Submission Checklist
|
||||||
|
description: Ensure you've reviewed these items before submitting your report.
|
||||||
|
options:
|
||||||
|
- label: I have searched existing issues to confirm this is not a duplicate.
|
||||||
|
required: true
|
||||||
|
- label: I have provided a clear and descriptive title.
|
||||||
|
required: true
|
||||||
|
- label: I am using the **[latest](https://github.com/rebelonion/Dantotsu/latest)** version of Dantotsu. If not, I have provided a reason for not updating.
|
||||||
|
required: true
|
||||||
|
- label: I have updated all relevant extensions or dependencies.
|
||||||
|
required: true
|
||||||
|
- label: I have filled out all the requested information accurately.
|
||||||
|
required: true
|
||||||
54
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
Normal file
54
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: 🚀 Feature Request
|
||||||
|
description: Propose a new feature to enhance Dantotsu
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: feature-summary
|
||||||
|
attributes:
|
||||||
|
label: Feature Summary
|
||||||
|
description: Provide a concise summary of the feature you'd like to see.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"Add support for dark mode..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: detailed-description
|
||||||
|
attributes:
|
||||||
|
label: Detailed Description
|
||||||
|
description: Elaborate on how this feature should function and its potential impact.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"The dark mode should automatically activate based on system settings..."
|
||||||
|
value: |
|
||||||
|
### Current Behavior
|
||||||
|
- Describe the current functionality or lack thereof.
|
||||||
|
|
||||||
|
### Proposed Solution
|
||||||
|
- Detail how the feature should work and any potential benefits.
|
||||||
|
|
||||||
|
### Considerations
|
||||||
|
- Mention any potential challenges or alternatives.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Provide any other information, relevant screenshots, or references.
|
||||||
|
placeholder: "Include links to relevant resources, external tools, or related issues."
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: Submission Checklist
|
||||||
|
description: Ensure you've completed these before submitting.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and confirm that this feature has not been requested before.
|
||||||
|
required: true
|
||||||
|
- label: I have provided a clear and descriptive title.
|
||||||
|
required: true
|
||||||
|
- label: I am using the **[latest](https://github.com/rebelonion/Dantotsu/releases/latest)** version of Dantotsu. If not, I have provided a reason for using an older version.
|
||||||
|
required: true
|
||||||
|
- label: I understand that not all feature requests will be accepted, and if declined, I won't resubmit the same request.
|
||||||
|
required: true
|
||||||
297
.github/workflows/beta.yml
vendored
297
.github/workflows/beta.yml
vendored
@@ -1,17 +1,25 @@
|
|||||||
name: Build APK and Notify Discord
|
name: Build APK and Notify Discord
|
||||||
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches-ignore:
|
||||||
- dev
|
- main
|
||||||
|
- l10n_dev_crowdin
|
||||||
|
- custom-download-location
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/README.md'
|
- '**/README.md'
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
SKIP_BUILD: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -19,14 +27,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
||||||
- name: Download last SHA artifact
|
- name: Download last SHA artifact
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: beta.yml
|
workflow: beta.yml
|
||||||
name: last-sha
|
name: last-sha
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Get Commits Since Last Run
|
- name: Get Commits Since Last Run
|
||||||
@@ -39,7 +45,9 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Commits since $LAST_SHA:"
|
echo "Commits since $LAST_SHA:"
|
||||||
# Accumulate commit logs in a shell variable
|
# Accumulate commit logs in a shell variable
|
||||||
COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an")
|
COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an [֍](https://github.com/${{ github.repository }}/commit/%H)" --max-count=10)
|
||||||
|
# Replace commit messages with pull request links
|
||||||
|
COMMIT_LOGS=$(echo "$COMMIT_LOGS" | sed -E 's/#([0-9]+)/[#\1](https:\/\/github.com\/rebelonion\/Dantotsu\/pull\/\1)/g')
|
||||||
# URL-encode the newline characters for GitHub Actions
|
# URL-encode the newline characters for GitHub Actions
|
||||||
COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}"
|
COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}"
|
||||||
COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}"
|
COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}"
|
||||||
@@ -49,6 +57,10 @@ jobs:
|
|||||||
# Debugging: Print the variable to check its content
|
# Debugging: Print the variable to check its content
|
||||||
echo "$COMMIT_LOGS"
|
echo "$COMMIT_LOGS"
|
||||||
echo "$COMMIT_LOGS" > commit_log.txt
|
echo "$COMMIT_LOGS" > commit_log.txt
|
||||||
|
# Extract branch name from github.ref
|
||||||
|
BRANCH=${{ github.ref }}
|
||||||
|
BRANCH=${BRANCH#refs/heads/}
|
||||||
|
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
|
||||||
shell: /usr/bin/bash -e {0}
|
shell: /usr/bin/bash -e {0}
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
@@ -65,53 +77,278 @@ jobs:
|
|||||||
echo "Version $VERSION"
|
echo "Version $VERSION"
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: List files in the directory
|
||||||
|
run: ls -l
|
||||||
|
|
||||||
- name: Setup JDK 17
|
- name: Setup JDK 17
|
||||||
|
if: ${{ env.SKIP_BUILD != 'true' }}
|
||||||
uses: actions/setup-java@v4
|
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
|
|
||||||
run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore
|
|
||||||
|
|
||||||
- name: List files in the directory
|
|
||||||
run: ls -l
|
|
||||||
|
|
||||||
- name: Make gradlew executable
|
|
||||||
run: chmod +x ./gradlew
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
|
||||||
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: Decode Keystore File
|
||||||
|
if: ${{ github.repository == 'rebelonion/Dantotsu' }}
|
||||||
|
run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore
|
||||||
|
|
||||||
|
- name: Make gradlew executable
|
||||||
|
if: ${{ env.SKIP_BUILD != 'true' }}
|
||||||
|
run: chmod +x ./gradlew
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
if: ${{ env.SKIP_BUILD != 'true' }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.repository }}" == "rebelonion/Dantotsu" ]; then
|
||||||
|
./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 }};
|
||||||
|
else
|
||||||
|
./gradlew assembleGoogleAlpha;
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Upload a Build Artifact
|
- name: Upload a Build Artifact
|
||||||
|
if: ${{ env.SKIP_BUILD != 'true' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Dantotsu
|
name: Dantotsu
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
compression-level: 9
|
compression-level: 9
|
||||||
path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
|
path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
|
||||||
|
|
||||||
- name: Upload APK to Discord and Telegram
|
- name: Upload APK to Discord and Telegram
|
||||||
if: ${{ github.repository == 'rebelonion/Dantotsu' }}
|
if: ${{ github.repository == 'rebelonion/Dantotsu' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
#Discord
|
# Prepare Discord embed
|
||||||
|
fetch_user_details() {
|
||||||
|
local login=$1
|
||||||
|
user_details=$(curl -s "https://api.github.com/users/$login")
|
||||||
|
name=$(echo "$user_details" | jq -r '.name // .login')
|
||||||
|
login=$(echo "$user_details" | jq -r '.login')
|
||||||
|
avatar_url=$(echo "$user_details" | jq -r '.avatar_url')
|
||||||
|
echo "$name|$login|$avatar_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Additional information for the goats
|
||||||
|
declare -A additional_info
|
||||||
|
additional_info["ibo"]="\n Discord: <@951737931159187457>\n AniList: [takarealist112](<https://anilist.co/user/5790266/>)"
|
||||||
|
additional_info["aayush262"]="\n Discord: <@918825160654598224>\n AniList: [aayush262](<https://anilist.co/user/5144645/>)"
|
||||||
|
additional_info["rebel onion"]="\n Discord: <@714249925248024617>\n AniList: [rebelonion](<https://anilist.co/user/6077251/>)\n PornHub: [rebelonion](<https://www.cornhub.com/model/rebelonion>)"
|
||||||
|
additional_info["Ankit Grai"]="\n Discord: <@1125628254330560623>\n AniList: [bheshnarayan](<https://anilist.co/user/6417303/>)"
|
||||||
|
|
||||||
|
# Decimal color codes for contributors
|
||||||
|
declare -A contributor_colors
|
||||||
|
default_color="#bf2cc8"
|
||||||
|
contributor_colors["ibo"]="#ff9b46"
|
||||||
|
contributor_colors["aayush262"]="#5d689d"
|
||||||
|
contributor_colors["Sadwhy"]="#ff7e95"
|
||||||
|
contributor_colors["grayankit"]="#c51aa1"
|
||||||
|
contributor_colors["rebelonion"]="#d4e5ed"
|
||||||
|
|
||||||
|
hex_to_decimal() { printf '%d' "0x${1#"#"}"; }
|
||||||
|
|
||||||
|
# Count recent commits and create an associative array Okay
|
||||||
|
declare -A recent_commit_counts
|
||||||
|
echo "Debug: Processing COMMIT_LOG:"
|
||||||
|
echo "$COMMIT_LOG"
|
||||||
|
while read -r count name; do
|
||||||
|
recent_commit_counts["$name"]=$count
|
||||||
|
echo "Debug: Commit count for $name: $count"
|
||||||
|
done < <(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^[]*' | sort | uniq -c | sort -rn)
|
||||||
|
|
||||||
|
echo "Debug: Fetching contributors from GitHub"
|
||||||
|
# Fetch contributors from GitHub
|
||||||
|
contributors=$(curl -s "https://api.github.com/repos/${{ github.repository }}/contributors")
|
||||||
|
echo "Debug: Contributors response:"
|
||||||
|
echo "$contributors"
|
||||||
|
|
||||||
|
# Create a sorted list of contributors based on recent commit counts
|
||||||
|
sorted_contributors=$(for login in $(echo "$contributors" | jq -r '.[].login'); do
|
||||||
|
user_info=$(fetch_user_details "$login")
|
||||||
|
name=$(echo "$user_info" | cut -d'|' -f1)
|
||||||
|
count=${recent_commit_counts["$name"]:-0}
|
||||||
|
echo "$count|$login"
|
||||||
|
done | sort -rn | cut -d'|' -f2)
|
||||||
|
|
||||||
|
# Initialize needed variables
|
||||||
|
developers=""
|
||||||
|
committers_count=0
|
||||||
|
max_commits=0
|
||||||
|
top_contributor=""
|
||||||
|
top_contributor_count=0
|
||||||
|
top_contributor_avatar=""
|
||||||
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
|
|
||||||
|
# Process contributors in the new order
|
||||||
|
while read -r login; do
|
||||||
|
user_info=$(fetch_user_details "$login")
|
||||||
|
name=$(echo "$user_info" | cut -d'|' -f1)
|
||||||
|
login=$(echo "$user_info" | cut -d'|' -f2)
|
||||||
|
avatar_url=$(echo "$user_info" | cut -d'|' -f3)
|
||||||
|
|
||||||
|
# Only process if they have recent commits
|
||||||
|
commit_count=${recent_commit_counts["$name"]:-0}
|
||||||
|
if [ $commit_count -gt 0 ]; then
|
||||||
|
# Update top contributor information
|
||||||
|
if [ $commit_count -gt $max_commits ]; then
|
||||||
|
max_commits=$commit_count
|
||||||
|
top_contributors=("$login")
|
||||||
|
top_contributor_count=1
|
||||||
|
top_contributor_avatar="$avatar_url"
|
||||||
|
embed_color=$(hex_to_decimal "${contributor_colors[$name]:-$default_color}")
|
||||||
|
elif [ $commit_count -eq $max_commits ]; then
|
||||||
|
top_contributors+=("$login")
|
||||||
|
top_contributor_count=$((top_contributor_count + 1))
|
||||||
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
|
fi
|
||||||
|
echo "Debug top contributors:"
|
||||||
|
echo "$top_contributors"
|
||||||
|
|
||||||
|
# Get commit count for this contributor on the dev branch
|
||||||
|
branch_commit_count=$(git log --author="$login" --author="$name" --oneline | awk '!seen[$0]++' | wc -l)
|
||||||
|
|
||||||
|
# Debug: Print recent_commit_counts
|
||||||
|
echo "Debug: recent_commit_counts contents:"
|
||||||
|
for key in "${!recent_commit_counts[@]}"; do
|
||||||
|
echo "$key: ${recent_commit_counts[$key]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
extra_info="${additional_info[$name]}"
|
||||||
|
if [ -n "$extra_info" ]; then
|
||||||
|
extra_info=$(echo "$extra_info" | sed 's/\\n/\n- /g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Construct the developer entry
|
||||||
|
developer_entry="◗ **${name}** ${extra_info}
|
||||||
|
- Github: [${login}](https://github.com/${login})
|
||||||
|
- Commits: ${branch_commit_count}"
|
||||||
|
|
||||||
|
# Add the entry to developers, with a newline if it's not the first entry
|
||||||
|
if [ -n "$developers" ]; then
|
||||||
|
developers="${developers}
|
||||||
|
${developer_entry}"
|
||||||
|
else
|
||||||
|
developers="${developer_entry}"
|
||||||
|
fi
|
||||||
|
committers_count=$((committers_count + 1))
|
||||||
|
fi
|
||||||
|
done <<< "$sorted_contributors"
|
||||||
|
|
||||||
|
# Set the thumbnail URL and color based on top contributor(s)
|
||||||
|
if [ $top_contributor_count -eq 1 ]; then
|
||||||
|
thumbnail_url="$top_contributor_avatar"
|
||||||
|
else
|
||||||
|
thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif"
|
||||||
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Truncate field values
|
||||||
|
max_length=1000
|
||||||
commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/')
|
commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/')
|
||||||
# Truncate commit messages if they are too long
|
if [ ${#developers} -gt $max_length ]; then
|
||||||
max_length=1900 # Adjust this value as needed
|
developers="${developers:0:$max_length}... (truncated)"
|
||||||
|
fi
|
||||||
if [ ${#commit_messages} -gt $max_length ]; then
|
if [ ${#commit_messages} -gt $max_length ]; then
|
||||||
commit_messages="${commit_messages:0:$max_length}... (truncated)"
|
commit_messages="${commit_messages:0:$max_length}... (truncated)"
|
||||||
fi
|
fi
|
||||||
contentbody=$( jq -nc --arg msg "Alpha-Build: <@&1225347048321191996> **$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-universal-alpha.apk" \
|
|
||||||
-F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \
|
|
||||||
https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument
|
|
||||||
|
|
||||||
|
# Construct Discord payload
|
||||||
|
discord_data=$(jq -nc \
|
||||||
|
--arg field_value "$commit_messages" \
|
||||||
|
--arg author_value "$developers" \
|
||||||
|
--arg footer_text "Version $VERSION" \
|
||||||
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
|
||||||
|
--arg thumbnail_url "$thumbnail_url" \
|
||||||
|
--arg embed_color "$embed_color" \
|
||||||
|
'{
|
||||||
|
"content": "<@&1225347048321191996>",
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
"title": "New Alpha-Build dropped",
|
||||||
|
"color": $embed_color,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Commits:",
|
||||||
|
"value": $field_value,
|
||||||
|
"inline": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Developers:",
|
||||||
|
"value": $author_value,
|
||||||
|
"inline": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"footer": {
|
||||||
|
"text": $footer_text
|
||||||
|
},
|
||||||
|
"timestamp": $timestamp,
|
||||||
|
"thumbnail": {
|
||||||
|
"url": $thumbnail_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attachments": []
|
||||||
|
}')
|
||||||
|
echo "Debug: Final Discord payload:"
|
||||||
|
echo "$discord_data"
|
||||||
|
|
||||||
|
# Send Discord message
|
||||||
|
curl -H "Content-Type: application/json" \
|
||||||
|
-d "$discord_data" \
|
||||||
|
${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
echo "You have only send an embed to discord due to SKIP_BUILD being set to true"
|
||||||
|
|
||||||
|
# Upload APK to Discord
|
||||||
|
if [ "$SKIP_BUILD" != "true" ]; then
|
||||||
|
curl -F "payload_json=${contentbody}" \
|
||||||
|
-F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \
|
||||||
|
${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
else
|
||||||
|
echo "Skipping APK upload to Discord due to SKIP_BUILD being set to true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format commit messages for Telegram
|
||||||
|
telegram_commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | while read -r line; do
|
||||||
|
message=$(echo "$line" | sed -E 's/● (.*) ~(.*) \[֍\]\((.*)\)/● \1 ~\2 <a href="\3">֍<\/a>/')
|
||||||
|
message=$(echo "$message" | sed -E 's/\[#([0-9]+)\]\((https:\/\/github\.com\/[^)]+)\)/<a href="\2">#\1<\/a>/g')
|
||||||
|
echo "$message"
|
||||||
|
done)
|
||||||
|
telegram_commit_messages="<blockquote>${telegram_commit_messages}</blockquote>"
|
||||||
|
|
||||||
|
# Configuring dev info
|
||||||
|
echo "$developers" > dev_info.txt
|
||||||
|
echo "$developers"
|
||||||
|
# making the file executable
|
||||||
|
chmod +x workflowscripts/tel_parser.sed
|
||||||
|
./workflowscripts/tel_parser.sed dev_info.txt >> output.txt
|
||||||
|
dev_info_tel=$(< output.txt)
|
||||||
|
|
||||||
|
telegram_dev_info="<blockquote>${dev_info_tel}</blockquote>"
|
||||||
|
echo "$telegram_dev_info"
|
||||||
|
|
||||||
|
# Upload APK to Telegram
|
||||||
|
if [ "$SKIP_BUILD" != "true" ]; then
|
||||||
|
APK_PATH="app/build/outputs/apk/google/alpha/app-google-alpha.apk"
|
||||||
|
response=$(curl -sS -f -X POST \
|
||||||
|
"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument" \
|
||||||
|
-F "chat_id=-1002117798698" \
|
||||||
|
-F "message_thread_id=7044" \
|
||||||
|
-F "document=@$APK_PATH" \
|
||||||
|
-F "caption=New Alpha-Build dropped 🔥
|
||||||
|
|
||||||
|
Commits:
|
||||||
|
${telegram_commit_messages}
|
||||||
|
Dev:
|
||||||
|
${telegram_dev_info}
|
||||||
|
version: ${VERSION}" \
|
||||||
|
-F "parse_mode=HTML")
|
||||||
|
else
|
||||||
|
echo "skipping because skip build set to true"
|
||||||
|
fi
|
||||||
|
|
||||||
env:
|
env:
|
||||||
COMMIT_LOG: ${{ env.COMMIT_LOG }}
|
COMMIT_LOG: ${{ env.COMMIT_LOG }}
|
||||||
VERSION: ${{ env.VERSION }}
|
VERSION: ${{ env.VERSION }}
|
||||||
|
|||||||
84
.github/workflows/bug_greetings.yml
vendored
Normal file
84
.github/workflows/bug_greetings.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Bug Report Greeting
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
jobs:
|
||||||
|
greeting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Check if the issue is labeled as a Bug Report
|
||||||
|
id: check_bug_label
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ISSUE_NUMBER=$(jq -r '.issue.number' "$GITHUB_EVENT_PATH")
|
||||||
|
LABELS=$(gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name')
|
||||||
|
|
||||||
|
if echo "$LABELS" | grep -q 'bug'; then
|
||||||
|
echo "This issue is labeled as a bug report. Checking if the issue creator is the repository owner."
|
||||||
|
echo "skip_label_check=false" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "This issue is not labeled as a bug report. Skipping greeting message."
|
||||||
|
echo "skip_label_check=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
- name: Check if the issue creator is the repo owner
|
||||||
|
if: env.skip_label_check == 'false'
|
||||||
|
id: check_owner
|
||||||
|
run: |
|
||||||
|
ISSUE_AUTHOR=$(jq -r '.issue.user.login' "$GITHUB_EVENT_PATH")
|
||||||
|
REPO_OWNER=$(jq -r '.repository.owner.login' "$GITHUB_EVENT_PATH")
|
||||||
|
if [ "$ISSUE_AUTHOR" = "$REPO_OWNER" ]; then
|
||||||
|
echo "The issue creator is the repository owner. Skipping greeting message."
|
||||||
|
echo "skip=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "The issue creator is not the repository owner. Checking for previous bug reports..."
|
||||||
|
echo "skip=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
- name: Check if the user has submitted a bug report before
|
||||||
|
if: env.skip == 'false'
|
||||||
|
id: check_first_bug_report
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ISSUE_AUTHOR=$(jq -r '.issue.user.login' "$GITHUB_EVENT_PATH")
|
||||||
|
ISSUE_NUMBER=$(jq -r '.issue.number' "$GITHUB_EVENT_PATH")
|
||||||
|
|
||||||
|
# Get all issues (both open and closed) by the author except the current one
|
||||||
|
PREVIOUS_REPORTS=$(gh issue list --author "$ISSUE_AUTHOR" --label "Bug" --state all --json number --jq '. | map(select(.number != '$ISSUE_NUMBER')) | length')
|
||||||
|
echo "User $ISSUE_AUTHOR has submitted $PREVIOUS_REPORTS bug report(s) previously"
|
||||||
|
|
||||||
|
if [ "$PREVIOUS_REPORTS" -eq 0 ]; then
|
||||||
|
echo "This is the user's first bug report. Sending greeting message."
|
||||||
|
echo "skip_first_report=false" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "User has previous bug reports. Skipping greeting message."
|
||||||
|
echo "skip_first_report=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
- name: Send Greeting Message
|
||||||
|
if: env.skip_label_check == 'false' && env.skip != 'true' && env.skip_first_report != 'true'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const issueNumber = context.payload.issue.number;
|
||||||
|
const message = `
|
||||||
|
**🛠️ Thank you for reporting a bug!**
|
||||||
|
Your issue has been successfully submitted and is now awaiting review. We appreciate your help in making Dantotsu better.
|
||||||
|
**🔍 What Happens Next**
|
||||||
|
- Our team will investigate the issue and provide updates as soon as possible.
|
||||||
|
- You may be asked for additional details or clarification if needed.
|
||||||
|
- Once resolved, we'll notify you of the fix or provide a workaround.
|
||||||
|
**👥 Connect with Us**
|
||||||
|
- **[Discord](https://discord.com/invite/4HPZ5nAWwM)**: Engage with our community and ask questions.
|
||||||
|
- **[Telegram](https://t.me/dantotsuapp)**: Reach out for real-time discussions and updates.
|
||||||
|
We're working hard to resolve the issue and appreciate your patience!
|
||||||
|
`;
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
112
.github/workflows/extension-issue-handling.yml
vendored
Normal file
112
.github/workflows/extension-issue-handling.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
name: Extension Issue Handling
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, labeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
handle-extension-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check Issue Content
|
||||||
|
id: check-issue
|
||||||
|
env:
|
||||||
|
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||||
|
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||||
|
run: |
|
||||||
|
# Regex patterns for extension-related issues
|
||||||
|
EXTENSION_REGEX_PATTERNS=(
|
||||||
|
# Extension not working (more flexible match)
|
||||||
|
".*(\w+)\s*(extension)?\s*(not working|doesn't work|does not work|cant work|can't work).*"
|
||||||
|
|
||||||
|
# No extension available
|
||||||
|
".*(no|can't find|cannot find|missing).*extension.*"
|
||||||
|
|
||||||
|
# No repo or repositories available
|
||||||
|
".*(no|can't find|cannot find|missing).*repo(s)?\s*(available|found|accessible).*"
|
||||||
|
|
||||||
|
# Specific server/stream issues
|
||||||
|
".*(no streams|server).*(available|working).*"
|
||||||
|
|
||||||
|
# Variants of extension problems
|
||||||
|
".*{.*}.*not working.*"
|
||||||
|
".*{.*}.*extension.*(issue|problem).*"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to lowercase for case-insensitive matching
|
||||||
|
LOWER_TITLE=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]')
|
||||||
|
LOWER_BODY=$(echo "$ISSUE_BODY" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
# Flag to track issue type
|
||||||
|
IS_EXTENSION_ISSUE=false
|
||||||
|
IS_NO_EXTENSION_ISSUE=false
|
||||||
|
|
||||||
|
# Check title and body against regex patterns
|
||||||
|
for pattern in "${EXTENSION_REGEX_PATTERNS[@]}"; do
|
||||||
|
if [[ "$LOWER_TITLE" =~ $pattern ]] || [[ "$LOWER_BODY" =~ $pattern ]]; then
|
||||||
|
IS_EXTENSION_ISSUE=true
|
||||||
|
|
||||||
|
# Special check for no extensions available
|
||||||
|
if [[ "$LOWER_TITLE" =~ "no extension" ]] || [[ "$LOWER_TITLE" =~ "can't find extension" ]]; then
|
||||||
|
IS_NO_EXTENSION_ISSUE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Explicitly output boolean values
|
||||||
|
if [ "$IS_EXTENSION_ISSUE" = true ]; then
|
||||||
|
echo "is_extension_issue=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "is_extension_issue=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$IS_NO_EXTENSION_ISSUE" = true ]; then
|
||||||
|
echo "is_no_extension_issue=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "is_no_extension_issue=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Comment and Close Extension Issue
|
||||||
|
if: steps.check-issue.outputs.is_extension_issue == 'true'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const issueNumber = context.issue.number;
|
||||||
|
|
||||||
|
// Check if it's a "No Extension" issue
|
||||||
|
if (${{ steps.check-issue.outputs.is_no_extension_issue }}) {
|
||||||
|
// DMCA notice message
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: "# Automated Message\n" +
|
||||||
|
"On 13 June 2024, the official Aniyomi repository got a DMCA notice and had to remove all of their extensions. Because of this, we will not be providing anyone with any links or extensions to avoid legal problems.\n" +
|
||||||
|
"# How to add repos?\n" +
|
||||||
|
"Although we do not give or maintain any repositories, we support adding custom repository links to your Dantotsu. \n" +
|
||||||
|
"Go to `Profile > Settings > Extensions` then paste your anime or manga links there.\n" +
|
||||||
|
"# How to find repos?\n" +
|
||||||
|
"It's very easy. Search on Google. But remember that the URL must end with <u><b>index.min.json</b></u> or else it won't work.\n" +
|
||||||
|
"`TLDR: We will not give repo links.`"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Standard extension issue message
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: `Dantotsu doesn't maintain extensions.
|
||||||
|
If the extension doesn't work we cannot help you.
|
||||||
|
Contact the owner of Respective Repo for extension-related problems`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the issue
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
state: 'closed'
|
||||||
|
});
|
||||||
86
.github/workflows/feature_greetings.yml
vendored
Normal file
86
.github/workflows/feature_greetings.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: Feature Request Greeting
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
greeting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check if the issue is labeled as a Feature Request
|
||||||
|
id: check_feature_label
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ISSUE_NUMBER=$(jq -r '.issue.number' "$GITHUB_EVENT_PATH")
|
||||||
|
LABELS=$(gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name')
|
||||||
|
|
||||||
|
if echo "$LABELS" | grep -q 'enhancement'; then
|
||||||
|
echo "This issue is labeled as a feature request. Checking if the issue creator is the repository owner."
|
||||||
|
echo "skip_label_check=false" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "This issue is not labeled as a feature request. Skipping greeting message."
|
||||||
|
echo "skip_label_check=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if the user has submitted a feature request before
|
||||||
|
if: env.skip_label_check == 'false'
|
||||||
|
id: check_first_request
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ISSUE_AUTHOR=$(jq -r '.issue.user.login' "$GITHUB_EVENT_PATH")
|
||||||
|
REPO_OWNER=$(jq -r '.repository.owner.login' "$GITHUB_EVENT_PATH")
|
||||||
|
ISSUE_NUMBER=$(jq -r '.issue.number' "$GITHUB_EVENT_PATH")
|
||||||
|
|
||||||
|
if [ "$ISSUE_AUTHOR" = "$REPO_OWNER" ]; then
|
||||||
|
echo "The issue creator is the repository owner. Skipping greeting message."
|
||||||
|
echo "skip_first_request=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "Checking for previous feature requests..."
|
||||||
|
# Get all issues (both open and closed) by the author except the current one
|
||||||
|
PREVIOUS_REQUESTS=$(gh issue list --author "$ISSUE_AUTHOR" --label "New Feature" --state all --json number --jq '. | map(select(.number != '$ISSUE_NUMBER')) | length')
|
||||||
|
echo "User $ISSUE_AUTHOR has submitted $PREVIOUS_REQUESTS feature request(s) previously"
|
||||||
|
|
||||||
|
if [ "$PREVIOUS_REQUESTS" -eq 0 ]; then
|
||||||
|
echo "This is the user's first feature request. Sending greeting message."
|
||||||
|
echo "skip_first_request=false" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "User has previous feature requests. Skipping greeting message."
|
||||||
|
echo "skip_first_request=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Send Greeting Message
|
||||||
|
if: env.skip_label_check == 'false' && env.skip_first_request == 'false'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const issueNumber = context.payload.issue.number;
|
||||||
|
const message = `
|
||||||
|
**💡 Thank you for your feature request!**
|
||||||
|
Your request has been successfully submitted and is now under consideration. We value your input in shaping the future of Dantotsu.
|
||||||
|
|
||||||
|
**📈 What to Expect Next**
|
||||||
|
- Our team will review your request and assess its feasibility.
|
||||||
|
- We may reach out for additional details or clarification.
|
||||||
|
- Updates on the request will be provided, and it may be scheduled for future development.
|
||||||
|
|
||||||
|
**👥 Stay Connected**
|
||||||
|
- **[Discord](https://discord.com/invite/4HPZ5nAWwM)**: Join our community to discuss ideas and stay updated.
|
||||||
|
- **[Telegram](https://t.me/dantotsuapp)**: Connect with us directly for real-time updates.
|
||||||
|
|
||||||
|
We appreciate your suggestion and look forward to potentially implementing it!
|
||||||
|
`;
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
80
.github/workflows/pr_greetings.yml
vendored
Normal file
80
.github/workflows/pr_greetings.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: PR Greetings
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
greeting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check if the PR creator is the repo owner or Weblate
|
||||||
|
id: check_owner
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
|
||||||
|
REPO_OWNER=$(jq -r '.repository.owner.login' "$GITHUB_EVENT_PATH")
|
||||||
|
|
||||||
|
if [ "$PR_AUTHOR" = "$REPO_OWNER" ] || [ "$PR_AUTHOR" = "weblate" ]; then
|
||||||
|
echo "The PR creator is the repository owner or Weblate. Skipping greeting message."
|
||||||
|
echo "skip=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "The PR creator is not the repository owner or Weblate. Checking for previous PRs..."
|
||||||
|
|
||||||
|
# Check for both open and closed pull requests by the author
|
||||||
|
OPEN_PRS=$(gh pr list --author "$PR_AUTHOR" --state open --json number --jq '. | length')
|
||||||
|
CLOSED_PRS=$(gh pr list --author "$PR_AUTHOR" --state closed --json number --jq '. | length')
|
||||||
|
TOTAL_PRS=$((OPEN_PRS + CLOSED_PRS))
|
||||||
|
|
||||||
|
echo "User $PR_AUTHOR has created $TOTAL_PRS pull request(s) in total"
|
||||||
|
echo "Open PRs: $OPEN_PRS"
|
||||||
|
echo "Closed PRs: $CLOSED_PRS"
|
||||||
|
|
||||||
|
if [ "$TOTAL_PRS" -eq 1 ]; then
|
||||||
|
echo "This is the user's first pull request. Sending greeting message."
|
||||||
|
echo "skip=false" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "User has previous pull requests. Skipping greeting message."
|
||||||
|
echo "skip=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Send Greeting Message
|
||||||
|
if: env.skip != 'true'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const message = `
|
||||||
|
**🎉 Thank you for your contribution!**
|
||||||
|
|
||||||
|
Your Pull Request has been successfully submitted and is now awaiting review. We truly appreciate your efforts to improve Dantotsu.
|
||||||
|
|
||||||
|
**👥 Connect with the Community**
|
||||||
|
|
||||||
|
While you're here, why not join our communities to stay engaged?
|
||||||
|
- **[Discord](https://discord.com/invite/4HPZ5nAWwM)**: Chat with fellow developers, ask questions, and get the latest updates.
|
||||||
|
- **[Telegram](https://t.me/dantotsuapp)**: Connect directly with us for real-time discussions and updates.
|
||||||
|
|
||||||
|
**📋 What to Expect Next**
|
||||||
|
- Our team will review your pull request as soon as possible.
|
||||||
|
- You'll receive notifications if further information or changes are needed.
|
||||||
|
- Once approved, your changes will be merged into the main project.
|
||||||
|
|
||||||
|
We're excited to collaborate with you. Stay tuned for updates!
|
||||||
|
`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,6 +2,9 @@
|
|||||||
.gradle/
|
.gradle/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
#kotlin
|
||||||
|
.kotlin/
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
# Local configuration file (sdk path, etc)
|
||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
@@ -33,4 +36,7 @@ output.json
|
|||||||
scripts/
|
scripts/
|
||||||
|
|
||||||
#crowdin
|
#crowdin
|
||||||
crowdin.yml
|
crowdin.yml
|
||||||
|
|
||||||
|
#vscode
|
||||||
|
.vscode
|
||||||
21
README.md
21
README.md
@@ -14,7 +14,24 @@ Dantotsu is an [Anilist](https://anilist.co/) only client.
|
|||||||
|
|
||||||
> **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge!
|
> **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge!
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=030201&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff" /></a>
|
## Terms of Use
|
||||||
|
By downloading, installing, or using this application, you agree to:
|
||||||
|
- Use the application in compliance with all applicable laws
|
||||||
|
- Not use the application to infringe on copyrighted content
|
||||||
|
- Take full responsibility for any extensions you install or use
|
||||||
|
- Understand that the developer(s) are not responsible for third-party extensions or user actions
|
||||||
|
|
||||||
|
This application is designed for anime tracking and legal streaming service integration. The developers do not provide, maintain, or endorse any extensions that enable access to unauthorized content.
|
||||||
|
|
||||||
|
## Important Notice
|
||||||
|
This application is an anime tracking and management tool. The extension system is designed to integrate with legal streaming services like Jellyfin.
|
||||||
|
|
||||||
|
We do not:
|
||||||
|
- Provide or maintain any streaming extensions
|
||||||
|
- Host or distribute copyrighted content
|
||||||
|
- Endorse or encourage copyright infringement
|
||||||
|
|
||||||
|
Users are responsible for ensuring their use of this software complies with local laws and regulations.
|
||||||
|
|
||||||
### 🚀 STAR THIS REPOSITORY TO SUPPORT THE DEVELOPER AND ENCOURAGE THE DEVELOPMENT OF THE APPLICATION!
|
### 🚀 STAR THIS REPOSITORY TO SUPPORT THE DEVELOPER AND ENCOURAGE THE DEVELOPMENT OF THE APPLICATION!
|
||||||
|
|
||||||
@@ -38,4 +55,4 @@ You can come hang out with our awesome community, request new features, and repo
|
|||||||
|
|
||||||
## LICENSE 📜
|
## LICENSE 📜
|
||||||
|
|
||||||
Dantotsu is licensed under the [GNU General Public License v3.0](LICENSE.md)
|
Dantotsu is licensed under the Unabandon Public License (UPL). More info can be found [here.](LICENSE.md)
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ def gitCommitHash = providers.exec {
|
|||||||
}.standardOutput.asText.get().trim()
|
}.standardOutput.asText.get().trim()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 34
|
compileSdk 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ani.dantotsu"
|
applicationId "ani.dantotsu"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 34
|
targetSdk 35
|
||||||
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "3.1.0"
|
versionName "3.2.1"
|
||||||
versionCode 300100000
|
versionCode versionName.split("\\.").collect { it.toInteger() * 100 }.join("") as Integer
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,10 @@ android {
|
|||||||
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round"
|
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round"
|
||||||
debuggable System.getenv("CI") == null
|
debuggable System.getenv("CI") == null
|
||||||
isDefault true
|
isDefault true
|
||||||
|
debuggable true
|
||||||
|
jniDebuggable true
|
||||||
|
minifyEnabled false
|
||||||
|
shrinkResources false
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
@@ -81,26 +85,29 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// FireBase
|
// FireBase
|
||||||
googleImplementation platform('com.google.firebase:firebase-bom:33.0.0')
|
googleImplementation platform('com.google.firebase:firebase-bom:33.13.0')
|
||||||
googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.0.0'
|
googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.4.0'
|
||||||
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.0.0'
|
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.4.3'
|
||||||
// Core
|
// Core
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.browser:browser:1.8.0'
|
implementation 'androidx.browser:browser:1.8.0'
|
||||||
implementation 'androidx.core:core-ktx:1.13.1'
|
implementation 'androidx.core:core-ktx:1.16.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
implementation 'androidx.fragment:fragment-ktx:1.8.6'
|
||||||
|
implementation 'androidx.activity:activity-ktx:1.10.1'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
implementation "androidx.work:work-runtime-ktx:2.10.1"
|
||||||
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.1'
|
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.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.webkit:webkit:1.11.0'
|
implementation 'androidx.webkit:webkit:1.13.0'
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
|
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
ext.glide_version = '4.16.0'
|
ext.glide_version = '4.16.0'
|
||||||
@@ -111,7 +118,7 @@ dependencies {
|
|||||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
ext.exo_version = '1.3.1'
|
ext.exo_version = '1.6.1'
|
||||||
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"
|
||||||
@@ -121,6 +128,8 @@ dependencies {
|
|||||||
// Media3 Casting
|
// Media3 Casting
|
||||||
implementation "androidx.media3:media3-cast:$exo_version"
|
implementation "androidx.media3:media3-cast:$exo_version"
|
||||||
implementation "androidx.mediarouter:mediarouter:1.7.0"
|
implementation "androidx.mediarouter:mediarouter:1.7.0"
|
||||||
|
// Media3 extension
|
||||||
|
implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.4"
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
@@ -129,9 +138,9 @@ dependencies {
|
|||||||
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.3.6'
|
||||||
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
||||||
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.1'
|
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.3'
|
||||||
|
|
||||||
// Markwon
|
// Markwon
|
||||||
ext.markwon_version = '4.6.2'
|
ext.markwon_version = '4.6.2'
|
||||||
@@ -157,14 +166,14 @@ dependencies {
|
|||||||
implementation 'ru.beryukhov:flowreactivenetwork:1.0.4'
|
implementation 'ru.beryukhov:flowreactivenetwork:1.0.4'
|
||||||
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 'uy.kohesive.injekt:injekt-core:1.16.+'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
|
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||||
implementation 'com.squareup.okio:okio:3.8.0'
|
implementation 'com.squareup.okio:okio:3.9.1'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12'
|
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.14'
|
||||||
implementation 'org.jsoup:jsoup:1.16.1'
|
implementation 'org.jsoup:jsoup:1.18.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.7.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'
|
||||||
|
|||||||
@@ -1,9 +1,40 @@
|
|||||||
package ani.dantotsu.others
|
package ani.dantotsu.others
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
object AppUpdater {
|
object AppUpdater {
|
||||||
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
||||||
//no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Serializable
|
||||||
|
data class GithubResponse(
|
||||||
|
@SerialName("html_url")
|
||||||
|
val htmlUrl: String,
|
||||||
|
@SerialName("tag_name")
|
||||||
|
val tagName: String,
|
||||||
|
val prerelease: Boolean,
|
||||||
|
@SerialName("created_at")
|
||||||
|
val createdAt: String,
|
||||||
|
val body: String? = null,
|
||||||
|
val assets: List<Asset>? = null
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Asset(
|
||||||
|
@SerialName("browser_download_url")
|
||||||
|
val browserDownloadURL: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun timeStamp(): Long {
|
||||||
|
return dateFormat.parse(createdAt)!!.time
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import ani.dantotsu.Mapper
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.buildMarkwon
|
import ani.dantotsu.buildMarkwon
|
||||||
import ani.dantotsu.client
|
import ani.dantotsu.client
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.decodeBase64ToString
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
@@ -37,26 +39,88 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
object AppUpdater {
|
object AppUpdater {
|
||||||
|
private val fallbackStableUrl: String
|
||||||
|
get() = "aHR0cHM6Ly9hcGkuZGFudG90c3UuYXBwL3VwZGF0ZXMvc3RhYmxl".decodeBase64ToString()
|
||||||
|
private val fallbackBetaUrl: String
|
||||||
|
get() = "aHR0cHM6Ly9hcGkuZGFudG90c3UuYXBwL3VwZGF0ZXMvYmV0YQ==".decodeBase64ToString()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FallbackResponse(
|
||||||
|
val version: String,
|
||||||
|
val changelog: String,
|
||||||
|
val downloadUrl: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun fetchUpdateInfo(repo: String, isDebug: Boolean): Pair<String, String>? {
|
||||||
|
return try {
|
||||||
|
fetchFromGithub(repo, isDebug)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log("Github fetch failed, trying fallback: ${e.message}")
|
||||||
|
try {
|
||||||
|
fetchFromFallback(isDebug)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log("Fallback fetch failed: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchFromGithub(repo: String, isDebug: Boolean): Pair<String, String> {
|
||||||
|
return if (isDebug) {
|
||||||
|
val res = client.get("https://api.github.com/repos/$repo/releases")
|
||||||
|
.parsed<JsonArray>().map {
|
||||||
|
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
|
||||||
|
}
|
||||||
|
val r = res.filter { it.prerelease }.filter { !it.tagName.contains("fdroid") }
|
||||||
|
.maxByOrNull {
|
||||||
|
it.timeStamp()
|
||||||
|
} ?: throw Exception("No Pre Release Found")
|
||||||
|
val v = r.tagName.substringAfter("v", "")
|
||||||
|
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
|
||||||
|
} else {
|
||||||
|
val res = client.get("https://raw.githubusercontent.com/$repo/main/stable.md").text
|
||||||
|
res to res.substringAfter("# ").substringBefore("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchFromFallback(isDebug: Boolean): Pair<String, String> {
|
||||||
|
val url = if (isDebug) fallbackBetaUrl else fallbackStableUrl
|
||||||
|
val response = CommentsAPI.requestBuilder().get(url).parsed<FallbackResponse>()
|
||||||
|
return response.changelog to response.version
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchApkUrl(repo: String, version: String, isDebug: Boolean): String? {
|
||||||
|
return try {
|
||||||
|
fetchApkUrlFromGithub(repo, version)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log("Github APK fetch failed, trying fallback: ${e.message}")
|
||||||
|
try {
|
||||||
|
fetchApkUrlFromFallback(version, isDebug)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log("Fallback APK fetch failed: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchApkUrlFromGithub(repo: String, version: String): String? {
|
||||||
|
val apks = client.get("https://api.github.com/repos/$repo/releases/tags/v$version")
|
||||||
|
.parsed<GithubResponse>().assets?.filter {
|
||||||
|
it.browserDownloadURL.endsWith(".apk")
|
||||||
|
}
|
||||||
|
return apks?.firstOrNull()?.browserDownloadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchApkUrlFromFallback(version: String, isDebug: Boolean): String? {
|
||||||
|
val url = if (isDebug) fallbackBetaUrl else fallbackStableUrl
|
||||||
|
return CommentsAPI.requestBuilder().get("$url/$version").parsed<FallbackResponse>().downloadUrl
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
|
||||||
if (post) snackString(currContext()?.getString(R.string.checking_for_update))
|
if (post) snackString(currContext()?.getString(R.string.checking_for_update))
|
||||||
val repo = activity.getString(R.string.repo)
|
val repo = activity.getString(R.string.repo)
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
val (md, version) = if (BuildConfig.DEBUG) {
|
val (md, version) = fetchUpdateInfo(repo, BuildConfig.DEBUG) ?: return@tryWithSuspend
|
||||||
val res = client.get("https://api.github.com/repos/$repo/releases")
|
|
||||||
.parsed<JsonArray>().map {
|
|
||||||
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
|
|
||||||
}
|
|
||||||
val r = res.filter { it.prerelease }.filter { !it.tagName.contains("fdroid") }
|
|
||||||
.maxByOrNull {
|
|
||||||
it.timeStamp()
|
|
||||||
} ?: throw Exception("No Pre Release Found")
|
|
||||||
val v = r.tagName.substringAfter("v", "")
|
|
||||||
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
|
|
||||||
} else {
|
|
||||||
val res =
|
|
||||||
client.get("https://raw.githubusercontent.com/$repo/main/stable.md").text
|
|
||||||
res to res.substringAfter("# ").substringBefore("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log("Git Version : $version")
|
Logger.log("Git Version : $version")
|
||||||
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
|
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
|
||||||
@@ -69,7 +133,11 @@ object AppUpdater {
|
|||||||
)
|
)
|
||||||
addView(
|
addView(
|
||||||
TextView(activity).apply {
|
TextView(activity).apply {
|
||||||
val markWon = buildMarkwon(activity, false)
|
val markWon = try {
|
||||||
|
buildMarkwon(activity, false)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
markWon.setMarkdown(this, md)
|
markWon.setMarkdown(this, md)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -85,17 +153,11 @@ object AppUpdater {
|
|||||||
setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
|
setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
|
||||||
MainScope().launch(Dispatchers.IO) {
|
MainScope().launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val apks =
|
val apkUrl = fetchApkUrl(repo, version, BuildConfig.DEBUG)
|
||||||
client.get("https://api.github.com/repos/$repo/releases/tags/v$version")
|
if (apkUrl != null) {
|
||||||
.parsed<GithubResponse>().assets?.filter {
|
activity.downloadUpdate(version, apkUrl)
|
||||||
it.browserDownloadURL.endsWith(
|
} else {
|
||||||
".apk"
|
openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
|
||||||
)
|
|
||||||
}
|
|
||||||
val apkToDownload = apks?.first()
|
|
||||||
apkToDownload?.browserDownloadURL.apply {
|
|
||||||
if (this != null) activity.downloadUpdate(version, this)
|
|
||||||
else openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
@@ -108,8 +170,7 @@ object AppUpdater {
|
|||||||
}
|
}
|
||||||
show(activity.supportFragmentManager, "dialog")
|
show(activity.supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (post) snackString(currContext()?.getString(R.string.no_update_found))
|
if (post) snackString(currContext()?.getString(R.string.no_update_found))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,8 +201,7 @@ object AppUpdater {
|
|||||||
|
|
||||||
|
|
||||||
//Blatantly kanged from https://github.com/LagradOst/CloudStream-3/blob/master/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt
|
//Blatantly kanged from https://github.com/LagradOst/CloudStream-3/blob/master/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt
|
||||||
private fun Activity.downloadUpdate(version: String, url: String): Boolean {
|
private fun Activity.downloadUpdate(version: String, url: String) {
|
||||||
|
|
||||||
toast(getString(R.string.downloading_update, version))
|
toast(getString(R.string.downloading_update, version))
|
||||||
|
|
||||||
val downloadManager = this.getSystemService<DownloadManager>()!!
|
val downloadManager = this.getSystemService<DownloadManager>()!!
|
||||||
@@ -163,7 +223,7 @@ object AppUpdater {
|
|||||||
logError(e)
|
logError(e)
|
||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
if (id == -1L) return true
|
if (id == -1L) return
|
||||||
ContextCompat.registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
this,
|
this,
|
||||||
object : BroadcastReceiver() {
|
object : BroadcastReceiver() {
|
||||||
@@ -184,7 +244,6 @@ object AppUpdater {
|
|||||||
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||||
ContextCompat.RECEIVER_EXPORTED
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
)
|
)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openApk(context: Context, uri: Uri) {
|
private fun openApk(context: Context, uri: Uri) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<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.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29" />
|
android:maxSdkVersion="29" />
|
||||||
@@ -112,10 +113,9 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="application/epub+zip" />
|
<data android:mimeType="application/epub+zip"/>
|
||||||
<data android:mimeType="application/x-mobipocket-ebook" />
|
<data android:mimeType="application/x-mobipocket-ebook" />
|
||||||
<data android:mimeType="application/vnd.amazon.ebook" />
|
<data android:mimeType="application/vnd.amazon.ebook" />
|
||||||
<data android:mimeType="application/fb2+zip" />
|
<data android:mimeType="application/fb2+zip" />
|
||||||
@@ -131,10 +131,11 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".others.calc.CalcActivity"
|
<activity android:name=".others.calc.CalcActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity android:name=".settings.FAQActivity" />
|
<activity android:name=".settings.AnilistSettingsActivity"/>
|
||||||
<activity android:name=".settings.ReaderSettingsActivity" />
|
|
||||||
<activity android:name=".settings.UserInterfaceSettingsActivity" />
|
<activity android:name=".settings.UserInterfaceSettingsActivity" />
|
||||||
<activity android:name=".settings.PlayerSettingsActivity" />
|
<activity android:name=".settings.PlayerSettingsActivity" />
|
||||||
|
<activity android:name=".settings.ReaderSettingsActivity" />
|
||||||
|
<activity android:name=".settings.FAQActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
@@ -155,7 +156,8 @@
|
|||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsExtensionsActivity"
|
android:name=".settings.SettingsExtensionsActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:windowSoftInputMode="adjustPan"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsAddonActivity"
|
android:name=".settings.SettingsAddonActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
@@ -194,14 +196,15 @@
|
|||||||
android:label="Inbox Activity"
|
android:label="Inbox Activity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".profile.activity.NotificationActivity"
|
android:name=".profile.notification.NotificationActivity"
|
||||||
android:label="Inbox Activity"
|
android:label="Inbox Activity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".others.imagesearch.ImageSearchActivity"
|
android:name=".others.imagesearch.ImageSearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".util.MarkdownCreatorActivity"/>
|
android:name=".util.ActivityMarkdownCreator"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateVisible" />
|
||||||
<activity android:name=".parsers.ParserTestActivity" />
|
<activity android:name=".parsers.ParserTestActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".media.ReviewActivity"
|
android:name=".media.ReviewActivity"
|
||||||
@@ -370,24 +373,29 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.Main" />
|
<action android:name="android.intent.action.Main" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
|
<data android:scheme="file" />
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
<data android:pathPattern=".*\\.ani" />
|
<data android:pathPattern=".*\\.ani" />
|
||||||
<data android:pathPattern=".*\\.sani" />
|
<data android:pathPattern=".*\\.sani" />
|
||||||
<data android:host="*" />
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:host="add-repo"/>
|
||||||
|
<data android:scheme="tachiyomi"/>
|
||||||
|
<data android:scheme="aniyomi"/>
|
||||||
|
<data android:scheme="novelyomi"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -105,21 +105,36 @@ class App : MultiDexApplication() {
|
|||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 0) {
|
||||||
|
if (BuildConfig.FLAVOR.contains("fdroid")) {
|
||||||
|
PrefManager.setVal(PrefName.CommentsEnabled, 2)
|
||||||
|
} else {
|
||||||
|
PrefManager.setVal(PrefName.CommentsEnabled, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
scope.launch {
|
||||||
animeExtensionManager = Injekt.get()
|
animeExtensionManager = Injekt.get()
|
||||||
animeExtensionManager.findAvailableExtensions()
|
launch {
|
||||||
|
animeExtensionManager.findAvailableExtensions()
|
||||||
|
}
|
||||||
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
||||||
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
scope.launch {
|
||||||
mangaExtensionManager = Injekt.get()
|
mangaExtensionManager = Injekt.get()
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
launch {
|
||||||
|
mangaExtensionManager.findAvailableExtensions()
|
||||||
|
}
|
||||||
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
||||||
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
scope.launch {
|
||||||
novelExtensionManager = Injekt.get()
|
novelExtensionManager = Injekt.get()
|
||||||
novelExtensionManager.findAvailableExtensions()
|
launch {
|
||||||
|
novelExtensionManager.findAvailableExtensions()
|
||||||
|
}
|
||||||
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
||||||
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
@@ -128,7 +143,9 @@ class App : MultiDexApplication() {
|
|||||||
downloadAddonManager = Injekt.get()
|
downloadAddonManager = Injekt.get()
|
||||||
torrentAddonManager.init()
|
torrentAddonManager.init()
|
||||||
downloadAddonManager.init()
|
downloadAddonManager.init()
|
||||||
CommentsAPI.fetchAuthToken(this@App)
|
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1) {
|
||||||
|
CommentsAPI.fetchAuthToken(this@App)
|
||||||
|
}
|
||||||
|
|
||||||
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
|
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
|
||||||
val scheduler = TaskScheduler.create(this@App, useAlarmManager)
|
val scheduler = TaskScheduler.create(this@App, useAlarmManager)
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ import android.widget.ImageView
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
@@ -92,12 +91,12 @@ 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.bakaupdates.MangaUpdates
|
|
||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
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.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.notifications.IncognitoNotificationClickReceiver
|
import ani.dantotsu.notifications.IncognitoNotificationClickReceiver
|
||||||
|
import ani.dantotsu.others.AlignTagHandler
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.SpoilerPlugin
|
import ani.dantotsu.others.SpoilerPlugin
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
@@ -106,7 +105,6 @@ import ani.dantotsu.settings.saving.PrefManager
|
|||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
||||||
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
|
||||||
import ani.dantotsu.util.CountUpTimer
|
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.RequestBuilder
|
import com.bumptech.glide.RequestBuilder
|
||||||
@@ -120,7 +118,6 @@ import com.bumptech.glide.load.resource.gif.GifDrawable
|
|||||||
import com.bumptech.glide.request.RequestListener
|
import com.bumptech.glide.request.RequestListener
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.request.target.ViewTarget
|
|
||||||
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
|
||||||
@@ -140,12 +137,9 @@ import io.noties.markwon.html.TagHandlerNoOp
|
|||||||
import io.noties.markwon.image.AsyncDrawable
|
import io.noties.markwon.image.AsyncDrawable
|
||||||
import io.noties.markwon.image.glide.GlideImagesPlugin
|
import io.noties.markwon.image.glide.GlideImagesPlugin
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@@ -154,10 +148,13 @@ import java.io.FileOutputStream
|
|||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import java.util.TimerTask
|
import java.util.TimerTask
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
import kotlin.math.log2
|
import kotlin.math.log2
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -314,6 +311,7 @@ fun Activity.reloadActivity() {
|
|||||||
Refresh.all()
|
Refresh.all()
|
||||||
finish()
|
finish()
|
||||||
startActivity(Intent(this, this::class.java))
|
startActivity(Intent(this, this::class.java))
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,6 +853,7 @@ fun savePrefsToDownloads(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StringFormatMatches")
|
||||||
fun savePrefs(serialized: String, path: String, title: String, context: Context): File? {
|
fun savePrefs(serialized: String, path: String, title: String, context: Context): File? {
|
||||||
var file = File(path, "$title.ani")
|
var file = File(path, "$title.ani")
|
||||||
var counter = 1
|
var counter = 1
|
||||||
@@ -874,6 +873,7 @@ fun savePrefs(serialized: String, path: String, title: String, context: Context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StringFormatMatches")
|
||||||
fun savePrefs(
|
fun savePrefs(
|
||||||
serialized: String,
|
serialized: String,
|
||||||
path: String,
|
path: String,
|
||||||
@@ -921,6 +921,7 @@ fun shareImage(title: String, bitmap: Bitmap, context: Context) {
|
|||||||
context.startActivity(Intent.createChooser(intent, "Share $title"))
|
context.startActivity(Intent.createChooser(intent, "Share $title"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StringFormatMatches")
|
||||||
fun saveImage(image: Bitmap, path: String, imageFileName: String): File? {
|
fun saveImage(image: Bitmap, path: String, imageFileName: String): File? {
|
||||||
val imageFile = File(path, "$imageFileName.png")
|
val imageFile = File(path, "$imageFileName.png")
|
||||||
return try {
|
return try {
|
||||||
@@ -1010,47 +1011,10 @@ fun countDown(media: Media, view: ViewGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sinceWhen(media: Media, view: ViewGroup) {
|
|
||||||
if (media.status != "RELEASING" && media.status != "HIATUS") return
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
MangaUpdates().search(media.mangaName(), media.startDate)?.let {
|
|
||||||
val latestChapter = MangaUpdates.getLatestChapter(view.context, it)
|
|
||||||
val timeSince = (System.currentTimeMillis() -
|
|
||||||
(it.metadata.series.lastUpdated!!.timestamp * 1000)) / 1000
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val v =
|
|
||||||
ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
|
|
||||||
view.addView(v.root, 0)
|
|
||||||
v.mediaCountdownText.text =
|
|
||||||
currActivity()?.getString(R.string.chapter_release_timeout, latestChapter)
|
|
||||||
|
|
||||||
object : CountUpTimer(86400000) {
|
|
||||||
override fun onTick(second: Int) {
|
|
||||||
val a = second + timeSince
|
|
||||||
v.mediaCountdown.text = currActivity()?.getString(
|
|
||||||
R.string.time_format,
|
|
||||||
a / 86400,
|
|
||||||
a % 86400 / 3600,
|
|
||||||
a % 86400 % 3600 / 60,
|
|
||||||
a % 86400 % 3600 % 60
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinish() {
|
|
||||||
// The legend will never die.
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun displayTimer(media: Media, view: ViewGroup) {
|
fun displayTimer(media: Media, view: ViewGroup) {
|
||||||
when {
|
when {
|
||||||
media.anime != null -> countDown(media, view)
|
media.anime != null -> countDown(media, view)
|
||||||
media.format == "MANGA" || media.format == "ONE_SHOT" -> sinceWhen(media, view)
|
else -> {}
|
||||||
else -> {} // No timer yet
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1447,6 +1411,8 @@ fun openOrCopyAnilistLink(link: String) {
|
|||||||
} else {
|
} else {
|
||||||
copyToClipboard(link, true)
|
copyToClipboard(link, true)
|
||||||
}
|
}
|
||||||
|
} else if (getYoutubeId(link).isNotEmpty()) {
|
||||||
|
openLinkInYouTube(link)
|
||||||
} else {
|
} else {
|
||||||
copyToClipboard(link, true)
|
copyToClipboard(link, true)
|
||||||
}
|
}
|
||||||
@@ -1483,6 +1449,7 @@ fun buildMarkwon(
|
|||||||
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
|
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
plugin.addHandler(AlignTagHandler())
|
||||||
})
|
})
|
||||||
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
|
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
|
||||||
|
|
||||||
@@ -1527,3 +1494,44 @@ fun buildMarkwon(
|
|||||||
.build()
|
.build()
|
||||||
return markwon
|
return markwon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getYoutubeId(url: String): String {
|
||||||
|
val regex =
|
||||||
|
"""(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|(?:youtu\.be|youtube\.com)/)([^"&?/\s]{11})|youtube\.com/""".toRegex()
|
||||||
|
val matchResult = regex.find(url)
|
||||||
|
return matchResult?.groupValues?.getOrNull(1) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLanguageCode(language: String): CharSequence {
|
||||||
|
val locales = Locale.getAvailableLocales()
|
||||||
|
for (locale in locales) {
|
||||||
|
if (locale.displayLanguage.equals(language, ignoreCase = true)) {
|
||||||
|
val lang: CharSequence = locale.language
|
||||||
|
return lang
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val out: CharSequence = "null"
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLanguageName(language: String): String? {
|
||||||
|
val locales = Locale.getAvailableLocales()
|
||||||
|
for (locale in locales) {
|
||||||
|
if (locale.language.equals(language, ignoreCase = true)) {
|
||||||
|
return locale.displayLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
|
fun String.decodeBase64ToString(): String {
|
||||||
|
return try {
|
||||||
|
String(Base64.decode(this), Charsets.UTF_8)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package ani.dantotsu
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
@@ -51,7 +50,8 @@ import ani.dantotsu.others.CustomBottomDialog
|
|||||||
import ani.dantotsu.others.calc.CalcActivity
|
import ani.dantotsu.others.calc.CalcActivity
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.profile.activity.FeedActivity
|
import ani.dantotsu.profile.activity.FeedActivity
|
||||||
import ani.dantotsu.profile.activity.NotificationActivity
|
import ani.dantotsu.profile.notification.NotificationActivity
|
||||||
|
import ani.dantotsu.settings.AddRepositoryBottomSheet
|
||||||
import ani.dantotsu.settings.ExtensionsActivity
|
import ani.dantotsu.settings.ExtensionsActivity
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefManager.asLiveBool
|
import ani.dantotsu.settings.saving.PrefManager.asLiveBool
|
||||||
@@ -60,10 +60,11 @@ import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData
|
|||||||
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
|
||||||
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.util.AudioHelper
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
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 io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
@@ -116,58 +117,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val action = intent.action
|
if (Intent.ACTION_VIEW == intent.action) {
|
||||||
val type = intent.type
|
handleViewIntent(intent)
|
||||||
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 bottomNavBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
val bottomNavBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||||
@@ -287,7 +238,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
.get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0
|
.get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0
|
||||||
) {
|
) {
|
||||||
snackString(R.string.extension_updates_available)
|
snackString(R.string.extension_updates_available)
|
||||||
?.setDuration(Snackbar.LENGTH_LONG)
|
?.setDuration(Snackbar.LENGTH_SHORT)
|
||||||
?.setAction(R.string.review) {
|
?.setAction(R.string.review) {
|
||||||
startActivity(Intent(this, ExtensionsActivity::class.java))
|
startActivity(Intent(this, ExtensionsActivity::class.java))
|
||||||
}
|
}
|
||||||
@@ -365,7 +316,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
} else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) {
|
} else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) {
|
||||||
Logger.log("MainActivity, onCreate: $activityId")
|
Logger.log("MainActivity, onCreate: $activityId")
|
||||||
val notificationIntent = Intent(this, NotificationActivity::class.java).apply {
|
val notificationIntent = Intent(this, NotificationActivity::class.java).apply {
|
||||||
putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS")
|
|
||||||
putExtra("activityId", activityId)
|
putExtra("activityId", activityId)
|
||||||
}
|
}
|
||||||
launched = true
|
launched = true
|
||||||
@@ -455,7 +405,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (PrefManager.getVal(PrefName.OC)) {
|
||||||
|
AudioHelper.run(this, R.raw.audio)
|
||||||
|
PrefManager.setVal(PrefName.OC, false)
|
||||||
|
}
|
||||||
val torrentManager = Injekt.get<TorrentAddonManager>()
|
val torrentManager = Injekt.get<TorrentAddonManager>()
|
||||||
fun startTorrent() {
|
fun startTorrent() {
|
||||||
if (torrentManager.isAvailable() && PrefManager.getVal(PrefName.TorrentEnabled)) {
|
if (torrentManager.isAvailable() && PrefManager.getVal(PrefName.TorrentEnabled)) {
|
||||||
@@ -490,39 +443,102 @@ class MainActivity : AppCompatActivity() {
|
|||||||
params.updateMargins(bottom = margin.toPx)
|
params.updateMargins(bottom = margin.toPx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleViewIntent(intent: Intent) {
|
||||||
|
val uri: Uri? = intent.data
|
||||||
|
try {
|
||||||
|
if (uri == null) {
|
||||||
|
throw Exception("Uri is null")
|
||||||
|
}
|
||||||
|
if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi" || uri.scheme == "novelyomi") && uri.host == "add-repo") {
|
||||||
|
val url = uri.getQueryParameter("url") ?: throw Exception("No url for repo import")
|
||||||
|
val (prefName, name) = when (uri.scheme) {
|
||||||
|
"tachiyomi" -> PrefName.MangaExtensionRepos to "Manga"
|
||||||
|
"aniyomi" -> PrefName.AnimeExtensionRepos to "Anime"
|
||||||
|
"novelyomi" -> PrefName.NovelExtensionRepos to "Novel"
|
||||||
|
else -> throw Exception("Invalid scheme")
|
||||||
|
}
|
||||||
|
val savedRepos: Set<String> = PrefManager.getVal(prefName)
|
||||||
|
val newRepos = savedRepos.toMutableSet()
|
||||||
|
AddRepositoryBottomSheet.addRepoWarning(this) {
|
||||||
|
newRepos.add(url)
|
||||||
|
PrefManager.setVal(prefName, newRepos)
|
||||||
|
toast("$name Extension Repo added")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (intent.type == null) return
|
||||||
|
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 newIntent = Intent(this, this.javaClass)
|
||||||
|
this.finish()
|
||||||
|
startActivity(newIntent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name.endsWith(".ani")) {
|
||||||
|
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
||||||
|
if (PreferencePackager.unpack(decryptedJson)) {
|
||||||
|
val newIntent = Intent(this, this.javaClass)
|
||||||
|
this.finish()
|
||||||
|
startActivity(newIntent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Invalid file type")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
toast("Error importing settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
|
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
|
||||||
val password = CharArray(16).apply { fill('0') }
|
val password = CharArray(16).apply { fill('0') }
|
||||||
|
|
||||||
// Inflate the dialog layout
|
// Inflate the dialog layout
|
||||||
val dialogView = DialogUserAgentBinding.inflate(layoutInflater)
|
val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply {
|
||||||
dialogView.userAgentTextBox.hint = "Password"
|
userAgentTextBox.hint = "Password"
|
||||||
dialogView.subtitle.visibility = View.VISIBLE
|
subtitle.visibility = View.VISIBLE
|
||||||
dialogView.subtitle.text = getString(R.string.enter_password_to_decrypt_file)
|
subtitle.text = getString(R.string.enter_password_to_decrypt_file)
|
||||||
|
}
|
||||||
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
|
customAlertDialog().apply {
|
||||||
.setTitle("Enter Password")
|
setTitle("Enter Password")
|
||||||
.setView(dialogView.root)
|
setCustomView(dialogView.root)
|
||||||
.setPositiveButton("OK", null)
|
setPosButton(R.string.yes) {
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
val editText = dialogView.userAgentTextBox
|
||||||
|
if (editText.text?.isNotBlank() == true) {
|
||||||
|
editText.text?.toString()?.trim()?.toCharArray(password)
|
||||||
|
callback(password)
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegButton(R.string.cancel) {
|
||||||
password.fill('0')
|
password.fill('0')
|
||||||
dialog.dismiss()
|
|
||||||
callback(null)
|
callback(null)
|
||||||
}
|
}
|
||||||
.create()
|
show()
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,17 @@ interface DownloadAddonApiV2 {
|
|||||||
statCallback: (Double) -> Unit
|
statCallback: (Double) -> Unit
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
suspend fun customFFMpeg(command: String, videoUrls: List<String>, logCallback: (String) -> Unit): Long
|
suspend fun customFFMpeg(
|
||||||
|
command: String,
|
||||||
|
videoUrls: List<String>,
|
||||||
|
logCallback: (String) -> Unit
|
||||||
|
): Long
|
||||||
|
|
||||||
suspend fun customFFProbe(command: String, videoUrls: List<String>, logCallback: (String) -> Unit)
|
suspend fun customFFProbe(
|
||||||
|
command: String,
|
||||||
|
videoUrls: List<String>,
|
||||||
|
logCallback: (String) -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
fun getState(sessionId: Long): String
|
fun getState(sessionId: Long): String
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.addons.AddonDownloader.Companion.hasUpdate
|
import ani.dantotsu.addons.AddonDownloader.Companion.hasUpdate
|
||||||
|
import ani.dantotsu.addons.AddonInstallReceiver
|
||||||
import ani.dantotsu.addons.AddonListener
|
import ani.dantotsu.addons.AddonListener
|
||||||
import ani.dantotsu.addons.AddonLoader
|
import ani.dantotsu.addons.AddonLoader
|
||||||
import ani.dantotsu.addons.AddonManager
|
import ani.dantotsu.addons.AddonManager
|
||||||
import ani.dantotsu.addons.LoadResult
|
import ani.dantotsu.addons.LoadResult
|
||||||
import ani.dantotsu.addons.AddonInstallReceiver
|
|
||||||
import ani.dantotsu.media.AddonType
|
import ani.dantotsu.media.AddonType
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class TorrentServerService : Service() {
|
|||||||
flags: Int,
|
flags: Int,
|
||||||
startId: Int,
|
startId: Int,
|
||||||
): Int {
|
): Int {
|
||||||
extension = Injekt.get<TorrentAddonManager>().extension?.extension ?: return START_NOT_STICKY
|
extension =
|
||||||
|
Injekt.get<TorrentAddonManager>().extension?.extension ?: return START_NOT_STICKY
|
||||||
intent?.let {
|
intent?.let {
|
||||||
if (it.action != null) {
|
if (it.action != null) {
|
||||||
when (it.action) {
|
when (it.action) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.media3.common.util.UnstableApi
|
|||||||
import androidx.media3.database.StandaloneDatabaseProvider
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
import ani.dantotsu.addons.download.DownloadAddonManager
|
import ani.dantotsu.addons.download.DownloadAddonManager
|
||||||
import ani.dantotsu.addons.torrent.TorrentAddonManager
|
import ani.dantotsu.addons.torrent.TorrentAddonManager
|
||||||
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
|
||||||
|
|||||||
@@ -2,15 +2,25 @@ package ani.dantotsu.connections.anilist
|
|||||||
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.media.Author
|
||||||
|
import ani.dantotsu.media.Character
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.Studio
|
||||||
|
import ani.dantotsu.profile.User
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class SearchResults(
|
interface SearchResults<T> {
|
||||||
|
var search: String?
|
||||||
|
var page: Int
|
||||||
|
var results: MutableList<T>
|
||||||
|
var hasNextPage: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AniMangaSearchResults(
|
||||||
val type: String,
|
val type: String,
|
||||||
var isAdult: Boolean,
|
var isAdult: Boolean,
|
||||||
var onList: Boolean? = null,
|
var onList: Boolean? = null,
|
||||||
var perPage: Int? = null,
|
var perPage: Int? = null,
|
||||||
var search: String? = null,
|
|
||||||
var countryOfOrigin: String? = null,
|
var countryOfOrigin: String? = null,
|
||||||
var sort: String? = null,
|
var sort: String? = null,
|
||||||
var genres: MutableList<String>? = null,
|
var genres: MutableList<String>? = null,
|
||||||
@@ -23,10 +33,11 @@ data class SearchResults(
|
|||||||
var seasonYear: Int? = null,
|
var seasonYear: Int? = null,
|
||||||
var startYear: Int? = null,
|
var startYear: Int? = null,
|
||||||
var season: String? = null,
|
var season: String? = null,
|
||||||
var page: Int = 1,
|
override var search: String? = null,
|
||||||
var results: MutableList<Media>,
|
override var page: Int = 1,
|
||||||
var hasNextPage: Boolean,
|
override var results: MutableList<Media>,
|
||||||
) : Serializable {
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Media>, Serializable {
|
||||||
fun toChipList(): List<SearchChip> {
|
fun toChipList(): List<SearchChip> {
|
||||||
val list = mutableListOf<SearchChip>()
|
val list = mutableListOf<SearchChip>()
|
||||||
sort?.let {
|
sort?.let {
|
||||||
@@ -108,4 +119,33 @@ data class SearchResults(
|
|||||||
val type: String,
|
val type: String,
|
||||||
val text: String
|
val text: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CharacterSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Character>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Character>, Serializable
|
||||||
|
|
||||||
|
data class StudioSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Studio>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Studio>, Serializable
|
||||||
|
|
||||||
|
|
||||||
|
data class StaffSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Author>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Author>, Serializable
|
||||||
|
|
||||||
|
data class UserSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<User>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<User>, Serializable
|
||||||
@@ -15,6 +15,8 @@ import ani.dantotsu.snackString
|
|||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
object Anilist {
|
object Anilist {
|
||||||
val query: AnilistQueries = AnilistQueries()
|
val query: AnilistQueries = AnilistQueries()
|
||||||
@@ -22,7 +24,7 @@ object Anilist {
|
|||||||
|
|
||||||
var token: String? = null
|
var token: String? = null
|
||||||
var username: String? = null
|
var username: String? = null
|
||||||
var adult: Boolean = false
|
|
||||||
var userid: Int? = null
|
var userid: Int? = null
|
||||||
var avatar: String? = null
|
var avatar: String? = null
|
||||||
var bg: String? = null
|
var bg: String? = null
|
||||||
@@ -36,6 +38,17 @@ object Anilist {
|
|||||||
var rateLimitReset: Long = 0
|
var rateLimitReset: Long = 0
|
||||||
|
|
||||||
var initialized = false
|
var initialized = false
|
||||||
|
var adult: Boolean = false
|
||||||
|
var titleLanguage: String? = null
|
||||||
|
var staffNameLanguage: String? = null
|
||||||
|
var airingNotifications: Boolean = false
|
||||||
|
var restrictMessagesToFollowing: Boolean = false
|
||||||
|
var scoreFormat: String? = null
|
||||||
|
var rowOrder: String? = null
|
||||||
|
var activityMergeTime: Int? = null
|
||||||
|
var timezone: String? = null
|
||||||
|
var animeCustomLists: List<String>? = null
|
||||||
|
var mangaCustomLists: List<String>? = null
|
||||||
|
|
||||||
val sortBy = listOf(
|
val sortBy = listOf(
|
||||||
"SCORE_DESC",
|
"SCORE_DESC",
|
||||||
@@ -96,6 +109,86 @@ object Anilist {
|
|||||||
"Original Creator", "Story & Art", "Story"
|
"Original Creator", "Story & Art", "Story"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val timeZone = listOf(
|
||||||
|
"(GMT-11:00) Pago Pago",
|
||||||
|
"(GMT-10:00) Hawaii Time",
|
||||||
|
"(GMT-09:00) Alaska Time",
|
||||||
|
"(GMT-08:00) Pacific Time",
|
||||||
|
"(GMT-07:00) Mountain Time",
|
||||||
|
"(GMT-06:00) Central Time",
|
||||||
|
"(GMT-05:00) Eastern Time",
|
||||||
|
"(GMT-04:00) Atlantic Time - Halifax",
|
||||||
|
"(GMT-03:00) Sao Paulo",
|
||||||
|
"(GMT-02:00) Mid-Atlantic",
|
||||||
|
"(GMT-01:00) Azores",
|
||||||
|
"(GMT+00:00) London",
|
||||||
|
"(GMT+01:00) Berlin",
|
||||||
|
"(GMT+02:00) Helsinki",
|
||||||
|
"(GMT+03:00) Istanbul",
|
||||||
|
"(GMT+04:00) Dubai",
|
||||||
|
"(GMT+04:30) Kabul",
|
||||||
|
"(GMT+05:00) Maldives",
|
||||||
|
"(GMT+05:30) India Standard Time",
|
||||||
|
"(GMT+05:45) Kathmandu",
|
||||||
|
"(GMT+06:00) Dhaka",
|
||||||
|
"(GMT+06:30) Cocos",
|
||||||
|
"(GMT+07:00) Bangkok",
|
||||||
|
"(GMT+08:00) Hong Kong",
|
||||||
|
"(GMT+08:30) Pyongyang",
|
||||||
|
"(GMT+09:00) Tokyo",
|
||||||
|
"(GMT+09:30) Central Time - Darwin",
|
||||||
|
"(GMT+10:00) Eastern Time - Brisbane",
|
||||||
|
"(GMT+10:30) Central Time - Adelaide",
|
||||||
|
"(GMT+11:00) Eastern Time - Melbourne, Sydney",
|
||||||
|
"(GMT+12:00) Nauru",
|
||||||
|
"(GMT+13:00) Auckland",
|
||||||
|
"(GMT+14:00) Kiritimati",
|
||||||
|
)
|
||||||
|
|
||||||
|
val titleLang = listOf(
|
||||||
|
"English (Attack on Titan)",
|
||||||
|
"Romaji (Shingeki no Kyojin)",
|
||||||
|
"Native (進撃の巨人)"
|
||||||
|
)
|
||||||
|
|
||||||
|
val staffNameLang = listOf(
|
||||||
|
"Romaji, Western Order (Killua Zoldyck)",
|
||||||
|
"Romaji (Zoldyck Killua)",
|
||||||
|
"Native (キルア=ゾルディック)"
|
||||||
|
)
|
||||||
|
|
||||||
|
val scoreFormats = listOf(
|
||||||
|
"100 Point (55/100)",
|
||||||
|
"10 Point Decimal (5.5/10)",
|
||||||
|
"10 Point (5/10)",
|
||||||
|
"5 Star (3/5)",
|
||||||
|
"3 Point Smiley :)"
|
||||||
|
)
|
||||||
|
|
||||||
|
val rowOrderMap = mapOf(
|
||||||
|
"Score" to "score",
|
||||||
|
"Title" to "title",
|
||||||
|
"Last Updated" to "updatedAt",
|
||||||
|
"Last Added" to "id"
|
||||||
|
)
|
||||||
|
|
||||||
|
val activityMergeTimeMap = mapOf(
|
||||||
|
"Never" to 0,
|
||||||
|
"30 mins" to 30,
|
||||||
|
"69 mins" to 69,
|
||||||
|
"1 hour" to 60,
|
||||||
|
"2 hours" to 120,
|
||||||
|
"3 hours" to 180,
|
||||||
|
"6 hours" to 360,
|
||||||
|
"12 hours" to 720,
|
||||||
|
"1 day" to 1440,
|
||||||
|
"2 days" to 2880,
|
||||||
|
"3 days" to 4320,
|
||||||
|
"1 week" to 10080,
|
||||||
|
"2 weeks" to 20160,
|
||||||
|
"Always" to 29160
|
||||||
|
)
|
||||||
|
|
||||||
private val cal: Calendar = Calendar.getInstance()
|
private val cal: Calendar = Calendar.getInstance()
|
||||||
private val currentYear = cal.get(Calendar.YEAR)
|
private val currentYear = cal.get(Calendar.YEAR)
|
||||||
private val currentSeason: Int = when (cal.get(Calendar.MONTH)) {
|
private val currentSeason: Int = when (cal.get(Calendar.MONTH)) {
|
||||||
@@ -106,6 +199,33 @@ object Anilist {
|
|||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDisplayTimezone(apiTimezone: String, context: Context): String {
|
||||||
|
val noTimezone = context.getString(R.string.selected_no_time_zone)
|
||||||
|
val parts = apiTimezone.split(":")
|
||||||
|
if (parts.size != 2) return noTimezone
|
||||||
|
|
||||||
|
val hours = parts[0].toIntOrNull() ?: 0
|
||||||
|
val minutes = parts[1].toIntOrNull() ?: 0
|
||||||
|
val sign = if (hours >= 0) "+" else "-"
|
||||||
|
val formattedHours = String.format(Locale.US, "%02d", abs(hours))
|
||||||
|
val formattedMinutes = String.format(Locale.US, "%02d", minutes)
|
||||||
|
|
||||||
|
val searchString = "(GMT$sign$formattedHours:$formattedMinutes)"
|
||||||
|
return timeZone.find { it.contains(searchString) } ?: noTimezone
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getApiTimezone(displayTimezone: String): String {
|
||||||
|
val regex = """\(GMT([+-])(\d{2}):(\d{2})\)""".toRegex()
|
||||||
|
val matchResult = regex.find(displayTimezone)
|
||||||
|
return if (matchResult != null) {
|
||||||
|
val (sign, hours, minutes) = matchResult.destructured
|
||||||
|
val formattedSign = if (sign == "+") "" else "-"
|
||||||
|
"$formattedSign$hours:$minutes"
|
||||||
|
} else {
|
||||||
|
"00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSeason(next: Boolean): Pair<String, Int> {
|
private fun getSeason(next: Boolean): Pair<String, Int> {
|
||||||
var newSeason = if (next) currentSeason + 1 else currentSeason - 1
|
var newSeason = if (next) currentSeason + 1 else currentSeason - 1
|
||||||
var newYear = currentYear
|
var newYear = currentYear
|
||||||
|
|||||||
@@ -3,16 +3,99 @@ package ani.dantotsu.connections.anilist
|
|||||||
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
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 com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
class AnilistMutations {
|
class AnilistMutations {
|
||||||
|
|
||||||
|
suspend fun updateSettings(
|
||||||
|
timezone: String? = null,
|
||||||
|
titleLanguage: String? = null,
|
||||||
|
staffNameLanguage: String? = null,
|
||||||
|
activityMergeTime: Int? = null,
|
||||||
|
airingNotifications: Boolean? = null,
|
||||||
|
displayAdultContent: Boolean? = null,
|
||||||
|
restrictMessagesToFollowing: Boolean? = null,
|
||||||
|
scoreFormat: String? = null,
|
||||||
|
rowOrder: String? = null,
|
||||||
|
) {
|
||||||
|
val query = """
|
||||||
|
mutation (
|
||||||
|
${"$"}timezone: String,
|
||||||
|
${"$"}titleLanguage: UserTitleLanguage,
|
||||||
|
${"$"}staffNameLanguage: UserStaffNameLanguage,
|
||||||
|
${"$"}activityMergeTime: Int,
|
||||||
|
${"$"}airingNotifications: Boolean,
|
||||||
|
${"$"}displayAdultContent: Boolean,
|
||||||
|
${"$"}restrictMessagesToFollowing: Boolean,
|
||||||
|
${"$"}scoreFormat: ScoreFormat,
|
||||||
|
${"$"}rowOrder: String
|
||||||
|
) {
|
||||||
|
UpdateUser(
|
||||||
|
timezone: ${"$"}timezone,
|
||||||
|
titleLanguage: ${"$"}titleLanguage,
|
||||||
|
staffNameLanguage: ${"$"}staffNameLanguage,
|
||||||
|
activityMergeTime: ${"$"}activityMergeTime,
|
||||||
|
airingNotifications: ${"$"}airingNotifications,
|
||||||
|
displayAdultContent: ${"$"}displayAdultContent,
|
||||||
|
restrictMessagesToFollowing: ${"$"}restrictMessagesToFollowing,
|
||||||
|
scoreFormat: ${"$"}scoreFormat,
|
||||||
|
rowOrder: ${"$"}rowOrder,
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
options {
|
||||||
|
timezone
|
||||||
|
titleLanguage
|
||||||
|
staffNameLanguage
|
||||||
|
activityMergeTime
|
||||||
|
airingNotifications
|
||||||
|
displayAdultContent
|
||||||
|
restrictMessagesToFollowing
|
||||||
|
}
|
||||||
|
mediaListOptions {
|
||||||
|
scoreFormat
|
||||||
|
rowOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val variables = """
|
||||||
|
{
|
||||||
|
${timezone?.let { """"timezone":"$it"""" } ?: ""}
|
||||||
|
${titleLanguage?.let { """"titleLanguage":"$it"""" } ?: ""}
|
||||||
|
${staffNameLanguage?.let { """"staffNameLanguage":"$it"""" } ?: ""}
|
||||||
|
${activityMergeTime?.let { """"activityMergeTime":$it""" } ?: ""}
|
||||||
|
${airingNotifications?.let { """"airingNotifications":$it""" } ?: ""}
|
||||||
|
${displayAdultContent?.let { """"displayAdultContent":$it""" } ?: ""}
|
||||||
|
${restrictMessagesToFollowing?.let { """"restrictMessagesToFollowing":$it""" } ?: ""}
|
||||||
|
${scoreFormat?.let { """"scoreFormat":"$it"""" } ?: ""}
|
||||||
|
${rowOrder?.let { """"rowOrder":"$it"""" } ?: ""}
|
||||||
|
}
|
||||||
|
""".trimIndent().replace("\n", "").replace(""" """, "").replace(",}", "}")
|
||||||
|
|
||||||
|
executeQuery<JsonObject>(query, variables)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun toggleFav(anime: Boolean = true, id: Int) {
|
suspend fun toggleFav(anime: Boolean = true, id: Int) {
|
||||||
val query =
|
val query = """
|
||||||
"""mutation (${"$"}animeId: Int,${"$"}mangaId:Int) { ToggleFavourite(animeId:${"$"}animeId,mangaId:${"$"}mangaId){ anime { edges { id } } manga { edges { id } } } }"""
|
mutation (${"$"}animeId: Int, ${"$"}mangaId: Int) {
|
||||||
|
ToggleFavourite(animeId: ${"$"}animeId, mangaId: ${"$"}mangaId) {
|
||||||
|
anime {
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manga {
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val variables = if (anime) """{"animeId":"$id"}""" else """{"mangaId":"$id"}"""
|
val variables = if (anime) """{"animeId":"$id"}""" else """{"mangaId":"$id"}"""
|
||||||
executeQuery<JsonObject>(query, variables)
|
executeQuery<JsonObject>(query, variables)
|
||||||
}
|
}
|
||||||
@@ -25,7 +108,17 @@ class AnilistMutations {
|
|||||||
FavType.STAFF -> "staffId"
|
FavType.STAFF -> "staffId"
|
||||||
FavType.STUDIO -> "studioId"
|
FavType.STUDIO -> "studioId"
|
||||||
}
|
}
|
||||||
val query = """mutation{ToggleFavourite($filter:$id){anime{pageInfo{total}}}}"""
|
val query = """
|
||||||
|
mutation {
|
||||||
|
ToggleFavourite($filter: $id) {
|
||||||
|
anime {
|
||||||
|
pageInfo {
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val result = executeQuery<JsonObject>(query)
|
val result = executeQuery<JsonObject>(query)
|
||||||
return result?.get("errors") == null && result != null
|
return result?.get("errors") == null && result != null
|
||||||
}
|
}
|
||||||
@@ -34,6 +127,54 @@ class AnilistMutations {
|
|||||||
ANIME, MANGA, CHARACTER, STAFF, STUDIO
|
ANIME, MANGA, CHARACTER, STAFF, STUDIO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteCustomList(name: String, type: String): Boolean {
|
||||||
|
val query = """
|
||||||
|
mutation (${"$"}name: String, ${"$"}type: MediaType) {
|
||||||
|
DeleteCustomList(customList: ${"$"}name, type: ${"$"}type) {
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val variables = """
|
||||||
|
{
|
||||||
|
"name": "$name",
|
||||||
|
"type": "$type"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val result = executeQuery<JsonObject>(query, variables)
|
||||||
|
return result?.get("errors") == null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateCustomLists(
|
||||||
|
animeCustomLists: List<String>?,
|
||||||
|
mangaCustomLists: List<String>?
|
||||||
|
): Boolean {
|
||||||
|
val query = """
|
||||||
|
mutation (${"$"}animeListOptions: MediaListOptionsInput, ${"$"}mangaListOptions: MediaListOptionsInput) {
|
||||||
|
UpdateUser(animeListOptions: ${"$"}animeListOptions, mangaListOptions: ${"$"}mangaListOptions) {
|
||||||
|
mediaListOptions {
|
||||||
|
animeList {
|
||||||
|
customLists
|
||||||
|
}
|
||||||
|
mangaList {
|
||||||
|
customLists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val variables = """
|
||||||
|
{
|
||||||
|
${animeCustomLists?.let { """"animeListOptions": {"customLists": ${Gson().toJson(it)}}""" } ?: ""}
|
||||||
|
${if (animeCustomLists != null && mangaCustomLists != null) "," else ""}
|
||||||
|
${mangaCustomLists?.let { """"mangaListOptions": {"customLists": ${Gson().toJson(it)}}""" } ?: ""}
|
||||||
|
}
|
||||||
|
""".trimIndent().replace("\n", "").replace(""" """, "").replace(",}", "}")
|
||||||
|
|
||||||
|
val result = executeQuery<JsonObject>(query, variables)
|
||||||
|
return result?.get("errors") == null
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun editList(
|
suspend fun editList(
|
||||||
mediaID: Int,
|
mediaID: Int,
|
||||||
progress: Int? = null,
|
progress: Int? = null,
|
||||||
@@ -46,14 +187,45 @@ class AnilistMutations {
|
|||||||
completedAt: FuzzyDate? = null,
|
completedAt: FuzzyDate? = null,
|
||||||
customList: List<String>? = null
|
customList: List<String>? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val query = """
|
val query = """
|
||||||
mutation ( ${"$"}mediaID: Int, ${"$"}progress: Int,${"$"}private:Boolean,${"$"}repeat: Int, ${"$"}notes: String, ${"$"}customLists: [String], ${"$"}scoreRaw:Int, ${"$"}status:MediaListStatus, ${"$"}start:FuzzyDateInput${if (startedAt != null) "=" + startedAt.toVariableString() else ""}, ${"$"}completed:FuzzyDateInput${if (completedAt != null) "=" + completedAt.toVariableString() else ""} ) {
|
mutation (
|
||||||
SaveMediaListEntry( mediaId: ${"$"}mediaID, progress: ${"$"}progress, repeat: ${"$"}repeat, notes: ${"$"}notes, private: ${"$"}private, scoreRaw: ${"$"}scoreRaw, status:${"$"}status, startedAt: ${"$"}start, completedAt: ${"$"}completed , customLists: ${"$"}customLists ) {
|
${"$"}mediaID: Int,
|
||||||
score(format:POINT_10_DECIMAL) startedAt{year month day} completedAt{year month day}
|
${"$"}progress: Int,
|
||||||
|
${"$"}private: Boolean,
|
||||||
|
${"$"}repeat: Int,
|
||||||
|
${"$"}notes: String,
|
||||||
|
${"$"}customLists: [String],
|
||||||
|
${"$"}scoreRaw: Int,
|
||||||
|
${"$"}status: MediaListStatus,
|
||||||
|
${"$"}start: FuzzyDateInput${if (startedAt != null) "=" + startedAt.toVariableString() else ""},
|
||||||
|
${"$"}completed: FuzzyDateInput${if (completedAt != null) "=" + completedAt.toVariableString() else ""}
|
||||||
|
) {
|
||||||
|
SaveMediaListEntry(
|
||||||
|
mediaId: ${"$"}mediaID,
|
||||||
|
progress: ${"$"}progress,
|
||||||
|
repeat: ${"$"}repeat,
|
||||||
|
notes: ${"$"}notes,
|
||||||
|
private: ${"$"}private,
|
||||||
|
scoreRaw: ${"$"}scoreRaw,
|
||||||
|
status: ${"$"}status,
|
||||||
|
startedAt: ${"$"}start,
|
||||||
|
completedAt: ${"$"}completed,
|
||||||
|
customLists: ${"$"}customLists
|
||||||
|
) {
|
||||||
|
score(format: POINT_10_DECIMAL)
|
||||||
|
startedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
completedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""".replace("\n", "").replace(""" """, "")
|
""".trimIndent()
|
||||||
|
|
||||||
val variables = """{"mediaID":$mediaID
|
val variables = """{"mediaID":$mediaID
|
||||||
${if (private != null) ""","private":$private""" else ""}
|
${if (private != null) ""","private":$private""" else ""}
|
||||||
@@ -69,43 +241,179 @@ class AnilistMutations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteList(listId: Int) {
|
suspend fun deleteList(listId: Int) {
|
||||||
val query = "mutation(${"$"}id:Int){DeleteMediaListEntry(id:${"$"}id){deleted}}"
|
val query = """
|
||||||
|
mutation(${"$"}id: Int) {
|
||||||
|
DeleteMediaListEntry(id: ${"$"}id) {
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val variables = """{"id":"$listId"}"""
|
val variables = """{"id":"$listId"}"""
|
||||||
executeQuery<JsonObject>(query, variables)
|
executeQuery<JsonObject>(query, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun rateReview(reviewId: Int, rating: String): Query.RateReviewResponse? {
|
suspend fun rateReview(reviewId: Int, rating: String): Query.RateReviewResponse? {
|
||||||
val query = "mutation{RateReview(reviewId:$reviewId,rating:$rating){id mediaId mediaType summary body(asHtml:true)rating ratingAmount userRating score private siteUrl createdAt updatedAt user{id name bannerImage avatar{medium large}}}}"
|
val query = """
|
||||||
|
mutation {
|
||||||
|
RateReview(reviewId: $reviewId, rating: $rating) {
|
||||||
|
id
|
||||||
|
mediaId
|
||||||
|
mediaType
|
||||||
|
summary
|
||||||
|
body(asHtml: true)
|
||||||
|
rating
|
||||||
|
ratingAmount
|
||||||
|
userRating
|
||||||
|
score
|
||||||
|
private
|
||||||
|
siteUrl
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bannerImage
|
||||||
|
avatar {
|
||||||
|
medium
|
||||||
|
large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
return executeQuery<Query.RateReviewResponse>(query)
|
return executeQuery<Query.RateReviewResponse>(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun postActivity(text:String): String {
|
suspend fun toggleFollow(id: Int): Query.ToggleFollow? {
|
||||||
|
return executeQuery<Query.ToggleFollow>(
|
||||||
|
"""
|
||||||
|
mutation {
|
||||||
|
ToggleFollow(userId: $id) {
|
||||||
|
id
|
||||||
|
isFollowing
|
||||||
|
isFollower
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun toggleLike(id: Int, type: String): ToggleLike? {
|
||||||
|
return executeQuery<ToggleLike>(
|
||||||
|
"""
|
||||||
|
mutation Like {
|
||||||
|
ToggleLikeV2(id: $id, type: $type) {
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun postActivity(text: String, edit: Int? = null): String {
|
||||||
val encodedText = text.stringSanitizer()
|
val encodedText = text.stringSanitizer()
|
||||||
val query = "mutation{SaveTextActivity(text:$encodedText){siteUrl}}"
|
val query = """
|
||||||
|
mutation {
|
||||||
|
SaveTextActivity(${if (edit != null) "id: $edit," else ""} text: $encodedText) {
|
||||||
|
siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val result = executeQuery<JsonObject>(query)
|
val result = executeQuery<JsonObject>(query)
|
||||||
val errors = result?.get("errors")
|
val errors = result?.get("errors")
|
||||||
return errors?.toString()
|
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
|
||||||
?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success")
|
?: "Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun postMessage(
|
||||||
|
userId: Int,
|
||||||
|
text: String,
|
||||||
|
edit: Int? = null,
|
||||||
|
isPrivate: Boolean = false
|
||||||
|
): String {
|
||||||
|
val encodedText = text.replace("", "").stringSanitizer()
|
||||||
|
val query = """
|
||||||
|
mutation {
|
||||||
|
SaveMessageActivity(
|
||||||
|
${if (edit != null) "id: $edit," else ""}
|
||||||
|
recipientId: $userId,
|
||||||
|
message: $encodedText,
|
||||||
|
private: $isPrivate
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val result = executeQuery<JsonObject>(query)
|
||||||
|
val errors = result?.get("errors")
|
||||||
|
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
|
||||||
|
?: "Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun postReply(activityId: Int, text: String, edit: Int? = null): String {
|
||||||
|
val encodedText = text.stringSanitizer()
|
||||||
|
val query = """
|
||||||
|
mutation {
|
||||||
|
SaveActivityReply(
|
||||||
|
${if (edit != null) "id: $edit," else ""}
|
||||||
|
activityId: $activityId,
|
||||||
|
text: $encodedText
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val result = executeQuery<JsonObject>(query)
|
||||||
|
val errors = result?.get("errors")
|
||||||
|
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
|
||||||
|
?: "Success")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun postReview(summary: String, body: String, mediaId: Int, score: Int): String {
|
suspend fun postReview(summary: String, body: String, mediaId: Int, score: Int): String {
|
||||||
val encodedSummary = summary.stringSanitizer()
|
val encodedSummary = summary.stringSanitizer()
|
||||||
val encodedBody = body.stringSanitizer()
|
val encodedBody = body.stringSanitizer()
|
||||||
val query = "mutation{SaveReview(mediaId:$mediaId,summary:$encodedSummary,body:$encodedBody,score:$score){siteUrl}}"
|
val query = """
|
||||||
|
mutation {
|
||||||
|
SaveReview(
|
||||||
|
mediaId: $mediaId,
|
||||||
|
summary: $encodedSummary,
|
||||||
|
body: $encodedBody,
|
||||||
|
score: $score
|
||||||
|
) {
|
||||||
|
siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val result = executeQuery<JsonObject>(query)
|
val result = executeQuery<JsonObject>(query)
|
||||||
val errors = result?.get("errors")
|
val errors = result?.get("errors")
|
||||||
return errors?.toString()
|
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
|
||||||
?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success")
|
?: "Success")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun postReply(activityId: Int, text: String): String {
|
suspend fun deleteActivityReply(activityId: Int): Boolean {
|
||||||
val encodedText = text.stringSanitizer()
|
val query = """
|
||||||
val query = "mutation{SaveActivityReply(activityId:$activityId,text:$encodedText){id}}"
|
mutation {
|
||||||
|
DeleteActivityReply(id: $activityId) {
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
val result = executeQuery<JsonObject>(query)
|
val result = executeQuery<JsonObject>(query)
|
||||||
val errors = result?.get("errors")
|
val errors = result?.get("errors")
|
||||||
return errors?.toString()
|
return errors == null
|
||||||
?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success")
|
}
|
||||||
|
|
||||||
|
suspend fun deleteActivity(activityId: Int): Boolean {
|
||||||
|
val query = """
|
||||||
|
mutation {
|
||||||
|
DeleteActivity(id: $activityId) {
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val result = executeQuery<JsonObject>(query)
|
||||||
|
val errors = result?.get("errors")
|
||||||
|
return errors == null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.stringSanitizer(): String {
|
private fun String.stringSanitizer(): String {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
suspend fun getUserId(context: Context, block: () -> Unit) {
|
suspend fun getUserId(context: Context, block: () -> Unit) {
|
||||||
if (!Anilist.initialized) {
|
if (!Anilist.initialized && PrefManager.getVal<String>(PrefName.AnilistToken) != "") {
|
||||||
if (Anilist.query.getUserData()) {
|
if (Anilist.query.getUserData()) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
if (MAL.token != null && !MAL.query.getUserData())
|
if (MAL.token != null && !MAL.query.getUserData())
|
||||||
@@ -81,24 +81,26 @@ class AnilistHomeViewModel : ViewModel() {
|
|||||||
MutableLiveData<ArrayList<User>>(null)
|
MutableLiveData<ArrayList<User>>(null)
|
||||||
|
|
||||||
fun getUserStatus(): LiveData<ArrayList<User>> = userStatus
|
fun getUserStatus(): LiveData<ArrayList<User>> = userStatus
|
||||||
|
suspend fun initUserStatus() {
|
||||||
|
val res = Anilist.query.getUserStatus()
|
||||||
|
res?.let { userStatus.postValue(it) }
|
||||||
|
}
|
||||||
|
|
||||||
private val hidden: MutableLiveData<ArrayList<Media>> =
|
private val hidden: MutableLiveData<ArrayList<Media>> =
|
||||||
MutableLiveData<ArrayList<Media>>(null)
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
fun getHidden(): LiveData<ArrayList<Media>> = hidden
|
fun getHidden(): LiveData<ArrayList<Media>> = hidden
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
suspend fun initHomePage() {
|
suspend fun initHomePage() {
|
||||||
val res = Anilist.query.initHomePage()
|
val res = Anilist.query.initHomePage()
|
||||||
res["currentAnime"]?.let { animeContinue.postValue(it as ArrayList<Media>?) }
|
res["currentAnime"]?.let { animeContinue.postValue(it) }
|
||||||
res["favoriteAnime"]?.let { animeFav.postValue(it as ArrayList<Media>?) }
|
res["favoriteAnime"]?.let { animeFav.postValue(it) }
|
||||||
res["plannedAnime"]?.let { animePlanned.postValue(it as ArrayList<Media>?) }
|
res["currentAnimePlanned"]?.let { animePlanned.postValue(it) }
|
||||||
res["currentManga"]?.let { mangaContinue.postValue(it as ArrayList<Media>?) }
|
res["currentManga"]?.let { mangaContinue.postValue(it) }
|
||||||
res["favoriteManga"]?.let { mangaFav.postValue(it as ArrayList<Media>?) }
|
res["favoriteManga"]?.let { mangaFav.postValue(it) }
|
||||||
res["plannedManga"]?.let { mangaPlanned.postValue(it as ArrayList<Media>?) }
|
res["currentMangaPlanned"]?.let { mangaPlanned.postValue(it) }
|
||||||
res["recommendations"]?.let { recommendation.postValue(it as ArrayList<Media>?) }
|
res["recommendations"]?.let { recommendation.postValue(it) }
|
||||||
res["hidden"]?.let { hidden.postValue(it as ArrayList<Media>?) }
|
res["hidden"]?.let { hidden.postValue(it) }
|
||||||
res["status"]?.let { userStatus.postValue(it as ArrayList<User>?) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadMain(context: FragmentActivity) {
|
suspend fun loadMain(context: FragmentActivity) {
|
||||||
@@ -126,7 +128,7 @@ class AnilistHomeViewModel : ViewModel() {
|
|||||||
class AnilistAnimeViewModel : ViewModel() {
|
class AnilistAnimeViewModel : ViewModel() {
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val type = "ANIME"
|
private val type = "ANIME"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> =
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
MutableLiveData<MutableList<Media>>(null)
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
@@ -135,7 +137,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||||||
suspend fun loadTrending(i: Int) {
|
suspend fun loadTrending(i: Int) {
|
||||||
val (season, year) = Anilist.currentSeasons[i]
|
val (season, year) = Anilist.currentSeasons[i]
|
||||||
trending.postValue(
|
trending.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
perPage = 12,
|
perPage = 12,
|
||||||
sort = Anilist.sortBy[2],
|
sort = Anilist.sortBy[2],
|
||||||
@@ -148,9 +150,9 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val animePopular = MutableLiveData<SearchResults?>(null)
|
private val animePopular = MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
|
|
||||||
fun getPopular(): LiveData<SearchResults?> = animePopular
|
fun getPopular(): LiveData<AniMangaSearchResults?> = animePopular
|
||||||
suspend fun loadPopular(
|
suspend fun loadPopular(
|
||||||
type: String,
|
type: String,
|
||||||
searchVal: String? = null,
|
searchVal: String? = null,
|
||||||
@@ -159,7 +161,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||||||
onList: Boolean = true,
|
onList: Boolean = true,
|
||||||
) {
|
) {
|
||||||
animePopular.postValue(
|
animePopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
search = searchVal,
|
search = searchVal,
|
||||||
onList = if (onList) null else false,
|
onList = if (onList) null else false,
|
||||||
@@ -171,8 +173,8 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = animePopular.postValue(
|
suspend fun loadNextPage(r: AniMangaSearchResults) = animePopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
@@ -222,7 +224,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||||||
class AnilistMangaViewModel : ViewModel() {
|
class AnilistMangaViewModel : ViewModel() {
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val type = "MANGA"
|
private val type = "MANGA"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> =
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
MutableLiveData<MutableList<Media>>(null)
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
@@ -230,7 +232,7 @@ class AnilistMangaViewModel : ViewModel() {
|
|||||||
fun getTrending(): LiveData<MutableList<Media>> = trending
|
fun getTrending(): LiveData<MutableList<Media>> = trending
|
||||||
suspend fun loadTrending() =
|
suspend fun loadTrending() =
|
||||||
trending.postValue(
|
trending.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
perPage = 10,
|
perPage = 10,
|
||||||
sort = Anilist.sortBy[2],
|
sort = Anilist.sortBy[2],
|
||||||
@@ -240,8 +242,8 @@ class AnilistMangaViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
private val mangaPopular = MutableLiveData<SearchResults?>(null)
|
private val mangaPopular = MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
fun getPopular(): LiveData<SearchResults?> = mangaPopular
|
fun getPopular(): LiveData<AniMangaSearchResults?> = mangaPopular
|
||||||
suspend fun loadPopular(
|
suspend fun loadPopular(
|
||||||
type: String,
|
type: String,
|
||||||
searchVal: String? = null,
|
searchVal: String? = null,
|
||||||
@@ -250,7 +252,7 @@ class AnilistMangaViewModel : ViewModel() {
|
|||||||
onList: Boolean = true,
|
onList: Boolean = true,
|
||||||
) {
|
) {
|
||||||
mangaPopular.postValue(
|
mangaPopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
search = searchVal,
|
search = searchVal,
|
||||||
onList = if (onList) null else false,
|
onList = if (onList) null else false,
|
||||||
@@ -262,8 +264,8 @@ class AnilistMangaViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = mangaPopular.postValue(
|
suspend fun loadNextPage(r: AniMangaSearchResults) = mangaPopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
@@ -323,14 +325,131 @@ class AnilistMangaViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AnilistSearch : ViewModel() {
|
class AnilistSearch : ViewModel() {
|
||||||
|
|
||||||
|
enum class SearchType {
|
||||||
|
ANIME, MANGA, CHARACTER, STAFF, STUDIO, USER;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun SearchType.toAnilistString(): String {
|
||||||
|
return when (this) {
|
||||||
|
ANIME -> "ANIME"
|
||||||
|
MANGA -> "MANGA"
|
||||||
|
CHARACTER -> "CHARACTER"
|
||||||
|
STAFF -> "STAFF"
|
||||||
|
STUDIO -> "STUDIO"
|
||||||
|
USER -> "USER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromString(string: String): SearchType {
|
||||||
|
return when (string.uppercase()) {
|
||||||
|
"ANIME" -> ANIME
|
||||||
|
"MANGA" -> MANGA
|
||||||
|
"CHARACTER" -> CHARACTER
|
||||||
|
"STAFF" -> STAFF
|
||||||
|
"STUDIO" -> STUDIO
|
||||||
|
"USER" -> USER
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val result: MutableLiveData<SearchResults?> = MutableLiveData<SearchResults?>(null)
|
private val aniMangaResult: MutableLiveData<AniMangaSearchResults?> =
|
||||||
|
MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
|
|
||||||
fun getSearch(): LiveData<SearchResults?> = result
|
lateinit var characterSearchResults: CharacterSearchResults
|
||||||
suspend fun loadSearch(r: SearchResults) = result.postValue(
|
private val characterResult: MutableLiveData<CharacterSearchResults?> =
|
||||||
Anilist.query.search(
|
MutableLiveData<CharacterSearchResults?>(null)
|
||||||
|
|
||||||
|
lateinit var studioSearchResults: StudioSearchResults
|
||||||
|
private val studioResult: MutableLiveData<StudioSearchResults?> =
|
||||||
|
MutableLiveData<StudioSearchResults?>(null)
|
||||||
|
|
||||||
|
lateinit var staffSearchResults: StaffSearchResults
|
||||||
|
private val staffResult: MutableLiveData<StaffSearchResults?> =
|
||||||
|
MutableLiveData<StaffSearchResults?>(null)
|
||||||
|
|
||||||
|
lateinit var userSearchResults: UserSearchResults
|
||||||
|
private val userResult: MutableLiveData<UserSearchResults?> =
|
||||||
|
MutableLiveData<UserSearchResults?>(null)
|
||||||
|
|
||||||
|
fun <T> getSearch(type: SearchType): MutableLiveData<T?> {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaResult as MutableLiveData<T?>
|
||||||
|
SearchType.CHARACTER -> characterResult as MutableLiveData<T?>
|
||||||
|
SearchType.STUDIO -> studioResult as MutableLiveData<T?>
|
||||||
|
SearchType.STAFF -> staffResult as MutableLiveData<T?>
|
||||||
|
SearchType.USER -> userResult as MutableLiveData<T?>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadSearch(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> loadAniMangaSearch(aniMangaSearchResults)
|
||||||
|
SearchType.CHARACTER -> loadCharacterSearch(characterSearchResults)
|
||||||
|
SearchType.STUDIO -> loadStudiosSearch(studioSearchResults)
|
||||||
|
SearchType.STAFF -> loadStaffSearch(staffSearchResults)
|
||||||
|
SearchType.USER -> loadUserSearch(userSearchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadNextPage(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> loadNextAniMangaPage(aniMangaSearchResults)
|
||||||
|
SearchType.CHARACTER -> loadNextCharacterPage(characterSearchResults)
|
||||||
|
SearchType.STUDIO -> loadNextStudiosPage(studioSearchResults)
|
||||||
|
SearchType.STAFF -> loadNextStaffPage(staffSearchResults)
|
||||||
|
SearchType.USER -> loadNextUserPage(userSearchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasNextPage(type: SearchType): Boolean {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.hasNextPage
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.hasNextPage
|
||||||
|
SearchType.STUDIO -> studioSearchResults.hasNextPage
|
||||||
|
SearchType.STAFF -> staffSearchResults.hasNextPage
|
||||||
|
SearchType.USER -> userSearchResults.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resultsIsNotEmpty(type: SearchType): Boolean {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.USER -> userSearchResults.results.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun size(type: SearchType): Int {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.size
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.size
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.size
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.size
|
||||||
|
SearchType.USER -> userSearchResults.results.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearResults(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.clear()
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.clear()
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.clear()
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.clear()
|
||||||
|
SearchType.USER -> userSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadAniMangaSearch(r: AniMangaSearchResults) = aniMangaResult.postValue(
|
||||||
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page,
|
r.page,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
@@ -352,8 +471,36 @@ class AnilistSearch : ViewModel() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = result.postValue(
|
private suspend fun loadCharacterSearch(r: CharacterSearchResults) = characterResult.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchCharacters(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadStudiosSearch(r: StudioSearchResults) = studioResult.postValue(
|
||||||
|
Anilist.query.searchStudios(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadStaffSearch(r: StaffSearchResults) = staffResult.postValue(
|
||||||
|
Anilist.query.searchStaff(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadUserSearch(r: UserSearchResults) = userResult.postValue(
|
||||||
|
Anilist.query.searchUsers(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextAniMangaPage(r: AniMangaSearchResults) = aniMangaResult.postValue(
|
||||||
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
@@ -374,6 +521,35 @@ class AnilistSearch : ViewModel() {
|
|||||||
r.season
|
r.season
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextCharacterPage(r: CharacterSearchResults) =
|
||||||
|
characterResult.postValue(
|
||||||
|
Anilist.query.searchCharacters(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextStudiosPage(r: StudioSearchResults) = studioResult.postValue(
|
||||||
|
Anilist.query.searchStudios(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextStaffPage(r: StaffSearchResults) = staffResult.postValue(
|
||||||
|
Anilist.query.searchStaff(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextUserPage(r: UserSearchResults) = userResult.postValue(
|
||||||
|
Anilist.query.searchUsers(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenresViewModel : ViewModel() {
|
class GenresViewModel : ViewModel() {
|
||||||
|
|||||||
@@ -0,0 +1,431 @@
|
|||||||
|
package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
|
val standardPageInformation = """
|
||||||
|
pageInfo {
|
||||||
|
total
|
||||||
|
perPage
|
||||||
|
currentPage
|
||||||
|
lastPage
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun String.prepare() = this.trimIndent().replace("\n", " ").replace(""" """, "")
|
||||||
|
|
||||||
|
fun characterInformation(includeMediaInfo: Boolean) = """
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
age
|
||||||
|
gender
|
||||||
|
description
|
||||||
|
dateOfBirth {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
${
|
||||||
|
if (includeMediaInfo) """
|
||||||
|
media(page: 0,sort:[POPULARITY_DESC,SCORE_DESC]) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
voiceActors {
|
||||||
|
id,
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
languageV2,
|
||||||
|
image {
|
||||||
|
medium,
|
||||||
|
large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
characterRole
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}""".prepare() else ""
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun studioInformation(page: Int, perPage: Int) = """
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isFavourite
|
||||||
|
favourites
|
||||||
|
media(page: $page, sort:START_DATE_DESC, perPage: $perPage) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
startDate{ year }
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun staffInformation(page: Int, perPage: Int) = """
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
dateOfBirth {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
dateOfDeath {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
age
|
||||||
|
yearsActive
|
||||||
|
homeTown
|
||||||
|
staffMedia(page: $page,sort:START_DATE_DESC, perPage: $perPage) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
staffRole
|
||||||
|
id
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
startDate{ year }
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun userInformation() = """
|
||||||
|
id
|
||||||
|
name
|
||||||
|
about(asHtml: true)
|
||||||
|
avatar {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
bannerImage
|
||||||
|
isFollowing
|
||||||
|
isFollower
|
||||||
|
isBlocked
|
||||||
|
siteUrl
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun aniMangaSearch(perPage: Int?) = """
|
||||||
|
query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: Boolean = false, ${"$"}search: String, ${"$"}format: [MediaFormat], ${"$"}status: MediaStatus, ${"$"}countryOfOrigin: CountryCode, ${"$"}source: MediaSource, ${"$"}season: MediaSeason, ${"$"}seasonYear: Int, ${"$"}year: String, ${"$"}onList: Boolean, ${"$"}yearLesser: FuzzyDateInt, ${"$"}yearGreater: FuzzyDateInt, ${"$"}episodeLesser: Int, ${"$"}episodeGreater: Int, ${"$"}durationLesser: Int, ${"$"}durationGreater: Int, ${"$"}chapterLesser: Int, ${"$"}chapterGreater: Int, ${"$"}volumeLesser: Int, ${"$"}volumeGreater: Int, ${"$"}licensedBy: [String], ${"$"}isLicensed: Boolean, ${"$"}genres: [String], ${"$"}excludedGenres: [String], ${"$"}tags: [String], ${"$"}excludedTags: [String], ${"$"}minimumTagRank: Int, ${"$"}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC, START_DATE_DESC]) {
|
||||||
|
Page(page: ${"$"}page, perPage: ${perPage ?: 50}) {
|
||||||
|
$standardPageInformation
|
||||||
|
media(id: ${"$"}id, type: ${"$"}type, season: ${"$"}season, format_in: ${"$"}format, status: ${"$"}status, countryOfOrigin: ${"$"}countryOfOrigin, source: ${"$"}source, search: ${"$"}search, onList: ${"$"}onList, seasonYear: ${"$"}seasonYear, startDate_like: ${"$"}year, startDate_lesser: ${"$"}yearLesser, startDate_greater: ${"$"}yearGreater, episodes_lesser: ${"$"}episodeLesser, episodes_greater: ${"$"}episodeGreater, duration_lesser: ${"$"}durationLesser, duration_greater: ${"$"}durationGreater, chapters_lesser: ${"$"}chapterLesser, chapters_greater: ${"$"}chapterGreater, volumes_lesser: ${"$"}volumeLesser, volumes_greater: ${"$"}volumeGreater, licensedBy_in: ${"$"}licensedBy, isLicensed: ${"$"}isLicensed, genre_in: ${"$"}genres, genre_not_in: ${"$"}excludedGenres, tag_in: ${"$"}tags, tag_not_in: ${"$"}excludedTags, minimumTagRank: ${"$"}minimumTagRank, sort: ${"$"}sort, isAdult: ${"$"}isAdult) {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun standardMediaInformation() = """
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
siteUrl
|
||||||
|
isAdult
|
||||||
|
status(version: 2)
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode {
|
||||||
|
episode
|
||||||
|
airingAt
|
||||||
|
}
|
||||||
|
type
|
||||||
|
genres
|
||||||
|
meanScore
|
||||||
|
popularity
|
||||||
|
favourites
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage {
|
||||||
|
large
|
||||||
|
extraLarge
|
||||||
|
}
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun fullMediaInformation(id: Int) = """
|
||||||
|
{
|
||||||
|
Media(id: $id) {
|
||||||
|
streamingEpisodes {
|
||||||
|
title
|
||||||
|
thumbnail
|
||||||
|
url
|
||||||
|
site
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
score(format: POINT_100)
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
notes
|
||||||
|
repeat
|
||||||
|
customLists
|
||||||
|
updatedAt
|
||||||
|
startedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
completedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reviews(perPage: 3, sort: SCORE_DESC) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
mediaId
|
||||||
|
mediaType
|
||||||
|
summary
|
||||||
|
body(asHtml: true)
|
||||||
|
rating
|
||||||
|
ratingAmount
|
||||||
|
userRating
|
||||||
|
score
|
||||||
|
private
|
||||||
|
siteUrl
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bannerImage
|
||||||
|
avatar {
|
||||||
|
medium
|
||||||
|
large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${standardMediaInformation()}
|
||||||
|
source
|
||||||
|
duration
|
||||||
|
season
|
||||||
|
seasonYear
|
||||||
|
startDate {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
endDate {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
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
|
||||||
|
voiceActors {
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
languageV2
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
image {
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
isFavourite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
relationType(version: 2)
|
||||||
|
node {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
staffPreview: staff(perPage: 8, sort: [RELEVANCE, ID]) {
|
||||||
|
edges {
|
||||||
|
role
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recommendations(sort: RATING_DESC) {
|
||||||
|
nodes {
|
||||||
|
mediaRecommendation {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
externalLinks {
|
||||||
|
url
|
||||||
|
site
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page(page: 1) {
|
||||||
|
$standardPageInformation
|
||||||
|
mediaList(isFollowing: true, sort: [STATUS], mediaId: $id) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
score(format: POINT_100)
|
||||||
|
progress
|
||||||
|
progressVolumes
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
avatar {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
""".prepare()
|
||||||
@@ -163,13 +163,9 @@ class Query {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Data(
|
data class Data(
|
||||||
@SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("recentUpdates2") val recentUpdates2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("trendingMovies2") val trendingMovies2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,15 +177,10 @@ class Query {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Data(
|
data class Data(
|
||||||
@SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("trendingManga2") val trendingManga2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("trendingManhwa2") val trendingManhwa2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("trendingNovel2") val trendingNovel2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
|
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
|
||||||
@SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ data class FeedResponse(
|
|||||||
val page: ActivityPage
|
val page: ActivityPage
|
||||||
) : java.io.Serializable
|
) : java.io.Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ActivityPage(
|
data class ActivityPage(
|
||||||
@SerialName("activities")
|
@SerialName("activities")
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ data class Media(
|
|||||||
@SerialName("externalLinks") var externalLinks: List<MediaExternalLink>?,
|
@SerialName("externalLinks") var externalLinks: List<MediaExternalLink>?,
|
||||||
|
|
||||||
// Data and links to legal streaming episodes on external sites
|
// Data and links to legal streaming episodes on external sites
|
||||||
// @SerialName("streamingEpisodes") var streamingEpisodes: List<MediaStreamingEpisode>?,
|
@SerialName("streamingEpisodes") var streamingEpisodes: List<MediaStreamingEpisode>?,
|
||||||
|
|
||||||
// The ranking of the media in a particular time span and format compared to other media
|
// The ranking of the media in a particular time span and format compared to other media
|
||||||
// @SerialName("rankings") var rankings: List<MediaRank>?,
|
// @SerialName("rankings") var rankings: List<MediaRank>?,
|
||||||
@@ -189,7 +189,7 @@ data class MediaTitle(
|
|||||||
|
|
||||||
// The currently authenticated users preferred title language. Default romaji for non-authenticated
|
// The currently authenticated users preferred title language. Default romaji for non-authenticated
|
||||||
@SerialName("userPreferred") var userPreferred: String,
|
@SerialName("userPreferred") var userPreferred: String,
|
||||||
): java.io.Serializable
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class MediaType {
|
enum class MediaType {
|
||||||
@@ -240,6 +240,21 @@ data class AiringSchedule(
|
|||||||
@SerialName("media") var media: Media?,
|
@SerialName("media") var media: Media?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MediaStreamingEpisode(
|
||||||
|
// The title of the episode
|
||||||
|
@SerialName("title") var title: String?,
|
||||||
|
|
||||||
|
// The thumbnail image of the episode
|
||||||
|
@SerialName("thumbnail") var thumbnail: String?,
|
||||||
|
|
||||||
|
// The url of the episode
|
||||||
|
@SerialName("url") var url: String?,
|
||||||
|
|
||||||
|
// The site location of the streaming episode
|
||||||
|
@SerialName("site") var site: String?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MediaCoverImage(
|
data class MediaCoverImage(
|
||||||
// The cover image url of the media at its largest size. If this size isn't available, large will be provided instead.
|
// The cover image url of the media at its largest size. If this size isn't available, large will be provided instead.
|
||||||
@@ -433,7 +448,7 @@ data class MediaEdge(
|
|||||||
@SerialName("staffRole") var staffRole: String?,
|
@SerialName("staffRole") var staffRole: String?,
|
||||||
|
|
||||||
// The voice actors of the character
|
// The voice actors of the character
|
||||||
// @SerialName("voiceActors") var voiceActors: List<Staff>?,
|
@SerialName("voiceActors") var voiceActors: List<Staff>?,
|
||||||
|
|
||||||
// The voice actors of the character with role date
|
// The voice actors of the character with role date
|
||||||
// @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?,
|
// @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?,
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ data class User(
|
|||||||
// The user's previously used names.
|
// The user's previously used names.
|
||||||
// @SerialName("previousNames") var previousNames: List<UserPreviousName>?,
|
// @SerialName("previousNames") var previousNames: List<UserPreviousName>?,
|
||||||
|
|
||||||
): java.io.Serializable
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserOptions(
|
data class UserOptions(
|
||||||
// The language the user wants to see media titles in
|
// The language the user wants to see media titles in
|
||||||
// @SerialName("titleLanguage") var titleLanguage: UserTitleLanguage?,
|
@SerialName("titleLanguage") var titleLanguage: UserTitleLanguage?,
|
||||||
|
|
||||||
// Whether the user has enabled viewing of 18+ content
|
// Whether the user has enabled viewing of 18+ content
|
||||||
@SerialName("displayAdultContent") var displayAdultContent: Boolean?,
|
@SerialName("displayAdultContent") var displayAdultContent: Boolean?,
|
||||||
@@ -88,17 +88,17 @@ data class UserOptions(
|
|||||||
// // Notification options
|
// // Notification options
|
||||||
// // @SerialName("notificationOptions") var notificationOptions: List<NotificationOption>?,
|
// // @SerialName("notificationOptions") var notificationOptions: List<NotificationOption>?,
|
||||||
//
|
//
|
||||||
// // The user's timezone offset (Auth user only)
|
// The user's timezone offset (Auth user only)
|
||||||
// @SerialName("timezone") var timezone: String?,
|
@SerialName("timezone") var timezone: String?,
|
||||||
//
|
//
|
||||||
// // Minutes between activity for them to be merged together. 0 is Never, Above 2 weeks (20160 mins) is Always.
|
// Minutes between activity for them to be merged together. 0 is Never, Above 2 weeks (20160 mins) is Always.
|
||||||
// @SerialName("activityMergeTime") var activityMergeTime: Int?,
|
@SerialName("activityMergeTime") var activityMergeTime: Int?,
|
||||||
//
|
//
|
||||||
// // The language the user wants to see staff and character names in
|
// The language the user wants to see staff and character names in
|
||||||
// // @SerialName("staffNameLanguage") var staffNameLanguage: UserStaffNameLanguage?,
|
@SerialName("staffNameLanguage") var staffNameLanguage: UserStaffNameLanguage?,
|
||||||
//
|
//
|
||||||
// // Whether the user only allow messages from users they follow
|
// Whether the user only allow messages from users they follow
|
||||||
// @SerialName("restrictMessagesToFollowing") var restrictMessagesToFollowing: Boolean?,
|
@SerialName("restrictMessagesToFollowing") var restrictMessagesToFollowing: Boolean?,
|
||||||
|
|
||||||
// The list activity types the user has disabled from being created from list updates
|
// The list activity types the user has disabled from being created from list updates
|
||||||
// @SerialName("disabledListActivity") var disabledListActivity: List<ListActivityOption>?,
|
// @SerialName("disabledListActivity") var disabledListActivity: List<ListActivityOption>?,
|
||||||
@@ -119,6 +119,48 @@ data class UserStatisticTypes(
|
|||||||
@SerialName("manga") var manga: UserStatistics?
|
@SerialName("manga") var manga: UserStatistics?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class UserTitleLanguage {
|
||||||
|
@SerialName("ENGLISH")
|
||||||
|
ENGLISH,
|
||||||
|
|
||||||
|
@SerialName("ROMAJI")
|
||||||
|
ROMAJI,
|
||||||
|
|
||||||
|
@SerialName("NATIVE")
|
||||||
|
NATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class UserStaffNameLanguage {
|
||||||
|
@SerialName("ROMAJI_WESTERN")
|
||||||
|
ROMAJI_WESTERN,
|
||||||
|
|
||||||
|
@SerialName("ROMAJI")
|
||||||
|
ROMAJI,
|
||||||
|
|
||||||
|
@SerialName("NATIVE")
|
||||||
|
NATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class ScoreFormat {
|
||||||
|
@SerialName("POINT_100")
|
||||||
|
POINT_100,
|
||||||
|
|
||||||
|
@SerialName("POINT_10_DECIMAL")
|
||||||
|
POINT_10_DECIMAL,
|
||||||
|
|
||||||
|
@SerialName("POINT_10")
|
||||||
|
POINT_10,
|
||||||
|
|
||||||
|
@SerialName("POINT_5")
|
||||||
|
POINT_5,
|
||||||
|
|
||||||
|
@SerialName("POINT_3")
|
||||||
|
POINT_3,
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserStatistics(
|
data class UserStatistics(
|
||||||
//
|
//
|
||||||
@@ -164,7 +206,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: String?,
|
@SerialName("scoreFormat") var scoreFormat: ScoreFormat?,
|
||||||
|
|
||||||
// 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?,
|
||||||
@@ -181,8 +223,8 @@ data class MediaListTypeOptions(
|
|||||||
// The order each list should be displayed in
|
// The order each list should be displayed in
|
||||||
@SerialName("sectionOrder") var sectionOrder: List<String>?,
|
@SerialName("sectionOrder") var sectionOrder: List<String>?,
|
||||||
|
|
||||||
// If the completed sections of the list should be separated by format
|
// // If the completed sections of the list should be separated by format
|
||||||
@SerialName("splitCompletedSectionByFormat") var splitCompletedSectionByFormat: Boolean?,
|
// @SerialName("splitCompletedSectionByFormat") var splitCompletedSectionByFormat: Boolean?,
|
||||||
|
|
||||||
// The names of the user's custom lists
|
// The names of the user's custom lists
|
||||||
@SerialName("customLists") var customLists: List<String>?,
|
@SerialName("customLists") var customLists: List<String>?,
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
package ani.dantotsu.connections.bakaupdates
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import ani.dantotsu.R
|
|
||||||
import ani.dantotsu.client
|
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
|
||||||
import ani.dantotsu.tryWithSuspend
|
|
||||||
import ani.dantotsu.util.Logger
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import okio.ByteString.Companion.encode
|
|
||||||
import org.json.JSONException
|
|
||||||
import org.json.JSONObject
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
|
|
||||||
class MangaUpdates {
|
|
||||||
|
|
||||||
private val Int?.dateFormat get() = String.format("%02d", this)
|
|
||||||
|
|
||||||
private val apiUrl = "https://api.mangaupdates.com/v1/releases/search"
|
|
||||||
|
|
||||||
suspend fun search(title: String, startDate: FuzzyDate?): MangaUpdatesResponse.Results? {
|
|
||||||
return tryWithSuspend {
|
|
||||||
val query = JSONObject().apply {
|
|
||||||
try {
|
|
||||||
put("search", title.encode(Charset.forName("UTF-8")))
|
|
||||||
startDate?.let {
|
|
||||||
put(
|
|
||||||
"start_date",
|
|
||||||
"${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
put("include_metadata", true)
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val res = try {
|
|
||||||
client.post(apiUrl, json = query).parsed<MangaUpdatesResponse>()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Logger.log(e.toString())
|
|
||||||
return@tryWithSuspend null
|
|
||||||
}
|
|
||||||
coroutineScope {
|
|
||||||
res.results?.map {
|
|
||||||
async(Dispatchers.IO) {
|
|
||||||
Logger.log(it.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?.awaitAll()
|
|
||||||
res.results?.first {
|
|
||||||
it.metadata.series.lastUpdated?.timestamp != null
|
|
||||||
&& (it.metadata.series.latestChapter != null
|
|
||||||
|| (it.record.volume.isNullOrBlank() && it.record.chapter != null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getLatestChapter(context: Context, results: MangaUpdatesResponse.Results): String {
|
|
||||||
return results.metadata.series.latestChapter?.let {
|
|
||||||
context.getString(R.string.chapter_number, it)
|
|
||||||
} ?: results.record.chapter!!.substringAfterLast("-").trim().let { chapter ->
|
|
||||||
chapter.takeIf {
|
|
||||||
it.toIntOrNull() == null
|
|
||||||
} ?: context.getString(R.string.chapter_number, chapter.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MangaUpdatesResponse(
|
|
||||||
@SerialName("total_hits")
|
|
||||||
val totalHits: Int?,
|
|
||||||
@SerialName("page")
|
|
||||||
val page: Int?,
|
|
||||||
@SerialName("per_page")
|
|
||||||
val perPage: Int?,
|
|
||||||
val results: List<Results>? = null
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class Results(
|
|
||||||
val record: Record,
|
|
||||||
val metadata: MetaData
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class Record(
|
|
||||||
@SerialName("id")
|
|
||||||
val id: Int,
|
|
||||||
@SerialName("title")
|
|
||||||
val title: String,
|
|
||||||
@SerialName("volume")
|
|
||||||
val volume: String?,
|
|
||||||
@SerialName("chapter")
|
|
||||||
val chapter: String?,
|
|
||||||
@SerialName("release_date")
|
|
||||||
val releaseDate: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MetaData(
|
|
||||||
val series: Series
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class Series(
|
|
||||||
@SerialName("series_id")
|
|
||||||
val seriesId: Long?,
|
|
||||||
@SerialName("title")
|
|
||||||
val title: String?,
|
|
||||||
@SerialName("latest_chapter")
|
|
||||||
val latestChapter: Int?,
|
|
||||||
@SerialName("last_updated")
|
|
||||||
val lastUpdated: LastUpdated?
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class LastUpdated(
|
|
||||||
@SerialName("timestamp")
|
|
||||||
val timestamp: Long,
|
|
||||||
@SerialName("as_rfc3339")
|
|
||||||
val asRfc3339: String,
|
|
||||||
@SerialName("as_string")
|
|
||||||
val asString: String
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,8 +27,11 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object CommentsAPI {
|
object CommentsAPI {
|
||||||
private const val ADDRESS: String = "https://api.dantotsu.app"
|
private const val API_ADDRESS: String = "https://api.dantotsu.app"
|
||||||
|
private const val LOCAL_HOST: String = "https://127.0.0.1"
|
||||||
private var isOnline: Boolean = true
|
private var isOnline: Boolean = true
|
||||||
|
private var commentsEnabled = PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1
|
||||||
|
private val ADDRESS: String get() = if (commentsEnabled) API_ADDRESS else LOCAL_HOST
|
||||||
var authToken: String? = null
|
var authToken: String? = null
|
||||||
var userId: String? = null
|
var userId: String? = null
|
||||||
var isBanned: Boolean = false
|
var isBanned: Boolean = false
|
||||||
@@ -371,8 +374,8 @@ object CommentsAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun errorMessage(reason: String) {
|
private fun errorMessage(reason: String) {
|
||||||
Logger.log(reason)
|
if (commentsEnabled) Logger.log(reason)
|
||||||
if (isOnline) snackString(reason)
|
if (isOnline && commentsEnabled) snackString(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
@@ -408,7 +411,7 @@ object CommentsAPI {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests {
|
fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests {
|
||||||
return Requests(
|
return Requests(
|
||||||
client,
|
client,
|
||||||
headerBuilder()
|
headerBuilder()
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ object Discord {
|
|||||||
|
|
||||||
const val application_Id = "1163925779692912771"
|
const val application_Id = "1163925779692912771"
|
||||||
const val small_Image: String =
|
const val small_Image: String =
|
||||||
"mp:external/GJEe4hKzr8w56IW6ZKQz43HFVEo8pOtA_C-dJiWwxKo/https/cdn.discordapp.com/app-icons/1163925779692912771/f6b42d41dfdf0b56fcc79d4a12d2ac66.png"
|
"mp:external/9NqpMxXs4ZNQtMG42L7hqINW92GqqDxgxS9Oh0Sp880/%3Fsize%3D48%26quality%3Dlossless%26name%3DDantotsu/https/cdn.discordapp.com/emojis/1167344924874784828.gif"
|
||||||
const val small_Image_AniList: String =
|
const val small_Image_AniList: String =
|
||||||
"mp:external/rHOIjjChluqQtGyL_UHk6Z4oAqiVYlo_B7HSGPLSoUg/%3Fsize%3D128/https/cdn.discordapp.com/icons/210521487378087947/a_f54f910e2add364a3da3bb2f2fce0c72.webp"
|
"https://anilist.co/img/icons/android-chrome-512x512.png"
|
||||||
}
|
}
|
||||||
@@ -47,9 +47,9 @@ class Login : AppCompatActivity() {
|
|||||||
view.evaluateJavascript(
|
view.evaluateJavascript(
|
||||||
"""
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
const wreq = (webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken();
|
const m = []; webpackChunkdiscord_app.push([[""], {}, e => {for (let c in e.c)m.push(e.c[c])}]);
|
||||||
return wreq;
|
return m.find(n => n?.exports?.default?.getToken !== void 0)?.exports?.default?.getToken();
|
||||||
})()
|
})()
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
) { result ->
|
) { result ->
|
||||||
login(result.trim('"'))
|
login(result.trim('"'))
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
package ani.dantotsu.connections.discord
|
package ani.dantotsu.connections.discord
|
||||||
|
|
||||||
|
import ani.dantotsu.connections.discord.Discord.token
|
||||||
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.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import ani.dantotsu.client as app
|
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
||||||
|
|
||||||
private val json = Json {
|
|
||||||
encodeDefaults = true
|
|
||||||
allowStructuredMapKeys = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
PLAYING, STREAMING, LISTENING, WATCHING, COMPETING
|
PLAYING, STREAMING, LISTENING, WATCHING, COMPETING
|
||||||
}
|
}
|
||||||
@@ -27,7 +22,7 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
data class RPCData(
|
data class RPCData(
|
||||||
val applicationId: String? = null,
|
val applicationId: String,
|
||||||
val type: Type? = null,
|
val type: Type? = null,
|
||||||
val activityName: String? = null,
|
val activityName: String? = null,
|
||||||
val details: String? = null,
|
val details: String? = null,
|
||||||
@@ -40,22 +35,21 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
|||||||
val buttons: MutableList<Link> = mutableListOf()
|
val buttons: MutableList<Link> = mutableListOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class KizzyApi(val id: String)
|
|
||||||
|
|
||||||
val api = "https://kizzy-api.vercel.app/image?url="
|
|
||||||
private suspend fun String.discordUrl(): String? {
|
|
||||||
if (startsWith("mp:")) return this
|
|
||||||
val json = app.get("$api$this").parsedSafe<KizzyApi>()
|
|
||||||
return json?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun createPresence(data: RPCData): String {
|
suspend fun createPresence(data: RPCData): String {
|
||||||
val json = Json {
|
val json = Json {
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
allowStructuredMapKeys = true
|
allowStructuredMapKeys = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(10, SECONDS)
|
||||||
|
.readTimeout(10, SECONDS)
|
||||||
|
.writeTimeout(10, SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json)
|
||||||
|
suspend fun String.discordUrl() = assetApi.getDiscordUri(this)
|
||||||
|
|
||||||
return json.encodeToString(Presence.Response(
|
return json.encodeToString(Presence.Response(
|
||||||
3,
|
3,
|
||||||
Presence(
|
Presence(
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// this code was kanged from the greatest mind of this era, aka shivam brahmkshatriya
|
||||||
|
// please subscribe to my only fans here: https://github.com/brahmkshatriya
|
||||||
|
package ani.dantotsu.connections.discord
|
||||||
|
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.IOException
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class RPCExternalAsset(
|
||||||
|
applicationId: String,
|
||||||
|
private val token: String,
|
||||||
|
private val client: OkHttpClient,
|
||||||
|
private val json: Json
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ExternalAsset(
|
||||||
|
val url: String? = null,
|
||||||
|
@SerialName("external_asset_path")
|
||||||
|
val externalAssetPath: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private val api = "https://discord.com/api/v9/applications/$applicationId/external-assets"
|
||||||
|
suspend fun getDiscordUri(imageUrl: String): String? {
|
||||||
|
if (imageUrl.startsWith("mp:")) return imageUrl
|
||||||
|
val request = Request.Builder().url(api).header("Authorization", token)
|
||||||
|
.post("{\"urls\":[\"$imageUrl\"]}".toRequestBody("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
return runCatching {
|
||||||
|
val res = client.newCall(request).await()
|
||||||
|
json.decodeFromString<List<ExternalAsset>>(res.body.string())
|
||||||
|
.firstOrNull()?.externalAssetPath?.let { "mp:$it" }
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend inline fun Call.await(): Response {
|
||||||
|
return suspendCoroutine {
|
||||||
|
enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
it.resumeWithException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
it.resume(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ data class Activity(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Timestamps(
|
data class Timestamps(
|
||||||
val start: Long? = null,
|
val start: Long? = null,
|
||||||
|
@SerialName("end")
|
||||||
val stop: Long? = null
|
val stop: Long? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ class Contributors {
|
|||||||
"rebelonion" -> "Owner & Maintainer"
|
"rebelonion" -> "Owner & Maintainer"
|
||||||
"sneazy-ibo" -> "Contributor & Comment Moderator"
|
"sneazy-ibo" -> "Contributor & Comment Moderator"
|
||||||
"WaiWhat" -> "Icon Designer"
|
"WaiWhat" -> "Icon Designer"
|
||||||
|
"itsmechinmoy" -> "Discord and Telegram Admin/Helper, Comment Moderator & Translator"
|
||||||
else -> "Contributor"
|
else -> "Contributor"
|
||||||
}
|
}
|
||||||
developers = developers.plus(
|
developers = developers.plus(
|
||||||
@@ -89,9 +90,15 @@ class Contributors {
|
|||||||
"Comment Moderator and Arabic Translator",
|
"Comment Moderator and Arabic Translator",
|
||||||
"https://anilist.co/user/6049773"
|
"https://anilist.co/user/6049773"
|
||||||
),
|
),
|
||||||
|
Developer(
|
||||||
|
"Dawnusedyeet",
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6237399-RHFvRHriXjwS.png",
|
||||||
|
"Contributor",
|
||||||
|
"https://anilist.co/user/Dawnusedyeet/"
|
||||||
|
),
|
||||||
Developer(
|
Developer(
|
||||||
"hastsu",
|
"hastsu",
|
||||||
"https://cdn.discordapp.com/avatars/602422545077108749/20b4a6efa4314550e4ed51cdbe4fef3d.webp?size=160",
|
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6183359-9os7zUhYdF64.jpg",
|
||||||
"Comment Moderator and Arabic Translator",
|
"Comment Moderator and Arabic Translator",
|
||||||
"https://anilist.co/user/6183359"
|
"https://anilist.co/user/6183359"
|
||||||
),
|
),
|
||||||
@@ -111,4 +118,4 @@ class Contributors {
|
|||||||
@SerialName("html_url")
|
@SerialName("html_url")
|
||||||
val htmlUrl: String
|
val htmlUrl: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class DownloadCompat {
|
|||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
return OfflineAnimeModel(
|
return OfflineAnimeModel(
|
||||||
"unknown",
|
downloadedType.titleName,
|
||||||
"0",
|
"0",
|
||||||
"??",
|
"??",
|
||||||
"??",
|
"??",
|
||||||
@@ -188,7 +188,7 @@ class DownloadCompat {
|
|||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
return OfflineMangaModel(
|
return OfflineMangaModel(
|
||||||
"unknown",
|
downloadedType.titleName,
|
||||||
"0",
|
"0",
|
||||||
"??",
|
"??",
|
||||||
"??",
|
"??",
|
||||||
@@ -260,7 +260,7 @@ class DownloadCompat {
|
|||||||
"$mangaLink/${it.name}",
|
"$mangaLink/${it.name}",
|
||||||
it.name,
|
it.name,
|
||||||
null,
|
null,
|
||||||
null,
|
"Unknown",
|
||||||
SChapter.create()
|
SChapter.create()
|
||||||
)
|
)
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import ani.dantotsu.snackString
|
|||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import com.anggrayudi.storage.callback.FolderCallback
|
import com.anggrayudi.storage.callback.FolderCallback
|
||||||
import com.anggrayudi.storage.file.deleteRecursively
|
import com.anggrayudi.storage.file.deleteRecursively
|
||||||
import com.anggrayudi.storage.file.findFolder
|
|
||||||
import com.anggrayudi.storage.file.moveFolderTo
|
import com.anggrayudi.storage.file.moveFolderTo
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
@@ -61,7 +60,7 @@ class DownloadsManager(private val context: Context) {
|
|||||||
onFinished: () -> Unit
|
onFinished: () -> Unit
|
||||||
) {
|
) {
|
||||||
removeDownloadCompat(context, downloadedType, toast)
|
removeDownloadCompat(context, downloadedType, toast)
|
||||||
downloadsList.remove(downloadedType)
|
downloadsList.removeAll { it.titleName == downloadedType.titleName && it.chapterName == downloadedType.chapterName }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
removeDirectory(downloadedType, toast)
|
removeDirectory(downloadedType, toast)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@@ -235,7 +234,7 @@ class DownloadsManager(private val context: Context) {
|
|||||||
val directory =
|
val directory =
|
||||||
baseDirectory?.findFolder(downloadedType.titleName)
|
baseDirectory?.findFolder(downloadedType.titleName)
|
||||||
?.findFolder(downloadedType.chapterName)
|
?.findFolder(downloadedType.chapterName)
|
||||||
downloadsList.remove(downloadedType)
|
downloadsList.removeAll { it.titleName == downloadedType.titleName && it.chapterName == downloadedType.chapterName }
|
||||||
// Check if the directory exists and delete it recursively
|
// Check if the directory exists and delete it recursively
|
||||||
if (directory?.exists() == true) {
|
if (directory?.exists() == true) {
|
||||||
val deleted = directory.deleteRecursively(context, false)
|
val deleted = directory.deleteRecursively(context, false)
|
||||||
@@ -279,6 +278,7 @@ class DownloadsManager(private val context: Context) {
|
|||||||
* @param type the type of media
|
* @param type the type of media
|
||||||
* @return the base directory
|
* @return the base directory
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? {
|
private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? {
|
||||||
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
|
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
|
||||||
if (baseDirectory == Uri.EMPTY) return null
|
if (baseDirectory == Uri.EMPTY) return null
|
||||||
@@ -307,6 +307,7 @@ class DownloadsManager(private val context: Context) {
|
|||||||
* @param chapter the chapter of the media
|
* @param chapter the chapter of the media
|
||||||
* @return the subdirectory
|
* @return the subdirectory
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun getSubDirectory(
|
fun getSubDirectory(
|
||||||
context: Context,
|
context: Context,
|
||||||
type: MediaType,
|
type: MediaType,
|
||||||
@@ -344,23 +345,34 @@ class DownloadsManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
private fun getBaseDirectory(context: Context): DocumentFile? {
|
private fun getBaseDirectory(context: Context): DocumentFile? {
|
||||||
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
|
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
|
||||||
if (baseDirectory == Uri.EMPTY) return null
|
if (baseDirectory == Uri.EMPTY) return null
|
||||||
return DocumentFile.fromTreeUri(context, baseDirectory)
|
val base = DocumentFile.fromTreeUri(context, baseDirectory) ?: return null
|
||||||
|
return base.findOrCreateFolder(BASE_LOCATION, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val lock = Any()
|
||||||
|
|
||||||
private fun DocumentFile.findOrCreateFolder(
|
private fun DocumentFile.findOrCreateFolder(
|
||||||
name: String, overwrite: Boolean
|
name: String, overwrite: Boolean
|
||||||
): DocumentFile? {
|
): DocumentFile? {
|
||||||
return if (overwrite) {
|
val validName = name.findValidName()
|
||||||
findFolder(name.findValidName())?.delete()
|
synchronized(lock) {
|
||||||
createDirectory(name.findValidName())
|
return if (overwrite) {
|
||||||
} else {
|
findFolder(validName)?.delete()
|
||||||
findFolder(name.findValidName()) ?: createDirectory(name.findValidName())
|
createDirectory(validName)
|
||||||
|
} else {
|
||||||
|
val folder = findFolder(validName)
|
||||||
|
folder ?: createDirectory(validName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun DocumentFile.findFolder(name: String): DocumentFile? =
|
||||||
|
listFiles().find { it.name == name && it.isDirectory }
|
||||||
|
|
||||||
private const val RATIO_THRESHOLD = 95
|
private const val RATIO_THRESHOLD = 95
|
||||||
fun Media.compareName(name: String): Boolean {
|
fun Media.compareName(name: String): Boolean {
|
||||||
val mainName = mainName().findValidName().lowercase()
|
val mainName = mainName().findValidName().lowercase()
|
||||||
@@ -379,7 +391,7 @@ class DownloadsManager(private val context: Context) {
|
|||||||
|
|
||||||
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
|
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
|
||||||
fun String?.findValidName(): String {
|
fun String?.findValidName(): String {
|
||||||
return this?.replace("/","_")?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
|
return this?.replace("/", "_")?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DownloadedType(
|
data class DownloadedType(
|
||||||
@@ -389,10 +401,13 @@ data class DownloadedType(
|
|||||||
@Deprecated("use pTitle instead")
|
@Deprecated("use pTitle instead")
|
||||||
private val title: String? = null,
|
private val title: String? = null,
|
||||||
@Deprecated("use pChapter instead")
|
@Deprecated("use pChapter instead")
|
||||||
private val chapter: String? = null
|
private val chapter: String? = null,
|
||||||
|
val scanlator: String = "Unknown"
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
val titleName: String
|
val titleName: String
|
||||||
get() = title ?: pTitle.findValidName()
|
get() = title ?: pTitle.findValidName()
|
||||||
val chapterName: String
|
val chapterName: String
|
||||||
get() = chapter ?: pChapter.findValidName()
|
get() = chapter ?: pChapter.findValidName()
|
||||||
|
val uniqueName: String
|
||||||
|
get() = "$chapterName-${scanlator}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ class AnimeDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification() {
|
private fun updateNotification() {
|
||||||
// Update the notification to reflect the current state of the queue
|
|
||||||
val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size
|
val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size
|
||||||
val text = if (pendingDownloads > 0) {
|
val text = if (pendingDownloads > 0) {
|
||||||
"Pending downloads: $pendingDownloads"
|
"Pending downloads: $pendingDownloads"
|
||||||
@@ -201,8 +200,8 @@ class AnimeDownloaderService : Service() {
|
|||||||
|
|
||||||
@androidx.annotation.OptIn(UnstableApi::class)
|
@androidx.annotation.OptIn(UnstableApi::class)
|
||||||
suspend fun download(task: AnimeDownloadTask) {
|
suspend fun download(task: AnimeDownloadTask) {
|
||||||
try {
|
withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
try {
|
||||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
this@AnimeDownloaderService,
|
this@AnimeDownloaderService,
|
||||||
@@ -214,22 +213,34 @@ class AnimeDownloaderService : Service() {
|
|||||||
|
|
||||||
builder.setContentText("Downloading ${getTaskName(task.title, task.episode)}")
|
builder.setContentText("Downloading ${getTaskName(task.title, task.episode)}")
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputDir = getSubDirectory(
|
val baseOutputDir = getSubDirectory(
|
||||||
this@AnimeDownloaderService,
|
this@AnimeDownloaderService,
|
||||||
MediaType.ANIME,
|
MediaType.ANIME,
|
||||||
false,
|
false,
|
||||||
|
task.title
|
||||||
|
) ?: throw Exception("Failed to create output directory")
|
||||||
|
val outputDir = getSubDirectory(
|
||||||
|
this@AnimeDownloaderService,
|
||||||
|
MediaType.ANIME,
|
||||||
|
true,
|
||||||
task.title,
|
task.title,
|
||||||
task.episode
|
task.episode
|
||||||
) ?: throw Exception("Failed to create output directory")
|
) ?: throw Exception("Failed to create output directory")
|
||||||
|
|
||||||
val extension = ffExtension!!.getFileExtension()
|
val extension = ffExtension!!.getFileExtension()
|
||||||
outputDir.findFile("${task.getTaskName().findValidName()}.${extension.first}")?.delete()
|
outputDir.findFile("${task.getTaskName().findValidName()}.${extension.first}")
|
||||||
|
?.delete()
|
||||||
|
|
||||||
val outputFile =
|
val outputFile =
|
||||||
outputDir.createFile(extension.second, "${task.getTaskName()}.${extension.first}")
|
outputDir.createFile(
|
||||||
|
extension.second,
|
||||||
|
"${task.getTaskName()}.${extension.first}"
|
||||||
|
)
|
||||||
?: throw Exception("Failed to create output file")
|
?: throw Exception("Failed to create output file")
|
||||||
|
|
||||||
var percent = 0
|
var percent = 0
|
||||||
@@ -273,7 +284,7 @@ class AnimeDownloaderService : Service() {
|
|||||||
currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId =
|
currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId =
|
||||||
ffTask
|
ffTask
|
||||||
|
|
||||||
saveMediaInfo(task)
|
saveMediaInfo(task, baseOutputDir)
|
||||||
|
|
||||||
// periodically check if the download is complete
|
// periodically check if the download is complete
|
||||||
while (ffExtension.getState(ffTask) != "COMPLETED") {
|
while (ffExtension.getState(ffTask) != "COMPLETED") {
|
||||||
@@ -287,7 +298,11 @@ class AnimeDownloaderService : Service() {
|
|||||||
)
|
)
|
||||||
} Download failed"
|
} Download failed"
|
||||||
)
|
)
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
if (notifi) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
toast("${getTaskName(task.title, task.episode)} Download failed")
|
toast("${getTaskName(task.title, task.episode)} Download failed")
|
||||||
Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}")
|
Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}")
|
||||||
downloadsManager.removeDownload(
|
downloadsManager.removeDownload(
|
||||||
@@ -320,7 +335,9 @@ class AnimeDownloaderService : Service() {
|
|||||||
percent.coerceAtMost(99)
|
percent.coerceAtMost(99)
|
||||||
)
|
)
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
kotlinx.coroutines.delay(2000)
|
kotlinx.coroutines.delay(2000)
|
||||||
}
|
}
|
||||||
@@ -335,7 +352,11 @@ class AnimeDownloaderService : Service() {
|
|||||||
)
|
)
|
||||||
} Download failed"
|
} Download failed"
|
||||||
)
|
)
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
if (notifi) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
snackString("${getTaskName(task.title, task.episode)} Download failed")
|
snackString("${getTaskName(task.title, task.episode)} Download failed")
|
||||||
downloadsManager.removeDownload(
|
downloadsManager.removeDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
@@ -367,7 +388,11 @@ class AnimeDownloaderService : Service() {
|
|||||||
)
|
)
|
||||||
} Download completed"
|
} Download completed"
|
||||||
)
|
)
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
if (notifi) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
snackString("${getTaskName(task.title, task.episode)} Download completed")
|
snackString("${getTaskName(task.title, task.episode)} Download completed")
|
||||||
PrefManager.getAnimeDownloadPreferences().edit().putString(
|
PrefManager.getAnimeDownloadPreferences().edit().putString(
|
||||||
task.getTaskName(),
|
task.getTaskName(),
|
||||||
@@ -385,23 +410,20 @@ class AnimeDownloaderService : Service() {
|
|||||||
broadcastDownloadFinished(task.episode)
|
broadcastDownloadFinished(task.episode)
|
||||||
} else throw Exception("Download failed")
|
} else throw Exception("Download failed")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.message?.contains("Coroutine was cancelled") == false) { //wut
|
||||||
|
Logger.log("Exception while downloading file: ${e.message}")
|
||||||
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
|
}
|
||||||
|
broadcastDownloadFailed(task.episode)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
if (e.message?.contains("Coroutine was cancelled") == false) { //wut
|
|
||||||
Logger.log("Exception while downloading file: ${e.message}")
|
|
||||||
snackString("Exception while downloading file: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
|
||||||
}
|
|
||||||
broadcastDownloadFailed(task.episode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveMediaInfo(task: AnimeDownloadTask) {
|
private fun saveMediaInfo(task: AnimeDownloadTask, directory: DocumentFile) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val directory =
|
|
||||||
getSubDirectory(this@AnimeDownloaderService, MediaType.ANIME, false, task.title)
|
|
||||||
?: throw Exception("Directory not found")
|
|
||||||
directory.findFile("media.json")?.forceDelete(this@AnimeDownloaderService)
|
directory.findFile("media.json")?.forceDelete(this@AnimeDownloaderService)
|
||||||
val file = directory.createFile("application/json", "media.json")
|
val file = directory.createFile("application/json", "media.json")
|
||||||
?: throw Exception("File not created")
|
?: throw Exception("File not created")
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import ani.dantotsu.settings.saving.PrefManager
|
|||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.anggrayudi.storage.file.openInputStream
|
import com.anggrayudi.storage.file.openInputStream
|
||||||
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
|
||||||
@@ -202,25 +203,24 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
val type: MediaType = MediaType.ANIME
|
val type: MediaType = MediaType.ANIME
|
||||||
|
|
||||||
// Alert dialog to confirm deletion
|
// Alert dialog to confirm deletion
|
||||||
val builder =
|
requireContext().customAlertDialog().apply {
|
||||||
androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
setTitle("Delete ${item.title}?")
|
||||||
builder.setTitle("Delete ${item.title}?")
|
setMessage("Are you sure you want to delete ${item.title}?")
|
||||||
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
setPosButton(R.string.yes) {
|
||||||
builder.setPositiveButton("Yes") { _, _ ->
|
downloadManager.removeMedia(item.title, type)
|
||||||
downloadManager.removeMedia(item.title, type)
|
val mediaIds =
|
||||||
val mediaIds =
|
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
|
||||||
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
|
?: emptySet()
|
||||||
?: 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
|
}
|
||||||
|
getDownloads()
|
||||||
}
|
}
|
||||||
getDownloads()
|
setNegButton(R.string.no) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,10 +288,12 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
}
|
}
|
||||||
downloadsJob = Job()
|
downloadsJob = Job()
|
||||||
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
||||||
val animeTitles = downloadManager.animeDownloadedTypes.map { it.titleName.findValidName() }.distinct()
|
val animeTitles =
|
||||||
|
downloadManager.animeDownloadedTypes.map { it.titleName.findValidName() }.distinct()
|
||||||
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
||||||
for (title in animeTitles) {
|
for (title in animeTitles) {
|
||||||
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title }
|
val tDownloads =
|
||||||
|
downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title }
|
||||||
val download = tDownloads.firstOrNull() ?: continue
|
val download = tDownloads.firstOrNull() ?: continue
|
||||||
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
||||||
if (offlineAnimeModel.title == "unknown") offlineAnimeModel.title = title
|
if (offlineAnimeModel.title == "unknown") offlineAnimeModel.title = title
|
||||||
@@ -319,17 +321,20 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
)
|
)
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
SChapterImpl() // Provide an instance of SChapterImpl
|
SChapterImpl()
|
||||||
})
|
})
|
||||||
.registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> {
|
.registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> {
|
||||||
SAnimeImpl() // Provide an instance of SAnimeImpl
|
SAnimeImpl()
|
||||||
})
|
})
|
||||||
.registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> {
|
.registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> {
|
||||||
SEpisodeImpl() // Provide an instance of SEpisodeImpl
|
SEpisodeImpl()
|
||||||
})
|
})
|
||||||
.create()
|
.create()
|
||||||
val media = directory?.findFile("media.json")
|
val media = directory?.findFile("media.json")
|
||||||
?: return loadMediaCompat(downloadedType)
|
if (media == null) {
|
||||||
|
Logger.log("No media.json found at ${directory?.uri?.path}")
|
||||||
|
return loadMediaCompat(downloadedType)
|
||||||
|
}
|
||||||
val mediaJson =
|
val mediaJson =
|
||||||
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
||||||
it?.readText()
|
it?.readText()
|
||||||
@@ -394,6 +399,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
bannerUri
|
bannerUri
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
return try {
|
return try {
|
||||||
loadOfflineAnimeModelCompat(downloadedType)
|
loadOfflineAnimeModelCompat(downloadedType)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -401,7 +407,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
OfflineAnimeModel(
|
OfflineAnimeModel(
|
||||||
"unknown",
|
downloadedType.titleName,
|
||||||
"0",
|
"0",
|
||||||
"??",
|
"??",
|
||||||
"??",
|
"??",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STAR
|
|||||||
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 ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.NumberConverter.Companion.ofLength
|
||||||
import com.anggrayudi.storage.file.deleteRecursively
|
import com.anggrayudi.storage.file.deleteRecursively
|
||||||
import com.anggrayudi.storage.file.forceDelete
|
import com.anggrayudi.storage.file.forceDelete
|
||||||
import com.anggrayudi.storage.file.openOutputStream
|
import com.anggrayudi.storage.file.openOutputStream
|
||||||
@@ -134,15 +135,15 @@ class MangaDownloaderService : Service() {
|
|||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
downloadJobs[task.chapter] = job
|
downloadJobs[task.chapter] = job
|
||||||
}
|
}
|
||||||
job.join() // Wait for the job to complete before continuing to the next task
|
job.join()
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
downloadJobs.remove(task.chapter)
|
downloadJobs.remove(task.chapter)
|
||||||
}
|
}
|
||||||
updateNotification() // Update the notification after each task is completed
|
updateNotification()
|
||||||
}
|
}
|
||||||
if (MangaServiceDataSingleton.downloadQueue.isEmpty()) {
|
if (MangaServiceDataSingleton.downloadQueue.isEmpty()) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
stopSelf() // Stop the service when the queue is empty
|
stopSelf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +182,7 @@ class MangaDownloaderService : Service() {
|
|||||||
|
|
||||||
suspend fun download(task: DownloadTask) {
|
suspend fun download(task: DownloadTask) {
|
||||||
try {
|
try {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.IO) {
|
||||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
this@MangaDownloaderService,
|
this@MangaDownloaderService,
|
||||||
@@ -194,18 +195,27 @@ class MangaDownloaderService : Service() {
|
|||||||
val deferredMap = mutableMapOf<Int, Deferred<Bitmap?>>()
|
val deferredMap = mutableMapOf<Int, Deferred<Bitmap?>>()
|
||||||
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubDirectory(
|
val baseOutputDir = getSubDirectory(
|
||||||
|
this@MangaDownloaderService,
|
||||||
|
MediaType.MANGA,
|
||||||
|
false,
|
||||||
|
task.title
|
||||||
|
) ?: throw Exception("Base output directory not found")
|
||||||
|
val outputDir = getSubDirectory(
|
||||||
this@MangaDownloaderService,
|
this@MangaDownloaderService,
|
||||||
MediaType.MANGA,
|
MediaType.MANGA,
|
||||||
false,
|
false,
|
||||||
task.title,
|
task.title,
|
||||||
task.chapter
|
task.chapter
|
||||||
)?.deleteRecursively(this@MangaDownloaderService)
|
) ?: throw Exception("Output directory not found")
|
||||||
|
|
||||||
|
outputDir.deleteRecursively(this@MangaDownloaderService, true)
|
||||||
|
|
||||||
// Loop through each ImageData object from the task
|
|
||||||
var farthest = 0
|
var farthest = 0
|
||||||
for ((index, image) in task.imageData.withIndex()) {
|
for ((index, image) in task.imageData.withIndex()) {
|
||||||
if (deferredMap.size >= task.simultaneousDownloads) {
|
if (deferredMap.size >= task.simultaneousDownloads) {
|
||||||
@@ -222,64 +232,76 @@ class MangaDownloaderService : Service() {
|
|||||||
image.page,
|
image.page,
|
||||||
image.source
|
image.source
|
||||||
)
|
)
|
||||||
|
if (bitmap == null) {
|
||||||
|
snackString("${task.chapter} - Retrying to download page ${index.ofLength(3)}, attempt ${retryCount + 1}.")
|
||||||
|
}
|
||||||
retryCount++
|
retryCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap == null) {
|
||||||
saveToDisk("$index.jpg", bitmap, task.title, task.chapter)
|
outputDir.deleteRecursively(this@MangaDownloaderService, false)
|
||||||
|
throw Exception("${task.chapter} - Unable to download all pages after $retryCount attempts. Try again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap)
|
||||||
farthest++
|
farthest++
|
||||||
|
|
||||||
builder.setProgress(task.imageData.size, farthest, false)
|
builder.setProgress(task.imageData.size, farthest, false)
|
||||||
|
|
||||||
broadcastDownloadProgress(
|
broadcastDownloadProgress(
|
||||||
task.chapter,
|
task.uniqueName,
|
||||||
farthest * 100 / task.imageData.size
|
farthest * 100 / task.imageData.size
|
||||||
)
|
)
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
withContext(Dispatchers.Main) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitmap
|
bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for any remaining deferred to complete
|
|
||||||
deferredMap.values.awaitAll()
|
deferredMap.values.awaitAll()
|
||||||
|
|
||||||
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
withContext(Dispatchers.Main) {
|
||||||
.setProgress(0, 0, false)
|
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
.setProgress(0, 0, false)
|
||||||
|
if (notifi) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveMediaInfo(task)
|
saveMediaInfo(task, baseOutputDir)
|
||||||
downloadsManager.addDownload(
|
downloadsManager.addDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
task.title,
|
task.title,
|
||||||
task.chapter,
|
task.chapter,
|
||||||
MediaType.MANGA
|
MediaType.MANGA,
|
||||||
|
scanlator = task.scanlator,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
broadcastDownloadFinished(task.chapter)
|
broadcastDownloadFinished(task.uniqueName)
|
||||||
snackString("${task.title} - ${task.chapter} Download finished")
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("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}")
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
broadcastDownloadFailed(task.chapter)
|
broadcastDownloadFailed(task.uniqueName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun saveToDisk(fileName: String, bitmap: Bitmap, title: String, chapter: String) {
|
private fun saveToDisk(
|
||||||
|
fileName: String,
|
||||||
|
directory: DocumentFile,
|
||||||
|
bitmap: Bitmap
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// Define the directory within the private external storage space
|
|
||||||
val directory = getSubDirectory(this, MediaType.MANGA, false, title, chapter)
|
|
||||||
?: throw Exception("Directory not found")
|
|
||||||
directory.findFile(fileName)?.forceDelete(this)
|
directory.findFile(fileName)?.forceDelete(this)
|
||||||
// Create a file reference within that directory for the image
|
|
||||||
val file =
|
val file =
|
||||||
directory.createFile("image/jpeg", fileName) ?: throw Exception("File not created")
|
directory.createFile("image/jpeg", fileName) ?: throw Exception("File not created")
|
||||||
|
|
||||||
// Use a FileOutputStream to write the bitmap to the file
|
|
||||||
file.openOutputStream(this, false).use { outputStream ->
|
file.openOutputStream(this, false).use { outputStream ->
|
||||||
if (outputStream == null) throw Exception("Output stream is null")
|
if (outputStream == null) throw Exception("Output stream is null")
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||||
@@ -292,11 +314,8 @@ class MangaDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun saveMediaInfo(task: DownloadTask) {
|
private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) {
|
||||||
launchIO {
|
launchIO {
|
||||||
val directory =
|
|
||||||
getSubDirectory(this@MangaDownloaderService, MediaType.MANGA, false, task.title)
|
|
||||||
?: throw Exception("Directory not found")
|
|
||||||
directory.findFile("media.json")?.forceDelete(this@MangaDownloaderService)
|
directory.findFile("media.json")?.forceDelete(this@MangaDownloaderService)
|
||||||
val file = directory.createFile("application/json", "media.json")
|
val file = directory.createFile("application/json", "media.json")
|
||||||
?: throw Exception("File not created")
|
?: throw Exception("File not created")
|
||||||
@@ -411,11 +430,15 @@ class MangaDownloaderService : Service() {
|
|||||||
data class DownloadTask(
|
data class DownloadTask(
|
||||||
val title: String,
|
val title: String,
|
||||||
val chapter: String,
|
val chapter: String,
|
||||||
|
val scanlator: String,
|
||||||
val imageData: List<ImageData>,
|
val imageData: List<ImageData>,
|
||||||
val sourceMedia: Media? = null,
|
val sourceMedia: Media? = null,
|
||||||
val retries: Int = 2,
|
val retries: Int = 2,
|
||||||
val simultaneousDownloads: Int = 2,
|
val simultaneousDownloads: Int = 2,
|
||||||
)
|
) {
|
||||||
|
val uniqueName: String
|
||||||
|
get() = "$chapter-$scanlator"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NOTIFICATION_ID = 1103
|
private const val NOTIFICATION_ID = 1103
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import ani.dantotsu.settings.saving.PrefManager
|
|||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.anggrayudi.storage.file.openInputStream
|
import com.anggrayudi.storage.file.openInputStream
|
||||||
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
|
||||||
@@ -171,7 +172,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
val item = adapter.getItem(position) as OfflineMangaModel
|
val item = adapter.getItem(position) as OfflineMangaModel
|
||||||
val media =
|
val media =
|
||||||
downloadManager.mangaDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
|
downloadManager.mangaDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
|
||||||
?: downloadManager.novelDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
|
?: downloadManager.novelDownloadedTypes.firstOrNull {
|
||||||
|
it.titleName.compareName(
|
||||||
|
item.title
|
||||||
|
)
|
||||||
|
}
|
||||||
media?.let {
|
media?.let {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
@@ -197,19 +202,15 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
MediaType.NOVEL
|
MediaType.NOVEL
|
||||||
}
|
}
|
||||||
// Alert dialog to confirm deletion
|
// Alert dialog to confirm deletion
|
||||||
val builder =
|
requireContext().customAlertDialog().apply {
|
||||||
androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
setTitle("Delete ${item.title}?")
|
||||||
builder.setTitle("Delete ${item.title}?")
|
setMessage("Are you sure you want to delete ${item.title}?")
|
||||||
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
setPosButton(R.string.yes) {
|
||||||
builder.setPositiveButton("Yes") { _, _ ->
|
downloadManager.removeMedia(item.title, type)
|
||||||
downloadManager.removeMedia(item.title, type)
|
getDownloads()
|
||||||
getDownloads()
|
}
|
||||||
}
|
setNegButton(R.string.no)
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
}.show()
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,10 +280,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
downloads = listOf()
|
downloads = listOf()
|
||||||
downloadsJob = Job()
|
downloadsJob = Job()
|
||||||
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
||||||
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.titleName.findValidName() }.distinct()
|
val mangaTitles =
|
||||||
|
downloadManager.mangaDownloadedTypes.map { it.titleName.findValidName() }.distinct()
|
||||||
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
for (title in mangaTitles) {
|
for (title in mangaTitles) {
|
||||||
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.titleName.findValidName() == title }
|
val tDownloads =
|
||||||
|
downloadManager.mangaDownloadedTypes.filter { it.titleName.findValidName() == title }
|
||||||
val download = tDownloads.firstOrNull() ?: continue
|
val download = tDownloads.firstOrNull() ?: continue
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
newMangaDownloads += offlineMangaModel
|
newMangaDownloads += offlineMangaModel
|
||||||
@@ -291,7 +294,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct()
|
val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct()
|
||||||
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
for (title in novelTitles) {
|
for (title in novelTitles) {
|
||||||
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.titleName.findValidName() == title }
|
val tDownloads =
|
||||||
|
downloadManager.novelDownloadedTypes.filter { it.titleName.findValidName() == title }
|
||||||
val download = tDownloads.firstOrNull() ?: continue
|
val download = tDownloads.firstOrNull() ?: continue
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
newNovelDownloads += offlineMangaModel
|
newNovelDownloads += offlineMangaModel
|
||||||
@@ -320,11 +324,14 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
)
|
)
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
SChapterImpl() // Provide an instance of SChapterImpl
|
SChapterImpl()
|
||||||
})
|
})
|
||||||
.create()
|
.create()
|
||||||
val media = directory?.findFile("media.json")
|
val media = directory?.findFile("media.json")
|
||||||
?: return DownloadCompat.loadMediaCompat(downloadedType)
|
if (media == null) {
|
||||||
|
Logger.log("No media.json found at ${directory?.uri?.path}")
|
||||||
|
return DownloadCompat.loadMediaCompat(downloadedType)
|
||||||
|
}
|
||||||
val mediaJson =
|
val mediaJson =
|
||||||
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
||||||
it?.readText()
|
it?.readText()
|
||||||
@@ -340,7 +347,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
|
|
||||||
private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
||||||
val type = downloadedType.type.asText()
|
val type = downloadedType.type.asText()
|
||||||
//load media.json and convert to media class with gson
|
|
||||||
try {
|
try {
|
||||||
val directory = getSubDirectory(
|
val directory = getSubDirectory(
|
||||||
context ?: currContext()!!, downloadedType.type,
|
context ?: currContext()!!, downloadedType.type,
|
||||||
@@ -378,6 +384,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
bannerUri
|
bannerUri
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
return try {
|
return try {
|
||||||
loadOfflineMangaModelCompat(downloadedType)
|
loadOfflineMangaModelCompat(downloadedType)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -385,7 +392,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
return OfflineMangaModel(
|
return OfflineMangaModel(
|
||||||
"unknown",
|
downloadedType.titleName,
|
||||||
"0",
|
"0",
|
||||||
"??",
|
"??",
|
||||||
"??",
|
"??",
|
||||||
|
|||||||
@@ -239,6 +239,13 @@ class NovelDownloaderService : Service() {
|
|||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val baseDirectory = getSubDirectory(
|
||||||
|
this@NovelDownloaderService,
|
||||||
|
MediaType.NOVEL,
|
||||||
|
false,
|
||||||
|
task.title
|
||||||
|
) ?: throw Exception("Directory not found")
|
||||||
|
|
||||||
// Start the download
|
// Start the download
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@@ -334,7 +341,7 @@ class NovelDownloaderService : Service() {
|
|||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMediaInfo(task)
|
saveMediaInfo(task, baseDirectory)
|
||||||
downloadsManager.addDownload(
|
downloadsManager.addDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
task.title,
|
task.title,
|
||||||
@@ -354,15 +361,8 @@ class NovelDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun saveMediaInfo(task: DownloadTask) {
|
private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) {
|
||||||
launchIO {
|
launchIO {
|
||||||
val directory =
|
|
||||||
getSubDirectory(
|
|
||||||
this@NovelDownloaderService,
|
|
||||||
MediaType.NOVEL,
|
|
||||||
false,
|
|
||||||
task.title
|
|
||||||
) ?: throw Exception("Directory not found")
|
|
||||||
directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService)
|
directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService)
|
||||||
val file = directory.createFile("application/json", "media.json")
|
val file = directory.createFile("application/json", "media.json")
|
||||||
?: throw Exception("File not created")
|
?: throw Exception("File not created")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package ani.dantotsu.download.video
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@@ -29,10 +28,10 @@ import ani.dantotsu.download.anime.AnimeDownloaderService
|
|||||||
import ani.dantotsu.download.anime.AnimeServiceDataSingleton
|
import ani.dantotsu.download.anime.AnimeServiceDataSingleton
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.parsers.Subtitle
|
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
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
|
||||||
@@ -72,19 +71,19 @@ object Helper {
|
|||||||
episodeImage
|
episodeImage
|
||||||
)
|
)
|
||||||
|
|
||||||
val downloadsManger = Injekt.get<DownloadsManager>()
|
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||||
val downloadCheck = downloadsManger
|
val downloadCheck = downloadsManager
|
||||||
.queryDownload(title, episode, MediaType.ANIME)
|
.queryDownload(title, episode, MediaType.ANIME)
|
||||||
|
|
||||||
if (downloadCheck) {
|
if (downloadCheck) {
|
||||||
AlertDialog.Builder(context, R.style.MyPopup)
|
context.customAlertDialog().apply {
|
||||||
.setTitle("Download Exists")
|
setTitle("Download Exists")
|
||||||
.setMessage("A download for this episode already exists. Do you want to overwrite it?")
|
setMessage("A download for this episode already exists. Do you want to overwrite it?")
|
||||||
.setPositiveButton("Yes") { _, _ ->
|
setPosButton(R.string.yes) {
|
||||||
PrefManager.getAnimeDownloadPreferences().edit()
|
PrefManager.getAnimeDownloadPreferences().edit()
|
||||||
.remove(animeDownloadTask.getTaskName())
|
.remove(animeDownloadTask.getTaskName())
|
||||||
.apply()
|
.apply()
|
||||||
downloadsManger.removeDownload(
|
downloadsManager.removeDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
title,
|
title,
|
||||||
episode,
|
episode,
|
||||||
@@ -99,8 +98,9 @@ object Helper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton("No") { _, _ -> }
|
setNegButton(R.string.no)
|
||||||
.show()
|
show()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)
|
AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)
|
||||||
if (!AnimeServiceDataSingleton.isServiceRunning) {
|
if (!AnimeServiceDataSingleton.isServiceRunning) {
|
||||||
@@ -177,6 +177,7 @@ object Helper {
|
|||||||
downloadManager
|
downloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun getSimpleCache(context: Context): SimpleCache {
|
fun getSimpleCache(context: Context): SimpleCache {
|
||||||
@@ -189,6 +190,7 @@ object Helper {
|
|||||||
simpleCache!!
|
simpleCache!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
private fun getDownloadDirectory(context: Context): File {
|
private fun getDownloadDirectory(context: Context): File {
|
||||||
@@ -200,12 +202,16 @@ object Helper {
|
|||||||
}
|
}
|
||||||
return downloadDirectory!!
|
return downloadDirectory!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
private var download: DownloadManager? = null
|
private var download: DownloadManager? = null
|
||||||
|
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads"
|
private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads"
|
||||||
|
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
private var simpleCache: SimpleCache? = null
|
private var simpleCache: SimpleCache? = null
|
||||||
|
|
||||||
@Deprecated("exoplayer download manager is no longer used")
|
@Deprecated("exoplayer download manager is no longer used")
|
||||||
private var downloadDirectory: File? = null
|
private var downloadDirectory: File? = null
|
||||||
}
|
}
|
||||||
@@ -22,9 +22,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
|
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
|
||||||
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.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
@@ -38,6 +38,7 @@ import ani.dantotsu.snackString
|
|||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -99,7 +100,7 @@ class AnimeFragment : Fragment() {
|
|||||||
var loading = true
|
var loading = true
|
||||||
if (model.notSet) {
|
if (model.notSet) {
|
||||||
model.notSet = false
|
model.notSet = false
|
||||||
model.searchResults = SearchResults(
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
"ANIME",
|
"ANIME",
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
onList = false,
|
onList = false,
|
||||||
@@ -108,7 +109,7 @@ class AnimeFragment : Fragment() {
|
|||||||
sort = Anilist.sortBy[1]
|
sort = Anilist.sortBy[1]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
val popularAdaptor = MediaAdaptor(1, model.aniMangaSearchResults.results, requireActivity())
|
||||||
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
||||||
val adapter = ConcatAdapter(animePageAdapter, popularAdaptor, progressAdaptor)
|
val adapter = ConcatAdapter(animePageAdapter, popularAdaptor, progressAdaptor)
|
||||||
binding.animePageRecyclerView.adapter = adapter
|
binding.animePageRecyclerView.adapter = adapter
|
||||||
@@ -141,7 +142,7 @@ class AnimeFragment : Fragment() {
|
|||||||
animePageAdapter.onIncludeListClick = { checked ->
|
animePageAdapter.onIncludeListClick = { checked ->
|
||||||
oldIncludeList = !checked
|
oldIncludeList = !checked
|
||||||
loading = true
|
loading = true
|
||||||
model.searchResults.results.clear()
|
model.aniMangaSearchResults.results.clear()
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = checked)
|
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = checked)
|
||||||
@@ -151,17 +152,17 @@ class AnimeFragment : Fragment() {
|
|||||||
model.getPopular().observe(viewLifecycleOwner) {
|
model.getPopular().observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
if (oldIncludeList == (it.onList != false)) {
|
if (oldIncludeList == (it.onList != false)) {
|
||||||
val prev = model.searchResults.results.size
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
} else {
|
} else {
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
oldIncludeList = it.onList ?: true
|
oldIncludeList = it.onList ?: true
|
||||||
}
|
}
|
||||||
model.searchResults.onList = it.onList
|
model.aniMangaSearchResults.onList = it.onList
|
||||||
model.searchResults.hasNextPage = it.hasNextPage
|
model.aniMangaSearchResults.hasNextPage = it.hasNextPage
|
||||||
model.searchResults.page = it.page
|
model.aniMangaSearchResults.page = it.page
|
||||||
if (it.hasNextPage)
|
if (it.hasNextPage)
|
||||||
progressAdaptor.bar?.visibility = View.VISIBLE
|
progressAdaptor.bar?.visibility = View.VISIBLE
|
||||||
else {
|
else {
|
||||||
@@ -176,10 +177,10 @@ class AnimeFragment : Fragment() {
|
|||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.aniMangaSearchResults.hasNextPage && model.aniMangaSearchResults.results.isNotEmpty() && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(model.aniMangaSearchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,8 +277,9 @@ class AnimeFragment : Fragment() {
|
|||||||
running = true
|
running = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Anilist.userid = PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
Anilist.userid =
|
||||||
?.toIntOrNull()
|
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
||||||
|
?.toIntOrNull()
|
||||||
if (Anilist.userid == null) {
|
if (Anilist.userid == null) {
|
||||||
getUserId(requireContext()) {
|
getUserId(requireContext()) {
|
||||||
load()
|
load()
|
||||||
@@ -289,15 +291,20 @@ class AnimeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.loaded = true
|
}
|
||||||
model.loadTrending(1)
|
model.loaded = true
|
||||||
model.loadAll()
|
val loadTrending = async(Dispatchers.IO) { model.loadTrending(1) }
|
||||||
|
val loadAll = async(Dispatchers.IO) { model.loadAll() }
|
||||||
|
val loadPopular = async(Dispatchers.IO) {
|
||||||
model.loadPopular(
|
model.loadPopular(
|
||||||
"ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
|
"ANIME",
|
||||||
PrefName.PopularAnimeList
|
sort = Anilist.sortBy[1],
|
||||||
)
|
onList = PrefManager.getVal(PrefName.PopularAnimeList)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
loadTrending.await()
|
||||||
|
loadAll.await()
|
||||||
|
loadPopular.await()
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
_binding?.animeRefresh?.isRefreshing = false
|
_binding?.animeRefresh?.isRefreshing = false
|
||||||
running = false
|
running = false
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -21,7 +20,6 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
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.ItemAnimePageBinding
|
import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||||
import ani.dantotsu.databinding.LayoutTrendingBinding
|
import ani.dantotsu.databinding.LayoutTrendingBinding
|
||||||
import ani.dantotsu.getAppString
|
import ani.dantotsu.getAppString
|
||||||
@@ -83,13 +81,21 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
|
|
||||||
updateAvatar()
|
updateAvatar()
|
||||||
|
|
||||||
trendingBinding.searchBar.hint = "ANIME"
|
trendingBinding.searchBar.hint = binding.root.context.getString(R.string.search)
|
||||||
trendingBinding.searchBarText.setOnClickListener {
|
trendingBinding.searchBarText.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
val context = binding.root.context
|
||||||
it.context,
|
if (PrefManager.getVal(PrefName.AniMangaSearchDirect) && Anilist.token != null) {
|
||||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"),
|
ContextCompat.startActivity(
|
||||||
null
|
context,
|
||||||
)
|
Intent(context, SearchActivity::class.java).putExtra("type", "ANIME"),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||||
@@ -111,8 +117,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
trendingBinding.searchBar.performClick()
|
trendingBinding.searchBar.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
trendingBinding.notificationCount.visibility =
|
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
@@ -259,7 +265,15 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(adaptor: MediaAdaptor, recyclerView: RecyclerView, progress: View, title: View , more: View , string: String, media : MutableList<Media>) {
|
fun init(
|
||||||
|
adaptor: MediaAdaptor,
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
progress: View,
|
||||||
|
title: View,
|
||||||
|
more: View,
|
||||||
|
string: String,
|
||||||
|
media: MutableList<Media>
|
||||||
|
) {
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
recyclerView.adapter = adaptor
|
recyclerView.adapter = adaptor
|
||||||
recyclerView.layoutManager =
|
recyclerView.layoutManager =
|
||||||
@@ -268,8 +282,9 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
LinearLayoutManager.HORIZONTAL,
|
LinearLayoutManager.HORIZONTAL,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
MediaListViewActivity.passedMedia = media.toCollection(ArrayList())
|
|
||||||
more.setOnClickListener {
|
more.setOnClickListener {
|
||||||
|
MediaListViewActivity.passedMedia = media.toCollection(ArrayList())
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
it.context, Intent(it.context, MediaListViewActivity::class.java)
|
it.context, Intent(it.context, MediaListViewActivity::class.java)
|
||||||
.putExtra("title", string),
|
.putExtra("title", string),
|
||||||
@@ -294,8 +309,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
|
|
||||||
fun updateNotificationCount() {
|
fun updateNotificationCount() {
|
||||||
if (this::binding.isInitialized) {
|
if (this::binding.isInitialized) {
|
||||||
trendingBinding.notificationCount.visibility =
|
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ 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.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@@ -92,6 +92,7 @@ class HomeFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
binding.homeUserDataProgressBar.visibility = View.GONE
|
binding.homeUserDataProgressBar.visibility = View.GONE
|
||||||
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
|
|
||||||
binding.homeAnimeList.setOnClickListener {
|
binding.homeAnimeList.setOnClickListener {
|
||||||
@@ -132,6 +133,12 @@ class HomeFragment : Fragment() {
|
|||||||
"dialog"
|
"dialog"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
binding.searchImageContainer.setSafeOnClickListener {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(it.context as androidx.appcompat.app.AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
binding.homeUserAvatarContainer.setOnLongClickListener {
|
binding.homeUserAvatarContainer.setOnLongClickListener {
|
||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
@@ -456,51 +463,58 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
var running = false
|
var running = false
|
||||||
val live = Refresh.activity.getOrPut(1) { MutableLiveData(true) }
|
val live = Refresh.activity.getOrPut(1) { MutableLiveData(true) }
|
||||||
live.observe(viewLifecycleOwner)
|
live.observe(viewLifecycleOwner) { shouldRefresh ->
|
||||||
{
|
if (!running && shouldRefresh) {
|
||||||
if (!running && it) {
|
|
||||||
running = true
|
running = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
//Get userData First
|
// Get user data first
|
||||||
Anilist.userid =
|
Anilist.userid =
|
||||||
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
||||||
?.toIntOrNull()
|
?.toIntOrNull()
|
||||||
if (Anilist.userid == null) {
|
if (Anilist.userid == null) {
|
||||||
getUserId(requireContext()) {
|
withContext(Dispatchers.Main) {
|
||||||
load()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
getUserId(requireContext()) {
|
getUserId(requireContext()) {
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
getUserId(requireContext()) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
model.loaded = true
|
model.loaded = true
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
model.setListImages()
|
||||||
model.setListImages()
|
}
|
||||||
}
|
|
||||||
var empty = true
|
var empty = true
|
||||||
val homeLayoutShow: List<Boolean> =
|
val homeLayoutShow: List<Boolean> = PrefManager.getVal(PrefName.HomeLayout)
|
||||||
PrefManager.getVal(PrefName.HomeLayout)
|
|
||||||
model.initHomePage()
|
withContext(Dispatchers.Main) {
|
||||||
(array.indices).forEach { i ->
|
homeLayoutShow.indices.forEach { i ->
|
||||||
if (homeLayoutShow.elementAt(i)) {
|
if (homeLayoutShow.elementAt(i)) {
|
||||||
empty = false
|
empty = false
|
||||||
} else withContext(Dispatchers.Main) {
|
} else {
|
||||||
containers[i].visibility = View.GONE
|
containers[i].visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.empty.postValue(empty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val initHomePage = async(Dispatchers.IO) { model.initHomePage() }
|
||||||
|
val initUserStatus = async(Dispatchers.IO) { model.initUserStatus() }
|
||||||
|
initHomePage.await()
|
||||||
|
initUserStatus.await()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
model.empty.postValue(empty)
|
||||||
|
binding.homeHiddenItemsContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
_binding?.homeRefresh?.isRefreshing = false
|
_binding?.homeRefresh?.isRefreshing = false
|
||||||
running = false
|
running = false
|
||||||
}
|
}
|
||||||
binding.homeHiddenItemsContainer.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,6 +522,7 @@ class HomeFragment : Fragment() {
|
|||||||
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
|
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
|
||||||
if (_binding != null) {
|
if (_binding != null) {
|
||||||
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
}
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
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.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.documentfile.provider.DocumentFile
|
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.DialogUserAgentBinding
|
||||||
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.PreferenceKeystore
|
||||||
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
import ani.dantotsu.settings.saving.internal.PreferencePackager
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import ani.dantotsu.util.customAlertDialog
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
@@ -94,38 +93,31 @@ class LoginFragment : Fragment() {
|
|||||||
val password = CharArray(16).apply { fill('0') }
|
val password = CharArray(16).apply { fill('0') }
|
||||||
|
|
||||||
// Inflate the dialog layout
|
// Inflate the dialog layout
|
||||||
val dialogView =
|
val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply {
|
||||||
LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null)
|
userAgentTextBox.hint = "Password"
|
||||||
dialogView.findViewById<TextInputEditText>(R.id.userAgentTextBox)?.hint = "Password"
|
subtitle.visibility = View.VISIBLE
|
||||||
val subtitleTextView = dialogView.findViewById<TextView>(R.id.subtitle)
|
subtitle.text = getString(R.string.enter_password_to_decrypt_file)
|
||||||
subtitleTextView?.visibility = View.VISIBLE
|
}
|
||||||
subtitleTextView?.text = "Enter your password to decrypt the file"
|
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup)
|
requireActivity().customAlertDialog().apply {
|
||||||
.setTitle("Enter Password")
|
setTitle("Enter Password")
|
||||||
.setView(dialogView)
|
setCustomView(dialogView.root)
|
||||||
.setPositiveButton("OK", null)
|
setPosButton(R.string.ok) {
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
val editText = dialogView.userAgentTextBox
|
||||||
|
if (editText.text?.isNotBlank() == true) {
|
||||||
|
editText.text?.toString()?.trim()?.toCharArray(password)
|
||||||
|
callback(password)
|
||||||
|
} else {
|
||||||
|
toast("Password cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegButton(R.string.cancel) {
|
||||||
password.fill('0')
|
password.fill('0')
|
||||||
dialog.dismiss()
|
|
||||||
callback(null)
|
callback(null)
|
||||||
}
|
}
|
||||||
.create()
|
}.show()
|
||||||
|
|
||||||
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() {
|
private fun restartApp() {
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistMangaViewModel
|
import ani.dantotsu.connections.anilist.AnilistMangaViewModel
|
||||||
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.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
@@ -35,6 +35,7 @@ import ani.dantotsu.snackString
|
|||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -93,7 +94,7 @@ class MangaFragment : Fragment() {
|
|||||||
var loading = true
|
var loading = true
|
||||||
if (model.notSet) {
|
if (model.notSet) {
|
||||||
model.notSet = false
|
model.notSet = false
|
||||||
model.searchResults = SearchResults(
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
"MANGA",
|
"MANGA",
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
onList = false,
|
onList = false,
|
||||||
@@ -102,7 +103,7 @@ class MangaFragment : Fragment() {
|
|||||||
sort = Anilist.sortBy[1]
|
sort = Anilist.sortBy[1]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
val popularAdaptor = MediaAdaptor(1, model.aniMangaSearchResults.results, requireActivity())
|
||||||
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
||||||
binding.mangaPageRecyclerView.adapter =
|
binding.mangaPageRecyclerView.adapter =
|
||||||
ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
||||||
@@ -134,10 +135,10 @@ class MangaFragment : Fragment() {
|
|||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.aniMangaSearchResults.hasNextPage && model.aniMangaSearchResults.results.isNotEmpty() && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(model.aniMangaSearchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,10 @@ class MangaFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
model.getPopularManga().observe(viewLifecycleOwner) {
|
model.getPopularManga().observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
mangaPageAdapter.updateTrendingManga(MediaAdaptor(0, it, requireActivity()), it)
|
mangaPageAdapter.updateTrendingManga(
|
||||||
|
MediaAdaptor(0, it, requireActivity()),
|
||||||
|
it
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.getPopularManhwa().observe(viewLifecycleOwner) {
|
model.getPopularManhwa().observe(viewLifecycleOwner) {
|
||||||
@@ -219,7 +223,7 @@ class MangaFragment : Fragment() {
|
|||||||
mangaPageAdapter.onIncludeListClick = { checked ->
|
mangaPageAdapter.onIncludeListClick = { checked ->
|
||||||
oldIncludeList = !checked
|
oldIncludeList = !checked
|
||||||
loading = true
|
loading = true
|
||||||
model.searchResults.results.clear()
|
model.aniMangaSearchResults.results.clear()
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = checked)
|
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = checked)
|
||||||
@@ -229,17 +233,17 @@ class MangaFragment : Fragment() {
|
|||||||
model.getPopular().observe(viewLifecycleOwner) {
|
model.getPopular().observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
if (oldIncludeList == (it.onList != false)) {
|
if (oldIncludeList == (it.onList != false)) {
|
||||||
val prev = model.searchResults.results.size
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
} else {
|
} else {
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
oldIncludeList = it.onList ?: true
|
oldIncludeList = it.onList ?: true
|
||||||
}
|
}
|
||||||
model.searchResults.onList = it.onList
|
model.aniMangaSearchResults.onList = it.onList
|
||||||
model.searchResults.hasNextPage = it.hasNextPage
|
model.aniMangaSearchResults.hasNextPage = it.hasNextPage
|
||||||
model.searchResults.page = it.page
|
model.aniMangaSearchResults.page = it.page
|
||||||
if (it.hasNextPage)
|
if (it.hasNextPage)
|
||||||
progressAdaptor.bar?.visibility = View.VISIBLE
|
progressAdaptor.bar?.visibility = View.VISIBLE
|
||||||
else {
|
else {
|
||||||
@@ -261,8 +265,9 @@ class MangaFragment : Fragment() {
|
|||||||
running = true
|
running = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Anilist.userid = PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
Anilist.userid =
|
||||||
?.toIntOrNull()
|
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
||||||
|
?.toIntOrNull()
|
||||||
if (Anilist.userid == null) {
|
if (Anilist.userid == null) {
|
||||||
getUserId(requireContext()) {
|
getUserId(requireContext()) {
|
||||||
load()
|
load()
|
||||||
@@ -274,15 +279,22 @@ class MangaFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.loaded = true
|
}
|
||||||
model.loadTrending()
|
model.loaded = true
|
||||||
model.loadAll()
|
val loadTrending = async(Dispatchers.IO) { model.loadTrending() }
|
||||||
|
val loadAll = async(Dispatchers.IO) { model.loadAll() }
|
||||||
|
val loadPopular = async(Dispatchers.IO) {
|
||||||
model.loadPopular(
|
model.loadPopular(
|
||||||
"MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
|
"MANGA",
|
||||||
PrefName.PopularMangaList
|
sort = Anilist.sortBy[1],
|
||||||
)
|
onList = PrefManager.getVal(PrefName.PopularAnimeList)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTrending.await()
|
||||||
|
loadAll.await()
|
||||||
|
loadPopular.await()
|
||||||
|
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
_binding?.mangaRefresh?.isRefreshing = false
|
_binding?.mangaRefresh?.isRefreshing = false
|
||||||
running = false
|
running = false
|
||||||
|
|||||||
@@ -80,14 +80,23 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
|
|
||||||
updateAvatar()
|
updateAvatar()
|
||||||
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
trendingBinding.searchBar.hint = "MANGA"
|
trendingBinding.searchBar.hint = binding.root.context.getString(R.string.search)
|
||||||
trendingBinding.searchBarText.setOnClickListener {
|
trendingBinding.searchBarText.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
val context = binding.root.context
|
||||||
it.context,
|
if (PrefManager.getVal(PrefName.AniMangaSearchDirect) && Anilist.token != null) {
|
||||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
ContextCompat.startActivity(
|
||||||
null
|
context,
|
||||||
)
|
Intent(context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||||
@@ -257,10 +266,10 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
adaptor: MediaAdaptor,
|
adaptor: MediaAdaptor,
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
progress: View,
|
progress: View,
|
||||||
title: View ,
|
title: View,
|
||||||
more: View ,
|
more: View,
|
||||||
string: String,
|
string: String,
|
||||||
media : MutableList<Media>
|
media: MutableList<Media>
|
||||||
) {
|
) {
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
recyclerView.adapter = adaptor
|
recyclerView.adapter = adaptor
|
||||||
@@ -296,8 +305,8 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
|
|
||||||
fun updateNotificationCount() {
|
fun updateNotificationCount() {
|
||||||
if (this::binding.isInitialized) {
|
if (this::binding.isInitialized) {
|
||||||
trendingBinding.notificationCount.visibility =
|
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
app/src/main/java/ani/dantotsu/home/SearchBottomSheet.kt
Normal file
74
app/src/main/java/ani/dantotsu/home/SearchBottomSheet.kt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
|
||||||
|
import ani.dantotsu.databinding.BottomSheetSearchBinding
|
||||||
|
import ani.dantotsu.media.SearchActivity
|
||||||
|
|
||||||
|
class SearchBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
private var _binding: BottomSheetSearchBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = BottomSheetSearchBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.animeSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.ANIME)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.mangaSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.MANGA)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.characterSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.CHARACTER)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.staffSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.STAFF)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.studioSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.STUDIO)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.userSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.USER)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startActivity(context: Context, type: SearchType) {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, SearchActivity::class.java).putExtra("type", type.toAnilistString()),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = SearchBottomSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
|
|||||||
|
|
||||||
fun setColor(int: Int) {
|
fun setColor(int: Int) {
|
||||||
paint.color = if (int < booleanList.size && booleanList[int]) {
|
paint.color = if (int < booleanList.size && booleanList[int]) {
|
||||||
Color.GRAY
|
Color.GRAY
|
||||||
} else {
|
} else {
|
||||||
if (isUser) secondColor else primaryColor
|
if (isUser) secondColor else primaryColor
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
|
|||||||
} else {
|
} else {
|
||||||
val effectiveAngle = totalAngle / parts
|
val effectiveAngle = totalAngle / parts
|
||||||
for (i in 0 until parts) {
|
for (i in 0 until parts) {
|
||||||
val startAngle = i * (effectiveAngle + gapAngle) -90f
|
val startAngle = i * (effectiveAngle + gapAngle) - 90f
|
||||||
path.reset()
|
path.reset()
|
||||||
path.addArc(
|
path.addArc(
|
||||||
centerX - radius,
|
centerX - radius,
|
||||||
@@ -74,7 +74,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setParts(parts: Int, list : List<Boolean> = mutableListOf(), isUser: Boolean) {
|
fun setParts(parts: Int, list: List<Boolean> = mutableListOf(), isUser: Boolean) {
|
||||||
this.parts = parts
|
this.parts = parts
|
||||||
this.booleanList = list
|
this.booleanList = list
|
||||||
this.isUser = isUser
|
this.isUser = isUser
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import androidx.core.view.updateLayoutParams
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.api.Activity
|
import ani.dantotsu.connections.anilist.api.Activity
|
||||||
import ani.dantotsu.databinding.ActivityStatusBinding
|
import ani.dantotsu.databinding.ActivityStatusBinding
|
||||||
import ani.dantotsu.initActivity
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.home.status.listener.StoriesCallback
|
import ani.dantotsu.home.status.listener.StoriesCallback
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.profile.User
|
import ani.dantotsu.profile.User
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
|
||||||
class StatusActivity : AppCompatActivity(), StoriesCallback {
|
class StatusActivity : AppCompatActivity(), StoriesCallback {
|
||||||
private lateinit var activity: ArrayList<User>
|
private lateinit var activity: ArrayList<User>
|
||||||
@@ -44,12 +45,20 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
|
|||||||
|
|
||||||
val key = "activities"
|
val key = "activities"
|
||||||
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
||||||
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity )
|
if (activity.getOrNull(position) != null) {
|
||||||
val startIndex = if ( startFrom > 0) startFrom else 0
|
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
|
||||||
binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1)
|
val startIndex = if (startFrom > 0) startFrom else 0
|
||||||
|
binding.stories.setStoriesList(
|
||||||
|
activityList = activity[position].activity,
|
||||||
|
startIndex = startIndex + 1
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Logger.log("index out of bounds for position $position of size ${activity.size}")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findFirstNonMatch(watchedActivity: Set<Int>, activity: List<Activity>): Int {
|
private fun findFirstNonMatch(watchedActivity: Set<Int>, activity: List<Activity>): Int {
|
||||||
for (activityItem in activity) {
|
for (activityItem in activity) {
|
||||||
if (activityItem.id !in watchedActivity) {
|
if (activityItem.id !in watchedActivity) {
|
||||||
@@ -58,13 +67,16 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
|
|||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
binding.stories.pause()
|
binding.stories.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
binding.stories.resume()
|
if (hasWindowFocus())
|
||||||
|
binding.stories.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
@@ -75,15 +87,16 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
|
|||||||
binding.stories.pause()
|
binding.stories.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStoriesEnd() {
|
override fun onStoriesEnd() {
|
||||||
position += 1
|
position += 1
|
||||||
if (position < activity.size) {
|
if (position < activity.size) {
|
||||||
val key = "activities"
|
val key = "activities"
|
||||||
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
||||||
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity )
|
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
|
||||||
val startIndex= if ( startFrom > 0) startFrom else 0
|
val startIndex = if (startFrom > 0) startFrom else 0
|
||||||
binding.stories.startAnimation(slideOutLeft)
|
binding.stories.startAnimation(slideOutLeft)
|
||||||
binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1)
|
binding.stories.setStoriesList(activity[position].activity, startIndex + 1)
|
||||||
binding.stories.startAnimation(slideInRight)
|
binding.stories.startAnimation(slideInRight)
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
@@ -92,18 +105,19 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
|
|||||||
|
|
||||||
override fun onStoriesStart() {
|
override fun onStoriesStart() {
|
||||||
position -= 1
|
position -= 1
|
||||||
if (position >= 0) {
|
if (position >= 0 && activity[position].activity.isNotEmpty()) {
|
||||||
val key = "activities"
|
val key = "activities"
|
||||||
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
|
||||||
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity )
|
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
|
||||||
val startIndex = if ( startFrom > 0) startFrom else 0
|
val startIndex = if (startFrom > 0) startFrom else 0
|
||||||
binding.stories.startAnimation(slideOutRight)
|
binding.stories.startAnimation(slideOutRight)
|
||||||
binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1)
|
binding.stories.setStoriesList(activity[position].activity, startIndex + 1)
|
||||||
binding.stories.startAnimation(slideInLeft)
|
binding.stories.startAnimation(slideInLeft)
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var user: ArrayList<User> = arrayListOf()
|
var user: ArrayList<User> = arrayListOf()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import ani.dantotsu.profile.ProfileActivity
|
|||||||
import ani.dantotsu.profile.User
|
import ani.dantotsu.profile.User
|
||||||
import ani.dantotsu.profile.UsersDialogFragment
|
import ani.dantotsu.profile.UsersDialogFragment
|
||||||
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
||||||
|
import ani.dantotsu.profile.activity.RepliesBottomDialog
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
@@ -48,7 +49,6 @@ import kotlin.math.abs
|
|||||||
class Stories @JvmOverloads constructor(
|
class Stories @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener {
|
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener {
|
||||||
private lateinit var activity: FragmentActivity
|
|
||||||
private lateinit var binding: FragmentStatusBinding
|
private lateinit var binding: FragmentStatusBinding
|
||||||
private lateinit var activityList: List<Activity>
|
private lateinit var activityList: List<Activity>
|
||||||
private lateinit var storiesListener: StoriesCallback
|
private lateinit var storiesListener: StoriesCallback
|
||||||
@@ -74,16 +74,14 @@ class Stories @JvmOverloads constructor(
|
|||||||
|
|
||||||
if (context is StoriesCallback) storiesListener = context as StoriesCallback
|
if (context is StoriesCallback) storiesListener = context as StoriesCallback
|
||||||
|
|
||||||
binding.leftTouchPanel.setOnTouchListener(this)
|
binding.touchPanel.setOnTouchListener(this)
|
||||||
binding.rightTouchPanel.setOnTouchListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun setStoriesList(
|
fun setStoriesList(
|
||||||
activityList: List<Activity>, activity: FragmentActivity, startIndex: Int = 1
|
activityList: List<Activity>, startIndex: Int = 1
|
||||||
) {
|
) {
|
||||||
this.activityList = activityList
|
this.activityList = activityList
|
||||||
this.activity = activity
|
|
||||||
this.storyIndex = startIndex
|
this.storyIndex = startIndex
|
||||||
addLoadingViews(activityList)
|
addLoadingViews(activityList)
|
||||||
}
|
}
|
||||||
@@ -264,50 +262,6 @@ class Stories @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private var startClickTime = 0L
|
|
||||||
private var startX = 0f
|
|
||||||
private var startY = 0f
|
|
||||||
private var isLongPress = false
|
|
||||||
private val swipeThreshold = 100
|
|
||||||
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
|
|
||||||
val maxClickDuration = 200
|
|
||||||
when (event?.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
startX = event.x
|
|
||||||
startY = event.y
|
|
||||||
startClickTime = Calendar.getInstance().timeInMillis
|
|
||||||
pause()
|
|
||||||
isLongPress = false
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
val deltaX = event.x - startX
|
|
||||||
val deltaY = event.y - startY
|
|
||||||
if (!isLongPress && (abs(deltaX) > swipeThreshold || abs(deltaY) > swipeThreshold)) {
|
|
||||||
isLongPress = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
|
|
||||||
if (clickDuration < maxClickDuration && !isLongPress) {
|
|
||||||
when (view?.id) {
|
|
||||||
R.id.leftTouchPanel -> leftPanelTouch()
|
|
||||||
R.id.rightTouchPanel -> rightPanelTouch()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resume()
|
|
||||||
}
|
|
||||||
val deltaX = event.x - startX
|
|
||||||
if (abs(deltaX) > swipeThreshold) {
|
|
||||||
if (deltaX > 0) onStoriesPrevious()
|
|
||||||
else onStoriesCompleted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rightPanelTouch() {
|
private fun rightPanelTouch() {
|
||||||
Logger.log("rightPanelTouch: $storyIndex")
|
Logger.log("rightPanelTouch: $storyIndex")
|
||||||
if (storyIndex == activityList.size) {
|
if (storyIndex == activityList.size) {
|
||||||
@@ -359,6 +313,7 @@ class Stories @JvmOverloads constructor(
|
|||||||
timer.resume()
|
timer.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun loadStory(story: Activity) {
|
private fun loadStory(story: Activity) {
|
||||||
val key = "activities"
|
val key = "activities"
|
||||||
val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id))
|
val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id))
|
||||||
@@ -374,6 +329,15 @@ class Stories @JvmOverloads constructor(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.textActivity.setOnTouchListener { v, event ->
|
||||||
|
onTouchView(v, event, true)
|
||||||
|
v.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
binding.textActivityContainer.setOnTouchListener { v, event ->
|
||||||
|
onTouchView(v, event, true)
|
||||||
|
v.onTouchEvent(event)
|
||||||
|
}
|
||||||
fun visible(isList: Boolean) {
|
fun visible(isList: Boolean) {
|
||||||
binding.textActivity.isVisible = !isList
|
binding.textActivity.isVisible = !isList
|
||||||
binding.textActivityContainer.isVisible = !isList
|
binding.textActivityContainer.isVisible = !isList
|
||||||
@@ -397,15 +361,17 @@ class Stories @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ${story.progress ?: story.media?.title?.userPreferred} " +
|
} ${story.progress ?: story.media?.title?.userPreferred} " +
|
||||||
if (
|
if (
|
||||||
story.status?.contains("completed") == false &&
|
story.status?.contains("completed") == false &&
|
||||||
!story.status.contains("plans") &&
|
!story.status.contains("plans") &&
|
||||||
!story.status.contains("repeating")
|
!story.status.contains("repeating") &&
|
||||||
) {
|
!story.status.contains("paused") &&
|
||||||
"of ${story.media?.title?.userPreferred}"
|
!story.status.contains("dropped")
|
||||||
} else {
|
) {
|
||||||
""
|
"of ${story.media?.title?.userPreferred}"
|
||||||
}
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
binding.infoText.text = text
|
binding.infoText.text = text
|
||||||
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
|
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
|
||||||
blurImage(
|
blurImage(
|
||||||
@@ -421,7 +387,7 @@ class Stories @JvmOverloads constructor(
|
|||||||
story.media?.id
|
story.media?.id
|
||||||
),
|
),
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
activity,
|
(it.context as FragmentActivity),
|
||||||
binding.coverImage,
|
binding.coverImage,
|
||||||
ViewCompat.getTransitionName(binding.coverImage)!!
|
ViewCompat.getTransitionName(binding.coverImage)!!
|
||||||
).toBundle()
|
).toBundle()
|
||||||
@@ -455,22 +421,21 @@ class Stories @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
val likeColor = ContextCompat.getColor(context, R.color.yt_red)
|
val likeColor = ContextCompat.getColor(context, R.color.yt_red)
|
||||||
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
|
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
|
||||||
|
binding.replyCount.text = story.replyCount.toString()
|
||||||
|
binding.activityReplies.setColorFilter(ContextCompat.getColor(context, R.color.bg_opp))
|
||||||
binding.activityRepliesContainer.setOnClickListener {
|
binding.activityRepliesContainer.setOnClickListener {
|
||||||
RepliesBottomDialog.newInstance(story.id)
|
RepliesBottomDialog.newInstance(story.id)
|
||||||
.show(activity.supportFragmentManager, "replies")
|
.show((it.context as FragmentActivity).supportFragmentManager, "replies")
|
||||||
}
|
}
|
||||||
binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor)
|
binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor)
|
||||||
binding.replyCount.text = story.replyCount.toString()
|
|
||||||
binding.activityLikeCount.text = story.likeCount.toString()
|
binding.activityLikeCount.text = story.likeCount.toString()
|
||||||
binding.activityReplies.setColorFilter(ContextCompat.getColor(context, R.color.bg_opp))
|
|
||||||
binding.activityLikeContainer.setOnClickListener {
|
binding.activityLikeContainer.setOnClickListener {
|
||||||
like()
|
like()
|
||||||
}
|
}
|
||||||
binding.activityLikeContainer.setOnLongClickListener {
|
binding.activityLikeContainer.setOnLongClickListener {
|
||||||
val context = activity
|
|
||||||
UsersDialogFragment().apply {
|
UsersDialogFragment().apply {
|
||||||
userList(userList)
|
userList(userList)
|
||||||
show(context.supportFragmentManager, "dialog")
|
show((it.context as FragmentActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -484,7 +449,7 @@ class Stories @JvmOverloads constructor(
|
|||||||
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
|
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
|
||||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val res = Anilist.query.toggleLike(story.id, "ACTIVITY")
|
val res = Anilist.mutation.toggleLike(story.id, "ACTIVITY")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
if (story.isLiked == true) {
|
if (story.isLiked == true) {
|
||||||
@@ -502,4 +467,73 @@ class Stories @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var startClickTime = 0L
|
||||||
|
private var startX = 0f
|
||||||
|
private var startY = 0f
|
||||||
|
private var isLongPress = false
|
||||||
|
private val swipeThreshold = 100
|
||||||
|
override fun onTouch(view: View, event: MotionEvent): Boolean {
|
||||||
|
onTouchView(view, event)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onTouchView(view: View, event: MotionEvent, isText: Boolean = false) {
|
||||||
|
val maxClickDuration = 200
|
||||||
|
val screenWidth = view.width
|
||||||
|
val leftHalf = screenWidth / 2
|
||||||
|
val leftQuarter = screenWidth * 0.15
|
||||||
|
val rightQuarter = screenWidth * 0.85
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
startX = event.x
|
||||||
|
startY = event.y
|
||||||
|
startClickTime = Calendar.getInstance().timeInMillis
|
||||||
|
pause()
|
||||||
|
isLongPress = false
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val deltaX = event.x - startX
|
||||||
|
val deltaY = event.y - startY
|
||||||
|
if (!isLongPress && (abs(deltaX) > swipeThreshold || abs(deltaY) > swipeThreshold)) {
|
||||||
|
isLongPress = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
|
||||||
|
if (isText) {
|
||||||
|
if (clickDuration < maxClickDuration && !isLongPress) {
|
||||||
|
if (event.x < leftQuarter) {
|
||||||
|
leftPanelTouch()
|
||||||
|
} else if (event.x > rightQuarter) {
|
||||||
|
rightPanelTouch()
|
||||||
|
} else {
|
||||||
|
resume()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resume()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (clickDuration < maxClickDuration && !isLongPress) {
|
||||||
|
if (event.x < leftHalf) {
|
||||||
|
leftPanelTouch()
|
||||||
|
} else {
|
||||||
|
rightPanelTouch()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val deltaX = event.x - startX
|
||||||
|
val deltaY = event.y - startY
|
||||||
|
if (abs(deltaX) > swipeThreshold && !(abs(deltaY) > 10)) {
|
||||||
|
if (deltaX > 0) onStoriesPrevious()
|
||||||
|
else onStoriesCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.home.status
|
package ani.dantotsu.home.status
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -15,6 +14,8 @@ import ani.dantotsu.profile.ProfileActivity
|
|||||||
import ani.dantotsu.profile.User
|
import ani.dantotsu.profile.User
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.util.ActivityMarkdownCreator
|
||||||
|
|
||||||
class UserStatusAdapter(private val user: ArrayList<User>) :
|
class UserStatusAdapter(private val user: ArrayList<User>) :
|
||||||
RecyclerView.Adapter<UserStatusAdapter.UsersViewHolder>() {
|
RecyclerView.Adapter<UserStatusAdapter.UsersViewHolder>() {
|
||||||
@@ -23,6 +24,10 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
|
|||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
|
if (user[bindingAdapterPosition].activity.isEmpty()) {
|
||||||
|
snackString("No activity")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
StatusActivity.user = user
|
StatusActivity.user = user
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
itemView.context,
|
itemView.context,
|
||||||
@@ -34,14 +39,23 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
itemView.setOnLongClickListener {
|
itemView.setOnLongClickListener {
|
||||||
ContextCompat.startActivity(
|
if (user[bindingAdapterPosition].id == Anilist.userid) {
|
||||||
itemView.context,
|
ContextCompat.startActivity(
|
||||||
Intent(
|
|
||||||
itemView.context,
|
itemView.context,
|
||||||
ProfileActivity::class.java
|
Intent(itemView.context, ActivityMarkdownCreator::class.java)
|
||||||
).putExtra("userId", user[bindingAdapterPosition].id),
|
.putExtra("type", "activity"),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
itemView.context,
|
||||||
|
Intent(
|
||||||
|
itemView.context,
|
||||||
|
ProfileActivity::class.java
|
||||||
|
).putExtra("userId", user[bindingAdapterPosition].id),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,10 +76,15 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
|
|||||||
setAnimation(b.root.context, b.root)
|
setAnimation(b.root.context, b.root)
|
||||||
val user = user[position]
|
val user = user[position]
|
||||||
b.profileUserAvatar.loadImage(user.pfp)
|
b.profileUserAvatar.loadImage(user.pfp)
|
||||||
b.profileUserName.text = if (Anilist.userid == user.id) getAppString(R.string.your_story) else user.name
|
b.profileUserName.text =
|
||||||
|
if (Anilist.userid == user.id) getAppString(R.string.your_story) else user.name
|
||||||
val watchedActivity = PrefManager.getCustomVal<Set<Int>>("activities", setOf())
|
val watchedActivity = PrefManager.getCustomVal<Set<Int>>("activities", setOf())
|
||||||
val booleanList = user.activity.map { watchedActivity.contains(it.id) }
|
val booleanList = user.activity.map { watchedActivity.contains(it.id) }
|
||||||
b.profileUserStatusIndicator.setParts(user.activity.size, booleanList, user.id == Anilist.userid)
|
b.profileUserStatusIndicator.setParts(
|
||||||
|
user.activity.size,
|
||||||
|
booleanList,
|
||||||
|
user.id == Anilist.userid
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ data class Author(
|
|||||||
var name: String?,
|
var name: String?,
|
||||||
var image: String?,
|
var image: String?,
|
||||||
var role: String?,
|
var role: String?,
|
||||||
|
var age: Int? = null,
|
||||||
|
var yearsActive: List<Int>? = null,
|
||||||
|
var dateOfBirth: String? = null,
|
||||||
|
var dateOfDeath: String? = null,
|
||||||
|
var homeTown: String? = null,
|
||||||
var yearMedia: MutableMap<String, ArrayList<Media>>? = null,
|
var yearMedia: MutableMap<String, ArrayList<Media>>? = null,
|
||||||
var character: ArrayList<Character>? = null
|
var character: ArrayList<Character>? = null,
|
||||||
|
var isFav: Boolean = false
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
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
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -16,57 +18,127 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import ani.dantotsu.EmptyAdapter
|
import ani.dantotsu.EmptyAdapter
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityAuthorBinding
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistMutations
|
||||||
|
import ani.dantotsu.databinding.ActivityCharacterBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
|
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.SpoilerPlugin
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
|
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 io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
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 kotlin.math.abs
|
||||||
|
|
||||||
class AuthorActivity : AppCompatActivity() {
|
class AuthorActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
||||||
private lateinit var binding: ActivityAuthorBinding
|
private lateinit var binding: ActivityCharacterBinding
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
private val model: OtherDetailsViewModel by viewModels()
|
private val model: OtherDetailsViewModel by viewModels()
|
||||||
private var author: Author? = null
|
private lateinit var author: Author
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
|
|
||||||
|
private var screenWidth: Float = 0f
|
||||||
|
private val percent = 30
|
||||||
|
private var mMaxScrollSize = 0
|
||||||
|
private var isCollapsed = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
||||||
|
if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
|
|
||||||
val screenWidth = resources.displayMetrics.run { widthPixels / density }
|
val banner =
|
||||||
|
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen
|
||||||
|
|
||||||
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
banner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.studioRecycler.updatePadding(bottom = 64f.px + navBarHeight)
|
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
binding.studioTitle.isSelected = true
|
binding.characterCollapsing.minimumHeight = statusBarHeight
|
||||||
|
binding.characterCover.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
|
binding.characterRecyclerView.updatePadding(bottom = 64f.px + navBarHeight)
|
||||||
|
binding.characterTitle.isSelected = true
|
||||||
|
binding.characterAppBar.addOnOffsetChangedListener(this)
|
||||||
|
|
||||||
author = intent.getSerialized("author")
|
binding.characterClose.setOnClickListener {
|
||||||
binding.studioTitle.text = author?.name
|
|
||||||
|
|
||||||
binding.studioClose.setOnClickListener {
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
author = intent.getSerialized("author") ?: return
|
||||||
|
binding.characterTitle.text = author.name
|
||||||
|
binding.characterCoverImage.loadImage(author.image)
|
||||||
|
binding.characterCoverImage.setOnLongClickListener {
|
||||||
|
ImageViewDialog.newInstance(
|
||||||
|
this,
|
||||||
|
author.name,
|
||||||
|
author.image
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val link = "https://anilist.co/staff/${author.id}"
|
||||||
|
binding.characterShare.setOnClickListener {
|
||||||
|
val i = Intent(Intent.ACTION_SEND)
|
||||||
|
i.type = "text/plain"
|
||||||
|
i.putExtra(Intent.EXTRA_TEXT, link)
|
||||||
|
startActivity(Intent.createChooser(i, author.name))
|
||||||
|
}
|
||||||
|
binding.characterShare.setOnLongClickListener {
|
||||||
|
openLinkInBrowser(link)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
author.isFav =
|
||||||
|
Anilist.query.isUserFav(AnilistMutations.FavType.STAFF, author.id)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (author.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.characterFav.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (Anilist.mutation.toggleFav(AnilistMutations.FavType.CHARACTER, author.id)) {
|
||||||
|
author.isFav = !author.isFav
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (author.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("Failed to toggle favorite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
model.getAuthor().observe(this) {
|
model.getAuthor().observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
author = it
|
author = it
|
||||||
loaded = true
|
loaded = true
|
||||||
binding.studioProgressBar.visibility = View.GONE
|
binding.characterProgress.visibility = View.GONE
|
||||||
binding.studioRecycler.visibility = View.VISIBLE
|
binding.characterRecyclerView.visibility = View.VISIBLE
|
||||||
if (author!!.yearMedia.isNullOrEmpty()) {
|
if (author.yearMedia.isNullOrEmpty()) {
|
||||||
binding.studioRecycler.visibility = View.GONE
|
binding.characterRecyclerView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
val titlePosition = arrayListOf<Int>()
|
val titlePosition = arrayListOf<Int>()
|
||||||
val concatAdapter = ConcatAdapter()
|
val concatAdapter = ConcatAdapter()
|
||||||
val map = author!!.yearMedia ?: return@observe
|
val map = author.yearMedia ?: return@observe
|
||||||
val keys = map.keys.toTypedArray()
|
val keys = map.keys.toTypedArray()
|
||||||
var pos = 0
|
var pos = 0
|
||||||
|
|
||||||
@@ -80,6 +152,10 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val desc = createDesc(author)
|
||||||
|
val markWon = Markwon.builder(this).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
.usePlugin(SpoilerPlugin()).build()
|
||||||
|
markWon.setMarkdown(binding.authorCharacterDesc, desc)
|
||||||
for (i in keys.indices) {
|
for (i in keys.indices) {
|
||||||
val medias = map[keys[i]]!!
|
val medias = map[keys[i]]!!
|
||||||
val empty = if (medias.size >= 4) medias.size % 4 else 4 - medias.size
|
val empty = if (medias.size >= 4) medias.size % 4 else 4 - medias.size
|
||||||
@@ -90,18 +166,18 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true))
|
concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true))
|
||||||
concatAdapter.addAdapter(EmptyAdapter(empty))
|
concatAdapter.addAdapter(EmptyAdapter(empty))
|
||||||
}
|
}
|
||||||
binding.studioRecycler.adapter = concatAdapter
|
binding.characterRecyclerView.adapter = concatAdapter
|
||||||
binding.studioRecycler.layoutManager = gridLayoutManager
|
binding.characterRecyclerView.layoutManager = gridLayoutManager
|
||||||
|
|
||||||
binding.charactersRecycler.visibility = View.VISIBLE
|
binding.authorCharactersRecycler.visibility = View.VISIBLE
|
||||||
binding.charactersText.visibility = View.VISIBLE
|
binding.AuthorCharactersText.visibility = View.VISIBLE
|
||||||
binding.charactersRecycler.adapter =
|
binding.authorCharactersRecycler.adapter =
|
||||||
CharacterAdapter(author!!.character ?: arrayListOf())
|
CharacterAdapter(author.character ?: arrayListOf())
|
||||||
binding.charactersRecycler.layoutManager =
|
binding.authorCharactersRecycler.layoutManager =
|
||||||
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||||
if (author!!.character.isNullOrEmpty()) {
|
if (author.character.isNullOrEmpty()) {
|
||||||
binding.charactersRecycler.visibility = View.GONE
|
binding.authorCharactersRecycler.visibility = View.GONE
|
||||||
binding.charactersText.visibility = View.GONE
|
binding.AuthorCharactersText.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,14 +185,28 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
live.observe(this) {
|
live.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (author != null)
|
withContext(Dispatchers.IO) { model.loadAuthor(author) }
|
||||||
withContext(Dispatchers.IO) { model.loadAuthor(author!!) }
|
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createDesc(author: Author): String {
|
||||||
|
val age = if (author.age != null) "${getString(R.string.age)} ${author.age}" else ""
|
||||||
|
val yearsActive =
|
||||||
|
if (author.yearsActive != null) "${getString(R.string.years_active)} ${author.yearsActive}" else ""
|
||||||
|
val dob =
|
||||||
|
if (author.dateOfBirth != null) "${getString(R.string.birthday)} ${author.dateOfBirth}" else ""
|
||||||
|
val homeTown =
|
||||||
|
if (author.homeTown != null) "${getString(R.string.hometown)} ${author.homeTown}" else ""
|
||||||
|
val dod =
|
||||||
|
if (author.dateOfDeath != null) "${getString(R.string.date_of_death)} ${author.dateOfDeath}" else ""
|
||||||
|
|
||||||
|
return "$age $yearsActive $dob $homeTown $dod"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
if (Refresh.activity.containsKey(this.hashCode())) {
|
if (Refresh.activity.containsKey(this.hashCode())) {
|
||||||
Refresh.activity.remove(this.hashCode())
|
Refresh.activity.remove(this.hashCode())
|
||||||
@@ -125,7 +215,31 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
binding.studioProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
binding.characterProgress.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
||||||
|
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
||||||
|
val percentage = abs(i) * 100 / mMaxScrollSize
|
||||||
|
val cap = clamp((percent - percentage) / percent.toFloat(), 0f, 1f)
|
||||||
|
|
||||||
|
binding.characterCover.scaleX = 1f * cap
|
||||||
|
binding.characterCover.scaleY = 1f * cap
|
||||||
|
binding.characterCover.cardElevation = 32f * cap
|
||||||
|
|
||||||
|
binding.characterCover.visibility =
|
||||||
|
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
|
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
|
||||||
|
if (percentage >= percent && !isCollapsed) {
|
||||||
|
isCollapsed = true
|
||||||
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.nav_bg)
|
||||||
|
}
|
||||||
|
if (percentage <= percent && isCollapsed) {
|
||||||
|
isCollapsed = false
|
||||||
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ import ani.dantotsu.setAnimation
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class AuthorAdapter(
|
class AuthorAdapter(
|
||||||
private val authorList: ArrayList<Author>,
|
private val authorList: MutableList<Author>,
|
||||||
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
|
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
@@ -26,7 +26,7 @@ class AuthorAdapter(
|
|||||||
override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(binding.root.context, holder.binding.root)
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
val author = authorList[position]
|
val author = authorList.getOrNull(position) ?: return
|
||||||
binding.itemCompactRelation.text = author.role
|
binding.itemCompactRelation.text = author.role
|
||||||
binding.itemCompactImage.loadImage(author.image)
|
binding.itemCompactImage.loadImage(author.image)
|
||||||
binding.itemCompactTitle.text = author.name
|
binding.itemCompactTitle.text = author.name
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityListBinding
|
private lateinit var binding: ActivityListBinding
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
private var selectedTabIdx = 1
|
private var selectedTabIdx = 1
|
||||||
|
private var showOnlyLibrary = false
|
||||||
private val model: OtherDetailsViewModel by viewModels()
|
private val model: OtherDetailsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -38,8 +39,6 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface)
|
val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface)
|
||||||
val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary)
|
val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary)
|
||||||
val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline)
|
val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline)
|
||||||
@@ -79,6 +78,17 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
binding.listed.setOnClickListener {
|
||||||
|
showOnlyLibrary = !showOnlyLibrary
|
||||||
|
binding.listed.setImageResource(
|
||||||
|
if (showOnlyLibrary) R.drawable.ic_round_collections_bookmark_24
|
||||||
|
else R.drawable.ic_round_library_books_24
|
||||||
|
)
|
||||||
|
scope.launch {
|
||||||
|
model.loadCalendar(showOnlyLibrary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model.getCalendar().observe(this) {
|
model.getCalendar().observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
binding.listProgressBar.visibility = View.GONE
|
binding.listProgressBar.visibility = View.GONE
|
||||||
@@ -97,11 +107,10 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
live.observe(this) {
|
live.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) { model.loadCalendar() }
|
withContext(Dispatchers.IO) { model.loadCalendar(showOnlyLibrary) }
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.util.Pair
|
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.copyToClipboard
|
||||||
import ani.dantotsu.databinding.ItemCharacterBinding
|
import ani.dantotsu.databinding.ItemCharacterBinding
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class CharacterAdapter(
|
class CharacterAdapter(
|
||||||
private val characterList: ArrayList<Character>
|
private val characterList: MutableList<Character>
|
||||||
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
@@ -26,9 +27,8 @@ class CharacterAdapter(
|
|||||||
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)
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
val character = characterList[position]
|
val character = characterList.getOrNull(position) ?: return
|
||||||
val whitespace = "${character.role} "
|
val whitespace = "${if (character.role.lowercase() == "null") "" else character.role} "
|
||||||
character.voiceActor
|
|
||||||
binding.itemCompactRelation.text = whitespace
|
binding.itemCompactRelation.text = whitespace
|
||||||
binding.itemCompactImage.loadImage(character.image)
|
binding.itemCompactImage.loadImage(character.image)
|
||||||
binding.itemCompactTitle.text = character.name
|
binding.itemCompactTitle.text = character.name
|
||||||
@@ -55,6 +55,11 @@ class CharacterAdapter(
|
|||||||
).toBundle()
|
).toBundle()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
itemView.setOnLongClickListener {
|
||||||
|
copyToClipboard(
|
||||||
|
characterList[bindingAdapterPosition].name ?: ""
|
||||||
|
); true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.math.MathUtils.clamp
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -45,6 +46,11 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
private lateinit var character: Character
|
private lateinit var character: Character
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
|
|
||||||
|
private var isCollapsed = false
|
||||||
|
private val percent = 30
|
||||||
|
private var mMaxScrollSize = 0
|
||||||
|
private var screenWidth: Float = 0f
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -71,6 +77,11 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
binding.characterClose.setOnClickListener {
|
binding.characterClose.setOnClickListener {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.authorCharactersRecycler.isVisible = false
|
||||||
|
binding.AuthorCharactersText.isVisible = false
|
||||||
|
binding.authorCharacterDesc.isVisible = false
|
||||||
|
|
||||||
character = intent.getSerialized("character") ?: return
|
character = intent.getSerialized("character") ?: return
|
||||||
binding.characterTitle.text = character.name
|
binding.characterTitle.text = character.name
|
||||||
banner.loadImage(character.banner)
|
banner.loadImage(character.banner)
|
||||||
@@ -158,11 +169,6 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isCollapsed = false
|
|
||||||
private val percent = 30
|
|
||||||
private var mMaxScrollSize = 0
|
|
||||||
private var screenWidth: Float = 0f
|
|
||||||
|
|
||||||
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
||||||
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
||||||
val percentage = abs(i) * 100 / mMaxScrollSize
|
val percentage = abs(i) * 100 / mMaxScrollSize
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import android.view.ViewGroup
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.buildMarkwon
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.databinding.ItemCharacterDetailsBinding
|
import ani.dantotsu.databinding.ItemCharacterDetailsBinding
|
||||||
import ani.dantotsu.others.SpoilerPlugin
|
|
||||||
import io.noties.markwon.Markwon
|
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
|
||||||
|
|
||||||
class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
|
class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
|
||||||
RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() {
|
RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() {
|
||||||
@@ -24,7 +22,9 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
|
|||||||
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val desc =
|
val desc =
|
||||||
(if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") +
|
(if (character.id == 4004)
|
||||||
|
" \n" else "") +
|
||||||
|
(if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") +
|
||||||
(if (character.dateOfBirth.toString() != "")
|
(if (character.dateOfBirth.toString() != "")
|
||||||
"${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
|
"${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
|
||||||
(if (character.gender != "null")
|
(if (character.gender != "null")
|
||||||
@@ -41,8 +41,7 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
|
|||||||
} else "") + "\n" + character.description
|
} else "") + "\n" + character.description
|
||||||
|
|
||||||
binding.characterDesc.isTextSelectable
|
binding.characterDesc.isTextSelectable
|
||||||
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
val markWon = buildMarkwon(activity)
|
||||||
.usePlugin(SpoilerPlugin()).build()
|
|
||||||
markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||"))
|
markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||"))
|
||||||
binding.voiceActorRecycler.adapter = AuthorAdapter(character.voiceActor ?: arrayListOf())
|
binding.voiceActorRecycler.adapter = AuthorAdapter(character.voiceActor ?: arrayListOf())
|
||||||
binding.voiceActorRecycler.layoutManager = LinearLayoutManager(
|
binding.voiceActorRecycler.layoutManager = LinearLayoutManager(
|
||||||
|
|||||||
77
app/src/main/java/ani/dantotsu/media/HeaderInterface.kt
Normal file
77
app/src/main/java/ani/dantotsu/media/HeaderInterface.kt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
||||||
|
|
||||||
|
abstract class HeaderInterface : RecyclerView.Adapter<HeaderInterface.SearchHeaderViewHolder>() {
|
||||||
|
private val itemViewType = 6969
|
||||||
|
var search: Runnable? = null
|
||||||
|
var requestFocus: Runnable? = null
|
||||||
|
protected var textWatcher: TextWatcher? = null
|
||||||
|
protected lateinit var searchHistoryAdapter: SearchHistoryAdapter
|
||||||
|
protected lateinit var binding: ItemSearchHeaderBinding
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
||||||
|
val binding =
|
||||||
|
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return SearchHeaderViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.clearHistory.visibility = View.GONE
|
||||||
|
binding.searchHistoryList.visibility = View.GONE
|
||||||
|
binding.searchByImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fadeInAnimation(): Animation {
|
||||||
|
return AlphaAnimation(0f, 1f).apply {
|
||||||
|
duration = 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun fadeOutAnimation(): Animation {
|
||||||
|
return AlphaAnimation(1f, 0f).apply {
|
||||||
|
duration = 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun updateClearHistoryVisibility() {
|
||||||
|
binding.clearHistory.visibility =
|
||||||
|
if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addHistory() {
|
||||||
|
if (::searchHistoryAdapter.isInitialized && binding.searchBarText.text.toString()
|
||||||
|
.isNotBlank()
|
||||||
|
) searchHistoryAdapter.add(binding.searchBarText.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return itemViewType
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,22 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
import ani.dantotsu.connections.anilist.api.MediaEdge
|
import ani.dantotsu.connections.anilist.api.MediaEdge
|
||||||
import ani.dantotsu.connections.anilist.api.MediaList
|
import ani.dantotsu.connections.anilist.api.MediaList
|
||||||
|
import ani.dantotsu.connections.anilist.api.MediaStreamingEpisode
|
||||||
import ani.dantotsu.connections.anilist.api.MediaType
|
import ani.dantotsu.connections.anilist.api.MediaType
|
||||||
import ani.dantotsu.connections.anilist.api.Query
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
|
import ani.dantotsu.connections.mal.MAL
|
||||||
import ani.dantotsu.media.anime.Anime
|
import ani.dantotsu.media.anime.Anime
|
||||||
import ani.dantotsu.media.manga.Manga
|
import ani.dantotsu.media.manga.Manga
|
||||||
import ani.dantotsu.profile.User
|
import ani.dantotsu.profile.User
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import ani.dantotsu.connections.anilist.api.Media as ApiMedia
|
import ani.dantotsu.connections.anilist.api.Media as ApiMedia
|
||||||
|
|
||||||
@@ -76,7 +84,7 @@ data class Media(
|
|||||||
var nameMAL: String? = null,
|
var nameMAL: String? = null,
|
||||||
var shareLink: String? = null,
|
var shareLink: String? = null,
|
||||||
var selected: Selected? = null,
|
var selected: Selected? = null,
|
||||||
|
var streamingEpisodes: List<MediaStreamingEpisode>? = null,
|
||||||
var idKitsu: String? = null,
|
var idKitsu: String? = null,
|
||||||
|
|
||||||
var cameFromContinue: Boolean = false
|
var cameFromContinue: Boolean = false
|
||||||
@@ -129,6 +137,37 @@ data class Media(
|
|||||||
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
|
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Media?.deleteFromList(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
onSuccess: suspend () -> Unit,
|
||||||
|
onError: suspend (e: Exception) -> Unit,
|
||||||
|
onNotFound: suspend () -> Unit
|
||||||
|
) {
|
||||||
|
val id = this?.userListId
|
||||||
|
scope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
this@deleteFromList?.let { media ->
|
||||||
|
val _id = id ?: Anilist.query.userMediaDetails(media).userListId
|
||||||
|
_id?.let { listId ->
|
||||||
|
try {
|
||||||
|
Anilist.mutation.deleteList(listId)
|
||||||
|
MAL.query.deleteList(media.anime != null, media.idMAL)
|
||||||
|
|
||||||
|
val removeList = PrefManager.getCustomVal("removeList", setOf<Int>())
|
||||||
|
PrefManager.setCustomVal(
|
||||||
|
"removeList", removeList.minus(listId)
|
||||||
|
)
|
||||||
|
|
||||||
|
onSuccess()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError(e)
|
||||||
|
}
|
||||||
|
} ?: onNotFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun emptyMedia() = Media(
|
fun emptyMedia() = Media(
|
||||||
id = 0,
|
id = 0,
|
||||||
name = "No media found",
|
name = "No media found",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.animation.ObjectAnimator
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
@@ -12,6 +13,8 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -19,8 +22,10 @@ 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.WindowInsetsControllerCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updateMargins
|
import androidx.core.view.updateMargins
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -79,6 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
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)
|
val id = intent.getIntExtra("mediaId", -1)
|
||||||
@@ -109,6 +115,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
// Ui init
|
// Ui init
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|
||||||
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
}
|
}
|
||||||
@@ -132,10 +139,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
val navBarBottomMargin = if (resources.configuration.orientation ==
|
val navBarBottomMargin = if (resources.configuration.orientation ==
|
||||||
Configuration.ORIENTATION_LANDSCAPE
|
Configuration.ORIENTATION_LANDSCAPE
|
||||||
) 0 else navBarHeight
|
) 0 else navBarHeight
|
||||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
navBar.setPadding(
|
||||||
rightMargin = navBarRightMargin
|
navBar.paddingLeft,
|
||||||
bottomMargin = navBarBottomMargin
|
navBar.paddingTop,
|
||||||
}
|
navBar.paddingRight + navBarRightMargin,
|
||||||
|
navBar.paddingBottom + navBarBottomMargin
|
||||||
|
)
|
||||||
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 }
|
||||||
@@ -251,10 +260,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
fun total() {
|
fun total() {
|
||||||
val text = SpannableStringBuilder().apply {
|
val text = SpannableStringBuilder().apply {
|
||||||
|
|
||||||
val white = this@MediaDetailsActivity.getThemeColor(com.google.android.material.R.attr.colorOnBackground)
|
val white =
|
||||||
|
this@MediaDetailsActivity.getThemeColor(com.google.android.material.R.attr.colorOnBackground)
|
||||||
if (media.userStatus != null) {
|
if (media.userStatus != null) {
|
||||||
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
|
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
|
||||||
val colorSecondary = getThemeColor(com.google.android.material.R.attr.colorSecondary)
|
val colorSecondary =
|
||||||
|
getThemeColor(com.google.android.material.R.attr.colorSecondary)
|
||||||
bold { color(colorSecondary) { append("${media.userProgress}") } }
|
bold { color(colorSecondary) { append("${media.userProgress}") } }
|
||||||
append(
|
append(
|
||||||
if (media.anime != null) getString(R.string.episodes_out_of) else getString(
|
if (media.anime != null) getString(R.string.episodes_out_of) else getString(
|
||||||
@@ -293,7 +304,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
binding.mediaTotal.visibility = View.VISIBLE
|
binding.mediaTotal.visibility = View.VISIBLE
|
||||||
binding.mediaAddToList.text = userStatus
|
binding.mediaAddToList.text = userStatus
|
||||||
} else {
|
} else {
|
||||||
binding.mediaAddToList.setText(R.string.add)
|
binding.mediaAddToList.setText(R.string.add_list)
|
||||||
}
|
}
|
||||||
total()
|
total()
|
||||||
binding.mediaAddToList.setOnClickListener {
|
binding.mediaAddToList.setOnClickListener {
|
||||||
@@ -372,7 +383,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
|
navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
|
||||||
navBar.addTab(infoTab)
|
navBar.addTab(infoTab)
|
||||||
navBar.addTab(watchTab)
|
navBar.addTab(watchTab)
|
||||||
navBar.addTab(commentTab)
|
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1) {
|
||||||
|
navBar.addTab(commentTab)
|
||||||
|
}
|
||||||
if (model.continueMedia == null && media.cameFromContinue) {
|
if (model.continueMedia == null && media.cameFromContinue) {
|
||||||
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
|
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
|
||||||
selected = 1
|
selected = 1
|
||||||
@@ -424,7 +437,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
navBar.selectTabAt(selected)
|
if (::navBar.isInitialized)
|
||||||
|
navBar.selectTabAt(selected)
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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
|
||||||
import ani.dantotsu.others.AniSkip
|
import ani.dantotsu.others.AniSkip
|
||||||
|
import ani.dantotsu.others.Anify
|
||||||
import ani.dantotsu.others.Jikan
|
import ani.dantotsu.others.Jikan
|
||||||
import ani.dantotsu.others.Kitsu
|
import ani.dantotsu.others.Kitsu
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
@@ -100,6 +101,16 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val anifyEpisodes: MutableLiveData<Map<String, Episode>> =
|
||||||
|
MutableLiveData<Map<String, Episode>>(null)
|
||||||
|
|
||||||
|
fun getAnifyEpisodes(): LiveData<Map<String, Episode>> = anifyEpisodes
|
||||||
|
suspend fun loadAnifyEpisodes(s: Int) {
|
||||||
|
tryWithSuspend {
|
||||||
|
if (anifyEpisodes.value == null) anifyEpisodes.postValue(Anify.fetchAndParseMetadata(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> =
|
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> =
|
||||||
MutableLiveData<Map<String, Episode>>(null)
|
MutableLiveData<Map<String, Episode>>(null)
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ class MediaInfoFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility =
|
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility =
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
val infoNameRomanji = tripleTab + media.nameRomaji
|
val infoNameRomaji = tripleTab + media.nameRomaji
|
||||||
binding.mediaInfoNameRomaji.text = infoNameRomanji
|
binding.mediaInfoNameRomaji.text = infoNameRomaji
|
||||||
binding.mediaInfoNameRomaji.setOnLongClickListener {
|
binding.mediaInfoNameRomaji.setOnLongClickListener {
|
||||||
copyToClipboard(media.nameRomaji)
|
copyToClipboard(media.nameRomaji)
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -271,29 +271,23 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.mediaListDelete.setOnClickListener {
|
binding.mediaListDelete.setOnClickListener {
|
||||||
var id = media!!.userListId
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
media?.deleteFromList(scope, onSuccess = {
|
||||||
if (id != null) {
|
Refresh.all()
|
||||||
Anilist.mutation.deleteList(id!!)
|
snackString(getString(R.string.deleted_from_list))
|
||||||
MAL.query.deleteList(media?.anime != null, media?.idMAL)
|
dismissAllowingStateLoss()
|
||||||
} else {
|
}, onError = { e ->
|
||||||
val profile = Anilist.query.userMediaDetails(media!!)
|
withContext(Dispatchers.Main) {
|
||||||
profile.userListId?.let { listId ->
|
snackString(
|
||||||
id = listId
|
getString(
|
||||||
Anilist.mutation.deleteList(listId)
|
R.string.delete_fail_reason, e.message
|
||||||
MAL.query.deleteList(media?.anime != null, media?.idMAL)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}, onNotFound = {
|
||||||
PrefManager.setCustomVal("removeList", removeList.minus(media?.id))
|
snackString(getString(R.string.no_list_id))
|
||||||
}
|
})
|
||||||
if (id != null) {
|
|
||||||
Refresh.all()
|
|
||||||
snackString(getString(R.string.deleted_from_list))
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
} else {
|
|
||||||
snackString(getString(R.string.no_list_id))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,36 +63,24 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
|||||||
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 {
|
binding.mediaListDelete.setOnClickListener {
|
||||||
var id = media.userListId
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
scope.launch {
|
||||||
if (id != null) {
|
media.deleteFromList(scope, onSuccess = {
|
||||||
try {
|
|
||||||
Anilist.mutation.deleteList(id!!)
|
|
||||||
MAL.query.deleteList(media.anime != null, media.idMAL)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
snackString(getString(R.string.delete_fail_reason, 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()
|
Refresh.all()
|
||||||
snackString(getString(R.string.deleted_from_list))
|
snackString(getString(R.string.deleted_from_list))
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
} else {
|
}, onError = { e ->
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
snackString(
|
||||||
|
getString(
|
||||||
|
R.string.delete_fail_reason, e.message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, onNotFound = {
|
||||||
snackString(getString(R.string.no_list_id))
|
snackString(getString(R.string.no_list_id))
|
||||||
}
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ import ani.dantotsu.settings.saving.PrefManager
|
|||||||
import ani.dantotsu.settings.saving.PrefName
|
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 java.util.ArrayList
|
|
||||||
|
|
||||||
class MediaListViewActivity: AppCompatActivity() {
|
class MediaListViewActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMediaListViewBinding
|
private lateinit var binding: ActivityMediaListViewBinding
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -52,7 +51,8 @@ class MediaListViewActivity: AppCompatActivity() {
|
|||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(primaryTextColor)
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
val screenWidth = resources.displayMetrics.run { widthPixels / density }
|
val screenWidth = resources.displayMetrics.run { widthPixels / density }
|
||||||
val mediaList = passedMedia ?: intent.getSerialized("media") as? ArrayList<Media> ?: ArrayList()
|
val mediaList =
|
||||||
|
passedMedia ?: intent.getSerialized("media") as? ArrayList<Media> ?: ArrayList()
|
||||||
if (passedMedia != null) passedMedia = null
|
if (passedMedia != null) passedMedia = null
|
||||||
val view = PrefManager.getCustomVal("mediaView", 0)
|
val view = PrefManager.getCustomVal("mediaView", 0)
|
||||||
var mediaView: View = when (view) {
|
var mediaView: View = when (view) {
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ class MediaSocialAdapter(
|
|||||||
profileUserName.text = user.name
|
profileUserName.text = user.name
|
||||||
profileInfo.apply {
|
profileInfo.apply {
|
||||||
text = when (user.status) {
|
text = when (user.status) {
|
||||||
"CURRENT" -> if (type == "ANIME") getAppString(R.string.watching) else getAppString(R.string.reading)
|
"CURRENT" -> if (type == "ANIME") getAppString(R.string.watching) else getAppString(
|
||||||
|
R.string.reading
|
||||||
|
)
|
||||||
|
|
||||||
else -> user.status ?: ""
|
else -> user.status ?: ""
|
||||||
}
|
}
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
@@ -63,10 +66,12 @@ class MediaSocialAdapter(
|
|||||||
profileCompactProgressContainer.visibility = View.VISIBLE
|
profileCompactProgressContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
profileUserAvatar.setOnClickListener {
|
profileUserAvatar.setOnClickListener {
|
||||||
ContextCompat.startActivity(root.context,
|
ContextCompat.startActivity(
|
||||||
|
root.context,
|
||||||
Intent(root.context, ProfileActivity::class.java)
|
Intent(root.context, ProfileActivity::class.java)
|
||||||
.putExtra("userId", user.id),
|
.putExtra("userId", user.id),
|
||||||
null)
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
profileUserAvatarContainer.setOnLongClickListener {
|
profileUserAvatarContainer.setOnLongClickListener {
|
||||||
ImageViewDialog.newInstance(
|
ImageViewDialog.newInstance(
|
||||||
|
|||||||
@@ -26,25 +26,50 @@ class OtherDetailsViewModel : ViewModel() {
|
|||||||
if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m))
|
if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cachedAllCalendarData: Map<String, MutableList<Media>>? = null
|
||||||
|
private var cachedLibraryCalendarData: Map<String, MutableList<Media>>? = null
|
||||||
private val calendar: MutableLiveData<Map<String, MutableList<Media>>> = MutableLiveData(null)
|
private val calendar: MutableLiveData<Map<String, MutableList<Media>>> = MutableLiveData(null)
|
||||||
fun getCalendar(): LiveData<Map<String, MutableList<Media>>> = calendar
|
fun getCalendar(): LiveData<Map<String, MutableList<Media>>> = calendar
|
||||||
suspend fun loadCalendar() {
|
suspend fun loadCalendar(showOnlyLibrary: Boolean = false) {
|
||||||
val curr = System.currentTimeMillis() / 1000
|
if (cachedAllCalendarData == null || cachedLibraryCalendarData == null) {
|
||||||
val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6))
|
val curr = System.currentTimeMillis() / 1000
|
||||||
val df = DateFormat.getDateInstance(DateFormat.FULL)
|
val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6))
|
||||||
val map = mutableMapOf<String, MutableList<Media>>()
|
val df = DateFormat.getDateInstance(DateFormat.FULL)
|
||||||
val idMap = mutableMapOf<String, MutableList<Int>>()
|
val allMap = mutableMapOf<String, MutableList<Media>>()
|
||||||
res?.forEach {
|
val libraryMap = mutableMapOf<String, MutableList<Media>>()
|
||||||
val v = it.relation?.split(",")?.map { i -> i.toLong() }!!
|
val idMap = mutableMapOf<String, MutableList<Int>>()
|
||||||
val dateInfo = df.format(Date(v[1] * 1000))
|
|
||||||
val list = map.getOrPut(dateInfo) { mutableListOf() }
|
val userId = Anilist.userid ?: 0
|
||||||
val idList = idMap.getOrPut(dateInfo) { mutableListOf() }
|
val userLibrary = Anilist.query.getMediaLists(true, userId)
|
||||||
it.relation = "Episode ${v[0]}"
|
val libraryMediaIds = userLibrary.flatMap { it.value }.map { it.id }
|
||||||
if (!idList.contains(it.id)) {
|
|
||||||
idList.add(it.id)
|
res.forEach {
|
||||||
list.add(it)
|
val v = it.relation?.split(",")?.map { i -> i.toLong() }!!
|
||||||
|
val dateInfo = df.format(Date(v[1] * 1000))
|
||||||
|
val list = allMap.getOrPut(dateInfo) { mutableListOf() }
|
||||||
|
val libraryList = if (libraryMediaIds.contains(it.id)) {
|
||||||
|
libraryMap.getOrPut(dateInfo) { mutableListOf() }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val idList = idMap.getOrPut(dateInfo) { mutableListOf() }
|
||||||
|
it.relation = "Episode ${v[0]}"
|
||||||
|
if (!idList.contains(it.id)) {
|
||||||
|
idList.add(it.id)
|
||||||
|
list.add(it)
|
||||||
|
libraryList?.add(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cachedAllCalendarData = allMap
|
||||||
|
cachedLibraryCalendarData = libraryMap
|
||||||
}
|
}
|
||||||
calendar.postValue(map)
|
|
||||||
|
val cacheToUse: Map<String, MutableList<Media>> = if (showOnlyLibrary) {
|
||||||
|
cachedLibraryCalendarData ?: emptyMap()
|
||||||
|
} else {
|
||||||
|
cachedAllCalendarData ?: emptyMap()
|
||||||
|
}
|
||||||
|
calendar.postValue(cacheToUse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package ani.dantotsu.media
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.SpannableString
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -21,7 +20,7 @@ import ani.dantotsu.initActivity
|
|||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.util.MarkdownCreatorActivity
|
import ani.dantotsu.util.ActivityMarkdownCreator
|
||||||
import com.xwray.groupie.GroupieAdapter
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -59,7 +58,7 @@ class ReviewActivity : AppCompatActivity() {
|
|||||||
binding.followFilterButton.setOnClickListener {
|
binding.followFilterButton.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
this,
|
this,
|
||||||
Intent(this, MarkdownCreatorActivity::class.java)
|
Intent(this, ActivityMarkdownCreator::class.java)
|
||||||
.putExtra("type", "review"),
|
.putExtra("type", "review"),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
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.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.api.Query
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
import ani.dantotsu.databinding.ItemReviewsBinding
|
import ani.dantotsu.databinding.ItemReviewsBinding
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.openImage
|
import ani.dantotsu.openImage
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
@@ -40,7 +32,7 @@ class ReviewAdapter(
|
|||||||
binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium)
|
binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium)
|
||||||
binding.reviewText.text = review.summary
|
binding.reviewText.text = review.summary
|
||||||
binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
|
binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
|
||||||
val text = "[${review.score/ 10.0f}]"
|
val text = "[${review.score / 10.0f}]"
|
||||||
binding.reviewTag.text = text
|
binding.reviewTag.text = text
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
@@ -85,6 +77,7 @@ class ReviewAdapter(
|
|||||||
override fun initializeViewBinding(view: View): ItemReviewsBinding {
|
override fun initializeViewBinding(view: View): ItemReviewsBinding {
|
||||||
return ItemReviewsBinding.bind(view)
|
return ItemReviewsBinding.bind(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun userVote(type: String) {
|
private fun userVote(type: String) {
|
||||||
when (type) {
|
when (type) {
|
||||||
"NO_VOTE" -> {
|
"NO_VOTE" -> {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import ani.dantotsu.initActivity
|
|||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.openImage
|
import ani.dantotsu.openImage
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
@@ -52,8 +51,9 @@ class ReviewViewActivity : AppCompatActivity() {
|
|||||||
binding.userAvatar.loadImage(review.user?.avatar?.medium)
|
binding.userAvatar.loadImage(review.user?.avatar?.medium)
|
||||||
binding.userTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
|
binding.userTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
|
||||||
binding.userContainer.setOnClickListener {
|
binding.userContainer.setOnClickListener {
|
||||||
startActivity(Intent(this, ProfileActivity::class.java)
|
startActivity(
|
||||||
.putExtra("userId", review.user?.id)
|
Intent(this, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", review.user?.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
binding.userAvatar.openImage(
|
binding.userAvatar.openImage(
|
||||||
@@ -61,8 +61,9 @@ class ReviewViewActivity : AppCompatActivity() {
|
|||||||
review.user?.avatar?.medium ?: ""
|
review.user?.avatar?.medium ?: ""
|
||||||
)
|
)
|
||||||
binding.userAvatar.setOnClickListener {
|
binding.userAvatar.setOnClickListener {
|
||||||
startActivity(Intent(this, ProfileActivity::class.java)
|
startActivity(
|
||||||
.putExtra("userId", review.user?.id)
|
Intent(this, ProfileActivity::class.java)
|
||||||
|
.putExtra("userId", review.user?.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
binding.profileUserBio.settings.loadWithOverviewMode = true
|
binding.profileUserBio.settings.loadWithOverviewMode = true
|
||||||
|
|||||||
@@ -13,12 +13,18 @@ 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.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
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.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.CharacterSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.StaffSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.StudioSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.UserSearchResults
|
||||||
import ani.dantotsu.databinding.ActivitySearchBinding
|
import ani.dantotsu.databinding.ActivitySearchBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.UsersAdapter
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
@@ -35,14 +41,25 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
val model: AnilistSearch by viewModels()
|
val model: AnilistSearch by viewModels()
|
||||||
|
|
||||||
var style: Int = 0
|
var style: Int = 0
|
||||||
|
lateinit var searchType: SearchType
|
||||||
private var screenWidth: Float = 0f
|
private var screenWidth: Float = 0f
|
||||||
|
|
||||||
private lateinit var mediaAdaptor: MediaAdaptor
|
private lateinit var mediaAdaptor: MediaAdaptor
|
||||||
|
private lateinit var characterAdaptor: CharacterAdapter
|
||||||
|
private lateinit var studioAdaptor: StudioAdapter
|
||||||
|
private lateinit var staffAdaptor: AuthorAdapter
|
||||||
|
private lateinit var usersAdapter: UsersAdapter
|
||||||
|
|
||||||
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
|
private lateinit var headerAdaptor: HeaderInterface
|
||||||
|
|
||||||
|
lateinit var aniMangaResult: AniMangaSearchResults
|
||||||
|
lateinit var characterResult: CharacterSearchResults
|
||||||
|
lateinit var studioResult: StudioSearchResults
|
||||||
|
lateinit var staffResult: StaffSearchResults
|
||||||
|
lateinit var userResult: UserSearchResults
|
||||||
|
|
||||||
lateinit var result: SearchResults
|
|
||||||
lateinit var updateChips: (() -> Unit)
|
lateinit var updateChips: (() -> Unit)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -59,39 +76,117 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
bottom = navBarHeight + 80f.px
|
bottom = navBarHeight + 80f.px
|
||||||
)
|
)
|
||||||
|
|
||||||
style = PrefManager.getVal(PrefName.SearchStyle)
|
|
||||||
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
|
||||||
if (!listOnly!!) listOnly = null
|
|
||||||
|
|
||||||
val notSet = model.notSet
|
val notSet = model.notSet
|
||||||
if (model.notSet) {
|
searchType = SearchType.fromString(intent.getStringExtra("type") ?: "ANIME")
|
||||||
model.notSet = false
|
when (searchType) {
|
||||||
model.searchResults = SearchResults(
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
intent.getStringExtra("type") ?: "ANIME",
|
style = PrefManager.getVal(PrefName.SearchStyle)
|
||||||
isAdult = if (Anilist.adult) intent.getBooleanExtra("hentai", false) else false,
|
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
||||||
onList = listOnly,
|
if (!listOnly!!) listOnly = null
|
||||||
search = intent.getStringExtra("query"),
|
|
||||||
genres = intent.getStringExtra("genre")?.let { mutableListOf(it) },
|
if (model.notSet) {
|
||||||
tags = intent.getStringExtra("tag")?.let { mutableListOf(it) },
|
model.notSet = false
|
||||||
sort = intent.getStringExtra("sortBy"),
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
status = intent.getStringExtra("status"),
|
intent.getStringExtra("type") ?: "ANIME",
|
||||||
source = intent.getStringExtra("source"),
|
isAdult = if (Anilist.adult) intent.getBooleanExtra(
|
||||||
countryOfOrigin = intent.getStringExtra("country"),
|
"hentai",
|
||||||
season = intent.getStringExtra("season"),
|
false
|
||||||
seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra("seasonYear")
|
) else false,
|
||||||
?.toIntOrNull() else null,
|
onList = listOnly,
|
||||||
startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra("seasonYear")
|
search = intent.getStringExtra("query"),
|
||||||
?.toIntOrNull() else null,
|
genres = intent.getStringExtra("genre")?.let { mutableListOf(it) },
|
||||||
results = mutableListOf(),
|
tags = intent.getStringExtra("tag")?.let { mutableListOf(it) },
|
||||||
hasNextPage = false
|
sort = intent.getStringExtra("sortBy"),
|
||||||
)
|
status = intent.getStringExtra("status"),
|
||||||
|
source = intent.getStringExtra("source"),
|
||||||
|
countryOfOrigin = intent.getStringExtra("country"),
|
||||||
|
season = intent.getStringExtra("season"),
|
||||||
|
seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra(
|
||||||
|
"seasonYear"
|
||||||
|
)
|
||||||
|
?.toIntOrNull() else null,
|
||||||
|
startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra(
|
||||||
|
"seasonYear"
|
||||||
|
)
|
||||||
|
?.toIntOrNull() else null,
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
aniMangaResult = model.aniMangaSearchResults
|
||||||
|
mediaAdaptor =
|
||||||
|
MediaAdaptor(
|
||||||
|
style,
|
||||||
|
model.aniMangaSearchResults.results,
|
||||||
|
this,
|
||||||
|
matchParent = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.characterSearchResults = CharacterSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
characterResult = model.characterSearchResults
|
||||||
|
characterAdaptor = CharacterAdapter(model.characterSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.studioSearchResults = StudioSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
studioResult = model.studioSearchResults
|
||||||
|
studioAdaptor = StudioAdapter(model.studioSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.staffSearchResults = StaffSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
staffResult = model.staffSearchResults
|
||||||
|
staffAdaptor = AuthorAdapter(model.staffSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.userSearchResults = UserSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
userResult = model.userSearchResults
|
||||||
|
usersAdapter = UsersAdapter(model.userSearchResults.results, grid = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = model.searchResults
|
|
||||||
|
|
||||||
progressAdapter = ProgressAdapter(searched = model.searched)
|
progressAdapter = ProgressAdapter(searched = model.searched)
|
||||||
mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true)
|
headerAdaptor = if (searchType == SearchType.ANIME || searchType == SearchType.MANGA) {
|
||||||
headerAdaptor = SearchAdapter(this, model.searchResults.type)
|
SearchAdapter(this, searchType)
|
||||||
|
} else {
|
||||||
|
SupportingSearchAdapter(this, searchType)
|
||||||
|
}
|
||||||
|
|
||||||
val gridSize = (screenWidth / 120f).toInt()
|
val gridSize = (screenWidth / 120f).toInt()
|
||||||
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
||||||
@@ -108,7 +203,27 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
concatAdapter = ConcatAdapter(headerAdaptor, mediaAdaptor, progressAdapter)
|
concatAdapter = when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
ConcatAdapter(headerAdaptor, mediaAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
ConcatAdapter(headerAdaptor, characterAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
ConcatAdapter(headerAdaptor, studioAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
ConcatAdapter(headerAdaptor, staffAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
ConcatAdapter(headerAdaptor, usersAdapter, progressAdapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.searchRecyclerView.layoutManager = gridLayoutManager
|
binding.searchRecyclerView.layoutManager = gridLayoutManager
|
||||||
binding.searchRecyclerView.adapter = concatAdapter
|
binding.searchRecyclerView.adapter = concatAdapter
|
||||||
@@ -117,9 +232,9 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.hasNextPage(searchType) && model.resultsIsNotEmpty(searchType) && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(searchType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,34 +242,110 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
model.getSearch().observe(this) {
|
when (searchType) {
|
||||||
if (it != null) {
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
model.searchResults.apply {
|
model.getSearch<AniMangaSearchResults>(searchType).observe(this) {
|
||||||
onList = it.onList
|
if (it != null) {
|
||||||
isAdult = it.isAdult
|
model.aniMangaSearchResults.apply {
|
||||||
perPage = it.perPage
|
onList = it.onList
|
||||||
search = it.search
|
isAdult = it.isAdult
|
||||||
sort = it.sort
|
perPage = it.perPage
|
||||||
genres = it.genres
|
search = it.search
|
||||||
excludedGenres = it.excludedGenres
|
sort = it.sort
|
||||||
excludedTags = it.excludedTags
|
genres = it.genres
|
||||||
tags = it.tags
|
excludedGenres = it.excludedGenres
|
||||||
season = it.season
|
excludedTags = it.excludedTags
|
||||||
startYear = it.startYear
|
tags = it.tags
|
||||||
seasonYear = it.seasonYear
|
season = it.season
|
||||||
status = it.status
|
startYear = it.startYear
|
||||||
source = it.source
|
seasonYear = it.seasonYear
|
||||||
format = it.format
|
status = it.status
|
||||||
countryOfOrigin = it.countryOfOrigin
|
source = it.source
|
||||||
page = it.page
|
format = it.format
|
||||||
hasNextPage = it.hasNextPage
|
countryOfOrigin = it.countryOfOrigin
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
|
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val prev = model.searchResults.results.size
|
SearchType.CHARACTER -> {
|
||||||
model.searchResults.results.addAll(it.results)
|
model.getSearch<CharacterSearchResults>(searchType).observe(this) {
|
||||||
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
if (it != null) {
|
||||||
|
model.characterSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
progressAdapter.bar?.isVisible = it.hasNextPage
|
val prev = model.characterSearchResults.results.size
|
||||||
|
model.characterSearchResults.results.addAll(it.results)
|
||||||
|
characterAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
model.getSearch<StudioSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.studioSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.studioSearchResults.results.size
|
||||||
|
model.studioSearchResults.results.addAll(it.results)
|
||||||
|
studioAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
model.getSearch<StaffSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.staffSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.staffSearchResults.results.size
|
||||||
|
model.staffSearchResults.results.addAll(it.results)
|
||||||
|
staffAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
model.getSearch<UserSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.userSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.userSearchResults.results.size
|
||||||
|
model.userSearchResults.results.addAll(it.results)
|
||||||
|
usersAdapter.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +370,35 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
fun emptyMediaAdapter() {
|
fun emptyMediaAdapter() {
|
||||||
searchTimer.cancel()
|
searchTimer.cancel()
|
||||||
searchTimer.purge()
|
searchTimer.purge()
|
||||||
mediaAdaptor.notifyItemRangeRemoved(0, model.searchResults.results.size)
|
when (searchType) {
|
||||||
model.searchResults.results.clear()
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
mediaAdaptor.notifyItemRangeRemoved(0, model.aniMangaSearchResults.results.size)
|
||||||
|
model.aniMangaSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
characterAdaptor.notifyItemRangeRemoved(
|
||||||
|
0,
|
||||||
|
model.characterSearchResults.results.size
|
||||||
|
)
|
||||||
|
model.characterSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
studioAdaptor.notifyItemRangeRemoved(0, model.studioSearchResults.results.size)
|
||||||
|
model.studioSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
staffAdaptor.notifyItemRangeRemoved(0, model.staffSearchResults.results.size)
|
||||||
|
model.staffSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
usersAdapter.notifyItemRangeRemoved(0, model.userSearchResults.results.size)
|
||||||
|
model.userSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
progressAdapter.bar?.visibility = View.GONE
|
progressAdapter.bar?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,10 +406,30 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
private var loading = false
|
private var loading = false
|
||||||
fun search() {
|
fun search() {
|
||||||
headerAdaptor.setHistoryVisibility(false)
|
headerAdaptor.setHistoryVisibility(false)
|
||||||
val size = model.searchResults.results.size
|
val size = model.size(searchType)
|
||||||
model.searchResults.results.clear()
|
model.clearResults(searchType)
|
||||||
binding.searchRecyclerView.post {
|
binding.searchRecyclerView.post {
|
||||||
mediaAdaptor.notifyItemRangeRemoved(0, size)
|
when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
mediaAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
characterAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
studioAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
staffAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
usersAdapter.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progressAdapter.bar?.visibility = View.VISIBLE
|
progressAdapter.bar?.visibility = View.VISIBLE
|
||||||
@@ -202,7 +440,7 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
override fun run() {
|
override fun run() {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadSearch(result)
|
model.loadSearch(searchType)
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,8 +451,10 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun recycler() {
|
fun recycler() {
|
||||||
mediaAdaptor.type = style
|
if (searchType == SearchType.ANIME || searchType == SearchType.MANGA) {
|
||||||
mediaAdaptor.notifyDataSetChanged()
|
mediaAdaptor.type = style
|
||||||
|
mediaAdaptor.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var state: Parcelable? = null
|
var state: Parcelable? = null
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ 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 android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
@@ -22,8 +20,8 @@ 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.connections.anilist.AnilistSearch.SearchType
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
@@ -36,18 +34,11 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class SearchAdapter(private val activity: SearchActivity, private val type: SearchType) :
|
||||||
class SearchAdapter(private val activity: SearchActivity, private val type: String) :
|
HeaderInterface() {
|
||||||
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
|
||||||
private val itemViewType = 6969
|
|
||||||
var search: Runnable? = null
|
|
||||||
var requestFocus: Runnable? = null
|
|
||||||
private var textWatcher: TextWatcher? = null
|
|
||||||
private lateinit var searchHistoryAdapter: SearchHistoryAdapter
|
|
||||||
private lateinit var binding: ItemSearchHeaderBinding
|
|
||||||
|
|
||||||
private fun updateFilterTextViewDrawable() {
|
private fun updateFilterTextViewDrawable() {
|
||||||
val filterDrawable = when (activity.result.sort) {
|
val filterDrawable = when (activity.aniMangaResult.sort) {
|
||||||
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
||||||
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
||||||
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
||||||
@@ -60,18 +51,13 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0)
|
binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
|
||||||
val binding =
|
|
||||||
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
return SearchHeaderViewHolder(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
||||||
binding = holder.binding
|
binding = holder.binding
|
||||||
|
|
||||||
searchHistoryAdapter = SearchHistoryAdapter(type) {
|
searchHistoryAdapter = SearchHistoryAdapter(type) {
|
||||||
binding.searchBarText.setText(it)
|
binding.searchBarText.setText(it)
|
||||||
|
binding.searchBarText.setSelection(it.length)
|
||||||
}
|
}
|
||||||
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
|
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
|
||||||
binding.searchHistoryList.adapter = searchHistoryAdapter
|
binding.searchHistoryList.adapter = searchHistoryAdapter
|
||||||
@@ -79,6 +65,10 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
val imm: InputMethodManager =
|
val imm: InputMethodManager =
|
||||||
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
|
if (activity.searchType != SearchType.MANGA && activity.searchType != SearchType.ANIME) {
|
||||||
|
throw IllegalArgumentException("Invalid search type (wrong adapter)")
|
||||||
|
}
|
||||||
|
|
||||||
when (activity.style) {
|
when (activity.style) {
|
||||||
0 -> {
|
0 -> {
|
||||||
binding.searchResultGrid.alpha = 1f
|
binding.searchResultGrid.alpha = 1f
|
||||||
@@ -91,7 +81,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchBar.hint = activity.result.type
|
binding.searchBar.hint = activity.aniMangaResult.type
|
||||||
if (PrefManager.getVal(PrefName.Incognito)) {
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
val startIconDrawableRes = R.drawable.ic_incognito_24
|
val startIconDrawableRes = R.drawable.ic_incognito_24
|
||||||
val startIconDrawable: Drawable? =
|
val startIconDrawable: Drawable? =
|
||||||
@@ -99,11 +89,11 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
binding.searchBar.startIconDrawable = startIconDrawable
|
binding.searchBar.startIconDrawable = startIconDrawable
|
||||||
}
|
}
|
||||||
|
|
||||||
var adult = activity.result.isAdult
|
var adult = activity.aniMangaResult.isAdult
|
||||||
var listOnly = activity.result.onList
|
var listOnly = activity.aniMangaResult.onList
|
||||||
|
|
||||||
binding.searchBarText.removeTextChangedListener(textWatcher)
|
binding.searchBarText.removeTextChangedListener(textWatcher)
|
||||||
binding.searchBarText.setText(activity.result.search)
|
binding.searchBarText.setText(activity.aniMangaResult.search)
|
||||||
|
|
||||||
binding.searchAdultCheck.isChecked = adult
|
binding.searchAdultCheck.isChecked = adult
|
||||||
binding.searchList.isChecked = listOnly == true
|
binding.searchList.isChecked = listOnly == true
|
||||||
@@ -124,49 +114,49 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
popupMenu.setOnMenuItemClickListener { item ->
|
popupMenu.setOnMenuItemClickListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.sort_by_score -> {
|
R.id.sort_by_score -> {
|
||||||
activity.result.sort = Anilist.sortBy[0]
|
activity.aniMangaResult.sort = Anilist.sortBy[0]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_popular -> {
|
R.id.sort_by_popular -> {
|
||||||
activity.result.sort = Anilist.sortBy[1]
|
activity.aniMangaResult.sort = Anilist.sortBy[1]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_trending -> {
|
R.id.sort_by_trending -> {
|
||||||
activity.result.sort = Anilist.sortBy[2]
|
activity.aniMangaResult.sort = Anilist.sortBy[2]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_recent -> {
|
R.id.sort_by_recent -> {
|
||||||
activity.result.sort = Anilist.sortBy[3]
|
activity.aniMangaResult.sort = Anilist.sortBy[3]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_a_z -> {
|
R.id.sort_by_a_z -> {
|
||||||
activity.result.sort = Anilist.sortBy[4]
|
activity.aniMangaResult.sort = Anilist.sortBy[4]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_z_a -> {
|
R.id.sort_by_z_a -> {
|
||||||
activity.result.sort = Anilist.sortBy[5]
|
activity.aniMangaResult.sort = Anilist.sortBy[5]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_pure_pain -> {
|
R.id.sort_by_pure_pain -> {
|
||||||
activity.result.sort = Anilist.sortBy[6]
|
activity.aniMangaResult.sort = Anilist.sortBy[6]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
@@ -177,14 +167,20 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
popupMenu.show()
|
popupMenu.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
if (activity.result.type != "ANIME") {
|
if (activity.aniMangaResult.type != "ANIME") {
|
||||||
binding.searchByImage.visibility = View.GONE
|
binding.searchByImage.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.searchByImage.setOnClickListener {
|
binding.searchByImage.setOnClickListener {
|
||||||
activity.startActivity(Intent(activity, ImageSearchActivity::class.java))
|
activity.startActivity(Intent(activity, ImageSearchActivity::class.java))
|
||||||
}
|
}
|
||||||
|
binding.clearHistory.setOnClickListener {
|
||||||
|
it.startAnimation(fadeOutAnimation())
|
||||||
|
it.visibility = View.GONE
|
||||||
|
searchHistoryAdapter.clearHistory()
|
||||||
|
}
|
||||||
|
updateClearHistoryVisibility()
|
||||||
fun searchTitle() {
|
fun searchTitle() {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
search =
|
search =
|
||||||
if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
||||||
onList = listOnly
|
onList = listOnly
|
||||||
@@ -286,61 +282,12 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fadeOutAnimation(): Animation {
|
|
||||||
return AlphaAnimation(1f, 0f).apply {
|
|
||||||
duration = 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun addHistory() {
|
|
||||||
if (::searchHistoryAdapter.isInitialized &&
|
|
||||||
binding.searchBarText.text.toString().isNotBlank()
|
|
||||||
)
|
|
||||||
searchHistoryAdapter.add(binding.searchBarText.text.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
|
||||||
|
|
||||||
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root)
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return itemViewType
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SearchChipAdapter(
|
class SearchChipAdapter(
|
||||||
val activity: SearchActivity,
|
val activity: SearchActivity,
|
||||||
private val searchAdapter: SearchAdapter
|
private val searchAdapter: SearchAdapter
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
||||||
private var chips = activity.result.toChipList()
|
private var chips = activity.aniMangaResult.toChipList()
|
||||||
|
|
||||||
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
@@ -357,7 +304,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
holder.binding.root.apply {
|
holder.binding.root.apply {
|
||||||
text = chip.text.replace("_", " ")
|
text = chip.text.replace("_", " ")
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
activity.result.removeChip(chip)
|
activity.aniMangaResult.removeChip(chip)
|
||||||
update()
|
update()
|
||||||
activity.search()
|
activity.search()
|
||||||
searchAdapter.updateFilterTextViewDrawable()
|
searchAdapter.updateFilterTextViewDrawable()
|
||||||
@@ -367,7 +314,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun update() {
|
fun update() {
|
||||||
chips = activity.result.toChipList()
|
chips = activity.aniMangaResult.toChipList()
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
searchAdapter.updateFilterTextViewDrawable()
|
searchAdapter.updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
@@ -375,4 +322,3 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
override fun getItemCount(): Int = chips.size
|
override fun getItemCount(): Int = chips.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setSortByFilterImage() {
|
private fun setSortByFilterImage() {
|
||||||
val filterDrawable = when (activity.result.sort) {
|
val filterDrawable = when (activity.aniMangaResult.sort) {
|
||||||
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
||||||
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
||||||
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
||||||
@@ -71,10 +71,10 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resetSearchFilter() {
|
private fun resetSearchFilter() {
|
||||||
activity.result.sort = null
|
activity.aniMangaResult.sort = null
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_alt_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_alt_24)
|
||||||
startBounceZoomAnimation(binding.sortByFilter)
|
startBounceZoomAnimation(binding.sortByFilter)
|
||||||
activity.result.countryOfOrigin = null
|
activity.aniMangaResult.countryOfOrigin = null
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
|
|
||||||
@@ -98,10 +98,10 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
activity = requireActivity() as SearchActivity
|
activity = requireActivity() as SearchActivity
|
||||||
|
|
||||||
selectedGenres = activity.result.genres ?: mutableListOf()
|
selectedGenres = activity.aniMangaResult.genres ?: mutableListOf()
|
||||||
exGenres = activity.result.excludedGenres ?: mutableListOf()
|
exGenres = activity.aniMangaResult.excludedGenres ?: mutableListOf()
|
||||||
selectedTags = activity.result.tags ?: mutableListOf()
|
selectedTags = activity.aniMangaResult.tags ?: mutableListOf()
|
||||||
exTags = activity.result.excludedTags ?: mutableListOf()
|
exTags = activity.aniMangaResult.excludedTags ?: mutableListOf()
|
||||||
setSortByFilterImage()
|
setSortByFilterImage()
|
||||||
|
|
||||||
binding.resetSearchFilter.setOnClickListener {
|
binding.resetSearchFilter.setOnClickListener {
|
||||||
@@ -126,7 +126,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
resetSearchFilter()
|
resetSearchFilter()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
status =
|
status =
|
||||||
binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
source =
|
source =
|
||||||
@@ -135,7 +135,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
season = binding.searchSeason.text.toString().ifBlank { null }
|
season = binding.searchSeason.text.toString().ifBlank { null }
|
||||||
startYear = binding.searchYear.text.toString().toIntOrNull()
|
startYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
sort = activity.result.sort
|
sort = activity.aniMangaResult.sort
|
||||||
genres = selectedGenres
|
genres = selectedGenres
|
||||||
tags = selectedTags
|
tags = selectedTags
|
||||||
excludedGenres = exGenres
|
excludedGenres = exGenres
|
||||||
@@ -155,43 +155,43 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.sort_by_score -> {
|
R.id.sort_by_score -> {
|
||||||
activity.result.sort = Anilist.sortBy[0]
|
activity.aniMangaResult.sort = Anilist.sortBy[0]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_area_chart_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_area_chart_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_popular -> {
|
R.id.sort_by_popular -> {
|
||||||
activity.result.sort = Anilist.sortBy[1]
|
activity.aniMangaResult.sort = Anilist.sortBy[1]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_peak_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_peak_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_trending -> {
|
R.id.sort_by_trending -> {
|
||||||
activity.result.sort = Anilist.sortBy[2]
|
activity.aniMangaResult.sort = Anilist.sortBy[2]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_star_graph_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_star_graph_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_recent -> {
|
R.id.sort_by_recent -> {
|
||||||
activity.result.sort = Anilist.sortBy[3]
|
activity.aniMangaResult.sort = Anilist.sortBy[3]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_new_releases_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_new_releases_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_a_z -> {
|
R.id.sort_by_a_z -> {
|
||||||
activity.result.sort = Anilist.sortBy[4]
|
activity.aniMangaResult.sort = Anilist.sortBy[4]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_z_a -> {
|
R.id.sort_by_z_a -> {
|
||||||
activity.result.sort = Anilist.sortBy[5]
|
activity.aniMangaResult.sort = Anilist.sortBy[5]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24_reverse)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24_reverse)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_pure_pain -> {
|
R.id.sort_by_pure_pain -> {
|
||||||
activity.result.sort = Anilist.sortBy[6]
|
activity.aniMangaResult.sort = Anilist.sortBy[6]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_assist_walker_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_assist_walker_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
@@ -212,25 +212,25 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_china -> {
|
R.id.country_china -> {
|
||||||
activity.result.countryOfOrigin = "CN"
|
activity.aniMangaResult.countryOfOrigin = "CN"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_south_korea -> {
|
R.id.country_south_korea -> {
|
||||||
activity.result.countryOfOrigin = "KR"
|
activity.aniMangaResult.countryOfOrigin = "KR"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_japan -> {
|
R.id.country_japan -> {
|
||||||
activity.result.countryOfOrigin = "JP"
|
activity.aniMangaResult.countryOfOrigin = "JP"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_taiwan -> {
|
R.id.country_taiwan -> {
|
||||||
activity.result.countryOfOrigin = "TW"
|
activity.aniMangaResult.countryOfOrigin = "TW"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
@@ -241,18 +241,18 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.searchFilterApply.setOnClickListener {
|
binding.searchFilterApply.setOnClickListener {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
status = binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
status = binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
source = binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
|
source = binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
format = binding.searchFormat.text.toString().ifBlank { null }
|
format = binding.searchFormat.text.toString().ifBlank { null }
|
||||||
season = binding.searchSeason.text.toString().ifBlank { null }
|
season = binding.searchSeason.text.toString().ifBlank { null }
|
||||||
if (activity.result.type == "ANIME") {
|
if (activity.aniMangaResult.type == "ANIME") {
|
||||||
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
} else {
|
} else {
|
||||||
startYear = binding.searchYear.text.toString().toIntOrNull()
|
startYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
}
|
}
|
||||||
sort = activity.result.sort
|
sort = activity.aniMangaResult.sort
|
||||||
countryOfOrigin = activity.result.countryOfOrigin
|
countryOfOrigin = activity.aniMangaResult.countryOfOrigin
|
||||||
genres = selectedGenres
|
genres = selectedGenres
|
||||||
tags = selectedTags
|
tags = selectedTags
|
||||||
excludedGenres = exGenres
|
excludedGenres = exGenres
|
||||||
@@ -266,8 +266,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
val format =
|
val format =
|
||||||
if (activity.result.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
|
if (activity.aniMangaResult.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
|
||||||
binding.searchStatus.setText(activity.result.status?.replace("_", " "))
|
binding.searchStatus.setText(activity.aniMangaResult.status?.replace("_", " "))
|
||||||
binding.searchStatus.setAdapter(
|
binding.searchStatus.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
@@ -276,7 +276,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.searchSource.setText(activity.result.source?.replace("_", " "))
|
binding.searchSource.setText(activity.aniMangaResult.source?.replace("_", " "))
|
||||||
binding.searchSource.setAdapter(
|
binding.searchSource.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
@@ -285,19 +285,19 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.searchFormat.setText(activity.result.format)
|
binding.searchFormat.setText(activity.aniMangaResult.format)
|
||||||
binding.searchFormat.setAdapter(
|
binding.searchFormat.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
(if (activity.result.type == "ANIME") Anilist.animeFormats else Anilist.mangaFormats).toTypedArray()
|
(if (activity.aniMangaResult.type == "ANIME") Anilist.animeFormats else Anilist.mangaFormats).toTypedArray()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (activity.result.type == "ANIME") {
|
if (activity.aniMangaResult.type == "ANIME") {
|
||||||
binding.searchYear.setText(activity.result.seasonYear?.toString())
|
binding.searchYear.setText(activity.aniMangaResult.seasonYear?.toString())
|
||||||
} else {
|
} else {
|
||||||
binding.searchYear.setText(activity.result.startYear?.toString())
|
binding.searchYear.setText(activity.aniMangaResult.startYear?.toString())
|
||||||
}
|
}
|
||||||
binding.searchYear.setAdapter(
|
binding.searchYear.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
@@ -308,9 +308,9 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (activity.result.type == "MANGA") binding.searchSeasonCont.visibility = GONE
|
if (activity.aniMangaResult.type == "MANGA") binding.searchSeasonCont.visibility = GONE
|
||||||
else {
|
else {
|
||||||
binding.searchSeason.setText(activity.result.season)
|
binding.searchSeason.setText(activity.aniMangaResult.season)
|
||||||
binding.searchSeason.setAdapter(
|
binding.searchSeason.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
@@ -346,7 +346,9 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
|||||||
binding.searchGenresGrid.isChecked = false
|
binding.searchGenresGrid.isChecked = false
|
||||||
|
|
||||||
binding.searchFilterTags.adapter =
|
binding.searchFilterTags.adapter =
|
||||||
FilterChipAdapter(Anilist.tags?.get(activity.result.isAdult) ?: listOf()) { chip ->
|
FilterChipAdapter(
|
||||||
|
Anilist.tags?.get(activity.aniMangaResult.isAdult) ?: listOf()
|
||||||
|
) { chip ->
|
||||||
val tag = chip.text.toString()
|
val tag = chip.text.toString()
|
||||||
chip.isChecked = selectedTags.contains(tag)
|
chip.isChecked = selectedTags.contains(tag)
|
||||||
chip.isCloseIconVisible = exTags.contains(tag)
|
chip.isCloseIconVisible = exTags.contains(tag)
|
||||||
|
|||||||
@@ -7,48 +7,75 @@ import androidx.recyclerview.widget.DiffUtil
|
|||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
import ani.dantotsu.databinding.ItemSearchHistoryBinding
|
import ani.dantotsu.databinding.ItemSearchHistoryBinding
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefManager.asLiveStringSet
|
import ani.dantotsu.settings.saving.PrefManager.asLiveClass
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.settings.saving.SharedPreferenceStringSetLiveData
|
import ani.dantotsu.settings.saving.SharedPreferenceClassLiveData
|
||||||
import java.util.Locale
|
import java.io.Serializable
|
||||||
|
|
||||||
class SearchHistoryAdapter(private val type: String, private val searchClicked: (String) -> Unit) :
|
data class SearchHistory(val search: String, val time: Long) : Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchHistoryAdapter(type: SearchType, private val searchClicked: (String) -> Unit) :
|
||||||
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
|
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
|
||||||
DIFF_CALLBACK_INSTALLED
|
DIFF_CALLBACK_INSTALLED
|
||||||
) {
|
) {
|
||||||
private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null
|
private var searchHistoryLiveData: SharedPreferenceClassLiveData<List<SearchHistory>>? = null
|
||||||
private var searchHistory: MutableSet<String>? = null
|
private var searchHistory: MutableList<SearchHistory>? = null
|
||||||
private var historyType: PrefName = when (type.lowercase(Locale.ROOT)) {
|
private var historyType: PrefName = when (type) {
|
||||||
"anime" -> PrefName.AnimeSearchHistory
|
SearchType.ANIME -> PrefName.SortedAnimeSH
|
||||||
"manga" -> PrefName.MangaSearchHistory
|
SearchType.MANGA -> PrefName.SortedMangaSH
|
||||||
else -> throw IllegalArgumentException("Invalid type")
|
SearchType.CHARACTER -> PrefName.SortedCharacterSH
|
||||||
|
SearchType.STAFF -> PrefName.SortedStaffSH
|
||||||
|
SearchType.STUDIO -> PrefName.SortedStudioSH
|
||||||
|
SearchType.USER -> PrefName.SortedUserSH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableList<SearchHistory>?.sorted(): List<String>? =
|
||||||
|
this?.sortedByDescending { it.time }?.map { it.search }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
searchHistoryLiveData =
|
searchHistoryLiveData =
|
||||||
PrefManager.getLiveVal(historyType, mutableSetOf<String>()).asLiveStringSet()
|
PrefManager.getLiveVal(historyType, mutableListOf<SearchHistory>()).asLiveClass()
|
||||||
searchHistoryLiveData?.observeForever {
|
searchHistoryLiveData?.observeForever { data ->
|
||||||
searchHistory = it.toMutableSet()
|
searchHistory = data.toMutableList()
|
||||||
submitList(searchHistory?.toList())
|
submitList(searchHistory?.sorted())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: String) {
|
fun remove(item: String) {
|
||||||
searchHistory?.remove(item)
|
searchHistory?.let { list ->
|
||||||
|
list.removeAll { it.search == item }
|
||||||
|
}
|
||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
submitList(searchHistory?.toList())
|
submitList(searchHistory?.sorted())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(item: String) {
|
fun add(item: String) {
|
||||||
if (searchHistory?.contains(item) == true || item.isBlank()) return
|
val maxSize = 25
|
||||||
|
if (searchHistory?.any { it.search == item } == true || item.isBlank()) return
|
||||||
if (PrefManager.getVal(PrefName.Incognito)) return
|
if (PrefManager.getVal(PrefName.Incognito)) return
|
||||||
searchHistory?.add(item)
|
searchHistory?.add(SearchHistory(item, System.currentTimeMillis()))
|
||||||
submitList(searchHistory?.toList())
|
if ((searchHistory?.size ?: 0) > maxSize) {
|
||||||
|
searchHistory?.removeAt(
|
||||||
|
searchHistory?.sorted()?.lastIndex ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
submitList(searchHistory?.sorted())
|
||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearHistory() {
|
||||||
|
searchHistory?.clear()
|
||||||
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
|
submitList(searchHistory?.sorted())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
|
|||||||
@@ -5,5 +5,8 @@ import java.io.Serializable
|
|||||||
data class Studio(
|
data class Studio(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val isFavourite: Boolean?,
|
||||||
|
val favourites: Int?,
|
||||||
|
val imageUrl: String?,
|
||||||
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|||||||
65
app/src/main/java/ani/dantotsu/media/StudioAdapter.kt
Normal file
65
app/src/main/java/ani/dantotsu/media/StudioAdapter.kt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
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.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.copyToClipboard
|
||||||
|
import ani.dantotsu.databinding.ItemCharacterBinding
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.setAnimation
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class StudioAdapter(
|
||||||
|
private val studioList: MutableList<Studio>
|
||||||
|
) : RecyclerView.Adapter<StudioAdapter.StudioViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudioViewHolder {
|
||||||
|
val binding =
|
||||||
|
ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return StudioViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: StudioViewHolder, position: Int) {
|
||||||
|
val binding = holder.binding
|
||||||
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
|
val studio = studioList.getOrNull(position) ?: return
|
||||||
|
binding.itemCompactRelation.isVisible = false
|
||||||
|
binding.itemCompactImage.loadImage(studio.imageUrl)
|
||||||
|
binding.itemCompactTitle.text = studio.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = studioList.size
|
||||||
|
inner class StudioViewHolder(val binding: ItemCharacterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
val studio = studioList[bindingAdapterPosition]
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
itemView.context,
|
||||||
|
Intent(
|
||||||
|
itemView.context,
|
||||||
|
StudioActivity::class.java
|
||||||
|
).putExtra("studio", studio as Serializable),
|
||||||
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
|
itemView.context as Activity,
|
||||||
|
Pair.create(
|
||||||
|
binding.itemCompactImage,
|
||||||
|
ViewCompat.getTransitionName(binding.itemCompactImage)!!
|
||||||
|
),
|
||||||
|
).toBundle()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
itemView.setOnLongClickListener {
|
||||||
|
copyToClipboard(
|
||||||
|
studioList[bindingAdapterPosition].name
|
||||||
|
); true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import androidx.core.net.toUri
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
@@ -21,28 +23,32 @@ class SubtitleDownloader {
|
|||||||
suspend fun loadSubtitleType(url: String): SubtitleType =
|
suspend fun loadSubtitleType(url: String): SubtitleType =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
return@withContext try {
|
return@withContext try {
|
||||||
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
if (!url.startsWith("file")) {
|
||||||
val networkHelper = Injekt.get<NetworkHelper>()
|
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
||||||
val request = Request.Builder()
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
.url(url)
|
val request = Request.Builder()
|
||||||
.build()
|
.url(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
val response = networkHelper.client.newCall(request).execute()
|
val response = networkHelper.client.newCall(request).execute()
|
||||||
|
|
||||||
// Check if response is successful
|
// Check if response is successful
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val responseBody = response.body.string()
|
val responseBody = response.body.string()
|
||||||
|
|
||||||
|
|
||||||
val subtitleType = when {
|
val subtitleType = getType(responseBody)
|
||||||
responseBody.contains("[Script Info]") -> SubtitleType.ASS
|
|
||||||
responseBody.contains("WEBVTT") -> SubtitleType.VTT
|
subtitleType
|
||||||
else -> SubtitleType.SRT
|
} else {
|
||||||
|
SubtitleType.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
subtitleType
|
|
||||||
} else {
|
} else {
|
||||||
SubtitleType.UNKNOWN
|
val uri = url.toUri()
|
||||||
|
val file = uri.toFile()
|
||||||
|
val fileBody = file.readText()
|
||||||
|
val subtitleType = getType(fileBody)
|
||||||
|
subtitleType
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
@@ -50,6 +56,15 @@ class SubtitleDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getType(content: String): SubtitleType {
|
||||||
|
return when {
|
||||||
|
content.contains("[Script Info]") -> SubtitleType.ASS
|
||||||
|
content.contains("WEBVTT") -> SubtitleType.VTT
|
||||||
|
content.contains("SRT") -> SubtitleType.SRT
|
||||||
|
else -> SubtitleType.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//actually downloads lol
|
//actually downloads lol
|
||||||
@Deprecated("handled externally")
|
@Deprecated("handled externally")
|
||||||
suspend fun downloadSubtitle(
|
suspend fun downloadSubtitle(
|
||||||
@@ -72,14 +87,14 @@ class SubtitleDownloader {
|
|||||||
|
|
||||||
val client = Injekt.get<NetworkHelper>().client
|
val client = Injekt.get<NetworkHelper>().client
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
val reponse = client.newCall(request).execute()
|
val response = client.newCall(request).execute()
|
||||||
|
|
||||||
if (!reponse.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
snackString("Failed to download subtitle")
|
snackString("Failed to download subtitle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reponse.body.byteStream().use { input ->
|
response.body.byteStream().use { input ->
|
||||||
subtitleFile.openOutputStream(context, false).use { output ->
|
subtitleFile.openOutputStream(context, false).use { output ->
|
||||||
if (output == null) throw Exception("Could not open output stream")
|
if (output == null) throw Exception("Could not open output stream")
|
||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
|
|||||||
143
app/src/main/java/ani/dantotsu/media/SupportingSearchAdapter.kt
Normal file
143
app/src/main/java/ani/dantotsu/media/SupportingSearchAdapter.kt
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.App.Companion.context
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
|
||||||
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class SupportingSearchAdapter(private val activity: SearchActivity, private val type: SearchType) :
|
||||||
|
HeaderInterface() {
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
||||||
|
binding = holder.binding
|
||||||
|
|
||||||
|
searchHistoryAdapter = SearchHistoryAdapter(type) {
|
||||||
|
binding.searchBarText.setText(it)
|
||||||
|
binding.searchBarText.setSelection(it.length)
|
||||||
|
}
|
||||||
|
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
|
||||||
|
binding.searchHistoryList.adapter = searchHistoryAdapter
|
||||||
|
|
||||||
|
val imm: InputMethodManager =
|
||||||
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
|
if (activity.searchType == SearchType.MANGA || activity.searchType == SearchType.ANIME) {
|
||||||
|
throw IllegalArgumentException("Invalid search type (wrong adapter)")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchByImage.visibility = View.GONE
|
||||||
|
binding.searchResultGrid.visibility = View.GONE
|
||||||
|
binding.searchResultList.visibility = View.GONE
|
||||||
|
binding.searchFilter.visibility = View.GONE
|
||||||
|
binding.searchAdultCheck.visibility = View.GONE
|
||||||
|
binding.searchList.visibility = View.GONE
|
||||||
|
binding.searchChipRecycler.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.searchBar.hint = activity.searchType.toAnilistString()
|
||||||
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
|
val startIconDrawableRes = R.drawable.ic_incognito_24
|
||||||
|
val startIconDrawable: Drawable? =
|
||||||
|
context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) }
|
||||||
|
binding.searchBar.startIconDrawable = startIconDrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchBarText.removeTextChangedListener(textWatcher)
|
||||||
|
when (type) {
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
binding.searchBarText.setText(activity.characterResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
binding.searchBarText.setText(activity.studioResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
binding.searchBarText.setText(activity.staffResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
binding.searchBarText.setText(activity.userResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.clearHistory.setOnClickListener {
|
||||||
|
it.startAnimation(fadeOutAnimation())
|
||||||
|
it.visibility = View.GONE
|
||||||
|
searchHistoryAdapter.clearHistory()
|
||||||
|
}
|
||||||
|
updateClearHistoryVisibility()
|
||||||
|
fun searchTitle() {
|
||||||
|
val searchText = binding.searchBarText.text.toString().takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
val result: SearchResults<*> = when (type) {
|
||||||
|
SearchType.CHARACTER -> activity.characterResult
|
||||||
|
SearchType.STUDIO -> activity.studioResult
|
||||||
|
SearchType.STAFF -> activity.staffResult
|
||||||
|
SearchType.USER -> activity.userResult
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.search = searchText
|
||||||
|
activity.search()
|
||||||
|
}
|
||||||
|
|
||||||
|
textWatcher = object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable) {}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
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.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
return@setOnEditorActionListener when (actionId) {
|
||||||
|
EditorInfo.IME_ACTION_SEARCH -> {
|
||||||
|
searchTitle()
|
||||||
|
binding.searchBarText.clearFocus()
|
||||||
|
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.searchBar.setEndIconOnClickListener { searchTitle() }
|
||||||
|
|
||||||
|
search = Runnable { searchTitle() }
|
||||||
|
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,4 +26,5 @@ data class Anime(
|
|||||||
var slug: String? = null,
|
var slug: String? = null,
|
||||||
var kitsuEpisodes: Map<String, Episode>? = null,
|
var kitsuEpisodes: Map<String, Episode>? = null,
|
||||||
var fillerEpisodes: Map<String, Episode>? = null,
|
var fillerEpisodes: Map<String, Episode>? = null,
|
||||||
|
var anifyEpisodes: Map<String, Episode>? = null,
|
||||||
) : Serializable
|
) : Serializable
|
||||||
@@ -8,8 +8,8 @@ import android.view.ViewGroup
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.getString
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@@ -18,9 +18,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.DialogLayoutBinding
|
import ani.dantotsu.databinding.DialogLayoutBinding
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
|
import ani.dantotsu.databinding.ItemMediaSourceBinding
|
||||||
import ani.dantotsu.displayTimer
|
import ani.dantotsu.displayTimer
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
@@ -33,12 +34,15 @@ import ani.dantotsu.others.LanguageMapper
|
|||||||
import ani.dantotsu.others.webview.CookieCatcher
|
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.OfflineAnimeParser
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.FAQActivity
|
import ani.dantotsu.settings.FAQActivity
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
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.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
||||||
@@ -54,16 +58,14 @@ class AnimeWatchAdapter(
|
|||||||
) : RecyclerView.Adapter<AnimeWatchAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<AnimeWatchAdapter.ViewHolder>() {
|
||||||
private var autoSelect = true
|
private var autoSelect = true
|
||||||
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
||||||
private var _binding: ItemAnimeWatchBinding? = null
|
private var _binding: ItemMediaSourceBinding? = null
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val bind =
|
||||||
|
ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ViewHolder(bind)
|
return ViewHolder(bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var nestedDialog: AlertDialog? = null
|
|
||||||
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
_binding = binding
|
_binding = binding
|
||||||
@@ -75,7 +77,7 @@ class AnimeWatchAdapter(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//Youtube
|
// Youtube
|
||||||
if (media.anime?.youtube != null && PrefManager.getVal(PrefName.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 {
|
||||||
@@ -89,7 +91,7 @@ class AnimeWatchAdapter(
|
|||||||
R.string.subbed
|
R.string.subbed
|
||||||
)
|
)
|
||||||
|
|
||||||
//PreferDub
|
// PreferDub
|
||||||
var changing = false
|
var changing = false
|
||||||
binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked ->
|
binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked ->
|
||||||
binding.animeSourceDubbedText.text =
|
binding.animeSourceDubbedText.text =
|
||||||
@@ -99,8 +101,8 @@ class AnimeWatchAdapter(
|
|||||||
if (!changing) fragment.onDubClicked(isChecked)
|
if (!changing) fragment.onDubClicked(isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Wrong Title
|
// Wrong Title
|
||||||
binding.animeSourceSearch.setOnClickListener {
|
binding.mediaSourceSearch.setOnClickListener {
|
||||||
SourceSearchDialogFragment().show(
|
SourceSearchDialogFragment().show(
|
||||||
fragment.requireActivity().supportFragmentManager,
|
fragment.requireActivity().supportFragmentManager,
|
||||||
null
|
null
|
||||||
@@ -108,37 +110,37 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||||
|
|
||||||
binding.animeSourceNameContainer.isGone = offline
|
binding.mediaSourceNameContainer.isGone = offline
|
||||||
binding.animeSourceSettings.isGone = offline
|
binding.mediaSourceSettings.isGone = offline
|
||||||
binding.animeSourceSearch.isGone = offline
|
binding.mediaSourceSearch.isGone = offline
|
||||||
binding.animeSourceTitle.isGone = offline
|
binding.mediaSourceTitle.isGone = offline
|
||||||
|
|
||||||
//Source Selection
|
// Source Selection
|
||||||
var source =
|
var source =
|
||||||
media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
||||||
setLanguageList(media.selected!!.langIndex, source)
|
setLanguageList(media.selected!!.langIndex, source)
|
||||||
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||||
binding.animeSource.setText(watchSources.names[source])
|
binding.mediaSource.setText(watchSources.names[source])
|
||||||
watchSources[source].apply {
|
watchSources[source].apply {
|
||||||
this.selectDub = media.selected!!.preferDub
|
this.selectDub = media.selected!!.preferDub
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.mediaSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } }
|
||||||
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSource.setAdapter(
|
binding.mediaSource.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
fragment.requireContext(),
|
fragment.requireContext(),
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
watchSources.names
|
watchSources.names
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.animeSourceTitle.isSelected = true
|
binding.mediaSourceTitle.isSelected = true
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.mediaSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
fragment.onSourceChange(i).apply {
|
fragment.onSourceChange(i).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.mediaSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } }
|
||||||
changing = true
|
changing = true
|
||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
@@ -150,15 +152,15 @@ class AnimeWatchAdapter(
|
|||||||
fragment.loadEpisodes(i, false)
|
fragment.loadEpisodes(i, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ ->
|
binding.mediaSourceLanguage.setOnItemClickListener { _, _, i, _ ->
|
||||||
// Check if 'extension' and 'selected' properties exist and are accessible
|
// Check if 'extension' and 'selected' properties exist and are accessible
|
||||||
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
||||||
ext.sourceLanguage = i
|
ext.sourceLanguage = i
|
||||||
fragment.onLangChange(i)
|
fragment.onLangChange(i)
|
||||||
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.mediaSourceTitle.text = showUserText
|
||||||
showUserTextListener =
|
showUserTextListener =
|
||||||
{ MainScope().launch { binding.animeSourceTitle.text = it } }
|
{ MainScope().launch { binding.mediaSourceTitle.text = it } }
|
||||||
changing = true
|
changing = true
|
||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
@@ -170,19 +172,19 @@ class AnimeWatchAdapter(
|
|||||||
} ?: run { }
|
} ?: run { }
|
||||||
}
|
}
|
||||||
|
|
||||||
//settings
|
// Settings
|
||||||
binding.animeSourceSettings.setOnClickListener {
|
binding.mediaSourceSettings.setOnClickListener {
|
||||||
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
||||||
fragment.openSettings(ext.extension)
|
fragment.openSettings(ext.extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Icons
|
// Icons
|
||||||
|
|
||||||
//subscribe
|
// Subscribe
|
||||||
subscribe = MediaDetailsActivity.PopImageButton(
|
subscribe = MediaDetailsActivity.PopImageButton(
|
||||||
fragment.lifecycleScope,
|
fragment.lifecycleScope,
|
||||||
binding.animeSourceSubscribe,
|
binding.mediaSourceSubscribe,
|
||||||
R.drawable.ic_round_notifications_active_24,
|
R.drawable.ic_round_notifications_active_24,
|
||||||
R.drawable.ic_round_notifications_none_24,
|
R.drawable.ic_round_notifications_none_24,
|
||||||
R.color.bg_opp,
|
R.color.bg_opp,
|
||||||
@@ -190,125 +192,164 @@ class AnimeWatchAdapter(
|
|||||||
fragment.subscribed,
|
fragment.subscribed,
|
||||||
true
|
true
|
||||||
) {
|
) {
|
||||||
fragment.onNotificationPressed(it, binding.animeSource.text.toString())
|
fragment.onNotificationPressed(it, binding.mediaSource.text.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
|
|
||||||
binding.animeSourceSubscribe.setOnLongClickListener {
|
binding.mediaSourceSubscribe.setOnLongClickListener {
|
||||||
openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
|
openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Nested Button
|
// Nested Button
|
||||||
binding.animeNestedButton.setOnClickListener {
|
binding.mediaNestedButton.setOnClickListener {
|
||||||
val dialogView =
|
val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater)
|
||||||
LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
dialogBinding.apply {
|
||||||
val dialogBinding = DialogLayoutBinding.bind(dialogView)
|
var refresh = false
|
||||||
var refresh = false
|
var run = false
|
||||||
var run = false
|
var reversed = media.selected!!.recyclerReversed
|
||||||
var reversed = media.selected!!.recyclerReversed
|
var style =
|
||||||
var style =
|
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView)
|
||||||
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView)
|
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
mediaSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
dialogBinding.animeSourceTop.setOnClickListener {
|
mediaSourceTop.setOnClickListener {
|
||||||
reversed = !reversed
|
reversed = !reversed
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
mediaSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
run = true
|
run = true
|
||||||
}
|
|
||||||
//Grids
|
|
||||||
var selected = when (style) {
|
|
||||||
0 -> dialogBinding.animeSourceList
|
|
||||||
1 -> dialogBinding.animeSourceGrid
|
|
||||||
2 -> dialogBinding.animeSourceCompact
|
|
||||||
else -> dialogBinding.animeSourceList
|
|
||||||
}
|
|
||||||
when (style) {
|
|
||||||
0 -> dialogBinding.layoutText.setText(R.string.list)
|
|
||||||
1 -> dialogBinding.layoutText.setText(R.string.grid)
|
|
||||||
2 -> dialogBinding.layoutText.setText(R.string.compact)
|
|
||||||
else -> dialogBinding.animeSourceList
|
|
||||||
}
|
|
||||||
selected.alpha = 1f
|
|
||||||
fun selected(it: ImageButton) {
|
|
||||||
selected.alpha = 0.33f
|
|
||||||
selected = it
|
|
||||||
selected.alpha = 1f
|
|
||||||
}
|
|
||||||
dialogBinding.animeSourceList.setOnClickListener {
|
|
||||||
selected(it as ImageButton)
|
|
||||||
style = 0
|
|
||||||
dialogBinding.layoutText.setText(R.string.list)
|
|
||||||
run = true
|
|
||||||
}
|
|
||||||
dialogBinding.animeSourceGrid.setOnClickListener {
|
|
||||||
selected(it as ImageButton)
|
|
||||||
style = 1
|
|
||||||
dialogBinding.layoutText.setText(R.string.grid)
|
|
||||||
run = true
|
|
||||||
}
|
|
||||||
dialogBinding.animeSourceCompact.setOnClickListener {
|
|
||||||
selected(it as ImageButton)
|
|
||||||
style = 2
|
|
||||||
dialogBinding.layoutText.setText(R.string.compact)
|
|
||||||
run = true
|
|
||||||
}
|
|
||||||
dialogBinding.animeWebviewContainer.setOnClickListener {
|
|
||||||
if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
|
|
||||||
toast(R.string.webview_not_installed)
|
|
||||||
}
|
}
|
||||||
//start CookieCatcher activity
|
// Grids
|
||||||
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
var selected = when (style) {
|
||||||
val sourceAHH = watchSources[source] as? DynamicAnimeParser
|
0 -> mediaSourceList
|
||||||
val sourceHttp =
|
1 -> mediaSourceGrid
|
||||||
sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource
|
2 -> mediaSourceCompact
|
||||||
val url = sourceHttp?.baseUrl
|
else -> mediaSourceList
|
||||||
url?.let {
|
}
|
||||||
refresh = true
|
when (style) {
|
||||||
val headersMap = try {
|
0 -> layoutText.setText(R.string.list)
|
||||||
sourceHttp.headers.toMultimap()
|
1 -> layoutText.setText(R.string.grid)
|
||||||
.mapValues { it.value.getOrNull(0) ?: "" }
|
2 -> layoutText.setText(R.string.compact)
|
||||||
} catch (e: Exception) {
|
else -> mediaSourceList
|
||||||
emptyMap()
|
}
|
||||||
|
selected.alpha = 1f
|
||||||
|
fun selected(it: ImageButton) {
|
||||||
|
selected.alpha = 0.33f
|
||||||
|
selected = it
|
||||||
|
selected.alpha = 1f
|
||||||
|
}
|
||||||
|
mediaSourceList.setOnClickListener {
|
||||||
|
selected(it as ImageButton)
|
||||||
|
style = 0
|
||||||
|
layoutText.setText(R.string.list)
|
||||||
|
run = true
|
||||||
|
}
|
||||||
|
mediaSourceGrid.setOnClickListener {
|
||||||
|
selected(it as ImageButton)
|
||||||
|
style = 1
|
||||||
|
layoutText.setText(R.string.grid)
|
||||||
|
run = true
|
||||||
|
}
|
||||||
|
mediaSourceCompact.setOnClickListener {
|
||||||
|
selected(it as ImageButton)
|
||||||
|
style = 2
|
||||||
|
layoutText.setText(R.string.compact)
|
||||||
|
run = true
|
||||||
|
}
|
||||||
|
mediaWebviewContainer.setOnClickListener {
|
||||||
|
if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
|
||||||
|
toast(R.string.webview_not_installed)
|
||||||
|
}
|
||||||
|
// Start CookieCatcher activity
|
||||||
|
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||||
|
val sourceAHH = watchSources[source] as? DynamicAnimeParser
|
||||||
|
val sourceHttp =
|
||||||
|
sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource
|
||||||
|
val url = sourceHttp?.baseUrl
|
||||||
|
url?.let {
|
||||||
|
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)
|
||||||
|
.putExtra("url", url)
|
||||||
|
.putExtra("headers", headersMap as HashMap<String, String>)
|
||||||
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
}
|
}
|
||||||
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
|
||||||
.putExtra("url", url)
|
|
||||||
.putExtra("headers", headersMap as HashMap<String, String>)
|
|
||||||
startActivity(fragment.requireContext(), intent, null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resetProgress.setOnClickListener {
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle(" Delete Progress for all episodes of ${media.nameRomaji}")
|
||||||
|
setMessage("This will delete all the locally stored progress for all episodes")
|
||||||
|
setPosButton(R.string.ok) {
|
||||||
|
val prefix = "${media.id}_"
|
||||||
|
val regex = Regex("^${prefix}\\d+$")
|
||||||
|
|
||||||
|
PrefManager.getAllCustomValsForMedia(prefix)
|
||||||
|
.keys
|
||||||
|
.filter { it.matches(regex) }
|
||||||
|
.onEach { key -> PrefManager.removeCustomVal(key) }
|
||||||
|
snackString("Deleted the progress of all Episodes for ${media.nameRomaji}")
|
||||||
|
}
|
||||||
|
setNegButton(R.string.no)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetProgressDef.text = getString(currContext()!!, R.string.clear_stored_episode)
|
||||||
|
|
||||||
|
// Hidden
|
||||||
|
mangaScanlatorContainer.visibility = View.GONE
|
||||||
|
animeDownloadContainer.visibility = View.GONE
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle("Options")
|
||||||
|
setCustomView(dialogBinding.root)
|
||||||
|
setPosButton("OK") {
|
||||||
|
if (run) fragment.onIconPressed(style, reversed)
|
||||||
|
if (refresh) fragment.loadEpisodes(source, true)
|
||||||
|
}
|
||||||
|
setNegButton("Cancel") {
|
||||||
|
if (refresh) fragment.loadEpisodes(source, true)
|
||||||
|
}
|
||||||
|
show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//hidden
|
|
||||||
dialogBinding.animeScanlatorContainer.visibility = View.GONE
|
|
||||||
dialogBinding.animeDownloadContainer.visibility = View.GONE
|
|
||||||
|
|
||||||
nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup)
|
|
||||||
.setTitle("Options")
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton("OK") { _, _ ->
|
|
||||||
if (run) fragment.onIconPressed(style, reversed)
|
|
||||||
if (refresh) fragment.loadEpisodes(source, true)
|
|
||||||
}
|
|
||||||
.setNegativeButton("Cancel") { _, _ ->
|
|
||||||
if (refresh) fragment.loadEpisodes(source, true)
|
|
||||||
}
|
|
||||||
.setOnCancelListener {
|
|
||||||
if (refresh) fragment.loadEpisodes(source, true)
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
nestedDialog?.show()
|
|
||||||
}
|
}
|
||||||
//Episode Handling
|
// Episode Handling
|
||||||
handleEpisodes()
|
handleEpisodes()
|
||||||
|
|
||||||
|
//clear progress
|
||||||
|
binding.sourceTitle.setOnLongClickListener {
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle(" Delete Progress for all episodes of ${media.nameRomaji}")
|
||||||
|
setMessage("This will delete all the locally stored progress for all episodes")
|
||||||
|
setPosButton(R.string.ok) {
|
||||||
|
val prefix = "${media.id}_"
|
||||||
|
val regex = Regex("^${prefix}\\d+$")
|
||||||
|
|
||||||
|
PrefManager.getAllCustomValsForMedia(prefix)
|
||||||
|
.keys
|
||||||
|
.filter { it.matches(regex) }
|
||||||
|
.onEach { key -> PrefManager.removeCustomVal(key) }
|
||||||
|
snackString("Deleted the progress of all Episodes for ${media.nameRomaji}")
|
||||||
|
}
|
||||||
|
setNegButton(R.string.no)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribeButton(enabled: Boolean) {
|
fun subscribeButton(enabled: Boolean) {
|
||||||
subscribe?.enabled(enabled)
|
subscribe?.enabled(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Chips
|
// Chips
|
||||||
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
|
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
|
||||||
val binding = _binding
|
val binding = _binding
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
@@ -319,13 +360,13 @@ class AnimeWatchAdapter(
|
|||||||
val chip =
|
val chip =
|
||||||
ItemChipBinding.inflate(
|
ItemChipBinding.inflate(
|
||||||
LayoutInflater.from(fragment.context),
|
LayoutInflater.from(fragment.context),
|
||||||
binding.animeSourceChipGroup,
|
binding.mediaSourceChipGroup,
|
||||||
false
|
false
|
||||||
).root
|
).root
|
||||||
chip.isCheckable = true
|
chip.isCheckable = true
|
||||||
fun selected() {
|
fun selected() {
|
||||||
chip.isChecked = true
|
chip.isChecked = true
|
||||||
binding.animeWatchChipScroll.smoothScrollTo(
|
binding.mediaWatchChipScroll.smoothScrollTo(
|
||||||
(chip.left - screenWidth / 2) + (chip.width / 2),
|
(chip.left - screenWidth / 2) + (chip.width / 2),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
@@ -344,14 +385,14 @@ class AnimeWatchAdapter(
|
|||||||
selected()
|
selected()
|
||||||
fragment.onChipClicked(position, limit * (position), last - 1)
|
fragment.onChipClicked(position, limit * (position), last - 1)
|
||||||
}
|
}
|
||||||
binding.animeSourceChipGroup.addView(chip)
|
binding.mediaSourceChipGroup.addView(chip)
|
||||||
if (selected == position) {
|
if (selected == position) {
|
||||||
selected()
|
selected()
|
||||||
select = chip
|
select = chip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (select != null)
|
if (select != null)
|
||||||
binding.animeWatchChipScroll.apply {
|
binding.mediaWatchChipScroll.apply {
|
||||||
post {
|
post {
|
||||||
scrollTo(
|
scrollTo(
|
||||||
(select.left - screenWidth / 2) + (select.width / 2),
|
(select.left - screenWidth / 2) + (select.width / 2),
|
||||||
@@ -363,7 +404,7 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearChips() {
|
fun clearChips() {
|
||||||
_binding?.animeSourceChipGroup?.removeAllViews()
|
_binding?.mediaSourceChipGroup?.removeAllViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEpisodes() {
|
fun handleEpisodes() {
|
||||||
@@ -379,15 +420,15 @@ class AnimeWatchAdapter(
|
|||||||
|
|
||||||
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)) {
|
||||||
binding.animeSourceContinue.visibility = View.VISIBLE
|
binding.sourceContinue.visibility = View.VISIBLE
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemEpisodeProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemEpisodeProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
continueEp
|
continueEp
|
||||||
)
|
)
|
||||||
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal<Float>(
|
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal<Float>(
|
||||||
PrefName.WatchPercentage
|
PrefName.WatchPercentage
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -395,9 +436,9 @@ class AnimeWatchAdapter(
|
|||||||
if (e != -1 && e + 1 < episodes.size) {
|
if (e != -1 && e + 1 < episodes.size) {
|
||||||
continueEp = episodes[e + 1]
|
continueEp = episodes[e + 1]
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemEpisodeProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemEpisodeProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
continueEp
|
continueEp
|
||||||
)
|
)
|
||||||
@@ -407,51 +448,65 @@ class AnimeWatchAdapter(
|
|||||||
|
|
||||||
val cleanedTitle = ep.title?.let { MediaNameAdapter.removeEpisodeNumber(it) }
|
val cleanedTitle = ep.title?.let { MediaNameAdapter.removeEpisodeNumber(it) }
|
||||||
|
|
||||||
binding.itemEpisodeImage.loadImage(
|
binding.itemMediaImage.loadImage(
|
||||||
ep.thumb ?: FileUrl[media.banner ?: media.cover], 0
|
ep.thumb ?: FileUrl[media.banner ?: media.cover], 0
|
||||||
)
|
)
|
||||||
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
|
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.animeSourceContinueText.text =
|
binding.mediaSourceContinueText.text =
|
||||||
currActivity()!!.getString(
|
currActivity()!!.getString(
|
||||||
R.string.continue_episode, ep.number, if (ep.filler)
|
R.string.continue_episode, ep.number, if (ep.filler)
|
||||||
currActivity()!!.getString(R.string.filler_tag)
|
currActivity()!!.getString(R.string.filler_tag)
|
||||||
else
|
else
|
||||||
"", cleanedTitle
|
"", cleanedTitle
|
||||||
)
|
)
|
||||||
binding.animeSourceContinue.setOnClickListener {
|
binding.sourceContinue.setOnClickListener {
|
||||||
fragment.onEpisodeClick(continueEp)
|
fragment.onEpisodeClick(continueEp)
|
||||||
}
|
}
|
||||||
if (fragment.continueEp) {
|
if (fragment.continueEp) {
|
||||||
if (
|
if (
|
||||||
(binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams)
|
(binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams)
|
||||||
.weight < PrefManager.getVal<Float>(PrefName.WatchPercentage)
|
.weight < PrefManager.getVal<Float>(PrefName.WatchPercentage)
|
||||||
) {
|
) {
|
||||||
binding.animeSourceContinue.performClick()
|
binding.sourceContinue.performClick()
|
||||||
fragment.continueEp = false
|
fragment.continueEp = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.animeSourceContinue.visibility = View.GONE
|
binding.sourceContinue.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceProgressBar.visibility = View.GONE
|
binding.sourceProgressBar.visibility = View.GONE
|
||||||
|
|
||||||
val sourceFound = media.anime.episodes!!.isNotEmpty()
|
val sourceFound = media.anime.episodes!!.isNotEmpty()
|
||||||
binding.animeSourceNotFound.isGone = sourceFound
|
val isDownloadedSource =
|
||||||
|
watchSources[media.selected!!.sourceIndex] is OfflineAnimeParser
|
||||||
|
|
||||||
|
if (isDownloadedSource) {
|
||||||
|
binding.sourceNotFound.text = if (sourceFound) {
|
||||||
|
currActivity()!!.getString(R.string.source_not_found)
|
||||||
|
} else {
|
||||||
|
currActivity()!!.getString(R.string.download_not_found)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.sourceNotFound.text =
|
||||||
|
currActivity()!!.getString(R.string.source_not_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.sourceNotFound.isGone = sourceFound
|
||||||
binding.faqbutton.isGone = sourceFound
|
binding.faqbutton.isGone = sourceFound
|
||||||
|
|
||||||
if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) {
|
if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) {
|
||||||
if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) {
|
if (binding.mediaSource.adapter.count > media.selected!!.sourceIndex + 1) {
|
||||||
val nextIndex = media.selected!!.sourceIndex + 1
|
val nextIndex = media.selected!!.sourceIndex + 1
|
||||||
binding.animeSource.setText(
|
binding.mediaSource.setText(
|
||||||
binding.animeSource.adapter
|
binding.mediaSource.adapter
|
||||||
.getItem(nextIndex).toString(), false
|
.getItem(nextIndex).toString(), false
|
||||||
)
|
)
|
||||||
fragment.onSourceChange(nextIndex).apply {
|
fragment.onSourceChange(nextIndex).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.mediaSourceTitle.text = showUserText
|
||||||
showUserTextListener =
|
showUserTextListener =
|
||||||
{ MainScope().launch { binding.animeSourceTitle.text = it } }
|
{ MainScope().launch { binding.mediaSourceTitle.text = it } }
|
||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
||||||
setLanguageList(0, nextIndex)
|
setLanguageList(0, nextIndex)
|
||||||
@@ -460,13 +515,13 @@ class AnimeWatchAdapter(
|
|||||||
fragment.loadEpisodes(nextIndex, false)
|
fragment.loadEpisodes(nextIndex, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.animeSource.setOnClickListener { autoSelect = false }
|
binding.mediaSource.setOnClickListener { autoSelect = false }
|
||||||
} else {
|
} else {
|
||||||
binding.animeSourceContinue.visibility = View.GONE
|
binding.sourceContinue.visibility = View.GONE
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.sourceNotFound.visibility = View.GONE
|
||||||
binding.faqbutton.visibility = View.GONE
|
binding.faqbutton.visibility = View.GONE
|
||||||
clearChips()
|
clearChips()
|
||||||
binding.animeSourceProgressBar.visibility = View.VISIBLE
|
binding.sourceProgressBar.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,9 +535,9 @@ class AnimeWatchAdapter(
|
|||||||
ext.sourceLanguage = lang
|
ext.sourceLanguage = lang
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
binding?.mediaSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
||||||
} catch (e: IndexOutOfBoundsException) {
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
binding?.animeSourceLanguage?.setText(
|
binding?.mediaSourceLanguage?.setText(
|
||||||
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
|
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -493,9 +548,9 @@ class AnimeWatchAdapter(
|
|||||||
)
|
)
|
||||||
val items = adapter.count
|
val items = adapter.count
|
||||||
|
|
||||||
binding?.animeSourceLanguageContainer?.visibility =
|
binding?.mediaSourceLanguageContainer?.visibility =
|
||||||
if (items > 1) View.VISIBLE else View.GONE
|
if (items > 1) View.VISIBLE else View.GONE
|
||||||
binding?.animeSourceLanguage?.setAdapter(adapter)
|
binding?.mediaSourceLanguage?.setAdapter(adapter)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +558,7 @@ class AnimeWatchAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemAnimeWatchBinding) :
|
inner class ViewHolder(val binding: ItemMediaSourceBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
displayTimer(media, binding.animeSourceContainer)
|
displayTimer(media, binding.animeSourceContainer)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -28,10 +27,9 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.FileUrl
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.addons.download.DownloadAddonManager
|
import ani.dantotsu.addons.download.DownloadAddonManager
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentMediaSourceBinding
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.download.DownloadsManager.Companion.compareName
|
import ani.dantotsu.download.DownloadsManager.Companion.compareName
|
||||||
@@ -61,6 +59,7 @@ import ani.dantotsu.toast
|
|||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
||||||
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.anggrayudi.storage.file.extension
|
import com.anggrayudi.storage.file.extension
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
@@ -78,7 +77,7 @@ import kotlin.math.max
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class AnimeWatchFragment : Fragment() {
|
class AnimeWatchFragment : Fragment() {
|
||||||
private var _binding: FragmentAnimeWatchBinding? = null
|
private var _binding: FragmentMediaSourceBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private val model: MediaDetailsViewModel by activityViewModels()
|
private val model: MediaDetailsViewModel by activityViewModels()
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
_binding = FragmentAnimeWatchBinding.inflate(inflater, container, false)
|
_binding = FragmentMediaSourceBinding.inflate(inflater, container, false)
|
||||||
return _binding?.root
|
return _binding?.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +125,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.dp
|
screenWidth = resources.displayMetrics.widthPixels.dp
|
||||||
|
|
||||||
var maxGridSize = (screenWidth / 100f).roundToInt()
|
var maxGridSize = (screenWidth / 100f).roundToInt()
|
||||||
@@ -150,13 +149,13 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceRecycler.layoutManager = gridLayoutManager
|
binding.mediaSourceRecycler.layoutManager = gridLayoutManager
|
||||||
|
|
||||||
binding.ScrollTop.setOnClickListener {
|
binding.ScrollTop.setOnClickListener {
|
||||||
binding.animeSourceRecycler.scrollToPosition(10)
|
binding.mediaSourceRecycler.scrollToPosition(10)
|
||||||
binding.animeSourceRecycler.smoothScrollToPosition(0)
|
binding.mediaSourceRecycler.smoothScrollToPosition(0)
|
||||||
}
|
}
|
||||||
binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.mediaSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
model.scrolledToTop.observe(viewLifecycleOwner) {
|
model.scrolledToTop.observe(viewLifecycleOwner) {
|
||||||
if (it) binding.animeSourceRecycler.scrollToPosition(0)
|
if (it) binding.mediaSourceRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
continueEp = model.continueMedia ?: false
|
continueEp = model.continueMedia ?: false
|
||||||
@@ -203,7 +202,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
offlineMode = offlineMode
|
offlineMode = offlineMode
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter =
|
binding.mediaSourceRecycler.adapter =
|
||||||
ConcatAdapter(headerAdapter, episodeAdapter)
|
ConcatAdapter(headerAdapter, episodeAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -212,10 +211,11 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
if (offline) {
|
if (offline) {
|
||||||
media.selected!!.sourceIndex = model.watchSources!!.list.lastIndex
|
media.selected!!.sourceIndex = model.watchSources!!.list.lastIndex
|
||||||
} else {
|
} else {
|
||||||
awaitAll(
|
val kitsuEpisodes = async { model.loadKitsuEpisodes(media) }
|
||||||
async { model.loadKitsuEpisodes(media) },
|
val anifyEpisodes = async { model.loadAnifyEpisodes(media.id) }
|
||||||
async { model.loadFillerEpisodes(media) }
|
val fillerEpisodes = async { model.loadFillerEpisodes(media) }
|
||||||
)
|
|
||||||
|
awaitAll(kitsuEpisodes, anifyEpisodes, fillerEpisodes)
|
||||||
}
|
}
|
||||||
model.loadEpisodes(media, media.selected!!.sourceIndex)
|
model.loadEpisodes(media, media.selected!!.sourceIndex)
|
||||||
}
|
}
|
||||||
@@ -230,6 +230,21 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
val episodes = loadedEpisodes[media.selected!!.sourceIndex]
|
val episodes = loadedEpisodes[media.selected!!.sourceIndex]
|
||||||
if (episodes != null) {
|
if (episodes != null) {
|
||||||
episodes.forEach { (i, episode) ->
|
episodes.forEach { (i, episode) ->
|
||||||
|
if (media.anime?.anifyEpisodes != null) {
|
||||||
|
if (media.anime!!.anifyEpisodes!!.containsKey(i)) {
|
||||||
|
episode.desc =
|
||||||
|
media.anime!!.anifyEpisodes!![i]?.desc ?: episode.desc
|
||||||
|
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
|
||||||
|
episode.title ?: ""
|
||||||
|
).isBlank()
|
||||||
|
) media.anime!!.anifyEpisodes!![i]?.title
|
||||||
|
?: episode.title else episode.title
|
||||||
|
?: media.anime!!.anifyEpisodes!![i]?.title ?: episode.title
|
||||||
|
episode.thumb =
|
||||||
|
media.anime!!.anifyEpisodes!![i]?.thumb ?: episode.thumb
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
if (media.anime?.fillerEpisodes != null) {
|
if (media.anime?.fillerEpisodes != null) {
|
||||||
if (media.anime!!.fillerEpisodes!!.containsKey(i)) {
|
if (media.anime!!.fillerEpisodes!!.containsKey(i)) {
|
||||||
episode.title =
|
episode.title =
|
||||||
@@ -247,14 +262,14 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
) media.anime!!.kitsuEpisodes!![i]?.title
|
) media.anime!!.kitsuEpisodes!![i]?.title
|
||||||
?: episode.title else episode.title
|
?: episode.title else episode.title
|
||||||
?: media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title
|
?: media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title
|
||||||
episode.thumb = media.anime!!.kitsuEpisodes!![i]?.thumb
|
episode.thumb =
|
||||||
?: FileUrl[media.cover]
|
media.anime!!.kitsuEpisodes!![i]?.thumb ?: episode.thumb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
media.anime?.episodes = episodes
|
media.anime?.episodes = episodes
|
||||||
|
|
||||||
//CHIP GROUP
|
// CHIP GROUP
|
||||||
val total = episodes.size
|
val total = episodes.size
|
||||||
val divisions = total.toDouble() / 10
|
val divisions = total.toDouble() / 10
|
||||||
start = 0
|
start = 0
|
||||||
@@ -295,6 +310,10 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
if (i != null)
|
if (i != null)
|
||||||
media.anime?.fillerEpisodes = i
|
media.anime?.fillerEpisodes = i
|
||||||
}
|
}
|
||||||
|
model.getAnifyEpisodes().observe(viewLifecycleOwner) { i ->
|
||||||
|
if (i != null)
|
||||||
|
media.anime?.anifyEpisodes = i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSourceChange(i: Int): AnimeParser {
|
fun onSourceChange(i: Int): AnimeParser {
|
||||||
@@ -380,34 +399,33 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
if (allSettings.size > 1) {
|
if (allSettings.size > 1) {
|
||||||
val names =
|
val names =
|
||||||
allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray()
|
allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray()
|
||||||
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
requireContext()
|
||||||
.setTitle("Select a Source")
|
.customAlertDialog()
|
||||||
.setSingleChoiceItems(names, -1) { dialog, which ->
|
.apply {
|
||||||
selectedSetting = allSettings[which]
|
setTitle("Select a Source")
|
||||||
itemSelected = true
|
singleChoiceItems(names) { which ->
|
||||||
dialog.dismiss()
|
selectedSetting = allSettings[which]
|
||||||
|
itemSelected = true
|
||||||
// Move the fragment transaction here
|
requireActivity().runOnUiThread {
|
||||||
requireActivity().runOnUiThread {
|
val fragment =
|
||||||
val fragment =
|
AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
||||||
AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
changeUIVisibility(true)
|
||||||
changeUIVisibility(true)
|
loadEpisodes(media.selected!!.sourceIndex, true)
|
||||||
loadEpisodes(media.selected!!.sourceIndex, true)
|
}
|
||||||
}
|
parentFragmentManager.beginTransaction()
|
||||||
parentFragmentManager.beginTransaction()
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
.replace(R.id.fragmentExtensionsContainer, fragment)
|
.addToBackStack(null)
|
||||||
.addToBackStack(null)
|
.commit()
|
||||||
.commit()
|
}
|
||||||
}
|
}
|
||||||
}
|
onDismiss {
|
||||||
.setOnDismissListener {
|
if (!itemSelected) {
|
||||||
if (!itemSelected) {
|
changeUIVisibility(true)
|
||||||
changeUIVisibility(true)
|
}
|
||||||
}
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
} else {
|
} else {
|
||||||
// If there's only one setting, proceed with the fragment transaction
|
// If there's only one setting, proceed with the fragment transaction
|
||||||
requireActivity().runOnUiThread {
|
requireActivity().runOnUiThread {
|
||||||
@@ -416,11 +434,12 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
changeUIVisibility(true)
|
changeUIVisibility(true)
|
||||||
loadEpisodes(media.selected!!.sourceIndex, true)
|
loadEpisodes(media.selected!!.sourceIndex, true)
|
||||||
}
|
}
|
||||||
parentFragmentManager.beginTransaction()
|
parentFragmentManager.beginTransaction().apply {
|
||||||
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
.replace(R.id.fragmentExtensionsContainer, fragment)
|
replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
.addToBackStack(null)
|
addToBackStack(null)
|
||||||
.commit()
|
commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,7 +638,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
private fun reload() {
|
private fun reload() {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
|
|
||||||
//Find latest episode for subscription
|
// Find latest episode for subscription
|
||||||
selected.latest =
|
selected.latest =
|
||||||
media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
||||||
selected.latest =
|
selected.latest =
|
||||||
@@ -663,14 +682,14 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
binding.mediaInfoProgressBar.visibility = progress
|
binding.mediaInfoProgressBar.visibility = progress
|
||||||
binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state)
|
||||||
|
|
||||||
requireActivity().setNavigationTheme()
|
requireActivity().setNavigationTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -13,7 +12,6 @@ import androidx.media3.common.util.UnstableApi
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.updateProgress
|
import ani.dantotsu.connections.updateProgress
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||||
import ani.dantotsu.databinding.ItemEpisodeGridBinding
|
import ani.dantotsu.databinding.ItemEpisodeGridBinding
|
||||||
import ani.dantotsu.databinding.ItemEpisodeListBinding
|
import ani.dantotsu.databinding.ItemEpisodeListBinding
|
||||||
@@ -23,6 +21,7 @@ import ani.dantotsu.media.MediaNameAdapter
|
|||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
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
|
||||||
@@ -106,8 +105,8 @@ class EpisodeAdapter(
|
|||||||
|
|
||||||
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 }
|
||||||
Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0)
|
Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0)
|
||||||
.into(binding.itemEpisodeImage)
|
.into(binding.itemMediaImage)
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title
|
binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title
|
||||||
|
|
||||||
@@ -140,9 +139,9 @@ class EpisodeAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemEpisodeProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemEpisodeProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
ep.number
|
ep.number
|
||||||
)
|
)
|
||||||
@@ -154,8 +153,8 @@ class EpisodeAdapter(
|
|||||||
|
|
||||||
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 }
|
||||||
Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0)
|
Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0)
|
||||||
.into(binding.itemEpisodeImage)
|
.into(binding.itemMediaImage)
|
||||||
|
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeTitle.text = title
|
binding.itemEpisodeTitle.text = title
|
||||||
@@ -183,9 +182,9 @@ class EpisodeAdapter(
|
|||||||
binding.itemEpisodeViewed.visibility = View.GONE
|
binding.itemEpisodeViewed.visibility = View.GONE
|
||||||
}
|
}
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemEpisodeProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemEpisodeProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
ep.number
|
ep.number
|
||||||
)
|
)
|
||||||
@@ -208,9 +207,9 @@ class EpisodeAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemEpisodeProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemEpisodeProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
ep.number
|
ep.number
|
||||||
)
|
)
|
||||||
@@ -318,16 +317,14 @@ class EpisodeAdapter(
|
|||||||
fragment.onAnimeEpisodeStopDownloadClick(episodeNumber)
|
fragment.onAnimeEpisodeStopDownloadClick(episodeNumber)
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else if (downloadedEpisodes.contains(episodeNumber)) {
|
} else if (downloadedEpisodes.contains(episodeNumber)) {
|
||||||
val builder = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
binding.root.context.customAlertDialog().apply {
|
||||||
builder.setTitle("Delete Episode")
|
setTitle("Delete Episode")
|
||||||
builder.setMessage("Are you sure you want to delete Episode ${episodeNumber}?")
|
setMessage("Are you sure you want to delete Episode $episodeNumber?")
|
||||||
builder.setPositiveButton("Yes") { _, _ ->
|
setPosButton(R.string.yes) {
|
||||||
fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber)
|
fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber)
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
setNegButton(R.string.no)
|
||||||
}
|
}.show()
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else {
|
} else {
|
||||||
fragment.onAnimeEpisodeDownloadClick(episodeNumber)
|
fragment.onAnimeEpisodeDownloadClick(episodeNumber)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ package ani.dantotsu.media.anime
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
@@ -444,15 +443,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
if (subtitles.isNotEmpty()) {
|
if (subtitles.isNotEmpty()) {
|
||||||
val subtitleNames = subtitles.map { it.language }
|
val subtitleNames = subtitles.map { it.language }
|
||||||
var subtitleToDownload: Subtitle? = null
|
var subtitleToDownload: Subtitle? = null
|
||||||
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
|
requireActivity().customAlertDialog().apply {
|
||||||
.setTitle(R.string.download_subtitle)
|
setTitle(R.string.download_subtitle)
|
||||||
.setSingleChoiceItems(
|
singleChoiceItems(subtitleNames.toTypedArray()) { which ->
|
||||||
subtitleNames.toTypedArray(),
|
|
||||||
-1
|
|
||||||
) { _, which ->
|
|
||||||
subtitleToDownload = subtitles[which]
|
subtitleToDownload = subtitles[which]
|
||||||
}
|
}
|
||||||
.setPositiveButton(R.string.download) { dialog, _ ->
|
setPosButton(R.string.download) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (subtitleToDownload != null) {
|
if (subtitleToDownload != null) {
|
||||||
SubtitleDownloader.downloadSubtitle(
|
SubtitleDownloader.downloadSubtitle(
|
||||||
@@ -466,13 +462,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
setNegButton(R.string.cancel) {}
|
||||||
dialog.dismiss()
|
}.show()
|
||||||
}
|
|
||||||
.show()
|
|
||||||
alertDialog.window?.setDimAmount(0.8f)
|
|
||||||
} else {
|
} else {
|
||||||
snackString(R.string.no_subtitles_available)
|
snackString(R.string.no_subtitles_available)
|
||||||
}
|
}
|
||||||
@@ -490,7 +482,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val downloadAddonManager: DownloadAddonManager = Injekt.get()
|
val downloadAddonManager: DownloadAddonManager = Injekt.get()
|
||||||
if (!downloadAddonManager.isAvailable()){
|
if (!downloadAddonManager.isAvailable()) {
|
||||||
val context = currContext() ?: requireContext()
|
val context = currContext() ?: requireContext()
|
||||||
context.customAlertDialog().apply {
|
context.customAlertDialog().apply {
|
||||||
setTitle(R.string.download_addon_not_installed)
|
setTitle(R.string.download_addon_not_installed)
|
||||||
@@ -571,70 +563,73 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
snackString(R.string.no_video_selected)
|
snackString(R.string.no_video_selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkAudioTracks() {
|
fun checkAudioTracks() {
|
||||||
val audioTracks = extractor.audioTracks.map { it.lang }
|
val audioTracks = extractor.audioTracks.map { it.lang }
|
||||||
if (audioTracks.isNotEmpty()) {
|
if (audioTracks.isNotEmpty()) {
|
||||||
val audioNamesArray = audioTracks.toTypedArray()
|
val audioNamesArray = audioTracks.toTypedArray()
|
||||||
val checkedItems = BooleanArray(audioNamesArray.size) { false }
|
val checkedItems = BooleanArray(audioNamesArray.size) { false }
|
||||||
val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup)
|
|
||||||
.setTitle(R.string.download_audio_tracks)
|
currContext.customAlertDialog().apply { // ToTest
|
||||||
.setMultiChoiceItems(audioNamesArray, checkedItems) { _, which, isChecked ->
|
setTitle(R.string.download_audio_tracks)
|
||||||
val audioPair = Pair(extractor.audioTracks[which].url, extractor.audioTracks[which].lang)
|
multiChoiceItems(audioNamesArray, checkedItems) {
|
||||||
if (isChecked) {
|
it.forEachIndexed { index, isChecked ->
|
||||||
selectedAudioTracks.add(audioPair)
|
val audioPair = Pair(
|
||||||
} else {
|
extractor.audioTracks[index].url,
|
||||||
selectedAudioTracks.remove(audioPair)
|
extractor.audioTracks[index].lang
|
||||||
|
)
|
||||||
|
if (isChecked) {
|
||||||
|
selectedAudioTracks.add(audioPair)
|
||||||
|
} else {
|
||||||
|
selectedAudioTracks.remove(audioPair)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setPositiveButton(R.string.download) { _, _ ->
|
setPosButton(R.string.download) {
|
||||||
dialog?.dismiss()
|
|
||||||
go()
|
go()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.skip) { dialog, _ ->
|
setNegButton(R.string.skip) {
|
||||||
selectedAudioTracks = mutableListOf()
|
selectedAudioTracks = mutableListOf()
|
||||||
go()
|
go()
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
setNeutralButton(R.string.cancel) {
|
||||||
selectedAudioTracks = mutableListOf()
|
selectedAudioTracks = mutableListOf()
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.show()
|
show()
|
||||||
alertDialog.window?.setDimAmount(0.8f)
|
}
|
||||||
} else {
|
} else {
|
||||||
go()
|
go()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subtitles.isNotEmpty()) {
|
if (subtitles.isNotEmpty()) { // ToTest
|
||||||
val subtitleNamesArray = subtitleNames.toTypedArray()
|
val subtitleNamesArray = subtitleNames.toTypedArray()
|
||||||
val checkedItems = BooleanArray(subtitleNamesArray.size) { false }
|
val checkedItems = BooleanArray(subtitleNamesArray.size) { false }
|
||||||
|
|
||||||
val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup)
|
currContext.customAlertDialog().apply {
|
||||||
.setTitle(R.string.download_subtitle)
|
setTitle(R.string.download_subtitle)
|
||||||
.setMultiChoiceItems(subtitleNamesArray, checkedItems) { _, which, isChecked ->
|
multiChoiceItems(subtitleNamesArray, checkedItems) {
|
||||||
val subtitlePair = Pair(subtitles[which].file.url, subtitles[which].language)
|
it.forEachIndexed { index, isChecked ->
|
||||||
if (isChecked) {
|
val subtitlePair =
|
||||||
selectedSubtitles.add(subtitlePair)
|
Pair(subtitles[index].file.url, subtitles[index].language)
|
||||||
} else {
|
if (isChecked) {
|
||||||
selectedSubtitles.remove(subtitlePair)
|
selectedSubtitles.add(subtitlePair)
|
||||||
|
} else {
|
||||||
|
selectedSubtitles.remove(subtitlePair)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setPositiveButton(R.string.download) { _, _ ->
|
setPosButton(R.string.download) {
|
||||||
dialog?.dismiss()
|
|
||||||
checkAudioTracks()
|
checkAudioTracks()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.skip) { dialog, _ ->
|
setNegButton(R.string.skip) {
|
||||||
selectedSubtitles = mutableListOf()
|
selectedSubtitles = mutableListOf()
|
||||||
checkAudioTracks()
|
checkAudioTracks()
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
setNeutralButton(R.string.cancel) {
|
||||||
selectedSubtitles = mutableListOf()
|
selectedSubtitles = mutableListOf()
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.show()
|
show()
|
||||||
alertDialog.window?.setDimAmount(0.8f)
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
checkAudioTracks()
|
checkAudioTracks()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,12 @@ class TrackGroupDialogFragment(
|
|||||||
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
trackGroups[position].let { trackGroup ->
|
trackGroups[position].let { trackGroup ->
|
||||||
if (overrideTrackNames?.getOrNull(position - (trackGroups.size - (overrideTrackNames?.size?:0))) != null) {
|
if (overrideTrackNames?.getOrNull(
|
||||||
val pair = overrideTrackNames!![position - (trackGroups.size - overrideTrackNames!!.size)]
|
position - (trackGroups.size - (overrideTrackNames?.size ?: 0))
|
||||||
|
) != null
|
||||||
|
) {
|
||||||
|
val pair =
|
||||||
|
overrideTrackNames!![position - (trackGroups.size - overrideTrackNames!!.size)]
|
||||||
binding.subtitleTitle.text =
|
binding.subtitleTitle.text =
|
||||||
"[${pair.second}] ${pair.first}"
|
"[${pair.second}] ${pair.first}"
|
||||||
} else when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) {
|
} else when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) {
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import ani.dantotsu.databinding.ItemCommentsBinding
|
|||||||
import ani.dantotsu.getAppString
|
import ani.dantotsu.getAppString
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.openImage
|
import ani.dantotsu.openImage
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast
|
import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast
|
||||||
import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio
|
import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.xwray.groupie.GroupieAdapter
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.Section
|
import com.xwray.groupie.Section
|
||||||
import com.xwray.groupie.viewbinding.BindableItem
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
@@ -385,19 +385,14 @@ class CommentItem(
|
|||||||
* @param callback the callback to call when the user clicks yes
|
* @param callback the callback to call when the user clicks yes
|
||||||
*/
|
*/
|
||||||
private fun dialogBuilder(title: String, message: String, callback: () -> Unit) {
|
private fun dialogBuilder(title: String, message: String, callback: () -> Unit) {
|
||||||
val alertDialog =
|
commentsFragment.activity.customAlertDialog().apply {
|
||||||
android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup)
|
setTitle(title)
|
||||||
.setTitle(title)
|
setMessage(message)
|
||||||
.setMessage(message)
|
setPosButton("Yes") {
|
||||||
.setPositiveButton("Yes") { dialog, _ ->
|
callback()
|
||||||
callback()
|
}
|
||||||
dialog.dismiss()
|
setNegButton("No") {}
|
||||||
}
|
}.show()
|
||||||
.setNegativeButton("No") { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
val dialog = alertDialog.show()
|
|
||||||
dialog?.window?.setDimAmount(0.8f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val usernameColors: Array<String> = arrayOf(
|
private val usernameColors: Array<String> = arrayOf(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ani.dantotsu.media.comments
|
|||||||
|
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context.INPUT_METHOD_SERVICE
|
import android.content.Context.INPUT_METHOD_SERVICE
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -12,7 +11,6 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
@@ -25,6 +23,7 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.comments.Comment
|
import ani.dantotsu.connections.comments.Comment
|
||||||
import ani.dantotsu.connections.comments.CommentResponse
|
import ani.dantotsu.connections.comments.CommentResponse
|
||||||
import ani.dantotsu.connections.comments.CommentsAPI
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
|
import ani.dantotsu.databinding.DialogEdittextBinding
|
||||||
import ani.dantotsu.databinding.FragmentCommentsBinding
|
import ani.dantotsu.databinding.FragmentCommentsBinding
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
@@ -32,8 +31,8 @@ import ani.dantotsu.setBaseline
|
|||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.toast
|
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.xwray.groupie.GroupieAdapter
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.Section
|
import com.xwray.groupie.Section
|
||||||
import io.noties.markwon.editor.MarkwonEditor
|
import io.noties.markwon.editor.MarkwonEditor
|
||||||
@@ -160,35 +159,48 @@ class CommentsFragment : Fragment() {
|
|||||||
popup.inflate(R.menu.comments_sort_menu)
|
popup.inflate(R.menu.comments_sort_menu)
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
binding.openRules.setOnClickListener {
|
||||||
|
activity.customAlertDialog().apply {
|
||||||
|
setTitle("Commenting Rules")
|
||||||
|
.setMessage(
|
||||||
|
"🚨 BREAK ANY RULE = YOU'RE GONE\n\n" +
|
||||||
|
"1. NO RACISM, DISCRIMINATION, OR HATE SPEECH\n" +
|
||||||
|
"2. NO SPAMMING OR SELF-PROMOTION\n" +
|
||||||
|
"3. ABSOLUTELY NO NSFW CONTENT\n" +
|
||||||
|
"4. ENGLISH ONLY – NO EXCEPTIONS\n" +
|
||||||
|
"5. NO IMPERSONATION, HARASSMENT, OR ABUSE\n" +
|
||||||
|
"6. NO ILLEGAL CONTENT OR EXTREME DISRESPECT TOWARDS ANY FANDOM\n" +
|
||||||
|
"7. DO NOT REQUEST OR SHARE REPOSITORIES/EXTENSIONS\n" +
|
||||||
|
"8. SPOILERS ALLOWED ONLY WITH SPOILER TAGS AND A WARNING\n" +
|
||||||
|
"9. NO SEXUALIZING OR INAPPROPRIATE COMMENTS ABOUT MINOR CHARACTERS\n" +
|
||||||
|
"10. IF IT'S WRONG, DON'T POST IT!\n\n"
|
||||||
|
)
|
||||||
|
setNegButton("I Understand") {}
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.commentFilter.setOnClickListener {
|
binding.commentFilter.setOnClickListener {
|
||||||
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup)
|
activity.customAlertDialog().apply {
|
||||||
.setTitle("Enter a chapter/episode number tag")
|
val customView = DialogEdittextBinding.inflate(layoutInflater)
|
||||||
.setView(R.layout.dialog_edittext)
|
setTitle("Enter a chapter/episode number tag")
|
||||||
.setPositiveButton("OK") { dialog, _ ->
|
setCustomView(customView.root)
|
||||||
val editText =
|
setPosButton("OK") {
|
||||||
(dialog as AlertDialog).findViewById<EditText>(R.id.dialogEditText)
|
val text = customView.dialogEditText.text.toString()
|
||||||
val text = editText?.text.toString()
|
|
||||||
filterTag = text.toIntOrNull()
|
filterTag = text.toIntOrNull()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
loadAndDisplayComments()
|
loadAndDisplayComments()
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNeutralButton("Clear") { dialog, _ ->
|
setNeutralButton("Clear") {
|
||||||
filterTag = null
|
filterTag = null
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
loadAndDisplayComments()
|
loadAndDisplayComments()
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
setNegButton("Cancel") { filterTag = null }
|
||||||
filterTag = null
|
show()
|
||||||
dialog.dismiss()
|
}
|
||||||
}
|
|
||||||
val dialog = alertDialog.show()
|
|
||||||
dialog?.window?.setDimAmount(0.8f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isFetching = false
|
var isFetching = false
|
||||||
@@ -303,13 +315,12 @@ class CommentsFragment : Fragment() {
|
|||||||
|
|
||||||
activity.binding.commentLabel.setOnClickListener {
|
activity.binding.commentLabel.setOnClickListener {
|
||||||
//alert dialog to enter a number, with a cancel and ok button
|
//alert dialog to enter a number, with a cancel and ok button
|
||||||
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup)
|
activity.customAlertDialog().apply {
|
||||||
.setTitle("Enter a chapter/episode number tag")
|
val customView = DialogEdittextBinding.inflate(layoutInflater)
|
||||||
.setView(R.layout.dialog_edittext)
|
setTitle("Enter a chapter/episode number tag")
|
||||||
.setPositiveButton("OK") { dialog, _ ->
|
setCustomView(customView.root)
|
||||||
val editText =
|
setPosButton("OK") {
|
||||||
(dialog as AlertDialog).findViewById<EditText>(R.id.dialogEditText)
|
val text = customView.dialogEditText.text.toString()
|
||||||
val text = editText?.text.toString()
|
|
||||||
tag = text.toIntOrNull()
|
tag = text.toIntOrNull()
|
||||||
if (tag == null) {
|
if (tag == null) {
|
||||||
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
@@ -324,28 +335,25 @@ class CommentsFragment : Fragment() {
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNeutralButton("Clear") { dialog, _ ->
|
setNeutralButton("Clear") {
|
||||||
tag = null
|
tag = null
|
||||||
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
resources,
|
resources,
|
||||||
R.drawable.ic_label_off_24,
|
R.drawable.ic_label_off_24,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
setNegButton("Cancel") {
|
||||||
tag = null
|
tag = null
|
||||||
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
activity.binding.commentLabel.background = ResourcesCompat.getDrawable(
|
||||||
resources,
|
resources,
|
||||||
R.drawable.ic_label_off_24,
|
R.drawable.ic_label_off_24,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
val dialog = alertDialog.show()
|
show()
|
||||||
dialog?.window?.setDimAmount(0.8f)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,11 +371,6 @@ class CommentsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
@@ -579,31 +582,28 @@ class CommentsFragment : Fragment() {
|
|||||||
* Called when the user tries to comment for the first time
|
* Called when the user tries to comment for the first time
|
||||||
*/
|
*/
|
||||||
private fun showCommentRulesDialog() {
|
private fun showCommentRulesDialog() {
|
||||||
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup)
|
activity.customAlertDialog().apply {
|
||||||
.setTitle("Commenting Rules")
|
setTitle("Commenting Rules")
|
||||||
.setMessage(
|
.setMessage(
|
||||||
"I WILL BAN YOU WITHOUT HESITATION\n" +
|
"🚨 BREAK ANY RULE = YOU'RE GONE\n\n" +
|
||||||
"1. No racism\n" +
|
"1. NO RACISM, DISCRIMINATION, OR HATE SPEECH\n" +
|
||||||
"2. No hate speech\n" +
|
"2. NO SPAMMING OR SELF-PROMOTION\n" +
|
||||||
"3. No spam\n" +
|
"3. ABSOLUTELY NO NSFW CONTENT\n" +
|
||||||
"4. No NSFW content\n" +
|
"4. ENGLISH ONLY – NO EXCEPTIONS\n" +
|
||||||
"6. ENGLISH ONLY\n" +
|
"5. NO IMPERSONATION, HARASSMENT, OR ABUSE\n" +
|
||||||
"7. No self promotion\n" +
|
"6. NO ILLEGAL CONTENT OR EXTREME DISRESPECT TOWARDS ANY FANDOM\n" +
|
||||||
"8. No impersonation\n" +
|
"7. DO NOT REQUEST OR SHARE REPOSITORIES/EXTENSIONS\n" +
|
||||||
"9. No harassment\n" +
|
"8. SPOILERS ALLOWED ONLY WITH SPOILER TAGS AND A WARNING\n" +
|
||||||
"10. No illegal content\n" +
|
"9. NO SEXUALIZING OR INAPPROPRIATE COMMENTS ABOUT MINOR CHARACTERS\n" +
|
||||||
"11. Anything you know you shouldn't comment\n"
|
"10. IF IT'S WRONG, DON'T POST IT!\n\n"
|
||||||
)
|
)
|
||||||
.setPositiveButton("I Understand") { dialog, _ ->
|
setPosButton("I Understand") {
|
||||||
dialog.dismiss()
|
|
||||||
PrefManager.setVal(PrefName.FirstComment, false)
|
PrefManager.setVal(PrefName.FirstComment, false)
|
||||||
processComment()
|
processComment()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
setNegButton(R.string.cancel)
|
||||||
dialog.dismiss()
|
show()
|
||||||
}
|
}
|
||||||
val dialog = alertDialog.show()
|
|
||||||
dialog?.window?.setDimAmount(0.8f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processComment() {
|
private fun processComment() {
|
||||||
@@ -709,4 +709,4 @@ class CommentsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.io.Serializable
|
|||||||
|
|
||||||
data class Manga(
|
data class Manga(
|
||||||
var totalChapters: Int? = null,
|
var totalChapters: Int? = null,
|
||||||
var selectedChapter: String? = null,
|
var selectedChapter: MangaChapter? = null,
|
||||||
var chapters: MutableMap<String, MangaChapter>? = null,
|
var chapters: MutableMap<String, MangaChapter>? = null,
|
||||||
var slug: String? = null,
|
var slug: String? = null,
|
||||||
var author: Author? = null,
|
var author: Author? = null,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
data class ImageData(
|
data class ImageData(
|
||||||
@@ -76,7 +77,7 @@ fun saveImage(
|
|||||||
uri?.let {
|
uri?.let {
|
||||||
contentResolver.openOutputStream(it)?.use { os ->
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
bitmap.compress(format, quality, os)
|
bitmap.compress(format, quality, os)
|
||||||
}
|
} ?: throw FileNotFoundException("Failed to open output stream for URI: $uri")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val directory =
|
val directory =
|
||||||
@@ -86,12 +87,20 @@ fun saveImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val file = File(directory, filename)
|
val file = File(directory, filename)
|
||||||
|
|
||||||
|
// Check if the file already exists
|
||||||
|
if (file.exists()) {
|
||||||
|
println("File already exists: ${file.absolutePath}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
FileOutputStream(file).use { outputStream ->
|
FileOutputStream(file).use { outputStream ->
|
||||||
bitmap.compress(format, quality, outputStream)
|
bitmap.compress(format, quality, outputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
println("File not found: ${e.message}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Handle exception here
|
|
||||||
println("Exception while saving image: ${e.message}")
|
println("Exception while saving image: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,6 @@ data class MangaChapter(
|
|||||||
private val dualPages = mutableListOf<Pair<MangaImage, MangaImage?>>()
|
private val dualPages = mutableListOf<Pair<MangaImage, MangaImage?>>()
|
||||||
fun dualPages(): List<Pair<MangaImage, MangaImage?>> = dualPages
|
fun dualPages(): List<Pair<MangaImage, MangaImage?>> = dualPages
|
||||||
|
|
||||||
|
fun uniqueNumber(): String = "${number}-${scanlator ?: "Unknown"}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -63,7 +62,7 @@ class MangaChapterAdapter(
|
|||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,7 @@ class MangaChapterAdapter(
|
|||||||
fun startDownload(chapterNumber: String) {
|
fun startDownload(chapterNumber: String) {
|
||||||
activeDownloads.add(chapterNumber)
|
activeDownloads.add(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
@@ -84,17 +83,17 @@ class MangaChapterAdapter(
|
|||||||
activeDownloads.remove(chapterNumber)
|
activeDownloads.remove(chapterNumber)
|
||||||
downloadedChapters.add(chapterNumber)
|
downloadedChapters.add(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = "Downloaded"
|
arr[position].progress = "Downloaded"
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDownload(chapterNumber: String) {
|
fun deleteDownload(chapterNumber: MangaChapter) {
|
||||||
downloadedChapters.remove(chapterNumber)
|
downloadedChapters.remove(chapterNumber.uniqueNumber())
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber.uniqueNumber() }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = ""
|
arr[position].progress = ""
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
@@ -105,7 +104,7 @@ class MangaChapterAdapter(
|
|||||||
activeDownloads.remove(chapterNumber)
|
activeDownloads.remove(chapterNumber)
|
||||||
downloadedChapters.remove(chapterNumber)
|
downloadedChapters.remove(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = ""
|
arr[position].progress = ""
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
@@ -114,7 +113,7 @@ class MangaChapterAdapter(
|
|||||||
|
|
||||||
fun updateDownloadProgress(chapterNumber: String, progress: Int) {
|
fun updateDownloadProgress(chapterNumber: String, progress: Int) {
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = "Downloading: ${progress}%"
|
arr[position].progress = "Downloading: ${progress}%"
|
||||||
|
|
||||||
@@ -127,7 +126,8 @@ class MangaChapterAdapter(
|
|||||||
if (position < 0 || position >= arr.size) return
|
if (position < 0 || position >= arr.size) return
|
||||||
for (i in 0..<n) {
|
for (i in 0..<n) {
|
||||||
if (position + i < arr.size) {
|
if (position + i < arr.size) {
|
||||||
val chapterNumber = arr[position + i].number
|
val chapter = arr[position + i]
|
||||||
|
val chapterNumber = chapter.uniqueNumber()
|
||||||
if (activeDownloads.contains(chapterNumber)) {
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
//do nothing
|
//do nothing
|
||||||
continue
|
continue
|
||||||
@@ -135,8 +135,8 @@ class MangaChapterAdapter(
|
|||||||
//do nothing
|
//do nothing
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
fragment.onMangaChapterDownloadClick(chapterNumber)
|
fragment.onMangaChapterDownloadClick(chapter)
|
||||||
startDownload(chapterNumber)
|
startDownload(chapter.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,28 +201,29 @@ class MangaChapterAdapter(
|
|||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition])
|
||||||
}
|
}
|
||||||
binding.itemDownload.setOnClickListener {
|
binding.itemDownload.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
||||||
val chapterNumber = arr[bindingAdapterPosition].number
|
val chapter = arr[bindingAdapterPosition]
|
||||||
|
val chapterNumber = chapter.uniqueNumber()
|
||||||
if (activeDownloads.contains(chapterNumber)) {
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
fragment.onMangaChapterStopDownloadClick(chapterNumber)
|
fragment.onMangaChapterStopDownloadClick(chapter)
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else if (downloadedChapters.contains(chapterNumber)) {
|
} else if (downloadedChapters.contains(chapterNumber)) {
|
||||||
it.context.customAlertDialog().apply {
|
it.context.customAlertDialog().apply {
|
||||||
setTitle("Delete Chapter")
|
setTitle("Delete Chapter")
|
||||||
setMessage("Are you sure you want to delete ${chapterNumber}?")
|
setMessage("Are you sure you want to delete ${chapterNumber}?")
|
||||||
setPosButton(R.string.delete) {
|
setPosButton(R.string.delete) {
|
||||||
fragment.onMangaChapterRemoveDownloadClick(chapterNumber)
|
fragment.onMangaChapterRemoveDownloadClick(chapter)
|
||||||
}
|
}
|
||||||
setNegButton(R.string.cancel)
|
setNegButton(R.string.cancel)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else {
|
} else {
|
||||||
fragment.onMangaChapterDownloadClick(chapterNumber)
|
fragment.onMangaChapterDownloadClick(chapter)
|
||||||
startDownload(chapterNumber)
|
startDownload(chapter.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +278,7 @@ class MangaChapterAdapter(
|
|||||||
is ChapterListViewHolder -> {
|
is ChapterListViewHolder -> {
|
||||||
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.uniqueNumber(), ep.progress)
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
binding.itemChapterNumber.text = ep.number
|
binding.itemChapterNumber.text = ep.number
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user