Merge pull request #185 from rebelonion/dev

Dev
This commit is contained in:
rebel onion 2024-02-07 08:12:43 -06:00 committed by GitHub
commit de1788950f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
410 changed files with 7912 additions and 5835 deletions

View file

@ -16,12 +16,50 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Download last SHA artifact
uses: dawidd6/action-download-artifact@v3
with:
workflow: beta.yml
name: last-sha
path: .
continue-on-error: true
- name: Get Commits Since Last Run
run: |
if [ -f last_sha.txt ]; then
LAST_SHA=$(cat last_sha.txt)
else
# Fallback to first commit if no previous SHA available
LAST_SHA=$(git rev-list --max-parents=0 HEAD)
fi
echo "Commits since $LAST_SHA:"
# Accumulate commit logs in a shell variable
COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"%h - %s")
# URL-encode the newline characters for GitHub Actions
COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}"
COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}"
COMMIT_LOGS="${COMMIT_LOGS//$'\r'/'%0D'}"
# Append the encoded commit logs to the COMMIT_LOG environment variable
echo "COMMIT_LOG=${COMMIT_LOGS}" >> $GITHUB_ENV
# Debugging: Print the variable to check its content
echo "$COMMIT_LOGS"
shell: /usr/bin/bash -e {0}
env:
CI: true
- name: Save Current SHA for Next Run
run: echo ${{ github.sha }} > last_sha.txt
- name: Set variables
run: |
VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"')
SHA=${{ github.sha }}
VERSION="$VER.${SHA:0:7}"
VERSION="$VER+${SHA:0:7}"
echo "Version $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
@ -31,30 +69,54 @@ jobs:
distribution: 'temurin'
java-version: 17
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 assembleDebug -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.0.0
with:
name: Dantotsu
path: "app/build/outputs/apk/debug/app-debug.apk"
- name: Upload APK to Discord
path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
- name: Upload APK to Discord and Telegram
shell: bash
run: |
contentbody=$( jq -Rsa . <<< "${{ github.event.head_commit.message }}" )
curl -F "payload_json={\"content\":\" Debug-Build: <@719439449423085569> **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }}
#Discord
commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g')
# Truncate commit messages if they are too long
max_length=1900 # Adjust this value as needed
if [ ${#commit_messages} -gt $max_length ]; then
commit_messages="${commit_messages:0:$max_length}... (truncated)"
fi
contentbody=$( jq -nc --arg msg "Alpha-Build: <@719439449423085569> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' )
curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }}
#Telegram
curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \
-F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \
-F "caption=[Alpha-Build: ${VERSION}] Change logs :${commit_messages}" \
https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument
env:
COMMIT_LOG: ${{ env.COMMIT_LOG }}
VERSION: ${{ env.VERSION }}
- name: Upload Current SHA as Artifact
uses: actions/upload-artifact@v2
with:
name: last-sha
path: last_sha.txt
- name: Delete Old Pre-Releases
id: delete-pre-releases

4
app/.gitignore vendored
View file

@ -1,4 +1,8 @@
/build
/debug
/debug/output-metadata.json
/alpha
/alpha/output-metadata.json
/google/*
/fdroid/*
/release

View file

@ -1,12 +1,9 @@
plugins {
id 'com.android.application'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'kotlin-android'
id 'kotlinx-serialization'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
}
def gitCommitHash = providers.exec {
@ -20,14 +17,39 @@ android {
applicationId "ani.dantotsu"
minSdk 23
targetSdk 34
versionCode ((System.currentTimeMillis() / 60000).toInteger())
versionName "2.0.0-beta01-iv1"
versionCode((System.currentTimeMillis() / 60000).toInteger())
versionName "2.1.0"
versionCode 210000000
signingConfig signingConfigs.debug
}
flavorDimensions "store"
productFlavors {
fdroid {
// F-Droid specific configuration
dimension "store"
versionNameSuffix "-fdroid"
}
google {
// Google Play specific configuration
dimension "store"
isDefault true
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
}
}
buildTypes {
alpha {
applicationIdSuffix ".beta" // keep as beta by popular request
versionNameSuffix "-alpha01"
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"]
debuggable System.getenv("CI") == null
isDefault true
}
debug {
applicationIdSuffix ".beta"
versionNameSuffix "-beta01"
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta", icon_placeholder_round: "@mipmap/ic_launcher_beta_round"]
debuggable System.getenv("CI") == null
}
@ -39,6 +61,7 @@ android {
}
buildFeatures {
viewBinding true
buildConfig true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@ -52,6 +75,11 @@ android {
}
dependencies {
// FireBase
googleImplementation platform('com.google.firebase:firebase-bom:32.2.3')
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.0'
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.1'
// Core
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.browser:browser:1.7.0'
@ -67,7 +95,7 @@ dependencies {
implementation 'com.github.Blatzar:NiceHttp:0.4.4'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.webkit:webkit:1.9.0'
implementation 'androidx.webkit:webkit:1.10.0'
// Glide
ext.glide_version = '4.16.0'
@ -77,13 +105,8 @@ dependencies {
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
// FireBase
implementation platform('com.google.firebase:firebase-bom:32.2.3')
implementation 'com.google.firebase:firebase-analytics-ktx:21.5.0'
implementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.0'
// Exoplayer
ext.exo_version = '1.2.0'
ext.exo_version = '1.2.1'
implementation "androidx.media3:media3-exoplayer:$exo_version"
implementation "androidx.media3:media3-ui:$exo_version"
implementation "androidx.media3:media3-exoplayer-hls:$exo_version"

View file

@ -43,6 +43,25 @@
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf",
"android_client_info": {
"package_name": "ani.dantotsu.alpha"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCiXo_q4S2ofA5oCztsoLnlDqJi3GtTJjY"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf",

View file

@ -0,0 +1,376 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="768dp"
android:height="768dp"
android:viewportWidth="768"
android:viewportHeight="768">
<group
android:name="wrapper"
android:pivotX="384"
android:pivotY="384">
<clip-path
android:name="clippath"
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z" />
<group android:name="group">
<group android:name="group_1">
<path
android:name="path"
android:fillColor="#ED0021"
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
android:strokeWidth="1" />
<group
android:name="group_12"
android:pivotX="384"
android:pivotY="384">
<path
android:name="path_2"
android:fillColor="#D40037"
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
android:strokeWidth="1" />
</group>
</group>
<group android:name="group_2">
<group android:name="group_7">
<group android:name="group_10">
<group
android:name="group_11"
android:pivotX="94"
android:pivotY="440"
android:rotation="-90">
<path
android:name="path_1"
android:fillColor="#A70060"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:strokeWidth="1" />
<clip-path
android:name="mask_2"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z" />
</group>
</group>
<group
android:name="group_13"
android:pivotX="384"
android:pivotY="384">
<clip-path
android:name="mask_1"
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z" />
<group
android:name="group_9"
android:pivotX="94"
android:pivotY="440"
android:rotation="-90">
<path
android:name="path_3"
android:fillColor="#BF005E"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:strokeWidth="1" />
</group>
</group>
<group
android:name="group_6"
android:pivotX="94"
android:pivotY="440"
android:rotation="-5"
android:scaleX="1.2"
android:scaleY="1.2" />
</group>
<group
android:name="group_8"
android:pivotX="94"
android:pivotY="440"
android:rotation="-90">
<group
android:name="group_14"
android:pivotX="94"
android:pivotY="440">
<path
android:name="path_4"
android:fillColor="#C70051"
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
android:strokeWidth="1" />
</group>
</group>
</group>
<group
android:name="group_3"
android:translateX="-360">
<path
android:name="path_6"
android:fillColor="#251528"
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
android:strokeWidth="1" />
</group>
<group
android:name="group_4"
android:pivotX="384"
android:pivotY="384"
android:scaleX="1.5"
android:scaleY="1.5">
<path
android:name="path_5"
android:fillColor="#251528"
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
android:strokeWidth="1" />
</group>
<group
android:name="group_5"
android:pivotX="384"
android:pivotY="384"
android:rotation="-15"
android:scaleX="3"
android:scaleY="3">
<path
android:name="path_7"
android:fillAlpha="0"
android:fillColor="#FFD8DF"
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
android:strokeWidth="1" />
</group>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="wrapper">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="500"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="500"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_6">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:valueFrom="-10"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="350"
android:valueFrom="1.2"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="350"
android:valueFrom="1.2"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_3">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="400"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="translateX"
android:startOffset="250"
android:valueFrom="-360"
android:valueTo="0"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_4">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="400"
android:valueFrom="1.5"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="400"
android:valueFrom="1.5"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="path_7">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="fillAlpha"
android:startOffset="350"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_5">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:valueFrom="-45"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="350"
android:valueFrom="3"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="350"
android:valueFrom="3"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_8">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_9">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_11">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_12">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_13">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_14">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="200"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:valueFrom="5"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:duration="100"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="250"
android:valueFrom="0"
android:valueTo="5"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
</animated-vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Dantotsu α</string>
</resources>

View file

@ -1,5 +1,4 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
@ -14,23 +13,23 @@
android:pivotY="384">
<clip-path
android:name="clippath"
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z"/>
android:pathData="M 384 128.04 C 329.836 127.869 276.99 144.889 233.11 176.638 C 189.23 208.387 156.539 253.255 139.769 304.75 C 122.999 356.244 122.999 411.756 139.769 463.25 C 156.539 514.745 189.23 559.613 233.11 591.362 C 276.99 623.111 329.836 640.131 384 639.96 C 451.869 639.96 517.028 612.974 565.019 564.991 C 613.01 517.008 640 451.859 640 384 C 640 316.141 613.01 250.992 565.019 203.009 C 517.028 155.026 451.869 128.04 384 128.04 Z" />
<group android:name="group">
<group android:name="group_1">
<path
android:name="path"
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
android:fillColor="#6901fd"
android:strokeWidth="1"/>
android:pathData="M 128 128 L 640 128 L 640 639.96 L 128 639.96 Z"
android:strokeWidth="1" />
<group
android:name="group_12"
android:pivotX="384"
android:pivotY="384">
<path
android:name="path_2"
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
android:fillColor="#4800e5"
android:strokeWidth="1"/>
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"
android:strokeWidth="1" />
</group>
</group>
<group android:name="group_2">
@ -43,12 +42,12 @@
android:rotation="-90">
<path
android:name="path_1"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:fillColor="#2000bd"
android:strokeWidth="1"/>
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:strokeWidth="1" />
<clip-path
android:name="mask_2"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"/>
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z" />
</group>
</group>
<group
@ -57,7 +56,7 @@
android:pivotY="384">
<clip-path
android:name="mask_1"
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z"/>
android:pathData="M 384 211.74 C 338.331 211.74 294.486 229.901 262.194 262.194 C 229.901 294.486 211.74 338.331 211.74 384 C 211.74 429.669 229.901 473.514 262.194 505.806 C 294.486 538.099 338.331 556.26 384 556.26 C 429.669 556.26 473.514 538.099 505.806 505.806 C 538.099 473.514 556.26 429.669 556.26 384 C 556.26 338.331 538.099 294.486 505.806 262.194 C 473.514 229.901 429.669 211.74 384 211.74 Z" />
<group
android:name="group_9"
android:pivotX="94"
@ -65,18 +64,18 @@
android:rotation="-90">
<path
android:name="path_3"
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:fillColor="#1e00d1"
android:strokeWidth="1"/>
android:pathData="M 128 128 L 128 463.26 C 151.32 466.96 175.23 468.89 199.58 468.89 C 411.17 468.89 588.92 323.99 639.01 128 L 128 128 Z"
android:strokeWidth="1" />
</group>
</group>
<group
android:name="group_6"
android:pivotX="94"
android:pivotY="440"
android:rotation="-5"
android:scaleX="1.2"
android:scaleY="1.2"
android:rotation="-5"/>
android:scaleY="1.2" />
</group>
<group
android:name="group_8"
@ -89,9 +88,9 @@
android:pivotY="440">
<path
android:name="path_4"
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
android:fillColor="#2900da"
android:strokeWidth="1"/>
android:pathData="M 539.28 128 C 503.71 317.07 337.72 460.12 138.31 460.12 C 134.86 460.12 131.42 460.06 128 459.98 L 128 465.73 C 168.23 476.19 210.43 481.78 253.93 481.78 C 409.53 481.78 548.48 410.55 640 298.94 L 640 128.01 L 539.28 128.01 Z"
android:strokeWidth="1" />
</group>
</group>
</group>
@ -100,9 +99,9 @@
android:translateX="-360">
<path
android:name="path_6"
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
android:fillColor="#1f1f30"
android:strokeWidth="1"/>
android:pathData="M 481.82 384 C 481.82 438.03 438.02 481.82 384 481.82 L 0 481.82 L 0 286.18 L 384 286.18 C 438.02 286.18 481.82 329.98 481.82 384 Z"
android:strokeWidth="1" />
</group>
<group
android:name="group_4"
@ -112,23 +111,23 @@
android:scaleY="1.5">
<path
android:name="path_5"
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
android:fillColor="#1f1f30"
android:strokeWidth="1"/>
android:pathData="M 44.26 128 C 44.26 174.25 81.75 211.74 128 211.74 L 384 211.74 C 479.13 211.74 556.26 288.86 556.26 384 C 556.26 479.13 479.14 556.26 384 556.26 L 128 556.26 C 81.76 556.26 44.28 593.73 44.26 639.97 L 768 639.97 L 768 128 L 44.26 128 Z"
android:strokeWidth="1" />
</group>
<group
android:name="group_5"
android:pivotX="384"
android:pivotY="384"
android:rotation="-15"
android:scaleX="3"
android:scaleY="3"
android:rotation="-15">
android:scaleY="3">
<path
android:name="path_7"
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
android:fillColor="#efe7ff"
android:fillAlpha="0"
android:strokeWidth="1"/>
android:fillColor="#efe7ff"
android:pathData="M 442 366.7 L 365.98 322.81 C 352.66 315.12 336.02 324.73 336.02 340.11 L 336.02 427.89 C 336.02 443.27 352.67 452.88 365.98 445.19 L 442 401.3 C 455.32 393.61 455.32 374.39 442 366.7 Z"
android:strokeWidth="1" />
</group>
</group>
</group>
@ -138,19 +137,19 @@
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="500"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:duration="500"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:propertyName="scaleY"
android:duration="500"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
@ -158,177 +157,177 @@
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:duration="550"
android:valueFrom="-10"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="350"
android:duration="300"
android:valueFrom="1.2"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="350"
android:duration="300"
android:valueFrom="1.2"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_3">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="400"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="translateX"
android:startOffset="250"
android:duration="400"
android:valueFrom="-360"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_4">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="400"
android:duration="350"
android:valueFrom="1.5"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="400"
android:duration="350"
android:valueFrom="1.5"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="path_7">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="fillAlpha"
android:startOffset="350"
android:duration="550"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_5">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:duration="550"
android:valueFrom="-45"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:startOffset="350"
android:duration="550"
android:valueFrom="3"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
<objectAnimator
android:duration="550"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:startOffset="350"
android:duration="550"
android:valueFrom="3"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_8">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:duration="350"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_9">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:duration="350"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:duration="350"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
<objectAnimator
android:propertyName="scaleY"
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="group_11">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="350"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:startOffset="100"
android:duration="350"
android:valueFrom="-90"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="group_12">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:duration="550"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:propertyName="scaleY"
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
@ -336,19 +335,19 @@
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleX"
android:duration="550"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:propertyName="scaleY"
android:duration="550"
android:interpolator="@android:anim/overshoot_interpolator"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
@ -356,21 +355,21 @@
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="200"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="350"
android:duration="200"
android:valueFrom="5"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
android:valueType="floatType" />
<objectAnimator
android:duration="100"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:startOffset="250"
android:duration="100"
android:valueFrom="0"
android:valueTo="5"
android:valueType="floatType"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
android:valueType="floatType" />
</set>
</aapt:attr>
</target>

View file

@ -0,0 +1,9 @@
package ani.dantotsu.connections.crashlytics
class CrashlyticsFactory {
companion object {
fun createCrashlytics(): CrashlyticsInterface {
return CrashlyticsStub()
}
}
}

View file

@ -0,0 +1,9 @@
package ani.dantotsu.others
import androidx.fragment.app.FragmentActivity
object AppUpdater {
suspend fun check(activity: FragmentActivity, post: Boolean = false) {
//no-op
}
}

View file

@ -0,0 +1,9 @@
package ani.dantotsu.connections.crashlytics
class CrashlyticsFactory {
companion object {
fun createCrashlytics(): CrashlyticsInterface {
return FirebaseCrashlytics()
}
}
}

View file

@ -0,0 +1,34 @@
package ani.dantotsu.connections.crashlytics
import android.content.Context
import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.app
class FirebaseCrashlytics : CrashlyticsInterface {
override fun initialize(context: Context) {
FirebaseApp.initializeApp(context)
}
override fun logException(e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e)
}
override fun log(message: String) {
FirebaseCrashlytics.getInstance().log(message)
}
override fun setUserId(id: String) {
Firebase.crashlytics.setUserId(id)
}
override fun setCustomKey(key: String, value: String) {
FirebaseCrashlytics.getInstance().setCustomKey(key, value)
}
override fun setCrashlyticsCollectionEnabled(enabled: Boolean) {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled)
}
}

View file

@ -15,6 +15,7 @@ import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.*
import ani.dantotsu.settings.saving.PrefManager
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@ -50,7 +51,7 @@ object AppUpdater {
}
logger("Git Version : $version")
val dontShow = loadData("dont_ask_for_update_$version") ?: false
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
CustomBottomDialog.newInstance().apply {
setTitleText(
@ -71,7 +72,7 @@ object AppUpdater {
false
) { isChecked ->
if (isChecked) {
saveData("dont_ask_for_update_$version", true)
PrefManager.setCustomVal("dont_ask_for_update_$version", true)
}
}
setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
@ -186,7 +187,7 @@ object AppUpdater {
return true
}
fun openApk(context: Context, uri: Uri) {
private fun openApk(context: Context, uri: Uri) {
try {
uri.path?.let {
val contentUri = FileProvider.getUriForFile(

View file

@ -10,7 +10,8 @@
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
<uses-permission
android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
tools:ignore="LeanbackUsesWifi" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@ -51,7 +52,7 @@
android:icon="${icon_placeholder}"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:requestLegacyExternalStorage="false"
android:roundIcon="${icon_placeholder_round}"
android:supportsRtl="true"
android:theme="@style/Theme.Dantotsu"
@ -289,13 +290,13 @@
<service
android:name=".widgets.CurrentlyAiringRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="true" />
android:exported="true"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".download.video.ExoplayerDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync">
<intent-filter>
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
<category android:name="android.intent.category.DEFAULT" />
@ -317,19 +318,22 @@
android:name=".download.novel.NovelDownloaderService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service android:name=".download.anime.AnimeDownloaderService"
<service
android:name=".download.anime.AnimeDownloaderService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".connections.discord.DiscordService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
<service
android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -8,16 +8,16 @@ import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication
import ani.dantotsu.aniyomi.anime.custom.AppModule
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.others.DisabledReports
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.parsers.NovelSources
import ani.dantotsu.parsers.novel.NovelExtensionManager
import ani.dantotsu.settings.SettingsActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.google.android.material.color.DynamicColors
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
@ -51,36 +51,35 @@ class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
PrefManager.init(this)
Injekt.importModule(AppModule(this))
Injekt.importModule(PreferenceModule(this))
val crashlytics = Injekt.get<CrashlyticsInterface>()
crashlytics.initialize(this)
val useMaterialYou: Boolean = PrefManager.getVal(PrefName.UseMaterialYou)
if (useMaterialYou) {
DynamicColors.applyToActivitiesIfAvailable(this)
//TODO: HarmonizedColors
}
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
getSharedPreferences(
getString(R.string.preference_file_key),
Context.MODE_PRIVATE
).getBoolean("shared_user_id", true).let {
crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
(PrefManager.getVal(PrefName.SharedUserID) as Boolean).let {
if (!it) return@let
val dUsername = getSharedPreferences(
getString(R.string.preference_file_key),
Context.MODE_PRIVATE
).getString("discord_username", null)
val aUsername = getSharedPreferences(
getString(R.string.preference_file_key),
Context.MODE_PRIVATE
).getString("anilist_username", null)
if (dUsername != null || aUsername != null) {
Firebase.crashlytics.setUserId("$dUsername - $aUsername")
val dUsername = PrefManager.getVal(PrefName.DiscordUserName, null as String?)
val aUsername = PrefManager.getVal(PrefName.AnilistUserName, null as String?)
if (dUsername != null) {
crashlytics.setCustomKey("dUsername", dUsername)
}
if (aUsername != null) {
crashlytics.setCustomKey("aUsername", aUsername)
}
}
FirebaseCrashlytics.getInstance().setCustomKey("device Info", SettingsActivity.getDeviceInfo())
crashlytics.setCustomKey("device Info", SettingsActivity.getDeviceInfo())
Injekt.importModule(AppModule(this))
Injekt.importModule(PreferenceModule(this))
initializeNetwork(baseContext)
@ -97,13 +96,13 @@ class App : MultiDexApplication() {
animeScope.launch {
animeExtensionManager.findAvailableExtensions()
logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
AnimeSources.init(animeExtensionManager.installedExtensionsFlow, this@App)
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
}
val mangaScope = CoroutineScope(Dispatchers.Default)
mangaScope.launch {
mangaExtensionManager.findAvailableExtensions()
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
MangaSources.init(mangaExtensionManager.installedExtensionsFlow, this@App)
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
}
val novelScope = CoroutineScope(Dispatchers.Default)
novelScope.launch {

View file

@ -11,11 +11,12 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources.getSystem
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.Manifest
import android.media.MediaScannerConnection
import android.net.ConnectivityManager
import android.net.NetworkCapabilities.*
@ -31,8 +32,11 @@ import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.animation.*
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.FileProvider
import androidx.core.math.MathUtils.clamp
@ -45,12 +49,15 @@ import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.BuildConfig.APPLICATION_ID
import ani.dantotsu.connections.anilist.Genre
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.ItemCountDownBinding
import ani.dantotsu.media.Media
import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.subcriptions.NotificationClickReceiver
import ani.dantotsu.themes.ThemeManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
@ -60,10 +67,11 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.internal.ViewUtils
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics
import eu.kanade.tachiyomi.data.notification.Notifications
import kotlinx.coroutines.*
import nl.joery.animatedbottombar.AnimatedBottomBar
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.*
import java.lang.Runnable
import java.lang.reflect.Field
@ -105,63 +113,22 @@ fun logger(e: Any?, print: Boolean = true) {
println(e)
}
fun saveData(fileName: String, data: Any?, context: Context? = null) {
tryWith {
val a = context ?: currContext()
if (a != null) {
val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE)
val os = ObjectOutputStream(fos)
os.writeObject(data)
os.close()
fos.close()
}
}
}
@Suppress("UNCHECKED_CAST")
fun <T> loadData(fileName: String, context: Context? = null, toast: Boolean = true): T? {
val a = context ?: currContext()
try {
if (a?.fileList() != null)
if (fileName in a.fileList()) {
val fileIS: FileInputStream = a.openFileInput(fileName)
val objIS = ObjectInputStream(fileIS)
val data = objIS.readObject() as T
objIS.close()
fileIS.close()
return data
}
} catch (e: Exception) {
if (toast) snackString(a?.getString(R.string.error_loading_data, fileName))
//try to delete the file
try {
a?.deleteFile(fileName)
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().log("Failed to delete file $fileName")
FirebaseCrashlytics.getInstance().recordException(e)
}
e.printStackTrace()
}
return null
}
fun initActivity(a: Activity) {
val window = a.window
WindowCompat.setDecorFitsSystemWindows(window, false)
val uiSettings = loadData<UserInterfaceSettings>("ui_settings", toast = false)
?: UserInterfaceSettings().apply {
saveData("ui_settings", this)
}
uiSettings.darkMode.apply {
val darkMode = PrefManager.getVal<Int>(PrefName.DarkMode)
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
darkMode.apply {
AppCompatDelegate.setDefaultNightMode(
when (this) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
2 -> AppCompatDelegate.MODE_NIGHT_YES
1 -> AppCompatDelegate.MODE_NIGHT_NO
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
)
}
if (uiSettings.immersiveMode) {
if (immersiveMode) {
if (navBarHeight == 0) {
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
?.apply {
@ -216,7 +183,11 @@ open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
}
val typedValue = TypedValue()
val theme = requireContext().theme
theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurfaceInverse, typedValue, true)
theme.resolveAttribute(
com.google.android.material.R.attr.colorSurface,
typedValue,
true
)
window.navigationBarColor = typedValue.data
}
@ -325,20 +296,20 @@ class InputFilterMinMax(
}
class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) :
class ZoomOutPageTransformer() :
ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) {
if (position == 0.0f && uiSettings.layoutAnimations) {
if (position == 0.0f && PrefManager.getVal(PrefName.LayoutAnimations)) {
setAnimation(
view.context,
view,
uiSettings,
300,
floatArrayOf(1.3f, 1f, 1.3f, 1f),
0.5f to 0f
)
ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f)
.setDuration((200 * uiSettings.animationSpeed).toLong()).start()
.setDuration((200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong())
.start()
}
}
}
@ -346,12 +317,11 @@ class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) :
fun setAnimation(
context: Context,
viewToAnimate: View,
uiSettings: UserInterfaceSettings,
duration: Long = 150,
list: FloatArray = floatArrayOf(0.0f, 1.0f, 0.0f, 1.0f),
pivot: Pair<Float, Float> = 0.5f to 0.5f
) {
if (uiSettings.layoutAnimations) {
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
val anim = ScaleAnimation(
list[0],
list[1],
@ -362,7 +332,7 @@ fun setAnimation(
Animation.RELATIVE_TO_SELF,
pivot.second
)
anim.duration = (duration * uiSettings.animationSpeed).toLong()
anim.duration = (duration * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong()
anim.setInterpolator(context, R.anim.over_shoot)
viewToAnimate.startAnimation(anim)
}
@ -615,7 +585,7 @@ fun openLinkInBrowser(link: String?) {
}
}
fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) {
fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Activity) {
FileProvider.getUriForFile(
context,
"$APPLICATION_ID.provider",
@ -627,6 +597,105 @@ fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) {
)
}
fun savePrefsToDownloads(
title: String,
serialized: String,
context: Activity,
password: CharArray? = null
) {
FileProvider.getUriForFile(
context,
"$APPLICATION_ID.provider",
if (password != null) {
savePrefs(
serialized,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
title,
context,
password
) ?: return
} else {
savePrefs(
serialized,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
title,
context
) ?: return
}
)
}
fun savePrefs(serialized: String, path: String, title: String, context: Context): File? {
var file = File(path, "$title.ani")
var counter = 1
while (file.exists()) {
file = File(path, "${title}_${counter}.ani")
counter++
}
return try {
file.writeText(serialized)
scanFile(file.absolutePath, context)
toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath)))
file
} catch (e: Exception) {
snackString("Failed to save settings: ${e.localizedMessage}")
null
}
}
fun savePrefs(
serialized: String,
path: String,
title: String,
context: Context,
password: CharArray
): File? {
var file = File(path, "$title.ani")
var counter = 1
while (file.exists()) {
file = File(path, "${title}_${counter}.sani")
counter++
}
val salt = generateSalt()
return try {
val encryptedData = PreferenceKeystore.encryptWithPassword(password, serialized, salt)
// Combine salt and encrypted data
val dataToSave = salt + encryptedData
file.writeBytes(dataToSave)
scanFile(file.absolutePath, context)
toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath)))
file
} catch (e: Exception) {
snackString("Failed to save settings: ${e.localizedMessage}")
null
}
}
fun downloadsPermission(activity: AppCompatActivity): Boolean {
val permissions = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
val requiredPermissions = permissions.filter {
ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED
}.toTypedArray()
return if (requiredPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(activity, requiredPermissions, DOWNLOADS_PERMISSION_REQUEST_CODE)
false
} else {
true
}
}
private const val DOWNLOADS_PERMISSION_REQUEST_CODE = 100
fun shareImage(title: String, bitmap: Bitmap, context: Context) {
val contentUri = FileProvider.getUriForFile(
@ -743,10 +812,11 @@ fun MutableMap<String, Genre>.checkGenreTime(genre: String): Boolean {
return true
}
fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
if (uiSettings.layoutAnimations) {
fun setSlideIn() = AnimationSet(false).apply {
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
animation.duration = (500 * uiSettings.animationSpeed).toLong()
val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed)
animation.duration = (500 * animationSpeed).toLong()
animation.interpolator = AccelerateDecelerateInterpolator()
addAnimation(animation)
@ -757,16 +827,17 @@ fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
Animation.RELATIVE_TO_SELF, 0f
)
animation.duration = (750 * uiSettings.animationSpeed).toLong()
animation.duration = (750 * animationSpeed).toLong()
animation.interpolator = OvershootInterpolator(1.1f)
addAnimation(animation)
}
}
fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
if (uiSettings.layoutAnimations) {
fun setSlideUp() = AnimationSet(false).apply {
if (PrefManager.getVal(PrefName.LayoutAnimations)) {
var animation: Animation = AlphaAnimation(0.0f, 1.0f)
animation.duration = (500 * uiSettings.animationSpeed).toLong()
val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed)
animation.duration = (500 * animationSpeed).toLong()
animation.interpolator = AccelerateDecelerateInterpolator()
addAnimation(animation)
@ -777,7 +848,7 @@ fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply {
Animation.RELATIVE_TO_SELF, 0f
)
animation.duration = (750 * uiSettings.animationSpeed).toLong()
animation.duration = (750 * animationSpeed).toLong()
animation.interpolator = OvershootInterpolator(1.1f)
addAnimation(animation)
}
@ -839,7 +910,7 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
}
} catch (e: Exception) {
logger(e.stackTraceToString())
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
}
}
@ -964,8 +1035,7 @@ const val INCOGNITO_CHANNEL_ID = 26
fun incognitoNotification(context: Context) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val incognito = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("incognito", false)
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
if (incognito) {
val intent = Intent(context, NotificationClickReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(

View file

@ -2,7 +2,6 @@ package ani.dantotsu
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Animatable
import android.graphics.drawable.GradientDrawable
@ -45,15 +44,13 @@ import ani.dantotsu.home.MangaFragment
import ani.dantotsu.home.NoInternet
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.SharedPreferenceBooleanLiveData
import ani.dantotsu.parsers.novel.NovelExtensionManager
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefManager.asLiveBool
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.themes.ThemeManager
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@ -73,18 +70,16 @@ class MainActivity : AppCompatActivity() {
private val scope = lifecycleScope
private var load = false
private var uiSettings = UserInterfaceSettings()
@SuppressLint("InternalInsetResource", "DiscouragedApi")
@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
ThemeManager(this).applyTheme()
LangSet.setLocale(this)
super.onCreate(savedInstanceState)
//get FRAGMENT_CLASS_NAME from intent
val FRAGMENT_CLASS_NAME = intent.getStringExtra("FRAGMENT_CLASS_NAME")
val fragment = intent.getStringExtra("FRAGMENT_CLASS_NAME")
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -98,12 +93,8 @@ class MainActivity : AppCompatActivity() {
backgroundDrawable.setColor(semiTransparentColor)
_bottomBar.background = backgroundDrawable
}
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
val colorOverflow = sharedPreferences.getBoolean("colorOverflow", false)
if (!colorOverflow) {
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
}
val offset = try {
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
@ -114,11 +105,10 @@ class MainActivity : AppCompatActivity() {
val layoutParams = binding.incognito.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = 11 * offset / 12
binding.incognito.layoutParams = layoutParams
incognitoLiveData = SharedPreferenceBooleanLiveData(
sharedPreferences,
"incognito",
incognitoLiveData = PrefManager.getLiveVal(
PrefName.Incognito,
false
)
).asLiveBool()
incognitoLiveData.observe(this) {
if (it) {
val slideDownAnim = ObjectAnimator.ofFloat(
@ -162,7 +152,9 @@ class MainActivity : AppCompatActivity() {
}
val preferences: SourcePreferences = Injekt.get()
if (preferences.animeExtensionUpdatesCount().get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0) {
if (preferences.animeExtensionUpdatesCount()
.get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0
) {
Toast.makeText(
this,
"You have extension updates available!",
@ -213,24 +205,22 @@ class MainActivity : AppCompatActivity() {
binding.root.doOnAttach {
initActivity(this)
uiSettings = loadData("ui_settings") ?: uiSettings
selectedOption = if (FRAGMENT_CLASS_NAME != null) {
when (FRAGMENT_CLASS_NAME) {
selectedOption = if (fragment != null) {
when (fragment) {
AnimeFragment::class.java.name -> 0
HomeFragment::class.java.name -> 1
MangaFragment::class.java.name -> 2
else -> 1
}
} else {
uiSettings.defaultStartUpTab
PrefManager.getVal(PrefName.DefaultStartUpTab)
}
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
}
val offlineMode = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("offlineMode", false)
val offlineMode: Boolean = PrefManager.getVal(PrefName.OfflineMode)
if (!isOnline(this)) {
snackString(this@MainActivity.getString(R.string.no_internet_connection))
startActivity(Intent(this, NoInternet::class.java))
@ -251,7 +241,7 @@ class MainActivity : AppCompatActivity() {
mainViewPager.isUserInputEnabled = false
mainViewPager.adapter =
ViewPagerAdapter(supportFragmentManager, lifecycle)
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
mainViewPager.setPageTransformer(ZoomOutPageTransformer())
navbar.setOnTabSelectListener(object :
AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(
@ -305,7 +295,7 @@ class MainActivity : AppCompatActivity() {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (loadData<Boolean>("allow_opening_links", this) != true) {
if (!(PrefManager.getVal(PrefName.AllowOpeningLinks) as Boolean)) {
CustomBottomDialog.newInstance().apply {
title = "Allow Dantotsu to automatically open Anilist & MAL Links?"
val md = "Open settings & click +Add Links & select Anilist & Mal urls"
@ -317,18 +307,19 @@ class MainActivity : AppCompatActivity() {
})
setNegativeButton(this@MainActivity.getString(R.string.no)) {
saveData("allow_opening_links", true, this@MainActivity)
PrefManager.setVal(PrefName.AllowOpeningLinks, true)
dismiss()
}
setPositiveButton(this@MainActivity.getString(R.string.yes)) {
saveData("allow_opening_links", true, this@MainActivity)
PrefManager.setVal(PrefName.AllowOpeningLinks, true)
tryWith(true) {
startActivity(
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
.setData(Uri.parse("package:$packageName"))
)
}
dismiss()
}
}.show(supportFragmentManager, "dialog")
}
@ -342,7 +333,7 @@ class MainActivity : AppCompatActivity() {
while (downloadCursor.moveToNext()) {
val download = downloadCursor.download
Log.e("Downloader", download.request.uri.toString())
Log.e("Downloader", download.request.id.toString())
Log.e("Downloader", download.request.id)
Log.e("Downloader", download.request.mimeType.toString())
Log.e("Downloader", download.request.data.size.toString())
Log.e("Downloader", download.bytesDownloaded.toString())

View file

@ -2,11 +2,11 @@ package ani.dantotsu.aniyomi.anime.custom
import android.app.Application
import android.content.Context
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.parsers.novel.NovelExtensionManager
@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
import kotlinx.serialization.json.Json
@ -36,7 +35,7 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { DownloadsManager(app) }
addSingletonFactory { NetworkHelper(app, get()) }
addSingletonFactory { NetworkHelper(app) }
addSingletonFactory { AnimeExtensionManager(app) }
addSingletonFactory { MangaExtensionManager(app) }
@ -45,9 +44,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
addSingleton(sharedPreferences)
addSingletonFactory {
Json {
ignoreUnknownKeys = true
@ -57,6 +53,10 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { StandaloneDatabaseProvider(app) }
addSingletonFactory<CrashlyticsInterface> {
ani.dantotsu.connections.crashlytics.CrashlyticsFactory.createCrashlytics()
}
addSingletonFactory { MangaCache() }
ContextCompat.getMainExecutor(app).execute {
@ -72,13 +72,6 @@ class PreferenceModule(val application: Application) : InjektModule {
AndroidPreferenceStore(application)
}
addSingletonFactory {
NetworkPreferences(
preferenceStore = get(),
verboseLogging = false,
)
}
addSingletonFactory {
SourcePreferences(get())
}

View file

@ -6,14 +6,15 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.mal.MAL
import ani.dantotsu.currContext
import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
fun updateProgress(media: Media, number: String) {
val incognito = currContext()?.getSharedPreferences("Dantotsu", 0)
?.getBoolean("incognito", false) ?: false
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
if (!incognito) {
if (Anilist.userid != null) {
CoroutineScope(Dispatchers.IO).launch {

View file

@ -8,8 +8,10 @@ import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.currContext
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend
import java.io.File
import java.util.Calendar
object Anilist {
@ -28,6 +30,8 @@ object Anilist {
var genres: ArrayList<String>? = null
var tags: Map<Boolean, List<String>>? = null
var rateLimitReset: Long = 0
val sortBy = listOf(
"SCORE_DESC",
"POPULARITY_DESC",
@ -94,15 +98,12 @@ object Anilist {
}
}
fun getSavedToken(context: Context): Boolean {
if ("anilistToken" in context.fileList()) {
token = File(context.filesDir, "anilistToken").readText()
return true
}
return false
fun getSavedToken(): Boolean {
token = PrefManager.getVal(PrefName.AnilistToken, null as String?)
return !token.isNullOrEmpty()
}
fun removeSavedToken(context: Context) {
fun removeSavedToken() {
token = null
username = null
adult = false
@ -111,9 +112,7 @@ object Anilist {
bg = null
episodesWatched = null
chapterRead = null
if ("anilistToken" in context.fileList()) {
File(context.filesDir, "anilistToken").delete()
}
PrefManager.removeVal(PrefName.AnilistToken)
}
suspend inline fun <reified T : Any> executeQuery(
@ -125,6 +124,11 @@ object Anilist {
cache: Int? = null
): T? {
return tryWithSuspend {
if (rateLimitReset > System.currentTimeMillis() / 1000) {
toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
}
val data = mapOf(
"query" to query,
"variables" to variables
@ -143,6 +147,16 @@ object Anilist {
data = data,
cacheTime = cache ?: 10
)
if (json.code == 429) {
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0
if (retry > 0) {
rateLimitReset = passedLimitReset
}
toast("Rate limited. Try after $retry seconds")
throw Exception("Rate limited after $retry seconds")
}
if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down))
if (show) println("Response : ${json.text}")
json.parsed()

View file

@ -1,7 +1,7 @@
package ani.dantotsu.connections.anilist
import android.app.Activity
import android.content.Context
import android.util.Base64
import ani.dantotsu.R
import ani.dantotsu.checkGenreTime
import ani.dantotsu.checkId
@ -12,18 +12,23 @@ import ani.dantotsu.connections.anilist.api.Page
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.currContext
import ani.dantotsu.isOnline
import ani.dantotsu.loadData
import ani.dantotsu.logError
import ani.dantotsu.media.Author
import ani.dantotsu.media.Character
import ani.dantotsu.media.Media
import ani.dantotsu.media.Studio
import ani.dantotsu.others.MalScraper
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import kotlin.system.measureTimeMillis
class AnilistQueries {
@ -35,12 +40,7 @@ class AnilistQueries {
}.also { println("time : $it") }
val user = response?.data?.user ?: return false
currContext()?.let {
it.getSharedPreferences(it.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
.edit()
.putString("anilist_username", user.name)
.apply()
}
PrefManager.setVal(PrefName.AnilistUserName, user.name)
Anilist.userid = user.id
Anilist.username = user.name
@ -281,8 +281,8 @@ class AnilistQueries {
}
statuses.forEach { repeat(it) }
val set = loadData<MutableSet<Int>>("continue_$type")
if (set != null) {
val set = PrefManager.getCustomVal<Set<Int>>("continue_$type", setOf()).toMutableSet()
if (set.isNotEmpty()) {
set.reversed().forEach {
if (map.containsKey(it)) returnArray.add(map[it]!!)
}
@ -355,7 +355,11 @@ class AnilistQueries {
}
private suspend fun bannerImage(type: String): String? {
var image = loadData<BannerImage>("banner_$type")
//var image = loadData<BannerImage>("banner_$type")
val image: BannerImage? = BannerImage(
PrefManager.getCustomVal("banner_${type}_url", null),
PrefManager.getCustomVal("banner_${type}_time", 0L)
)
if (image == null || image.checkTime()) {
val response =
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: ${Anilist.userid}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """)
@ -366,13 +370,9 @@ class AnilistQueries {
else null
}
}?.flatten()?.randomOrNull() ?: return null
image = BannerImage(
random,
System.currentTimeMillis()
)
saveData("banner_$type", image)
return image.url
PrefManager.setCustomVal("banner_${type}_url", random)
PrefManager.setCustomVal("banner_${type}_time", System.currentTimeMillis())
return random
} else return image.url
}
@ -419,11 +419,17 @@ class AnilistQueries {
sorted["Favourites"] = favMedia(anime)
sorted["Favourites"]?.sortWith(compareBy { it.userFavOrder })
//favMedia doesn't fill userProgress, so we need to fill it manually by searching :(
sorted["Favourites"]?.forEach { fav ->
all.find { it.id == fav.id }?.let {
fav.userProgress = it.userProgress
}
}
sorted["All"] = all
val listsort = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getString("sort_order", "score")
val sort = listsort ?: sortOrder ?: options?.rowOrder
val listSort: String = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder)
else PrefManager.getVal(PrefName.MangaListSortOrder)
val sort = listSort ?: sortOrder ?: options?.rowOrder
for (i in sorted.keys) {
when (sort) {
"score" -> sorted[i]?.sortWith { b, a ->
@ -444,11 +450,19 @@ class AnilistQueries {
}
suspend fun getGenresAndTags(activity: Activity): Boolean {
var genres: ArrayList<String>? = loadData("genres_list", activity)
var tags: Map<Boolean, List<String>>? = loadData("tags_map", activity)
suspend fun getGenresAndTags(): Boolean {
var genres: ArrayList<String>? = PrefManager.getVal<Set<String>>(PrefName.GenresList)
.toMutableList() as ArrayList<String>?
val adultTags = PrefManager.getVal<Set<String>>(PrefName.TagsListIsAdult).toMutableList()
val nonAdultTags =
PrefManager.getVal<Set<String>>(PrefName.TagsListNonAdult).toMutableList()
var tags = if (adultTags.isEmpty() || nonAdultTags.isEmpty()) null else
mapOf(
true to adultTags,
false to nonAdultTags
)
if (genres == null) {
if (genres.isNullOrEmpty()) {
executeQuery<Query.GenreCollection>(
"""{GenreCollection}""",
force = true,
@ -458,7 +472,7 @@ class AnilistQueries {
forEach {
genres?.add(it)
}
saveData("genres_list", genres!!)
PrefManager.setVal(PrefName.GenresList, genres?.toSet())
}
}
if (tags == null) {
@ -476,10 +490,11 @@ class AnilistQueries {
true to adult,
false to good
)
saveData("tags_map", tags)
PrefManager.setVal(PrefName.TagsListIsAdult, adult.toSet())
PrefManager.setVal(PrefName.TagsListNonAdult, good.toSet())
}
}
return if (genres != null && tags != null) {
return if (!genres.isNullOrEmpty() && tags != null) {
Anilist.genres = genres
Anilist.tags = tags
true
@ -496,8 +511,37 @@ class AnilistQueries {
}
}
private fun <K, V : Serializable> saveSerializableMap(prefKey: String, map: Map<K, V>) {
val byteStream = ByteArrayOutputStream()
ObjectOutputStream(byteStream).use { outputStream ->
outputStream.writeObject(map)
}
val serializedMap = Base64.encodeToString(byteStream.toByteArray(), Base64.DEFAULT)
PrefManager.setCustomVal(prefKey, serializedMap)
}
@Suppress("UNCHECKED_CAST")
private fun <K, V : Serializable> loadSerializableMap(prefKey: String): Map<K, V>? {
try {
val serializedMap = PrefManager.getCustomVal(prefKey, "")
if (serializedMap.isEmpty()) return null
val bytes = Base64.decode(serializedMap, Base64.DEFAULT)
val byteArrayStream = ByteArrayInputStream(bytes)
return ObjectInputStream(byteArrayStream).use { inputStream ->
inputStream.readObject() as? Map<K, V>
}
} catch (e: Exception) {
return null
}
}
private suspend fun getGenreThumbnail(genre: String): Genre? {
val genres = loadData<MutableMap<String, Genre>>("genre_thumb") ?: mutableMapOf()
val genres: MutableMap<String, Genre> =
loadSerializableMap<String, Genre>("genre_thumb")?.toMutableMap()
?: mutableMapOf()
if (genres.checkGenreTime(genre)) {
try {
val genreQuery =
@ -510,7 +554,7 @@ class AnilistQueries {
it.bannerImage!!,
System.currentTimeMillis()
)
saveData("genre_thumb", genres)
saveSerializableMap("genre_thumb", genres)
return genres[genre]
}
}
@ -665,7 +709,7 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
page = pageInfo.currentPage.toString().toIntOrNull() ?: 0,
hasNextPage = pageInfo.hasNextPage == true,
)
} else snackString(currContext()?.getString(R.string.empty_response))
}
return null
}
@ -723,7 +767,7 @@ Page(page:$page,perPage:50) {
if (smaller) {
val response = execute()?.airingSchedules ?: return null
val idArr = mutableListOf<Int>()
val listOnly = loadData("recently_list_only") ?: false
val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly)
return response.mapNotNull { i ->
i.media?.let {
if (!idArr.contains(it.id))

View file

@ -5,12 +5,14 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import ani.dantotsu.BuildConfig
import ani.dantotsu.R
import ani.dantotsu.connections.discord.Discord
import ani.dantotsu.connections.mal.MAL
import ani.dantotsu.loadData
import ani.dantotsu.media.Media
import ani.dantotsu.others.AppUpdater
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.tryWithSuspend
import kotlinx.coroutines.CoroutineScope
@ -19,12 +21,8 @@ import kotlinx.coroutines.launch
suspend fun getUserId(context: Context, block: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
val token = sharedPref.getString("discord_token", null)
val userid = sharedPref.getString("discord_id", null)
val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
val userid = PrefManager.getVal(PrefName.DiscordId, null as String?)
if (userid == null && token != null) {
/*if (!Discord.getUserData())
snackString(context.getString(R.string.error_loading_discord_user_data))*/
@ -100,11 +98,13 @@ class AnilistHomeViewModel : ViewModel() {
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
suspend fun loadMain(context: FragmentActivity) {
Anilist.getSavedToken(context)
Anilist.getSavedToken()
MAL.getSavedToken(context)
Discord.getSavedToken(context)
if (loadData<Boolean>("check_update") != false) AppUpdater.check(context)
genres.postValue(Anilist.query.getGenresAndTags(context))
if (!BuildConfig.FLAVOR.contains("fdroid")) {
if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context)
}
genres.postValue(Anilist.query.getGenresAndTags())
}
val empty = MutableLiveData<Boolean>(null)

View file

@ -1,29 +1,26 @@
package ani.dantotsu.connections.anilist
import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.logError
import ani.dantotsu.logger
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager
class Login : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
val data: Uri? = intent?.data
logger(data.toString())
try {
Anilist.token =
Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
val filename = "anilistToken"
this.openFileOutput(filename, Context.MODE_PRIVATE).use {
it.write(Anilist.token!!.toByteArray())
}
PrefManager.setVal(PrefName.AnilistToken, Anilist.token ?: "")
} catch (e: Exception) {
logError(e)
}

View file

@ -5,14 +5,13 @@ import android.net.Uri
import android.os.Bundle
import androidx.core.os.bundleOf
import ani.dantotsu.loadMedia
import ani.dantotsu.others.LangSet
import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager
class UrlMedia : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
var isMAL = false

View file

@ -0,0 +1,12 @@
package ani.dantotsu.connections.crashlytics
import android.content.Context
interface CrashlyticsInterface {
fun initialize(context: Context)
fun logException(e: Throwable)
fun log(message: String)
fun setUserId(id: String)
fun setCustomKey(key: String, value: String)
fun setCrashlyticsCollectionEnabled(enabled: Boolean)
}

View file

@ -0,0 +1,29 @@
package ani.dantotsu.connections.crashlytics
import android.content.Context
class CrashlyticsStub : CrashlyticsInterface {
override fun initialize(context: Context) {
//no-op
}
override fun logException(e: Throwable) {
//no-op
}
override fun log(message: String) {
//no-op
}
override fun setUserId(id: String) {
//no-op
}
override fun setCustomKey(key: String, value: String) {
//no-op
}
override fun setCrashlyticsCollectionEnabled(enabled: Boolean) {
//no-op
}
}

View file

@ -3,9 +3,10 @@ package ani.dantotsu.connections.discord
import android.content.Context
import android.content.Intent
import android.widget.TextView
import androidx.core.content.edit
import ani.dantotsu.R
import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast
import ani.dantotsu.tryWith
import io.noties.markwon.Markwon
@ -18,37 +19,20 @@ object Discord {
var userid: String? = null
var avatar: String? = null
const val TOKEN = "discord_token"
fun getSavedToken(context: Context): Boolean {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
token = PrefManager.getVal(
PrefName.DiscordToken, null as String?
)
token = sharedPref.getString(TOKEN, null)
return token != null
}
fun saveToken(context: Context, token: String) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
sharedPref.edit {
putString(TOKEN, token)
commit()
}
PrefManager.setVal(PrefName.DiscordToken, token)
}
fun removeSavedToken(context: Context) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
sharedPref.edit {
remove(TOKEN)
commit()
}
PrefManager.removeVal(PrefName.DiscordToken)
tryWith(true) {
val dir = File(context.filesDir?.parentFile, "app_webview")

View file

@ -24,6 +24,8 @@ import ani.dantotsu.R
import ani.dantotsu.connections.discord.serializers.Presence
import ani.dantotsu.connections.discord.serializers.User
import ani.dantotsu.isOnline
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
@ -149,19 +151,11 @@ class DiscordService : Service() {
}
fun saveProfile(response: String) {
val sharedPref = baseContext.getSharedPreferences(
baseContext.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
val user = json.decodeFromString<User.Response>(response).d.user
log("User data: $user")
with(sharedPref.edit()) {
putString("discord_username", user.username)
putString("discord_id", user.id)
putString("discord_avatar", user.avatar)
apply()
}
PrefManager.setVal(PrefName.DiscordUserName, user.username)
PrefManager.setVal(PrefName.DiscordId, user.id)
PrefManager.setVal(PrefName.DiscordAvatar, user.avatar)
}
override fun onBind(p0: Intent?): IBinder? = null
@ -318,17 +312,13 @@ class DiscordService : Service() {
}
fun getToken(context: Context): String {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
val token = sharedPref.getString(Discord.TOKEN, null)
if (token == null) {
val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
return if (token == null) {
log("WebSocket: Token not found")
errorNotification("Could not set the presence", "token not found")
return ""
""
} else {
return token
token
}
}

View file

@ -11,7 +11,6 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.R
import ani.dantotsu.connections.discord.Discord.saveToken
import ani.dantotsu.others.LangSet
import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager
@ -20,7 +19,7 @@ class Login : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()

View file

@ -8,9 +8,9 @@ import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.connections.mal.MAL.clientId
import ani.dantotsu.connections.mal.MAL.saveResponse
import ani.dantotsu.loadData
import ani.dantotsu.logError
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager
@ -21,12 +21,12 @@ import kotlinx.coroutines.launch
class Login : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
try {
val data: Uri = intent?.data
?: throw Exception(getString(R.string.mal_login_uri_not_found))
val codeChallenge: String = loadData("malCodeChallenge", this)
val codeChallenge = PrefManager.getVal(PrefName.MALCodeChallenge, null as String?)
?: throw Exception(getString(R.string.mal_login_code_challenge_not_found))
val code = data.getQueryParameter("code")
?: throw Exception(getString(R.string.mal_login_code_not_present))

View file

@ -9,13 +9,12 @@ import androidx.fragment.app.FragmentActivity
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.tryWithSuspend
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.io.File
import java.security.SecureRandom
object MAL {
@ -34,7 +33,7 @@ object MAL {
.replace("/", "_")
.replace("\n", "")
saveData("malCodeChallenge", codeChallenge, context)
PrefManager.setVal(PrefName.MALCodeChallenge, codeChallenge)
val request =
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$clientId&code_challenge=$codeChallenge"
try {
@ -47,11 +46,9 @@ object MAL {
}
}
private const val MAL_TOKEN = "malToken"
private suspend fun refreshToken(): ResponseToken? {
return tryWithSuspend {
val token = loadData<ResponseToken>(MAL_TOKEN)
val token = PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
?: throw Exception(currContext()?.getString(R.string.refresh_token_load_failed))
val res = client.post(
"https://myanimelist.net/v1/oauth2/token",
@ -69,8 +66,9 @@ object MAL {
suspend fun getSavedToken(context: FragmentActivity): Boolean {
return tryWithSuspend(false) {
var res: ResponseToken = loadData(MAL_TOKEN, context)
?: return@tryWithSuspend false
var res: ResponseToken =
PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
?: return@tryWithSuspend false
if (System.currentTimeMillis() > res.expiresIn)
res = refreshToken()
?: throw Exception(currContext()?.getString(R.string.refreshing_token_failed))
@ -84,14 +82,12 @@ object MAL {
username = null
userid = null
avatar = null
if (MAL_TOKEN in context.fileList()) {
File(context.filesDir, MAL_TOKEN).delete()
}
PrefManager.removeVal(PrefName.MALToken)
}
fun saveResponse(res: ResponseToken) {
res.expiresIn += System.currentTimeMillis()
saveData(MAL_TOKEN, res)
PrefManager.setVal(PrefName.MALToken, res)
}
@Serializable
@ -100,6 +96,10 @@ object MAL {
@SerialName("expires_in") var expiresIn: Long,
@SerialName("access_token") val accessToken: String,
@SerialName("refresh_token") val refreshToken: String,
) : java.io.Serializable
) : java.io.Serializable {
companion object {
private const val serialVersionUID = 1L
}
}
}

View file

@ -1,17 +1,16 @@
package ani.dantotsu.download
import android.content.Context
import android.content.SharedPreferences
import android.os.Environment
import android.widget.Toast
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.Serializable
class DownloadsManager(private val context: Context) {
private val prefs: SharedPreferences =
context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE)
private val gson = Gson()
private val downloadsList = loadDownloads().toMutableList()
@ -24,11 +23,11 @@ class DownloadsManager(private val context: Context) {
private fun saveDownloads() {
val jsonString = gson.toJson(downloadsList)
prefs.edit().putString("downloads_key", jsonString).apply()
PrefManager.setVal(PrefName.DownloadsKeys, jsonString)
}
private fun loadDownloads(): List<DownloadedType> {
val jsonString = prefs.getString("downloads_key", null)
val jsonString = PrefManager.getVal(PrefName.DownloadsKeys, null as String?)
return if (jsonString != null) {
val type = object : TypeToken<List<DownloadedType>>() {}.type
gson.fromJson(jsonString, type)
@ -75,9 +74,11 @@ class DownloadsManager(private val context: Context) {
DownloadedType.Type.MANGA -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA }
}
DownloadedType.Type.ANIME -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME }
}
DownloadedType.Type.NOVEL -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL }
}
@ -252,7 +253,12 @@ class DownloadsManager(private val context: Context) {
const val mangaLocation = "Dantotsu/Manga"
const val animeLocation = "Dantotsu/Anime"
fun getDirectory(context: Context, type: DownloadedType.Type, title: String, chapter: String? = null): File {
fun getDirectory(
context: Context,
type: DownloadedType.Type,
title: String,
chapter: String? = null
): File {
return if (type == DownloadedType.Type.MANGA) {
if (chapter != null) {
File(

View file

@ -21,6 +21,7 @@ import androidx.media3.exoplayer.offline.DownloadManager
import androidx.media3.exoplayer.offline.DownloadService
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
@ -32,8 +33,8 @@ import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.media.anime.AnimeWatchFragment
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.Video
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.animesource.model.SAnime
@ -84,7 +85,7 @@ class AnimeDownloaderService : Service() {
builder =
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
setContentTitle("Anime Download Progress")
setSmallIcon(R.drawable.ic_round_download_24)
setSmallIcon(R.drawable.ic_download_24)
priority = NotificationCompat.PRIORITY_DEFAULT
setOnlyAlertOnce(true)
}
@ -224,8 +225,6 @@ class AnimeDownloaderService : Service() {
notificationManager.notify(NOTIFICATION_ID, builder.build())
}
broadcastDownloadStarted(task.episode)
currActivity()?.let {
Helper.downloadVideo(
it,
@ -276,7 +275,7 @@ class AnimeDownloaderService : Service() {
DownloadedType.Type.ANIME,
)
)
FirebaseCrashlytics.getInstance().recordException(
Injekt.get<CrashlyticsInterface>().logException(
Exception(
"Anime Download failed:" +
" ${download.failureReason}" +
@ -294,10 +293,7 @@ class AnimeDownloaderService : Service() {
builder.setContentText("${task.title} - ${task.episode} Download completed")
notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download completed")
getSharedPreferences(
getString(R.string.anime_downloads),
Context.MODE_PRIVATE
).edit().putString(
PrefManager.getAnimeDownloadPreferences().edit().putString(
task.getTaskName(),
task.video.file.url
).apply()
@ -335,7 +331,7 @@ class AnimeDownloaderService : Service() {
logger("Exception while downloading file: ${e.message}")
snackString("Exception while downloading file: ${e.message}")
e.printStackTrace()
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
}
broadcastDownloadFailed(task.episode)
}

View file

@ -12,6 +12,8 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import ani.dantotsu.R
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
class OfflineAnimeAdapter(
@ -22,8 +24,7 @@ class OfflineAnimeAdapter(
private val inflater: LayoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
private var originalItems: List<OfflineAnimeModel> = items
private var style =
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
private var style: Int = PrefManager.getVal(PrefName.OfflineView)
override fun getCount(): Int {
return items.size
@ -105,8 +106,7 @@ class OfflineAnimeAdapter(
}
fun notifyNewGrid() {
style =
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
style = PrefManager.getVal(PrefName.OfflineView)
notifyDataSetChanged()
}
}

View file

@ -1,11 +1,8 @@
package ani.dantotsu.download.anime
import android.animation.ObjectAnimator
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.text.Editable
@ -16,7 +13,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.LayoutAnimationController
import android.view.animation.OvershootInterpolator
import android.widget.AbsListView
import android.widget.AutoCompleteTextView
import android.widget.GridView
@ -25,34 +21,30 @@ import android.widget.TextView
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import ani.dantotsu.R
import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity
import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.textfield.TextInputLayout
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.animesource.model.SAnime
@ -64,8 +56,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import kotlin.math.max
import kotlin.math.min
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
@ -73,9 +63,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
private var downloads: List<OfflineAnimeModel> = listOf()
private lateinit var gridView: GridView
private lateinit var adapter: OfflineAnimeAdapter
private lateinit var total : TextView
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
private lateinit var total: TextView
override fun onCreateView(
inflater: LayoutInflater,
@ -101,15 +89,12 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineANIME)
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
}
if (!uiSettings.immersiveMode) {
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
view.rootView.fitsSystemWindows = true
}
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("colorOverflow", false) ?: false
if (!colorOverflow) {
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
}
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
searchView.addTextChangedListener(object : TextWatcher {
@ -123,8 +108,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
onSearchQuery(s.toString())
}
})
var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getInt("offline_view", 0)
var style: Int = PrefManager.getVal(PrefName.OfflineView)
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
var selected = when (style) {
@ -143,8 +127,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
layoutList.setOnClickListener {
selected(it as ImageView)
style = 0
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putInt("offline_view", style!!)?.apply()
PrefManager.setVal(PrefName.OfflineView, style)
gridView.visibility = View.GONE
gridView = view.findViewById(R.id.gridView)
adapter.notifyNewGrid()
@ -154,15 +137,15 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
layoutcompact.setOnClickListener {
selected(it as ImageView)
style = 1
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putInt("offline_view", style!!)?.apply()
PrefManager.setVal(PrefName.OfflineView, style)
gridView.visibility = View.GONE
gridView = view.findViewById(R.id.gridView1)
adapter.notifyNewGrid()
grid()
}
gridView = if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
gridView =
if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
total = view.findViewById(R.id.total)
grid()
return view
@ -195,13 +178,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
requireActivity(),
Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("download", true),
ActivityOptionsCompat.makeSceneTransitionAnimation(
requireActivity(),
Pair.create(
requireActivity().findViewById<ImageView>(R.id.itemCompactImage),
ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage))
),
).toBundle()
null
)
} ?: run {
snackString("no media found")
@ -220,11 +197,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
builder.setMessage("Are you sure you want to delete ${item.title}?")
builder.setPositiveButton("Yes") { _, _ ->
downloadManager.removeMedia(item.title, type)
val mediaIds = requireContext().getSharedPreferences(
getString(R.string.anime_downloads),
Context.MODE_PRIVATE
)
?.all?.filter { it.key.contains(item.title) }?.values ?: emptySet()
val mediaIds =
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
?: emptySet()
if (mediaIds.isEmpty()) {
snackString("No media found") // if this happens, terrible things have happened
}
@ -252,41 +227,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var height = statusBarHeight
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
height = max(
statusBarHeight,
min(
displayCutout.boundingRects[0].width(),
displayCutout.boundingRects[0].height()
)
)
}
}
}
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
val visible = false
fun animate() {
val start = if (visible) 0f else 1f
val end = if (!visible) 0f else 1f
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
}
scrollTop.setOnClickListener {
gridView.smoothScrollToPositionFromTop(0, 0)
}
@ -306,7 +247,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
totalItemCount: Int
) {
val first = view.getChildAt(0)
val visibility = first != null && first.top < -height
val visibility = first != null && first.top < 0
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
}
})
@ -340,8 +283,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
for (title in animeTitles) {
val _downloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
val download = _downloads.first()
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineAnimeModel = loadOfflineAnimeModel(download)
newAnimeDownloads += offlineAnimeModel
}
@ -349,12 +292,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
}
private fun getMedia(downloadedType: DownloadedType): Media? {
val type = if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
} else {
"Novel"
val type = when (downloadedType.type) {
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -379,18 +320,16 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
null
}
}
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
val type = when (downloadedType.type) {
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -398,8 +337,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
)
//load media.json and convert to media class with gson
try {
val media = File(directory, "media.json")
val mediaJson = media.readText()
val mediaModel = getMedia(downloadedType)!!
val cover = File(directory, "cover.jpg")
val coverUri: Uri? = if (cover.exists()) {
@ -439,7 +376,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineAnimeModel(
"unknown",
"0",
@ -448,8 +385,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
"??",
"movie",
"hmm",
false,
false,
isOngoing = false,
isUserScored = false,
null,
null
)

View file

@ -18,6 +18,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
@ -29,7 +30,6 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROG
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
import ani.dantotsu.snackString
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
@ -76,7 +76,7 @@ class MangaDownloaderService : Service() {
notificationManager = NotificationManagerCompat.from(this)
builder = NotificationCompat.Builder(this, CHANNEL_DOWNLOADER_PROGRESS).apply {
setContentTitle("Manga Download Progress")
setSmallIcon(R.drawable.ic_round_download_24)
setSmallIcon(R.drawable.ic_download_24)
priority = NotificationCompat.PRIORITY_DEFAULT
setOnlyAlertOnce(true)
setProgress(0, 0, false)
@ -253,7 +253,7 @@ class MangaDownloaderService : Service() {
} catch (e: Exception) {
logger("Exception while downloading file: ${e.message}")
snackString("Exception while downloading file: ${e.message}")
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
broadcastDownloadFailed(task.chapter)
}
}
@ -283,7 +283,7 @@ class MangaDownloaderService : Service() {
} catch (e: Exception) {
println("Exception while saving image: ${e.message}")
snackString("Exception while saving image: ${e.message}")
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
}
}

View file

@ -11,6 +11,8 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import ani.dantotsu.R
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
class OfflineMangaAdapter(
@ -21,8 +23,7 @@ class OfflineMangaAdapter(
private val inflater: LayoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
private var originalItems: List<OfflineMangaModel> = items
private var style =
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
private var style: Int = PrefManager.getVal(PrefName.OfflineView)
override fun getCount(): Int {
return items.size
@ -104,8 +105,7 @@ class OfflineMangaAdapter(
}
fun notifyNewGrid() {
style =
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
style = PrefManager.getVal(PrefName.OfflineView)
notifyDataSetChanged()
}
}

View file

@ -1,10 +1,7 @@
package ani.dantotsu.download.manga
import android.animation.ObjectAnimator
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.text.Editable
@ -15,7 +12,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.LayoutAnimationController
import android.view.animation.OvershootInterpolator
import android.widget.AbsListView
import android.widget.AutoCompleteTextView
import android.widget.GridView
@ -23,33 +19,29 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment
import ani.dantotsu.R
import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity
import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.textfield.TextInputLayout
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.source.model.SChapter
@ -57,8 +49,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import kotlin.math.max
import kotlin.math.min
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
@ -67,8 +57,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private lateinit var gridView: GridView
private lateinit var adapter: OfflineMangaAdapter
private lateinit var total: TextView
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
override fun onCreateView(
inflater: LayoutInflater,
@ -94,15 +82,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineMANGA)
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
}
if (!uiSettings.immersiveMode) {
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
view.rootView.fitsSystemWindows = true
}
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("colorOverflow", false) ?: false
if (!colorOverflow) {
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
}
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
searchView.addTextChangedListener(object : TextWatcher {
@ -116,8 +101,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
onSearchQuery(s.toString())
}
})
var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getInt("offline_view", 0)
var style: Int = PrefManager.getVal(PrefName.OfflineView)
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
var selected = when (style) {
@ -136,8 +120,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
layoutList.setOnClickListener {
selected(it as ImageView)
style = 0
requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit()
.putInt("offline_view", style!!).apply()
PrefManager.setVal(PrefName.OfflineView, style)
gridView.visibility = View.GONE
gridView = view.findViewById(R.id.gridView)
adapter.notifyNewGrid()
@ -148,8 +131,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
layoutcompact.setOnClickListener {
selected(it as ImageView)
style = 1
requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit()
.putInt("offline_view", style!!).apply()
PrefManager.setVal(PrefName.OfflineView, style)
gridView.visibility = View.GONE
gridView = view.findViewById(R.id.gridView1)
adapter.notifyNewGrid()
@ -180,19 +162,13 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
media?.let {
ContextCompat.startActivity(
requireActivity(),
Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("media", getMedia(it))
.putExtra("download", true),
ActivityOptionsCompat.makeSceneTransitionAnimation(
requireActivity(),
Pair.create(
gridView.getChildAt(position)
.findViewById<ImageView>(R.id.itemCompactImage),
ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage))
)
).toBundle()
null
)
} ?: run {
snackString("no media found")
@ -236,41 +212,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initActivity(requireActivity())
var height = statusBarHeight
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
height = max(
statusBarHeight,
min(
displayCutout.boundingRects[0].width(),
displayCutout.boundingRects[0].height()
)
)
}
}
}
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
val visible = false
fun animate() {
val start = if (visible) 0f else 1f
val end = if (!visible) 0f else 1f
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
}
scrollTop.setOnClickListener {
gridView.smoothScrollToPositionFromTop(0, 0)
}
@ -290,8 +233,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
totalItemCount: Int
) {
val first = view.getChildAt(0)
val visibility = first != null && first.top < -height
val visibility = first != null && first.top < 0
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
}
})
@ -324,8 +269,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
for (title in mangaTitles) {
val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
val download = _downloads.first()
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel
}
@ -333,8 +278,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
for (title in novelTitles) {
val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
val download = _downloads.first()
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newNovelDownloads += offlineMangaModel
}
@ -343,12 +288,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}
private fun getMedia(downloadedType: DownloadedType): Media? {
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
val type = when (downloadedType.type) {
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -367,18 +310,16 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
null
}
}
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
val type = when (downloadedType.type) {
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -386,8 +327,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
)
//load media.json and convert to media class with gson
try {
val media = File(directory, "media.json")
val mediaJson = media.readText()
val mediaModel = getMedia(downloadedType)!!
val cover = File(directory, "cover.jpg")
val coverUri: Uri? = if (cover.exists()) {
@ -421,7 +360,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineMangaModel(
"unknown",
"0",
@ -429,8 +368,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
"??",
"movie",
"hmm",
false,
false,
isOngoing = false,
isUserScored = false,
null,
null
)

View file

@ -17,13 +17,13 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.novel.NovelReadFragment
import ani.dantotsu.snackString
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.data.notification.Notifications
@ -75,7 +75,7 @@ class NovelDownloaderService : Service() {
builder =
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
setContentTitle("Novel Download Progress")
setSmallIcon(R.drawable.ic_round_download_24)
setSmallIcon(R.drawable.ic_download_24)
priority = NotificationCompat.PRIORITY_DEFAULT
setOnlyAlertOnce(true)
setProgress(0, 0, false)
@ -342,7 +342,7 @@ class NovelDownloaderService : Service() {
} catch (e: Exception) {
logger("Exception while downloading .epub: ${e.message}")
snackString("Exception while downloading .epub: ${e.message}")
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
broadcastDownloadFailed(task.originalLink)
}
}

View file

@ -13,11 +13,9 @@ import android.util.Log
import androidx.annotation.OptIn
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getString
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider
import androidx.media3.datasource.DataSource
@ -31,7 +29,6 @@ import androidx.media3.exoplayer.offline.DownloadHelper
import androidx.media3.exoplayer.offline.DownloadManager
import androidx.media3.exoplayer.offline.DownloadService
import androidx.media3.exoplayer.scheduler.Requirements
import androidx.media3.ui.TrackSelectionDialogBuilder
import ani.dantotsu.R
import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.DownloadedType
@ -45,6 +42,7 @@ import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.parsers.Video
import ani.dantotsu.parsers.VideoType
import ani.dantotsu.settings.saving.PrefManager
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -231,19 +229,13 @@ object Helper {
DownloadService.sendRemoveDownload(
context,
ExoplayerDownloadService::class.java,
context.getSharedPreferences(
getString(context, R.string.anime_downloads),
Context.MODE_PRIVATE
).getString(
PrefManager.getAnimeDownloadPreferences().getString(
animeDownloadTask.getTaskName(),
""
) ?: "",
false
)
context.getSharedPreferences(
getString(context, R.string.anime_downloads),
Context.MODE_PRIVATE
).edit()
PrefManager.getAnimeDownloadPreferences().edit()
.remove(animeDownloadTask.getTaskName())
.apply()
downloadsManger.removeDownload(

View file

@ -2,7 +2,6 @@ package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -28,13 +27,13 @@ import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentAnimeBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.ProgressAdapter
import ani.dantotsu.media.SearchActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers
@ -50,9 +49,6 @@ class AnimeFragment : Fragment() {
private val binding get() = _binding!!
private lateinit var animePageAdapter: AnimePageAdapter
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistAnimeViewModel by activityViewModels()
override fun onCreateView(
@ -217,7 +213,7 @@ class AnimeFragment : Fragment() {
if (it != null) {
animePageAdapter.updateTrending(
MediaAdaptor(
if (uiSettings.smallView) 3 else 2,
if (PrefManager.getVal(PrefName.SmallView)) 3 else 2,
it,
requireActivity(),
viewPager = animePageAdapter.trendingViewPager
@ -268,8 +264,11 @@ class AnimeFragment : Fragment() {
model.loaded = true
model.loadTrending(1)
model.loadUpdated()
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("popular_list", false))
model.loadPopular(
"ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
PrefName.PopularAnimeList
)
)
}
live.postValue(false)
_binding?.animeRefresh?.isRefreshing = false

View file

@ -1,6 +1,5 @@
package ani.dantotsu.home
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
@ -22,7 +21,6 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemAnimePageBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.CalendarActivity
import ani.dantotsu.media.GenreActivity
@ -33,7 +31,8 @@ import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
@ -44,8 +43,6 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
val binding =
@ -68,17 +65,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
val color = typedValue.data
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("colorOverflow", false) ?: false
if (!colorOverflow) {
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
}
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
if (PrefManager.getVal(PrefName.SmallView)) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = (-108f).px
}
@ -133,14 +125,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
binding.animeIncludeList.visibility =
if (Anilist.userid != null) View.VISIBLE else View.GONE
binding.animeIncludeList.isChecked = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("popular_list", true) ?: true
binding.animeIncludeList.isChecked = PrefManager.getVal(PrefName.PopularAnimeList)
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
onIncludeListClick.invoke(isChecked)
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putBoolean("popular_list", isChecked)?.apply()
PrefManager.setVal(PrefName.PopularAnimeList, isChecked)
}
if (ready.value == false)
ready.postValue(true)
@ -179,12 +169,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
)
binding.animeTrendingViewPager.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.animeTitleContainer.startAnimation(setSlideUp(uiSettings))
LayoutAnimationController(setSlideIn(), 0.25f)
binding.animeTitleContainer.startAnimation(setSlideUp())
binding.animeListContainer.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
binding.animeSeasonsCont.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
}
fun updateRecent(adaptor: MediaAdaptor) {
@ -199,11 +189,11 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
binding.animeRecently.visibility = View.VISIBLE
binding.animeRecently.startAnimation(setSlideUp(uiSettings))
binding.animeRecently.startAnimation(setSlideUp())
binding.animeUpdatedRecyclerView.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
binding.animePopular.visibility = View.VISIBLE
binding.animePopular.startAnimation(setSlideUp(uiSettings))
binding.animePopular.startAnimation(setSlideUp())
}
fun updateAvatar() {

View file

@ -27,7 +27,6 @@ import ani.dantotsu.connections.anilist.AnilistHomeViewModel
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentHomeBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaAdaptor
@ -37,7 +36,8 @@ import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers
@ -70,14 +70,13 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val scope = lifecycleScope
var uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
fun load() {
if (activity != null && _binding != null) lifecycleScope.launch(Dispatchers.Main) {
binding.homeUserName.text = Anilist.username
binding.homeUserEpisodesWatched.text = Anilist.episodesWatched.toString()
binding.homeUserChaptersRead.text = Anilist.chapterRead.toString()
binding.homeUserAvatar.loadImage(Anilist.avatar)
if (!uiSettings.bannerAnimations) binding.homeUserBg.pause()
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause()
binding.homeUserBg.loadImage(Anilist.bg)
binding.homeUserDataProgressBar.visibility = View.GONE
@ -98,14 +97,14 @@ class HomeFragment : Fragment() {
)
}
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
binding.homeUserAvatarContainer.startAnimation(setSlideUp())
binding.homeUserDataContainer.visibility = View.VISIBLE
binding.homeUserDataContainer.layoutAnimation =
LayoutAnimationController(setSlideUp(uiSettings), 0.25f)
LayoutAnimationController(setSlideUp(), 0.25f)
binding.homeAnimeList.visibility = View.VISIBLE
binding.homeMangaList.visibility = View.VISIBLE
binding.homeListContainer.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
}
else {
snackString(currContext()?.getString(R.string.please_reload))
@ -127,7 +126,7 @@ class HomeFragment : Fragment() {
binding.homeTopContainer.updatePadding(top = statusBarHeight)
var reached = false
val duration = (uiSettings.animationSpeed * 200).toLong()
val duration = ((PrefManager.getVal(PrefName.AnimationSpeed) as Float) * 200).toLong()
binding.homeScroll.setOnScrollChangeListener { _, _, _, _, _ ->
if (!binding.homeScroll.canScrollVertically(1)) {
reached = true
@ -206,13 +205,13 @@ class HomeFragment : Fragment() {
)
recyclerView.visibility = View.VISIBLE
recyclerView.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
} else {
empty.visibility = View.VISIBLE
}
title.visibility = View.VISIBLE
title.startAnimation(setSlideUp(uiSettings))
title.startAnimation(setSlideUp())
progress.visibility = View.GONE
}
}
@ -295,12 +294,12 @@ class HomeFragment : Fragment() {
binding.homeRecommended
)
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
binding.homeUserAvatarContainer.startAnimation(setSlideUp())
model.empty.observe(viewLifecycleOwner) {
binding.homeDantotsuContainer.visibility = if (it == true) View.VISIBLE else View.GONE
(binding.homeDantotsuIcon.drawable as Animatable).start()
binding.homeDantotsuContainer.startAnimation(setSlideUp(uiSettings))
binding.homeDantotsuContainer.startAnimation(setSlideUp())
binding.homeDantotsuIcon.setSafeOnClickListener {
(binding.homeDantotsuIcon.drawable as Animatable).start()
}
@ -330,8 +329,6 @@ class HomeFragment : Fragment() {
live.observe(viewLifecycleOwner) {
if (it) {
scope.launch {
uiSettings =
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
withContext(Dispatchers.IO) {
//Get userData First
getUserId(requireContext()) {
@ -340,8 +337,10 @@ class HomeFragment : Fragment() {
model.loaded = true
model.setListImages()
var empty = true
val homeLayoutShow: List<Boolean> =
PrefManager.getVal(PrefName.HomeLayoutShow)
(array.indices).forEach { i ->
if (uiSettings.homeLayoutShow[i]) {
if (homeLayoutShow.elementAt(i)) {
array[i].run()
empty = false
} else withContext(Dispatchers.Main) {

View file

@ -1,14 +1,23 @@
package ani.dantotsu.home
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.FragmentLoginBinding
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferencePackager
import ani.dantotsu.toast
import com.google.android.material.textfield.TextInputEditText
class LoginFragment : Fragment() {
@ -29,5 +38,99 @@ class LoginFragment : Fragment() {
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
val openDocumentLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) {
try {
val jsonString =
requireActivity().contentResolver.openInputStream(uri)?.readBytes()
?: throw Exception("Error reading file")
val name =
DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings"
//.sani is encrypted, .ani is not
if (name.endsWith(".sani")) {
passwordAlertDialog() { password ->
if (password != null) {
val salt = jsonString.copyOfRange(0, 16)
val encrypted = jsonString.copyOfRange(16, jsonString.size)
val decryptedJson = try {
PreferenceKeystore.decryptWithPassword(
password,
encrypted,
salt
)
} catch (e: Exception) {
toast("Incorrect password")
return@passwordAlertDialog
}
if (PreferencePackager.unpack(decryptedJson))
restartApp()
} else {
toast("Password cannot be empty")
}
}
} else if (name.endsWith(".ani")) {
val decryptedJson = jsonString.toString(Charsets.UTF_8)
if (PreferencePackager.unpack(decryptedJson))
restartApp()
} else {
toast("Invalid file type")
}
} catch (e: Exception) {
e.printStackTrace()
toast("Error importing settings")
}
}
}
binding.importSettingsButton.setOnClickListener {
openDocumentLauncher.launch(arrayOf("*/*"))
}
}
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
val password = CharArray(16).apply { fill('0') }
// Inflate the dialog layout
val dialogView =
LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null)
dialogView.findViewById<TextInputEditText>(R.id.userAgentTextBox)?.hint = "Password"
val subtitleTextView = dialogView.findViewById<TextView>(R.id.subtitle)
subtitleTextView?.visibility = View.VISIBLE
subtitleTextView?.text = "Enter your password to decrypt the file"
val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup)
.setTitle("Enter Password")
.setView(dialogView)
.setPositiveButton("OK", null)
.setNegativeButton("Cancel") { dialog, _ ->
password.fill('0')
dialog.dismiss()
callback(null)
}
.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
// Override the positive button here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val editText = dialog.findViewById<TextInputEditText>(R.id.userAgentTextBox)
if (editText?.text?.isNotBlank() == true) {
editText.text?.toString()?.trim()?.toCharArray(password)
dialog.dismiss()
callback(password)
} else {
toast("Password cannot be empty")
}
}
}
private fun restartApp() {
val intent = Intent(requireActivity(), requireActivity().javaClass)
requireActivity().finish()
startActivity(intent)
}
}

View file

@ -2,7 +2,6 @@ package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
@ -26,12 +25,12 @@ import ani.dantotsu.connections.anilist.AnilistMangaViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.ProgressAdapter
import ani.dantotsu.navBarHeight
import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers
@ -46,9 +45,6 @@ class MangaFragment : Fragment() {
private val binding get() = _binding!!
private lateinit var mangaPageAdapter: MangaPageAdapter
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistMangaViewModel by activityViewModels()
override fun onCreateView(
@ -175,7 +171,7 @@ class MangaFragment : Fragment() {
if (it != null) {
mangaPageAdapter.updateTrending(
MediaAdaptor(
if (uiSettings.smallView) 3 else 2,
if (PrefManager.getVal(PrefName.SmallView)) 3 else 2,
it,
requireActivity(),
viewPager = mangaPageAdapter.trendingViewPager
@ -242,8 +238,11 @@ class MangaFragment : Fragment() {
model.loaded = true
model.loadTrending()
model.loadTrendingNovel()
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("popular_list", false) )
model.loadPopular(
"MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
PrefName.PopularMangaList
)
)
}
live.postValue(false)
_binding?.mangaRefresh?.isRefreshing = false

View file

@ -1,6 +1,5 @@
package ani.dantotsu.home
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
@ -22,7 +21,6 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemMangaPageBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.GenreActivity
import ani.dantotsu.media.MediaAdaptor
@ -32,7 +30,8 @@ import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
@ -43,8 +42,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
val binding =
@ -67,17 +64,12 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
val color = typedValue.data
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("colorOverflow", false) ?: false
if (!colorOverflow) {
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
}
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
if (PrefManager.getVal(PrefName.SmallView)) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = (-108f).px
}
@ -126,14 +118,12 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
binding.mangaIncludeList.visibility =
if (Anilist.userid != null) View.VISIBLE else View.GONE
binding.mangaIncludeList.isChecked = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("popular_list", true) ?: true
binding.mangaIncludeList.isChecked = PrefManager.getVal(PrefName.PopularMangaList)
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
onIncludeListClick.invoke(isChecked)
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putBoolean("popular_list", isChecked)?.apply()
PrefManager.setVal(PrefName.PopularMangaList, isChecked)
}
if (ready.value == false)
ready.postValue(true)
@ -169,10 +159,10 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
)
binding.mangaTrendingViewPager.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.mangaTitleContainer.startAnimation(setSlideUp(uiSettings))
LayoutAnimationController(setSlideIn(), 0.25f)
binding.mangaTitleContainer.startAnimation(setSlideUp())
binding.mangaListContainer.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
}
fun updateNovel(adaptor: MediaAdaptor) {
@ -187,11 +177,11 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
binding.mangaNovel.visibility = View.VISIBLE
binding.mangaNovel.startAnimation(setSlideUp(uiSettings))
binding.mangaNovel.startAnimation(setSlideUp())
binding.mangaNovelRecyclerView.layoutAnimation =
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
LayoutAnimationController(setSlideIn(), 0.25f)
binding.mangaPopular.visibility = View.VISIBLE
binding.mangaPopular.startAnimation(setSlideUp(uiSettings))
binding.mangaPopular.startAnimation(setSlideUp())
}
fun updateAvatar() {

View file

@ -1,6 +1,5 @@
package ani.dantotsu.home
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.Bundle
@ -23,43 +22,36 @@ import ani.dantotsu.databinding.ActivityNoInternetBinding
import ani.dantotsu.download.anime.OfflineAnimeFragment
import ani.dantotsu.download.manga.OfflineMangaFragment
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.navBarHeight
import ani.dantotsu.offline.OfflineFragment
import ani.dantotsu.others.LangSet
import ani.dantotsu.selectedOption
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager
import nl.joery.animatedbottombar.AnimatedBottomBar
class NoInternet : AppCompatActivity() {
private lateinit var binding: ActivityNoInternetBinding
lateinit var bottomBar: AnimatedBottomBar
private var uiSettings = UserInterfaceSettings()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityNoInternetBinding.inflate(layoutInflater)
setContentView(binding.root)
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
val bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val backgroundDrawable = _bottomBar.background as GradientDrawable
val backgroundDrawable = bottomBar.background as GradientDrawable
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
backgroundDrawable.setColor(semiTransparentColor)
_bottomBar.background = backgroundDrawable
bottomBar.background = backgroundDrawable
}
val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("colorOverflow", false)
if (!colorOverflow) {
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
}
var doubleBackToExitPressedOnce = false
onBackPressedDispatcher.addCallback(this) {
@ -76,8 +68,7 @@ class NoInternet : AppCompatActivity() {
binding.root.doOnAttach {
initActivity(this)
uiSettings = loadData("ui_settings") ?: uiSettings
selectedOption = uiSettings.defaultStartUpTab
selectedOption = PrefManager.getVal(PrefName.DefaultStartUpTab)
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
@ -89,7 +80,7 @@ class NoInternet : AppCompatActivity() {
val mainViewPager = binding.viewpager
mainViewPager.isUserInputEnabled = false
mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
mainViewPager.setPageTransformer(ZoomOutPageTransformer())
navbar.setOnTabSelectListener(object :
AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(

View file

@ -18,7 +18,6 @@ import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityAuthorBinding
import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.getSerialized
import ani.dantotsu.px
import ani.dantotsu.statusBarHeight
@ -36,7 +35,7 @@ class AuthorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityAuthorBinding.inflate(layoutInflater)
setContentView(binding.root)

View file

@ -16,11 +16,10 @@ import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R
import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityListBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.user.ListViewPagerAdapter
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import com.google.android.material.tabs.TabLayout
@ -38,7 +37,7 @@ class CalendarActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityListBinding.inflate(layoutInflater)
@ -67,8 +66,7 @@ class CalendarActivity : AppCompatActivity() {
binding.listTitle.setTextColor(primaryTextColor)
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
if (!uiSettings.immersiveMode) {
if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) {
this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv)
binding.root.fitsSystemWindows = true

View file

@ -11,10 +11,8 @@ import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemCharacterBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.setAnimation
import ani.dantotsu.settings.UserInterfaceSettings
import java.io.Serializable
class CharacterAdapter(
@ -26,13 +24,10 @@ class CharacterAdapter(
return CharacterViewHolder(binding)
}
private val uiSettings =
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
val binding = holder.binding
setAnimation(binding.root.context, holder.binding.root, uiSettings)
setAnimation(binding.root.context, holder.binding.root)
val character = characterList[position]
binding.itemCompactRelation.text = character.role + " "
binding.itemCompactImage.loadImage(character.image)

View file

@ -17,14 +17,13 @@ import ani.dantotsu.R
import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityCharacterBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.getSerialized
import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import com.google.android.material.appbar.AppBarLayout
@ -38,22 +37,21 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
private val model: OtherDetailsViewModel by viewModels()
private lateinit var character: Character
private var loaded = false
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityCharacterBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
screenWidth = resources.displayMetrics.run { widthPixels / density }
if (uiSettings.immersiveMode) this.window.statusBarColor =
if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.status)
val banner =
if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen
banner.updateLayoutParams { height += statusBarHeight }
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
@ -136,16 +134,16 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
binding.characterCover.visibility =
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
if (percentage >= percent && !isCollapsed) {
isCollapsed = true
if (uiSettings.immersiveMode) this.window.statusBarColor =
if (immersiveMode) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg)
binding.characterAppBar.setBackgroundResource(R.color.nav_bg)
}
if (percentage <= percent && isCollapsed) {
isCollapsed = false
if (uiSettings.immersiveMode) this.window.statusBarColor =
if (immersiveMode) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.status)
binding.characterAppBar.setBackgroundResource(R.color.bg)
}

View file

@ -12,9 +12,9 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.GenresViewModel
import ani.dantotsu.databinding.ActivityGenreBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import kotlinx.coroutines.Dispatchers
@ -27,7 +27,7 @@ class GenreActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityGenreBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -54,7 +54,9 @@ class GenreActivity : AppCompatActivity() {
GridLayoutManager(this, (screenWidth / 156f).toInt())
lifecycleScope.launch(Dispatchers.IO) {
model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) {
model.loadGenres(
Anilist.genres ?: loadLocalGenres() ?: arrayListOf()
) {
MainScope().launch {
adapter.addGenre(it)
}
@ -62,4 +64,14 @@ class GenreActivity : AppCompatActivity() {
}
}
}
private fun loadLocalGenres(): ArrayList<String>? {
val genres = PrefManager.getVal<Set<String>>(PrefName.GenresList)
.toMutableList() as ArrayList<String>?
return if (genres.isNullOrEmpty()) {
null
} else {
genres
}
}
}

View file

@ -26,7 +26,8 @@ import ani.dantotsu.databinding.ItemMediaCompactBinding
import ani.dantotsu.databinding.ItemMediaLargeBinding
import ani.dantotsu.databinding.ItemMediaPageBinding
import ani.dantotsu.databinding.ItemMediaPageSmallBinding
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl
@ -44,9 +45,6 @@ class MediaAdaptor(
private val viewPager: ViewPager2? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val uiSettings =
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (type) {
0 -> MediaViewHolder(
@ -91,7 +89,7 @@ class MediaAdaptor(
when (type) {
0 -> {
val b = (holder as MediaViewHolder).binding
setAnimation(activity, b.root, uiSettings)
setAnimation(activity, b.root)
val media = mediaList?.getOrNull(position)
if (media != null) {
b.itemCompactImage.loadImage(media.cover)
@ -135,7 +133,7 @@ class MediaAdaptor(
1 -> {
val b = (holder as MediaLargeViewHolder).binding
setAnimation(activity, b.root, uiSettings)
setAnimation(activity, b.root)
val media = mediaList?.get(position)
if (media != null) {
b.itemCompactImage.loadImage(media.cover)
@ -178,16 +176,17 @@ class MediaAdaptor(
val b = (holder as MediaPageViewHolder).binding
val media = mediaList?.get(position)
if (media != null) {
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
b.itemCompactImage.loadImage(media.cover)
if (uiSettings.bannerAnimations)
if (bannerAnimations)
b.itemCompactBanner.setTransitionGenerator(
RandomTransitionGenerator(
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed)) as Float)).toLong(),
AccelerateDecelerateInterpolator()
)
)
val banner =
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
val context = b.itemCompactBanner.context
if (!(context as Activity).isDestroyed)
Glide.with(context as Context)
@ -234,16 +233,17 @@ class MediaAdaptor(
val b = (holder as MediaPageSmallViewHolder).binding
val media = mediaList?.get(position)
if (media != null) {
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
b.itemCompactImage.loadImage(media.cover)
if (uiSettings.bannerAnimations)
if (bannerAnimations)
b.itemCompactBanner.setTransitionGenerator(
RandomTransitionGenerator(
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(),
AccelerateDecelerateInterpolator()
)
)
val banner =
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
val context = b.itemCompactBanner.context
if (!(context as Activity).isDestroyed)
Glide.with(context as Context)
@ -396,10 +396,8 @@ class MediaAdaptor(
if (itemCompactImage != null) {
ActivityOptionsCompat.makeSceneTransitionAnimation(
activity,
Pair.create(
itemCompactImage,
ViewCompat.getTransitionName(activity.findViewById(R.id.itemCompactImage))!!
),
itemCompactImage,
ViewCompat.getTransitionName(itemCompactImage)!!
).toBundle()
} else {
null

View file

@ -2,7 +2,6 @@ package ani.dantotsu.media
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.SpannableStringBuilder
@ -35,7 +34,6 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.copyToClipboard
import ani.dantotsu.databinding.ActivityMediaBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.anime.AnimeWatchFragment
import ani.dantotsu.media.manga.MangaReadFragment
@ -44,8 +42,8 @@ import ani.dantotsu.navBarHeight
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.getSerialized
import ani.dantotsu.saveData
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
@ -65,7 +63,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
private val scope = lifecycleScope
private val model: MediaDetailsViewModel by viewModels()
private lateinit var tabLayout: NavigationBarView
private lateinit var uiSettings: UserInterfaceSettings
var selected = 0
var anime = true
private var adult = false
@ -90,7 +87,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
//Ui init
initActivity(this)
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
@ -111,20 +107,21 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
onBackPressedDispatcher.onBackPressed()
}
if (uiSettings.bannerAnimations) {
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
if (bannerAnimations) {
val adi = AccelerateDecelerateInterpolator()
val generator = RandomTransitionGenerator(
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
(10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(),
adi
)
binding.mediaBanner.setTransitionGenerator(generator)
}
val banner =
if (uiSettings.bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
val viewPager = binding.mediaViewPager
tabLayout = binding.mediaTab as NavigationBarView
viewPager.isUserInputEnabled = false
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
viewPager.setPageTransformer(ZoomOutPageTransformer())
val isDownload = intent.getBooleanExtra("download", false)
@ -141,7 +138,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
banner.loadImage(media.banner ?: media.cover, 400)
val gestureDetector = GestureDetector(this, object : GesturesListener() {
override fun onDoubleClick(event: MotionEvent) {
if (!uiSettings.bannerAnimations)
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean))
snackString(getString(R.string.enable_banner_animations))
else {
binding.mediaBanner.restart()
@ -159,11 +156,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
}
})
banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true }
if (this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
.getBoolean("incognito", false)) {
if (PrefManager.getVal(PrefName.Incognito)) {
binding.mediaTitle.text = " ${media.userPreferredName}"
binding.incognito.visibility = View.VISIBLE
}else {
} else {
binding.mediaTitle.text = media.userPreferredName
}
binding.mediaTitle.setOnLongClickListener {
@ -284,7 +280,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
} else snackString(getString(R.string.please_login_anilist))
}
binding.mediaAddToList.setOnLongClickListener {
saveData("${media.id}_progressDialog", true)
PrefManager.setCustomVal(
"${media.id}_progressDialog",
true,
)
snackString(getString(R.string.auto_update_reset))
true
}
@ -345,7 +344,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
viewPager.setCurrentItem(selected, false)
val sel = model.loadSelected(media, isDownload)
sel.window = selected
model.saveSelected(media.id, sel, this)
model.saveSelected(media.id, sel)
true
}
@ -354,7 +353,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
viewPager.setCurrentItem(selected, false)
if (model.continueMedia == null && media.cameFromContinue) {
model.continueMedia = loadData("continue_media") ?: true
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
selected = 1
}
@ -395,7 +394,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
}
override fun onResume() {
tabLayout.selectedItemId = idFromSelect()
if (this::tabLayout.isInitialized) {
tabLayout.selectedItemId = idFromSelect()
}
super.onResume()
}
@ -437,7 +438,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
binding.mediaCover.visibility =
if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE
val duration = (200 * uiSettings.animationSpeed).toLong()
val duration = (200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong()
val typedValue = TypedValue()
this@MediaDetailsActivity.theme.resolveAttribute(
com.google.android.material.R.attr.colorSecondary,
@ -467,7 +468,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
.start()
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f)
.setDuration(duration).start()
if (uiSettings.bannerAnimations) binding.mediaBanner.resume()
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.mediaBanner.resume()
}
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(
false
@ -491,10 +492,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
init {
enabled(true)
scope.launch {
delay(100) //TODO: a listener would be better
clicked()
}
image.setOnClickListener {
if (pressable && !disabled) {
pressable = false

View file

@ -1,7 +1,5 @@
package ani.dantotsu.media
import android.app.Activity
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.FragmentManager
@ -11,7 +9,6 @@ import androidx.lifecycle.ViewModel
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.logger
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.anime.SelectorDialogFragment
@ -28,35 +25,32 @@ import ani.dantotsu.parsers.NovelSources
import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.parsers.VideoExtractor
import ani.dantotsu.parsers.WatchSources
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.tryWithSuspend
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MediaDetailsViewModel : ViewModel() {
val scrolledToTop = MutableLiveData(true)
fun saveSelected(id: Int, data: Selected, activity: Activity? = null) {
saveData("$id-select", data, activity)
fun saveSelected(id: Int, data: Selected) {
PrefManager.setCustomVal("Selected-$id", data)
}
fun loadSelected(media: Media, isDownload: Boolean = false): Selected {
val sharedPreferences = Injekt.get<SharedPreferences>()
val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0)
}
it.preferDub = loadData("settings_prefer_dub") ?: false
saveSelected(media.id, it)
it
}
val data =
PrefManager.getNullableCustomVal("Selected-${media.id}", null, Selected::class.java)
?: Selected().let {
it.sourceIndex = 0
it.preferDub = PrefManager.getVal(PrefName.SettingsPreferDub)
saveSelected(media.id, it)
it
}
if (isDownload) {
data.sourceIndex = if (media.anime != null) {
AnimeSources.list.size - 1

View file

@ -2,7 +2,6 @@ package ani.dantotsu.media
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -26,6 +25,8 @@ import ani.dantotsu.*
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.GenresViewModel
import ani.dantotsu.databinding.*
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@ -60,7 +61,7 @@ class MediaInfoFragment : Fragment() {
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val model: MediaDetailsViewModel by activityViewModels()
val offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("offlineMode", false) || !isOnline(requireContext())
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
@ -94,8 +95,31 @@ class MediaInfoFragment : Fragment() {
binding.mediaInfoStart.text = media.startDate?.toString() ?: "??"
binding.mediaInfoEnd.text = media.endDate?.toString() ?: "??"
if (media.anime != null) {
binding.mediaInfoDuration.text =
if (media.anime.episodeDuration != null) media.anime.episodeDuration.toString() else "??"
val episodeDuration = media.anime.episodeDuration
binding.mediaInfoDuration.text = when {
episodeDuration != null -> {
val hours = episodeDuration / 60
val minutes = episodeDuration % 60
val formattedDuration = buildString {
if (hours > 0) {
append("$hours hour")
if (hours > 1) append("s")
}
if (minutes > 0) {
if (hours > 0) append(", ")
append("$minutes min")
if (minutes > 1) append("s")
}
}
formattedDuration
}
else -> "??"
}
binding.mediaInfoDurationContainer.visibility = View.VISIBLE
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
binding.mediaInfoSeason.text =
@ -464,7 +488,7 @@ class MediaInfoFragment : Fragment() {
parent.addView(bindi.root)
}
if (!media.recommendations.isNullOrEmpty() && !offline ) {
if (!media.recommendations.isNullOrEmpty() && !offline) {
val bind = ItemTitleRecyclerBinding.inflate(
LayoutInflater.from(context),
parent,

View file

@ -16,7 +16,8 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistSearch
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.databinding.ActivitySearchBinding
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.themes.ThemeManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -33,13 +34,14 @@ class SearchActivity : AppCompatActivity() {
private lateinit var mediaAdaptor: MediaAdaptor
private lateinit var progressAdapter: ProgressAdapter
private lateinit var concatAdapter: ConcatAdapter
private lateinit var headerAdaptor: SearchAdapter
lateinit var result: SearchResults
lateinit var updateChips: (() -> Unit)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivitySearchBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -51,7 +53,7 @@ class SearchActivity : AppCompatActivity() {
bottom = navBarHeight + 80f.px
)
style = loadData<Int>("searchStyle") ?: 0
style = PrefManager.getVal(PrefName.SearchStyle)
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
if (!listOnly!!) listOnly = null
@ -76,7 +78,7 @@ class SearchActivity : AppCompatActivity() {
progressAdapter = ProgressAdapter(searched = model.searched)
mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true)
val headerAdaptor = SearchAdapter(this)
headerAdaptor = SearchAdapter(this, model.searchResults.type)
val gridSize = (screenWidth / 120f).toInt()
val gridLayoutManager = GridLayoutManager(this, gridSize)
@ -154,9 +156,18 @@ class SearchActivity : AppCompatActivity() {
}
}
fun emptyMediaAdapter() {
searchTimer.cancel()
searchTimer.purge()
mediaAdaptor.notifyItemRangeRemoved(0, model.searchResults.results.size)
model.searchResults.results.clear()
progressAdapter.bar?.visibility = View.GONE
}
private var searchTimer = Timer()
private var loading = false
fun search() {
headerAdaptor.setHistoryVisibility(false)
val size = model.searchResults.results.size
model.searchResults.results.clear()
binding.searchRecyclerView.post {
@ -188,6 +199,7 @@ class SearchActivity : AppCompatActivity() {
var state: Parcelable? = null
override fun onPause() {
headerAdaptor.addHistory()
super.onPause()
state = binding.searchRecyclerView.layoutManager?.onSaveInstanceState()
}

View file

@ -1,7 +1,6 @@
package ani.dantotsu.media
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Editable
import android.text.TextWatcher
@ -9,6 +8,8 @@ import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
@ -19,19 +20,25 @@ import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
import ani.dantotsu.App.Companion.context
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemSearchHeaderBinding
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.google.android.material.checkbox.MaterialCheckBox.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SearchAdapter(private val activity: SearchActivity) :
class SearchAdapter(private val activity: SearchActivity, private val type: String) :
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
private val itemViewType = 6969
var search: Runnable? = null
var requestFocus: Runnable? = null
private var textWatcher: TextWatcher? = null
private lateinit var searchHistoryAdapter: SearchHistoryAdapter
private lateinit var binding: ItemSearchHeaderBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
val binding =
@ -41,8 +48,13 @@ class SearchAdapter(private val activity: SearchActivity) :
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
val binding = holder.binding
binding = holder.binding
searchHistoryAdapter = SearchHistoryAdapter(type) {
binding.searchBarText.setText(it)
}
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
binding.searchHistoryList.adapter = searchHistoryAdapter
val imm: InputMethodManager =
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
@ -60,8 +72,7 @@ class SearchAdapter(private val activity: SearchActivity) :
}
binding.searchBar.hint = activity.result.type
if (currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("incognito", false ) == true){
if (PrefManager.getVal(PrefName.Incognito)) {
val startIconDrawableRes = R.drawable.ic_incognito_24
val startIconDrawable: Drawable? =
context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) }
@ -103,7 +114,18 @@ class SearchAdapter(private val activity: SearchActivity) :
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
searchTitle()
if (s.toString().isBlank()) {
activity.emptyMediaAdapter()
CoroutineScope(Dispatchers.IO).launch {
delay(200)
activity.runOnUiThread {
setHistoryVisibility(true)
}
}
} else {
setHistoryVisibility(false)
searchTitle()
}
}
}
binding.searchBarText.addTextChangedListener(textWatcher)
@ -126,14 +148,14 @@ class SearchAdapter(private val activity: SearchActivity) :
it.alpha = 1f
binding.searchResultList.alpha = 0.33f
activity.style = 0
saveData("searchStyle", 0)
PrefManager.setVal(PrefName.SearchStyle, 0)
activity.recycler()
}
binding.searchResultList.setOnClickListener {
it.alpha = 1f
binding.searchResultGrid.alpha = 0.33f
activity.style = 1
saveData("searchStyle", 1)
PrefManager.setVal(PrefName.SearchStyle, 1)
activity.recycler()
}
@ -176,6 +198,40 @@ class SearchAdapter(private val activity: SearchActivity) :
requestFocus = Runnable { binding.searchBarText.requestFocus() }
}
fun setHistoryVisibility(visible: Boolean) {
if (visible) {
binding.searchResultLayout.startAnimation(fadeOutAnimation())
binding.searchHistoryList.startAnimation(fadeInAnimation())
binding.searchResultLayout.visibility = View.GONE
binding.searchHistoryList.visibility = View.VISIBLE
} else {
if (binding.searchResultLayout.visibility != View.VISIBLE) {
binding.searchResultLayout.startAnimation(fadeInAnimation())
binding.searchHistoryList.startAnimation(fadeOutAnimation())
}
binding.searchResultLayout.visibility = View.VISIBLE
binding.searchHistoryList.visibility = View.GONE
}
}
private fun fadeInAnimation(): Animation {
return AlphaAnimation(0f, 1f).apply {
duration = 150
fillAfter = true
}
}
private fun fadeOutAnimation(): Animation {
return AlphaAnimation(1f, 0f).apply {
duration = 150
fillAfter = true
}
}
fun addHistory() {
searchHistoryAdapter.add(binding.searchBarText.text.toString())
}
override fun getItemCount(): Int = 1

View file

@ -17,6 +17,7 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.BottomSheetSearchFilterBinding
import ani.dantotsu.databinding.ItemChipBinding
import com.google.android.material.chip.Chip
import java.util.Calendar
class SearchFilterBottomDialog : BottomSheetDialogFragment() {
private var _binding: BottomSheetSearchFilterBinding? = null
@ -103,7 +104,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
ArrayAdapter(
binding.root.context,
R.layout.item_dropdown,
(1970 until 2025).map { it.toString() }.reversed().toTypedArray()
(1970 until Calendar.getInstance().get(Calendar.YEAR) + 2).map { it.toString() }
.reversed().toTypedArray()
)
)
}

View file

@ -0,0 +1,100 @@
package ani.dantotsu.media
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemSearchHistoryBinding
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefManager.asLiveStringSet
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.SharedPreferenceStringSetLiveData
import java.util.Locale
class SearchHistoryAdapter(private val type: String, private val searchClicked: (String) -> Unit) :
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
DIFF_CALLBACK_INSTALLED
) {
private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null
private var searchHistory: MutableSet<String>? = null
private var historyType: PrefName = when (type.lowercase(Locale.ROOT)) {
"anime" -> PrefName.AnimeSearchHistory
"manga" -> PrefName.MangaSearchHistory
else -> throw IllegalArgumentException("Invalid type")
}
init {
searchHistoryLiveData =
PrefManager.getLiveVal(historyType, mutableSetOf<String>()).asLiveStringSet()
searchHistoryLiveData?.observeForever {
searchHistory = it.toMutableSet()
submitList(searchHistory?.toList())
}
}
fun remove(item: String) {
searchHistory?.remove(item)
PrefManager.setVal(historyType, searchHistory)
submitList(searchHistory?.toList())
}
fun add(item: String) {
if (searchHistory?.contains(item) == true || item.isBlank()) return
if (PrefManager.getVal(PrefName.Incognito)) return
searchHistory?.add(item)
submitList(searchHistory?.toList())
PrefManager.setVal(historyType, searchHistory)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SearchHistoryAdapter.SearchHistoryViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_search_history, parent, false)
return SearchHistoryViewHolder(view)
}
override fun onBindViewHolder(
holder: SearchHistoryAdapter.SearchHistoryViewHolder,
position: Int
) {
val item = getItem(position)
holder.binding.searchHistoryTextView.text = item
holder.binding.closeTextView.setOnClickListener {
val currentPosition = holder.bindingAdapterPosition
if (currentPosition >= itemCount || currentPosition < 0) return@setOnClickListener
remove(getItem(currentPosition))
}
holder.binding.searchHistoryTextView.setOnClickListener {
val currentPosition = holder.bindingAdapterPosition
if (currentPosition >= itemCount || currentPosition < 0) return@setOnClickListener
searchClicked(getItem(currentPosition))
}
}
inner class SearchHistoryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ItemSearchHistoryBinding.bind(view)
}
companion object {
val DIFF_CALLBACK_INSTALLED = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(
oldItem: String,
newItem: String
): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(
oldItem: String,
newItem: String
): Boolean {
return oldItem == newItem
}
}
}
}

View file

@ -18,7 +18,6 @@ import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityStudioBinding
import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.getSerialized
import ani.dantotsu.px
import ani.dantotsu.statusBarHeight
@ -36,7 +35,7 @@ class StudioActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityStudioBinding.inflate(layoutInflater)
setContentView(binding.root)

View file

@ -45,9 +45,18 @@ class SubtitleDownloader {
}
//actually downloads lol
suspend fun downloadSubtitle(context: Context, url: String, downloadedType: DownloadedType) {
suspend fun downloadSubtitle(
context: Context,
url: String,
downloadedType: DownloadedType
) {
try {
val directory = DownloadsManager.getDirectory(context, downloadedType.type, downloadedType.title, downloadedType.chapter)
val directory = DownloadsManager.getDirectory(
context,
downloadedType.type,
downloadedType.title,
downloadedType.chapter
)
if (!directory.exists()) { //just in case
directory.mkdirs()
}

View file

@ -1,5 +1,6 @@
package ani.dantotsu.media.anime
import java.util.Locale
import java.util.regex.Matcher
import java.util.regex.Pattern
@ -9,7 +10,57 @@ class AnimeNameAdapter {
"(episode|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*"
const val failedEpisodeNumberRegex =
"(?<!part\\s)\\b(\\d+)\\b"
const val seasonRegex = "\\s+(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
const val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
const val subdubRegex = "^(soft)?[\\s-]*(sub|dub|mixed)(bed|s)?\\s*$"
fun setSubDub(text: String, typeToSetTo: SubDubType): String? {
val subdubPattern: Pattern = Pattern.compile(subdubRegex, Pattern.CASE_INSENSITIVE)
val subdubMatcher: Matcher = subdubPattern.matcher(text)
return if (subdubMatcher.find()) {
val soft = subdubMatcher.group(1)
val subdub = subdubMatcher.group(2)
val bed = subdubMatcher.group(3) ?: ""
val toggled = when (typeToSetTo) {
SubDubType.SUB -> "sub"
SubDubType.DUB -> "dub"
SubDubType.NULL -> ""
}
val toggledCasePreserved =
if (subdub?.get(0)?.isUpperCase() == true || soft?.get(0)
?.isUpperCase() == true
) toggled.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.ROOT
) else it.toString()
} else toggled
subdubMatcher.replaceFirst(toggledCasePreserved + bed)
} else {
null
}
}
fun getSubDub(text: String): SubDubType {
val subdubPattern: Pattern = Pattern.compile(subdubRegex, Pattern.CASE_INSENSITIVE)
val subdubMatcher: Matcher = subdubPattern.matcher(text)
return if (subdubMatcher.find()) {
val subdub = subdubMatcher.group(2)?.lowercase(Locale.ROOT)
when (subdub) {
"sub" -> SubDubType.SUB
"dub" -> SubDubType.DUB
else -> SubDubType.NULL
}
} else {
SubDubType.NULL
}
}
enum class SubDubType {
SUB, DUB, NULL
}
fun findSeasonNumber(text: String): Int? {
val seasonPattern: Pattern = Pattern.compile(seasonRegex, Pattern.CASE_INSENSITIVE)

View file

@ -1,7 +1,6 @@
package ani.dantotsu.media.anime
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
@ -27,6 +26,8 @@ import ani.dantotsu.others.webview.CookieCatcher
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.DynamicAnimeParser
import ani.dantotsu.parsers.WatchSources
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import com.google.android.material.chip.Chip
@ -59,7 +60,7 @@ class AnimeWatchAdapter(
_binding = binding
//Youtube
if (media.anime!!.youtube != null && fragment.uiSettings.showYtButton) {
if (media.anime!!.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) {
binding.animeSourceYT.visibility = View.VISIBLE
binding.animeSourceYT.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))
@ -90,11 +91,9 @@ class AnimeWatchAdapter(
null
)
}
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
"Dantotsu",
Context.MODE_PRIVATE
val offline = if (!isOnline(binding.root.context) || PrefManager.getVal(
PrefName.OfflineMode
)
?.getBoolean("offlineMode", false) == true
) View.GONE else View.VISIBLE
binding.animeSourceNameContainer.visibility = offline
@ -113,7 +112,7 @@ class AnimeWatchAdapter(
binding.animeSourceTitle.text = showUserText
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
binding.animeSourceDubbedCont.visibility =
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
}
}
@ -133,7 +132,7 @@ class AnimeWatchAdapter(
binding.animeSourceDubbed.isChecked = selectDub
changing = false
binding.animeSourceDubbedCont.visibility =
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
source = i
setLanguageList(0, i)
}
@ -154,7 +153,7 @@ class AnimeWatchAdapter(
binding.animeSourceDubbed.isChecked = selectDub
changing = false
binding.animeSourceDubbedCont.visibility =
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
setLanguageList(i, source)
}
subscribeButton(false)
@ -199,7 +198,8 @@ class AnimeWatchAdapter(
var refresh = false
var run = false
var reversed = media.selected!!.recyclerReversed
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
var style =
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView)
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
dialogBinding.animeSourceTop.setOnClickListener {
@ -357,7 +357,9 @@ class AnimeWatchAdapter(
val episodes = media.anime.episodes!!.keys.toTypedArray()
val anilistEp = (media.userProgress ?: 0).plus(1)
val appEp = loadData<String>("${media.id}_current_ep")?.toIntOrNull() ?: 1
val appEp =
PrefManager.getCustomVal<String?>("${media.id}_current_ep", "")?.toIntOrNull()
?: 1
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
if (episodes.contains(continueEp)) {
@ -369,7 +371,10 @@ class AnimeWatchAdapter(
media.id,
continueEp
)
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > fragment.playerSettings.watchPercentage) {
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal<Float>(
PrefName.WatchPercentage
)
) {
val e = episodes.indexOf(continueEp)
if (e != -1 && e + 1 < episodes.size) {
continueEp = episodes[e + 1]
@ -396,7 +401,10 @@ class AnimeWatchAdapter(
fragment.onEpisodeClick(continueEp)
}
if (fragment.continueEp) {
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < fragment.playerSettings.watchPercentage) {
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal<Float>(
PrefName.WatchPercentage
)
) {
binding.animeSourceContinue.performClick()
fragment.continueEp = false
}

View file

@ -25,6 +25,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.*
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
@ -39,9 +40,9 @@ import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.HAnimeSources
import ani.dantotsu.settings.PlayerSettings
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications
import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
@ -84,8 +85,6 @@ class AnimeWatchFragment : Fragment() {
var continueEp: Boolean = false
var loaded = false
lateinit var playerSettings: PlayerSettings
lateinit var uiSettings: UserInterfaceSettings
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -118,12 +117,6 @@ class AnimeWatchFragment : Fragment() {
var maxGridSize = (screenWidth / 100f).roundToInt()
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
playerSettings =
loadData("player_settings", toast = false)
?: PlayerSettings().apply { saveData("player_settings", this) }
uiSettings = loadData("ui_settings", toast = false)
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize)
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
@ -144,6 +137,23 @@ class AnimeWatchFragment : Fragment() {
binding.animeSourceRecycler.layoutManager = gridLayoutManager
binding.ScrollTop.setOnClickListener {
binding.animeSourceRecycler.scrollToPosition(10)
binding.animeSourceRecycler.smoothScrollToPosition(0)
}
binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val position = gridLayoutManager.findFirstVisibleItemPosition()
if (position > 2) {
binding.ScrollTop.translationY = -navBarHeight.toFloat()
binding.ScrollTop.visibility = View.VISIBLE
} else {
binding.ScrollTop.visibility = View.GONE
}
}
})
model.scrolledToTop.observe(viewLifecycleOwner) {
if (it) binding.animeSourceRecycler.scrollToPosition(0)
}
@ -155,7 +165,7 @@ class AnimeWatchFragment : Fragment() {
media.selected = model.loadSelected(media)
subscribed =
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
SubscriptionHelper.getSubscriptions().containsKey(media.id)
style = media.selected!!.recyclerStyle
reverse = media.selected!!.recyclerReversed
@ -172,7 +182,7 @@ class AnimeWatchFragment : Fragment() {
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
episodeAdapter =
EpisodeAdapter(
style ?: uiSettings.animeDefaultView,
style ?: PrefManager.getVal(PrefName.AnimeDefaultView),
media,
this,
offlineMode = offlineMode
@ -273,7 +283,7 @@ class AnimeWatchFragment : Fragment() {
model.watchSources?.get(selected.sourceIndex)?.showUserTextListener = null
selected.sourceIndex = i
selected.server = null
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
return model.watchSources?.get(i)!!
}
@ -281,7 +291,7 @@ class AnimeWatchFragment : Fragment() {
fun onLangChange(i: Int) {
val selected = model.loadSelected(media)
selected.langIndex = i
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
}
@ -289,7 +299,7 @@ class AnimeWatchFragment : Fragment() {
val selected = model.loadSelected(media)
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
selected.preferDub = checked
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
lifecycleScope.launch(Dispatchers.IO) {
model.forceLoadEpisode(
@ -308,7 +318,7 @@ class AnimeWatchFragment : Fragment() {
reverse = rev
media.selected!!.recyclerStyle = style
media.selected!!.recyclerReversed = reverse
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
reload()
}
@ -316,7 +326,7 @@ class AnimeWatchFragment : Fragment() {
media.selected!!.chip = i
start = s
end = e
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
reload()
}
@ -364,12 +374,10 @@ class AnimeWatchFragment : Fragment() {
if (allSettings.size > 1) {
val names =
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
.setTitle("Select a Source")
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
selectedIndex = which
selectedSetting = allSettings[selectedIndex]
.setSingleChoiceItems(names, -1) { dialog, which ->
selectedSetting = allSettings[which]
itemSelected = true
dialog.dismiss()
@ -419,7 +427,7 @@ class AnimeWatchFragment : Fragment() {
fun onEpisodeClick(i: String) {
model.continueMedia = false
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
}
@ -458,17 +466,11 @@ class AnimeWatchFragment : Fragment() {
)
)
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
val id = requireContext().getSharedPreferences(
ContextCompat.getString(requireContext(), R.string.anime_downloads),
Context.MODE_PRIVATE
).getString(
val id = PrefManager.getAnimeDownloadPreferences().getString(
taskName,
""
) ?: ""
requireContext().getSharedPreferences(
ContextCompat.getString(requireContext(), R.string.anime_downloads),
Context.MODE_PRIVATE
).edit().remove(taskName).apply()
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
DownloadService.sendRemoveDownload(
requireContext(),
ExoplayerDownloadService::class.java,
@ -520,7 +522,7 @@ class AnimeWatchFragment : Fragment() {
selected.latest =
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
headerAdapter.handleEpisodes()
val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
episodeAdapter.offlineMode = isDownloaded
@ -536,7 +538,7 @@ class AnimeWatchFragment : Fragment() {
arr = (arr.reversed() as? ArrayList<Episode>) ?: arr
}
episodeAdapter.arr = arr
episodeAdapter.updateType(style ?: uiSettings.animeDefaultView)
episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView))
episodeAdapter.notifyItemRangeInserted(0, arr.size)
for (download in downloadManager.animeDownloadedTypes) {
if (download.title == media.mainName()) {

View file

@ -2,14 +2,12 @@ package ani.dantotsu.media.anime
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.LinearLayout
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.lifecycle.coroutineScope
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadIndex
@ -22,6 +20,7 @@ import ani.dantotsu.databinding.ItemEpisodeListBinding
import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.video.Helper
import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import kotlinx.coroutines.delay
@ -30,8 +29,8 @@ import kotlin.math.ln
import kotlin.math.pow
fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) {
val curr = loadData<Long>("${mediaId}_${ep}")
val max = loadData<Long>("${mediaId}_${ep}_max")
val curr = PrefManager.getNullableCustomVal("${mediaId}_${ep}", null, Long::class.java)
val max = PrefManager.getNullableCustomVal("${mediaId}_${ep}_max", null, Long::class.java)
if (curr != null && max != null) {
cont.visibility = View.VISIBLE
val div = curr.toFloat() / max.toFloat()
@ -110,7 +109,7 @@ class EpisodeAdapter(
when (holder) {
is EpisodeListViewHolder -> {
val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
val thumb =
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
@ -129,7 +128,7 @@ class EpisodeAdapter(
binding.itemEpisodeDesc.visibility =
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
binding.itemEpisodeDesc.text = ep.desc ?: ""
holder.bind(ep.number, ep.downloadProgress , ep.desc)
holder.bind(ep.number, ep.downloadProgress, ep.desc)
if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) {
@ -159,7 +158,7 @@ class EpisodeAdapter(
is EpisodeGridViewHolder -> {
val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
val thumb =
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
@ -202,7 +201,7 @@ class EpisodeAdapter(
is EpisodeCompactViewHolder -> {
val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
binding.itemEpisodeNumber.text = ep.number
binding.itemEpisodeFillerView.visibility =
if (ep.filler) View.VISIBLE else View.GONE
@ -253,10 +252,7 @@ class EpisodeAdapter(
media.mainName(),
episodeNumber
)
val id = fragment.requireContext().getSharedPreferences(
ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads),
Context.MODE_PRIVATE
).getString(
val id = PrefManager.getAnimeDownloadPreferences().getString(
taskName,
""
) ?: ""
@ -391,9 +387,10 @@ class EpisodeAdapter(
}, 1000)
} else {
binding.itemDownloadStatus.visibility = View.GONE
binding.itemEpisodeDesc.visibility = if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
binding.itemEpisodeDesc.visibility =
if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
// Show download icon
binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
binding.itemDownload.setImageResource(R.drawable.ic_download_24)
binding.itemDownload.rotation = 0f
}

View file

@ -65,6 +65,7 @@ import androidx.mediarouter.app.MediaRouteButton
import ani.dantotsu.*
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.connections.discord.Discord
import ani.dantotsu.connections.discord.DiscordService
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
@ -77,13 +78,12 @@ import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.others.AniSkip
import ani.dantotsu.others.AniSkip.getType
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.ResettableTimer
import ani.dantotsu.others.getSerialized
import ani.dantotsu.parsers.*
import ani.dantotsu.settings.PlayerSettings
import ani.dantotsu.settings.PlayerSettingsActivity
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.themes.ThemeManager
import com.bumptech.glide.Glide
import com.google.android.gms.cast.framework.CastButtonFactory
@ -91,7 +91,6 @@ import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.material.slider.Slider
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.lagradost.nicehttp.ignoreAllSSLErrors
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -99,6 +98,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.internal.immutableListOf
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.*
import java.util.concurrent.*
import kotlin.math.max
@ -132,7 +133,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private lateinit var exoSubtitle: ImageButton
private lateinit var exoSubtitleView: SubtitleView
private lateinit var exoRotate: ImageButton
private lateinit var exoQuality: ImageButton
private lateinit var exoSpeed: ImageButton
private lateinit var exoScreen: ImageButton
private lateinit var exoNext: ImageButton
@ -148,9 +148,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private lateinit var skipTimeText: TextView
private lateinit var timeStampText: TextView
private lateinit var animeTitle: TextView
private lateinit var videoName: TextView
private lateinit var videoInfo: TextView
private lateinit var serverInfo: TextView
private lateinit var episodeTitle: Spinner
private var orientationListener: OrientationEventListener? = null
@ -187,9 +185,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var pipEnabled = false
private var aspectRatio = Rational(16, 9)
var settings = PlayerSettings()
private var uiSettings = UserInterfaceSettings()
private val handler = Handler(Looper.getMainLooper())
val model: MediaDetailsViewModel by viewModels()
@ -243,8 +238,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
private fun setupSubFormatting(playerView: PlayerView, settings: PlayerSettings) {
val primaryColor = when (settings.primaryColor) {
private fun setupSubFormatting(playerView: PlayerView) {
val primaryColor = when (PrefManager.getVal<Int>(PrefName.PrimaryColor)) {
0 -> Color.BLACK
1 -> Color.DKGRAY
2 -> Color.GRAY
@ -259,7 +254,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
11 -> Color.TRANSPARENT
else -> Color.WHITE
}
val secondaryColor = when (settings.secondaryColor) {
val secondaryColor = when (PrefManager.getVal<Int>(PrefName.SecondaryColor)) {
0 -> Color.BLACK
1 -> Color.DKGRAY
2 -> Color.GRAY
@ -274,14 +269,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
11 -> Color.TRANSPARENT
else -> Color.BLACK
}
val outline = when (settings.outline) {
val outline = when (PrefManager.getVal<Int>(PrefName.Outline)) {
0 -> EDGE_TYPE_OUTLINE // Normal
1 -> EDGE_TYPE_DEPRESSED // Shine
2 -> EDGE_TYPE_DROP_SHADOW // Drop shadow
3 -> EDGE_TYPE_NONE // No outline
else -> EDGE_TYPE_OUTLINE // Normal
}
val subBackground = when (settings.subBackground) {
val subBackground = when (PrefManager.getVal<Int>(PrefName.SubBackground)) {
0 -> Color.TRANSPARENT
1 -> Color.BLACK
2 -> Color.DKGRAY
@ -296,7 +291,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
11 -> Color.MAGENTA
else -> Color.TRANSPARENT
}
val subWindow = when (settings.subWindow) {
val subWindow = when (PrefManager.getVal<Int>(PrefName.SubWindow)) {
0 -> Color.TRANSPARENT
1 -> Color.BLACK
2 -> Color.DKGRAY
@ -311,7 +306,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
11 -> Color.MAGENTA
else -> Color.TRANSPARENT
}
val font = when (settings.font) {
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)
@ -334,7 +329,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityExoplayerBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -357,21 +352,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
finishAndRemoveTask()
}
settings = loadData("player_settings") ?: PlayerSettings().apply {
saveData(
"player_settings",
this
)
}
uiSettings = loadData("ui_settings") ?: UserInterfaceSettings().apply {
saveData(
"ui_settings",
this
)
}
playerView = findViewById(R.id.player_view)
exoQuality = playerView.findViewById(R.id.exo_quality)
exoPlay = playerView.findViewById(androidx.media3.ui.R.id.exo_play)
exoSource = playerView.findViewById(R.id.exo_source)
exoSettings = playerView.findViewById(R.id.exo_settings)
@ -406,21 +387,23 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}, AUDIO_CONTENT_TYPE_MOVIE, AUDIOFOCUS_GAIN)
if (System.getInt(contentResolver, System.ACCELEROMETER_ROTATION, 0) != 1) {
orientationListener =
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) {
if (orientation in 45..135) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
View.VISIBLE
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
} else if (orientation in 225..315) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility =
View.VISIBLE
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
if (PrefManager.getVal(PrefName.RotationPlayer)) {
orientationListener =
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) {
if (orientation in 45..135) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
View.VISIBLE
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
} else if (orientation in 225..315) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility =
View.VISIBLE
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
}
}
orientationListener?.enable()
orientationListener?.enable()
}
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
exoRotate.setOnClickListener {
@ -429,14 +412,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
setupSubFormatting(playerView, settings)
setupSubFormatting(playerView)
playerView.subtitleView?.alpha = when (settings.subtitles) {
playerView.subtitleView?.alpha = when (PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
true -> 1f
false -> 0f
}
val fontSize = settings.fontSize.toFloat()
val fontSize = PrefManager.getVal<Int>(PrefName.FontSize).toFloat()
playerView.subtitleView?.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
if (savedInstanceState != null) {
@ -469,17 +452,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} else View.GONE
}
exoSkipOpEd.alpha = if (settings.autoSkipOPED) 1f else 0.3f
exoSkipOpEd.alpha = if (PrefManager.getVal(PrefName.AutoSkipOPED)) 1f else 0.3f
exoSkipOpEd.setOnClickListener {
settings.autoSkipOPED = if (settings.autoSkipOPED) {
if (PrefManager.getVal(PrefName.AutoSkipOPED)) {
snackString(getString(R.string.disabled_auto_skip))
false
PrefManager.setVal(PrefName.AutoSkipOPED, false)
} else {
snackString(getString(R.string.auto_skip))
true
PrefManager.setVal(PrefName.AutoSkipOPED, true)
}
saveData("player_settings", settings)
exoSkipOpEd.alpha = if (settings.autoSkipOPED) 1f else 0.3f
exoSkipOpEd.alpha = if (PrefManager.getVal(PrefName.AutoSkipOPED)) 1f else 0.3f
}
//Play Pause
@ -506,7 +488,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
// Picture-in-picture
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
pipEnabled =
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && settings.pip
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && PrefManager.getVal(
PrefName.Pip
)
if (pipEnabled) {
exoPip.visibility = View.VISIBLE
exoPip.setOnClickListener {
@ -539,14 +523,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
//Skip Time Button
if (settings.skipTime > 0) {
exoSkip.findViewById<TextView>(R.id.exo_skip_time).text = settings.skipTime.toString()
var skipTime = PrefManager.getVal<Int>(PrefName.SkipTime)
if (skipTime > 0) {
exoSkip.findViewById<TextView>(R.id.exo_skip_time).text = skipTime.toString()
exoSkip.setOnClickListener {
if (isInitialized)
exoPlayer.seekTo(exoPlayer.currentPosition + settings.skipTime * 1000)
exoPlayer.seekTo(exoPlayer.currentPosition + skipTime * 1000)
}
exoSkip.setOnLongClickListener {
val dialog = Dialog(this, R.style.DialogTheme)
val dialog = Dialog(this, R.style.MyPopup)
dialog.setContentView(R.layout.item_seekbar_dialog)
dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true)
@ -554,18 +539,19 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
if (settings.skipTime <= 120) {
dialog.findViewById<Slider>(R.id.seekbar).value = settings.skipTime.toFloat()
if (skipTime <= 120) {
dialog.findViewById<Slider>(R.id.seekbar).value = skipTime.toFloat()
} else {
dialog.findViewById<Slider>(R.id.seekbar).value = 120f
}
dialog.findViewById<Slider>(R.id.seekbar).addOnChangeListener { _, value, _ ->
settings.skipTime = value.toInt()
saveData(player, settings)
skipTime = value.toInt()
//saveData(player, settings)
PrefManager.setVal(PrefName.SkipTime, skipTime)
playerView.findViewById<TextView>(R.id.exo_skip_time).text =
settings.skipTime.toString()
skipTime.toString()
dialog.findViewById<TextView>(R.id.seekbar_value).text =
settings.skipTime.toString()
skipTime.toString()
}
dialog.findViewById<Slider>(R.id.seekbar)
.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
@ -577,7 +563,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
dialog.findViewById<TextView>(R.id.seekbar_title).text =
getString(R.string.skip_time)
dialog.findViewById<TextView>(R.id.seekbar_value).text =
settings.skipTime.toString()
skipTime.toString()
@Suppress("DEPRECATION")
dialog.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
dialog.show()
@ -587,7 +573,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoSkip.visibility = View.GONE
}
val gestureSpeed = (300 * uiSettings.animationSpeed).toLong()
val gestureSpeed = (300 * PrefManager.getVal<Float>(PrefName.AnimationSpeed)).toLong()
//Player UI Visibility Handler
val brightnessRunnable = Runnable {
if (exoBrightnessCont.alpha == 1f)
@ -617,7 +603,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
})
val overshoot = AnimationUtils.loadInterpolator(this, R.anim.over_shoot)
val controllerDuration = (uiSettings.animationSpeed * 200).toLong()
val controllerDuration = (300 * PrefManager.getVal<Float>(PrefName.AnimationSpeed)).toLong()
fun handleController() {
if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) !isInPictureInPictureMode else true) {
if (playerView.isControllerFullyVisible) {
@ -702,13 +688,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
var seekTimesR = 0
fun seek(forward: Boolean, event: MotionEvent? = null) {
val seekTime = PrefManager.getVal<Int>(PrefName.SeekTime)
val (card, text) = if (forward) {
forwardText.text = "+${settings.seekTime * ++seekTimesF}"
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + settings.seekTime * 1000) }
forwardText.text = "+${seekTime * ++seekTimesF}"
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) }
fastForwardCard to forwardText
} else {
rewindText.text = "-${settings.seekTime * ++seekTimesR}"
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - settings.seekTime * 1000) }
rewindText.text = "-${seekTime * ++seekTimesR}"
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) }
fastRewindCard to rewindText
}
@ -763,7 +750,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
if (!settings.doubleTap) {
if (!PrefManager.getVal<Boolean>(PrefName.DoubleTap)) {
playerView.findViewById<View>(R.id.exo_fast_forward_button_cont).visibility =
View.VISIBLE
playerView.findViewById<View>(R.id.exo_fast_rewind_button_cont).visibility =
@ -784,10 +771,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
keyMap[KEYCODE_DPAD_LEFT] = { seek(false) }
//Screen Gestures
if (settings.gestures || settings.doubleTap) {
if (PrefManager.getVal<Boolean>(PrefName.Gestures) || PrefManager.getVal<Boolean>(PrefName.DoubleTap)) {
fun doubleTap(forward: Boolean, event: MotionEvent) {
if (!locked && isInitialized && settings.doubleTap) {
if (!locked && isInitialized && PrefManager.getVal<Boolean>(PrefName.DoubleTap)) {
seek(forward, event)
}
}
@ -844,7 +831,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
isFastForwarding = true
exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2)
fastForward.visibility = View.VISIBLE
fastForward.text = ("${exoPlayer.playbackParameters.speed}x")
fastForward.text = "${exoPlayer.playbackParameters.speed}x"
}
fun stopFastForward() {
@ -858,7 +845,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//FastRewind (Left Panel)
val fastRewindDetector = GestureDetector(this, object : GesturesListener() {
override fun onLongClick(event: MotionEvent) {
if (settings.fastforward) fastForward()
if (PrefManager.getVal(PrefName.FastForward)) fastForward()
}
override fun onDoubleClick(event: MotionEvent) {
@ -866,7 +853,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
override fun onScrollYClick(y: Float) {
if (!locked && settings.gestures) {
if (!locked && PrefManager.getVal(PrefName.Gestures)) {
exoBrightness.value = clamp(exoBrightness.value + y / 100, 0f, 10f)
if (exoBrightnessCont.visibility != View.VISIBLE) {
exoBrightnessCont.visibility = View.VISIBLE
@ -890,7 +877,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//FastForward (Right Panel)
val fastForwardDetector = GestureDetector(this, object : GesturesListener() {
override fun onLongClick(event: MotionEvent) {
if (settings.fastforward) fastForward()
if (PrefManager.getVal(PrefName.FastForward)) fastForward()
}
override fun onDoubleClick(event: MotionEvent) {
@ -898,7 +885,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
override fun onScrollYClick(y: Float) {
if (!locked && settings.gestures) {
if (!locked && PrefManager.getVal(PrefName.Gestures)) {
exoVolume.value = clamp(exoVolume.value + y / 100, 0f, 10f)
if (exoVolumeCont.visibility != View.VISIBLE) {
exoVolumeCont.visibility = View.VISIBLE
@ -926,21 +913,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
title = media.userPreferredName
episodes = media.anime?.episodes ?: return startMainActivity(this)
videoName = playerView.findViewById(R.id.exo_video_name)
videoInfo = playerView.findViewById(R.id.exo_video_info)
serverInfo = playerView.findViewById(R.id.exo_server_info)
if (!settings.videoInfo) {
videoName.visibility = View.GONE
videoInfo.visibility = View.GONE
serverInfo.visibility = View.GONE
} else {
videoName.isSelected = true
}
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
serverInfo.text = model.watchSources!!.names.getOrNull(media.selected!!.sourceIndex)
?: model.watchSources!!.names[0]
model.epChanged.observe(this) {
epChanging = !it
@ -964,10 +939,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
fun change(index: Int) {
if (isInitialized) {
changingServer = false
saveData(
PrefManager.setCustomVal(
"${media.id}_${episodeArr[currentEpisodeIndex]}",
exoPlayer.currentPosition,
this
exoPlayer.currentPosition
)
exoPlayer.seekTo(0)
val prev = episodeArr[currentEpisodeIndex]
@ -1024,14 +998,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
currentEpisodeIndex = episodeArr.indexOf(ep.number)
episodeTitle.setSelection(currentEpisodeIndex)
if (isInitialized) releasePlayer()
playbackPosition = loadData("${media.id}_${ep.number}", this) ?: 0
playbackPosition = PrefManager.getCustomVal(
"${media.id}_${ep.number}",
0
)
initPlayer()
preloading = false
val context = this
val incognito = baseContext.getSharedPreferences("Dantotsu", MODE_PRIVATE)
.getBoolean("incognito", false)
if (isOnline(context) && Discord.token != null && !incognito) {
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 presence = RPC.createPresence(RPC.Companion.RPCData(
applicationId = Discord.application_Id,
@ -1075,7 +1051,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
//FullScreen
isFullscreen = loadData("${media.id}_fullscreenInt", this) ?: isFullscreen
isFullscreen = PrefManager.getCustomVal("${media.id}_fullscreenInt", isFullscreen)
playerView.resizeMode = when (isFullscreen) {
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
@ -1099,11 +1075,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
else -> "Original"
}
)
saveData("${media.id}_fullscreenInt", isFullscreen, this)
PrefManager.setCustomVal("${media.id}_fullscreenInt", isFullscreen)
}
//Cast
if (settings.cast) {
if (PrefManager.getVal(PrefName.Cast)) {
playerView.findViewById<MediaRouteButton>(R.id.exo_cast).apply {
visibility = View.VISIBLE
try {
@ -1121,10 +1097,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//Settings
exoSettings.setOnClickListener {
saveData(
PrefManager.setCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}",
exoPlayer.currentPosition,
this
exoPlayer.currentPosition
)
val intent = Intent(this, PlayerSettingsActivity::class.java).apply {
putExtra("subtitle", subtitle)
@ -1135,7 +1110,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//Speed
val speeds =
if (settings.cursedSpeeds)
if (PrefManager.getVal(PrefName.CursedSpeeds))
arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f)
else
arrayOf(
@ -1155,16 +1130,20 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
)
val speedsName = speeds.map { "${it}x" }.toTypedArray()
var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed
//var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed
var curSpeed = PrefManager.getCustomVal(
"${media.id}_speed",
PrefManager.getVal<Int>(PrefName.DefaultSpeed)
)
playbackParameters = PlaybackParameters(speeds[curSpeed])
var speed: Float
val speedDialog =
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.speed))
AlertDialog.Builder(this, R.style.MyPopup).setTitle(getString(R.string.speed))
exoSpeed.setOnClickListener {
val dialog = speedDialog.setSingleChoiceItems(speedsName, curSpeed) { dialog, i ->
if (isInitialized) {
saveData("${media.id}_speed", i, this)
PrefManager.setCustomVal("${media.id}_speed", i)
speed = speeds[i]
curSpeed = i
playbackParameters = PlaybackParameters(speed)
@ -1177,7 +1156,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
speedDialog.setOnCancelListener { hideSystemBars() }
if (settings.autoPlay) {
if (PrefManager.getVal(PrefName.AutoPlay)) {
var touchTimer = Timer()
fun touched() {
interacted = true
@ -1198,8 +1177,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
isFullscreen = settings.resize
playerView.resizeMode = when (settings.resize) {
isFullscreen = PrefManager.getVal(PrefName.Resize)
playerView.resizeMode = when (isFullscreen) {
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
@ -1207,39 +1186,52 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
preloading = false
val incognito = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("incognito", false) ?: false
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
val showProgressDialog =
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
?: true else false
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
if (PrefManager.getVal(PrefName.AskIndividualPlayer)) PrefManager.getCustomVal(
"${media.id}_ProgressDialog",
true
) else false
if (!incognito && showProgressDialog && Anilist.userid != null && if (media.isAdult) PrefManager.getVal(
PrefName.UpdateForHPlayer
) else true
) {
AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.auto_update, media.userPreferredName))
.apply {
if (incognito) {
setMessage(getString(R.string.incognito_will_not_update))
}
setOnCancelListener { hideSystemBars() }
setCancelable(false)
setPositiveButton(getString(R.string.yes)) { dialog, _ ->
saveData("${media.id}_progressDialog", false)
saveData("${media.id}_save_progress", true)
PrefManager.setCustomVal(
"${media.id}_ProgressDialog",
false
)
PrefManager.setCustomVal(
"${media.id}_save_progress",
true
)
dialog.dismiss()
model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke")
}
setNegativeButton(getString(R.string.no)) { dialog, _ ->
saveData("${media.id}_progressDialog", false)
saveData("${media.id}_save_progress", false)
PrefManager.setCustomVal(
"${media.id}_ProgressDialog",
false
)
PrefManager.setCustomVal(
"${media.id}_save_progress",
false
)
toast(getString(R.string.reset_auto_update))
dialog.dismiss()
model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke")
}
show()
}
else model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke")
} else model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke")
//Start the recursive Fun
if (settings.timeStampsEnabled)
if (PrefManager.getVal(PrefName.TimeStampsEnabled))
updateTimeStamp()
}
@ -1247,12 +1239,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private fun initPlayer() {
checkNotch()
saveData("${media.id}_current_ep", media.anime!!.selectedEpisode!!, this)
PrefManager.setCustomVal(
"${media.id}_current_ep",
media.anime!!.selectedEpisode!!
)
val set = loadData<MutableSet<Int>>("continue_ANIME", this) ?: mutableSetOf()
if (set.contains(media.id)) set.remove(media.id)
set.add(media.id)
saveData("continue_ANIME", set, this)
val list = PrefManager.getVal<Set<Int>>(PrefName.ContinuedAnime).toMutableList()
if (list.contains(media.id)) list.remove(media.id)
list.add(media.id)
PrefManager.setVal(PrefName.ContinuedAnime, list.toList())
lifecycleScope.launch(Dispatchers.IO) {
extractor?.onVideoStopped(video)
@ -1263,7 +1258,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
video = ext.videos.getOrNull(episode.selectedVideo) ?: return
subtitle = intent.getSerialized("subtitle")
?: when (val subLang: String? = loadData("subLang_${media.id}", this)) {
?: when (val subLang: String? =
PrefManager.getCustomVal("subLang_${media.id}", null as String?)) {
null -> {
when (episode.selectedSubtitle) {
null -> null
@ -1305,7 +1301,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
println("sub: $sub")
} else {
val subUri = Uri.parse((subtitle!!.file.url))
val subUri = Uri.parse(subtitle!!.file.url)
sub = MediaItem.SubtitleConfiguration
.Builder(subUri)
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
@ -1342,7 +1338,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
dataSource
}
val dafuckDataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString()))
val dafuckDataSourceFactory =
DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString()))
cacheFactory = CacheDataSource.Factory().apply {
setCache(Helper.getSimpleCache(this@ExoplayerView))
if (ext.server.offline) {
@ -1361,7 +1358,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val downloadedMediaItem = if (ext.server.offline) {
val key = ext.server.name
downloadId = getSharedPreferences(getString(R.string.anime_downloads), MODE_PRIVATE)
downloadId = PrefManager.getAnimeDownloadPreferences()
.getString(key, null)
if (downloadId != null) {
Helper.downloadManager(this)
@ -1413,16 +1410,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
.setRendererDisabled(TRACK_TYPE_VIDEO, false)
.setRendererDisabled(C.TRACK_TYPE_AUDIO, false)
.setRendererDisabled(C.TRACK_TYPE_TEXT, false)
.setMinVideoSize(
loadData("maxWidth", this) ?: 720,
loadData("maxHeight", this) ?: 480
)
.setMaxVideoSize(1, 1)
//.setOverrideForType(
// TrackSelectionOverride(trackSelector, 2))
)
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
if (playbackPosition != 0L && !changingServer && !PrefManager.getVal<Boolean>(PrefName.AlwaysContinue)) {
val time = String.format(
"%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(playbackPosition),
TimeUnit.MILLISECONDS.toMinutes(playbackPosition) - TimeUnit.HOURS.toMinutes(
@ -1436,7 +1429,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
)
)
)
val dialog = AlertDialog.Builder(this, R.style.DialogTheme)
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.continue_from, time)).apply {
setCancelable(false)
setPositiveButton(getString(R.string.yes)) { d, _ ->
@ -1464,9 +1457,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
this.playbackParameters = this@ExoplayerView.playbackParameters
setMediaItem(mediaItem)
prepare()
loadData<Long>("${media.id}_${media.anime!!.selectedEpisode}_max")?.apply {
if (this <= playbackPosition) playbackPosition = max(0, this - 5)
}
PrefManager.getCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}_max",
Long.MAX_VALUE
)
.takeIf { it != Long.MAX_VALUE }
?.let { if (it <= playbackPosition) playbackPosition = max(0, it - 5) }
seekTo(playbackPosition)
}
playerView.player = exoPlayer
@ -1539,8 +1535,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
changingServer = true
media.selected!!.server = null
saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this)
model.saveSelected(media.id, media.selected!!, this)
PrefManager.setCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition
)
model.saveSelected(media.id, media.selected!!)
model.onEpisodeClick(
media, episode.number, this.supportFragmentManager,
launch = false
@ -1548,8 +1546,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
private fun subClick() {
saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this)
model.saveSelected(media.id, media.selected!!, this)
PrefManager.setCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition
)
model.saveSelected(media.id, media.selected!!)
SubtitleDialogFragment().show(supportFragmentManager, "dialog")
}
@ -1561,10 +1561,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
playerView.player?.pause()
}
if (exoPlayer.currentPosition > 5000) {
saveData(
PrefManager.setCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}",
exoPlayer.currentPosition,
this
exoPlayer.currentPosition
)
}
}
@ -1572,7 +1571,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
override fun onResume() {
super.onResume()
LangSet.setLocale(this)
orientationListener?.enable()
hideSystemBars()
if (isInitialized) {
@ -1590,7 +1588,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var wasPlaying = false
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (settings.focusPause && !epChanging) {
if (PrefManager.getVal(PrefName.FocusPause) && !epChanging) {
if (isInitialized && !hasFocus) wasPlaying = exoPlayer.isPlaying
if (hasFocus) {
if (isInitialized && wasPlaying) exoPlayer.play()
@ -1614,19 +1612,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
saveData("${media.id}_${media.anime!!.selectedEpisode}_max", exoPlayer.duration, this)
PrefManager.setCustomVal(
"${media.id}_${media.anime!!.selectedEpisode}_max",
exoPlayer.duration
)
val height = (exoPlayer.videoFormat ?: return).height
val width = (exoPlayer.videoFormat ?: return).width
if (video?.format != VideoType.CONTAINER) {
saveData("maxHeight", height)
saveData("maxWidth", width)
}
aspectRatio = Rational(width, height)
videoName.text = episode.selectedExtractor
videoInfo.text = "$width x $height"
videoInfo.text = "Quality: ${height}p"
if (exoPlayer.duration < playbackPosition)
exoPlayer.seekTo(0)
@ -1637,14 +1632,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.seekTo(0)
}
if (!isTimeStampsLoaded && settings.timeStampsEnabled) {
if (!isTimeStampsLoaded && PrefManager.getVal(PrefName.TimeStampsEnabled)) {
val dur = exoPlayer.duration
lifecycleScope.launch(Dispatchers.IO) {
model.loadTimeStamps(
media.idMAL,
media.anime?.selectedEpisode?.trim()?.toIntOrNull(),
dur / 1000,
settings.useProxyForTimeStamps
PrefManager.getVal(PrefName.UseProxyForTimeStamps)
)
}
}
@ -1654,7 +1649,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var preloading = false
private fun updateProgress() {
if (isInitialized) {
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) {
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > PrefManager.getVal<Float>(
PrefName.WatchPercentage
)
) {
preloading = true
nextEpisode(false) { i ->
val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode
@ -1685,7 +1683,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val new = currentTimeStamp
timeStampText.text = if (new != null) {
if (settings.showTimeStampButton) {
if (PrefManager.getVal(PrefName.ShowTimeStampButton)) {
skipTimeButton.visibility = View.VISIBLE
exoSkip.visibility = View.GONE
skipTimeText.text = new.skipType.getType()
@ -1693,7 +1691,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
}
}
if (settings.autoSkipOPED && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains(
if (PrefManager.getVal(PrefName.AutoSkipOPED) && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains(
new
)
) {
@ -1703,7 +1701,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
new.skipType.getType()
} else {
skipTimeButton.visibility = View.GONE
if (settings.skipTime > 0) exoSkip.visibility = View.VISIBLE
if (PrefManager.getVal<Int>(PrefName.SkipTime) > 0) exoSkip.visibility =
View.VISIBLE
""
}
}
@ -1736,13 +1735,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
println("Track: ${tracks.groups.size}")
if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE
else {
exoQuality.visibility = View.VISIBLE
exoQuality.setOnClickListener {
initPopupQuality().show()
}
}
}
override fun onPlayerError(error: PlaybackException) {
@ -1757,7 +1749,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
else
-> {
toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}")
FirebaseCrashlytics.getInstance().recordException(error)
Injekt.get<CrashlyticsInterface>().logException(error)
}
}
}
@ -1772,7 +1764,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
}
isBuffering = playbackState == Player.STATE_BUFFERING
if (playbackState == Player.STATE_ENDED && settings.autoPlay) {
if (playbackState == Player.STATE_ENDED && PrefManager.getVal(PrefName.AutoPlay)) {
if (interacted) exoNext.performClick()
else toast(getString(R.string.autoplay_cancelled))
}
@ -1780,8 +1772,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
private fun updateAniProgress() {
if (exoPlayer.currentPosition / episodeLength > settings.watchPercentage && Anilist.userid != null)
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true) {
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
if (!incognito && exoPlayer.currentPosition / episodeLength > PrefManager.getVal<Float>(
PrefName.WatchPercentage
) && Anilist.userid != null
)
if (PrefManager.getCustomVal(
"${media.id}_save_progress",
true
) && (if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHPlayer) else true)
) {
media.anime!!.selectedEpisode?.apply {
updateProgress(media, this)
}
@ -1794,7 +1794,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
while (isFiller) {
if (episodeArr.size > currentEpisodeIndex + i) {
isFiller =
if (settings.autoSkipFiller) episodes[episodeArr[currentEpisodeIndex + i]]?.filler
if (PrefManager.getVal(PrefName.AutoSkipFiller)) episodes[episodeArr[currentEpisodeIndex + i]]?.filler
?: false else false
if (!isFiller) runnable.invoke(i)
i++
@ -1830,20 +1830,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
finishAndRemoveTask()
}
// QUALITY SELECTOR
private fun initPopupQuality(): Dialog {
val trackSelectionDialogBuilder =
TrackSelectionDialogBuilder(this, "Available Qualities", exoPlayer, TRACK_TYPE_VIDEO)
trackSelectionDialogBuilder.setTheme(R.style.DialogTheme)
trackSelectionDialogBuilder.setTrackNameProvider {
if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)"
}
val trackDialog = trackSelectionDialogBuilder.build()
trackDialog.setOnDismissListener { hideSystemBars() }
return trackDialog
}
// Cast
private fun cast() {
val videoURL = video?.file?.url ?: return
@ -1905,7 +1891,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
orientationListener?.enable()
}
if (isInitialized) {
saveData("${media.id}_${episode.number}", exoPlayer.currentPosition, this)
PrefManager.setCustomVal(
"${media.id}_${episode.number}",
exoPlayer.currentPosition
)
if (wasPlaying) exoPlayer.play()
}
}
@ -1988,7 +1977,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}
override fun onCastSessionAvailable() {
if (isCastApiAvailable) {
if (isCastApiAvailable && !this.isDestroyed) {
startCastPlayer()
}
}

View file

@ -8,7 +8,6 @@ import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.TypedValue
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -18,6 +17,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.BottomSheetSelectorBinding
import ani.dantotsu.databinding.ItemStreamBinding
import ani.dantotsu.databinding.ItemUrlBinding
@ -28,11 +28,14 @@ import ani.dantotsu.others.Download.download
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.VideoExtractor
import ani.dantotsu.parsers.VideoType
import com.google.firebase.crashlytics.FirebaseCrashlytics
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.DecimalFormat
@ -93,7 +96,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
binding.selectorAutoText.text = selected
binding.selectorCancel.setOnClickListener {
media!!.selected!!.server = null
model.saveSelected(media!!.id, media!!.selected!!, requireActivity())
model.saveSelected(media!!.id, media!!.selected!!)
tryWith {
dismiss()
}
@ -142,11 +145,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
}
binding.selectorRecyclerView.adapter = null
binding.selectorProgressBar.visibility = View.VISIBLE
makeDefault = loadData("make_default") ?: true
makeDefault = PrefManager.getVal(PrefName.MakeDefault)
binding.selectorMakeDefault.isChecked = makeDefault
binding.selectorMakeDefault.setOnClickListener {
makeDefault = binding.selectorMakeDefault.isChecked
saveData("make_default", makeDefault)
PrefManager.setVal(PrefName.MakeDefault, makeDefault)
}
binding.selectorRecyclerView.layoutManager =
LinearLayoutManager(
@ -265,7 +268,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = 0
startExoplayer(media!!)
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
}
}
@ -300,96 +303,88 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
extractor.server.name
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
position
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
val selectedVideo =
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
val subtitles = extractor.subtitles
val subtitleNames = subtitles.map { it.language }
var subtitleToDownload: Subtitle? = null
if (subtitles.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle("Download Subtitle")
.setSingleChoiceItems(
subtitleNames.toTypedArray(),
-1
) { dialog, which ->
subtitleToDownload = subtitles[which]
}
.setPositiveButton("Download") { _, _ ->
dialog?.dismiss()
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
currActivity()!!,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
} else {
snackString("No Video Selected")
}
}
.setNegativeButton("Skip") { dialog, _ ->
subtitleToDownload = null
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
currActivity()!!,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
} else {
snackString("No Video Selected")
}
dialog.dismiss()
}
.setNeutralButton("Cancel") { dialog, _ ->
subtitleToDownload = null
dialog.dismiss()
}
.show()
alertDialog.window?.setDimAmount(0.8f)
} else {
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
requireActivity(),
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
} else {
snackString("No Video Selected")
}
}
dismiss()
}
binding.urlDownload.setOnLongClickListener {
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
if ((loadData<Int>("settings_download_manager") ?: 0) != 0) {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor =
extractor.server.name
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
position
if ((PrefManager.getVal(PrefName.DownloadManager) as Int) != 0) {
download(
requireActivity(),
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!,
media!!.userPreferredName
)
} else {
snackString("No Download Manager Selected")
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
val selectedVideo =
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
val subtitles = extractor.subtitles
val subtitleNames = subtitles.map { it.language }
var subtitleToDownload: Subtitle? = null
if (subtitles.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle("Download Subtitle")
.setSingleChoiceItems(
subtitleNames.toTypedArray(),
-1
) { dialog, which ->
subtitleToDownload = subtitles[which]
}
.setPositiveButton("Download") { _, _ ->
dialog?.dismiss()
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
currActivity()!!,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number)
} else {
snackString("No Video Selected")
}
}
.setNegativeButton("Skip") { dialog, _ ->
subtitleToDownload = null
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
currActivity()!!,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number)
} else {
snackString("No Video Selected")
}
dialog.dismiss()
}
.setNeutralButton("Cancel") { dialog, _ ->
subtitleToDownload = null
dialog.dismiss()
}
.show()
alertDialog.window?.setDimAmount(0.8f)
} else {
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
requireActivity(),
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number)
} else {
snackString("No Video Selected")
}
}
}
true
dismiss()
}
if (video.format == VideoType.CONTAINER) {
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
@ -404,6 +399,13 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
binding.urlQuality.text = extractor.server.name
}
private fun broadcastDownloadStarted(episodeNumber: String) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
}
requireActivity().sendBroadcast(intent)
}
override fun getItemCount(): Int = extractor.videos.size
private inner class UrlViewHolder(val binding: ItemUrlBinding) :
@ -422,7 +424,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
if (makeDefault) {
media!!.selected!!.server = extractor.server.name
media!!.selected!!.video = bindingAdapterPosition
model.saveSelected(media!!.id, media!!.selected!!, requireActivity())
model.saveSelected(media!!.id, media!!.selected!!)
}
startExoplayer(media!!)
}

View file

@ -15,10 +15,9 @@ import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetSubtitlesBinding
import ani.dantotsu.databinding.ItemSubtitleTextBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
class SubtitleDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetSubtitlesBinding? = null
@ -69,7 +68,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
binding.subtitleTitle.setText(R.string.none)
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
val selSubs: String? = loadData("subLang_${mediaID}", activity)
val selSubs = PrefManager.getCustomVal<String?>("subLang_${mediaID}", null)
if (episode.selectedSubtitle != null && selSubs != "None") {
binding.root.setCardBackgroundColor(TRANSPARENT)
}
@ -79,7 +78,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
model.setEpisode(episode, "Subtitle")
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
saveData("subLang_${mediaID}", "None", activity)
PrefManager.setCustomVal("subLang_${mediaID}", "None")
}
dismiss()
}
@ -108,7 +107,8 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
}
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
val selSubs: String? = loadData("subLang_${mediaID}", activity)
val selSubs: String? =
PrefManager.getCustomVal<String?>("subLang_${mediaID}", null)
if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) {
binding.root.setCardBackgroundColor(TRANSPARENT)
}
@ -119,7 +119,10 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
model.setEpisode(episode, "Subtitle")
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
saveData("subLang_${mediaID}", subtitles[position - 1].language, activity)
PrefManager.setCustomVal(
"subLang_${mediaID}",
subtitles[position - 1].language
)
}
dismiss()
}

View file

@ -166,7 +166,7 @@ class MangaChapterAdapter(
}, 1000)
} else {
// Show download icon
binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
binding.itemDownload.setImageResource(R.drawable.ic_download_24)
binding.itemDownload.rotation = 0f
}
@ -261,7 +261,7 @@ class MangaChapterAdapter(
when (holder) {
is ChapterCompactViewHolder -> {
val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
val ep = arr[position]
val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt()
binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: ep.number
@ -287,7 +287,7 @@ class MangaChapterAdapter(
val binding = holder.binding
val ep = arr[position]
holder.bind(ep.number, ep.progress)
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
binding.itemChapterNumber.text = ep.number
if (ep.progress.isNullOrEmpty()) {
binding.itemChapterTitle.visibility = View.GONE

View file

@ -2,7 +2,6 @@ package ani.dantotsu.media.manga
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
@ -28,6 +27,8 @@ import ani.dantotsu.others.webview.CookieCatcher
import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.MangaReadSources
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import com.google.android.material.chip.Chip
@ -69,12 +70,9 @@ class MangaReadAdapter(
null
)
}
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
"Dantotsu",
Context.MODE_PRIVATE
)
?.getBoolean("offlineMode", false) == true
) View.GONE else View.VISIBLE
val offline =
if (!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
) View.GONE else View.VISIBLE
binding.animeSourceNameContainer.visibility = offline
binding.animeSourceSettings.visibility = offline
@ -163,7 +161,8 @@ class MangaReadAdapter(
var refresh = false
var run = false
var reversed = media.selected!!.recyclerReversed
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.mangaDefaultView
var style =
media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView)
dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
dialogBinding.animeSourceTop.setOnClickListener {
@ -392,7 +391,8 @@ class MangaReadAdapter(
if (media.manga?.chapters != null) {
val chapters = media.manga.chapters!!.keys.toTypedArray()
val anilistEp = (media.userProgress ?: 0).plus(1)
val appEp = loadData<String>("${media.id}_current_chp")?.toIntOrNull() ?: 1
val appEp = PrefManager.getCustomVal<String?>("${media.id}_current_chp", null)
?.toIntOrNull() ?: 1
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
val filteredChapters = chapters.filter { chapterKey ->
val chapter = media.manga.chapters!![chapterKey]!!

View file

@ -26,6 +26,7 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.*
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
@ -42,8 +43,9 @@ import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaParser
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications
import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
@ -86,9 +88,6 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
var continueEp: Boolean = false
var loaded = false
val uiSettings = loadData("ui_settings", toast = false)
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -139,6 +138,23 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
binding.animeSourceRecycler.layoutManager = gridLayoutManager
binding.ScrollTop.setOnClickListener {
binding.animeSourceRecycler.scrollToPosition(10)
binding.animeSourceRecycler.smoothScrollToPosition(0)
}
binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val position = gridLayoutManager.findFirstVisibleItemPosition()
if (position > 2) {
binding.ScrollTop.translationY = -navBarHeight.toFloat()
binding.ScrollTop.visibility = View.VISIBLE
} else {
binding.ScrollTop.visibility = View.GONE
}
}
})
model.scrolledToTop.observe(viewLifecycleOwner) {
if (it) binding.animeSourceRecycler.scrollToPosition(0)
}
@ -154,7 +170,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
media.selected = model.loadSelected(media)
subscribed =
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
SubscriptionHelper.getSubscriptions().containsKey(media.id)
style = media.selected!!.recyclerStyle
reverse = media.selected!!.recyclerReversed
@ -165,10 +181,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
headerAdapter.scanlatorSelectionListener = this
chapterAdapter =
MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
MangaChapterAdapter(
style ?: PrefManager.getVal(PrefName.MangaDefaultView), media, this
)
for (download in downloadManager.mangaDownloadedTypes) {
chapterAdapter.stopDownload(download.chapter)
if (download.title == media.mainName()) {
chapterAdapter.stopDownload(download.chapter)
}
}
binding.animeSourceRecycler.adapter =
@ -284,7 +304,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
model.mangaReadSources?.get(selected.sourceIndex)?.showUserTextListener = null
selected.sourceIndex = i
selected.server = null
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
return model.mangaReadSources?.get(i)!!
}
@ -292,14 +312,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
fun onLangChange(i: Int) {
val selected = model.loadSelected(media)
selected.langIndex = i
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
}
fun onScanlatorChange(list: List<String>) {
val selected = model.loadSelected(media)
selected.scanlators = list
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
}
@ -312,7 +332,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
reverse = rev
media.selected!!.recyclerStyle = style
media.selected!!.recyclerReversed = reverse
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
reload()
}
@ -320,7 +340,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
media.selected!!.chip = i
start = s
end = e
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
reload()
}
@ -368,12 +388,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
if (allSettings.size > 1) {
val names =
allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
.setTitle("Select a Source")
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
selectedIndex = which
selectedSetting = allSettings[selectedIndex]
.setSingleChoiceItems(names, -1) { dialog, which ->
selectedSetting = allSettings[which]
itemSelected = true
dialog.dismiss()
@ -420,7 +438,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
model.continueMedia = false
media.manga?.chapters?.get(i)?.let {
media.manga?.selectedChapter = i
model.saveSelected(media.id, media.selected!!, requireActivity())
model.saveSelected(media.id, media.selected!!)
ChapterLoaderDialog.newInstance(it, true)
.show(requireActivity().supportFragmentManager, "dialog")
}
@ -558,7 +576,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
selected.latest =
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
headerAdapter.handleChapters()
chapterAdapter.notifyItemRangeRemoved(0, chapterAdapter.arr.size)
var arr: ArrayList<MangaChapter> = arrayListOf()
@ -572,7 +590,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
arr = (arr.reversed() as? ArrayList<MangaChapter>) ?: arr
}
chapterAdapter.arr = arr
chapterAdapter.updateType(style ?: uiSettings.mangaDefaultView)
chapterAdapter.updateType(style ?: PrefManager.getVal(PrefName.MangaDefaultView))
chapterAdapter.notifyItemRangeInserted(0, arr.size)
}

View file

@ -33,8 +33,7 @@ abstract class BaseImageAdapter(
val activity: MangaReaderActivity,
chapter: MangaChapter
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val settings = activity.settings.default
val uiSettings = activity.uiSettings
val settings = activity.defaultSettings
val images = chapter.images()
@SuppressLint("ClickableViewAccessibility")

View file

@ -14,6 +14,8 @@ import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT
import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@ -83,7 +85,7 @@ open class ImageAdapter(
imageView.minScale = scale
ObjectAnimator.ofFloat(parent, "alpha", 0f, 1f)
.setDuration((400 * uiSettings.animationSpeed).toLong())
.setDuration((400 * PrefManager.getVal<Float>(PrefName.AnimationSpeed)).toLong())
.start()
progress.visibility = View.GONE

View file

@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.*
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.connections.discord.Discord
import ani.dantotsu.connections.discord.DiscordService
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
@ -41,27 +42,29 @@ import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.manga.MangaNameAdapter
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaImage
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.CurrentReaderSettings
import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon
import ani.dantotsu.settings.CurrentReaderSettings.Directions.*
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.*
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
import ani.dantotsu.settings.ReaderSettings
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.themes.ThemeManager
import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates
@ -74,6 +77,8 @@ class MangaReaderActivity : AppCompatActivity() {
private val model: MediaDetailsViewModel by viewModels()
private val scope = lifecycleScope
var defaultSettings = CurrentReaderSettings()
private lateinit var media: Media
private lateinit var chapter: MangaChapter
private lateinit var chapters: MutableMap<String, MangaChapter>
@ -84,13 +89,9 @@ class MangaReaderActivity : AppCompatActivity() {
private var isContVisible = false
private var showProgressDialog = true
//private var progressDialog: AlertDialog.Builder? = null
private var maxChapterPage = 0L
private var currentChapterPage = 0L
lateinit var settings: ReaderSettings
lateinit var uiSettings: UserInterfaceSettings
private var notchHeight: Int? = null
private var imageAdapter: BaseImageAdapter? = null
@ -98,10 +99,8 @@ class MangaReaderActivity : AppCompatActivity() {
var sliding = false
var isAnimating = false
private var rpc: RPC? = null
override fun onAttachedToWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !settings.showSystemBars) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
val displayCutout = window.decorView.rootWindowInsets.displayCutout
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
@ -123,7 +122,7 @@ class MangaReaderActivity : AppCompatActivity() {
}
private fun hideBars() {
if (!settings.showSystemBars) hideSystemBars()
if (PrefManager.getVal(PrefName.ShowSystemBars)) hideSystemBars()
}
override fun onDestroy() {
@ -138,28 +137,20 @@ class MangaReaderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.mangaReaderBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
defaultSettings = loadReaderSettings("reader_settings") ?: defaultSettings
onBackPressedDispatcher.addCallback(this) {
progress { finish() }
}
settings = loadData("reader_settings", this)
?: ReaderSettings().apply { saveData("reader_settings", this) }
uiSettings = loadData("ui_settings", this) ?: UserInterfaceSettings().apply {
saveData(
"ui_settings",
this
)
}
controllerDuration = (uiSettings.animationSpeed * 200).toLong()
controllerDuration = (PrefManager.getVal<Float>(PrefName.AnimationSpeed) * 200).toLong()
hideBars()
@ -182,7 +173,7 @@ class MangaReaderActivity : AppCompatActivity() {
binding.mangaReaderSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
sliding = true
if (settings.default.layout != PAGED)
if (defaultSettings.layout != PAGED)
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 }
?: 1))
else
@ -205,34 +196,30 @@ class MangaReaderActivity : AppCompatActivity() {
else model.getMedia().value ?: return
model.setMedia(media)
if (settings.autoDetectWebtoon && media.countryOfOrigin != "JP") applyWebtoon(settings.default)
settings.default = loadData("${media.id}_current_settings") ?: settings.default
if (PrefManager.getVal(PrefName.AutoDetectWebtoon) && media.countryOfOrigin != "JP") applyWebtoon(
defaultSettings
)
defaultSettings = loadReaderSettings("${media.id}_current_settings") ?: defaultSettings
chapters = media.manga?.chapters ?: return
chapter = chapters[media.manga!!.selectedChapter] ?: return
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
binding.mangaReaderSource.visibility =
if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE
if (model.mangaReadSources!!.names.isEmpty()) {
//try to reload sources
try {
if (media.isAdult) {
val mangaSources = MangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow, this@MangaReaderActivity)
}
model.mangaReadSources = mangaSources
} else {
val mangaSources = HMangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
}
model.mangaReadSources = mangaSources
val mangaSources = MangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(
Injekt.get<MangaExtensionManager>().installedExtensionsFlow
)
}
model.mangaReadSources = mangaSources
} catch (e: Exception) {
Firebase.crashlytics.recordException(e)
Injekt.get<CrashlyticsInterface>().logException(e)
logError(e)
}
}
@ -255,13 +242,18 @@ class MangaReaderActivity : AppCompatActivity() {
}
showProgressDialog =
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
?: true else false
if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal(
"${media.id}_progressDialog",
true
) else false
//Chapter Change
fun change(index: Int) {
mangaCache.clear()
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
PrefManager.setCustomVal(
"${media.id}_${chaptersArr[currentChapterIndex]}",
currentChapterPage
)
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!)
.show(supportFragmentManager, "dialog")
}
@ -310,7 +302,7 @@ class MangaReaderActivity : AppCompatActivity() {
chapter = chap
media.manga!!.selectedChapter = chapter.number
media.selected = model.loadSelected(media)
saveData("${media.id}_current_chp", chap.number, this)
PrefManager.setCustomVal("${media.id}_current_chp", chap.number)
currentChapterIndex = chaptersArr.indexOf(chap.number)
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
binding.mangaReaderNextChap.text =
@ -319,9 +311,9 @@ class MangaReaderActivity : AppCompatActivity() {
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
applySettings()
val context = this
val incognito = context.getSharedPreferences("Dantotsu", 0)
?.getBoolean("incognito", false) ?: false
if (isOnline(context) && Discord.token != null && !incognito) {
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 presence = RPC.createPresence(
RPC.Companion.RPCData(
@ -369,7 +361,7 @@ class MangaReaderActivity : AppCompatActivity() {
private val snapHelper = PagerSnapHelper()
fun <T> dualPage(callback: () -> T): T? {
return when (settings.default.dualPageMode) {
return when (defaultSettings.dualPageMode) {
No -> null
Automatic -> {
val orientation = resources.configuration.orientation
@ -384,29 +376,29 @@ class MangaReaderActivity : AppCompatActivity() {
@SuppressLint("ClickableViewAccessibility")
fun applySettings() {
saveData("${media.id}_current_settings", settings.default)
saveReaderSettings("${media.id}_current_settings", defaultSettings)
hideBars()
//true colors
SubsamplingScaleImageView.setPreferredBitmapConfig(
if (settings.default.trueColors) Bitmap.Config.ARGB_8888
if (defaultSettings.trueColors) Bitmap.Config.ARGB_8888
else Bitmap.Config.RGB_565
)
//keep screen On
if (settings.default.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (defaultSettings.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding.mangaReaderPager.unregisterOnPageChangeCallback(pageChangeCallback)
currentChapterPage = loadData("${media.id}_${chapter.number}", this) ?: 1
currentChapterPage = PrefManager.getCustomVal("${media.id}_${chapter.number}", 1L)
val chapImages = chapter.images()
maxChapterPage = 0
if (chapImages.isNotEmpty()) {
maxChapterPage = chapImages.size.toLong()
saveData("${media.id}_${chapter.number}_max", maxChapterPage)
PrefManager.setCustomVal("${media.id}_${chapter.number}_max", maxChapterPage)
imageAdapter =
dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter)
@ -421,15 +413,15 @@ class MangaReaderActivity : AppCompatActivity() {
binding.mangaReaderSlider.visibility = View.GONE
}
binding.mangaReaderPageNumber.text =
if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
}
val currentPage = currentChapterPage.toInt()
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) {
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) {
binding.mangaReaderSwipy.vertical = true
if (settings.default.direction == TOP_TO_BOTTOM) {
if (defaultSettings.direction == TOP_TO_BOTTOM) {
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
?: getString(R.string.no_chapter)
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
@ -466,7 +458,7 @@ class MangaReaderActivity : AppCompatActivity() {
}
} else {
binding.mangaReaderSwipy.vertical = false
if (settings.default.direction == RIGHT_TO_LEFT) {
if (defaultSettings.direction == RIGHT_TO_LEFT) {
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
?: getString(R.string.no_chapter)
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
@ -503,11 +495,11 @@ class MangaReaderActivity : AppCompatActivity() {
}
}
if (settings.default.layout != PAGED) {
if (defaultSettings.layout != PAGED) {
binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE
binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled =
settings.default.rotation
defaultSettings.rotation
val detector = GestureDetectorCompat(this, object : GesturesListener() {
override fun onLongPress(e: MotionEvent) {
@ -530,7 +522,7 @@ class MangaReaderActivity : AppCompatActivity() {
val page =
chapter.dualPages().getOrNull(pos) ?: return@dualPage false
val nextPage = page.second
if (settings.default.direction != LEFT_TO_RIGHT && nextPage != null)
if (defaultSettings.direction != LEFT_TO_RIGHT && nextPage != null)
onImageLongClicked(pos * 2, nextPage, page.first, callback)
else
onImageLongClicked(pos * 2, page.first, nextPage, callback)
@ -552,11 +544,11 @@ class MangaReaderActivity : AppCompatActivity() {
val manager = PreloadLinearLayoutManager(
this,
if (settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)
if (defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)
RecyclerView.VERTICAL
else
RecyclerView.HORIZONTAL,
!(settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == LEFT_TO_RIGHT)
!(defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == LEFT_TO_RIGHT)
)
manager.preloadItemCount = 5
@ -575,7 +567,7 @@ class MangaReaderActivity : AppCompatActivity() {
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
settings.default.apply {
defaultSettings.apply {
if (
((direction == TOP_TO_BOTTOM || direction == BOTTOM_TO_TOP)
&& (!v.canScrollVertically(-1) || !v.canScrollVertically(1)))
@ -594,25 +586,25 @@ class MangaReaderActivity : AppCompatActivity() {
super.onScrolled(v, dx, dy)
}
})
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
updatePadding(0, 128f.px, 0, 128f.px)
else
updatePadding(128f.px, 0, 128f.px, 0)
snapHelper.attachToRecyclerView(
if (settings.default.layout == CONTINUOUS_PAGED) this
if (defaultSettings.layout == CONTINUOUS_PAGED) this
else null
)
onVolumeUp = {
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
smoothScrollBy(0, -500)
else
smoothScrollBy(-500, 0)
}
onVolumeDown = {
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP))
if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP))
smoothScrollBy(0, 500)
else
smoothScrollBy(500, 0)
@ -627,11 +619,11 @@ class MangaReaderActivity : AppCompatActivity() {
visibility = View.VISIBLE
adapter = imageAdapter
layoutDirection =
if (settings.default.direction == BOTTOM_TO_TOP || settings.default.direction == RIGHT_TO_LEFT)
if (defaultSettings.direction == BOTTOM_TO_TOP || defaultSettings.direction == RIGHT_TO_LEFT)
View.LAYOUT_DIRECTION_RTL
else View.LAYOUT_DIRECTION_LTR
orientation =
if (settings.default.direction == LEFT_TO_RIGHT || settings.default.direction == RIGHT_TO_LEFT)
if (defaultSettings.direction == LEFT_TO_RIGHT || defaultSettings.direction == RIGHT_TO_LEFT)
ViewPager2.ORIENTATION_HORIZONTAL
else ViewPager2.ORIENTATION_VERTICAL
registerOnPageChangeCallback(pageChangeCallback)
@ -654,7 +646,7 @@ class MangaReaderActivity : AppCompatActivity() {
return when (event.keyCode) {
KEYCODE_VOLUME_UP, KEYCODE_DPAD_UP, KEYCODE_PAGE_UP -> {
if (event.keyCode == KEYCODE_VOLUME_UP)
if (!settings.default.volumeButtons)
if (!defaultSettings.volumeButtons)
return false
if (event.action == ACTION_DOWN) {
onVolumeUp?.invoke()
@ -664,7 +656,7 @@ class MangaReaderActivity : AppCompatActivity() {
KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> {
if (event.keyCode == KEYCODE_VOLUME_DOWN)
if (!settings.default.volumeButtons)
if (!defaultSettings.volumeButtons)
return false
if (event.action == ACTION_DOWN) {
onVolumeDown?.invoke()
@ -711,14 +703,14 @@ class MangaReaderActivity : AppCompatActivity() {
fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
var pressLocation = pressPos.CENTER
if (!sliding) {
if (event != null && settings.default.layout == PAGED) {
if (event != null && defaultSettings.layout == PAGED) {
if (event.action != MotionEvent.ACTION_UP) return
val x = event.rawX.toInt()
val y = event.rawY.toInt()
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
//if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left
if (screenWidth / 5 in (x + 1)..<y) {
pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) {
if (screenWidth / 5 in x + 1..<y) {
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT) {
pressPos.RIGHT
} else {
pressPos.LEFT
@ -726,7 +718,7 @@ class MangaReaderActivity : AppCompatActivity() {
}
//if in the last 1/5th of the screen width, right and lower than 1/5th of the screen height, right
else if (x > screenWidth - screenWidth / 5 && y > screenWidth / 5) {
pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) {
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT) {
pressPos.LEFT
} else {
pressPos.RIGHT
@ -758,12 +750,12 @@ class MangaReaderActivity : AppCompatActivity() {
}
}
if (!settings.showSystemBars) {
if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
hideBars()
checkNotch()
}
//horizontal scrollbar
if (settings.default.horizontalScrollBar) {
if (defaultSettings.horizontalScrollBar) {
binding.mangaReaderSliderContainer.updateLayoutParams {
height = ViewGroup.LayoutParams.WRAP_CONTENT
width = ViewGroup.LayoutParams.WRAP_CONTENT
@ -790,7 +782,7 @@ class MangaReaderActivity : AppCompatActivity() {
}
}
binding.mangaReaderSlider.layoutDirection =
if (settings.default.direction == RIGHT_TO_LEFT || settings.default.direction == BOTTOM_TO_TOP)
if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP)
View.LAYOUT_DIRECTION_RTL
else View.LAYOUT_DIRECTION_LTR
shouldShow?.apply { isContVisible = !this }
@ -828,9 +820,9 @@ class MangaReaderActivity : AppCompatActivity() {
fun updatePageNumber(page: Long) {
if (currentChapterPage != page) {
currentChapterPage = page
saveData("${media.id}_${chapter.number}", page, this)
PrefManager.setCustomVal("${media.id}_${chapter.number}", page)
binding.mangaReaderPageNumber.text =
if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage"
if (!sliding) binding.mangaReaderSlider.apply {
value = clamp(currentChapterPage.toFloat(), 1f, valueTo)
}
@ -851,31 +843,27 @@ class MangaReaderActivity : AppCompatActivity() {
private fun progress(runnable: Runnable) {
if (maxChapterPage - currentChapterPage <= 1 && Anilist.userid != null) {
showProgressDialog =
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
?: true else false
if (showProgressDialog) {
if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal(
"${media.id}_progressDialog",
true
)
else false
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
if (showProgressDialog && !incognito) {
val dialogView = layoutInflater.inflate(R.layout.item_custom_dialog, null)
val checkbox = dialogView.findViewById<CheckBox>(R.id.dialog_checkbox)
checkbox.text = getString(R.string.dont_ask_again, media.userPreferredName)
checkbox.setOnCheckedChangeListener { _, isChecked ->
saveData("${media.id}_progressDialog", !isChecked)
PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked)
showProgressDialog = !isChecked
}
val incognito =
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("incognito", false) ?: false
AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.title_update_progress))
.apply {
if (incognito) {
setMessage(getString(R.string.incognito_will_not_update))
}
}
.setView(dialogView)
.setCancelable(false)
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
saveData("${media.id}_save_progress", true)
PrefManager.setCustomVal("${media.id}_save_progress", true)
updateProgress(
media,
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
@ -885,7 +873,7 @@ class MangaReaderActivity : AppCompatActivity() {
runnable.run()
}
.setNegativeButton(getString(R.string.no)) { dialog, _ ->
saveData("${media.id}_save_progress", false)
PrefManager.setCustomVal("${media.id}_save_progress", false)
dialog.dismiss()
runnable.run()
}
@ -893,7 +881,11 @@ class MangaReaderActivity : AppCompatActivity() {
.create()
.show()
} else {
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
if (!incognito && PrefManager.getCustomVal(
"${media.id}_save_progress",
true
) && if (media.isAdult) PrefManager.getVal<Boolean>(PrefName.UpdateForHReader) else true
)
updateProgress(
media,
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
@ -906,6 +898,51 @@ class MangaReaderActivity : AppCompatActivity() {
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> loadReaderSettings(
fileName: String,
context: Context? = null,
toast: Boolean = true
): T? {
val a = context ?: currContext()
try {
if (a?.fileList() != null)
if (fileName in a.fileList()) {
val fileIS: FileInputStream = a.openFileInput(fileName)
val objIS = ObjectInputStream(fileIS)
val data = objIS.readObject() as T
objIS.close()
fileIS.close()
return data
}
} catch (e: Exception) {
if (toast) snackString(a?.getString(R.string.error_loading_data, fileName))
//try to delete the file
try {
a?.deleteFile(fileName)
} catch (e: Exception) {
Injekt.get<CrashlyticsInterface>().log("Failed to delete file $fileName")
Injekt.get<CrashlyticsInterface>().logException(e)
}
e.printStackTrace()
}
return null
}
private fun saveReaderSettings(fileName: String, data: Any?, context: Context? = null) {
tryWith {
val a = context ?: currContext()
if (a != null) {
val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE)
val os = ObjectOutputStream(fos)
os.writeObject(data)
os.close()
fos.close()
}
}
}
fun getTransformation(mangaImage: MangaImage): BitmapTransformation? {
return model.loadTransformation(mangaImage, media.selected!!.sourceIndex)
}
@ -916,7 +953,7 @@ class MangaReaderActivity : AppCompatActivity() {
img2: MangaImage?,
callback: ((ImageViewDialog) -> Unit)? = null
): Boolean {
if (!settings.default.longClickImage) return false
if (!defaultSettings.longClickImage) return false
val title = "(Page ${pos + 1}${if (img2 != null) "-${pos + 2}" else ""}) ${
chaptersTitleArr.getOrNull(currentChapterIndex)?.replace(" : ", " - ") ?: ""
} [${media.userPreferredName}]"
@ -930,8 +967,8 @@ class MangaReaderActivity : AppCompatActivity() {
val parserTransformation2 = getTransformation(img2)
if (parserTransformation2 != null) transforms2.add(parserTransformation2)
}
val threshold = settings.default.cropBorderThreshold
if (settings.default.cropBorders) {
val threshold = defaultSettings.cropBorderThreshold
if (defaultSettings.cropBorders) {
transforms1.add(RemoveBordersTransformation(true, threshold))
transforms1.add(RemoveBordersTransformation(false, threshold))
if (img2 != null) {

View file

@ -26,7 +26,7 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as MangaReaderActivity
val settings = activity.settings.default
val settings = activity.defaultSettings
binding.readerDirectionText.text =
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]

View file

@ -26,14 +26,11 @@ import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.novel.NovelDownloaderService
import ani.dantotsu.download.novel.NovelServiceDataSingleton
import ani.dantotsu.loadData
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.saveData
import ani.dantotsu.settings.UserInterfaceSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -61,9 +58,6 @@ class NovelReadFragment : Fragment(),
private var continueEp: Boolean = false
var loaded = false
val uiSettings = loadData("ui_settings", toast = false)
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) {
Log.e("downloadTrigger", novelDownloadPackage.link)
val downloadTask = NovelDownloaderService.DownloadTask(
@ -253,7 +247,7 @@ class NovelReadFragment : Fragment(),
if (save) {
val selected = model.loadSelected(media)
selected.server = query
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
}
}
}
@ -263,7 +257,7 @@ class NovelReadFragment : Fragment(),
selected.sourceIndex = i
source = i
selected.server = null
model.saveSelected(media.id, selected, requireActivity())
model.saveSelected(media.id, selected)
media.selected = selected
}

View file

@ -35,7 +35,7 @@ class NovelResponseAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding
val novel = list[position]
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
setAnimation(fragment.requireContext(), holder.binding.root)
val cover = GlideUrl(novel.coverUrl.url) { novel.coverUrl.headers }
Glide.with(binding.itemEpisodeImage).load(cover).override(400, 0)
@ -104,7 +104,7 @@ class NovelResponseAdapter(
binding.root.setOnLongClickListener {
val builder = androidx.appcompat.app.AlertDialog.Builder(
fragment.requireContext(),
R.style.DialogTheme
R.style.MyPopup
)
builder.setTitle("Delete ${novel.name}?")
builder.setMessage("Are you sure you want to delete ${novel.name}?")

View file

@ -2,6 +2,7 @@ package ani.dantotsu.media.novel.novelreader
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Color
@ -28,17 +29,16 @@ import androidx.webkit.WebViewCompat
import ani.dantotsu.GesturesListener
import ani.dantotsu.NoPaddingArrayAdapter
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityNovelReaderBinding
import ani.dantotsu.hideSystemBars
import ani.dantotsu.loadData
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.saveData
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.CurrentNovelReaderSettings
import ani.dantotsu.settings.CurrentReaderSettings
import ani.dantotsu.settings.ReaderSettings
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.tryWith
@ -52,8 +52,13 @@ import com.vipulog.ebookreader.RelocationInfo
import com.vipulog.ebookreader.TocItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates
@ -63,9 +68,6 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
private lateinit var binding: ActivityNovelReaderBinding
private val scope = lifecycleScope
lateinit var settings: ReaderSettings
private lateinit var uiSettings: UserInterfaceSettings
private var notchHeight: Int? = null
var loaded = false
@ -78,6 +80,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
val themes = ArrayList<ReaderTheme>()
var defaultSettings = CurrentNovelReaderSettings()
init {
val forestTheme = ReaderTheme(
@ -171,16 +175,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
return
}
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
setContentView(binding.root)
settings = loadData("reader_settings", this)
?: ReaderSettings().apply { saveData("reader_settings", this) }
uiSettings = loadData("ui_settings", this)
?: UserInterfaceSettings().also { saveData("ui_settings", it) }
controllerDuration = (uiSettings.animationSpeed * 200).toLong()
controllerDuration = (PrefManager.getVal<Float>(PrefName.AnimationSpeed) * 200).toLong()
setupViews()
setupBackPressedHandler()
@ -286,12 +286,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
binding.bookReader.getAppearance {
currentTheme = it
themes.add(0, it)
settings.defaultLN =
loadData("${sanitizedBookId}_current_settings") ?: settings.defaultLN
defaultSettings =
loadReaderSettings("${sanitizedBookId}_current_settings") ?: defaultSettings
applySettings()
}
val cfi = loadData<String>("${sanitizedBookId}_progress")
val cfi = PrefManager.getCustomVal("${sanitizedBookId}_progress", null as String?)
cfi?.let { binding.bookReader.goto(it) }
binding.progress.visibility = View.GONE
@ -304,7 +304,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
binding.novelReaderSlider.value = info.fraction.toFloat()
val pos = info.tocItem?.let { item -> toc.indexOfFirst { it == item } }
if (pos != null) binding.novelReaderChapterSelect.setSelection(pos)
saveData("${sanitizedBookId}_progress", info.cfi)
PrefManager.setCustomVal("${sanitizedBookId}_progress", info.cfi)
}
@ -339,7 +339,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_PAGE_UP -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP)
if (!settings.defaultLN.volumeButtons)
if (!defaultSettings.volumeButtons)
return false
if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeUp?.invoke()
@ -349,7 +349,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_PAGE_DOWN -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
if (!settings.defaultLN.volumeButtons)
if (!defaultSettings.volumeButtons)
return false
if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeDown?.invoke()
@ -365,18 +365,18 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
fun applySettings() {
saveData("${sanitizedBookId}_current_settings", settings.defaultLN)
saveReaderSettings("${sanitizedBookId}_current_settings", defaultSettings)
hideBars()
if (settings.defaultLN.useOledTheme) {
if (defaultSettings.useOledTheme) {
themes.forEach { theme ->
theme.darkBg = Color.parseColor("#000000")
}
}
currentTheme =
themes.first { it.name.equals(settings.defaultLN.currentThemeName, ignoreCase = true) }
themes.first { it.name.equals(defaultSettings.currentThemeName, ignoreCase = true) }
when (settings.defaultLN.layout) {
when (defaultSettings.layout) {
CurrentNovelReaderSettings.Layouts.PAGED -> {
currentTheme?.flow = ReaderFlow.PAGINATED
}
@ -387,22 +387,22 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
}
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
when (settings.defaultLN.dualPageMode) {
when (defaultSettings.dualPageMode) {
CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1
CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2
CurrentReaderSettings.DualPageModes.Force -> requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
currentTheme?.lineHeight = settings.defaultLN.lineHeight
currentTheme?.gap = settings.defaultLN.margin
currentTheme?.maxInlineSize = settings.defaultLN.maxInlineSize
currentTheme?.maxBlockSize = settings.defaultLN.maxBlockSize
currentTheme?.useDark = settings.defaultLN.useDarkTheme
currentTheme?.lineHeight = defaultSettings.lineHeight
currentTheme?.gap = defaultSettings.margin
currentTheme?.maxInlineSize = defaultSettings.maxInlineSize
currentTheme?.maxBlockSize = defaultSettings.maxBlockSize
currentTheme?.useDark = defaultSettings.useDarkTheme
currentTheme?.let { binding.bookReader.setAppearance(it) }
if (settings.defaultLN.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (defaultSettings.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
@ -432,7 +432,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
fun handleController(shouldShow: Boolean? = null) {
if (!loaded) return
if (!settings.showSystemBars) {
if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
hideBars()
applyNotchMargin()
}
@ -465,7 +465,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
private fun checkNotch() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !settings.showSystemBars) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
val displayCutout = window.decorView.rootWindowInsets.displayCutout
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
@ -486,8 +486,53 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> loadReaderSettings(
fileName: String,
context: Context? = null,
toast: Boolean = true
): T? {
val a = context ?: currContext()
try {
if (a?.fileList() != null)
if (fileName in a.fileList()) {
val fileIS: FileInputStream = a.openFileInput(fileName)
val objIS = ObjectInputStream(fileIS)
val data = objIS.readObject() as T
objIS.close()
fileIS.close()
return data
}
} catch (e: Exception) {
if (toast) snackString(a?.getString(R.string.error_loading_data, fileName))
//try to delete the file
try {
a?.deleteFile(fileName)
} catch (e: Exception) {
Injekt.get<CrashlyticsInterface>().log("Failed to delete file $fileName")
Injekt.get<CrashlyticsInterface>().logException(e)
}
e.printStackTrace()
}
return null
}
private fun saveReaderSettings(fileName: String, data: Any?, context: Context? = null) {
tryWith {
val a = context ?: currContext()
if (a != null) {
val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE)
val os = ObjectOutputStream(fos)
os.writeObject(data)
os.close()
fos.close()
}
}
}
private fun hideBars() {
if (!settings.showSystemBars) hideSystemBars()
if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
hideSystemBars()
}
}
}

View file

@ -30,7 +30,7 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as NovelReaderActivity
val settings = activity.settings.defaultLN
val settings = activity.defaultSettings
val themeLabels = activity.themes.map { it.name }
binding.themeSelect.adapter =
NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels)

View file

@ -1,7 +1,6 @@
package ani.dantotsu.media.user
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.View
@ -17,12 +16,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R
import ani.dantotsu.Refresh
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityListBinding
import ani.dantotsu.loadData
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import com.google.android.material.tabs.TabLayout
@ -39,7 +36,7 @@ class ListActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme()
binding = ActivityListBinding.inflate(layoutInflater)
@ -67,8 +64,7 @@ class ListActivity : AppCompatActivity() {
binding.listTitle.setTextColor(primaryTextColor)
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
if (!uiSettings.immersiveMode) {
if (!PrefManager.getVal<Boolean>(PrefName.ImmersiveMode)) {
this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv)
binding.root.fitsSystemWindows = true
@ -154,8 +150,10 @@ class ListActivity : AppCompatActivity() {
R.id.release -> "release"
else -> null
}
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putString("sort_order", sort)?.apply()
PrefManager.setVal(
if (anime) PrefName.AnimeListSortOrder else PrefName.MangaListSortOrder,
sort ?: ""
)
binding.listProgressBar.visibility = View.VISIBLE
binding.listViewPager.adapter = null
scope.launch {

View file

@ -4,12 +4,13 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.loadData
import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.tryWithSuspend
class ListViewModel : ViewModel() {
var grid = MutableLiveData(loadData<Boolean>("listGrid") ?: true)
var grid = MutableLiveData(PrefManager.getVal<Boolean>(PrefName.ListGrid))
private val lists = MutableLiveData<MutableMap<String, ArrayList<Media>>>()
fun getLists(): LiveData<MutableMap<String, ArrayList<Media>>> = lists

View file

@ -1,6 +1,5 @@
package ani.dantotsu.offline
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -11,6 +10,8 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentOfflineBinding
import ani.dantotsu.isOnline
import ani.dantotsu.navBarHeight
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.startMainActivity
import ani.dantotsu.statusBarHeight
@ -26,8 +27,7 @@ class OfflineFragment : Fragment() {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("offlineMode", false) ?: false
offline = PrefManager.getVal(PrefName.OfflineMode)
binding.noInternet.text =
if (offline) "Offline Mode" else getString(R.string.no_internet)
binding.refreshButton.visibility = if (offline) View.GONE else View.VISIBLE
@ -41,7 +41,6 @@ class OfflineFragment : Fragment() {
override fun onResume() {
super.onResume()
offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getBoolean("offlineMode", false) ?: false
offline = PrefManager.getVal(PrefName.OfflineMode)
}
}

View file

@ -2,12 +2,10 @@ package ani.dantotsu.others
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetCustomBinding
open class CustomBottomDialog : BottomSheetDialogFragment() {

View file

@ -1,5 +1,5 @@
package ani.dantotsu.others
const val DisabledReports = false
//Setting this to false, will allow sending crash reports to Dantotsu's Firebase Crashlytics
//Setting this to false, will allow sending crash reports to Dantotsu's Firebase CrashlyticsInterface
//if you want a custom build without crash reporting, set this to true

View file

@ -14,9 +14,10 @@ import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.defaultHeaders
import ani.dantotsu.loadData
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.parsers.Book
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -36,7 +37,7 @@ object Download {
private fun getDownloadDir(context: Context): File {
val direct: File
if (loadData<Boolean>("sd_dl") == true) {
if (PrefManager.getVal(PrefName.SdDl)) {
val arrayOfFiles = ContextCompat.getExternalFilesDirs(context, null)
val parentDirectory = arrayOfFiles[1].toString()
direct = File(parentDirectory)
@ -92,7 +93,7 @@ object Download {
if (!file.url.startsWith("http"))
toast(context.getString(R.string.invalid_url))
else
when (loadData<Int>("settings_download_manager", context, false) ?: 0) {
when (PrefManager.getVal(PrefName.DownloadManager) as Int) {
1 -> oneDM(context, file, notif ?: fileName)
2 -> adm(context, file, fileName, folder)
else -> defaultDownload(context, file, fileName, folder, notif ?: fileName)
@ -117,7 +118,7 @@ object Download {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val arrayOfFiles = ContextCompat.getExternalFilesDirs(context, null)
if (loadData<Boolean>("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
if (PrefManager.getVal(PrefName.SdDl) && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
val parentDirectory = arrayOfFiles[1].toString() + folder
val direct = File(parentDirectory)
if (!direct.exists()) direct.mkdirs()

View file

@ -5,12 +5,14 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetImageBinding
import ani.dantotsu.downloadsPermission
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
@ -98,7 +100,8 @@ class ImageViewDialog : BottomSheetDialogFragment() {
binding.bottomImageShare.isEnabled = true
binding.bottomImageSave.isEnabled = true
binding.bottomImageSave.setOnClickListener {
saveImageToDownloads(title, bitmap, requireActivity())
if (downloadsPermission(context as AppCompatActivity))
saveImageToDownloads(title, bitmap, requireActivity())
}
binding.bottomImageShare.setOnClickListener {
shareImage(title, bitmap, requireContext())

View file

@ -96,6 +96,7 @@ query {
}
}
}
else -> {
res?.body?.string()
}

View file

@ -1,22 +0,0 @@
package ani.dantotsu.others
import android.app.Activity
import android.content.res.Configuration
import android.content.res.Resources
import java.util.Locale
class LangSet {
companion object {
fun setLocale(activity: Activity) {
val useCursedLang = activity.getSharedPreferences("Dantotsu", Activity.MODE_PRIVATE)
.getBoolean("use_cursed_lang", false)
val locale = if (useCursedLang) Locale("en", "DW") else Locale("en", "US")
Locale.setDefault(locale)
val resources: Resources = activity.resources
val config: Configuration = resources.configuration
config.setLocale(locale)
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}

View file

@ -1,11 +1,7 @@
package ani.dantotsu.others
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.currContext
import ani.dantotsu.media.Media
import ani.dantotsu.snackString
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout
object MalScraper {
@ -50,7 +46,7 @@ object MalScraper {
}
}
} catch (e: Exception) {
// if (e is TimeoutCancellationException) snackString(currContext()?.getString(R.string.error_loading_mal_data))
// if (e is TimeoutCancellationException) snackString(currContext()?.getString(R.string.error_loading_mal_data))
}
}
}

View file

@ -19,7 +19,6 @@ import ani.dantotsu.databinding.ActivityImageSearchBinding
import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast
import kotlinx.coroutines.Dispatchers
@ -52,7 +51,7 @@ class ImageSearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
initActivity(this)
ThemeManager(this).applyTheme()
binding = ActivityImageSearchBinding.inflate(layoutInflater)

View file

@ -5,13 +5,11 @@ import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.asyncMap
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.others.MalSyncBackup
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.tryWithSuspend
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import kotlin.properties.Delegates
/**
* An abstract class for creating a new Source
@ -163,7 +161,7 @@ abstract class AnimeParser : BaseParser() {
*
* **NOTE : do not forget to override `search` if the site does not support only dub search**
* **/
open val isDubAvailableSeparately by Delegates.notNull<Boolean>()
open fun isDubAvailableSeparately(sourceLang: Int? = null): Boolean = false
/**
* The app changes this, depending on user's choice.
@ -182,8 +180,12 @@ abstract class AnimeParser : BaseParser() {
* **/
override suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? {
checkIfVariablesAreEmpty()
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
var loaded = loadData<ShowResponse>("${saveName}${dub}_$mediaId")
val dub = if (isDubAvailableSeparately()) "_${if (selectDub) "dub" else "sub"}" else ""
var loaded = PrefManager.getNullableCustomVal(
"${saveName}${dub}_$mediaId",
null,
ShowResponse::class.java
)
if (loaded == null && malSyncBackupName.isNotEmpty())
loaded = MalSyncBackup.get(mediaId, malSyncBackupName, selectDub)
?.also { saveShowResponse(mediaId, it, true) }
@ -200,8 +202,8 @@ abstract class AnimeParser : BaseParser() {
)
} : ${response.name}"
)
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
saveData("${saveName}${dub}_$mediaId", response)
val dub = if (isDubAvailableSeparately()) "_${if (selectDub) "dub" else "sub"}" else ""
PrefManager.setCustomVal("${saveName}${dub}_$mediaId", response)
}
}
}
@ -209,8 +211,6 @@ abstract class AnimeParser : BaseParser() {
class EmptyAnimeParser : AnimeParser() {
override val name: String = "None"
override val saveName: String = "None"
override val isDubAvailableSeparately: Boolean = false
override suspend fun loadEpisodes(
animeLink: String,
extra: Map<String, String>?,

View file

@ -1,19 +1,21 @@
package ani.dantotsu.parsers
import android.content.Context
import ani.dantotsu.Lazier
import ani.dantotsu.lazyList
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
object AnimeSources : WatchSources() {
override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedAnimeSources: Set<String> = emptySet()
var pinnedAnimeSources: List<String> = emptyList()
suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>, context: Context) {
val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
pinnedAnimeSources = sharedPrefs.getStringSet("pinned_anime_sources", emptySet()) ?: emptySet()
suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) {
pinnedAnimeSources =
PrefManager.getNullableVal<List<String>>(PrefName.AnimeSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first()
@ -24,7 +26,10 @@ object AnimeSources : WatchSources() {
// Update as StateFlow emits new values
fromExtensions.collect { extensions ->
list = sortPinnedAnimeSources(createParsersFromExtensions(extensions), pinnedAnimeSources) + Lazier(
list = sortPinnedAnimeSources(
createParsersFromExtensions(extensions),
pinnedAnimeSources
) + Lazier(
{ OfflineAnimeParser() },
"Downloaded"
)
@ -34,7 +39,7 @@ object AnimeSources : WatchSources() {
fun performReorderAnimeSources() {
//remove the downloaded source from the list to avoid duplicates
list = list.filter { it.name != "Downloaded" }
list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier(
list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier(
{ OfflineAnimeParser() },
"Downloaded"
)
@ -47,13 +52,17 @@ object AnimeSources : WatchSources() {
}
}
private fun sortPinnedAnimeSources(Sources: List<Lazier<BaseParser>>, pinnedAnimeSources: Set<String>): List<Lazier<BaseParser>> {
//find the pinned sources
val pinnedSources = Sources.filter { pinnedAnimeSources.contains(it.name) }
//find the unpinned sources
val unpinnedSources = Sources.filter { !pinnedAnimeSources.contains(it.name) }
//put the pinned sources at the top of the list
return pinnedSources + unpinnedSources
private fun sortPinnedAnimeSources(
sources: List<Lazier<BaseParser>>,
pinnedAnimeSources: List<String>
): List<Lazier<BaseParser>> {
val pinnedSourcesMap = sources.filter { pinnedAnimeSources.contains(it.name) }
.associateBy { it.name }
val orderedPinnedSources = pinnedAnimeSources.mapNotNull { name ->
pinnedSourcesMap[name]
}
val unpinnedSources = sources.filterNot { pinnedAnimeSources.contains(it.name) }
return orderedPinnedSources + unpinnedSources
}
}

View file

@ -10,12 +10,14 @@ import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import ani.dantotsu.FileUrl
import ani.dantotsu.currContext
import ani.dantotsu.logger
import ani.dantotsu.media.anime.AnimeNameAdapter
import ani.dantotsu.media.manga.ImageData
import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.snackString
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
@ -26,6 +28,7 @@ import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
import eu.kanade.tachiyomi.source.anime.getPreferenceKey
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
@ -71,8 +74,78 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val name = extension.name
override val saveName = extension.name
override val hostUrl = extension.sources.first().name
override val isDubAvailableSeparately = false
override val isNSFW = extension.isNsfw
override var selectDub: Boolean
get() = getDub()
set(value) {
setDub(value)
}
private fun getDub(): Boolean {
val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource
?: return false
currContext()?.let { context ->
val sharedPreferences =
context.getSharedPreferences(
configurableSource.getPreferenceKey(),
Context.MODE_PRIVATE
)
sharedPreferences.all.filterValues { AnimeNameAdapter.getSubDub(it.toString()) != AnimeNameAdapter.Companion.SubDubType.NULL }
.forEach { value ->
return when (AnimeNameAdapter.getSubDub(value.value.toString())) {
AnimeNameAdapter.Companion.SubDubType.SUB -> false
AnimeNameAdapter.Companion.SubDubType.DUB -> true
AnimeNameAdapter.Companion.SubDubType.NULL -> false
}
}
}
return false
}
fun setDub(setDub: Boolean) {
val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource
?: return
val type = when (setDub) {
true -> AnimeNameAdapter.Companion.SubDubType.DUB
false -> AnimeNameAdapter.Companion.SubDubType.SUB
}
currContext()?.let { context ->
val sharedPreferences =
context.getSharedPreferences(
configurableSource.getPreferenceKey(),
Context.MODE_PRIVATE
)
sharedPreferences.all.filterValues { AnimeNameAdapter.getSubDub(it.toString()) != AnimeNameAdapter.Companion.SubDubType.NULL }
.forEach { value ->
val setValue = AnimeNameAdapter.setSubDub(value.value.toString(), type)
if (setValue != null) {
sharedPreferences.edit().putString(value.key, setValue).apply()
}
}
}
}
override fun isDubAvailableSeparately(sourceLang: Int?): Boolean {
val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource
?: return false
currContext()?.let { context ->
logger("isDubAvailableSeparately: ${configurableSource.getPreferenceKey()}")
val sharedPreferences =
context.getSharedPreferences(
configurableSource.getPreferenceKey(),
Context.MODE_PRIVATE
)
sharedPreferences.all.filterValues {
AnimeNameAdapter.setSubDub(
it.toString(),
AnimeNameAdapter.Companion.SubDubType.NULL
) != null
}
.forEach { _ -> return true }
}
return false
}
override suspend fun loadEpisodes(
animeLink: String,
extra: Map<String, String>?,
@ -106,6 +179,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
}
it
}
} else if (episodesAreIncrementing(res)) {
res.sortedBy { it.episode_number }
} else {
var episodeCounter = 1f
// Group by season, sort within each season, and then renumber while keeping episode number 0 as is
@ -113,20 +188,20 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
seasonGroups.keys.sortedBy { it.toInt() }
.flatMap { season ->
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
val potentialNumber =
AnimeNameAdapter.findEpisodeNumber(episode.name)
if (potentialNumber != null) {
episode.episode_number = potentialNumber
} else {
episode.episode_number = episodeCounter
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
val potentialNumber =
AnimeNameAdapter.findEpisodeNumber(episode.name)
if (potentialNumber != null) {
episode.episode_number = potentialNumber
} else {
episode.episode_number = episodeCounter
}
episodeCounter++
}
episodeCounter++
}
episode
} ?: emptyList()
}
episode
} ?: emptyList()
}
}
return sortedEpisodes.map { SEpisodeToEpisode(it) }
} catch (e: Exception) {
@ -135,6 +210,19 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
return emptyList()
}
private fun episodesAreIncrementing(episodes: List<SEpisode>): Boolean {
val sortedEpisodes = episodes.sortedBy { it.episode_number }
val takenNumbers = mutableListOf<Float>()
sortedEpisodes.forEach {
if (it.episode_number !in takenNumbers) {
takenNumbers.add(it.episode_number)
} else {
return false
}
}
return true
}
override suspend fun loadVideoServers(
episodeLink: String,
extra: Map<String, String>?,
@ -177,7 +265,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
} catch (e: CloudflareBypassException) {
logger("Exception in search: $e")
withContext(Dispatchers.Main) {
snackString( "Failed to bypass Cloudflare")
snackString("Failed to bypass Cloudflare")
}
emptyList()
} catch (e: Exception) {
@ -626,8 +714,6 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
// If the format is still undetermined, log an error
if (format == null) {
logger("Unknown video format: $videoUrl")
//FirebaseCrashlytics.getInstance()
// .recordException(Exception("Unknown video format: $videoUrl"))
format = VideoType.CONTAINER
}
val headersMap: Map<String, String> =

View file

@ -3,10 +3,9 @@ package ani.dantotsu.parsers
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.logger
import ani.dantotsu.media.Media
import ani.dantotsu.saveData
import ani.dantotsu.settings.saving.PrefManager
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.source.model.SManga
import me.xdrop.fuzzywuzzy.FuzzySearch
@ -135,7 +134,11 @@ abstract class BaseParser {
* **/
open suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? {
checkIfVariablesAreEmpty()
return loadData("${saveName}_$mediaId")
return PrefManager.getNullableCustomVal(
"${saveName}_$mediaId",
null,
ShowResponse::class.java
)
}
/**
@ -151,7 +154,7 @@ abstract class BaseParser {
)
} : ${response.name}"
)
saveData("${saveName}_$mediaId", response)
PrefManager.setCustomVal("${saveName}_$mediaId", response)
}
}

View file

@ -1,19 +1,21 @@
package ani.dantotsu.parsers
import android.content.Context
import ani.dantotsu.Lazier
import ani.dantotsu.lazyList
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
object MangaSources : MangaReadSources() {
override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedMangaSources: Set<String> = emptySet()
var pinnedMangaSources: List<String> = emptyList()
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>, context: Context) {
val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
pinnedMangaSources = sharedPrefs.getStringSet("pinned_manga_sources", emptySet()) ?: emptySet()
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
pinnedMangaSources =
PrefManager.getNullableVal<List<String>>(PrefName.MangaSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first()
@ -24,7 +26,10 @@ object MangaSources : MangaReadSources() {
// Update as StateFlow emits new values
fromExtensions.collect { extensions ->
list = sortPinnedMangaSources(createParsersFromExtensions(extensions), pinnedMangaSources) + Lazier(
list = sortPinnedMangaSources(
createParsersFromExtensions(extensions),
pinnedMangaSources
) + Lazier(
{ OfflineMangaParser() },
"Downloaded"
)
@ -47,21 +52,21 @@ object MangaSources : MangaReadSources() {
}
}
private fun sortPinnedMangaSources(Sources: List<Lazier<BaseParser>>, pinnedMangaSources: Set<String>): List<Lazier<BaseParser>> {
//find the pinned sources
val pinnedSources = Sources.filter { pinnedMangaSources.contains(it.name) }
//find the unpinned sources
val unpinnedSources = Sources.filter { !pinnedMangaSources.contains(it.name) }
//put the pinned sources at the top of the list
return pinnedSources + unpinnedSources
private fun sortPinnedMangaSources(
sources: List<Lazier<BaseParser>>,
pinnedMangaSources: List<String>
): List<Lazier<BaseParser>> {
val pinnedSourcesMap = sources.filter { pinnedMangaSources.contains(it.name) }
.associateBy { it.name }
val orderedPinnedSources = pinnedMangaSources.mapNotNull { name ->
pinnedSourcesMap[name]
}
val unpinnedSources = sources.filterNot { pinnedMangaSources.contains(it.name) }
return orderedPinnedSources + unpinnedSources
}
}
object HMangaSources : MangaReadSources() {
val aList: List<Lazier<BaseParser>> = lazyList()
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
//todo
}
private val aList: List<Lazier<BaseParser>> = lazyList()
override val list = listOf(aList, MangaSources.list).flatten()
}

View file

@ -4,13 +4,20 @@ import android.util.Log
import ani.dantotsu.Lazier
import ani.dantotsu.parsers.novel.DynamicNovelParser
import ani.dantotsu.parsers.novel.NovelExtension
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
object NovelSources : NovelReadSources() {
override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedNovelSources: List<String> = emptyList()
suspend fun init(fromExtensions: StateFlow<List<NovelExtension.Installed>>) {
pinnedNovelSources =
PrefManager.getNullableVal<List<String>>(PrefName.NovelSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first()
list = createParsersFromExtensions(initialExtensions) + Lazier(
@ -20,13 +27,25 @@ object NovelSources : NovelReadSources() {
// Update as StateFlow emits new values
fromExtensions.collect { extensions ->
list = createParsersFromExtensions(extensions) + Lazier(
list = sortPinnedNovelSources(
createParsersFromExtensions(extensions),
pinnedNovelSources
) + Lazier(
{ OfflineNovelParser() },
"Downloaded"
)
}
}
fun performReorderNovelSources() {
//remove the downloaded source from the list to avoid duplicates
list = list.filter { it.name != "Downloaded" }
list = sortPinnedNovelSources(list, pinnedNovelSources) + Lazier(
{ OfflineNovelParser() },
"Downloaded"
)
}
private fun createParsersFromExtensions(extensions: List<NovelExtension.Installed>): List<Lazier<BaseParser>> {
Log.d("NovelSources", "createParsersFromExtensions")
Log.d("NovelSources", extensions.toString())
@ -35,4 +54,17 @@ object NovelSources : NovelReadSources() {
Lazier({ DynamicNovelParser(extension) }, name)
}
}
private fun sortPinnedNovelSources(
parsers: List<Lazier<BaseParser>>,
pinnedSources: List<String>
): List<Lazier<BaseParser>> {
val pinnedSourcesMap = parsers.filter { pinnedSources.contains(it.name) }
.associateBy { it.name }
val orderedPinnedSources = pinnedSources.mapNotNull { name ->
pinnedSourcesMap[name]
}
val unpinnedSources = parsers.filterNot { pinnedSources.contains(it.name) }
return orderedPinnedSources + unpinnedSources
}
}

View file

@ -21,7 +21,6 @@ class OfflineAnimeParser : AnimeParser() {
override val name = "Offline"
override val saveName = "Offline"
override val hostUrl = "Offline"
override val isDubAvailableSeparately = false
override val isNSFW = false
override suspend fun loadEpisodes(
@ -119,7 +118,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
val sublist = getSubtitle(
videoServer.extraData?.get("title") ?: "",
videoServer.extraData?.get("episode") ?: ""
)?: emptyList()
) ?: emptyList()
//we need to return a "fake" video so that the app doesn't crash
val video = Video(
null,

View file

@ -62,6 +62,7 @@ data class VideoServer(
) : Serializable {
constructor(name: String, embedUrl: String, extraData: Map<String, String>? = null)
: this(name, FileUrl(embedUrl), extraData)
constructor(name: String, offline: Boolean, extraData: Map<String, String>?)
: this(name, FileUrl(""), extraData, null, offline)

View file

@ -2,8 +2,9 @@ package ani.dantotsu.parsers.novel
import android.content.Context
import ani.dantotsu.currContext
import ani.dantotsu.logger
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
@ -26,9 +27,7 @@ class NovelExtensionGithubApi {
private val novelExtensionManager: NovelExtensionManager by injectLazy()
private val json: Json by injectLazy()
private val lastExtCheck: Long =
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
?.getLong("last_ext_check", 0) ?: 0
private val lastExtCheck: Long = PrefManager.getVal(PrefName.NovelLastExtCheck)
private var requiresFallbackSource = false
@ -86,8 +85,7 @@ class NovelExtensionGithubApi {
novelExtensionManager.availableExtensionsFlow.value
} else {
findExtensions().also {
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
?.putLong("last_ext_check", Date().time)?.apply()
PrefManager.setVal(PrefName.NovelLastExtCheck, Date().time)
}
}

Some files were not shown because too many files have changed in this diff Show more