mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-11 09:56:15 +00:00
295
.github/workflows/beta.yml
vendored
295
.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@v6
|
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["rebelonion"]="\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="16721401"
|
||||||
|
contributor_colors["grayankit"]="#350297"
|
||||||
|
contributor_colors["ibo"]="#ff9b46"
|
||||||
|
contributor_colors["aayush262"]="#5d689d"
|
||||||
|
contributor_colors["Sadwhy"]="#ff7e95"
|
||||||
|
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=$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=$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=$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 }}
|
||||||
|
|||||||
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
|
||||||
@@ -11,12 +11,12 @@ 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.1.0"
|
||||||
versionCode 300100000
|
versionCode 300100000
|
||||||
@@ -101,6 +101,8 @@ dependencies {
|
|||||||
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.11.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 +113,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.5.0'
|
||||||
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
||||||
implementation "androidx.media3:media3-ui:$exo_version"
|
implementation "androidx.media3:media3-ui:$exo_version"
|
||||||
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
|
||||||
@@ -121,6 +123,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.3"
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
@@ -131,7 +135,7 @@ dependencies {
|
|||||||
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
||||||
implementation 'com.github.AAChartModel:AAChartCore-Kotlin: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,7 +161,7 @@ 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.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import ani.dantotsu.util.Logger
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.time.delay
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
@@ -69,7 +70,11 @@ object AppUpdater {
|
|||||||
)
|
)
|
||||||
addView(
|
addView(
|
||||||
TextView(activity).apply {
|
TextView(activity).apply {
|
||||||
val markWon = buildMarkwon(activity, false)
|
val markWon = try { //slower phones can destroy the activity before this is done
|
||||||
|
buildMarkwon(activity, false)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
markWon.setMarkdown(this, md)
|
markWon.setMarkdown(this, md)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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" />
|
||||||
@@ -115,7 +116,8 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="application/epub+zip" />
|
<data android:host="*"/>
|
||||||
|
<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 +133,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 +158,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 +198,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,25 +375,31 @@
|
|||||||
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:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
<data android:pathPattern=".*\\.ani" />
|
<data android:pathPattern=".*\\.ani" />
|
||||||
<data android:pathPattern=".*\\.sani" />
|
<data android:pathPattern=".*\\.sani" />
|
||||||
<data android:host="*" />
|
<data android:host="*" />
|
||||||
</intent-filter>
|
</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" />
|
||||||
|
<!-- Support both schemes -->
|
||||||
|
<data android:scheme="tachiyomi"/>
|
||||||
|
<data android:host="add-repo"/>
|
||||||
|
<data android:scheme="aniyomi"/>
|
||||||
|
<data android:host="add-repo"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallActivity"
|
android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallActivity"
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ class App : MultiDexApplication() {
|
|||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 0) {
|
||||||
|
if (BuildConfig.FLAVOR.contains("fdroid")) {
|
||||||
|
PrefManager.setVal(PrefName.CommentsEnabled, 2)
|
||||||
|
} else {
|
||||||
|
PrefManager.setVal(PrefName.CommentsEnabled, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
animeExtensionManager = Injekt.get()
|
animeExtensionManager = Injekt.get()
|
||||||
animeExtensionManager.findAvailableExtensions()
|
animeExtensionManager.findAvailableExtensions()
|
||||||
@@ -128,7 +136,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
|
||||||
@@ -119,8 +117,8 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withC
|
|||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
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.CustomTarget
|
||||||
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
|
||||||
@@ -154,6 +152,7 @@ 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
|
||||||
@@ -314,6 +313,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,7 +854,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 +874,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,
|
||||||
@@ -920,7 +921,7 @@ fun shareImage(title: String, bitmap: Bitmap, context: Context) {
|
|||||||
intent.putExtra(Intent.EXTRA_STREAM, contentUri)
|
intent.putExtra(Intent.EXTRA_STREAM, contentUri)
|
||||||
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 {
|
||||||
|
|
||||||
@@ -1500,7 +1467,6 @@ fun buildMarkwon(
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(
|
override fun onLoadFailed(
|
||||||
e: GlideException?,
|
e: GlideException?,
|
||||||
model: Any?,
|
model: Any?,
|
||||||
@@ -1527,3 +1493,34 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,101 @@ 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.host == "add-repo") {
|
||||||
|
val url = uri.getQueryParameter("url") ?: throw Exception("No url for repo import")
|
||||||
|
val prefName = if (uri.scheme == "tachiyomi") {
|
||||||
|
PrefName.MangaExtensionRepos
|
||||||
|
} else {
|
||||||
|
PrefName.AnimeExtensionRepos
|
||||||
|
}
|
||||||
|
val savedRepos: Set<String> = PrefManager.getVal(prefName)
|
||||||
|
val newRepos = savedRepos.toMutableSet()
|
||||||
|
AddRepositoryBottomSheet.addRepoWarning(this) {
|
||||||
|
newRepos.add(url)
|
||||||
|
PrefManager.setVal(prefName, newRepos)
|
||||||
|
toast("${if (uri.scheme == "tachiyomi") "Manga" else "Anime"} 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 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -191,6 +311,7 @@ object Anilist {
|
|||||||
)
|
)
|
||||||
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
|
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
|
||||||
Logger.log("Remaining requests: $remaining")
|
Logger.log("Remaining requests: $remaining")
|
||||||
|
println("Remaining requests: $remaining")
|
||||||
if (json.code == 429) {
|
if (json.code == 429) {
|
||||||
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
|
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
|
||||||
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0
|
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0
|
||||||
|
|||||||
@@ -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,51 @@ 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 +184,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 +238,168 @@ 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) ?: "Success")
|
||||||
?: (currContext()?.getString(ani.dantotsu.R.string.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) ?: "Success")
|
||||||
?: (currContext()?.getString(ani.dantotsu.R.string.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 {
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import ani.dantotsu.connections.anilist.Anilist.authorRoles
|
|||||||
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
||||||
import ani.dantotsu.connections.anilist.api.FeedResponse
|
import ani.dantotsu.connections.anilist.api.FeedResponse
|
||||||
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.MediaList
|
||||||
import ani.dantotsu.connections.anilist.api.NotificationResponse
|
import ani.dantotsu.connections.anilist.api.NotificationResponse
|
||||||
import ani.dantotsu.connections.anilist.api.Page
|
import ani.dantotsu.connections.anilist.api.Page
|
||||||
import ani.dantotsu.connections.anilist.api.Query
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
import ani.dantotsu.connections.anilist.api.ReplyResponse
|
import ani.dantotsu.connections.anilist.api.ReplyResponse
|
||||||
import ani.dantotsu.connections.anilist.api.ToggleLike
|
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
@@ -27,6 +28,7 @@ import ani.dantotsu.settings.saving.PrefName
|
|||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@@ -41,8 +43,8 @@ class AnilistQueries {
|
|||||||
suspend fun getUserData(): Boolean {
|
suspend fun getUserData(): Boolean {
|
||||||
val response: Query.Viewer?
|
val response: Query.Viewer?
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
response =
|
response = executeQuery(
|
||||||
executeQuery("""{Viewer{name options{displayAdultContent}avatar{medium}bannerImage id mediaListOptions{rowOrder animeList{sectionOrder customLists}mangaList{sectionOrder customLists}}statistics{anime{episodesWatched}manga{chaptersRead}}unreadNotificationCount}}""")
|
"""{Viewer{name options{timezone titleLanguage staffNameLanguage activityMergeTime airingNotifications displayAdultContent restrictMessagesToFollowing} avatar{medium} bannerImage id mediaListOptions{scoreFormat rowOrder animeList{customLists} mangaList{customLists}} statistics{anime{episodesWatched} manga{chaptersRead}} unreadNotificationCount}}""")
|
||||||
}.also { println("time : $it") }
|
}.also { println("time : $it") }
|
||||||
val user = response?.data?.user ?: return false
|
val user = response?.data?.user ?: return false
|
||||||
|
|
||||||
@@ -59,6 +61,27 @@ class AnilistQueries {
|
|||||||
val unread = PrefManager.getVal<Int>(PrefName.UnreadCommentNotifications)
|
val unread = PrefManager.getVal<Int>(PrefName.UnreadCommentNotifications)
|
||||||
Anilist.unreadNotificationCount += unread
|
Anilist.unreadNotificationCount += unread
|
||||||
Anilist.initialized = true
|
Anilist.initialized = true
|
||||||
|
|
||||||
|
user.options?.let {
|
||||||
|
Anilist.titleLanguage = it.titleLanguage.toString()
|
||||||
|
Anilist.staffNameLanguage = it.staffNameLanguage.toString()
|
||||||
|
Anilist.airingNotifications = it.airingNotifications ?: false
|
||||||
|
Anilist.restrictMessagesToFollowing = it.restrictMessagesToFollowing ?: false
|
||||||
|
Anilist.timezone = it.timezone
|
||||||
|
Anilist.activityMergeTime = it.activityMergeTime
|
||||||
|
}
|
||||||
|
user.mediaListOptions?.let {
|
||||||
|
Anilist.scoreFormat = it.scoreFormat.toString()
|
||||||
|
Anilist.rowOrder = it.rowOrder
|
||||||
|
|
||||||
|
it.animeList?.let { animeList ->
|
||||||
|
Anilist.animeCustomLists = animeList.customLists
|
||||||
|
}
|
||||||
|
|
||||||
|
it.mangaList?.let { mangaList ->
|
||||||
|
Anilist.mangaCustomLists = mangaList.customLists
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +98,7 @@ class AnilistQueries {
|
|||||||
media.cameFromContinue = false
|
media.cameFromContinue = false
|
||||||
|
|
||||||
val query =
|
val query =
|
||||||
"""{Media(id:${media.id}){id favourites popularity episodes chapters 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}}}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role 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{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
|
"""{Media(id:${media.id}){id favourites popularity episodes chapters 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}}}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role 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{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val anilist = async {
|
val anilist = async {
|
||||||
var response = executeQuery<Query.Media>(query, force = true)
|
var response = executeQuery<Query.Media>(query, force = true)
|
||||||
@@ -90,7 +113,7 @@ class AnilistQueries {
|
|||||||
media.popularity = fetchedMedia.popularity
|
media.popularity = fetchedMedia.popularity
|
||||||
media.startDate = fetchedMedia.startDate
|
media.startDate = fetchedMedia.startDate
|
||||||
media.endDate = fetchedMedia.endDate
|
media.endDate = fetchedMedia.endDate
|
||||||
|
media.streamingEpisodes = fetchedMedia.streamingEpisodes
|
||||||
if (fetchedMedia.genres != null) {
|
if (fetchedMedia.genres != null) {
|
||||||
media.genres = arrayListOf()
|
media.genres = arrayListOf()
|
||||||
fetchedMedia.genres?.forEach { i ->
|
fetchedMedia.genres?.forEach { i ->
|
||||||
@@ -211,7 +234,7 @@ class AnilistQueries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fetchedMedia.reviews?.nodes != null){
|
if (fetchedMedia.reviews?.nodes != null) {
|
||||||
media.review = fetchedMedia.reviews!!.nodes as ArrayList<Query.Review>
|
media.review = fetchedMedia.reviews!!.nodes as ArrayList<Query.Review>
|
||||||
}
|
}
|
||||||
if (user?.mediaList?.isNotEmpty() == true) {
|
if (user?.mediaList?.isNotEmpty() == true) {
|
||||||
@@ -376,9 +399,8 @@ class AnilistQueries {
|
|||||||
}
|
}
|
||||||
return media
|
return media
|
||||||
}
|
}
|
||||||
private fun continueMediaQuery(type: String, status: String): String {
|
|
||||||
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } """
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun favMedia(anime: Boolean, id: Int? = Anilist.userid): ArrayList<Media> {
|
private suspend fun favMedia(anime: Boolean, id: Int? = Anilist.userid): ArrayList<Media> {
|
||||||
var hasNextPage = true
|
var hasNextPage = true
|
||||||
@@ -404,10 +426,68 @@ class AnilistQueries {
|
|||||||
return responseArray
|
return responseArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getUserStatus(): ArrayList<User>? {
|
||||||
|
val toShow: List<Boolean> =
|
||||||
|
PrefManager.getVal(PrefName.HomeLayout)
|
||||||
|
if (toShow.getOrNull(7) != true) return null
|
||||||
|
val query = """{Page1:${status(1)}Page2:${status(2)}}"""
|
||||||
|
val response = executeQuery<Query.HomePageMedia>(query)
|
||||||
|
val list = mutableListOf<User>()
|
||||||
|
val threeDaysAgo = Calendar.getInstance().apply {
|
||||||
|
add(Calendar.DAY_OF_MONTH, -3)
|
||||||
|
}.timeInMillis
|
||||||
|
if (response?.data?.page1 != null && response.data.page2 != null) {
|
||||||
|
val activities = listOf(
|
||||||
|
response.data.page1.activities,
|
||||||
|
response.data.page2.activities
|
||||||
|
).asSequence().flatten()
|
||||||
|
.filter { it.typename != "MessageActivity" }
|
||||||
|
.filter { if (Anilist.adult) true else it.media?.isAdult != true }
|
||||||
|
.filter { it.createdAt * 1000L > threeDaysAgo }.toList()
|
||||||
|
.sortedByDescending { it.createdAt }
|
||||||
|
val anilistActivities = mutableListOf<User>()
|
||||||
|
val groupedActivities = activities.groupBy { it.userId }
|
||||||
|
|
||||||
|
groupedActivities.forEach { (_, userActivities) ->
|
||||||
|
val user = userActivities.firstOrNull()?.user
|
||||||
|
if (user != null) {
|
||||||
|
val userToAdd = User(
|
||||||
|
user.id,
|
||||||
|
user.name ?: "",
|
||||||
|
user.avatar?.medium,
|
||||||
|
user.bannerImage,
|
||||||
|
activity = userActivities.sortedBy { it.createdAt }.toList()
|
||||||
|
)
|
||||||
|
if (user.id == Anilist.userid) {
|
||||||
|
anilistActivities.add(0, userToAdd)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
list.add(userToAdd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anilistActivities.isEmpty() && Anilist.token != null) {
|
||||||
|
anilistActivities.add(
|
||||||
|
0,
|
||||||
|
User(
|
||||||
|
Anilist.userid!!,
|
||||||
|
Anilist.username!!,
|
||||||
|
Anilist.avatar,
|
||||||
|
Anilist.bg,
|
||||||
|
activity = listOf()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
list.addAll(0, anilistActivities)
|
||||||
|
return list.toCollection(ArrayList())
|
||||||
|
} else return null
|
||||||
|
}
|
||||||
private fun favMediaQuery(anime: Boolean, page: Int, id: Int? = Anilist.userid): String {
|
private fun favMediaQuery(anime: Boolean, page: Int, id: Int? = Anilist.userid): String {
|
||||||
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}"""
|
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recommendationQuery(): String {
|
private fun recommendationQuery(): String {
|
||||||
return """ Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } """
|
return """ Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } """
|
||||||
}
|
}
|
||||||
@@ -415,250 +495,183 @@ class AnilistQueries {
|
|||||||
private fun recommendationPlannedQuery(type: String): String {
|
private fun recommendationPlannedQuery(type: String): String {
|
||||||
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING${if (type == "ANIME") ", sort: MEDIA_POPULARITY_DESC" else ""} ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } }"""
|
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING${if (type == "ANIME") ", sort: MEDIA_POPULARITY_DESC" else ""} ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } }"""
|
||||||
}
|
}
|
||||||
|
private fun continueMediaQuery(type: String, status: String): String {
|
||||||
suspend fun initHomePage(): Map<String, ArrayList<*>> {
|
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } """
|
||||||
|
}
|
||||||
|
suspend fun initHomePage(): Map<String, ArrayList<Media>> {
|
||||||
val removeList = PrefManager.getCustomVal("removeList", setOf<Int>())
|
val removeList = PrefManager.getCustomVal("removeList", setOf<Int>())
|
||||||
|
val hidePrivate = PrefManager.getVal<Boolean>(PrefName.HidePrivate)
|
||||||
val removedMedia = ArrayList<Media>()
|
val removedMedia = ArrayList<Media>()
|
||||||
val toShow: List<Boolean> =
|
val toShow: List<Boolean> =
|
||||||
PrefManager.getVal(PrefName.HomeLayout) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations
|
PrefManager.getVal(PrefName.HomeLayout) // list of booleans for what to show
|
||||||
var query = """{"""
|
|
||||||
if (toShow.getOrNull(0) == true) query += """currentAnime: ${
|
|
||||||
continueMediaQuery(
|
|
||||||
"ANIME",
|
|
||||||
"CURRENT"
|
|
||||||
)
|
|
||||||
}, repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}"""
|
|
||||||
if (toShow.getOrNull(1) == true) query += """favoriteAnime: ${favMediaQuery(true, 1)}"""
|
|
||||||
if (toShow.getOrNull(2) == true) query += """plannedAnime: ${
|
|
||||||
continueMediaQuery(
|
|
||||||
"ANIME",
|
|
||||||
"PLANNING"
|
|
||||||
)
|
|
||||||
}"""
|
|
||||||
if (toShow.getOrNull(3) == true) query += """currentManga: ${
|
|
||||||
continueMediaQuery(
|
|
||||||
"MANGA",
|
|
||||||
"CURRENT"
|
|
||||||
)
|
|
||||||
}, repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}"""
|
|
||||||
if (toShow.getOrNull(4) == true) query += """favoriteManga: ${favMediaQuery(false, 1)}"""
|
|
||||||
if (toShow.getOrNull(5) == true) query += """plannedManga: ${
|
|
||||||
continueMediaQuery(
|
|
||||||
"MANGA",
|
|
||||||
"PLANNING"
|
|
||||||
)
|
|
||||||
}"""
|
|
||||||
if (toShow.getOrNull(6) == true) query += """recommendationQuery: ${recommendationQuery()}, recommendationPlannedQueryAnime: ${
|
|
||||||
recommendationPlannedQuery(
|
|
||||||
"ANIME"
|
|
||||||
)
|
|
||||||
}, recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}"""
|
|
||||||
if (toShow.getOrNull(7) == true) query += "Page1:${status(1)}Page2:${status(2)}"
|
|
||||||
query += """}""".trimEnd(',')
|
|
||||||
|
|
||||||
|
val queries = mutableListOf<String>()
|
||||||
|
if (toShow.getOrNull(0) == true) {
|
||||||
|
queries.add("""currentAnime: ${continueMediaQuery("ANIME", "CURRENT")}""")
|
||||||
|
queries.add("""repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(1) == true) queries.add("""favoriteAnime: ${favMediaQuery(true, 1)}""")
|
||||||
|
if (toShow.getOrNull(2) == true) queries.add(
|
||||||
|
"""plannedAnime: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"ANIME",
|
||||||
|
"PLANNING"
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
if (toShow.getOrNull(3) == true) {
|
||||||
|
queries.add("""currentManga: ${continueMediaQuery("MANGA", "CURRENT")}""")
|
||||||
|
queries.add("""repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""")
|
||||||
|
}
|
||||||
|
if (toShow.getOrNull(4) == true) queries.add(
|
||||||
|
"""favoriteManga: ${
|
||||||
|
favMediaQuery(
|
||||||
|
false,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
if (toShow.getOrNull(5) == true) queries.add(
|
||||||
|
"""plannedManga: ${
|
||||||
|
continueMediaQuery(
|
||||||
|
"MANGA",
|
||||||
|
"PLANNING"
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
if (toShow.getOrNull(6) == true) {
|
||||||
|
queries.add("""recommendationQuery: ${recommendationQuery()}""")
|
||||||
|
queries.add("""recommendationPlannedQueryAnime: ${recommendationPlannedQuery("ANIME")}""")
|
||||||
|
queries.add("""recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
val query = "{${queries.joinToString(",")}}"
|
||||||
val response = executeQuery<Query.HomePageMedia>(query, show = true)
|
val response = executeQuery<Query.HomePageMedia>(query, show = true)
|
||||||
val returnMap = mutableMapOf<String, ArrayList<*>>()
|
val returnMap = mutableMapOf<String, ArrayList<Media>>()
|
||||||
fun current(type: String) {
|
|
||||||
|
fun processMedia(
|
||||||
|
type: String,
|
||||||
|
currentMedia: List<MediaList>?,
|
||||||
|
repeatingMedia: List<MediaList>?
|
||||||
|
) {
|
||||||
val subMap = mutableMapOf<Int, Media>()
|
val subMap = mutableMapOf<Int, Media>()
|
||||||
val returnArray = arrayListOf<Media>()
|
val returnArray = arrayListOf<Media>()
|
||||||
val current =
|
|
||||||
if (type == "Anime") response?.data?.currentAnime else response?.data?.currentManga
|
(currentMedia ?: emptyList()).forEach { entry ->
|
||||||
val repeating =
|
val media = Media(entry)
|
||||||
if (type == "Anime") response?.data?.repeatingAnime else response?.data?.repeatingManga
|
if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) {
|
||||||
current?.lists?.forEach { li ->
|
media.cameFromContinue = true
|
||||||
li.entries?.reversed()?.forEach {
|
subMap[media.id] = media
|
||||||
val m = Media(it)
|
} else {
|
||||||
if (m.id !in removeList) {
|
removedMedia.add(media)
|
||||||
m.cameFromContinue = true
|
|
||||||
subMap[m.id] = m
|
|
||||||
} else {
|
|
||||||
removedMedia.add(m)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repeating?.lists?.forEach { li ->
|
(repeatingMedia ?: emptyList()).forEach { entry ->
|
||||||
li.entries?.reversed()?.forEach {
|
val media = Media(entry)
|
||||||
val m = Media(it)
|
if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) {
|
||||||
if (m.id !in removeList) {
|
media.cameFromContinue = true
|
||||||
m.cameFromContinue = true
|
subMap[media.id] = media
|
||||||
subMap[m.id] = m
|
} else {
|
||||||
} else {
|
removedMedia.add(media)
|
||||||
removedMedia.add(m)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type != "Anime") {
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val list = PrefManager.getNullableCustomVal(
|
||||||
|
"continue${type}List",
|
||||||
|
listOf<Int>(),
|
||||||
|
List::class.java
|
||||||
|
) as List<Int>
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
list.reversed().forEach { id ->
|
||||||
|
subMap[id]?.let { returnArray.add(it) }
|
||||||
|
}
|
||||||
|
subMap.values.forEach {
|
||||||
|
if (!returnArray.contains(it)) returnArray.add(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
returnArray.addAll(subMap.values)
|
returnArray.addAll(subMap.values)
|
||||||
returnMap["current$type"] = returnArray
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val list = PrefManager.getNullableCustomVal(
|
|
||||||
"continueAnimeList",
|
|
||||||
listOf<Int>(),
|
|
||||||
List::class.java
|
|
||||||
) as List<Int>
|
|
||||||
if (list.isNotEmpty()) {
|
|
||||||
list.reversed().forEach {
|
|
||||||
if (subMap.containsKey(it)) returnArray.add(subMap[it]!!)
|
|
||||||
}
|
|
||||||
for (i in subMap) {
|
|
||||||
if (i.value !in returnArray) returnArray.add(i.value)
|
|
||||||
}
|
|
||||||
} else returnArray.addAll(subMap.values)
|
|
||||||
returnMap["current$type"] = returnArray
|
returnMap["current$type"] = returnArray
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun planned(type: String) {
|
if (toShow.getOrNull(0) == true) processMedia(
|
||||||
val subMap = mutableMapOf<Int, Media>()
|
"Anime",
|
||||||
val returnArray = arrayListOf<Media>()
|
response?.data?.currentAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(),
|
||||||
val current =
|
response?.data?.repeatingAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed()
|
||||||
if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga
|
)
|
||||||
current?.lists?.forEach { li ->
|
if (toShow.getOrNull(2) == true) processMedia(
|
||||||
li.entries?.reversed()?.forEach {
|
"AnimePlanned",
|
||||||
val m = Media(it)
|
response?.data?.plannedAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(),
|
||||||
if (m.id !in removeList) {
|
null
|
||||||
m.cameFromContinue = true
|
)
|
||||||
subMap[m.id] = m
|
if (toShow.getOrNull(3) == true) processMedia(
|
||||||
} else {
|
"Manga",
|
||||||
removedMedia.add(m)
|
response?.data?.currentManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(),
|
||||||
}
|
response?.data?.repeatingManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed()
|
||||||
}
|
)
|
||||||
}
|
if (toShow.getOrNull(5) == true) processMedia(
|
||||||
@Suppress("UNCHECKED_CAST")
|
"MangaPlanned",
|
||||||
val list = PrefManager.getNullableCustomVal(
|
response?.data?.plannedManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(),
|
||||||
"continueAnimeList",
|
null
|
||||||
listOf<Int>(),
|
)
|
||||||
List::class.java
|
|
||||||
) as List<Int>
|
|
||||||
if (list.isNotEmpty()) {
|
|
||||||
list.reversed().forEach {
|
|
||||||
if (subMap.containsKey(it)) returnArray.add(subMap[it]!!)
|
|
||||||
}
|
|
||||||
for (i in subMap) {
|
|
||||||
if (i.value !in returnArray) returnArray.add(i.value)
|
|
||||||
}
|
|
||||||
} else returnArray.addAll(subMap.values)
|
|
||||||
returnMap["planned$type"] = returnArray
|
|
||||||
}
|
|
||||||
|
|
||||||
fun favorite(type: String) {
|
fun processFavorites(type: String, favorites: List<MediaEdge>?) {
|
||||||
val favourites =
|
|
||||||
if (type == "Anime") response?.data?.favoriteAnime?.favourites else response?.data?.favoriteManga?.favourites
|
|
||||||
val apiMediaList = if (type == "Anime") favourites?.anime else favourites?.manga
|
|
||||||
val returnArray = arrayListOf<Media>()
|
val returnArray = arrayListOf<Media>()
|
||||||
apiMediaList?.edges?.forEach {
|
favorites?.forEach { edge ->
|
||||||
it.node?.let { i ->
|
edge.node?.let {
|
||||||
val m = Media(i).apply { isFav = true }
|
val media = Media(it).apply { isFav = true }
|
||||||
if (m.id !in removeList) {
|
if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) {
|
||||||
returnArray.add(m)
|
returnArray.add(media)
|
||||||
} else {
|
} else {
|
||||||
removedMedia.add(m)
|
removedMedia.add(media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
returnMap["favorite$type"] = returnArray
|
returnMap["favorite$type"] = returnArray
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toShow.getOrNull(0) == true) {
|
if (toShow.getOrNull(1) == true) processFavorites(
|
||||||
current("Anime")
|
"Anime",
|
||||||
}
|
response?.data?.favoriteAnime?.favourites?.anime?.edges
|
||||||
if (toShow.getOrNull(1) == true) {
|
)
|
||||||
favorite("Anime")
|
if (toShow.getOrNull(4) == true) processFavorites(
|
||||||
}
|
"Manga",
|
||||||
if (toShow.getOrNull(2) == true) {
|
response?.data?.favoriteManga?.favourites?.manga?.edges
|
||||||
planned("Anime")
|
)
|
||||||
}
|
|
||||||
if (toShow.getOrNull(3) == true) {
|
|
||||||
current("Manga")
|
|
||||||
}
|
|
||||||
if (toShow.getOrNull(4) == true) {
|
|
||||||
favorite("Manga")
|
|
||||||
}
|
|
||||||
if (toShow.getOrNull(5) == true) {
|
|
||||||
planned("Manga")
|
|
||||||
}
|
|
||||||
if (toShow.getOrNull(6) == true) {
|
if (toShow.getOrNull(6) == true) {
|
||||||
val subMap = mutableMapOf<Int, Media>()
|
val subMap = mutableMapOf<Int, Media>()
|
||||||
response?.data?.recommendationQuery?.apply {
|
response?.data?.recommendationQuery?.recommendations?.forEach {
|
||||||
recommendations?.onEach {
|
it.mediaRecommendation?.let { json ->
|
||||||
val json = it.mediaRecommendation
|
val media = Media(json)
|
||||||
if (json != null) {
|
media.relation = json.type?.toString()
|
||||||
val m = Media(json)
|
subMap[media.id] = media
|
||||||
m.relation = json.type?.toString()
|
|
||||||
subMap[m.id] = m
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response?.data?.recommendationPlannedQueryAnime?.apply {
|
response?.data?.recommendationPlannedQueryAnime?.lists?.flatMap {
|
||||||
lists?.forEach { li ->
|
it.entries ?: emptyList()
|
||||||
li.entries?.forEach {
|
}?.forEach {
|
||||||
val m = Media(it)
|
val media = Media(it)
|
||||||
if (m.status == "RELEASING" || m.status == "FINISHED") {
|
if (media.status in listOf("RELEASING", "FINISHED")) {
|
||||||
m.relation = it.media?.type?.toString()
|
media.relation = it.media?.type?.toString()
|
||||||
subMap[m.id] = m
|
subMap[media.id] = media
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response?.data?.recommendationPlannedQueryManga?.apply {
|
response?.data?.recommendationPlannedQueryManga?.lists?.flatMap {
|
||||||
lists?.forEach { li ->
|
it.entries ?: emptyList()
|
||||||
li.entries?.forEach {
|
}?.forEach {
|
||||||
val m = Media(it)
|
val media = Media(it)
|
||||||
if (m.status == "RELEASING" || m.status == "FINISHED") {
|
if (media.status in listOf("RELEASING", "FINISHED")) {
|
||||||
m.relation = it.media?.type?.toString()
|
media.relation = it.media?.type?.toString()
|
||||||
subMap[m.id] = m
|
subMap[media.id] = media
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val list = ArrayList(subMap.values.toList())
|
val list = ArrayList(subMap.values).apply { sortByDescending { it.meanScore } }
|
||||||
list.sortByDescending { it.meanScore }
|
|
||||||
returnMap["recommendations"] = list
|
returnMap["recommendations"] = list
|
||||||
}
|
}
|
||||||
if (toShow.getOrNull(7) == true) {
|
|
||||||
val list = mutableListOf<User>()
|
|
||||||
val threeDaysAgo = Calendar.getInstance().apply {
|
|
||||||
add(Calendar.DAY_OF_MONTH, -3)
|
|
||||||
}.timeInMillis
|
|
||||||
if (response?.data?.page1 != null && response.data.page2 != null) {
|
|
||||||
val activities = listOf(
|
|
||||||
response.data.page1.activities,
|
|
||||||
response.data.page2.activities
|
|
||||||
).asSequence().flatten()
|
|
||||||
.filter { it.typename != "MessageActivity" }
|
|
||||||
.filter { if (Anilist.adult) true else it.media?.isAdult == false }
|
|
||||||
.filter { it.createdAt * 1000L > threeDaysAgo }.toList()
|
|
||||||
.sortedByDescending { it.createdAt }
|
|
||||||
val anilistActivities = mutableListOf<User>()
|
|
||||||
val groupedActivities = activities.groupBy { it.userId }
|
|
||||||
|
|
||||||
groupedActivities.forEach { (_, userActivities) ->
|
returnMap["hidden"] = removedMedia.distinctBy { it.id }.toCollection(arrayListOf())
|
||||||
val user = userActivities.firstOrNull()?.user
|
|
||||||
if (user != null) {
|
|
||||||
val userToAdd = User(
|
|
||||||
user.id,
|
|
||||||
user.name ?: "",
|
|
||||||
user.avatar?.medium,
|
|
||||||
user.bannerImage,
|
|
||||||
activity = userActivities.sortedBy { it.createdAt }.toList()
|
|
||||||
)
|
|
||||||
if (user.id == Anilist.userid) {
|
|
||||||
anilistActivities.add(0, userToAdd)
|
|
||||||
} else {
|
|
||||||
list.add(userToAdd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
list.addAll(0, anilistActivities)
|
|
||||||
returnMap["status"] = ArrayList(list)
|
|
||||||
}
|
|
||||||
returnMap["hidden"] = removedMedia.distinctBy { it.id } as ArrayList<Media>
|
|
||||||
}
|
|
||||||
return returnMap
|
return returnMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1034,153 +1047,105 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onListAnime =
|
private fun mediaList(media1: Page?): ArrayList<Media> {
|
||||||
(if (PrefManager.getVal(PrefName.IncludeAnimeList)) "" else "onList:false").replace(
|
val combinedList = arrayListOf<Media>()
|
||||||
"\"",
|
media1?.media?.mapTo(combinedList) { Media(it) }
|
||||||
""
|
return combinedList
|
||||||
)
|
}
|
||||||
private val isAdult =
|
|
||||||
(if (PrefManager.getVal(PrefName.AdultOnly)) "isAdult:true" else "").replace("\"", "")
|
private fun getPreference(pref: PrefName): Boolean = PrefManager.getVal(pref)
|
||||||
|
|
||||||
|
private fun buildQueryString(
|
||||||
|
sort: String,
|
||||||
|
type: String,
|
||||||
|
format: String? = null,
|
||||||
|
country: String? = null
|
||||||
|
): String {
|
||||||
|
val includeList = when {
|
||||||
|
type == "ANIME" && !getPreference(PrefName.IncludeAnimeList) -> "onList:false"
|
||||||
|
type == "MANGA" && !getPreference(PrefName.IncludeMangaList) -> "onList:false"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
val isAdult = if (getPreference(PrefName.AdultOnly)) "isAdult:true" else ""
|
||||||
|
val formatFilter = format?.let { "format:$it, " } ?: ""
|
||||||
|
val countryFilter = country?.let { "countryOfOrigin:$it, " } ?: ""
|
||||||
|
|
||||||
|
return buildString {
|
||||||
|
append("""Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun recentAnimeUpdates(page: Int): String {
|
private fun recentAnimeUpdates(page: Int): String {
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${System.currentTimeMillis() / 1000 - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}"""
|
val currentTime = System.currentTimeMillis() / 1000
|
||||||
}
|
return buildString {
|
||||||
|
append("""Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""")
|
||||||
private fun trendingMovies(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: ANIME, format: MOVIE, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun topRatedAnime(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mostFavAnime(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadAnimeList(): Map<String, ArrayList<Media>> {
|
|
||||||
val list = mutableMapOf<String, ArrayList<Media>>()
|
|
||||||
fun query(): String {
|
|
||||||
return """{
|
|
||||||
recentUpdates:${recentAnimeUpdates(1)}
|
|
||||||
recentUpdates2:${recentAnimeUpdates(2)}
|
|
||||||
trendingMovies:${trendingMovies(1)}
|
|
||||||
trendingMovies2:${trendingMovies(2)}
|
|
||||||
topRated:${topRatedAnime(1)}
|
|
||||||
topRated2:${topRatedAnime(2)}
|
|
||||||
mostFav:${mostFavAnime(1)}
|
|
||||||
mostFav2:${mostFavAnime(2)}
|
|
||||||
}""".trimIndent()
|
|
||||||
}
|
}
|
||||||
executeQuery<Query.AnimeList>(query(), force = true)?.data?.apply {
|
}
|
||||||
val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly)
|
|
||||||
val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly)
|
private fun queryAnimeList(): String {
|
||||||
val idArr = mutableListOf<Int>()
|
return buildString {
|
||||||
list["recentUpdates"] = recentUpdates?.airingSchedules?.mapNotNull { i ->
|
append("""{recentUpdates:${recentAnimeUpdates(1)} recentUpdates2:${recentAnimeUpdates(2)} trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} topRated:${buildQueryString("SCORE_DESC", "ANIME")} mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")}}""")
|
||||||
i.media?.let {
|
}
|
||||||
if (!idArr.contains(it.id))
|
}
|
||||||
if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) {
|
|
||||||
idArr.add(it.id)
|
private fun queryMangaList(): String {
|
||||||
Media(it)
|
return buildString {
|
||||||
} else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) {
|
append("""{trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} topRated:${buildQueryString("SCORE_DESC", "MANGA")} mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")}}""")
|
||||||
idArr.add(it.id)
|
}
|
||||||
Media(it)
|
}
|
||||||
} else if ((listOnly && it.mediaListEntry != null)) {
|
|
||||||
idArr.add(it.id)
|
suspend fun loadAnimeList(): Map<String, ArrayList<Media>> = coroutineScope {
|
||||||
Media(it)
|
val list = mutableMapOf<String, ArrayList<Media>>()
|
||||||
} else null
|
|
||||||
else null
|
fun filterRecentUpdates(page: Page?): ArrayList<Media> {
|
||||||
|
val listOnly = getPreference(PrefName.RecentlyListOnly)
|
||||||
|
val adultOnly = getPreference(PrefName.AdultOnly)
|
||||||
|
val idArr = mutableSetOf<Int>()
|
||||||
|
return page?.airingSchedules?.mapNotNull { i ->
|
||||||
|
i.media?.takeIf { !idArr.contains(it.id) }?.let {
|
||||||
|
val shouldAdd = when {
|
||||||
|
!listOnly && it.countryOfOrigin == "JP" && adultOnly && it.isAdult == true -> true
|
||||||
|
!listOnly && !adultOnly && it.countryOfOrigin == "JP" && it.isAdult == false -> true
|
||||||
|
listOnly && it.mediaListEntry != null -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (shouldAdd) {
|
||||||
|
idArr.add(it.id)
|
||||||
|
Media(it)
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
}?.toCollection(ArrayList()) ?: arrayListOf()
|
}?.toCollection(ArrayList()) ?: arrayListOf()
|
||||||
|
|
||||||
list["trendingMovies"] = trendingMovies?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["topRated"] = topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["mostFav"] = mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
|
|
||||||
list["recentUpdates"]?.addAll(recentUpdates2?.airingSchedules?.mapNotNull { i ->
|
|
||||||
i.media?.let {
|
|
||||||
if (!idArr.contains(it.id))
|
|
||||||
if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) {
|
|
||||||
idArr.add(it.id)
|
|
||||||
Media(it)
|
|
||||||
} else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) {
|
|
||||||
idArr.add(it.id)
|
|
||||||
Media(it)
|
|
||||||
} else if ((listOnly && it.mediaListEntry != null)) {
|
|
||||||
idArr.add(it.id)
|
|
||||||
Media(it)
|
|
||||||
} else null
|
|
||||||
else null
|
|
||||||
}
|
|
||||||
}?.toCollection(ArrayList()) ?: arrayListOf())
|
|
||||||
list["trendingMovies"]?.addAll(trendingMovies2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf())
|
|
||||||
list["topRated"]?.addAll(topRated2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf())
|
|
||||||
list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf())
|
|
||||||
}
|
}
|
||||||
return list
|
|
||||||
|
val animeList = async { executeQuery<Query.AnimeList>(queryAnimeList(), force = true) }
|
||||||
|
|
||||||
|
animeList.await()?.data?.apply {
|
||||||
|
list["recentUpdates"] = filterRecentUpdates(recentUpdates)
|
||||||
|
list["trendingMovies"] = mediaList(trendingMovies)
|
||||||
|
list["topRated"] = mediaList(topRated)
|
||||||
|
list["mostFav"] = mediaList(mostFav)
|
||||||
|
}
|
||||||
|
|
||||||
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onListManga =
|
suspend fun loadMangaList(): Map<String, ArrayList<Media>> = coroutineScope {
|
||||||
(if (PrefManager.getVal(PrefName.IncludeMangaList)) "" else "onList:false").replace(
|
|
||||||
"\"",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun trendingManga(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA,countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun trendingManhwa(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, countryOfOrigin:KR, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun trendingNovel(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, format: NOVEL, countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun topRatedManga(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mostFavManga(page: Int): String {
|
|
||||||
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadMangaList(): Map<String, ArrayList<Media>> {
|
|
||||||
val list = mutableMapOf<String, ArrayList<Media>>()
|
val list = mutableMapOf<String, ArrayList<Media>>()
|
||||||
fun query(): String {
|
|
||||||
return """{
|
val mangaList = async { executeQuery<Query.MangaList>(queryMangaList(), force = true) }
|
||||||
trendingManga:${trendingManga(1)}
|
|
||||||
trendingManga2:${trendingManga(2)}
|
mangaList.await()?.data?.apply {
|
||||||
trendingManhwa:${trendingManhwa(1)}
|
list["trendingManga"] = mediaList(trendingManga)
|
||||||
trendingManhwa2:${trendingManhwa(2)}
|
list["trendingManhwa"] = mediaList(trendingManhwa)
|
||||||
trendingNovel:${trendingNovel(1)}
|
list["trendingNovel"] = mediaList(trendingNovel)
|
||||||
trendingNovel2:${trendingNovel(2)}
|
list["topRated"] = mediaList(topRated)
|
||||||
topRated:${topRatedManga(1)}
|
list["mostFav"] = mediaList(mostFav)
|
||||||
topRated2:${topRatedManga(2)}
|
|
||||||
mostFav:${mostFavManga(1)}
|
|
||||||
mostFav2:${mostFavManga(2)}
|
|
||||||
}""".trimIndent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
executeQuery<Query.MangaList>(query(), force = true)?.data?.apply {
|
list
|
||||||
list["trendingManga"] = trendingManga?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["trendingManhwa"] = trendingManhwa?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["trendingNovel"] = trendingNovel?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["topRated"] = topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
list["mostFav"] = mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf()
|
|
||||||
|
|
||||||
list["trendingManga"]?.addAll(trendingManga2?.media?.map { Media(it) }?.toList() ?: arrayListOf())
|
|
||||||
list["trendingManhwa"]?.addAll(trendingManhwa2?.media?.map { Media(it) }?.toList() ?: arrayListOf())
|
|
||||||
list["trendingNovel"]?.addAll(trendingNovel2?.media?.map { Media(it) }?.toList() ?: arrayListOf())
|
|
||||||
list["topRated"]?.addAll(topRated2?.media?.map { Media(it) }?.toList() ?: arrayListOf())
|
|
||||||
list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) }?.toList() ?: arrayListOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun recentlyUpdated(
|
suspend fun recentlyUpdated(
|
||||||
greater: Long = 0,
|
greater: Long = 0,
|
||||||
lesser: Long = System.currentTimeMillis() / 1000 - 10000
|
lesser: Long = System.currentTimeMillis() / 1000 - 10000
|
||||||
@@ -1509,25 +1474,17 @@ Page(page:$page,perPage:50) {
|
|||||||
return author
|
return author
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "SCORE_DESC"): Query.ReviewsResponse? {
|
suspend fun getReviews(
|
||||||
|
mediaId: Int,
|
||||||
|
page: Int = 1,
|
||||||
|
sort: String = "SCORE_DESC"
|
||||||
|
): Query.ReviewsResponse? {
|
||||||
return executeQuery<Query.ReviewsResponse>(
|
return executeQuery<Query.ReviewsResponse>(
|
||||||
"""{Page(page:$page,perPage:10){pageInfo{currentPage,hasNextPage,total}reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
|
"""{Page(page:$page,perPage:10){pageInfo{currentPage,hasNextPage,total}reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun toggleFollow(id: Int): Query.ToggleFollow? {
|
|
||||||
return executeQuery<Query.ToggleFollow>(
|
|
||||||
"""mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun toggleLike(id: Int, type: String): ToggleLike? {
|
|
||||||
return executeQuery<ToggleLike>(
|
|
||||||
"""mutation Like{ToggleLikeV2(id:$id,type:$type){__typename}}"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
|
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
|
||||||
return executeQuery<Query.UserProfileResponse>(
|
return executeQuery<Query.UserProfileResponse>(
|
||||||
"""{followerPage:Page{followers(userId:$id){id}pageInfo{total}}followingPage:Page{following(userId:$id){id}pageInfo{total}}user:User(id:$id){id name about(asHtml:true)avatar{medium large}bannerImage isFollowing isFollower isBlocked favourites{anime{nodes{id coverImage{extraLarge large medium color}}}manga{nodes{id coverImage{extraLarge large medium color}}}characters{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}staff{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}studios{nodes{id name isFavourite}}}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}manga{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}}siteUrl}}""",
|
"""{followerPage:Page{followers(userId:$id){id}pageInfo{total}}followingPage:Page{following(userId:$id){id}pageInfo{total}}user:User(id:$id){id name about(asHtml:true)avatar{medium large}bannerImage isFollowing isFollower isBlocked favourites{anime{nodes{id coverImage{extraLarge large medium color}}}manga{nodes{id coverImage{extraLarge large medium color}}}characters{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}staff{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}studios{nodes{id name isFavourite}}}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}manga{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}}siteUrl}}""",
|
||||||
@@ -1586,11 +1543,13 @@ Page(page:$page,perPage:50) {
|
|||||||
suspend fun getNotifications(
|
suspend fun getNotifications(
|
||||||
id: Int,
|
id: Int,
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
resetNotification: Boolean = true
|
resetNotification: Boolean = true,
|
||||||
|
type: Boolean? = null
|
||||||
): NotificationResponse? {
|
): NotificationResponse? {
|
||||||
|
val typeIn = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]"
|
||||||
val reset = if (resetNotification) "true" else "false"
|
val reset = if (resetNotification) "true" else "false"
|
||||||
val res = executeQuery<NotificationResponse>(
|
val res = executeQuery<NotificationResponse>(
|
||||||
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
|
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) typeIn else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
if (res != null && resetNotification) {
|
if (res != null && resetNotification) {
|
||||||
@@ -1612,8 +1571,9 @@ Page(page:$page,perPage:50) {
|
|||||||
else if (userId != null) "userId:$userId,"
|
else if (userId != null) "userId:$userId,"
|
||||||
else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
|
else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
|
||||||
else "isFollowing:true,"
|
else "isFollowing:true,"
|
||||||
|
val typeIn = if (filter == "isFollowing:true,") "type_in:[TEXT,ANIME_LIST,MANGA_LIST,MEDIA_LIST]," else ""
|
||||||
return executeQuery<FeedResponse>(
|
return executeQuery<FeedResponse>(
|
||||||
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}isAdult}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""",
|
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}${typeIn}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}isAdult}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1621,13 +1581,14 @@ Page(page:$page,perPage:50) {
|
|||||||
suspend fun getReplies(
|
suspend fun getReplies(
|
||||||
activityId: Int,
|
activityId: Int,
|
||||||
page: Int = 1
|
page: Int = 1
|
||||||
) : ReplyResponse? {
|
): ReplyResponse? {
|
||||||
val query = """{Page(page:$page,perPage:50){activityReplies(activityId:$activityId){id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}}}"""
|
val query =
|
||||||
|
"""{Page(page:$page,perPage:50){activityReplies(activityId:$activityId){id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}}}"""
|
||||||
return executeQuery(query, force = true)
|
return executeQuery(query, force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun status(page: Int = 1): String {
|
private fun status(page: Int = 1): String {
|
||||||
return """Page(page:$page,perPage:50){activities(isFollowing: true,sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed replyCount likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed replyCount likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id isAdult title{english romaji native userPreferred}bannerImage coverImage{extraLarge medium large}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id type createdAt}}}"""
|
return """Page(page:$page,perPage:50){activities(isFollowing: true,type_in:[TEXT,ANIME_LIST,MANGA_LIST,MEDIA_LIST],sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed replyCount likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed replyCount likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id isAdult title{english romaji native userPreferred}bannerImage coverImage{extraLarge medium large}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id type createdAt}}}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUpcomingAnime(id: String): List<Media> {
|
suspend fun getUpcomingAnime(id: String): List<Media> {
|
||||||
@@ -1674,4 +1635,4 @@ Page(page:$page,perPage:50) {
|
|||||||
companion object {
|
companion object {
|
||||||
const val ITEMS_PER_PAGE = 25
|
const val ITEMS_PER_PAGE = 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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?,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>?,
|
||||||
@@ -239,7 +239,20 @@ data class AiringSchedule(
|
|||||||
// The associate media of the airing episode
|
// The associate media of the airing episode
|
||||||
@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?,
|
||||||
|
)
|
||||||
@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.
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ data class User(
|
|||||||
@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,40 @@ 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 +198,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 +215,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
|
||||||
@@ -369,10 +372,9 @@ object CommentsAPI {
|
|||||||
}
|
}
|
||||||
errorMessage("Failed to login after multiple attempts")
|
errorMessage("Failed to login after multiple attempts")
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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 kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import ani.dantotsu.client as app
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
@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,
|
||||||
@@ -39,23 +34,21 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
|||||||
val stopTimestamp: Long? = null,
|
val stopTimestamp: Long? = null,
|
||||||
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,59 @@
|
|||||||
|
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",
|
||||||
"??",
|
"??",
|
||||||
"??",
|
"??",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import ani.dantotsu.bottomBar
|
|||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.download.DownloadCompat
|
||||||
import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat
|
import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat
|
||||||
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat
|
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
@@ -48,6 +49,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 +204,22 @@ 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 = PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values ?: emptySet()
|
||||||
val mediaIds =
|
if (mediaIds.isEmpty()) {
|
||||||
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
|
snackString("No media found") // if this happens, terrible things have happened
|
||||||
?: emptySet()
|
}
|
||||||
if (mediaIds.isEmpty()) {
|
getDownloads()
|
||||||
snackString("No media found") // if this happens, terrible things have happened
|
|
||||||
}
|
}
|
||||||
getDownloads()
|
setNegButton(R.string.no) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,17 +318,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 +396,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 +404,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) {
|
||||||
@@ -226,30 +236,36 @@ class MangaDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
saveToDisk("$index.jpg", bitmap, task.title, task.chapter)
|
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.chapter,
|
||||||
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,
|
||||||
@@ -269,17 +285,16 @@ class MangaDownloaderService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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 +307,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")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -289,15 +290,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
|
||||||
|
|||||||
@@ -111,8 +111,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(
|
||||||
@@ -268,8 +268,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 +295,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import ani.dantotsu.statusBarHeight
|
|||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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 +93,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 {
|
||||||
@@ -456,51 +458,56 @@ 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)?.toIntOrNull()
|
||||||
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
|
|
||||||
?.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 +515,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()
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ 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 ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
@@ -94,38 +96,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() {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -274,15 +275,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,6 +80,7 @@ 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 = "MANGA"
|
||||||
trendingBinding.searchBarText.setOnClickListener {
|
trendingBinding.searchBarText.setOnClickListener {
|
||||||
@@ -296,8 +297,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ 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.toast
|
||||||
|
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,10 +46,17 @@ 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 {
|
||||||
@@ -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) {
|
||||||
@@ -83,7 +95,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
|
|||||||
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,13 +104,13 @@ 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()
|
||||||
|
|||||||
@@ -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,49 +262,7 @@ 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")
|
||||||
@@ -359,6 +315,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 +331,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 +363,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 +389,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 +423,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 +451,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 +469,69 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +9,7 @@ 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
|
||||||
@@ -55,6 +56,7 @@ class CharacterAdapter(
|
|||||||
).toBundle()
|
).toBundle()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
itemView.setOnLongClickListener { copyToClipboard(characterList[bindingAdapterPosition].name ?: ""); true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -293,7 +293,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 +372,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 +426,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
|
||||||
@@ -99,6 +100,15 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s))
|
if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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))
|
||||||
}
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -183,6 +183,12 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
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.result.apply {
|
||||||
search =
|
search =
|
||||||
@@ -300,11 +306,17 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.searchResultLayout.visibility = View.VISIBLE
|
binding.searchResultLayout.visibility = View.VISIBLE
|
||||||
|
binding.clearHistory.visibility = View.GONE
|
||||||
binding.searchHistoryList.visibility = View.GONE
|
binding.searchHistoryList.visibility = View.GONE
|
||||||
binding.searchByImage.visibility = View.GONE
|
binding.searchByImage.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateClearHistoryVisibility() {
|
||||||
|
binding.clearHistory.visibility =
|
||||||
|
if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
private fun fadeInAnimation(): Animation {
|
private fun fadeInAnimation(): Animation {
|
||||||
return AlphaAnimation(0f, 1f).apply {
|
return AlphaAnimation(0f, 1f).apply {
|
||||||
duration = 150
|
duration = 150
|
||||||
@@ -375,4 +387,3 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
|||||||
override fun getItemCount(): Int = chips.size
|
override fun getItemCount(): Int = chips.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ class SearchHistoryAdapter(private val type: String, private val searchClicked:
|
|||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearHistory() {
|
||||||
|
searchHistory?.clear()
|
||||||
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
|
submitList(searchHistory?.toList())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
|
|||||||
@@ -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,8 +18,9 @@ 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.ItemMediaSourceBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.displayTimer
|
import ani.dantotsu.displayTimer
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
@@ -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,13 @@ 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 +76,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 +90,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 +100,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 +109,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 +151,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 +171,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 +191,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 +359,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 +384,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 +403,7 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearChips() {
|
fun clearChips() {
|
||||||
_binding?.animeSourceChipGroup?.removeAllViews()
|
_binding?.mediaSourceChipGroup?.removeAllViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEpisodes() {
|
fun handleEpisodes() {
|
||||||
@@ -379,15 +419,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 +435,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 +447,63 @@ 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 +512,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 +532,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 +545,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 +555,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)
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import ani.dantotsu.FileUrl
|
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.connections.anilist.api.MediaStreamingEpisode
|
||||||
|
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
|
||||||
@@ -48,6 +49,7 @@ import ani.dantotsu.media.MediaType
|
|||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
|
||||||
|
import ani.dantotsu.others.Anify
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper
|
||||||
import ani.dantotsu.parsers.AnimeParser
|
import ani.dantotsu.parsers.AnimeParser
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
@@ -61,6 +63,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 +81,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 +108,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 +129,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 +153,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 +173,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 +206,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 +215,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 +234,18 @@ 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 =
|
||||||
@@ -239,22 +255,19 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
if (media.anime?.kitsuEpisodes != null) {
|
if (media.anime?.kitsuEpisodes != null) {
|
||||||
if (media.anime!!.kitsuEpisodes!!.containsKey(i)) {
|
if (media.anime!!.kitsuEpisodes!!.containsKey(i)) {
|
||||||
episode.desc =
|
episode.desc = media.anime!!.kitsuEpisodes!![i]?.desc ?: episode.desc
|
||||||
media.anime!!.kitsuEpisodes!![i]?.desc ?: episode.desc
|
|
||||||
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
|
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
|
||||||
episode.title ?: ""
|
episode.title ?: ""
|
||||||
).isBlank()
|
).isBlank()
|
||||||
) 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
|
||||||
episode.thumb = media.anime!!.kitsuEpisodes!![i]?.thumb
|
|
||||||
?: FileUrl[media.cover]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 +308,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,20 +397,18 @@ 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 {
|
||||||
|
setTitle("Select a Source")
|
||||||
|
singleChoiceItems(names) { which ->
|
||||||
selectedSetting = allSettings[which]
|
selectedSetting = allSettings[which]
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
dialog.dismiss()
|
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -401,13 +416,13 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setOnDismissListener {
|
onDismiss {
|
||||||
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 +431,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 +635,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 +679,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 {
|
||||||
|
|||||||
@@ -23,6 +23,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 +107,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 +141,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 +155,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 +184,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 +209,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 +319,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)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.content.Intent
|
|||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
@@ -71,9 +72,12 @@ import androidx.media3.common.MimeTypes
|
|||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.PlaybackParameters
|
import androidx.media3.common.PlaybackParameters
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.text.Cue
|
||||||
|
import androidx.media3.common.text.CueGroup
|
||||||
import androidx.media3.common.TrackGroup
|
import androidx.media3.common.TrackGroup
|
||||||
import androidx.media3.common.TrackSelectionOverride
|
import androidx.media3.common.TrackSelectionOverride
|
||||||
import androidx.media3.common.Tracks
|
import androidx.media3.common.Tracks
|
||||||
|
import androidx.media3.common.util.Util
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.datasource.DefaultDataSource
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
@@ -81,6 +85,7 @@ import androidx.media3.datasource.HttpDataSource
|
|||||||
import androidx.media3.datasource.cache.CacheDataSource
|
import androidx.media3.datasource.cache.CacheDataSource
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
@@ -117,6 +122,7 @@ import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
|||||||
import ani.dantotsu.download.video.Helper
|
import ani.dantotsu.download.video.Helper
|
||||||
import ani.dantotsu.dp
|
import ani.dantotsu.dp
|
||||||
import ani.dantotsu.getCurrentBrightnessValue
|
import ani.dantotsu.getCurrentBrightnessValue
|
||||||
|
import ani.dantotsu.getLanguageCode
|
||||||
import ani.dantotsu.hideSystemBars
|
import ani.dantotsu.hideSystemBars
|
||||||
import ani.dantotsu.hideSystemBarsExtendView
|
import ani.dantotsu.hideSystemBarsExtendView
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
@@ -135,6 +141,7 @@ import ani.dantotsu.others.getSerialized
|
|||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.HAnimeSources
|
import ani.dantotsu.parsers.HAnimeSources
|
||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
|
import ani.dantotsu.others.Xubtitle
|
||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
@@ -164,6 +171,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import io.github.anilbeesetti.nextlib.media3ext.ffdecoder.NextRenderersFactory
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
@@ -223,6 +231,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
private lateinit var animeTitle: TextView
|
private lateinit var animeTitle: TextView
|
||||||
private lateinit var videoInfo: TextView
|
private lateinit var videoInfo: TextView
|
||||||
private lateinit var episodeTitle: Spinner
|
private lateinit var episodeTitle: Spinner
|
||||||
|
private lateinit var customSubtitleView: Xubtitle
|
||||||
|
|
||||||
private var orientationListener: OrientationEventListener? = null
|
private var orientationListener: OrientationEventListener? = null
|
||||||
|
|
||||||
@@ -318,36 +327,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubFormatting(playerView: PlayerView) {
|
private fun setupSubFormatting(playerView: PlayerView) {
|
||||||
val primaryColor = when (PrefManager.getVal<Int>(PrefName.PrimaryColor)) {
|
val primaryColor = PrefManager.getVal<Int>(PrefName.PrimaryColor)
|
||||||
0 -> Color.BLACK
|
|
||||||
1 -> Color.DKGRAY
|
val secondaryColor = PrefManager.getVal<Int>(PrefName.SecondaryColor)
|
||||||
2 -> Color.GRAY
|
|
||||||
3 -> Color.LTGRAY
|
|
||||||
4 -> Color.WHITE
|
|
||||||
5 -> Color.RED
|
|
||||||
6 -> Color.YELLOW
|
|
||||||
7 -> Color.GREEN
|
|
||||||
8 -> Color.CYAN
|
|
||||||
9 -> Color.BLUE
|
|
||||||
10 -> Color.MAGENTA
|
|
||||||
11 -> Color.TRANSPARENT
|
|
||||||
else -> Color.WHITE
|
|
||||||
}
|
|
||||||
val secondaryColor = when (PrefManager.getVal<Int>(PrefName.SecondaryColor)) {
|
|
||||||
0 -> Color.BLACK
|
|
||||||
1 -> Color.DKGRAY
|
|
||||||
2 -> Color.GRAY
|
|
||||||
3 -> Color.LTGRAY
|
|
||||||
4 -> Color.WHITE
|
|
||||||
5 -> Color.RED
|
|
||||||
6 -> Color.YELLOW
|
|
||||||
7 -> Color.GREEN
|
|
||||||
8 -> Color.CYAN
|
|
||||||
9 -> Color.BLUE
|
|
||||||
10 -> Color.MAGENTA
|
|
||||||
11 -> Color.TRANSPARENT
|
|
||||||
else -> Color.BLACK
|
|
||||||
}
|
|
||||||
val outline = when (PrefManager.getVal<Int>(PrefName.Outline)) {
|
val outline = when (PrefManager.getVal<Int>(PrefName.Outline)) {
|
||||||
0 -> EDGE_TYPE_OUTLINE // Normal
|
0 -> EDGE_TYPE_OUTLINE // Normal
|
||||||
1 -> EDGE_TYPE_DEPRESSED // Shine
|
1 -> EDGE_TYPE_DEPRESSED // Shine
|
||||||
@@ -355,36 +338,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
3 -> EDGE_TYPE_NONE // No outline
|
3 -> EDGE_TYPE_NONE // No outline
|
||||||
else -> EDGE_TYPE_OUTLINE // Normal
|
else -> EDGE_TYPE_OUTLINE // Normal
|
||||||
}
|
}
|
||||||
val subBackground = when (PrefManager.getVal<Int>(PrefName.SubBackground)) {
|
|
||||||
0 -> Color.TRANSPARENT
|
val subBackground = PrefManager.getVal<Int>(PrefName.SubBackground)
|
||||||
1 -> Color.BLACK
|
|
||||||
2 -> Color.DKGRAY
|
val subWindow = PrefManager.getVal<Int>(PrefName.SubWindow)
|
||||||
3 -> Color.GRAY
|
|
||||||
4 -> Color.LTGRAY
|
|
||||||
5 -> Color.WHITE
|
|
||||||
6 -> Color.RED
|
|
||||||
7 -> Color.YELLOW
|
|
||||||
8 -> Color.GREEN
|
|
||||||
9 -> Color.CYAN
|
|
||||||
10 -> Color.BLUE
|
|
||||||
11 -> Color.MAGENTA
|
|
||||||
else -> Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
val subWindow = when (PrefManager.getVal<Int>(PrefName.SubWindow)) {
|
|
||||||
0 -> Color.TRANSPARENT
|
|
||||||
1 -> Color.BLACK
|
|
||||||
2 -> Color.DKGRAY
|
|
||||||
3 -> Color.GRAY
|
|
||||||
4 -> Color.LTGRAY
|
|
||||||
5 -> Color.WHITE
|
|
||||||
6 -> Color.RED
|
|
||||||
7 -> Color.YELLOW
|
|
||||||
8 -> Color.GREEN
|
|
||||||
9 -> Color.CYAN
|
|
||||||
10 -> Color.BLUE
|
|
||||||
11 -> Color.MAGENTA
|
|
||||||
else -> Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
val font = when (PrefManager.getVal<Int>(PrefName.Font)) {
|
val font = when (PrefManager.getVal<Int>(PrefName.Font)) {
|
||||||
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
||||||
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
|
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
|
||||||
@@ -422,6 +380,53 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applySubtitleStyles(textView: Xubtitle) {
|
||||||
|
val primaryColor = PrefManager.getVal<Int>(PrefName.PrimaryColor)
|
||||||
|
|
||||||
|
val subBackground = PrefManager.getVal<Int>(PrefName.SubBackground)
|
||||||
|
|
||||||
|
val secondaryColor = PrefManager.getVal<Int>(PrefName.SecondaryColor)
|
||||||
|
|
||||||
|
val subStroke = PrefManager.getVal<Float>(PrefName.SubStroke)
|
||||||
|
|
||||||
|
val fontSize = PrefManager.getVal<Int>(PrefName.FontSize).toFloat()
|
||||||
|
|
||||||
|
val font = when (PrefManager.getVal<Int>(PrefName.Font)) {
|
||||||
|
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
||||||
|
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
|
||||||
|
2 -> ResourcesCompat.getFont(this, R.font.poppins)
|
||||||
|
3 -> ResourcesCompat.getFont(this, R.font.poppins_thin)
|
||||||
|
4 -> ResourcesCompat.getFont(this, R.font.century_gothic_regular)
|
||||||
|
5 -> ResourcesCompat.getFont(this, R.font.levenim_mt_bold)
|
||||||
|
6 -> ResourcesCompat.getFont(this, R.font.blocky)
|
||||||
|
else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.setBackgroundColor(subBackground)
|
||||||
|
textView.setTextColor(primaryColor)
|
||||||
|
textView.typeface = font
|
||||||
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
|
||||||
|
|
||||||
|
textView.apply {
|
||||||
|
when (PrefManager.getVal<Int>(PrefName.Outline)) {
|
||||||
|
0 -> applyOutline(secondaryColor, subStroke)
|
||||||
|
1 -> applyShineEffect(secondaryColor)
|
||||||
|
2 -> applyDropShadow(secondaryColor, subStroke)
|
||||||
|
3 -> {}
|
||||||
|
else -> applyOutline(secondaryColor, subStroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.alpha =
|
||||||
|
when (PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
|
||||||
|
true -> PrefManager.getVal(PrefName.SubAlpha)
|
||||||
|
false -> 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val textElevation = PrefManager.getVal<Float>(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels
|
||||||
|
textView.translationY = -textElevation
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -467,6 +472,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
skipTimeButton = playerView.findViewById(R.id.exo_skip_timestamp)
|
skipTimeButton = playerView.findViewById(R.id.exo_skip_timestamp)
|
||||||
skipTimeText = skipTimeButton.findViewById(R.id.exo_skip_timestamp_text)
|
skipTimeText = skipTimeButton.findViewById(R.id.exo_skip_timestamp_text)
|
||||||
timeStampText = playerView.findViewById(R.id.exo_time_stamp_text)
|
timeStampText = playerView.findViewById(R.id.exo_time_stamp_text)
|
||||||
|
customSubtitleView = playerView.findViewById(R.id.customSubtitleView)
|
||||||
|
|
||||||
animeTitle = playerView.findViewById(R.id.exo_anime_title)
|
animeTitle = playerView.findViewById(R.id.exo_anime_title)
|
||||||
episodeTitle = playerView.findViewById(R.id.exo_ep_sel)
|
episodeTitle = playerView.findViewById(R.id.exo_ep_sel)
|
||||||
@@ -520,7 +526,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
it.visibility = View.GONE
|
it.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setupSubFormatting(playerView)
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
currentWindow = savedInstanceState.getInt(resumeWindow)
|
currentWindow = savedInstanceState.getInt(resumeWindow)
|
||||||
@@ -1114,60 +1119,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
)
|
)
|
||||||
initPlayer()
|
initPlayer()
|
||||||
preloading = false
|
preloading = false
|
||||||
val context = this
|
|
||||||
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
|
||||||
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
|
||||||
if ((isOnline(context) && !offline) && Discord.token != null && !incognito) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val discordMode = PrefManager.getCustomVal("discord_mode", "dantotsu")
|
|
||||||
val buttons = when (discordMode) {
|
|
||||||
"nothing" -> mutableListOf(
|
|
||||||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
"dantotsu" -> mutableListOf(
|
|
||||||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
|
||||||
RPC.Link("Watch on Dantotsu", getString(R.string.dantotsu))
|
|
||||||
)
|
|
||||||
|
|
||||||
"anilist" -> {
|
|
||||||
val userId = PrefManager.getVal<String>(PrefName.AnilistUserId)
|
|
||||||
val anilistLink = "https://anilist.co/user/$userId/"
|
|
||||||
mutableListOf(
|
|
||||||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
|
||||||
RPC.Link("View My AniList", anilistLink)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> mutableListOf()
|
|
||||||
}
|
|
||||||
val presence = RPC.createPresence(
|
|
||||||
RPC.Companion.RPCData(
|
|
||||||
applicationId = Discord.application_Id,
|
|
||||||
type = RPC.Type.WATCHING,
|
|
||||||
activityName = media.userPreferredName,
|
|
||||||
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(
|
|
||||||
R.string.episode_num,
|
|
||||||
ep.number
|
|
||||||
),
|
|
||||||
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}",
|
|
||||||
largeImage = media.cover?.let {
|
|
||||||
RPC.Link(
|
|
||||||
media.userPreferredName,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
},
|
|
||||||
smallImage = RPC.Link("Dantotsu", Discord.small_Image),
|
|
||||||
buttons = buttons
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val intent = Intent(context, DiscordService::class.java).apply {
|
|
||||||
putExtra("presence", presence)
|
|
||||||
}
|
|
||||||
DiscordServiceRunningSingleton.running = true
|
|
||||||
startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateProgress()
|
updateProgress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1358,6 +1309,73 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun discordRPC(){
|
||||||
|
val context = this
|
||||||
|
val ep = episode
|
||||||
|
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||||
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
|
val rpcenabled: Boolean = PrefManager.getVal(PrefName.rpcEnabled)
|
||||||
|
if ((isOnline(context) && !offline) && Discord.token != null && !incognito && rpcenabled) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val discordMode = PrefManager.getCustomVal("discord_mode", "dantotsu")
|
||||||
|
val buttons = when (discordMode) {
|
||||||
|
"nothing" -> mutableListOf(
|
||||||
|
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
"dantotsu" -> mutableListOf(
|
||||||
|
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||||
|
RPC.Link("Watch on Dantotsu", getString(R.string.dantotsu))
|
||||||
|
)
|
||||||
|
|
||||||
|
"anilist" -> {
|
||||||
|
val userId = PrefManager.getVal<String>(PrefName.AnilistUserId)
|
||||||
|
val anilistLink = "https://anilist.co/user/$userId/"
|
||||||
|
mutableListOf(
|
||||||
|
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||||
|
RPC.Link("View My AniList", anilistLink)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> mutableListOf()
|
||||||
|
}
|
||||||
|
val startTimestamp = Calendar.getInstance()
|
||||||
|
val durationInSeconds = if (exoPlayer.duration != C.TIME_UNSET) (exoPlayer.duration / 1000).toInt() else 1440
|
||||||
|
|
||||||
|
val endTimestamp = Calendar.getInstance().apply {
|
||||||
|
timeInMillis = startTimestamp.timeInMillis
|
||||||
|
add(Calendar.SECOND, durationInSeconds)
|
||||||
|
}
|
||||||
|
val presence = RPC.createPresence(
|
||||||
|
RPC.Companion.RPCData(
|
||||||
|
applicationId = Discord.application_Id,
|
||||||
|
type = RPC.Type.WATCHING,
|
||||||
|
activityName = media.userPreferredName,
|
||||||
|
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(
|
||||||
|
R.string.episode_num,
|
||||||
|
ep.number
|
||||||
|
),
|
||||||
|
startTimestamp = startTimestamp.timeInMillis,
|
||||||
|
stopTimestamp = endTimestamp.timeInMillis,
|
||||||
|
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}",
|
||||||
|
largeImage = media.cover?.let {
|
||||||
|
RPC.Link(
|
||||||
|
media.userPreferredName,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
smallImage = RPC.Link("Dantotsu", Discord.small_Image),
|
||||||
|
buttons = buttons
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val intent = Intent(context, DiscordService::class.java).apply {
|
||||||
|
putExtra("presence", presence)
|
||||||
|
}
|
||||||
|
DiscordServiceRunningSingleton.running = true
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private fun initPlayer() {
|
private fun initPlayer() {
|
||||||
checkNotch()
|
checkNotch()
|
||||||
|
|
||||||
@@ -1383,13 +1401,57 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return
|
val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return
|
||||||
extractor = ext
|
extractor = ext
|
||||||
video = ext.videos.getOrNull(episode.selectedVideo) ?: return
|
video = ext.videos.getOrNull(episode.selectedVideo) ?: return
|
||||||
|
val subLanguages = arrayOf(
|
||||||
|
"Albanian",
|
||||||
|
"Arabic",
|
||||||
|
"Bosnian",
|
||||||
|
"Bulgarian",
|
||||||
|
"Chinese",
|
||||||
|
"Croatian",
|
||||||
|
"Czech",
|
||||||
|
"Danish",
|
||||||
|
"Dutch",
|
||||||
|
"English",
|
||||||
|
"Estonian",
|
||||||
|
"Finnish",
|
||||||
|
"French",
|
||||||
|
"Georgian",
|
||||||
|
"German",
|
||||||
|
"Greek",
|
||||||
|
"Hebrew",
|
||||||
|
"Hindi",
|
||||||
|
"Indonesian",
|
||||||
|
"Irish",
|
||||||
|
"Italian",
|
||||||
|
"Japanese",
|
||||||
|
"Korean",
|
||||||
|
"Lithuanian",
|
||||||
|
"Luxembourgish",
|
||||||
|
"Macedonian",
|
||||||
|
"Mongolian",
|
||||||
|
"Norwegian",
|
||||||
|
"Polish",
|
||||||
|
"Portuguese",
|
||||||
|
"Punjabi",
|
||||||
|
"Romanian",
|
||||||
|
"Russian",
|
||||||
|
"Serbian",
|
||||||
|
"Slovak",
|
||||||
|
"Slovenian",
|
||||||
|
"Spanish",
|
||||||
|
"Turkish",
|
||||||
|
"Ukrainian",
|
||||||
|
"Urdu",
|
||||||
|
"Vietnamese",
|
||||||
|
)
|
||||||
|
val lang = subLanguages[PrefManager.getVal(PrefName.SubLanguage)]
|
||||||
subtitle = intent.getSerialized("subtitle")
|
subtitle = intent.getSerialized("subtitle")
|
||||||
?: when (val subLang: String? =
|
?: when (val subLang: String? =
|
||||||
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) {
|
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) {
|
||||||
null -> {
|
null -> {
|
||||||
when (episode.selectedSubtitle) {
|
when (episode.selectedSubtitle) {
|
||||||
null -> null
|
null -> null
|
||||||
-1 -> ext.subtitles.find { it.language.trim() == "English" || it.language == "en-US" }
|
-1 -> ext.subtitles.find { it.language.contains( lang, ignoreCase = true ) || it.language.contains( getLanguageCode(lang), ignoreCase = true ) }
|
||||||
else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!)
|
else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1642,7 +1704,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
hideSystemBars()
|
hideSystemBars()
|
||||||
exoPlayer = ExoPlayer.Builder(this)
|
|
||||||
|
val useExtensionDecoder = PrefManager.getVal<Boolean>(PrefName.UseAdditionalCodec)
|
||||||
|
val decoder = if (useExtensionDecoder) {
|
||||||
|
DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
||||||
|
} else {
|
||||||
|
DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
|
||||||
|
}
|
||||||
|
val renderersFactory = NextRenderersFactory(this)
|
||||||
|
.setEnableDecoderFallback(true)
|
||||||
|
.setExtensionRendererMode(decoder)
|
||||||
|
|
||||||
|
exoPlayer = ExoPlayer.Builder(this, renderersFactory)
|
||||||
.setMediaSourceFactory(DefaultMediaSourceFactory(cacheFactory))
|
.setMediaSourceFactory(DefaultMediaSourceFactory(cacheFactory))
|
||||||
.setTrackSelector(trackSelector)
|
.setTrackSelector(trackSelector)
|
||||||
.setLoadControl(loadControl)
|
.setLoadControl(loadControl)
|
||||||
@@ -1661,6 +1734,54 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
}
|
}
|
||||||
playerView.player = exoPlayer
|
playerView.player = exoPlayer
|
||||||
|
|
||||||
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
|
var activeSubtitles = ArrayDeque<String>(3)
|
||||||
|
var lastSubtitle: String? = null
|
||||||
|
var lastPosition: Long = 0
|
||||||
|
|
||||||
|
override fun onCues(cueGroup: CueGroup) {
|
||||||
|
if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) {
|
||||||
|
exoSubtitleView.visibility = View.GONE
|
||||||
|
customSubtitleView.visibility = View.VISIBLE
|
||||||
|
val newCues = cueGroup.cues.map { it.text.toString() ?: "" }
|
||||||
|
|
||||||
|
if (newCues.isEmpty()) {
|
||||||
|
customSubtitleView.text = ""
|
||||||
|
activeSubtitles.clear()
|
||||||
|
lastSubtitle = null
|
||||||
|
lastPosition = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentPosition = exoPlayer.currentPosition
|
||||||
|
|
||||||
|
if ((lastSubtitle?.length ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500)) {
|
||||||
|
activeSubtitles.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (newCue in newCues) {
|
||||||
|
if (newCue !in activeSubtitles) {
|
||||||
|
if (activeSubtitles.size >= 2) {
|
||||||
|
activeSubtitles.removeLast()
|
||||||
|
}
|
||||||
|
activeSubtitles.addFirst(newCue)
|
||||||
|
lastSubtitle = newCue
|
||||||
|
lastPosition = currentPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customSubtitleView.text = activeSubtitles.joinToString("\n")
|
||||||
|
} else {
|
||||||
|
customSubtitleView.text = ""
|
||||||
|
customSubtitleView.visibility = View.GONE
|
||||||
|
exoSubtitleView.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
applySubtitleStyles(customSubtitleView)
|
||||||
|
setupSubFormatting(playerView)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val rightNow = Calendar.getInstance()
|
val rightNow = Calendar.getInstance()
|
||||||
mediaSession = MediaSession.Builder(this, exoPlayer)
|
mediaSession = MediaSession.Builder(this, exoPlayer)
|
||||||
@@ -1950,7 +2071,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
TrackSelectionOverride(trackGroup.mediaTrackGroup, index)
|
TrackSelectionOverride(trackGroup.mediaTrackGroup, index)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
if (type == TRACK_TYPE_TEXT) setupSubFormatting(playerView)
|
if (type == TRACK_TYPE_TEXT) {
|
||||||
|
setupSubFormatting(playerView)
|
||||||
|
applySubtitleStyles(customSubtitleView)
|
||||||
|
}
|
||||||
playerView.subtitleView?.alpha = when (isDisabled) {
|
playerView.subtitleView?.alpha = when (isDisabled) {
|
||||||
false -> PrefManager.getVal(PrefName.SubAlpha)
|
false -> PrefManager.getVal(PrefName.SubAlpha)
|
||||||
true -> 0f
|
true -> 0f
|
||||||
@@ -2042,6 +2166,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||||||
exoPlayer.play()
|
exoPlayer.play()
|
||||||
if (episodeLength == 0f) {
|
if (episodeLength == 0f) {
|
||||||
episodeLength = exoPlayer.duration.toFloat()
|
episodeLength = exoPlayer.duration.toFloat()
|
||||||
|
discordRPC()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isBuffering = playbackState == Player.STATE_BUFFERING
|
isBuffering = playbackState == Player.STATE_BUFFERING
|
||||||
|
|||||||
@@ -444,15 +444,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 +463,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)
|
||||||
}
|
}
|
||||||
@@ -576,65 +569,63 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
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(extractor.audioTracks[index].url, extractor.audioTracks[index].lang)
|
||||||
} else {
|
if (isChecked) {
|
||||||
selectedAudioTracks.remove(audioPair)
|
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 = Pair(subtitles[index].file.url, subtitles[index].language)
|
||||||
selectedSubtitles.add(subtitlePair)
|
if (isChecked) {
|
||||||
} else {
|
selectedSubtitles.add(subtitlePair)
|
||||||
selectedSubtitles.remove(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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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 +386,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(
|
||||||
|
|||||||
@@ -25,6 +25,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
|
||||||
@@ -34,6 +35,7 @@ import ani.dantotsu.settings.saving.PrefName
|
|||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.toast
|
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 +162,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 +318,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 +338,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 +374,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 +585,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 +712,4 @@ class CommentsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -11,6 +11,7 @@ import android.widget.ImageButton
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.NumberPicker
|
import android.widget.NumberPicker
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.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
|
||||||
@@ -19,8 +20,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.databinding.CustomDialogLayoutBinding
|
||||||
import ani.dantotsu.databinding.DialogLayoutBinding
|
import ani.dantotsu.databinding.DialogLayoutBinding
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemMediaSourceBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
@@ -35,11 +37,14 @@ import ani.dantotsu.others.webview.CookieCatcher
|
|||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.parsers.OfflineMangaParser
|
||||||
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.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@@ -55,86 +60,108 @@ class MangaReadAdapter(
|
|||||||
) : RecyclerView.Adapter<MangaReadAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<MangaReadAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
||||||
private var _binding: ItemAnimeWatchBinding? = null
|
private var _binding: ItemMediaSourceBinding? = null
|
||||||
val hiddenScanlators = mutableListOf<String>()
|
val hiddenScanlators = mutableListOf<String>()
|
||||||
var scanlatorSelectionListener: ScanlatorSelectionListener? = null
|
var scanlatorSelectionListener: ScanlatorSelectionListener? = null
|
||||||
var options = listOf<String>()
|
var options = listOf<String>()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
private fun clearCustomValsForMedia(mediaId: String, suffix: String) {
|
||||||
val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val customVals = PrefManager.getAllCustomValsForMedia("$mediaId$suffix")
|
||||||
return ViewHolder(bind)
|
customVals.forEach { (key) ->
|
||||||
|
PrefManager.removeCustomVal(key)
|
||||||
|
Log.d("PrefManager", "Removed key: $key")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var nestedDialog: AlertDialog? = null
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return ViewHolder(bind)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
binding.sourceTitle.setText(R.string.chaps)
|
binding.sourceTitle.setText(R.string.chaps)
|
||||||
|
|
||||||
//Fuck u launch
|
// Fuck u launch
|
||||||
binding.faqbutton.setOnClickListener {
|
binding.faqbutton.setOnClickListener {
|
||||||
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
|
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
|
||||||
startActivity(fragment.requireContext(), intent, null)
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Wrong Title
|
// Wrong Title
|
||||||
binding.animeSourceSearch.setOnClickListener {
|
binding.mediaSourceSearch.setOnClickListener {
|
||||||
SourceSearchDialogFragment().show(
|
SourceSearchDialogFragment().show(
|
||||||
fragment.requireActivity().supportFragmentManager,
|
fragment.requireActivity().supportFragmentManager,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||||
|
//for removing saved progress
|
||||||
|
binding.sourceTitle.setOnLongClickListener{
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle(" Delete Progress for all chapters of ${media.nameRomaji}")
|
||||||
|
setMessage("This will delete all the locally stored progress for chapters")
|
||||||
|
setPosButton(R.string.ok){
|
||||||
|
clearCustomValsForMedia("${media.id}", "_Chapter")
|
||||||
|
clearCustomValsForMedia("${media.id}", "_Vol")
|
||||||
|
snackString("Deleted the progress of Chapters for ${media.nameRomaji}")
|
||||||
|
}
|
||||||
|
setNegButton(R.string.no)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
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 >= mangaReadSources.names.size) 0 else it }
|
media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
||||||
setLanguageList(media.selected!!.langIndex, source)
|
setLanguageList(media.selected!!.langIndex, source)
|
||||||
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||||
binding.animeSource.setText(mangaReadSources.names[source])
|
binding.mediaSource.setText(mangaReadSources.names[source])
|
||||||
mangaReadSources[source].apply {
|
mangaReadSources[source].apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.mediaSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
media.selected?.scanlators?.let {
|
media.selected?.scanlators?.let {
|
||||||
hiddenScanlators.addAll(it)
|
hiddenScanlators.addAll(it)
|
||||||
}
|
}
|
||||||
binding.animeSource.setAdapter(
|
binding.mediaSource.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
fragment.requireContext(),
|
fragment.requireContext(),
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
mangaReadSources.names
|
mangaReadSources.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 } }
|
||||||
source = i
|
source = i
|
||||||
setLanguageList(0, i)
|
setLanguageList(0, i)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
//invalidate if it's the last source
|
// Invalidate if it's the last source
|
||||||
val invalidate = i == mangaReadSources.names.size - 1
|
val invalidate = i == mangaReadSources.names.size - 1
|
||||||
fragment.loadChapters(i, invalidate)
|
fragment.loadChapters(i, invalidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
||||||
ext.sourceLanguage = i
|
ext.sourceLanguage = i
|
||||||
fragment.onLangChange(i, ext.saveName)
|
fragment.onLangChange(i, ext.saveName)
|
||||||
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 } }
|
||||||
setLanguageList(i, source)
|
setLanguageList(i, source)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
@@ -143,17 +170,17 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//settings
|
// Settings
|
||||||
binding.animeSourceSettings.setOnClickListener {
|
binding.mediaSourceSettings.setOnClickListener {
|
||||||
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
||||||
fragment.openSettings(ext.extension)
|
fragment.openSettings(ext.extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Grids
|
// Grids
|
||||||
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,
|
||||||
@@ -161,206 +188,207 @@ class MangaReadAdapter(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeNestedButton.setOnClickListener {
|
binding.mediaNestedButton.setOnClickListener {
|
||||||
|
val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater)
|
||||||
val dialogView =
|
|
||||||
LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
|
||||||
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.MangaDefaultView)
|
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView)
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
dialogBinding.apply {
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
mediaSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
dialogBinding.animeSourceTop.setOnClickListener {
|
sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
reversed = !reversed
|
mediaSourceTop.setOnClickListener {
|
||||||
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
reversed = !reversed
|
||||||
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
mediaSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
run = true
|
sortText.text = if (reversed) "Down to Up" else "Up to Down"
|
||||||
}
|
run = true
|
||||||
|
}
|
||||||
|
|
||||||
//Grids
|
// Grids
|
||||||
dialogBinding.animeSourceGrid.visibility = View.GONE
|
mediaSourceGrid.visibility = View.GONE
|
||||||
var selected = when (style) {
|
var selected = when (style) {
|
||||||
0 -> dialogBinding.animeSourceList
|
0 -> mediaSourceList
|
||||||
1 -> dialogBinding.animeSourceCompact
|
1 -> mediaSourceCompact
|
||||||
else -> dialogBinding.animeSourceList
|
else -> mediaSourceList
|
||||||
}
|
}
|
||||||
when (style) {
|
when (style) {
|
||||||
0 -> dialogBinding.layoutText.setText(R.string.list)
|
0 -> layoutText.setText(R.string.list)
|
||||||
1 -> dialogBinding.layoutText.setText(R.string.compact)
|
1 -> layoutText.setText(R.string.compact)
|
||||||
else -> dialogBinding.animeSourceList
|
else -> mediaSourceList
|
||||||
}
|
}
|
||||||
selected.alpha = 1f
|
|
||||||
fun selected(it: ImageButton) {
|
|
||||||
selected.alpha = 0.33f
|
|
||||||
selected = it
|
|
||||||
selected.alpha = 1f
|
selected.alpha = 1f
|
||||||
}
|
fun selected(it: ImageButton) {
|
||||||
dialogBinding.animeSourceList.setOnClickListener {
|
selected.alpha = 0.33f
|
||||||
selected(it as ImageButton)
|
selected = it
|
||||||
style = 0
|
selected.alpha = 1f
|
||||||
dialogBinding.layoutText.setText(R.string.list)
|
|
||||||
run = true
|
|
||||||
}
|
|
||||||
dialogBinding.animeSourceCompact.setOnClickListener {
|
|
||||||
selected(it as ImageButton)
|
|
||||||
style = 1
|
|
||||||
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
|
mediaSourceList.setOnClickListener {
|
||||||
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
selected(it as ImageButton)
|
||||||
val sourceAHH = mangaReadSources[source] as? DynamicMangaParser
|
style = 0
|
||||||
val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource
|
layoutText.setText(R.string.list)
|
||||||
val url = sourceHttp?.baseUrl
|
run = true
|
||||||
url?.let {
|
}
|
||||||
refresh = true
|
mediaSourceCompact.setOnClickListener {
|
||||||
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
selected(it as ImageButton)
|
||||||
.putExtra("url", url)
|
style = 1
|
||||||
startActivity(fragment.requireContext(), intent, null)
|
layoutText.setText(R.string.compact)
|
||||||
|
run = true
|
||||||
|
}
|
||||||
|
mediaWebviewContainer.setOnClickListener {
|
||||||
|
if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
|
||||||
|
toast(R.string.webview_not_installed)
|
||||||
}
|
}
|
||||||
}
|
// Start CookieCatcher activity
|
||||||
}
|
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||||
|
val sourceAHH = mangaReadSources[source] as? DynamicMangaParser
|
||||||
//Multi download
|
val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource
|
||||||
dialogBinding.downloadNo.text = "0"
|
val url = sourceHttp?.baseUrl
|
||||||
dialogBinding.animeDownloadTop.setOnClickListener {
|
url?.let {
|
||||||
//Alert dialog asking for the number of chapters to download
|
refresh = true
|
||||||
val alertDialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
val intent =
|
||||||
alertDialog.setTitle("Multi Chapter Downloader")
|
Intent(fragment.requireContext(), CookieCatcher::class.java)
|
||||||
alertDialog.setMessage("Enter the number of chapters to download")
|
.putExtra("url", url)
|
||||||
val input = NumberPicker(currContext())
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
input.minValue = 1
|
|
||||||
input.maxValue = 20
|
|
||||||
input.value = 1
|
|
||||||
alertDialog.setView(input)
|
|
||||||
alertDialog.setPositiveButton("OK") { _, _ ->
|
|
||||||
dialogBinding.downloadNo.text = "${input.value}"
|
|
||||||
}
|
|
||||||
alertDialog.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
|
|
||||||
val dialog = alertDialog.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Scanlator
|
|
||||||
dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1
|
|
||||||
dialogBinding.scanlatorNo.text = "${options.count()}"
|
|
||||||
dialogBinding.animeScanlatorTop.setOnClickListener {
|
|
||||||
val dialogView2 =
|
|
||||||
LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
|
||||||
val checkboxContainer =
|
|
||||||
dialogView2.findViewById<LinearLayout>(R.id.checkboxContainer)
|
|
||||||
val tickAllButton = dialogView2.findViewById<ImageButton>(R.id.toggleButton)
|
|
||||||
|
|
||||||
// Function to get the right image resource for the toggle button
|
|
||||||
fun getToggleImageResource(container: ViewGroup): Int {
|
|
||||||
var allChecked = true
|
|
||||||
var allUnchecked = true
|
|
||||||
|
|
||||||
for (i in 0 until container.childCount) {
|
|
||||||
val checkBox = container.getChildAt(i) as CheckBox
|
|
||||||
if (!checkBox.isChecked) {
|
|
||||||
allChecked = false
|
|
||||||
} else {
|
|
||||||
allUnchecked = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return when {
|
|
||||||
allChecked -> R.drawable.untick_all_boxes
|
|
||||||
allUnchecked -> R.drawable.tick_all_boxes
|
|
||||||
else -> R.drawable.invert_all_boxes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically add checkboxes
|
// Multi download
|
||||||
options.forEach { option ->
|
downloadNo.text = "0"
|
||||||
val checkBox = CheckBox(currContext()).apply {
|
mediaDownloadTop.setOnClickListener {
|
||||||
text = option
|
// Alert dialog asking for the number of chapters to download
|
||||||
setOnCheckedChangeListener { _, _ ->
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
// Update image resource when you change a checkbox
|
setTitle("Multi Chapter Downloader")
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
setMessage("Enter the number of chapters to download")
|
||||||
|
val input = NumberPicker(currContext())
|
||||||
|
input.minValue = 1
|
||||||
|
input.maxValue = 20
|
||||||
|
input.value = 1
|
||||||
|
setCustomView(input)
|
||||||
|
setPosButton(R.string.ok) {
|
||||||
|
downloadNo.text = "${input.value}"
|
||||||
}
|
}
|
||||||
|
setNegButton(R.string.cancel)
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set checked if its already selected
|
|
||||||
if (media.selected!!.scanlators != null) {
|
|
||||||
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
|
|
||||||
scanlatorSelectionListener?.onScanlatorsSelected()
|
|
||||||
} else {
|
|
||||||
checkBox.isChecked = true
|
|
||||||
}
|
|
||||||
checkboxContainer.addView(checkBox)
|
|
||||||
}
|
}
|
||||||
|
resetProgress.setOnClickListener {
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle(" Delete Progress for all chapters of ${media.nameRomaji}")
|
||||||
|
setMessage("This will delete all the locally stored progress for chapters")
|
||||||
|
setPosButton(R.string.ok){
|
||||||
|
// Usage
|
||||||
|
clearCustomValsForMedia("${media.id}", "_Chapter")
|
||||||
|
clearCustomValsForMedia("${media.id}", "_Vol")
|
||||||
|
|
||||||
// Create AlertDialog
|
snackString("Deleted the progress of Chapters for ${media.nameRomaji}")
|
||||||
val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
}
|
||||||
.setView(dialogView2)
|
setNegButton(R.string.no)
|
||||||
.setPositiveButton("OK") { _, _ ->
|
show()
|
||||||
hiddenScanlators.clear()
|
}
|
||||||
for (i in 0 until checkboxContainer.childCount) {
|
}
|
||||||
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
resetProgressDef.text = getString(currContext()!!,R.string.clear_stored_chapter)
|
||||||
|
|
||||||
|
// Scanlator
|
||||||
|
mangaScanlatorContainer.isVisible = options.count() > 1
|
||||||
|
scanlatorNo.text = "${options.count()}"
|
||||||
|
mangaScanlatorTop.setOnClickListener {
|
||||||
|
CustomDialogLayoutBinding.inflate(fragment.layoutInflater)
|
||||||
|
val dialogView = CustomDialogLayoutBinding.inflate(fragment.layoutInflater)
|
||||||
|
val checkboxContainer = dialogView.checkboxContainer
|
||||||
|
val tickAllButton = dialogView.toggleButton
|
||||||
|
|
||||||
|
fun getToggleImageResource(container: ViewGroup): Int {
|
||||||
|
var allChecked = true
|
||||||
|
var allUnchecked = true
|
||||||
|
|
||||||
|
for (i in 0 until container.childCount) {
|
||||||
|
val checkBox = container.getChildAt(i) as CheckBox
|
||||||
if (!checkBox.isChecked) {
|
if (!checkBox.isChecked) {
|
||||||
hiddenScanlators.add(checkBox.text.toString())
|
allChecked = false
|
||||||
|
} else {
|
||||||
|
allUnchecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment.onScanlatorChange(hiddenScanlators)
|
return when {
|
||||||
scanlatorSelectionListener?.onScanlatorsSelected()
|
allChecked -> R.drawable.untick_all_boxes
|
||||||
}
|
allUnchecked -> R.drawable.tick_all_boxes
|
||||||
.setNegativeButton("Cancel", null)
|
else -> R.drawable.invert_all_boxes
|
||||||
.show()
|
}
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
|
|
||||||
// Standard image resource
|
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
|
||||||
|
|
||||||
// Listens to ticked checkboxes and changes image resource accordingly
|
|
||||||
tickAllButton.setOnClickListener {
|
|
||||||
// Toggle checkboxes
|
|
||||||
for (i in 0 until checkboxContainer.childCount) {
|
|
||||||
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
|
||||||
checkBox.isChecked = !checkBox.isChecked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update image resource
|
options.forEach { option ->
|
||||||
|
val checkBox = CheckBox(currContext()).apply {
|
||||||
|
text = option
|
||||||
|
setOnCheckedChangeListener { _, _ ->
|
||||||
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.selected!!.scanlators != null) {
|
||||||
|
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
|
||||||
|
scanlatorSelectionListener?.onScanlatorsSelected()
|
||||||
|
} else {
|
||||||
|
checkBox.isChecked = true
|
||||||
|
}
|
||||||
|
checkboxContainer.addView(checkBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setCustomView(dialogView.root)
|
||||||
|
setPosButton("OK") {
|
||||||
|
hiddenScanlators.clear()
|
||||||
|
for (i in 0 until checkboxContainer.childCount) {
|
||||||
|
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
||||||
|
if (!checkBox.isChecked) {
|
||||||
|
hiddenScanlators.add(checkBox.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment.onScanlatorChange(hiddenScanlators)
|
||||||
|
scanlatorSelectionListener?.onScanlatorsSelected()
|
||||||
|
}
|
||||||
|
setNegButton("Cancel")
|
||||||
|
}.show()
|
||||||
|
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
|
||||||
|
tickAllButton.setOnClickListener {
|
||||||
|
for (i in 0 until checkboxContainer.childCount) {
|
||||||
|
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
||||||
|
checkBox.isChecked = !checkBox.isChecked
|
||||||
|
}
|
||||||
|
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.requireContext().customAlertDialog().apply {
|
||||||
|
setTitle("Options")
|
||||||
|
setCustomView(root)
|
||||||
|
setPosButton("OK") {
|
||||||
|
if (run) fragment.onIconPressed(style, reversed)
|
||||||
|
if (downloadNo.text != "0") {
|
||||||
|
fragment.multiDownload(downloadNo.text.toString().toInt())
|
||||||
|
}
|
||||||
|
if (refresh) fragment.loadChapters(source, true)
|
||||||
|
}
|
||||||
|
setNegButton("Cancel") {
|
||||||
|
if (refresh) fragment.loadChapters(source, true)
|
||||||
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup)
|
|
||||||
.setTitle("Options")
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton("OK") { _, _ ->
|
|
||||||
if (run) fragment.onIconPressed(style, reversed)
|
|
||||||
if (dialogBinding.downloadNo.text != "0") {
|
|
||||||
fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt())
|
|
||||||
}
|
|
||||||
if (refresh) fragment.loadChapters(source, true)
|
|
||||||
}
|
|
||||||
.setNegativeButton("Cancel") { _, _ ->
|
|
||||||
if (refresh) fragment.loadChapters(source, true)
|
|
||||||
}
|
|
||||||
.setOnCancelListener {
|
|
||||||
if (refresh) fragment.loadChapters(source, true)
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
nestedDialog?.show()
|
|
||||||
}
|
}
|
||||||
//Chapter Handling
|
// Chapter Handling
|
||||||
handleChapters()
|
handleChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +396,7 @@ class MangaReadAdapter(
|
|||||||
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) {
|
||||||
@@ -379,13 +407,13 @@ class MangaReadAdapter(
|
|||||||
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
|
||||||
)
|
)
|
||||||
@@ -403,7 +431,7 @@ class MangaReadAdapter(
|
|||||||
} else {
|
} else {
|
||||||
names[last - 1]
|
names[last - 1]
|
||||||
}
|
}
|
||||||
//chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
// chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||||
val chipText = "$startChapterString - $endChapterString"
|
val chipText = "$startChapterString - $endChapterString"
|
||||||
chip.text = chipText
|
chip.text = chipText
|
||||||
chip.setTextColor(
|
chip.setTextColor(
|
||||||
@@ -417,14 +445,14 @@ class MangaReadAdapter(
|
|||||||
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),
|
||||||
@@ -436,7 +464,7 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearChips() {
|
fun clearChips() {
|
||||||
_binding?.animeSourceChipGroup?.removeAllViews()
|
_binding?.mediaSourceChipGroup?.removeAllViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleChapters() {
|
fun handleChapters() {
|
||||||
@@ -462,70 +490,86 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
if (formattedChapters.contains(continueEp)) {
|
if (formattedChapters.contains(continueEp)) {
|
||||||
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
continueEp = chapters[formattedChapters.indexOf(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 > 0.8f) {
|
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) {
|
||||||
val e = chapters.indexOf(continueEp)
|
val e = chapters.indexOf(continueEp)
|
||||||
if (e != -1 && e + 1 < chapters.size) {
|
if (e != -1 && e + 1 < chapters.size) {
|
||||||
continueEp = chapters[e + 1]
|
continueEp = chapters[e + 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ep = media.manga.chapters!![continueEp]!!
|
val ep = media.manga.chapters!![continueEp]!!
|
||||||
binding.itemEpisodeImage.loadImage(media.banner ?: media.cover)
|
binding.itemMediaImage.loadImage(media.banner ?: media.cover)
|
||||||
binding.animeSourceContinueText.text =
|
binding.mediaSourceContinueText.text =
|
||||||
currActivity()!!.getString(
|
currActivity()!!.getString(
|
||||||
R.string.continue_chapter,
|
R.string.continue_chapter,
|
||||||
ep.number,
|
ep.number,
|
||||||
if (!ep.title.isNullOrEmpty()) ep.title else ""
|
if (!ep.title.isNullOrEmpty()) ep.title else ""
|
||||||
)
|
)
|
||||||
binding.animeSourceContinue.setOnClickListener {
|
binding.sourceContinue.setOnClickListener {
|
||||||
fragment.onMangaChapterClick(continueEp)
|
fragment.onMangaChapterClick(continueEp)
|
||||||
}
|
}
|
||||||
if (fragment.continueEp) {
|
if (fragment.continueEp) {
|
||||||
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) {
|
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) {
|
||||||
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
|
|
||||||
val sourceFound = media.manga.chapters!!.isNotEmpty()
|
binding.sourceProgressBar.visibility = View.GONE
|
||||||
binding.animeSourceNotFound.isGone = sourceFound
|
|
||||||
|
val sourceFound = filteredChapters.isNotEmpty()
|
||||||
|
val isDownloadedSource = mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser
|
||||||
|
|
||||||
|
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)) {
|
if (!sourceFound && PrefManager.getVal(PrefName.SearchSources)) {
|
||||||
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 } }
|
||||||
setLanguageList(0, nextIndex)
|
setLanguageList(0, nextIndex)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
// invalidate if it's the last source
|
// Invalidate if it's the last source
|
||||||
val invalidate = nextIndex == mangaReadSources.names.size - 1
|
val invalidate = nextIndex == mangaReadSources.names.size - 1
|
||||||
fragment.loadChapters(nextIndex, invalidate)
|
fragment.loadChapters(nextIndex, invalidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,9 +583,9 @@ class MangaReadAdapter(
|
|||||||
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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -551,9 +595,9 @@ class MangaReadAdapter(
|
|||||||
parser.extension.sources.map { LanguageMapper.getLanguageName(it.lang) }
|
parser.extension.sources.map { LanguageMapper.getLanguageName(it.lang) }
|
||||||
)
|
)
|
||||||
val items = adapter.count
|
val items = adapter.count
|
||||||
binding?.animeSourceLanguageContainer?.isVisible = items > 1
|
binding?.mediaSourceLanguageContainer?.isVisible = items > 1
|
||||||
|
|
||||||
binding?.animeSourceLanguage?.setAdapter(adapter)
|
binding?.mediaSourceLanguage?.setAdapter(adapter)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,7 +605,7 @@ class MangaReadAdapter(
|
|||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ani.dantotsu.media.manga
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
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
|
||||||
@@ -31,7 +30,7 @@ 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.R
|
import ani.dantotsu.R
|
||||||
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
|
||||||
@@ -60,6 +59,7 @@ import ani.dantotsu.settings.saving.PrefName
|
|||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
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.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
@@ -74,7 +74,7 @@ import kotlin.math.max
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
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()
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
ContextCompat.RECEIVER_EXPORTED
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
@@ -144,13 +144,13 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
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
|
||||||
@@ -199,7 +199,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter =
|
binding.mediaSourceRecycler.adapter =
|
||||||
ConcatAdapter(headerAdapter, chapterAdapter)
|
ConcatAdapter(headerAdapter, chapterAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -214,8 +214,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.animeNotSupported.visibility = View.VISIBLE
|
binding.mediaNotSupported.visibility = View.VISIBLE
|
||||||
binding.animeNotSupported.text =
|
binding.mediaNotSupported.text =
|
||||||
getString(R.string.not_supported, media.format ?: "")
|
getString(R.string.not_supported, media.format ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,10 +231,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun multiDownload(n: Int) {
|
fun multiDownload(n: Int) {
|
||||||
//get last viewed chapter
|
// Get last viewed chapter
|
||||||
val selected = media.userProgress
|
val selected = media.userProgress
|
||||||
val chapters = media.manga?.chapters?.values?.toList()
|
val chapters = media.manga?.chapters?.values?.toList()
|
||||||
//filter by selected language
|
// Filter by selected language
|
||||||
val progressChapterIndex = (chapters?.indexOfFirst {
|
val progressChapterIndex = (chapters?.indexOfFirst {
|
||||||
MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected
|
MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected
|
||||||
} ?: 0) + 1
|
} ?: 0) + 1
|
||||||
@@ -244,7 +244,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
// Calculate the end index
|
// Calculate the end index
|
||||||
val endIndex = minOf(progressChapterIndex + n, chapters.size)
|
val endIndex = minOf(progressChapterIndex + n, chapters.size)
|
||||||
|
|
||||||
//make sure there are enough chapters
|
// Make sure there are enough chapters
|
||||||
val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
|
val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
|
||||||
|
|
||||||
|
|
||||||
@@ -386,32 +386,30 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
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().customAlertDialog().apply {
|
||||||
.setTitle("Select a Source")
|
setTitle("Select a Source")
|
||||||
.setSingleChoiceItems(names, -1) { dialog, which ->
|
singleChoiceItems(names) { which ->
|
||||||
selectedSetting = allSettings[which]
|
selectedSetting = allSettings[which]
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
dialog.dismiss()
|
|
||||||
|
|
||||||
// Move the fragment transaction here
|
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
||||||
val fragment =
|
changeUIVisibility(true)
|
||||||
MangaSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
loadChapters(media.selected!!.sourceIndex, true)
|
||||||
changeUIVisibility(true)
|
}
|
||||||
loadChapters(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()
|
||||||
}
|
}
|
||||||
.setOnDismissListener {
|
onDismiss{
|
||||||
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
|
||||||
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
||||||
@@ -584,7 +582,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
private fun reload() {
|
private fun reload() {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
|
|
||||||
//Find latest chapter for subscription
|
// Find latest chapter for subscription
|
||||||
selected.latest =
|
selected.latest =
|
||||||
media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
||||||
selected.latest =
|
selected.latest =
|
||||||
@@ -618,14 +616,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||||||
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 {
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ import ani.dantotsu.media.Media
|
|||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.media.MediaNameAdapter
|
import ani.dantotsu.media.MediaNameAdapter
|
||||||
import ani.dantotsu.media.MediaSingleton
|
import ani.dantotsu.media.MediaSingleton
|
||||||
|
import ani.dantotsu.media.anime.ExoplayerView
|
||||||
|
import ani.dantotsu.media.anime.ExoplayerView.Companion
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
@@ -83,6 +85,7 @@ import ani.dantotsu.showSystemBarsRetractView
|
|||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
@@ -184,6 +187,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defaultSettings = loadReaderSettings("reader_settings") ?: defaultSettings
|
defaultSettings = loadReaderSettings("reader_settings") ?: defaultSettings
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
@@ -258,7 +263,16 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
else model.getMedia().value ?: return
|
else model.getMedia().value ?: return
|
||||||
model.setMedia(media)
|
model.setMedia(media)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val list = (PrefManager.getNullableCustomVal(
|
||||||
|
"continueMangaList",
|
||||||
|
listOf<Int>(),
|
||||||
|
List::class.java
|
||||||
|
) as List<Int>).toMutableList()
|
||||||
|
if (list.contains(media.id)) list.remove(media.id)
|
||||||
|
list.add(media.id)
|
||||||
|
|
||||||
|
PrefManager.setCustomVal("continueMangaList", list)
|
||||||
if (PrefManager.getVal(PrefName.AutoDetectWebtoon) && media.countryOfOrigin != "JP") applyWebtoon(
|
if (PrefManager.getVal(PrefName.AutoDetectWebtoon) && media.countryOfOrigin != "JP") applyWebtoon(
|
||||||
defaultSettings
|
defaultSettings
|
||||||
)
|
)
|
||||||
@@ -400,7 +414,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
val context = this
|
val context = this
|
||||||
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||||
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
|
||||||
if ((isOnline(context) && !offline) && Discord.token != null && !incognito) {
|
val rpcenabled: Boolean = PrefManager.getVal(PrefName.rpcEnabled)
|
||||||
|
if ((isOnline(context) && !offline) && Discord.token != null && !incognito && rpcenabled) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val discordMode = PrefManager.getCustomVal("discord_mode", "dantotsu")
|
val discordMode = PrefManager.getCustomVal("discord_mode", "dantotsu")
|
||||||
val buttons = when (discordMode) {
|
val buttons = when (discordMode) {
|
||||||
@@ -1013,28 +1028,27 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked)
|
PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked)
|
||||||
showProgressDialog = !isChecked
|
showProgressDialog = !isChecked
|
||||||
}
|
}
|
||||||
AlertDialog.Builder(this, R.style.MyPopup)
|
customAlertDialog().apply {
|
||||||
.setTitle(getString(R.string.title_update_progress))
|
setTitle(R.string.title_update_progress)
|
||||||
.setView(dialogView)
|
setCustomView(dialogView)
|
||||||
.setCancelable(false)
|
setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
|
setPosButton(R.string.yes) {
|
||||||
PrefManager.setCustomVal("${media.id}_save_progress", true)
|
PrefManager.setCustomVal("${media.id}_save_progress", true)
|
||||||
updateProgress(
|
updateProgress(
|
||||||
media,
|
media,
|
||||||
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
.toString()
|
.toString()
|
||||||
)
|
)
|
||||||
dialog.dismiss()
|
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
.setNegativeButton(getString(R.string.no)) { dialog, _ ->
|
setNegButton(R.string.no) {
|
||||||
PrefManager.setCustomVal("${media.id}_save_progress", false)
|
PrefManager.setCustomVal("${media.id}_save_progress", false)
|
||||||
dialog.dismiss()
|
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
.setOnCancelListener { hideSystemBars() }
|
setOnCancelListener { hideSystemBars() }
|
||||||
.create()
|
show()
|
||||||
.show()
|
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!incognito && PrefManager.getCustomVal(
|
if (!incognito && PrefManager.getCustomVal(
|
||||||
"${media.id}_save_progress",
|
"${media.id}_save_progress",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewConfiguration
|
import android.view.ViewConfiguration
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
class Swipy @JvmOverloads constructor(
|
class Swipy @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null
|
context: Context, attrs: AttributeSet? = null
|
||||||
@@ -16,7 +17,6 @@ class Swipy @JvmOverloads constructor(
|
|||||||
var dragDivider: Int = 5
|
var dragDivider: Int = 5
|
||||||
var vertical = true
|
var vertical = true
|
||||||
|
|
||||||
//public, in case a different sub child needs to be considered
|
|
||||||
var child: View? = getChildAt(0)
|
var child: View? = getChildAt(0)
|
||||||
|
|
||||||
var topBeingSwiped: ((Float) -> Unit) = {}
|
var topBeingSwiped: ((Float) -> Unit) = {}
|
||||||
@@ -29,49 +29,47 @@ class Swipy @JvmOverloads constructor(
|
|||||||
var rightBeingSwiped: ((Float) -> Unit) = {}
|
var rightBeingSwiped: ((Float) -> Unit) = {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DRAG_RATE = .5f
|
private const val DRAG_RATE = 0.5f
|
||||||
private const val INVALID_POINTER = -1
|
private const val INVALID_POINTER = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
private var touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
|
||||||
private var activePointerId = INVALID_POINTER
|
private var activePointerId = INVALID_POINTER
|
||||||
private var isBeingDragged = false
|
private var isBeingDragged = false
|
||||||
private var initialDown = 0f
|
private var initialDown = 0f
|
||||||
private var initialMotion = 0f
|
private var initialMotion = 0f
|
||||||
|
|
||||||
enum class VerticalPosition {
|
private enum class VerticalPosition { Top, None, Bottom }
|
||||||
Top,
|
private enum class HorizontalPosition { Left, None, Right }
|
||||||
None,
|
|
||||||
Bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class HorizontalPosition {
|
|
||||||
Left,
|
|
||||||
None,
|
|
||||||
Right
|
|
||||||
}
|
|
||||||
|
|
||||||
private var horizontalPos = HorizontalPosition.None
|
private var horizontalPos = HorizontalPosition.None
|
||||||
private var verticalPos = VerticalPosition.None
|
private var verticalPos = VerticalPosition.None
|
||||||
|
|
||||||
private fun setChildPosition() {
|
private fun setChildPosition() {
|
||||||
child?.apply {
|
child?.let {
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
verticalPos = VerticalPosition.None
|
verticalPos = when {
|
||||||
if (!canScrollVertically(1)) {
|
!it.canScrollVertically(1) && !it.canScrollVertically(-1) -> {
|
||||||
verticalPos = VerticalPosition.Bottom
|
if (initialDown > (Resources.getSystem().displayMetrics.heightPixels / 2))
|
||||||
}
|
VerticalPosition.Bottom
|
||||||
if (!canScrollVertically(-1)) {
|
else
|
||||||
verticalPos = VerticalPosition.Top
|
VerticalPosition.Top
|
||||||
|
}
|
||||||
|
!it.canScrollVertically(1) -> VerticalPosition.Bottom
|
||||||
|
!it.canScrollVertically(-1) -> VerticalPosition.Top
|
||||||
|
else -> VerticalPosition.None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
horizontalPos = HorizontalPosition.None
|
horizontalPos = when {
|
||||||
if (!canScrollHorizontally(1)) {
|
!it.canScrollHorizontally(1) && !it.canScrollHorizontally(-1) -> {
|
||||||
horizontalPos = HorizontalPosition.Right
|
if (initialDown > (Resources.getSystem().displayMetrics.widthPixels / 2))
|
||||||
}
|
HorizontalPosition.Right
|
||||||
if (!canScrollHorizontally(-1)) {
|
else
|
||||||
horizontalPos = HorizontalPosition.Left
|
HorizontalPosition.Left
|
||||||
|
}
|
||||||
|
!it.canScrollHorizontally(1) -> HorizontalPosition.Right
|
||||||
|
!it.canScrollHorizontally(-1) -> HorizontalPosition.Left
|
||||||
|
else -> HorizontalPosition.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,44 +83,26 @@ class Swipy @JvmOverloads constructor(
|
|||||||
|
|
||||||
private fun onSecondaryPointerUp(ev: MotionEvent) {
|
private fun onSecondaryPointerUp(ev: MotionEvent) {
|
||||||
val pointerIndex = ev.actionIndex
|
val pointerIndex = ev.actionIndex
|
||||||
val pointerId = ev.getPointerId(pointerIndex)
|
if (ev.getPointerId(pointerIndex) == activePointerId) {
|
||||||
if (pointerId == activePointerId) {
|
activePointerId = ev.getPointerId(if (pointerIndex == 0) 1 else 0)
|
||||||
val newPointerIndex = if (pointerIndex == 0) 1 else 0
|
|
||||||
activePointerId = ev.getPointerId(newPointerIndex)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
val action = ev.actionMasked
|
if (!isEnabled || canChildScroll()) return false
|
||||||
val pointerIndex: Int
|
|
||||||
if (!isEnabled || canChildScroll()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
when (action) {
|
when (ev.actionMasked) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
activePointerId = ev.getPointerId(0)
|
activePointerId = ev.getPointerId(0)
|
||||||
|
initialDown = if (vertical) ev.getY(0) else ev.getX(0)
|
||||||
isBeingDragged = false
|
isBeingDragged = false
|
||||||
pointerIndex = ev.findPointerIndex(activePointerId)
|
|
||||||
if (pointerIndex < 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
initialDown = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
if (activePointerId == INVALID_POINTER) {
|
val pointerIndex = ev.findPointerIndex(activePointerId)
|
||||||
//("Got ACTION_MOVE event but don't have an active pointer id.")
|
if (pointerIndex >= 0) {
|
||||||
return false
|
startDragging(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex))
|
||||||
}
|
}
|
||||||
pointerIndex = ev.findPointerIndex(activePointerId)
|
|
||||||
if (pointerIndex < 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
|
||||||
startDragging(pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
isBeingDragged = false
|
isBeingDragged = false
|
||||||
@@ -134,127 +114,97 @@ class Swipy @JvmOverloads constructor(
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
val action = ev.actionMasked
|
if (!isEnabled || canChildScroll()) return false
|
||||||
|
|
||||||
val pointerIndex: Int
|
val pointerIndex: Int
|
||||||
if (!isEnabled || canChildScroll()) {
|
when (ev.actionMasked) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
when (action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
activePointerId = ev.getPointerId(0)
|
activePointerId = ev.getPointerId(0)
|
||||||
isBeingDragged = false
|
isBeingDragged = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
pointerIndex = ev.findPointerIndex(activePointerId)
|
pointerIndex = ev.findPointerIndex(activePointerId)
|
||||||
if (pointerIndex < 0) {
|
if (pointerIndex >= 0) {
|
||||||
//("Got ACTION_MOVE event but have an invalid active pointer id.")
|
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
||||||
return false
|
startDragging(pos)
|
||||||
}
|
if (isBeingDragged) handleDrag(pos)
|
||||||
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
|
||||||
startDragging(pos)
|
|
||||||
if (isBeingDragged) {
|
|
||||||
val overscroll = (
|
|
||||||
if (vertical)
|
|
||||||
if (verticalPos == VerticalPosition.Top) pos - initialMotion else initialMotion - pos
|
|
||||||
else
|
|
||||||
if (horizontalPos == HorizontalPosition.Left) pos - initialMotion else initialMotion - pos
|
|
||||||
) * DRAG_RATE
|
|
||||||
|
|
||||||
if (overscroll > 0) {
|
|
||||||
parent.requestDisallowInterceptTouchEvent(true)
|
|
||||||
if (vertical) {
|
|
||||||
val totalDragDistance =
|
|
||||||
Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
|
||||||
if (verticalPos == VerticalPosition.Top)
|
|
||||||
topBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
|
|
||||||
else
|
|
||||||
bottomBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
|
|
||||||
} else {
|
|
||||||
val totalDragDistance =
|
|
||||||
Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
|
||||||
if (horizontalPos == HorizontalPosition.Left)
|
|
||||||
leftBeingSwiped.invoke(overscroll / totalDragDistance)
|
|
||||||
else
|
|
||||||
rightBeingSwiped.invoke(overscroll / totalDragDistance)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||||
pointerIndex = ev.actionIndex
|
pointerIndex = ev.actionIndex
|
||||||
if (pointerIndex < 0) {
|
if (pointerIndex >= 0) activePointerId = ev.getPointerId(pointerIndex)
|
||||||
//("Got ACTION_POINTER_DOWN event but have an invalid action index.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
activePointerId = ev.getPointerId(pointerIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
if (vertical) {
|
resetSwipes()
|
||||||
topBeingSwiped.invoke(0f)
|
|
||||||
bottomBeingSwiped.invoke(0f)
|
|
||||||
} else {
|
|
||||||
rightBeingSwiped.invoke(0f)
|
|
||||||
leftBeingSwiped.invoke(0f)
|
|
||||||
}
|
|
||||||
pointerIndex = ev.findPointerIndex(activePointerId)
|
pointerIndex = ev.findPointerIndex(activePointerId)
|
||||||
if (pointerIndex < 0) {
|
if (pointerIndex >= 0) finishSpinner(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex))
|
||||||
//("Got ACTION_UP event but don't have an active pointer id.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (isBeingDragged) {
|
|
||||||
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
|
||||||
val overscroll = (
|
|
||||||
if (vertical)
|
|
||||||
if (verticalPos == VerticalPosition.Top) pos - initialMotion else initialMotion - pos
|
|
||||||
else
|
|
||||||
if (horizontalPos == HorizontalPosition.Left) pos - initialMotion else initialMotion - pos
|
|
||||||
) * DRAG_RATE
|
|
||||||
isBeingDragged = false
|
|
||||||
finishSpinner(overscroll)
|
|
||||||
}
|
|
||||||
activePointerId = INVALID_POINTER
|
activePointerId = INVALID_POINTER
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_CANCEL -> return false
|
MotionEvent.ACTION_CANCEL -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startDragging(pos: Float) {
|
private fun startDragging(pos: Float) {
|
||||||
val posDiff =
|
val posDiff = if ((vertical && verticalPos == VerticalPosition.Top) || (!vertical && horizontalPos == HorizontalPosition.Left))
|
||||||
if ((vertical && verticalPos == VerticalPosition.Top) || (!vertical && horizontalPos == HorizontalPosition.Left))
|
pos - initialDown
|
||||||
pos - initialDown
|
else
|
||||||
else
|
initialDown - pos
|
||||||
initialDown - pos
|
|
||||||
if (posDiff > touchSlop && !isBeingDragged) {
|
if (posDiff > touchSlop && !isBeingDragged) {
|
||||||
initialMotion = initialDown + touchSlop
|
initialMotion = initialDown + touchSlop
|
||||||
isBeingDragged = true
|
isBeingDragged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finishSpinner(overscrollDistance: Float) {
|
private fun handleDrag(pos: Float) {
|
||||||
|
val overscroll = abs((pos - initialMotion) * DRAG_RATE)
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
||||||
if (overscrollDistance * 2 > totalDragDistance)
|
if (verticalPos == VerticalPosition.Top)
|
||||||
|
topBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
|
||||||
|
else
|
||||||
|
bottomBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
|
||||||
|
} else {
|
||||||
|
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
||||||
|
if (horizontalPos == HorizontalPosition.Left)
|
||||||
|
leftBeingSwiped.invoke(overscroll / totalDragDistance)
|
||||||
|
else
|
||||||
|
rightBeingSwiped.invoke(overscroll / totalDragDistance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetSwipes() {
|
||||||
|
if (vertical) {
|
||||||
|
topBeingSwiped.invoke(0f)
|
||||||
|
bottomBeingSwiped.invoke(0f)
|
||||||
|
} else {
|
||||||
|
rightBeingSwiped.invoke(0f)
|
||||||
|
leftBeingSwiped.invoke(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishSpinner(overscrollDistance: Float) {
|
||||||
|
if (vertical) {
|
||||||
|
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
||||||
|
val swipeDistance = abs(overscrollDistance - initialMotion)
|
||||||
|
if (swipeDistance > totalDragDistance) {
|
||||||
if (verticalPos == VerticalPosition.Top)
|
if (verticalPos == VerticalPosition.Top)
|
||||||
onTopSwiped.invoke()
|
onTopSwiped.invoke()
|
||||||
else
|
else
|
||||||
onBottomSwiped.invoke()
|
onBottomSwiped.invoke()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
||||||
if (overscrollDistance > totalDragDistance)
|
val swipeDistance = abs(overscrollDistance - initialMotion)
|
||||||
|
if (swipeDistance > totalDragDistance) {
|
||||||
if (horizontalPos == HorizontalPosition.Left)
|
if (horizontalPos == HorizontalPosition.Left)
|
||||||
onLeftSwiped.invoke()
|
onLeftSwiped.invoke()
|
||||||
else
|
else
|
||||||
onRightSwiped.invoke()
|
onRightSwiped.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ class NovelReadAdapter(
|
|||||||
val source =
|
val source =
|
||||||
media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
||||||
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
||||||
binding.animeSource.setText(novelReadSources.names[source], false)
|
binding.mediaSource.setText(novelReadSources.names[source], false)
|
||||||
}
|
}
|
||||||
binding.animeSource.setAdapter(
|
binding.mediaSource.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
fragment.requireContext(),
|
fragment.requireContext(),
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
novelReadSources.names
|
novelReadSources.names
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.mediaSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
fragment.onSourceChange(i)
|
fragment.onSourceChange(i)
|
||||||
search()
|
search()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
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.novel.NovelDownloaderService
|
import ani.dantotsu.download.novel.NovelDownloaderService
|
||||||
@@ -47,7 +47,7 @@ class NovelReadFragment : Fragment(),
|
|||||||
DownloadTriggerCallback,
|
DownloadTriggerCallback,
|
||||||
DownloadedCheckCallback {
|
DownloadedCheckCallback {
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
@@ -214,11 +214,11 @@ class NovelReadFragment : Fragment(),
|
|||||||
ContextCompat.RECEIVER_EXPORTED
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight)
|
||||||
|
|
||||||
binding.animeSourceRecycler.layoutManager = LinearLayoutManager(requireContext())
|
binding.mediaSourceRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
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
|
||||||
@@ -237,7 +237,7 @@ class NovelReadFragment : Fragment(),
|
|||||||
this,
|
this,
|
||||||
this
|
this
|
||||||
) // probably a better way to do this but it works
|
) // probably a better way to do this but it works
|
||||||
binding.animeSourceRecycler.adapter =
|
binding.mediaSourceRecycler.adapter =
|
||||||
ConcatAdapter(headerAdapter, novelResponseAdapter)
|
ConcatAdapter(headerAdapter, novelResponseAdapter)
|
||||||
loaded = true
|
loaded = true
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
@@ -290,7 +290,7 @@ class NovelReadFragment : 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,12 +304,12 @@ class NovelReadFragment : 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ani.dantotsu.parsers.ShowResponse
|
|||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
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.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class NovelResponseAdapter(
|
|||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val novel = list[position]
|
val novel = list[position]
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
binding.itemEpisodeImage.loadImage(novel.coverUrl, 400, 0)
|
binding.itemMediaImage.loadImage(novel.coverUrl, 400, 0)
|
||||||
|
|
||||||
val color =fragment.requireContext().getThemeColor(com.google.android.material.R.attr.colorOnBackground)
|
val color =fragment.requireContext().getThemeColor(com.google.android.material.R.attr.colorOnBackground)
|
||||||
binding.itemEpisodeTitle.text = novel.name
|
binding.itemEpisodeTitle.text = novel.name
|
||||||
@@ -93,27 +94,22 @@ class NovelResponseAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.root.setOnLongClickListener {
|
binding.root.setOnLongClickListener {
|
||||||
val builder = androidx.appcompat.app.AlertDialog.Builder(
|
it.context.customAlertDialog().apply {
|
||||||
fragment.requireContext(),
|
setTitle("Delete ${novel.name}?")
|
||||||
R.style.MyPopup
|
setMessage("Are you sure you want to delete ${novel.name}?")
|
||||||
)
|
setPosButton(R.string.yes) {
|
||||||
builder.setTitle("Delete ${novel.name}?")
|
downloadedCheckCallback.deleteDownload(novel)
|
||||||
builder.setMessage("Are you sure you want to delete ${novel.name}?")
|
deleteDownload(novel.link)
|
||||||
builder.setPositiveButton("Yes") { _, _ ->
|
snackString("Deleted ${novel.name}")
|
||||||
downloadedCheckCallback.deleteDownload(novel)
|
if (binding.itemEpisodeFiller.text.toString()
|
||||||
deleteDownload(novel.link)
|
.contains("Download", ignoreCase = true)
|
||||||
snackString("Deleted ${novel.name}")
|
) {
|
||||||
if (binding.itemEpisodeFiller.text.toString()
|
binding.itemEpisodeFiller.text = ""
|
||||||
.contains("Download", ignoreCase = true)
|
}
|
||||||
) {
|
|
||||||
binding.itemEpisodeFiller.text = ""
|
|
||||||
}
|
}
|
||||||
|
setNegButton(R.string.no)
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.media.novel.novelreader
|
|||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
@@ -45,12 +46,17 @@ import ani.dantotsu.tryWith
|
|||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.vipulog.ebookreader.Book
|
import com.vipulog.ebookreader.Book
|
||||||
import com.vipulog.ebookreader.EbookReaderEventListener
|
import com.vipulog.ebookreader.EbookReaderEventListener
|
||||||
|
import com.vipulog.ebookreader.EbookReaderView
|
||||||
import com.vipulog.ebookreader.ReaderError
|
import com.vipulog.ebookreader.ReaderError
|
||||||
import com.vipulog.ebookreader.ReaderFlow
|
import com.vipulog.ebookreader.ReaderFlow
|
||||||
import com.vipulog.ebookreader.ReaderTheme
|
import com.vipulog.ebookreader.ReaderTheme
|
||||||
import com.vipulog.ebookreader.RelocationInfo
|
import com.vipulog.ebookreader.RelocationInfo
|
||||||
import com.vipulog.ebookreader.TocItem
|
import com.vipulog.ebookreader.TocItem
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@@ -190,6 +196,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun setupViews() {
|
private fun setupViews() {
|
||||||
|
binding.bookReader.useSafeScope(this)
|
||||||
|
|
||||||
scope.launch { binding.bookReader.openBook(intent.data!!) }
|
scope.launch { binding.bookReader.openBook(intent.data!!) }
|
||||||
binding.bookReader.setEbookReaderListener(this)
|
binding.bookReader.setEbookReaderListener(this)
|
||||||
|
|
||||||
@@ -540,4 +548,42 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
|
|||||||
hideSystemBars()
|
hideSystemBars()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⚠️ TEMPORARY HOTFIX ⚠️
|
||||||
|
*
|
||||||
|
* This is a hacky workaround to handle crashes in the deprecated ebookreader library.
|
||||||
|
*
|
||||||
|
* Current implementation:
|
||||||
|
* - Uses reflection to access the private `scope` field in `EbookReaderView`.
|
||||||
|
* - Replaces the existing `CoroutineScope` with a new one that includes a
|
||||||
|
* `CoroutineExceptionHandler`.
|
||||||
|
* - Ensures that uncaught exceptions in coroutines are handled gracefully by showing a snackbar
|
||||||
|
* with error details.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - This is NOT a long-term solution
|
||||||
|
* - The underlying library is archived and unmaintained
|
||||||
|
* - Schedule migration to an actively maintained library
|
||||||
|
* - Consider alternatives like https://github.com/readium/kotlin-toolkit
|
||||||
|
*/
|
||||||
|
fun EbookReaderView.useSafeScope(activity: Activity) {
|
||||||
|
runCatching {
|
||||||
|
val scopeField = javaClass.getDeclaredField("scope").apply { isAccessible = true }
|
||||||
|
val currentScope = scopeField.get(this) as CoroutineScope
|
||||||
|
val safeScope = CoroutineScope(
|
||||||
|
SupervisorJob() +
|
||||||
|
currentScope.coroutineContext.minusKey(Job) +
|
||||||
|
scopeExceptionHandler(activity)
|
||||||
|
)
|
||||||
|
scopeField.set(this, safeScope)
|
||||||
|
}.onFailure { e ->
|
||||||
|
snackString(e.localizedMessage, activity, e.stackTraceToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scopeExceptionHandler(activity: Activity) = CoroutineExceptionHandler { _, e ->
|
||||||
|
snackString(e.localizedMessage, activity, e.stackTraceToString())
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class ListActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
window.statusBarColor = primaryColor
|
window.statusBarColor = primaryColor
|
||||||
window.navigationBarColor = primaryColor
|
window.navigationBarColor = primaryColor
|
||||||
|
binding.listed.visibility = View.GONE
|
||||||
binding.listTabLayout.setBackgroundColor(primaryColor)
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(primaryTextColor)
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
|
|||||||
@@ -20,21 +20,18 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
val intent = when (taskType) {
|
|
||||||
TaskType.COMMENT_NOTIFICATION -> Intent(
|
|
||||||
context,
|
|
||||||
CommentNotificationReceiver::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
TaskType.ANILIST_NOTIFICATION -> Intent(
|
val intent = when {
|
||||||
context,
|
taskType == TaskType.COMMENT_NOTIFICATION && PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1 ->
|
||||||
AnilistNotificationReceiver::class.java
|
Intent(context, CommentNotificationReceiver::class.java)
|
||||||
)
|
|
||||||
|
|
||||||
TaskType.SUBSCRIPTION_NOTIFICATION -> Intent(
|
taskType == TaskType.ANILIST_NOTIFICATION ->
|
||||||
context,
|
Intent(context, AnilistNotificationReceiver::class.java)
|
||||||
SubscriptionNotificationReceiver::class.java
|
|
||||||
)
|
taskType == TaskType.SUBSCRIPTION_NOTIFICATION ->
|
||||||
|
Intent(context, SubscriptionNotificationReceiver::class.java)
|
||||||
|
|
||||||
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
@@ -64,21 +61,18 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler {
|
|||||||
|
|
||||||
override fun cancelTask(taskType: TaskType) {
|
override fun cancelTask(taskType: TaskType) {
|
||||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
val intent = when (taskType) {
|
|
||||||
TaskType.COMMENT_NOTIFICATION -> Intent(
|
|
||||||
context,
|
|
||||||
CommentNotificationReceiver::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
TaskType.ANILIST_NOTIFICATION -> Intent(
|
val intent = when {
|
||||||
context,
|
taskType == TaskType.COMMENT_NOTIFICATION && PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1 ->
|
||||||
AnilistNotificationReceiver::class.java
|
Intent(context, CommentNotificationReceiver::class.java)
|
||||||
)
|
|
||||||
|
|
||||||
TaskType.SUBSCRIPTION_NOTIFICATION -> Intent(
|
taskType == TaskType.ANILIST_NOTIFICATION ->
|
||||||
context,
|
Intent(context, AnilistNotificationReceiver::class.java)
|
||||||
SubscriptionNotificationReceiver::class.java
|
|
||||||
)
|
taskType == TaskType.SUBSCRIPTION_NOTIFICATION ->
|
||||||
|
Intent(context, SubscriptionNotificationReceiver::class.java)
|
||||||
|
|
||||||
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
|||||||
32
app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt
Normal file
32
app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package ani.dantotsu.others
|
||||||
|
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.style.AlignmentSpan
|
||||||
|
import io.noties.markwon.MarkwonConfiguration
|
||||||
|
import io.noties.markwon.RenderProps
|
||||||
|
import io.noties.markwon.html.HtmlTag
|
||||||
|
import io.noties.markwon.html.tag.SimpleTagHandler
|
||||||
|
|
||||||
|
|
||||||
|
class AlignTagHandler : SimpleTagHandler() {
|
||||||
|
|
||||||
|
override fun getSpans(
|
||||||
|
configuration: MarkwonConfiguration,
|
||||||
|
renderProps: RenderProps,
|
||||||
|
tag: HtmlTag
|
||||||
|
): Any {
|
||||||
|
val alignment: Layout.Alignment = if (tag.attributes().containsKey("center")) {
|
||||||
|
Layout.Alignment.ALIGN_CENTER
|
||||||
|
} else if (tag.attributes().containsKey("end")) {
|
||||||
|
Layout.Alignment.ALIGN_OPPOSITE
|
||||||
|
} else {
|
||||||
|
Layout.Alignment.ALIGN_NORMAL
|
||||||
|
}
|
||||||
|
|
||||||
|
return AlignmentSpan.Standard(alignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportedTags(): Collection<String> {
|
||||||
|
return setOf("align")
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/src/main/java/ani/dantotsu/others/Anify.kt
Normal file
46
app/src/main/java/ani/dantotsu/others/Anify.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package ani.dantotsu.others
|
||||||
|
|
||||||
|
import ani.dantotsu.FileUrl
|
||||||
|
import ani.dantotsu.Mapper
|
||||||
|
import ani.dantotsu.client
|
||||||
|
import ani.dantotsu.media.anime.Episode
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
object Anify {
|
||||||
|
suspend fun fetchAndParseMetadata(id :Int): Map<String, Episode> {
|
||||||
|
val response = client.get("https://anify.eltik.cc/content-metadata/$id")
|
||||||
|
.parsed<JsonArray>().map {
|
||||||
|
Mapper.json.decodeFromJsonElement<AnifyElement>(it)
|
||||||
|
}
|
||||||
|
return response.firstOrNull()?.data?.associate {
|
||||||
|
it.number.toString() to Episode(
|
||||||
|
number = it.number.toString(),
|
||||||
|
title = it.title,
|
||||||
|
desc = it.description,
|
||||||
|
thumb = FileUrl[it.img],
|
||||||
|
)
|
||||||
|
} ?: emptyMap()
|
||||||
|
}
|
||||||
|
@Serializable
|
||||||
|
data class AnifyElement (
|
||||||
|
@SerialName("providerId")
|
||||||
|
val providerID: String? = null,
|
||||||
|
val data: List<Datum>? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Datum (
|
||||||
|
val id: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val hasDub: Boolean? = null,
|
||||||
|
val img: String? = null,
|
||||||
|
val isFiller: Boolean? = null,
|
||||||
|
val number: Long? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
val updatedAt: Long? = null,
|
||||||
|
val rating: Double? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ 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 android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
@@ -24,7 +25,10 @@ class CrashActivity : AppCompatActivity() {
|
|||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
binding = ActivityCrashBinding.inflate(layoutInflater)
|
binding = ActivityCrashBinding.inflate(layoutInflater)
|
||||||
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE
|
||||||
|
)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
topMargin = statusBarHeight
|
topMargin = statusBarHeight
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ object Kitsu {
|
|||||||
val headers = mapOf(
|
val headers = mapOf(
|
||||||
"Content-Type" to "application/json",
|
"Content-Type" to "application/json",
|
||||||
"Accept" to "application/json",
|
"Accept" to "application/json",
|
||||||
"Accept-Encoding" to "gzip, deflate",
|
|
||||||
"Accept-Language" to "en-US,en;q=0.5",
|
|
||||||
"Host" to "kitsu.io",
|
|
||||||
"Connection" to "keep-alive",
|
|
||||||
"Origin" to "https://kitsu.io",
|
|
||||||
"Sec-Fetch-Dest" to "empty",
|
|
||||||
"Sec-Fetch-Mode" to "cors",
|
|
||||||
"Sec-Fetch-Site" to "cross-site",
|
|
||||||
)
|
)
|
||||||
val response = tryWithSuspend {
|
val response = tryWithSuspend {
|
||||||
val res = client.post(
|
val res = client.post(
|
||||||
@@ -152,4 +144,4 @@ query {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
144
app/src/main/java/ani/dantotsu/others/Xubtitle.kt
Normal file
144
app/src/main/java/ani/dantotsu/others/Xubtitle.kt
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package ani.dantotsu.others
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.LinearGradient
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Shader
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
|
||||||
|
class Xubtitle
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0,
|
||||||
|
) : AppCompatTextView(context, attrs, defStyleAttr) {
|
||||||
|
private var outlineThickness: Float = 0f
|
||||||
|
private var effectColor: Int = currentTextColor
|
||||||
|
private var currentEffect: Effect = Effect.NONE
|
||||||
|
|
||||||
|
private val shadowPaint = Paint().apply { isAntiAlias = true }
|
||||||
|
private val outlinePaint = Paint().apply { isAntiAlias = true }
|
||||||
|
private var shineShader: Shader? = null
|
||||||
|
|
||||||
|
enum class Effect {
|
||||||
|
NONE,
|
||||||
|
OUTLINE,
|
||||||
|
SHINE,
|
||||||
|
DROP_SHADOW,
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
val text = text.toString()
|
||||||
|
val textPaint =
|
||||||
|
TextPaint(paint).apply {
|
||||||
|
color = currentTextColor
|
||||||
|
}
|
||||||
|
val staticLayout =
|
||||||
|
StaticLayout.Builder
|
||||||
|
.obtain(text, 0, text.length, textPaint, width)
|
||||||
|
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
|
.setLineSpacing(0f, 1f)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
when (currentEffect) {
|
||||||
|
Effect.OUTLINE -> {
|
||||||
|
textPaint.style = Paint.Style.STROKE
|
||||||
|
textPaint.strokeWidth = outlineThickness
|
||||||
|
textPaint.color = effectColor
|
||||||
|
|
||||||
|
staticLayout.draw(canvas)
|
||||||
|
|
||||||
|
textPaint.style = Paint.Style.FILL
|
||||||
|
textPaint.color = currentTextColor
|
||||||
|
staticLayout.draw(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect.DROP_SHADOW -> {
|
||||||
|
setLayerType(LAYER_TYPE_SOFTWARE, null)
|
||||||
|
textPaint.setShadowLayer(outlineThickness, 4f, 4f, effectColor)
|
||||||
|
|
||||||
|
staticLayout.draw(canvas)
|
||||||
|
|
||||||
|
textPaint.clearShadowLayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect.SHINE -> {
|
||||||
|
val shadowShader =
|
||||||
|
LinearGradient(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
width.toFloat(),
|
||||||
|
height.toFloat(),
|
||||||
|
intArrayOf(Color.WHITE, effectColor, Color.BLACK),
|
||||||
|
null,
|
||||||
|
Shader.TileMode.CLAMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
val shadowPaint =
|
||||||
|
Paint().apply {
|
||||||
|
isAntiAlias = true
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
textSize = textPaint.textSize
|
||||||
|
typeface = textPaint.typeface
|
||||||
|
shader = shadowShader
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawText(
|
||||||
|
text,
|
||||||
|
x + 4f, // Shadow offset
|
||||||
|
y + 4f,
|
||||||
|
shadowPaint,
|
||||||
|
)
|
||||||
|
|
||||||
|
val shader =
|
||||||
|
LinearGradient(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
width.toFloat(),
|
||||||
|
height.toFloat(),
|
||||||
|
intArrayOf(effectColor, Color.WHITE, Color.WHITE),
|
||||||
|
null,
|
||||||
|
Shader.TileMode.CLAMP,
|
||||||
|
)
|
||||||
|
textPaint.shader = shader
|
||||||
|
staticLayout.draw(canvas)
|
||||||
|
textPaint.shader = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect.NONE -> {
|
||||||
|
staticLayout.draw(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyOutline(
|
||||||
|
color: Int,
|
||||||
|
outlineThickness: Float,
|
||||||
|
) {
|
||||||
|
this.effectColor = color
|
||||||
|
this.outlineThickness = outlineThickness
|
||||||
|
currentEffect = Effect.OUTLINE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Too hard for me to figure it out
|
||||||
|
fun applyShineEffect(color: Int) {
|
||||||
|
this.effectColor = color
|
||||||
|
currentEffect = Effect.SHINE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyDropShadow(
|
||||||
|
color: Int,
|
||||||
|
outlineThickness: Float,
|
||||||
|
) {
|
||||||
|
this.effectColor = color
|
||||||
|
this.outlineThickness = outlineThickness
|
||||||
|
currentEffect = Effect.DROP_SHADOW
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package ani.dantotsu.others.calc
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
|
||||||
|
object BiometricPromptUtils {
|
||||||
|
private const val TAG = "BiometricPromptUtils"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a BiometricPrompt instance
|
||||||
|
* @param activity: AppCompatActivity
|
||||||
|
* @param processSuccess: success callback
|
||||||
|
*/
|
||||||
|
fun createBiometricPrompt(
|
||||||
|
activity: AppCompatActivity,
|
||||||
|
processSuccess: (BiometricPrompt.AuthenticationResult) -> Unit
|
||||||
|
): BiometricPrompt {
|
||||||
|
val executor = ContextCompat.getMainExecutor(activity)
|
||||||
|
|
||||||
|
val callback = object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errCode, errString)
|
||||||
|
Logger.log("$TAG errCode is $errCode and errString is: $errString")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Logger.log("$TAG User biometric rejected.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.d(TAG, "Authentication was successful")
|
||||||
|
processSuccess(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BiometricPrompt(activity, executor, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a BiometricPrompt.PromptInfo instance
|
||||||
|
* @param activity: AppCompatActivity
|
||||||
|
* @return BiometricPrompt.PromptInfo: instance
|
||||||
|
*/
|
||||||
|
fun createPromptInfo(activity: AppCompatActivity): BiometricPrompt.PromptInfo =
|
||||||
|
BiometricPrompt.PromptInfo.Builder().apply {
|
||||||
|
setTitle(activity.getString(R.string.bio_prompt_info_title))
|
||||||
|
setDescription(activity.getString(R.string.bio_prompt_info_desc))
|
||||||
|
setConfirmationRequired(false)
|
||||||
|
setNegativeButtonText(activity.getString(R.string.cancel))
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package ani.dantotsu.others.calc
|
package ani.dantotsu.others.calc
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -16,6 +20,8 @@ import ani.dantotsu.databinding.ActivityCalcBinding
|
|||||||
import ani.dantotsu.getThemeColor
|
import ani.dantotsu.getThemeColor
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.util.NumberConverter.Companion.toBinary
|
import ani.dantotsu.util.NumberConverter.Companion.toBinary
|
||||||
@@ -24,7 +30,13 @@ import ani.dantotsu.util.NumberConverter.Companion.toHex
|
|||||||
class CalcActivity : AppCompatActivity() {
|
class CalcActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityCalcBinding
|
private lateinit var binding: ActivityCalcBinding
|
||||||
private lateinit var code: String
|
private lateinit var code: String
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val runnable = Runnable {
|
||||||
|
success()
|
||||||
|
}
|
||||||
private val stack = CalcStack()
|
private val stack = CalcStack()
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
@@ -73,6 +85,29 @@ class CalcActivity : AppCompatActivity() {
|
|||||||
binding.displayHex.text = ""
|
binding.displayHex.text = ""
|
||||||
binding.display.text = "0"
|
binding.display.text = "0"
|
||||||
}
|
}
|
||||||
|
if (PrefManager.getVal(PrefName.OverridePassword, false)) {
|
||||||
|
buttonClear.setOnTouchListener { v, event ->
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
handler.postDelayed(runnable, 10000)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
v.performClick()
|
||||||
|
handler.removeCallbacks(runnable)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_CANCEL -> {
|
||||||
|
handler.removeCallbacks(runnable)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
buttonBackspace.setOnClickListener {
|
buttonBackspace.setOnClickListener {
|
||||||
stack.remove()
|
stack.remove()
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
@@ -81,6 +116,20 @@ class CalcActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (hasPermission) {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
if (PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()) {
|
||||||
|
val bioMetricPrompt = BiometricPromptUtils.createBiometricPrompt(this) {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
|
||||||
|
bioMetricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun success() {
|
private fun success() {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ object AnimeSources : WatchSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun performReorderAnimeSources() {
|
fun performReorderAnimeSources() {
|
||||||
//remove the downloaded source from the list to avoid duplicates
|
// Remove the downloaded source from the list to avoid duplicates
|
||||||
list = list.filter { it.name != "Downloaded" }
|
list = list.filter { it.name != "Downloaded" }
|
||||||
list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier(
|
list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier(
|
||||||
{ OfflineAnimeParser() },
|
{ OfflineAnimeParser() },
|
||||||
|
|||||||
@@ -348,9 +348,6 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
val res = source.getChapterList(sManga)
|
val res = source.getChapterList(sManga)
|
||||||
val reversedRes = res.reversed()
|
val reversedRes = res.reversed()
|
||||||
val chapterList = reversedRes.map { sChapterToMangaChapter(it) }
|
val chapterList = reversedRes.map { sChapterToMangaChapter(it) }
|
||||||
Logger.log("chapterList size: ${chapterList.size}")
|
|
||||||
Logger.log("chapterList: ${chapterList[1].title}")
|
|
||||||
Logger.log("chapterList: ${chapterList[1].description}")
|
|
||||||
chapterList
|
chapterList
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("loadChapters Exception: $e")
|
Logger.log("loadChapters Exception: $e")
|
||||||
|
|||||||
@@ -55,13 +55,11 @@ class OfflineAnimeParser : AnimeParser() {
|
|||||||
episodes.add(episode)
|
episodes.add(episode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//episodes.sortBy { MediaNameAdapter.findEpisodeNumber(it.number) }
|
|
||||||
episodes.addAll(loadEpisodesCompat(animeLink, extra, sAnime))
|
|
||||||
//filter those with the same name
|
|
||||||
return episodes.distinctBy { it.number }
|
|
||||||
.sortedBy { MediaNameAdapter.findEpisodeNumber(it.number) }
|
|
||||||
}
|
}
|
||||||
return emptyList()
|
episodes.addAll(loadEpisodesCompat(animeLink, extra, sAnime))
|
||||||
|
//filter those with the same name
|
||||||
|
return episodes.distinctBy { it.number }
|
||||||
|
.sortedBy { MediaNameAdapter.findEpisodeNumber(it.number) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadVideoServers(
|
override suspend fun loadVideoServers(
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ class OfflineMangaParser : MangaParser() {
|
|||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chapters.addAll(loadChaptersCompat(mangaLink, extra, sManga))
|
|
||||||
return chapters.distinctBy { it.number }
|
|
||||||
.sortedBy { MediaNameAdapter.findChapterNumber(it.number) }
|
|
||||||
}
|
}
|
||||||
return emptyList()
|
chapters.addAll(loadChaptersCompat(mangaLink, extra, sManga))
|
||||||
|
return chapters.distinctBy { it.number }
|
||||||
|
.sortedBy { MediaNameAdapter.findChapterNumber(it.number) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
||||||
@@ -66,17 +65,16 @@ class OfflineMangaParser : MangaParser() {
|
|||||||
for (image in images) {
|
for (image in images) {
|
||||||
Logger.log("imageNumber: ${image.url.url}")
|
Logger.log("imageNumber: ${image.url.url}")
|
||||||
}
|
}
|
||||||
return if (images.isNotEmpty()) {
|
|
||||||
images.sortBy { image ->
|
|
||||||
val matchResult = imageNumberRegex.find(image.url.url)
|
|
||||||
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
images
|
|
||||||
} else {
|
|
||||||
loadImagesCompat(chapterLink, sChapter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return emptyList()
|
return if (images.isNotEmpty()) {
|
||||||
|
images.sortBy { image ->
|
||||||
|
val matchResult = imageNumberRegex.find(image.url.url)
|
||||||
|
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
images
|
||||||
|
} else {
|
||||||
|
loadImagesCompat(chapterLink, sChapter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<ShowResponse> {
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class NovelExtensionManager(private val context: Context) {
|
|||||||
*
|
*
|
||||||
* @param pkgName The package name of the application to uninstall.
|
* @param pkgName The package name of the application to uninstall.
|
||||||
*/
|
*/
|
||||||
fun uninstallExtension(pkgName: String, context: Context) {
|
fun uninstallExtension(pkgName: String) {
|
||||||
installer.uninstallApk(pkgName)
|
installer.uninstallApk(pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.blurImage
|
import ani.dantotsu.blurImage
|
||||||
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.copyToClipboard
|
||||||
import ani.dantotsu.databinding.ActivityProfileBinding
|
import ani.dantotsu.databinding.ActivityProfileBinding
|
||||||
import ani.dantotsu.databinding.ItemProfileAppBarBinding
|
import ani.dantotsu.databinding.ItemProfileAppBarBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
@@ -30,15 +31,14 @@ import ani.dantotsu.media.user.ListActivity
|
|||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.openImage
|
import ani.dantotsu.openImage
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.profile.activity.ActivityFragment
|
||||||
import ani.dantotsu.profile.activity.FeedFragment
|
import ani.dantotsu.profile.activity.ActivityFragment.Companion.ActivityType
|
||||||
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.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.util.MarkdownCreatorActivity
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -136,7 +136,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
|
|
||||||
followButton.setOnClickListener {
|
followButton.setOnClickListener {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val res = Anilist.query.toggleFollow(user.id)
|
val res = Anilist.mutation.toggleFollow(user.id)
|
||||||
if (res?.data?.toggleFollow != null) {
|
if (res?.data?.toggleFollow != null) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
snackString(R.string.success)
|
snackString(R.string.success)
|
||||||
@@ -153,16 +153,18 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
popup.setOnMenuItemClickListener { item ->
|
popup.setOnMenuItemClickListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_view_on_anilist -> {
|
R.id.action_view_on_anilist -> {
|
||||||
openLinkInBrowser("https://anilist.co/user/${user.name}")
|
openLinkInBrowser(getString(R.string.anilist_link, user.name))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.action_create_new_activity -> {
|
R.id.action_share_profile -> {
|
||||||
ContextCompat.startActivity(
|
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
context,
|
shareIntent.type = "text/plain"
|
||||||
Intent(context, MarkdownCreatorActivity::class.java)
|
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.anilist_link, user.name))
|
||||||
.putExtra("type", "activity"),
|
startActivity(Intent.createChooser(shareIntent, "Share Profile"))
|
||||||
null
|
true
|
||||||
)
|
}
|
||||||
|
R.id.action_copy_user_id -> {
|
||||||
|
copyToClipboard(user.id.toString(), true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
@@ -177,7 +179,11 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
user.avatar?.medium ?: ""
|
user.avatar?.medium ?: ""
|
||||||
)
|
)
|
||||||
profileUserName.text = user.name
|
profileUserName.text = user.name
|
||||||
val bannerAnimations: ImageView= if (PrefManager.getVal(PrefName.BannerAnimations)) profileBannerImage else profileBannerImageNoKen
|
profileUserName.setOnClickListener {
|
||||||
|
copyToClipboard(profileUserName.text.toString(), true)
|
||||||
|
}
|
||||||
|
val bannerAnimations: ImageView =
|
||||||
|
if (PrefManager.getVal(PrefName.BannerAnimations)) profileBannerImage else profileBannerImageNoKen
|
||||||
|
|
||||||
blurImage(
|
blurImage(
|
||||||
bannerAnimations,
|
bannerAnimations,
|
||||||
@@ -199,7 +205,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
profileAppBar.addOnOffsetChangedListener(context)
|
profileAppBar.addOnOffsetChangedListener(context)
|
||||||
|
|
||||||
|
|
||||||
profileFollowerCount.text = (respond.data.followerPage?.pageInfo?.total ?: 0).toString()
|
profileFollowerCount.text =
|
||||||
|
(respond.data.followerPage?.pageInfo?.total ?: 0).toString()
|
||||||
profileFollowerCountContainer.setOnClickListener {
|
profileFollowerCountContainer.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
context,
|
context,
|
||||||
@@ -209,7 +216,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
profileFollowingCount.text = (respond.data.followingPage?.pageInfo?.total ?: 0).toString()
|
profileFollowingCount.text =
|
||||||
|
(respond.data.followingPage?.pageInfo?.total ?: 0).toString()
|
||||||
profileFollowingCountContainer.setOnClickListener {
|
profileFollowingCountContainer.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
context,
|
context,
|
||||||
@@ -320,7 +328,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||||||
override fun getItemCount(): Int = 3
|
override fun getItemCount(): Int = 3
|
||||||
override fun createFragment(position: Int): Fragment = when (position) {
|
override fun createFragment(position: Int): Fragment = when (position) {
|
||||||
0 -> ProfileFragment.newInstance(user)
|
0 -> ProfileFragment.newInstance(user)
|
||||||
1 -> FeedFragment.newInstance(user.id, false, -1)
|
1 -> ActivityFragment.newInstance(ActivityType.OTHER_USER, user.id)
|
||||||
2 -> StatsFragment.newInstance(user)
|
2 -> StatsFragment.newInstance(user)
|
||||||
else -> ProfileFragment.newInstance(user)
|
else -> ProfileFragment.newInstance(user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,14 +252,14 @@ class StatsFragment :
|
|||||||
stat?.statistics?.anime?.scores?.map {
|
stat?.statistics?.anime?.scores?.map {
|
||||||
convertScore(
|
convertScore(
|
||||||
it.score,
|
it.score,
|
||||||
stat.mediaListOptions.scoreFormat
|
stat.mediaListOptions.scoreFormat.toString()
|
||||||
)
|
)
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
} else {
|
} else {
|
||||||
stat?.statistics?.manga?.scores?.map {
|
stat?.statistics?.manga?.scores?.map {
|
||||||
convertScore(
|
convertScore(
|
||||||
it.score,
|
it.score,
|
||||||
stat.mediaListOptions.scoreFormat
|
stat.mediaListOptions.scoreFormat.toString()
|
||||||
)
|
)
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package ani.dantotsu.profile.activity
|
||||||
|
|
||||||
|
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 androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.api.Activity
|
||||||
|
import ani.dantotsu.databinding.FragmentFeedBinding
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
|
import ani.dantotsu.util.ActivityMarkdownCreator
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ActivityFragment : Fragment() {
|
||||||
|
private lateinit var type: ActivityType
|
||||||
|
private var userId: Int? = null
|
||||||
|
private var activityId: Int? = null
|
||||||
|
private lateinit var binding: FragmentFeedBinding
|
||||||
|
private var adapter: GroupieAdapter = GroupieAdapter()
|
||||||
|
private var page: Int = 1
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentFeedBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
arguments?.let {
|
||||||
|
type = it.getSerializableCompat<ActivityType>("type") as ActivityType
|
||||||
|
userId = it.getInt("userId")
|
||||||
|
activityId = it.getInt("activityId")
|
||||||
|
}
|
||||||
|
binding.titleBar.visibility =
|
||||||
|
if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE
|
||||||
|
binding.titleText.text =
|
||||||
|
if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message)
|
||||||
|
binding.titleImage.setOnClickListener { handleTitleImageClick() }
|
||||||
|
binding.listRecyclerView.adapter = adapter
|
||||||
|
binding.listRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
|
binding.listProgressBar.isVisible = true
|
||||||
|
|
||||||
|
binding.feedRefresh.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
binding.emptyTextView.text = getString(R.string.nothing_here)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
getList()
|
||||||
|
if (adapter.itemCount == 0) {
|
||||||
|
binding.emptyTextView.isVisible = true
|
||||||
|
}
|
||||||
|
binding.listProgressBar.isVisible = false
|
||||||
|
}
|
||||||
|
binding.feedSwipeRefresh.setOnRefreshListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
adapter.clear()
|
||||||
|
page = 1
|
||||||
|
getList()
|
||||||
|
binding.feedSwipeRefresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.listRecyclerView.addOnScrollListener(object :
|
||||||
|
RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
if (shouldLoadMore()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
binding.feedRefresh.isVisible = true
|
||||||
|
getList()
|
||||||
|
binding.feedRefresh.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTitleImageClick() {
|
||||||
|
val intent = Intent(context, ActivityMarkdownCreator::class.java).apply {
|
||||||
|
putExtra("type", if (userId == Anilist.userid) "activity" else "message")
|
||||||
|
putExtra("userId", userId)
|
||||||
|
}
|
||||||
|
ContextCompat.startActivity(requireContext(), intent, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getList() {
|
||||||
|
val list = when (type) {
|
||||||
|
ActivityType.GLOBAL -> getActivities(global = true)
|
||||||
|
ActivityType.USER -> getActivities(filter = true)
|
||||||
|
ActivityType.OTHER_USER -> getActivities(userId = userId)
|
||||||
|
ActivityType.ONE -> getActivities(activityId = activityId)
|
||||||
|
}
|
||||||
|
adapter.addAll(list.map { ActivityItem(it, adapter, ::onActivityClick) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getActivities(
|
||||||
|
global: Boolean = false,
|
||||||
|
userId: Int? = null,
|
||||||
|
activityId: Int? = null,
|
||||||
|
filter: Boolean = false
|
||||||
|
): List<Activity> {
|
||||||
|
val res = Anilist.query.getFeed(userId, global, page, activityId)?.data?.page?.activities
|
||||||
|
page += 1
|
||||||
|
return res
|
||||||
|
?.filter { if (Anilist.adult) true else it.media?.isAdult != true }
|
||||||
|
?.filterNot { it.recipient?.id != null && it.recipient.id != Anilist.userid && filter }
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldLoadMore(): Boolean {
|
||||||
|
val layoutManager =
|
||||||
|
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
||||||
|
val adapter = binding.listRecyclerView.adapter
|
||||||
|
return !binding.listRecyclerView.canScrollVertically(1) &&
|
||||||
|
!binding.feedRefresh.isVisible && adapter?.itemCount != 0 &&
|
||||||
|
layoutManager == (adapter!!.itemCount - 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onActivityClick(id: Int, type: String) {
|
||||||
|
val intent = when (type) {
|
||||||
|
"USER" -> Intent(requireContext(), ProfileActivity::class.java).putExtra("userId", id)
|
||||||
|
"MEDIA" -> Intent(
|
||||||
|
requireContext(),
|
||||||
|
MediaDetailsActivity::class.java
|
||||||
|
).putExtra("mediaId", id)
|
||||||
|
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
ContextCompat.startActivity(requireContext(), intent, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (this::binding.isInitialized) {
|
||||||
|
binding.root.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
enum class ActivityType { GLOBAL, USER, OTHER_USER, ONE }
|
||||||
|
|
||||||
|
fun newInstance(
|
||||||
|
type: ActivityType,
|
||||||
|
userId: Int? = null,
|
||||||
|
activityId: Int? = null
|
||||||
|
): ActivityFragment {
|
||||||
|
return ActivityFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putSerializable("type", type)
|
||||||
|
userId?.let { putInt("userId", it) }
|
||||||
|
activityId?.let { putInt("activityId", it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import android.view.View
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.blurImage
|
import ani.dantotsu.blurImage
|
||||||
import ani.dantotsu.buildMarkwon
|
import ani.dantotsu.buildMarkwon
|
||||||
@@ -18,7 +17,7 @@ import ani.dantotsu.profile.UsersDialogFragment
|
|||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
|
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
|
||||||
import ani.dantotsu.util.MarkdownCreatorActivity
|
import ani.dantotsu.util.ActivityMarkdownCreator
|
||||||
import com.xwray.groupie.GroupieAdapter
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.viewbinding.BindableItem
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -29,23 +28,16 @@ import kotlinx.coroutines.withContext
|
|||||||
|
|
||||||
class ActivityItem(
|
class ActivityItem(
|
||||||
private val activity: Activity,
|
private val activity: Activity,
|
||||||
|
private val parentAdapter: GroupieAdapter,
|
||||||
val clickCallback: (Int, type: String) -> Unit,
|
val clickCallback: (Int, type: String) -> Unit,
|
||||||
private val fragActivity: FragmentActivity
|
|
||||||
) : BindableItem<ItemActivityBinding>() {
|
) : BindableItem<ItemActivityBinding>() {
|
||||||
private lateinit var binding: ItemActivityBinding
|
private lateinit var binding: ItemActivityBinding
|
||||||
private lateinit var repliesAdapter: GroupieAdapter
|
|
||||||
|
|
||||||
override fun bind(viewBinding: ItemActivityBinding, position: Int) {
|
override fun bind(viewBinding: ItemActivityBinding, position: Int) {
|
||||||
binding = viewBinding
|
binding = viewBinding
|
||||||
|
val context = binding.root.context
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
setAnimation(binding.root.context, binding.root)
|
setAnimation(binding.root.context, binding.root)
|
||||||
|
|
||||||
repliesAdapter = GroupieAdapter()
|
|
||||||
binding.activityReplies.adapter = repliesAdapter
|
|
||||||
binding.activityReplies.layoutManager = LinearLayoutManager(
|
|
||||||
binding.root.context,
|
|
||||||
LinearLayoutManager.VERTICAL,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
binding.activityUserName.text = activity.user?.name ?: activity.messenger?.name
|
binding.activityUserName.text = activity.user?.name ?: activity.messenger?.name
|
||||||
binding.activityUserAvatar.loadImage(
|
binding.activityUserAvatar.loadImage(
|
||||||
activity.user?.avatar?.medium ?: activity.messenger?.avatar?.medium
|
activity.user?.avatar?.medium ?: activity.messenger?.avatar?.medium
|
||||||
@@ -54,66 +46,29 @@ class ActivityItem(
|
|||||||
val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red)
|
val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red)
|
||||||
val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp)
|
val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp)
|
||||||
binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor)
|
binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor)
|
||||||
binding.commentTotalReplies.isVisible = activity.replyCount > 0
|
|
||||||
binding.dot.isVisible = activity.replyCount > 0
|
|
||||||
binding.commentTotalReplies.setOnClickListener {
|
|
||||||
when (binding.activityReplies.visibility) {
|
|
||||||
View.GONE -> {
|
|
||||||
val replyItems = activity.replies?.map {
|
|
||||||
ActivityReplyItem(it,fragActivity) { id, type ->
|
|
||||||
clickCallback(
|
|
||||||
id,
|
|
||||||
type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: emptyList()
|
|
||||||
repliesAdapter.addAll(replyItems)
|
|
||||||
binding.activityReplies.visibility = View.VISIBLE
|
|
||||||
binding.commentTotalReplies.setText(R.string.hide_replies)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
repliesAdapter.clear()
|
|
||||||
binding.activityReplies.visibility = View.GONE
|
|
||||||
binding.commentTotalReplies.setText(R.string.view_replies)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (activity.isLocked != true) {
|
|
||||||
binding.commentReply.setOnClickListener {
|
|
||||||
val context = binding.root.context
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
context,
|
|
||||||
Intent(context, MarkdownCreatorActivity::class.java)
|
|
||||||
.putExtra("type", "replyActivity")
|
|
||||||
.putExtra("parentId", activity.id),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.commentReply.visibility = View.GONE
|
|
||||||
binding.dot.visibility = View.GONE
|
|
||||||
}
|
|
||||||
val userList = arrayListOf<User>()
|
val userList = arrayListOf<User>()
|
||||||
activity.likes?.forEach { i ->
|
activity.likes?.forEach { i ->
|
||||||
userList.add(User(i.id, i.name.toString(), i.avatar?.medium, i.bannerImage))
|
userList.add(User(i.id, i.name.toString(), i.avatar?.medium, i.bannerImage))
|
||||||
}
|
}
|
||||||
|
binding.activityRepliesContainer.setOnClickListener {
|
||||||
|
RepliesBottomDialog.newInstance(activity.id)
|
||||||
|
.show((context as FragmentActivity).supportFragmentManager, "replies")
|
||||||
|
}
|
||||||
|
binding.replyCount.text = activity.replyCount.toString()
|
||||||
|
binding.activityReplies.setColorFilter(ContextCompat.getColor(binding.root.context, R.color.bg_opp))
|
||||||
binding.activityLikeContainer.setOnLongClickListener {
|
binding.activityLikeContainer.setOnLongClickListener {
|
||||||
UsersDialogFragment().apply {
|
UsersDialogFragment().apply {
|
||||||
userList(userList)
|
userList(userList)
|
||||||
show(fragActivity.supportFragmentManager, "dialog")
|
show((context as FragmentActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
binding.activityLikeCount.text = (activity.likeCount ?: 0).toString()
|
binding.activityLikeCount.text = (activity.likeCount ?: 0).toString()
|
||||||
binding.activityLikeContainer.setOnClickListener {
|
binding.activityLikeContainer.setOnClickListener {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val res = Anilist.query.toggleLike(activity.id, "ACTIVITY")
|
val res = Anilist.mutation.toggleLike(activity.id, "ACTIVITY")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
|
|
||||||
if (activity.isLiked == true) {
|
if (activity.isLiked == true) {
|
||||||
activity.likeCount = activity.likeCount?.minus(1)
|
activity.likeCount = activity.likeCount?.minus(1)
|
||||||
} else {
|
} else {
|
||||||
@@ -129,13 +84,27 @@ class ActivityItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val context = binding.root.context
|
binding.activityDelete.isVisible = activity.userId == Anilist.userid || activity.messenger?.id == Anilist.userid
|
||||||
|
binding.activityDelete.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
val res = Anilist.mutation.deleteActivity(activity.id)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (res) {
|
||||||
|
snackString("Deleted activity")
|
||||||
|
parentAdapter.remove(this@ActivityItem)
|
||||||
|
} else {
|
||||||
|
snackString("Failed to delete activity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
when (activity.typename) {
|
when (activity.typename) {
|
||||||
"ListActivity" -> {
|
"ListActivity" -> {
|
||||||
val cover = activity.media?.coverImage?.large
|
val cover = activity.media?.coverImage?.large
|
||||||
val banner = activity.media?.bannerImage
|
val banner = activity.media?.bannerImage
|
||||||
binding.activityContent.visibility = View.GONE
|
binding.activityContent.visibility = View.GONE
|
||||||
binding.activityBannerContainer.visibility = View.VISIBLE
|
binding.activityBannerContainer.visibility = View.VISIBLE
|
||||||
|
binding.activityPrivate.visibility = View.GONE
|
||||||
binding.activityMediaName.text = activity.media?.title?.userPreferred
|
binding.activityMediaName.text = activity.media?.title?.userPreferred
|
||||||
val activityText = "${activity.user!!.name} ${activity.status} ${
|
val activityText = "${activity.user!!.name} ${activity.status} ${
|
||||||
activity.progress
|
activity.progress
|
||||||
@@ -156,11 +125,13 @@ class ActivityItem(
|
|||||||
binding.activityMediaName.setOnClickListener {
|
binding.activityMediaName.setOnClickListener {
|
||||||
clickCallback(activity.media?.id ?: -1, "MEDIA")
|
clickCallback(activity.media?.id ?: -1, "MEDIA")
|
||||||
}
|
}
|
||||||
|
binding.activityEdit.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
"TextActivity" -> {
|
"TextActivity" -> {
|
||||||
binding.activityBannerContainer.visibility = View.GONE
|
binding.activityBannerContainer.visibility = View.GONE
|
||||||
binding.activityContent.visibility = View.VISIBLE
|
binding.activityContent.visibility = View.VISIBLE
|
||||||
|
binding.activityPrivate.visibility = View.GONE
|
||||||
if (!(context as android.app.Activity).isDestroyed) {
|
if (!(context as android.app.Activity).isDestroyed) {
|
||||||
val markwon = buildMarkwon(context, false)
|
val markwon = buildMarkwon(context, false)
|
||||||
markwon.setMarkdown(
|
markwon.setMarkdown(
|
||||||
@@ -174,11 +145,23 @@ class ActivityItem(
|
|||||||
binding.activityUserName.setOnClickListener {
|
binding.activityUserName.setOnClickListener {
|
||||||
clickCallback(activity.userId ?: -1, "USER")
|
clickCallback(activity.userId ?: -1, "USER")
|
||||||
}
|
}
|
||||||
|
binding.activityEdit.isVisible = activity.userId == Anilist.userid
|
||||||
|
binding.activityEdit.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, ActivityMarkdownCreator::class.java)
|
||||||
|
.putExtra("type", "activity")
|
||||||
|
.putExtra("other", activity.text)
|
||||||
|
.putExtra("edit", activity.id),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"MessageActivity" -> {
|
"MessageActivity" -> {
|
||||||
binding.activityBannerContainer.visibility = View.GONE
|
binding.activityBannerContainer.visibility = View.GONE
|
||||||
binding.activityContent.visibility = View.VISIBLE
|
binding.activityContent.visibility = View.VISIBLE
|
||||||
|
binding.activityPrivate.visibility = if (activity.isPrivate == true) View.VISIBLE else View.GONE
|
||||||
if (!(context as android.app.Activity).isDestroyed) {
|
if (!(context as android.app.Activity).isDestroyed) {
|
||||||
val markwon = buildMarkwon(context, false)
|
val markwon = buildMarkwon(context, false)
|
||||||
markwon.setMarkdown(
|
markwon.setMarkdown(
|
||||||
@@ -192,6 +175,19 @@ class ActivityItem(
|
|||||||
binding.activityUserName.setOnClickListener {
|
binding.activityUserName.setOnClickListener {
|
||||||
clickCallback(activity.messengerId ?: -1, "USER")
|
clickCallback(activity.messengerId ?: -1, "USER")
|
||||||
}
|
}
|
||||||
|
binding.activityEdit.isVisible = false
|
||||||
|
binding.activityEdit.isVisible = activity.messenger?.id == Anilist.userid
|
||||||
|
binding.activityEdit.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, ActivityMarkdownCreator::class.java)
|
||||||
|
.putExtra("type", "message")
|
||||||
|
.putExtra("other", activity.message)
|
||||||
|
.putExtra("edit", activity.id)
|
||||||
|
.putExtra("userId", activity.recipientId),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package ani.dantotsu.profile.activity
|
package ani.dantotsu.profile.activity
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.buildMarkwon
|
import ani.dantotsu.buildMarkwon
|
||||||
@@ -13,6 +15,8 @@ import ani.dantotsu.profile.User
|
|||||||
import ani.dantotsu.profile.UsersDialogFragment
|
import ani.dantotsu.profile.UsersDialogFragment
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
|
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
|
||||||
|
import ani.dantotsu.util.ActivityMarkdownCreator
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.viewbinding.BindableItem
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -22,23 +26,27 @@ import kotlinx.coroutines.withContext
|
|||||||
|
|
||||||
class ActivityReplyItem(
|
class ActivityReplyItem(
|
||||||
private val reply: ActivityReply,
|
private val reply: ActivityReply,
|
||||||
|
private val parentId : Int,
|
||||||
private val fragActivity: FragmentActivity,
|
private val fragActivity: FragmentActivity,
|
||||||
|
private val parentAdapter: GroupieAdapter,
|
||||||
private val clickCallback: (Int, type: String) -> Unit,
|
private val clickCallback: (Int, type: String) -> Unit,
|
||||||
) : BindableItem<ItemActivityReplyBinding>() {
|
) : BindableItem<ItemActivityReplyBinding>() {
|
||||||
private lateinit var binding: ItemActivityReplyBinding
|
private lateinit var binding: ItemActivityReplyBinding
|
||||||
|
|
||||||
override fun bind(viewBinding: ItemActivityReplyBinding, position: Int) {
|
override fun bind(viewBinding: ItemActivityReplyBinding, position: Int) {
|
||||||
binding = viewBinding
|
binding = viewBinding
|
||||||
|
val context = binding.root.context
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
binding.activityUserAvatar.loadImage(reply.user.avatar?.medium)
|
binding.activityUserAvatar.loadImage(reply.user.avatar?.medium)
|
||||||
binding.activityUserName.text = reply.user.name
|
binding.activityUserName.text = reply.user.name
|
||||||
binding.activityTime.text = ActivityItemBuilder.getDateTime(reply.createdAt)
|
binding.activityTime.text = ActivityItemBuilder.getDateTime(reply.createdAt)
|
||||||
binding.activityLikeCount.text = reply.likeCount.toString()
|
binding.activityLikeCount.text = reply.likeCount.toString()
|
||||||
val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red)
|
val likeColor = ContextCompat.getColor(context, R.color.yt_red)
|
||||||
val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp)
|
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
|
||||||
binding.activityLike.setColorFilter(if (reply.isLiked) likeColor else notLikeColor)
|
binding.activityLike.setColorFilter(if (reply.isLiked) likeColor else notLikeColor)
|
||||||
val markwon = buildMarkwon(binding.root.context)
|
val markwon = buildMarkwon(context)
|
||||||
markwon.setMarkdown(binding.activityContent, getBasicAniHTML(reply.text))
|
markwon.setMarkdown(binding.activityContent, getBasicAniHTML(reply.text))
|
||||||
|
|
||||||
val userList = arrayListOf<User>()
|
val userList = arrayListOf<User>()
|
||||||
reply.likes?.forEach { i ->
|
reply.likes?.forEach { i ->
|
||||||
userList.add(User(i.id, i.name.toString(), i.avatar?.medium, i.bannerImage))
|
userList.add(User(i.id, i.name.toString(), i.avatar?.medium, i.bannerImage))
|
||||||
@@ -51,9 +59,8 @@ class ActivityReplyItem(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
binding.activityLikeContainer.setOnClickListener {
|
binding.activityLikeContainer.setOnClickListener {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val res = Anilist.query.toggleLike(reply.id, "ACTIVITY_REPLY")
|
val res = Anilist.mutation.toggleLike(reply.id, "ACTIVITY_REPLY")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
if (reply.isLiked) {
|
if (reply.isLiked) {
|
||||||
@@ -71,6 +78,42 @@ class ActivityReplyItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.activityReply.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, ActivityMarkdownCreator::class.java)
|
||||||
|
.putExtra("type", "replyActivity")
|
||||||
|
.putExtra("parentId", parentId)
|
||||||
|
.putExtra("other", "@${reply.user.name} "),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.activityEdit.isVisible = reply.userId == Anilist.userid
|
||||||
|
binding.activityEdit.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, ActivityMarkdownCreator::class.java)
|
||||||
|
.putExtra("type", "replyActivity")
|
||||||
|
.putExtra("parentId", parentId)
|
||||||
|
.putExtra("other", reply.text)
|
||||||
|
.putExtra("edit", reply.id),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.activityDelete.isVisible = reply.userId == Anilist.userid
|
||||||
|
binding.activityDelete.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
val res = Anilist.mutation.deleteActivityReply(reply.id)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (res) {
|
||||||
|
snackString("Deleted")
|
||||||
|
parentAdapter.remove(this@ActivityReplyItem)
|
||||||
|
} else {
|
||||||
|
snackString("Failed to delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.activityAvatarContainer.setOnClickListener {
|
binding.activityAvatarContainer.setOnClickListener {
|
||||||
clickCallback(reply.userId, "USER")
|
clickCallback(reply.userId, "USER")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.profile.activity
|
|||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
@@ -10,16 +11,20 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.ActivityFeedBinding
|
import ani.dantotsu.databinding.ActivityFeedBinding
|
||||||
|
import ani.dantotsu.databinding.ActivityNotificationBinding
|
||||||
import ani.dantotsu.initActivity
|
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.profile.activity.ActivityFragment.Companion.ActivityType
|
||||||
|
import ani.dantotsu.profile.notification.NotificationActivity
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
class FeedActivity : AppCompatActivity() {
|
class FeedActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityFeedBinding
|
private lateinit var binding: ActivityNotificationBinding
|
||||||
private var selected: Int = 0
|
private var selected: Int = 0
|
||||||
lateinit var navBar: AnimatedBottomBar
|
lateinit var navBar: AnimatedBottomBar
|
||||||
|
|
||||||
@@ -27,28 +32,29 @@ class FeedActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
binding = ActivityFeedBinding.inflate(layoutInflater)
|
binding = ActivityNotificationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
navBar = binding.feedNavBar
|
binding.notificationTitle.text = getString(R.string.activities)
|
||||||
val navBarMargin = if (resources.configuration.orientation ==
|
binding.notificationToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
Configuration.ORIENTATION_LANDSCAPE
|
topMargin = statusBarHeight
|
||||||
) 0 else navBarHeight
|
|
||||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarMargin }
|
|
||||||
val personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Following")
|
|
||||||
val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global")
|
|
||||||
navBar.addTab(personalTab)
|
|
||||||
navBar.addTab(globalTab)
|
|
||||||
binding.listTitle.text = getString(R.string.activities)
|
|
||||||
binding.feedViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
bottomMargin = navBarMargin
|
|
||||||
topMargin += statusBarHeight
|
|
||||||
}
|
}
|
||||||
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
navBar = binding.notificationNavBar
|
||||||
val activityId = intent.getIntExtra("activityId", -1)
|
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
binding.feedViewPager.adapter =
|
bottomMargin = navBarHeight
|
||||||
ViewPagerAdapter(supportFragmentManager, lifecycle, activityId)
|
}
|
||||||
binding.feedViewPager.setCurrentItem(selected, false)
|
val tabs = listOf(
|
||||||
binding.feedViewPager.isUserInputEnabled = false
|
Pair(R.drawable.ic_round_person_24, "Following"),
|
||||||
|
Pair(R.drawable.ic_globe_24, "Global"),
|
||||||
|
)
|
||||||
|
tabs.forEach { (icon, title) -> navBar.addTab(navBar.createTab(icon, title)) }
|
||||||
|
|
||||||
|
binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
||||||
|
val getOne = intent.getIntExtra("activityId", -1)
|
||||||
|
if (getOne != -1) { navBar.visibility = View.GONE }
|
||||||
|
binding.notificationViewPager.isUserInputEnabled = false
|
||||||
|
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne)
|
||||||
|
binding.notificationViewPager.setOffscreenPageLimit(4)
|
||||||
|
binding.notificationViewPager.setCurrentItem(selected, false)
|
||||||
navBar.selectTabAt(selected)
|
navBar.selectTabAt(selected)
|
||||||
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||||
override fun onTabSelected(
|
override fun onTabSelected(
|
||||||
@@ -58,24 +64,9 @@ class FeedActivity : AppCompatActivity() {
|
|||||||
newTab: AnimatedBottomBar.Tab
|
newTab: AnimatedBottomBar.Tab
|
||||||
) {
|
) {
|
||||||
selected = newIndex
|
selected = newIndex
|
||||||
binding.feedViewPager.setCurrentItem(selected, true)
|
binding.notificationViewPager.setCurrentItem(selected, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
binding.listBack.setOnClickListener {
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
||||||
super.onConfigurationChanged(newConfig)
|
|
||||||
val margin =
|
|
||||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
|
||||||
val params: ViewGroup.MarginLayoutParams =
|
|
||||||
binding.feedViewPager.layoutParams as ViewGroup.MarginLayoutParams
|
|
||||||
val paramsNav: ViewGroup.MarginLayoutParams =
|
|
||||||
navBar.layoutParams as ViewGroup.MarginLayoutParams
|
|
||||||
params.updateMargins(bottom = margin)
|
|
||||||
paramsNav.updateMargins(bottom = margin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -88,12 +79,12 @@ class FeedActivity : AppCompatActivity() {
|
|||||||
lifecycle: Lifecycle,
|
lifecycle: Lifecycle,
|
||||||
private val activityId: Int
|
private val activityId: Int
|
||||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
override fun getItemCount(): Int = 2
|
override fun getItemCount(): Int = if (activityId != -1) 1 else 2
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when (position) {
|
return when (position) {
|
||||||
0 -> FeedFragment.newInstance(null, false, activityId)
|
0 -> ActivityFragment.newInstance(if (activityId != -1) ActivityType.ONE else ActivityType.USER, activityId = activityId)
|
||||||
else -> FeedFragment.newInstance(null, true, -1)
|
else -> ActivityFragment.newInstance(ActivityType.GLOBAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
package ani.dantotsu.profile.activity
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
|
||||||
import ani.dantotsu.connections.anilist.AnilistQueries
|
|
||||||
import ani.dantotsu.connections.anilist.api.Activity
|
|
||||||
import ani.dantotsu.databinding.FragmentFeedBinding
|
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
|
||||||
import ani.dantotsu.setBaseline
|
|
||||||
import ani.dantotsu.util.Logger
|
|
||||||
import com.xwray.groupie.GroupieAdapter
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class FeedFragment : Fragment() {
|
|
||||||
private lateinit var binding: FragmentFeedBinding
|
|
||||||
private var adapter: GroupieAdapter = GroupieAdapter()
|
|
||||||
private var activityList: List<Activity> = emptyList()
|
|
||||||
private lateinit var activity: androidx.activity.ComponentActivity
|
|
||||||
private var page: Int = 1
|
|
||||||
private var loadedFirstTime = false
|
|
||||||
private var userId: Int? = null
|
|
||||||
private var global: Boolean = false
|
|
||||||
private var activityId: Int = -1
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
binding = FragmentFeedBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
activity = requireActivity()
|
|
||||||
|
|
||||||
userId = arguments?.getInt("userId", -1)
|
|
||||||
activityId = arguments?.getInt("activityId", -1) ?: -1
|
|
||||||
if (userId == -1) userId = null
|
|
||||||
global = arguments?.getBoolean("global", false) ?: false
|
|
||||||
|
|
||||||
val navBar = if (userId != null) {
|
|
||||||
(activity as ProfileActivity).navBar
|
|
||||||
} else {
|
|
||||||
(activity as FeedActivity).navBar
|
|
||||||
}
|
|
||||||
binding.listRecyclerView.setBaseline(navBar)
|
|
||||||
binding.listRecyclerView.adapter = adapter
|
|
||||||
binding.listRecyclerView.layoutManager =
|
|
||||||
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
|
||||||
binding.listProgressBar.visibility = ViewGroup.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (this::binding.isInitialized) {
|
|
||||||
binding.root.requestLayout()
|
|
||||||
val navBar = if (userId != null) {
|
|
||||||
(activity as ProfileActivity).navBar
|
|
||||||
} else {
|
|
||||||
(activity as FeedActivity).navBar
|
|
||||||
}
|
|
||||||
binding.listRecyclerView.setBaseline(navBar)
|
|
||||||
if (!loadedFirstTime) {
|
|
||||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
val nulledId = if (activityId == -1) null else activityId
|
|
||||||
val res = Anilist.query.getFeed(userId, global, activityId = nulledId)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
res?.data?.page?.activities?.let { activities ->
|
|
||||||
activityList = activities
|
|
||||||
val filtered =
|
|
||||||
activityList
|
|
||||||
.filter { if (Anilist.adult) true else it.media?.isAdult == false }
|
|
||||||
.filterNot { //filter out messages that are not directed to the user
|
|
||||||
it.recipient?.id != null && it.recipient.id != Anilist.userid
|
|
||||||
}
|
|
||||||
adapter.update(filtered.map {
|
|
||||||
ActivityItem(
|
|
||||||
it,
|
|
||||||
::onActivityClick,
|
|
||||||
requireActivity()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
binding.listProgressBar.visibility = ViewGroup.GONE
|
|
||||||
val scrollView = binding.listRecyclerView
|
|
||||||
|
|
||||||
binding.listRecyclerView.setOnTouchListener { _, event ->
|
|
||||||
if (event?.action == MotionEvent.ACTION_UP) {
|
|
||||||
if (activityList.size % AnilistQueries.ITEMS_PER_PAGE != 0 && !global) {
|
|
||||||
//snackString("No more activities") fix spam?
|
|
||||||
Logger.log("No more activities")
|
|
||||||
} else if (!scrollView.canScrollVertically(1) && !binding.feedRefresh.isVisible
|
|
||||||
&& binding.listRecyclerView.adapter!!.itemCount != 0 &&
|
|
||||||
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1)
|
|
||||||
) {
|
|
||||||
page++
|
|
||||||
binding.feedRefresh.visibility = ViewGroup.VISIBLE
|
|
||||||
loadPage {
|
|
||||||
binding.feedRefresh.visibility = ViewGroup.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.feedSwipeRefresh.setOnRefreshListener {
|
|
||||||
page = 1
|
|
||||||
adapter.clear()
|
|
||||||
activityList = emptyList()
|
|
||||||
loadPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadedFirstTime = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadPage(onFinish: () -> Unit = {}) {
|
|
||||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
val newRes = Anilist.query.getFeed(userId, global, page)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
newRes?.data?.page?.activities?.let { activities ->
|
|
||||||
activityList += activities
|
|
||||||
val filtered = activities.filterNot {
|
|
||||||
it.recipient?.id != null && it.recipient.id != Anilist.userid
|
|
||||||
}
|
|
||||||
adapter.addAll(filtered.map {
|
|
||||||
ActivityItem(
|
|
||||||
it,
|
|
||||||
::onActivityClick,
|
|
||||||
requireActivity()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
binding.feedSwipeRefresh.isRefreshing = false
|
|
||||||
onFinish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onActivityClick(id: Int, type: String) {
|
|
||||||
when (type) {
|
|
||||||
"USER" -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
activity, Intent(activity, ProfileActivity::class.java)
|
|
||||||
.putExtra("userId", id), null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"MEDIA" -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
activity, Intent(activity, MediaDetailsActivity::class.java)
|
|
||||||
.putExtra("mediaId", id), null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun newInstance(userId: Int?, global: Boolean, activityId: Int): FeedFragment {
|
|
||||||
val fragment = FeedFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt("userId", userId ?: -1)
|
|
||||||
args.putBoolean("global", global)
|
|
||||||
args.putInt("activityId", activityId)
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
package ani.dantotsu.profile.activity
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import ani.dantotsu.R
|
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
|
||||||
import ani.dantotsu.connections.anilist.api.Notification
|
|
||||||
import ani.dantotsu.connections.anilist.api.NotificationType
|
|
||||||
import ani.dantotsu.connections.anilist.api.NotificationType.Companion.fromFormattedString
|
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import ani.dantotsu.databinding.ActivityFollowBinding
|
|
||||||
import ani.dantotsu.initActivity
|
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
|
||||||
import ani.dantotsu.navBarHeight
|
|
||||||
import ani.dantotsu.notifications.comment.CommentStore
|
|
||||||
import ani.dantotsu.notifications.subscription.SubscriptionStore
|
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
|
||||||
import ani.dantotsu.statusBarHeight
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.util.Logger
|
|
||||||
import com.xwray.groupie.GroupieAdapter
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class NotificationActivity : AppCompatActivity() {
|
|
||||||
private lateinit var binding: ActivityFollowBinding
|
|
||||||
private lateinit var commentStore: List<CommentStore>
|
|
||||||
private lateinit var subscriptionStore: List<SubscriptionStore>
|
|
||||||
private var adapter: GroupieAdapter = GroupieAdapter()
|
|
||||||
private var notificationList: List<Notification> = emptyList()
|
|
||||||
val filters = ArrayList<String>()
|
|
||||||
private var currentPage: Int = 1
|
|
||||||
private var hasNextPage: Boolean = true
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
ThemeManager(this).applyTheme()
|
|
||||||
initActivity(this)
|
|
||||||
binding = ActivityFollowBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
binding.listTitle.text = getString(R.string.notifications)
|
|
||||||
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
topMargin = statusBarHeight
|
|
||||||
}
|
|
||||||
binding.listFrameLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
bottomMargin = navBarHeight
|
|
||||||
}
|
|
||||||
binding.listRecyclerView.adapter = adapter
|
|
||||||
binding.listRecyclerView.layoutManager =
|
|
||||||
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
|
||||||
binding.followerGrid.visibility = ViewGroup.GONE
|
|
||||||
binding.followerList.visibility = ViewGroup.GONE
|
|
||||||
binding.listBack.setOnClickListener {
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
}
|
|
||||||
binding.listProgressBar.visibility = ViewGroup.VISIBLE
|
|
||||||
commentStore = PrefManager.getNullableVal<List<CommentStore>>(
|
|
||||||
PrefName.CommentNotificationStore,
|
|
||||||
null
|
|
||||||
) ?: listOf()
|
|
||||||
subscriptionStore = PrefManager.getNullableVal<List<SubscriptionStore>>(
|
|
||||||
PrefName.SubscriptionNotificationStore,
|
|
||||||
null
|
|
||||||
) ?: listOf()
|
|
||||||
|
|
||||||
binding.followFilterButton.setOnClickListener {
|
|
||||||
val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
|
||||||
val checkboxContainer = dialogView.findViewById<LinearLayout>(R.id.checkboxContainer)
|
|
||||||
val tickAllButton = dialogView.findViewById<ImageButton>(R.id.toggleButton)
|
|
||||||
val title = dialogView.findViewById<TextView>(R.id.scantitle)
|
|
||||||
title.visibility = ViewGroup.GONE
|
|
||||||
fun getToggleImageResource(container: ViewGroup): Int {
|
|
||||||
var allChecked = true
|
|
||||||
var allUnchecked = true
|
|
||||||
|
|
||||||
for (i in 0 until container.childCount) {
|
|
||||||
val checkBox = container.getChildAt(i) as CheckBox
|
|
||||||
if (!checkBox.isChecked) {
|
|
||||||
allChecked = false
|
|
||||||
} else {
|
|
||||||
allUnchecked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return when {
|
|
||||||
allChecked -> R.drawable.untick_all_boxes
|
|
||||||
allUnchecked -> R.drawable.tick_all_boxes
|
|
||||||
else -> R.drawable.invert_all_boxes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NotificationType.entries.forEach { notificationType ->
|
|
||||||
val checkBox = CheckBox(currContext())
|
|
||||||
checkBox.text = notificationType.toFormattedString()
|
|
||||||
checkBox.isChecked = !filters.contains(notificationType.value.fromFormattedString())
|
|
||||||
checkBox.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked) {
|
|
||||||
filters.remove(notificationType.value.fromFormattedString())
|
|
||||||
} else {
|
|
||||||
filters.add(notificationType.value.fromFormattedString())
|
|
||||||
}
|
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
|
||||||
}
|
|
||||||
checkboxContainer.addView(checkBox)
|
|
||||||
}
|
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
|
||||||
tickAllButton.setOnClickListener {
|
|
||||||
for (i in 0 until checkboxContainer.childCount) {
|
|
||||||
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
|
||||||
checkBox.isChecked = !checkBox.isChecked
|
|
||||||
}
|
|
||||||
|
|
||||||
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer))
|
|
||||||
}
|
|
||||||
val alertD = AlertDialog.Builder(this, R.style.MyPopup)
|
|
||||||
alertD.setTitle("Filter")
|
|
||||||
alertD.setView(dialogView)
|
|
||||||
alertD.setPositiveButton("OK") { _, _ ->
|
|
||||||
currentPage = 1
|
|
||||||
hasNextPage = true
|
|
||||||
adapter.clear()
|
|
||||||
adapter.addAll(notificationList.filter { notification ->
|
|
||||||
!filters.contains(notification.notificationType)
|
|
||||||
}.map {
|
|
||||||
NotificationItem(
|
|
||||||
it,
|
|
||||||
::onNotificationClick
|
|
||||||
)
|
|
||||||
})
|
|
||||||
loadPage(-1) {
|
|
||||||
binding.followRefresh.visibility = ViewGroup.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alertD.setNegativeButton("Cancel") { _, _ -> }
|
|
||||||
val dialog = alertD.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
}
|
|
||||||
|
|
||||||
val activityId = intent.getIntExtra("activityId", -1)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
loadPage(activityId) {
|
|
||||||
binding.listProgressBar.visibility = ViewGroup.GONE
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
binding.listProgressBar.visibility = ViewGroup.GONE
|
|
||||||
binding.listRecyclerView.setOnTouchListener { _, event ->
|
|
||||||
if (event?.action == MotionEvent.ACTION_UP) {
|
|
||||||
if (hasNextPage && !binding.listRecyclerView.canScrollVertically(1) && !binding.followRefresh.isVisible
|
|
||||||
&& binding.listRecyclerView.adapter!!.itemCount != 0 &&
|
|
||||||
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1)
|
|
||||||
) {
|
|
||||||
binding.followRefresh.visibility = ViewGroup.VISIBLE
|
|
||||||
loadPage(-1) {
|
|
||||||
binding.followRefresh.visibility = ViewGroup.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.followSwipeRefresh.setOnRefreshListener {
|
|
||||||
currentPage = 1
|
|
||||||
hasNextPage = true
|
|
||||||
adapter.clear()
|
|
||||||
notificationList = emptyList()
|
|
||||||
loadPage(-1) {
|
|
||||||
binding.followSwipeRefresh.isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadPage(activityId: Int, onFinish: () -> Unit = {}) {
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
val resetNotification = activityId == -1
|
|
||||||
val res = Anilist.query.getNotifications(
|
|
||||||
Anilist.userid ?: PrefManager.getVal<String>(PrefName.AnilistUserId).toIntOrNull()
|
|
||||||
?: 0, currentPage, resetNotification = resetNotification
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val newNotifications: MutableList<Notification> = mutableListOf()
|
|
||||||
res?.data?.page?.notifications?.let { notifications ->
|
|
||||||
Logger.log("Notifications: $notifications")
|
|
||||||
newNotifications += if (activityId != -1) {
|
|
||||||
notifications.filter { it.id == activityId }
|
|
||||||
} else {
|
|
||||||
notifications
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
if (activityId == -1) {
|
|
||||||
val furthestTime = newNotifications.minOfOrNull { it.createdAt } ?: 0
|
|
||||||
commentStore.forEach {
|
|
||||||
if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification ->
|
|
||||||
notification.commentId == it.commentId && notification.createdAt == (it.time / 1000L).toInt()
|
|
||||||
}) {
|
|
||||||
val notification = Notification(
|
|
||||||
it.type.toString(),
|
|
||||||
System.currentTimeMillis().toInt(),
|
|
||||||
commentId = it.commentId,
|
|
||||||
notificationType = it.type.toString(),
|
|
||||||
mediaId = it.mediaId,
|
|
||||||
context = it.title + "\n" + it.content,
|
|
||||||
createdAt = (it.time / 1000L).toInt(),
|
|
||||||
)
|
|
||||||
newNotifications += notification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscriptionStore.forEach {
|
|
||||||
if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification ->
|
|
||||||
notification.mediaId == it.mediaId && notification.createdAt == (it.time / 1000L).toInt()
|
|
||||||
}) {
|
|
||||||
val notification = Notification(
|
|
||||||
it.type,
|
|
||||||
System.currentTimeMillis().toInt(),
|
|
||||||
commentId = it.mediaId,
|
|
||||||
mediaId = it.mediaId,
|
|
||||||
notificationType = it.type,
|
|
||||||
context = it.title + ": " + it.content,
|
|
||||||
createdAt = (it.time / 1000L).toInt(),
|
|
||||||
image = it.image,
|
|
||||||
banner = it.banner ?: it.image
|
|
||||||
)
|
|
||||||
newNotifications += notification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newNotifications.sortByDescending { it.createdAt }
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationList += newNotifications
|
|
||||||
adapter.addAll(newNotifications.filter { notification ->
|
|
||||||
!filters.contains(notification.notificationType)
|
|
||||||
}.map {
|
|
||||||
NotificationItem(
|
|
||||||
it,
|
|
||||||
::onNotificationClick
|
|
||||||
)
|
|
||||||
})
|
|
||||||
currentPage = res?.data?.page?.pageInfo?.currentPage?.plus(1) ?: 1
|
|
||||||
hasNextPage = res?.data?.page?.pageInfo?.hasNextPage ?: false
|
|
||||||
binding.followSwipeRefresh.isRefreshing = false
|
|
||||||
onFinish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onNotificationClick(id: Int, optional: Int?, type: NotificationClickType) {
|
|
||||||
when (type) {
|
|
||||||
NotificationClickType.USER -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
this, Intent(this, ProfileActivity::class.java)
|
|
||||||
.putExtra("userId", id), null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationClickType.MEDIA -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
this, Intent(this, MediaDetailsActivity::class.java)
|
|
||||||
.putExtra("mediaId", id), null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationClickType.ACTIVITY -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
this, Intent(this, FeedActivity::class.java)
|
|
||||||
.putExtra("activityId", id), null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationClickType.COMMENT -> {
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
this, Intent(this, MediaDetailsActivity::class.java)
|
|
||||||
.putExtra("FRAGMENT_TO_LOAD", "COMMENTS")
|
|
||||||
.putExtra("mediaId", id)
|
|
||||||
.putExtra("commentId", optional ?: -1),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationClickType.UNDEFINED -> {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
enum class NotificationClickType {
|
|
||||||
USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.home.status
|
package ani.dantotsu.profile.activity
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -14,9 +14,8 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.anilist.api.ActivityReply
|
import ani.dantotsu.connections.anilist.api.ActivityReply
|
||||||
import ani.dantotsu.databinding.BottomSheetRecyclerBinding
|
import ani.dantotsu.databinding.BottomSheetRecyclerBinding
|
||||||
import ani.dantotsu.profile.ProfileActivity
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
import ani.dantotsu.profile.activity.ActivityReplyItem
|
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
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
|
||||||
@@ -49,7 +48,7 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
|
|||||||
binding.replyButton.setOnClickListener {
|
binding.replyButton.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
context,
|
context,
|
||||||
Intent(context, MarkdownCreatorActivity::class.java)
|
Intent(context, ActivityMarkdownCreator::class.java)
|
||||||
.putExtra("type", "replyActivity")
|
.putExtra("type", "replyActivity")
|
||||||
.putExtra("parentId", activityId),
|
.putExtra("parentId", activityId),
|
||||||
null
|
null
|
||||||
@@ -58,29 +57,30 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
|
|||||||
activityId = requireArguments().getInt("activityId")
|
activityId = requireArguments().getInt("activityId")
|
||||||
loading(true)
|
loading(true)
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val response = Anilist.query.getReplies(activityId)
|
loadData()
|
||||||
withContext(Dispatchers.Main) {
|
}
|
||||||
loading(false)
|
}
|
||||||
if (response != null) {
|
|
||||||
replies.clear()
|
private suspend fun loadData() {
|
||||||
replies.addAll(response.data.page.activityReplies)
|
val response = Anilist.query.getReplies(activityId)
|
||||||
adapter.update(
|
withContext(Dispatchers.Main) {
|
||||||
replies.map {
|
loading(false)
|
||||||
ActivityReplyItem(
|
if (response != null) {
|
||||||
it,
|
replies.clear()
|
||||||
requireActivity(),
|
replies.addAll(response.data.page.activityReplies)
|
||||||
clickCallback = { int, _ ->
|
adapter.update(
|
||||||
onClick(int)
|
replies.map {
|
||||||
}
|
ActivityReplyItem(
|
||||||
)
|
it, activityId, requireActivity(), adapter,
|
||||||
|
) { i, _ ->
|
||||||
|
onClick(i)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} else {
|
)
|
||||||
snackString("Failed to load replies")
|
} else {
|
||||||
}
|
snackString("Failed to load replies")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClick(int: Int) {
|
private fun onClick(int: Int) {
|
||||||
@@ -101,6 +101,14 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
loading(true)
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(activityId: Int): RepliesBottomDialog {
|
fun newInstance(activityId: Int): RepliesBottomDialog {
|
||||||
return RepliesBottomDialog().apply {
|
return RepliesBottomDialog().apply {
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package ani.dantotsu.profile.notification
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.databinding.ActivityNotificationBinding
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.*
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.newInstance
|
||||||
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
|
class NotificationActivity : AppCompatActivity() {
|
||||||
|
lateinit var binding: ActivityNotificationBinding
|
||||||
|
private var selected: Int = 0
|
||||||
|
lateinit var navBar: AnimatedBottomBar
|
||||||
|
private val CommentsEnabled = PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
initActivity(this)
|
||||||
|
binding = ActivityNotificationBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
binding.notificationTitle.text = getString(R.string.notifications)
|
||||||
|
binding.notificationToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = statusBarHeight
|
||||||
|
}
|
||||||
|
navBar = binding.notificationNavBar
|
||||||
|
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
val tabs = mutableListOf(
|
||||||
|
Pair(R.drawable.ic_round_person_24, "User"),
|
||||||
|
Pair(R.drawable.ic_round_movie_filter_24, "Media"),
|
||||||
|
Pair(R.drawable.ic_round_notifications_active_24, "Subs")
|
||||||
|
)
|
||||||
|
if (CommentsEnabled) {
|
||||||
|
tabs.add(Pair(R.drawable.ic_round_comment_24, "Comments"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.forEach { (icon, title) -> navBar.addTab(navBar.createTab(icon, title)) }
|
||||||
|
|
||||||
|
binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
||||||
|
val getOne = intent.getIntExtra("activityId", -1)
|
||||||
|
if (getOne != -1) navBar.isVisible = false
|
||||||
|
binding.notificationViewPager.isUserInputEnabled = false
|
||||||
|
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne, CommentsEnabled)
|
||||||
|
binding.notificationViewPager.setCurrentItem(selected, false)
|
||||||
|
navBar.selectTabAt(selected)
|
||||||
|
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||||
|
override fun onTabSelected(
|
||||||
|
lastIndex: Int,
|
||||||
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
|
newIndex: Int,
|
||||||
|
newTab: AnimatedBottomBar.Tab
|
||||||
|
) {
|
||||||
|
selected = newIndex
|
||||||
|
binding.notificationViewPager.setCurrentItem(selected, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (this::navBar.isInitialized) {
|
||||||
|
navBar.selectTabAt(selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewPagerAdapter(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
lifecycle: Lifecycle,
|
||||||
|
val id: Int = -1,
|
||||||
|
val commentsEnabled: Boolean
|
||||||
|
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
|
override fun getItemCount(): Int = if (id != -1) 1 else if (commentsEnabled) 4 else 3
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment = when (position) {
|
||||||
|
0 -> newInstance(if (id != -1) ONE else USER, id)
|
||||||
|
1 -> newInstance(MEDIA)
|
||||||
|
2 -> newInstance(SUBSCRIPTION)
|
||||||
|
3 -> newInstance(COMMENT)
|
||||||
|
else -> newInstance(MEDIA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package ani.dantotsu.profile.notification
|
||||||
|
|
||||||
|
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 androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.api.Notification
|
||||||
|
import ani.dantotsu.databinding.FragmentNotificationsBinding
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.notifications.comment.CommentStore
|
||||||
|
import ani.dantotsu.notifications.subscription.SubscriptionStore
|
||||||
|
import ani.dantotsu.profile.ProfileActivity
|
||||||
|
import ani.dantotsu.profile.activity.FeedActivity
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.MEDIA
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.ONE
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.USER
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationFragment : Fragment() {
|
||||||
|
private lateinit var type: NotificationType
|
||||||
|
private var getID: Int = -1
|
||||||
|
private lateinit var binding: FragmentNotificationsBinding
|
||||||
|
private var adapter: GroupieAdapter = GroupieAdapter()
|
||||||
|
private var currentPage = 1
|
||||||
|
private var hasNextPage = false
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentNotificationsBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
arguments?.let {
|
||||||
|
getID = it.getInt("id")
|
||||||
|
type = it.getSerializableCompat<NotificationType>("type") as NotificationType
|
||||||
|
}
|
||||||
|
binding.notificationRecyclerView.adapter = adapter
|
||||||
|
binding.notificationRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
|
binding.notificationProgressBar.isVisible = true
|
||||||
|
binding.emptyTextView.text = getString(R.string.nothing_here)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
getList()
|
||||||
|
|
||||||
|
binding.notificationProgressBar.isVisible = false
|
||||||
|
}
|
||||||
|
binding.notificationSwipeRefresh.setOnRefreshListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
adapter.clear()
|
||||||
|
currentPage = 1
|
||||||
|
getList()
|
||||||
|
binding.notificationSwipeRefresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.notificationRecyclerView.addOnScrollListener(object :
|
||||||
|
RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
if (shouldLoadMore()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
binding.notificationRefresh.isVisible = true
|
||||||
|
getList()
|
||||||
|
binding.notificationRefresh.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getList() {
|
||||||
|
val list = when (type) {
|
||||||
|
ONE -> getNotificationsFiltered(false) { it.id == getID }
|
||||||
|
MEDIA -> getNotificationsFiltered(type = true) { it.media != null }
|
||||||
|
USER -> getNotificationsFiltered { it.media == null }
|
||||||
|
SUBSCRIPTION -> getSubscriptions()
|
||||||
|
COMMENT -> getComments()
|
||||||
|
}
|
||||||
|
adapter.addAll(list.map { NotificationItem(it, type, adapter, ::onClick) })
|
||||||
|
if (adapter.itemCount == 0) {
|
||||||
|
binding.emptyTextView.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getNotificationsFiltered(
|
||||||
|
reset: Boolean = true,
|
||||||
|
type: Boolean? = null,
|
||||||
|
filter: (Notification) -> Boolean
|
||||||
|
): List<Notification> {
|
||||||
|
val userId =
|
||||||
|
Anilist.userid ?: PrefManager.getVal<String>(PrefName.AnilistUserId).toIntOrNull() ?: 0
|
||||||
|
val res = Anilist.query.getNotifications(userId, currentPage, reset, type)?.data?.page
|
||||||
|
currentPage = res?.pageInfo?.currentPage?.plus(1) ?: 1
|
||||||
|
hasNextPage = res?.pageInfo?.hasNextPage ?: false
|
||||||
|
return res?.notifications?.filter(filter) ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSubscriptions(): List<Notification> {
|
||||||
|
val list = PrefManager.getNullableVal<List<SubscriptionStore>>(
|
||||||
|
PrefName.SubscriptionNotificationStore,
|
||||||
|
null
|
||||||
|
) ?: listOf()
|
||||||
|
|
||||||
|
return list
|
||||||
|
.sortedByDescending { (it.time / 1000L).toInt() }
|
||||||
|
.filter { it.image != null } // to remove old data
|
||||||
|
.map {
|
||||||
|
Notification(
|
||||||
|
it.type,
|
||||||
|
System.currentTimeMillis().toInt(),
|
||||||
|
commentId = it.mediaId,
|
||||||
|
mediaId = it.mediaId,
|
||||||
|
notificationType = it.type,
|
||||||
|
context = it.title + ": " + it.content,
|
||||||
|
createdAt = (it.time / 1000L).toInt(),
|
||||||
|
image = it.image,
|
||||||
|
banner = it.banner ?: it.image
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getComments(): List<Notification> {
|
||||||
|
val list = PrefManager.getNullableVal<List<CommentStore>>(
|
||||||
|
PrefName.CommentNotificationStore,
|
||||||
|
null
|
||||||
|
) ?: listOf()
|
||||||
|
return list
|
||||||
|
.sortedByDescending { (it.time / 1000L).toInt() }
|
||||||
|
.map {
|
||||||
|
Notification(
|
||||||
|
it.type.toString(),
|
||||||
|
System.currentTimeMillis().toInt(),
|
||||||
|
commentId = it.commentId,
|
||||||
|
notificationType = it.type.toString(),
|
||||||
|
mediaId = it.mediaId,
|
||||||
|
context = it.title + "\n" + it.content,
|
||||||
|
createdAt = (it.time / 1000L).toInt(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldLoadMore(): Boolean {
|
||||||
|
val layoutManager =
|
||||||
|
(binding.notificationRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
||||||
|
val adapter = binding.notificationRecyclerView.adapter
|
||||||
|
|
||||||
|
return hasNextPage && !binding.notificationRefresh.isVisible && adapter?.itemCount != 0 &&
|
||||||
|
layoutManager == (adapter!!.itemCount - 1) &&
|
||||||
|
!binding.notificationRecyclerView.canScrollVertically(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onClick(id: Int, optional: Int?, type: NotificationClickType) {
|
||||||
|
val intent = when (type) {
|
||||||
|
NotificationClickType.USER -> Intent(
|
||||||
|
requireContext(),
|
||||||
|
ProfileActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra("userId", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationClickType.MEDIA -> Intent(
|
||||||
|
requireContext(),
|
||||||
|
MediaDetailsActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra("mediaId", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationClickType.ACTIVITY -> Intent(
|
||||||
|
requireContext(),
|
||||||
|
FeedActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra("activityId", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationClickType.COMMENT -> Intent(
|
||||||
|
requireContext(),
|
||||||
|
MediaDetailsActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra("FRAGMENT_TO_LOAD", "COMMENTS")
|
||||||
|
putExtra("mediaId", id)
|
||||||
|
putExtra("commentId", optional ?: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationClickType.UNDEFINED -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
intent?.let {
|
||||||
|
ContextCompat.startActivity(requireContext(), it, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (this::binding.isInitialized) {
|
||||||
|
binding.root.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
enum class NotificationClickType { USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED }
|
||||||
|
enum class NotificationType { MEDIA, USER, SUBSCRIPTION, COMMENT, ONE }
|
||||||
|
|
||||||
|
fun newInstance(type: NotificationType, id: Int = -1): NotificationFragment {
|
||||||
|
return NotificationFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putSerializable("type", type)
|
||||||
|
putInt("id", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.profile.activity
|
package ani.dantotsu.profile.notification
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -8,15 +8,27 @@ import ani.dantotsu.connections.anilist.api.Notification
|
|||||||
import ani.dantotsu.connections.anilist.api.NotificationType
|
import ani.dantotsu.connections.anilist.api.NotificationType
|
||||||
import ani.dantotsu.databinding.ItemNotificationBinding
|
import ani.dantotsu.databinding.ItemNotificationBinding
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType
|
import ani.dantotsu.notifications.comment.CommentStore
|
||||||
|
import ani.dantotsu.notifications.subscription.SubscriptionStore
|
||||||
|
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT
|
||||||
|
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.toPx
|
import ani.dantotsu.toPx
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.viewbinding.BindableItem
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
|
||||||
class NotificationItem(
|
class NotificationItem(
|
||||||
private val notification: Notification,
|
private val notification: Notification,
|
||||||
val clickCallback: (Int, Int?, NotificationClickType) -> Unit
|
val type: NotificationFragment.Companion.NotificationType,
|
||||||
) : BindableItem<ItemNotificationBinding>() {
|
val parentAdapter: GroupieAdapter,
|
||||||
|
val clickCallback: (Int, Int?, NotificationClickType) -> Unit,
|
||||||
|
|
||||||
|
) : BindableItem<ItemNotificationBinding>() {
|
||||||
private lateinit var binding: ItemNotificationBinding
|
private lateinit var binding: ItemNotificationBinding
|
||||||
override fun bind(viewBinding: ItemNotificationBinding, position: Int) {
|
override fun bind(viewBinding: ItemNotificationBinding, position: Int) {
|
||||||
binding = viewBinding
|
binding = viewBinding
|
||||||
@@ -24,6 +36,48 @@ class NotificationItem(
|
|||||||
setBinding()
|
setBinding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dialog() {
|
||||||
|
when (type) {
|
||||||
|
COMMENT, SUBSCRIPTION -> {
|
||||||
|
binding.root.context.customAlertDialog().apply {
|
||||||
|
setTitle(R.string.delete)
|
||||||
|
setMessage(ActivityItemBuilder.getContent(notification))
|
||||||
|
setPosButton(R.string.yes) {
|
||||||
|
when (type) {
|
||||||
|
COMMENT -> {
|
||||||
|
val list = PrefManager.getNullableVal<List<CommentStore>>(
|
||||||
|
PrefName.CommentNotificationStore,
|
||||||
|
null
|
||||||
|
) ?: listOf()
|
||||||
|
val newList = list.filter { it.commentId != notification.commentId }
|
||||||
|
PrefManager.setVal(PrefName.CommentNotificationStore, newList)
|
||||||
|
parentAdapter.remove(this@NotificationItem)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBSCRIPTION -> {
|
||||||
|
val list = PrefManager.getNullableVal<List<SubscriptionStore>>(
|
||||||
|
PrefName.SubscriptionNotificationStore,
|
||||||
|
null
|
||||||
|
) ?: listOf()
|
||||||
|
val newList = list.filter { (it.time / 1000L).toInt() != notification.createdAt}
|
||||||
|
PrefManager.setVal(PrefName.SubscriptionNotificationStore, newList)
|
||||||
|
parentAdapter.remove(this@NotificationItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegButton(R.string.no)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLayout(): Int {
|
override fun getLayout(): Int {
|
||||||
return R.layout.item_notification
|
return R.layout.item_notification
|
||||||
}
|
}
|
||||||
@@ -32,7 +86,11 @@ class NotificationItem(
|
|||||||
return ItemNotificationBinding.bind(view)
|
return ItemNotificationBinding.bind(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun image(user: Boolean = false, commentNotification: Boolean = false, newRelease: Boolean = false) {
|
private fun image(
|
||||||
|
user: Boolean = false,
|
||||||
|
commentNotification: Boolean = false,
|
||||||
|
newRelease: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
val cover = if (user) notification.user?.bannerImage
|
val cover = if (user) notification.user?.bannerImage
|
||||||
?: notification.user?.avatar?.medium else notification.media?.bannerImage
|
?: notification.user?.avatar?.medium else notification.media?.bannerImage
|
||||||
@@ -347,6 +405,14 @@ class NotificationItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.notificationCoverUser.setOnLongClickListener {
|
||||||
|
dialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
binding.notificationBannerImage.setOnLongClickListener {
|
||||||
|
dialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.BottomSheetAddRepositoryBinding
|
||||||
|
import ani.dantotsu.databinding.ItemRepoBinding
|
||||||
|
import ani.dantotsu.media.MediaType
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
|
||||||
|
class RepoItem(
|
||||||
|
val url: String,
|
||||||
|
val onRemove: (String) -> Unit
|
||||||
|
) :BindableItem<ItemRepoBinding>() {
|
||||||
|
override fun getLayout() = R.layout.item_repo
|
||||||
|
|
||||||
|
override fun bind(viewBinding: ItemRepoBinding, position: Int) {
|
||||||
|
viewBinding.repoNameTextView.text = url
|
||||||
|
viewBinding.repoDeleteImageView.setOnClickListener {
|
||||||
|
onRemove(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initializeViewBinding(view: View): ItemRepoBinding {
|
||||||
|
return ItemRepoBinding.bind(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
private var _binding: BottomSheetAddRepositoryBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private var mediaType: MediaType = MediaType.ANIME
|
||||||
|
private var onRepositoryAdded: ((String, MediaType) -> Unit)? = null
|
||||||
|
private var repositories: MutableList<String> = mutableListOf()
|
||||||
|
private var onRepositoryRemoved: ((String) -> Unit)? = null
|
||||||
|
private var adapter: GroupieAdapter = GroupieAdapter()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = BottomSheetAddRepositoryBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.repositoriesRecyclerView.adapter = adapter
|
||||||
|
binding.repositoriesRecyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
adapter.addAll(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
||||||
|
|
||||||
|
binding.repositoryInput.hint = when(mediaType) {
|
||||||
|
MediaType.ANIME -> getString(R.string.anime_add_repository)
|
||||||
|
MediaType.MANGA -> getString(R.string.manga_add_repository)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.addButton.setOnClickListener {
|
||||||
|
val input = binding.repositoryInput.text.toString()
|
||||||
|
val error = isValidUrl(input)
|
||||||
|
if (error == null) {
|
||||||
|
context?.let { context ->
|
||||||
|
addRepoWarning(context) {
|
||||||
|
onRepositoryAdded?.invoke(input, mediaType)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.repositoryInput.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.cancelButton.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.repositoryInput.setOnEditorActionListener { textView, action, keyEvent ->
|
||||||
|
if (action == EditorInfo.IME_ACTION_DONE ||
|
||||||
|
(keyEvent?.action == KeyEvent.ACTION_UP && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
val url = textView.text.toString()
|
||||||
|
if (url.isNotBlank()) {
|
||||||
|
val error = isValidUrl(url)
|
||||||
|
if (error == null) {
|
||||||
|
context?.let { context ->
|
||||||
|
addRepoWarning(context) {
|
||||||
|
onRepositoryAdded?.invoke(url, mediaType)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@setOnEditorActionListener true
|
||||||
|
} else {
|
||||||
|
binding.repositoryInput.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRepositoryRemoved(url: String) {
|
||||||
|
onRepositoryRemoved?.invoke(url)
|
||||||
|
repositories.remove(url)
|
||||||
|
adapter.update(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidUrl(url: String): String? {
|
||||||
|
if (!url.startsWith("https://") && !url.startsWith("http://"))
|
||||||
|
return "URL must start with http:// or https://"
|
||||||
|
if (!url.removeSuffix("/").endsWith("index.min.json"))
|
||||||
|
return "URL must end with index.min.json"
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun addRepoWarning(context: Context, onRepositoryAdded: () -> Unit) {
|
||||||
|
context.customAlertDialog()
|
||||||
|
.setTitle(R.string.warning)
|
||||||
|
.setMessage(R.string.add_repository_warning)
|
||||||
|
.setPosButton(R.string.ok) {
|
||||||
|
onRepositoryAdded.invoke()
|
||||||
|
}
|
||||||
|
.setNegButton(R.string.cancel) { }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
fun newInstance(
|
||||||
|
mediaType: MediaType,
|
||||||
|
repositories: List<String>,
|
||||||
|
onRepositoryAdded: (String, MediaType) -> Unit,
|
||||||
|
onRepositoryRemoved: (String) -> Unit
|
||||||
|
): AddRepositoryBottomSheet {
|
||||||
|
return AddRepositoryBottomSheet().apply {
|
||||||
|
this.mediaType = mediaType
|
||||||
|
this.repositories.addAll(repositories)
|
||||||
|
this.onRepositoryAdded = onRepositoryAdded
|
||||||
|
this.onRepositoryRemoved = onRepositoryRemoved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist.activityMergeTimeMap
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist.rowOrderMap
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist.scoreFormats
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist.staffNameLang
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist.titleLang
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistMutations
|
||||||
|
import ani.dantotsu.connections.anilist.api.ScoreFormat
|
||||||
|
import ani.dantotsu.connections.anilist.api.UserStaffNameLanguage
|
||||||
|
import ani.dantotsu.connections.anilist.api.UserTitleLanguage
|
||||||
|
import ani.dantotsu.databinding.ActivitySettingsAnilistBinding
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.restartApp
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AnilistSettingsActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivitySettingsAnilistBinding
|
||||||
|
private lateinit var anilistMutations: AnilistMutations
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
initActivity(this)
|
||||||
|
val context = this
|
||||||
|
binding = ActivitySettingsAnilistBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
anilistMutations = AnilistMutations()
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
settingsAnilistLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = statusBarHeight
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
binding.anilistSettingsBack.setOnClickListener {
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentTitleLang = Anilist.titleLanguage
|
||||||
|
val titleFormat = UserTitleLanguage.entries.firstOrNull { it.name == currentTitleLang } ?: UserTitleLanguage.ENGLISH
|
||||||
|
|
||||||
|
settingsAnilistTitleLanguage.setText(titleLang[titleFormat.ordinal])
|
||||||
|
settingsAnilistTitleLanguage.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, titleLang)
|
||||||
|
)
|
||||||
|
settingsAnilistTitleLanguage.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedLanguage = when (i) {
|
||||||
|
0 -> "ENGLISH"
|
||||||
|
1 -> "ROMAJI"
|
||||||
|
2 -> "NATIVE"
|
||||||
|
else -> "ENGLISH"
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(titleLanguage = selectedLanguage)
|
||||||
|
Anilist.titleLanguage = selectedLanguage
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistTitleLanguage.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentStaffNameLang = Anilist.staffNameLanguage
|
||||||
|
val staffNameFormat = UserStaffNameLanguage.entries.firstOrNull { it.name == currentStaffNameLang } ?: UserStaffNameLanguage.ROMAJI_WESTERN
|
||||||
|
|
||||||
|
settingsAnilistStaffLanguage.setText(staffNameLang[staffNameFormat.ordinal])
|
||||||
|
settingsAnilistStaffLanguage.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, staffNameLang)
|
||||||
|
)
|
||||||
|
settingsAnilistStaffLanguage.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedLanguage = when (i) {
|
||||||
|
0 -> "ROMAJI_WESTERN"
|
||||||
|
1 -> "ROMAJI"
|
||||||
|
2 -> "NATIVE"
|
||||||
|
else -> "ROMAJI_WESTERN"
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(staffNameLanguage = selectedLanguage)
|
||||||
|
Anilist.staffNameLanguage = selectedLanguage
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistStaffLanguage.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentMergeTimeDisplay = activityMergeTimeMap.entries.firstOrNull { it.value == Anilist.activityMergeTime }?.key
|
||||||
|
?: "${Anilist.activityMergeTime} mins"
|
||||||
|
settingsAnilistActivityMergeTime.setText(currentMergeTimeDisplay)
|
||||||
|
settingsAnilistActivityMergeTime.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, activityMergeTimeMap.keys.toList())
|
||||||
|
)
|
||||||
|
settingsAnilistActivityMergeTime.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedDisplayTime = activityMergeTimeMap.keys.toList()[i]
|
||||||
|
val selectedApiTime = activityMergeTimeMap[selectedDisplayTime] ?: 0
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(activityMergeTime = selectedApiTime)
|
||||||
|
Anilist.activityMergeTime = selectedApiTime
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistActivityMergeTime.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentScoreFormat = Anilist.scoreFormat
|
||||||
|
val scoreFormat = ScoreFormat.entries.firstOrNull{ it.name == currentScoreFormat } ?: ScoreFormat.POINT_100
|
||||||
|
settingsAnilistScoreFormat.setText(scoreFormats[scoreFormat.ordinal])
|
||||||
|
settingsAnilistScoreFormat.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, scoreFormats)
|
||||||
|
)
|
||||||
|
settingsAnilistScoreFormat.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedFormat = when (i) {
|
||||||
|
0 -> "POINT_100"
|
||||||
|
1 -> "POINT_10_DECIMAL"
|
||||||
|
2 -> "POINT_10"
|
||||||
|
3 -> "POINT_5"
|
||||||
|
4 -> "POINT_3"
|
||||||
|
else -> "POINT_100"
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(scoreFormat = selectedFormat)
|
||||||
|
Anilist.scoreFormat = selectedFormat
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistScoreFormat.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentRowOrder = rowOrderMap.entries.firstOrNull { it.value == Anilist.rowOrder }?.key ?: "Score"
|
||||||
|
settingsAnilistRowOrder.setText(currentRowOrder)
|
||||||
|
settingsAnilistRowOrder.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, rowOrderMap.keys.toList())
|
||||||
|
)
|
||||||
|
settingsAnilistRowOrder.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedDisplayOrder = rowOrderMap.keys.toList()[i]
|
||||||
|
val selectedApiOrder = rowOrderMap[selectedDisplayOrder] ?: "score"
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(rowOrder = selectedApiOrder)
|
||||||
|
Anilist.rowOrder = selectedApiOrder
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistRowOrder.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val containers = listOf(binding.animeCustomListsContainer, binding.mangaCustomListsContainer)
|
||||||
|
val customLists = listOf(Anilist.animeCustomLists, Anilist.mangaCustomLists)
|
||||||
|
val buttons = listOf(binding.addAnimeListButton, binding.addMangaListButton)
|
||||||
|
|
||||||
|
containers.forEachIndexed { index, container ->
|
||||||
|
customLists[index]?.forEach { listName ->
|
||||||
|
addCustomListItem(listName, container, index == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.forEachIndexed { index, button ->
|
||||||
|
button.setOnClickListener {
|
||||||
|
addCustomListItem("", containers[index], index == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.SettingsAnilistCustomListSave.setOnClickListener {
|
||||||
|
saveCustomLists()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentTimezone = Anilist.timezone?.let { Anilist.getDisplayTimezone(it, context) } ?: context.getString(R.string.selected_no_time_zone)
|
||||||
|
settingsAnilistTimezone.setText(currentTimezone)
|
||||||
|
settingsAnilistTimezone.setAdapter(
|
||||||
|
ArrayAdapter(context, R.layout.item_dropdown, Anilist.timeZone)
|
||||||
|
)
|
||||||
|
settingsAnilistTimezone.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
val selectedTimezone = Anilist.timeZone[i]
|
||||||
|
val apiTimezone = Anilist.getApiTimezone(selectedTimezone)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(timezone = apiTimezone)
|
||||||
|
Anilist.timezone = apiTimezone
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
settingsAnilistTimezone.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
val displayAdultContent = Anilist.adult
|
||||||
|
val airingNotifications = Anilist.airingNotifications
|
||||||
|
|
||||||
|
binding.settingsRecyclerView1.adapter = SettingsAdapter(
|
||||||
|
arrayListOf(
|
||||||
|
Settings(
|
||||||
|
type = 2,
|
||||||
|
name = getString(R.string.airing_notifications),
|
||||||
|
desc = getString(R.string.airing_notifications_desc),
|
||||||
|
icon = R.drawable.ic_round_notifications_active_24,
|
||||||
|
isChecked = airingNotifications,
|
||||||
|
switch = { isChecked, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(airingNotifications = isChecked)
|
||||||
|
Anilist.airingNotifications = isChecked
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Settings(
|
||||||
|
type = 2,
|
||||||
|
name = getString(R.string.display_adult_content),
|
||||||
|
desc = getString(R.string.display_adult_content_desc),
|
||||||
|
icon = R.drawable.ic_round_nsfw_24,
|
||||||
|
isChecked = displayAdultContent,
|
||||||
|
switch = { isChecked, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(displayAdultContent = isChecked)
|
||||||
|
Anilist.adult = isChecked
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.settingsRecyclerView1.layoutManager =
|
||||||
|
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.settingsRecyclerView2.adapter = SettingsAdapter(
|
||||||
|
arrayListOf(
|
||||||
|
Settings(
|
||||||
|
type = 2,
|
||||||
|
name = getString(R.string.restrict_messages),
|
||||||
|
desc = getString(R.string.restrict_messages_desc),
|
||||||
|
icon = R.drawable.ic_round_lock_open_24,
|
||||||
|
isChecked = Anilist.restrictMessagesToFollowing,
|
||||||
|
switch = { isChecked, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
anilistMutations.updateSettings(restrictMessagesToFollowing = isChecked)
|
||||||
|
Anilist.restrictMessagesToFollowing = isChecked
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.settingsRecyclerView2.layoutManager =
|
||||||
|
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCustomListItem(listName: String, container: LinearLayout, isAnime: Boolean) {
|
||||||
|
val customListItemView = layoutInflater.inflate(R.layout.item_custom_list, container, false)
|
||||||
|
val textInputLayout = customListItemView.findViewById<TextInputLayout>(R.id.customListItem)
|
||||||
|
val editText = textInputLayout.editText as? TextInputEditText
|
||||||
|
editText?.setText(listName)
|
||||||
|
textInputLayout.setEndIconOnClickListener {
|
||||||
|
val name = editText?.text.toString()
|
||||||
|
if (name.isNotEmpty()) {
|
||||||
|
val listExists = if (isAnime) {
|
||||||
|
Anilist.animeCustomLists?.contains(name) ?: false
|
||||||
|
} else {
|
||||||
|
Anilist.mangaCustomLists?.contains(name) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listExists) {
|
||||||
|
customAlertDialog().apply {
|
||||||
|
setTitle(getString(R.string.delete_custom_list))
|
||||||
|
setMessage(getString(R.string.delete_custom_list_confirm, name))
|
||||||
|
setPosButton(getString(R.string.delete)) {
|
||||||
|
deleteCustomList(name, isAnime)
|
||||||
|
container.removeView(customListItemView)
|
||||||
|
}
|
||||||
|
setNegButton(getString(R.string.cancel))
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
container.removeView(customListItemView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
container.removeView(customListItemView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.addView(customListItemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteCustomList(name: String, isAnime: Boolean) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val type = if (isAnime) "ANIME" else "MANGA"
|
||||||
|
val success = anilistMutations.deleteCustomList(name, type)
|
||||||
|
if (success) {
|
||||||
|
if (isAnime) {
|
||||||
|
Anilist.animeCustomLists = Anilist.animeCustomLists?.filter { it != name }
|
||||||
|
} else {
|
||||||
|
Anilist.mangaCustomLists = Anilist.mangaCustomLists?.filter { it != name }
|
||||||
|
}
|
||||||
|
toast("Custom list deleted")
|
||||||
|
} else {
|
||||||
|
toast("Failed to delete custom list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCustomLists() {
|
||||||
|
val animeCustomLists = binding.animeCustomListsContainer.children
|
||||||
|
.mapNotNull { (it.findViewById<TextInputLayout>(R.id.customListItem).editText as? TextInputEditText)?.text?.toString() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.toList()
|
||||||
|
val mangaCustomLists = binding.mangaCustomListsContainer.children
|
||||||
|
.mapNotNull { (it.findViewById<TextInputLayout>(R.id.customListItem).editText as? TextInputEditText)?.text?.toString() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val success = anilistMutations.updateCustomLists(animeCustomLists, mangaCustomLists)
|
||||||
|
if (success) {
|
||||||
|
Anilist.animeCustomLists = animeCustomLists
|
||||||
|
Anilist.mangaCustomLists = mangaCustomLists
|
||||||
|
toast("Custom lists saved")
|
||||||
|
} else {
|
||||||
|
toast("Failed to save custom lists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
|||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.copyToClipboard
|
import ani.dantotsu.copyToClipboard
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
||||||
import ani.dantotsu.databinding.DialogRepositoriesBinding
|
import ani.dantotsu.databinding.DialogRepositoriesBinding
|
||||||
import ani.dantotsu.databinding.ItemRepositoryBinding
|
import ani.dantotsu.databinding.ItemRepositoryBinding
|
||||||
@@ -35,6 +34,7 @@ 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 ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class ExtensionsActivity : AppCompatActivity() {
|
class ExtensionsActivity : AppCompatActivity() {
|
||||||
lateinit var binding: ActivityExtensionsBinding
|
lateinit var binding: ActivityExtensionsBinding
|
||||||
@@ -173,26 +174,24 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
initActivity(this)
|
initActivity(this)
|
||||||
binding.languageselect.setOnClickListener {
|
binding.languageselect.setOnClickListener {
|
||||||
val languageOptions =
|
val languageOptions =
|
||||||
LanguageMapper.Companion.Language.entries.map { it.name }.toTypedArray()
|
LanguageMapper.Companion.Language.entries.map { entry ->
|
||||||
val builder = AlertDialog.Builder(currContext(), R.style.MyPopup)
|
entry.name.lowercase().replace("_", " ")
|
||||||
|
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() }
|
||||||
|
}.toTypedArray()
|
||||||
val listOrder: String = PrefManager.getVal(PrefName.LangSort)
|
val listOrder: String = PrefManager.getVal(PrefName.LangSort)
|
||||||
val index = LanguageMapper.Companion.Language.entries.toTypedArray()
|
val index = LanguageMapper.Companion.Language.entries.toTypedArray()
|
||||||
.indexOfFirst { it.code == listOrder }
|
.indexOfFirst { it.code == listOrder }
|
||||||
builder.setTitle("Language")
|
customAlertDialog().apply {
|
||||||
builder.setSingleChoiceItems(languageOptions, index) { dialog, i ->
|
setTitle("Language")
|
||||||
PrefManager.setVal(
|
singleChoiceItems(languageOptions, index) { selected ->
|
||||||
PrefName.LangSort,
|
PrefManager.setVal(PrefName.LangSort, LanguageMapper.Companion.Language.entries[selected].code)
|
||||||
LanguageMapper.Companion.Language.entries[i].code
|
val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
|
||||||
)
|
if (currentFragment is SearchQueryHandler) {
|
||||||
val currentFragment =
|
currentFragment.notifyDataChanged()
|
||||||
supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
|
}
|
||||||
if (currentFragment is SearchQueryHandler) {
|
|
||||||
currentFragment.notifyDataChanged()
|
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
show()
|
||||||
}
|
}
|
||||||
val dialog = builder.show()
|
|
||||||
dialog.window?.setDimAmount(0.8f)
|
|
||||||
}
|
}
|
||||||
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
topMargin = statusBarHeight
|
topMargin = statusBarHeight
|
||||||
@@ -243,10 +242,10 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com")
|
view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com")
|
||||||
view.repositoryItem.setOnClickListener {
|
view.repositoryItem.setOnClickListener {
|
||||||
AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup)
|
customAlertDialog().apply {
|
||||||
.setTitle(R.string.rem_repository)
|
setTitle(R.string.rem_repository)
|
||||||
.setMessage(item)
|
setMessage(item)
|
||||||
.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
|
setPosButton(R.string.ok) {
|
||||||
val repos = PrefManager.getVal<Set<String>>(prefName).minus(item)
|
val repos = PrefManager.getVal<Set<String>>(prefName).minus(item)
|
||||||
PrefManager.setVal(prefName, repos)
|
PrefManager.setVal(prefName, repos)
|
||||||
repoInventory.removeView(view.root)
|
repoInventory.removeView(view.root)
|
||||||
@@ -263,13 +262,10 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
setNegButton(R.string.cancel)
|
||||||
dialog.dismiss()
|
show()
|
||||||
}
|
}
|
||||||
.create()
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
view.repositoryItem.setOnLongClickListener {
|
view.repositoryItem.setOnLongClickListener {
|
||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
@@ -320,20 +316,18 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
dialogView.repoInventory.apply {
|
dialogView.repoInventory.apply {
|
||||||
getSavedRepositories(this, type)
|
getSavedRepositories(this, type)
|
||||||
}
|
}
|
||||||
val alertDialog = AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup)
|
|
||||||
.setTitle(R.string.edit_repositories)
|
|
||||||
.setView(dialogView.root)
|
|
||||||
.setPositiveButton(getString(R.string.add)) { _, _ ->
|
|
||||||
if (!dialogView.repositoryTextBox.text.isNullOrBlank())
|
|
||||||
processUserInput(dialogView.repositoryTextBox.text.toString(), type)
|
|
||||||
}
|
|
||||||
.setNegativeButton(getString(R.string.close)) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
processEditorAction(dialogView.repositoryTextBox, type)
|
processEditorAction(dialogView.repositoryTextBox, type)
|
||||||
alertDialog.show()
|
customAlertDialog().apply {
|
||||||
alertDialog.window?.setDimAmount(0.8f)
|
setTitle(R.string.edit_repositories)
|
||||||
|
setCustomView(dialogView.root)
|
||||||
|
setPosButton(R.string.add_list) {
|
||||||
|
if (!dialogView.repositoryTextBox.text.isNullOrBlank()) {
|
||||||
|
processUserInput(dialogView.repositoryTextBox.text.toString(), type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegButton(R.string.close)
|
||||||
|
show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ class FAQActivity : AppCompatActivity() {
|
|||||||
currContext()?.getString(R.string.question_5) ?: "",
|
currContext()?.getString(R.string.question_5) ?: "",
|
||||||
currContext()?.getString(R.string.answer_5) ?: ""
|
currContext()?.getString(R.string.answer_5) ?: ""
|
||||||
),
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_anilist,
|
||||||
|
currContext()?.getString(R.string.question_18) ?: "",
|
||||||
|
currContext()?.getString(R.string.answer_18) ?: ""
|
||||||
|
),
|
||||||
Triple(
|
Triple(
|
||||||
R.drawable.ic_anilist,
|
R.drawable.ic_anilist,
|
||||||
currContext()?.getString(R.string.question_6) ?: "",
|
currContext()?.getString(R.string.question_6) ?: "",
|
||||||
@@ -60,6 +65,11 @@ class FAQActivity : AppCompatActivity() {
|
|||||||
currContext()?.getString(R.string.question_7) ?: "",
|
currContext()?.getString(R.string.question_7) ?: "",
|
||||||
currContext()?.getString(R.string.answer_7) ?: ""
|
currContext()?.getString(R.string.answer_7) ?: ""
|
||||||
),
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_magnet_24,
|
||||||
|
currContext()?.getString(R.string.question_19) ?: "",
|
||||||
|
currContext()?.getString(R.string.answer_19) ?: ""
|
||||||
|
),
|
||||||
Triple(
|
Triple(
|
||||||
R.drawable.ic_round_lock_open_24,
|
R.drawable.ic_round_lock_open_24,
|
||||||
currContext()?.getString(R.string.question_9) ?: "",
|
currContext()?.getString(R.string.question_9) ?: "",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -25,13 +24,15 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.databinding.FragmentExtensionsBinding
|
import ani.dantotsu.databinding.FragmentExtensionsBinding
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper.Companion.getLanguageName
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
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.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
@@ -42,13 +43,10 @@ import kotlinx.coroutines.launch
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Collections
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
||||||
|
|
||||||
|
|
||||||
private var _binding: FragmentExtensionsBinding? = null
|
private var _binding: FragmentExtensionsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private lateinit var extensionsRecyclerView: RecyclerView
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
@@ -72,16 +70,15 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
if (allSettings.isNotEmpty()) {
|
if (allSettings.isNotEmpty()) {
|
||||||
var selectedSetting = allSettings[0]
|
var selectedSetting = allSettings[0]
|
||||||
if (allSettings.size > 1) {
|
if (allSettings.size > 1) {
|
||||||
val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }
|
val names = allSettings.map { getLanguageName(it.lang) }
|
||||||
.toTypedArray()
|
.toTypedArray()
|
||||||
var selectedIndex = 0
|
var selectedIndex = 0
|
||||||
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
requireContext().customAlertDialog().apply {
|
||||||
.setTitle("Select a Source")
|
setTitle("Select a Source")
|
||||||
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
|
singleChoiceItems(names, selectedIndex) { which ->
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
selectedIndex = which
|
selectedIndex = which
|
||||||
selectedSetting = allSettings[selectedIndex]
|
selectedSetting = allSettings[selectedIndex]
|
||||||
dialog.dismiss()
|
|
||||||
|
|
||||||
val fragment =
|
val fragment =
|
||||||
AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
|
||||||
@@ -93,13 +90,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
.setOnDismissListener {
|
onDismiss {
|
||||||
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
|
||||||
val fragment =
|
val fragment =
|
||||||
@@ -121,15 +118,20 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ pkg, forceDelete ->
|
{ pkg ->
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
if (isAdded) {
|
||||||
val context = requireContext() // Store context in a variable
|
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
snackString("Extension uninstalled")
|
||||||
|
}
|
||||||
|
}, { pkg ->
|
||||||
|
if (isAdded) {
|
||||||
|
val context = requireContext()
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
if (pkg.hasUpdate && !forceDelete) {
|
if (pkg.hasUpdate) {
|
||||||
animeExtensionManager.updateExtension(pkg)
|
animeExtensionManager.updateExtension(pkg)
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ installStep ->
|
{ installStep ->
|
||||||
val builder = NotificationCompat.Builder(
|
val builder = NotificationCompat.Builder(
|
||||||
@@ -144,7 +146,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
},
|
},
|
||||||
{ error ->
|
{ error ->
|
||||||
Injekt.get<CrashlyticsInterface>().logException(error)
|
Injekt.get<CrashlyticsInterface>().logException(error)
|
||||||
Logger.log(error) // Log the error
|
Logger.log(error)
|
||||||
val builder = NotificationCompat.Builder(
|
val builder = NotificationCompat.Builder(
|
||||||
context,
|
context,
|
||||||
Notifications.CHANNEL_DOWNLOADER_ERROR
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
@@ -170,14 +172,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
snackString("No update available")
|
||||||
snackString("Extension uninstalled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}, skipIcons
|
}, skipIcons
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -197,17 +198,10 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val newList = extensionsAdapter.currentList.toMutableList()
|
|
||||||
val fromPosition = viewHolder.absoluteAdapterPosition
|
val fromPosition = viewHolder.absoluteAdapterPosition
|
||||||
val toPosition = target.absoluteAdapterPosition
|
val toPosition = target.absoluteAdapterPosition
|
||||||
if (fromPosition < toPosition) { //probably need to switch to a recyclerview adapter
|
val newList = extensionsAdapter.currentList.toMutableList().apply {
|
||||||
for (i in fromPosition until toPosition) {
|
add(toPosition, removeAt(fromPosition))
|
||||||
Collections.swap(newList, i, i + 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in fromPosition downTo toPosition + 1) {
|
|
||||||
Collections.swap(newList, i, i - 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
extensionsAdapter.submitList(newList)
|
extensionsAdapter.submitList(newList)
|
||||||
return true
|
return true
|
||||||
@@ -269,7 +263,8 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
|
|
||||||
private class AnimeExtensionsAdapter(
|
private class AnimeExtensionsAdapter(
|
||||||
private val onSettingsClicked: (AnimeExtension.Installed) -> Unit,
|
private val onSettingsClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
private val onUninstallClicked: (AnimeExtension.Installed, Boolean) -> Unit,
|
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
|
private val onUpdateClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
val skipIcons: Boolean
|
val skipIcons: Boolean
|
||||||
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
||||||
DIFF_CALLBACK_INSTALLED
|
DIFF_CALLBACK_INSTALLED
|
||||||
@@ -295,7 +290,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val extension = getItem(position)
|
val extension = getItem(position)
|
||||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||||
val lang = LanguageMapper.getLanguageName(extension.lang)
|
val lang = getLanguageName(extension.lang)
|
||||||
holder.extensionNameTextView.text = extension.name
|
holder.extensionNameTextView.text = extension.name
|
||||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||||
holder.extensionVersionTextView.text = versionText
|
holder.extensionVersionTextView.text = versionText
|
||||||
@@ -303,20 +298,19 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
}
|
}
|
||||||
if (extension.hasUpdate) {
|
if (extension.hasUpdate) {
|
||||||
holder.closeTextView.setImageResource(R.drawable.ic_round_sync_24)
|
holder.updateView.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
holder.closeTextView.setImageResource(R.drawable.ic_round_delete_24)
|
holder.updateView.isVisible = false
|
||||||
}
|
}
|
||||||
holder.closeTextView.setOnClickListener {
|
holder.deleteView.setOnClickListener {
|
||||||
onUninstallClicked(extension, false)
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
holder.updateView.setOnClickListener {
|
||||||
|
onUpdateClicked(extension)
|
||||||
}
|
}
|
||||||
holder.settingsImageView.setOnClickListener {
|
holder.settingsImageView.setOnClickListener {
|
||||||
onSettingsClicked(extension)
|
onSettingsClicked(extension)
|
||||||
}
|
}
|
||||||
holder.closeTextView.setOnLongClickListener {
|
|
||||||
onUninstallClicked(extension, true)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filter(query: String, currentList: List<AnimeExtension.Installed>) {
|
fun filter(query: String, currentList: List<AnimeExtension.Installed>) {
|
||||||
@@ -336,7 +330,8 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
view.findViewById(R.id.extensionVersionTextView)
|
view.findViewById(R.id.extensionVersionTextView)
|
||||||
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
val closeTextView: ImageView = view.findViewById(R.id.closeTextView)
|
val deleteView: ImageView = view.findViewById(R.id.deleteTextView)
|
||||||
|
val updateView: ImageView = view.findViewById(R.id.updateTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -28,12 +26,14 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.databinding.FragmentExtensionsBinding
|
import ani.dantotsu.databinding.FragmentExtensionsBinding
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper
|
||||||
|
import ani.dantotsu.others.LanguageMapper.Companion.getLanguageName
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||||
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.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
@@ -44,7 +44,6 @@ import kotlinx.coroutines.launch
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Collections
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
||||||
@@ -74,13 +73,12 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }
|
val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }
|
||||||
.toTypedArray()
|
.toTypedArray()
|
||||||
var selectedIndex = 0
|
var selectedIndex = 0
|
||||||
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
requireContext().customAlertDialog().apply {
|
||||||
.setTitle("Select a Source")
|
setTitle("Select a Source")
|
||||||
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
|
singleChoiceItems(names, selectedIndex) { which ->
|
||||||
itemSelected = true
|
itemSelected = true
|
||||||
selectedIndex = which
|
selectedIndex = which
|
||||||
selectedSetting = allSettings[selectedIndex]
|
selectedSetting = allSettings[selectedIndex]
|
||||||
dialog.dismiss()
|
|
||||||
|
|
||||||
// Move the fragment transaction here
|
// Move the fragment transaction here
|
||||||
val fragment =
|
val fragment =
|
||||||
@@ -93,13 +91,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
.setOnDismissListener {
|
onDismiss {
|
||||||
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
|
||||||
val fragment =
|
val fragment =
|
||||||
@@ -120,15 +118,20 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ pkg: MangaExtension.Installed, forceDelete: Boolean ->
|
{ pkg: MangaExtension.Installed ->
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
if (isAdded) {
|
||||||
val context = requireContext() // Store context in a variable
|
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
snackString("Extension uninstalled")
|
||||||
|
}
|
||||||
|
}, { pkg ->
|
||||||
|
if (isAdded) {
|
||||||
|
val context = requireContext()
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
if (pkg.hasUpdate && !forceDelete) {
|
if (pkg.hasUpdate) {
|
||||||
mangaExtensionManager.updateExtension(pkg)
|
mangaExtensionManager.updateExtension(pkg)
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ installStep ->
|
{ installStep ->
|
||||||
val builder = NotificationCompat.Builder(
|
val builder = NotificationCompat.Builder(
|
||||||
@@ -143,7 +146,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
},
|
},
|
||||||
{ error ->
|
{ error ->
|
||||||
Injekt.get<CrashlyticsInterface>().logException(error)
|
Injekt.get<CrashlyticsInterface>().logException(error)
|
||||||
Logger.log(error) // Log the error
|
Logger.log(error)
|
||||||
val builder = NotificationCompat.Builder(
|
val builder = NotificationCompat.Builder(
|
||||||
context,
|
context,
|
||||||
Notifications.CHANNEL_DOWNLOADER_ERROR
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
@@ -160,7 +163,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
context,
|
context,
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
)
|
)
|
||||||
.setSmallIcon(R.drawable.ic_check)
|
.setSmallIcon(R.drawable.ic_circle_check)
|
||||||
.setContentTitle("Update complete")
|
.setContentTitle("Update complete")
|
||||||
.setContentText("The extension has been successfully updated.")
|
.setContentText("The extension has been successfully updated.")
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
@@ -169,9 +172,9 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
snackString("No update available")
|
||||||
snackString("Extension uninstalled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}, skipIcons
|
}, skipIcons
|
||||||
)
|
)
|
||||||
@@ -195,17 +198,10 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val newList = extensionsAdapter.currentList.toMutableList()
|
|
||||||
val fromPosition = viewHolder.absoluteAdapterPosition
|
val fromPosition = viewHolder.absoluteAdapterPosition
|
||||||
val toPosition = target.absoluteAdapterPosition
|
val toPosition = target.absoluteAdapterPosition
|
||||||
if (fromPosition < toPosition) { //probably need to switch to a recyclerview adapter
|
val newList = extensionsAdapter.currentList.toMutableList().apply {
|
||||||
for (i in fromPosition until toPosition) {
|
add(toPosition, removeAt(fromPosition))
|
||||||
Collections.swap(newList, i, i + 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in fromPosition downTo toPosition + 1) {
|
|
||||||
Collections.swap(newList, i, i - 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
extensionsAdapter.submitList(newList)
|
extensionsAdapter.submitList(newList)
|
||||||
return true
|
return true
|
||||||
@@ -266,7 +262,8 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
|
|
||||||
private class MangaExtensionsAdapter(
|
private class MangaExtensionsAdapter(
|
||||||
private val onSettingsClicked: (MangaExtension.Installed) -> Unit,
|
private val onSettingsClicked: (MangaExtension.Installed) -> Unit,
|
||||||
private val onUninstallClicked: (MangaExtension.Installed, Boolean) -> Unit,
|
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
||||||
|
private val onUpdateClicked: (MangaExtension.Installed) -> Unit,
|
||||||
val skipIcons: Boolean
|
val skipIcons: Boolean
|
||||||
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
||||||
DIFF_CALLBACK_INSTALLED
|
DIFF_CALLBACK_INSTALLED
|
||||||
@@ -276,24 +273,23 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
submitList(newExtensions)
|
submitList(newExtensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updatePref() {
|
||||||
|
val map = currentList.map { it.name }
|
||||||
|
PrefManager.setVal(PrefName.MangaSourcesOrder, map)
|
||||||
|
MangaSources.pinnedMangaSources = map
|
||||||
|
MangaSources.performReorderMangaSources()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.item_extension, parent, false)
|
.inflate(R.layout.item_extension, parent, false)
|
||||||
return ViewHolder(view)
|
return ViewHolder(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePref() {
|
|
||||||
val map = currentList.map { it.name }.toList()
|
|
||||||
PrefManager.setVal(PrefName.MangaSourcesOrder, map)
|
|
||||||
MangaSources.pinnedMangaSources = map
|
|
||||||
MangaSources.performReorderMangaSources()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
val extension = getItem(position)
|
||||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||||
val lang = LanguageMapper.getLanguageName(extension.lang)
|
val lang = getLanguageName(extension.lang)
|
||||||
holder.extensionNameTextView.text = extension.name
|
holder.extensionNameTextView.text = extension.name
|
||||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||||
holder.extensionVersionTextView.text = versionText
|
holder.extensionVersionTextView.text = versionText
|
||||||
@@ -301,12 +297,15 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
}
|
}
|
||||||
if (extension.hasUpdate) {
|
if (extension.hasUpdate) {
|
||||||
holder.closeTextView.setImageResource(R.drawable.ic_round_sync_24)
|
holder.updateView.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
holder.closeTextView.setImageResource(R.drawable.ic_round_delete_24)
|
holder.updateView.isVisible = false
|
||||||
}
|
}
|
||||||
holder.closeTextView.setOnClickListener {
|
holder.deleteView.setOnClickListener {
|
||||||
onUninstallClicked(extension, false)
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
holder.updateView.setOnClickListener {
|
||||||
|
onUpdateClicked(extension)
|
||||||
}
|
}
|
||||||
holder.settingsImageView.setOnClickListener {
|
holder.settingsImageView.setOnClickListener {
|
||||||
onSettingsClicked(extension)
|
onSettingsClicked(extension)
|
||||||
@@ -330,7 +329,8 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||||||
view.findViewById(R.id.extensionVersionTextView)
|
view.findViewById(R.id.extensionVersionTextView)
|
||||||
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
val closeTextView: ImageView = view.findViewById(R.id.closeTextView)
|
val deleteView: ImageView = view.findViewById(R.id.deleteTextView)
|
||||||
|
val updateView: ImageView = view.findViewById(R.id.updateTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user