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: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 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 - name: Set variables
run: | run: |
VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"') VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"')
SHA=${{ github.sha }} SHA=${{ github.sha }}
VERSION="$VER.${SHA:0:7}" VERSION="$VER+${SHA:0:7}"
echo "Version $VERSION" echo "Version $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
@ -31,30 +69,54 @@ jobs:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
cache: gradle cache: gradle
- name: Decode Keystore File - name: Decode Keystore File
run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore
- name: List files in the directory - name: List files in the directory
run: ls -l run: ls -l
- name: Make gradlew executable - name: Make gradlew executable
run: chmod +x ./gradlew run: chmod +x ./gradlew
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew assembleDebug -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
- name: Upload a Build Artifact - name: Upload a Build Artifact
uses: actions/upload-artifact@v3.0.0 uses: actions/upload-artifact@v3.0.0
with: with:
name: Dantotsu name: Dantotsu
path: "app/build/outputs/apk/debug/app-debug.apk" path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
- name: Upload APK to Discord - name: Upload APK to Discord and Telegram
shell: bash shell: bash
run: | run: |
contentbody=$( jq -Rsa . <<< "${{ github.event.head_commit.message }}" ) #Discord
curl -F "payload_json={\"content\":\" Debug-Build: <@719439449423085569> **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }} commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g')
# 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 - name: Delete Old Pre-Releases
id: delete-pre-releases id: delete-pre-releases

4
app/.gitignore vendored
View file

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

View file

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

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,14 +5,13 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import ani.dantotsu.loadMedia import ani.dantotsu.loadMedia
import ani.dantotsu.others.LangSet
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
class UrlMedia : Activity() { class UrlMedia : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0 var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
var isMAL = false 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.Context
import android.content.Intent import android.content.Intent
import android.widget.TextView import android.widget.TextView
import androidx.core.content.edit
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast import ani.dantotsu.toast
import ani.dantotsu.tryWith import ani.dantotsu.tryWith
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
@ -18,37 +19,20 @@ object Discord {
var userid: String? = null var userid: String? = null
var avatar: String? = null var avatar: String? = null
const val TOKEN = "discord_token"
fun getSavedToken(context: Context): Boolean { fun getSavedToken(context: Context): Boolean {
val sharedPref = context.getSharedPreferences( token = PrefManager.getVal(
context.getString(R.string.preference_file_key), PrefName.DiscordToken, null as String?
Context.MODE_PRIVATE
) )
token = sharedPref.getString(TOKEN, null)
return token != null return token != null
} }
fun saveToken(context: Context, token: String) { fun saveToken(context: Context, token: String) {
val sharedPref = context.getSharedPreferences( PrefManager.setVal(PrefName.DiscordToken, token)
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
sharedPref.edit {
putString(TOKEN, token)
commit()
}
} }
fun removeSavedToken(context: Context) { fun removeSavedToken(context: Context) {
val sharedPref = context.getSharedPreferences( PrefManager.removeVal(PrefName.DiscordToken)
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
sharedPref.edit {
remove(TOKEN)
commit()
}
tryWith(true) { tryWith(true) {
val dir = File(context.filesDir?.parentFile, "app_webview") val dir = File(context.filesDir?.parentFile, "app_webview")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,23 @@
package ani.dantotsu.home package ani.dantotsu.home
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.FragmentLoginBinding import ani.dantotsu.databinding.FragmentLoginBinding
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferencePackager
import ani.dantotsu.toast
import com.google.android.material.textfield.TextInputEditText
class LoginFragment : Fragment() { class LoginFragment : Fragment() {
@ -29,5 +38,99 @@ class LoginFragment : Fragment() {
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) } binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) } binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) } binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
val openDocumentLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) {
try {
val jsonString =
requireActivity().contentResolver.openInputStream(uri)?.readBytes()
?: throw Exception("Error reading file")
val name =
DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings"
//.sani is encrypted, .ani is not
if (name.endsWith(".sani")) {
passwordAlertDialog() { password ->
if (password != null) {
val salt = jsonString.copyOfRange(0, 16)
val encrypted = jsonString.copyOfRange(16, jsonString.size)
val decryptedJson = try {
PreferenceKeystore.decryptWithPassword(
password,
encrypted,
salt
)
} catch (e: Exception) {
toast("Incorrect password")
return@passwordAlertDialog
}
if (PreferencePackager.unpack(decryptedJson))
restartApp()
} else {
toast("Password cannot be empty")
}
}
} else if (name.endsWith(".ani")) {
val decryptedJson = jsonString.toString(Charsets.UTF_8)
if (PreferencePackager.unpack(decryptedJson))
restartApp()
} else {
toast("Invalid file type")
}
} catch (e: Exception) {
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.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -26,12 +25,12 @@ import ani.dantotsu.connections.anilist.AnilistMangaViewModel
import ani.dantotsu.connections.anilist.SearchResults import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentMangaBinding import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.ProgressAdapter import ani.dantotsu.media.ProgressAdapter
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.px import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -46,9 +45,6 @@ class MangaFragment : Fragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var mangaPageAdapter: MangaPageAdapter private lateinit var mangaPageAdapter: MangaPageAdapter
private var uiSettings: UserInterfaceSettings =
loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistMangaViewModel by activityViewModels() val model: AnilistMangaViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
@ -175,7 +171,7 @@ class MangaFragment : Fragment() {
if (it != null) { if (it != null) {
mangaPageAdapter.updateTrending( mangaPageAdapter.updateTrending(
MediaAdaptor( MediaAdaptor(
if (uiSettings.smallView) 3 else 2, if (PrefManager.getVal(PrefName.SmallView)) 3 else 2,
it, it,
requireActivity(), requireActivity(),
viewPager = mangaPageAdapter.trendingViewPager viewPager = mangaPageAdapter.trendingViewPager
@ -242,8 +238,11 @@ class MangaFragment : Fragment() {
model.loaded = true model.loaded = true
model.loadTrending() model.loadTrending()
model.loadTrendingNovel() model.loadTrendingNovel()
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) model.loadPopular(
.getBoolean("popular_list", false) ) "MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal(
PrefName.PopularMangaList
)
)
} }
live.postValue(false) live.postValue(false)
_binding?.mangaRefresh?.isRefreshing = false _binding?.mangaRefresh?.isRefreshing = false

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,14 +17,13 @@ import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityCharacterBinding import ani.dantotsu.databinding.ActivityCharacterBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.getSerialized import ani.dantotsu.others.getSerialized
import ani.dantotsu.px import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
@ -38,22 +37,21 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
private val model: OtherDetailsViewModel by viewModels() private val model: OtherDetailsViewModel by viewModels()
private lateinit var character: Character private lateinit var character: Character
private var loaded = false private var loaded = false
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
binding = ActivityCharacterBinding.inflate(layoutInflater) binding = ActivityCharacterBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
initActivity(this) initActivity(this)
screenWidth = resources.displayMetrics.run { widthPixels / density } screenWidth = resources.displayMetrics.run { widthPixels / density }
if (uiSettings.immersiveMode) this.window.statusBarColor = if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.status) ContextCompat.getColor(this, R.color.status)
val banner = val banner =
if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen
banner.updateLayoutParams { height += statusBarHeight } banner.updateLayoutParams { height += statusBarHeight }
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight } binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
@ -136,16 +134,16 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
binding.characterCover.visibility = binding.characterCover.visibility =
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
if (percentage >= percent && !isCollapsed) { if (percentage >= percent && !isCollapsed) {
isCollapsed = true isCollapsed = true
if (uiSettings.immersiveMode) this.window.statusBarColor = if (immersiveMode) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg) ContextCompat.getColor(this, R.color.nav_bg)
binding.characterAppBar.setBackgroundResource(R.color.nav_bg) binding.characterAppBar.setBackgroundResource(R.color.nav_bg)
} }
if (percentage <= percent && isCollapsed) { if (percentage <= percent && isCollapsed) {
isCollapsed = false isCollapsed = false
if (uiSettings.immersiveMode) this.window.statusBarColor = if (immersiveMode) this.window.statusBarColor =
ContextCompat.getColor(this, R.color.status) ContextCompat.getColor(this, R.color.status)
binding.characterAppBar.setBackgroundResource(R.color.bg) 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.connections.anilist.GenresViewModel
import ani.dantotsu.databinding.ActivityGenreBinding import ani.dantotsu.databinding.ActivityGenreBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -27,7 +27,7 @@ class GenreActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
LangSet.setLocale(this)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
binding = ActivityGenreBinding.inflate(layoutInflater) binding = ActivityGenreBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -54,7 +54,9 @@ class GenreActivity : AppCompatActivity() {
GridLayoutManager(this, (screenWidth / 156f).toInt()) GridLayoutManager(this, (screenWidth / 156f).toInt())
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) { model.loadGenres(
Anilist.genres ?: loadLocalGenres() ?: arrayListOf()
) {
MainScope().launch { MainScope().launch {
adapter.addGenre(it) adapter.addGenre(it)
} }
@ -62,4 +64,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.ItemMediaLargeBinding
import ani.dantotsu.databinding.ItemMediaPageBinding import ani.dantotsu.databinding.ItemMediaPageBinding
import ani.dantotsu.databinding.ItemMediaPageSmallBinding import ani.dantotsu.databinding.ItemMediaPageSmallBinding
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
@ -44,9 +45,6 @@ class MediaAdaptor(
private val viewPager: ViewPager2? = null, private val viewPager: ViewPager2? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val uiSettings =
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (type) { return when (type) {
0 -> MediaViewHolder( 0 -> MediaViewHolder(
@ -91,7 +89,7 @@ class MediaAdaptor(
when (type) { when (type) {
0 -> { 0 -> {
val b = (holder as MediaViewHolder).binding val b = (holder as MediaViewHolder).binding
setAnimation(activity, b.root, uiSettings) setAnimation(activity, b.root)
val media = mediaList?.getOrNull(position) val media = mediaList?.getOrNull(position)
if (media != null) { if (media != null) {
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
@ -135,7 +133,7 @@ class MediaAdaptor(
1 -> { 1 -> {
val b = (holder as MediaLargeViewHolder).binding val b = (holder as MediaLargeViewHolder).binding
setAnimation(activity, b.root, uiSettings) setAnimation(activity, b.root)
val media = mediaList?.get(position) val media = mediaList?.get(position)
if (media != null) { if (media != null) {
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
@ -178,16 +176,17 @@ class MediaAdaptor(
val b = (holder as MediaPageViewHolder).binding val b = (holder as MediaPageViewHolder).binding
val media = mediaList?.get(position) val media = mediaList?.get(position)
if (media != null) { if (media != null) {
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
if (uiSettings.bannerAnimations) if (bannerAnimations)
b.itemCompactBanner.setTransitionGenerator( b.itemCompactBanner.setTransitionGenerator(
RandomTransitionGenerator( RandomTransitionGenerator(
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(), (10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed)) as Float)).toLong(),
AccelerateDecelerateInterpolator() AccelerateDecelerateInterpolator()
) )
) )
val banner = val banner =
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
val context = b.itemCompactBanner.context val context = b.itemCompactBanner.context
if (!(context as Activity).isDestroyed) if (!(context as Activity).isDestroyed)
Glide.with(context as Context) Glide.with(context as Context)
@ -234,16 +233,17 @@ class MediaAdaptor(
val b = (holder as MediaPageSmallViewHolder).binding val b = (holder as MediaPageSmallViewHolder).binding
val media = mediaList?.get(position) val media = mediaList?.get(position)
if (media != null) { if (media != null) {
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
if (uiSettings.bannerAnimations) if (bannerAnimations)
b.itemCompactBanner.setTransitionGenerator( b.itemCompactBanner.setTransitionGenerator(
RandomTransitionGenerator( RandomTransitionGenerator(
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(), (10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(),
AccelerateDecelerateInterpolator() AccelerateDecelerateInterpolator()
) )
) )
val banner = val banner =
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
val context = b.itemCompactBanner.context val context = b.itemCompactBanner.context
if (!(context as Activity).isDestroyed) if (!(context as Activity).isDestroyed)
Glide.with(context as Context) Glide.with(context as Context)
@ -396,10 +396,8 @@ class MediaAdaptor(
if (itemCompactImage != null) { if (itemCompactImage != null) {
ActivityOptionsCompat.makeSceneTransitionAnimation( ActivityOptionsCompat.makeSceneTransitionAnimation(
activity, activity,
Pair.create( itemCompactImage,
itemCompactImage, ViewCompat.getTransitionName(itemCompactImage)!!
ViewCompat.getTransitionName(activity.findViewById(R.id.itemCompactImage))!!
),
).toBundle() ).toBundle()
} else { } else {
null null

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -26,7 +26,7 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as MangaReaderActivity val activity = requireActivity() as MangaReaderActivity
val settings = activity.settings.default val settings = activity.defaultSettings
binding.readerDirectionText.text = binding.readerDirectionText.text =
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal] 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.DownloadsManager
import ani.dantotsu.download.novel.NovelDownloaderService import ani.dantotsu.download.novel.NovelDownloaderService
import ani.dantotsu.download.novel.NovelServiceDataSingleton import ani.dantotsu.download.novel.NovelServiceDataSingleton
import ani.dantotsu.loadData
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.saveData
import ani.dantotsu.settings.UserInterfaceSettings
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -61,9 +58,6 @@ class NovelReadFragment : Fragment(),
private var continueEp: Boolean = false private var continueEp: Boolean = false
var loaded = false var loaded = false
val uiSettings = loadData("ui_settings", toast = false)
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) { override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) {
Log.e("downloadTrigger", novelDownloadPackage.link) Log.e("downloadTrigger", novelDownloadPackage.link)
val downloadTask = NovelDownloaderService.DownloadTask( val downloadTask = NovelDownloaderService.DownloadTask(
@ -253,7 +247,7 @@ class NovelReadFragment : Fragment(),
if (save) { if (save) {
val selected = model.loadSelected(media) val selected = model.loadSelected(media)
selected.server = query selected.server = query
model.saveSelected(media.id, selected, requireActivity()) model.saveSelected(media.id, selected)
} }
} }
} }
@ -263,7 +257,7 @@ class NovelReadFragment : Fragment(),
selected.sourceIndex = i selected.sourceIndex = i
source = i source = i
selected.server = null selected.server = null
model.saveSelected(media.id, selected, requireActivity()) model.saveSelected(media.id, selected)
media.selected = selected media.selected = selected
} }

View file

@ -35,7 +35,7 @@ class NovelResponseAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val novel = list[position] 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 } val cover = GlideUrl(novel.coverUrl.url) { novel.coverUrl.headers }
Glide.with(binding.itemEpisodeImage).load(cover).override(400, 0) Glide.with(binding.itemEpisodeImage).load(cover).override(400, 0)
@ -104,7 +104,7 @@ class NovelResponseAdapter(
binding.root.setOnLongClickListener { binding.root.setOnLongClickListener {
val builder = androidx.appcompat.app.AlertDialog.Builder( val builder = androidx.appcompat.app.AlertDialog.Builder(
fragment.requireContext(), fragment.requireContext(),
R.style.DialogTheme R.style.MyPopup
) )
builder.setTitle("Delete ${novel.name}?") builder.setTitle("Delete ${novel.name}?")
builder.setMessage("Are you sure you want to 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.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.graphics.Color import android.graphics.Color
@ -28,17 +29,16 @@ import androidx.webkit.WebViewCompat
import ani.dantotsu.GesturesListener import ani.dantotsu.GesturesListener
import ani.dantotsu.NoPaddingArrayAdapter import ani.dantotsu.NoPaddingArrayAdapter
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityNovelReaderBinding import ani.dantotsu.databinding.ActivityNovelReaderBinding
import ani.dantotsu.hideSystemBars import ani.dantotsu.hideSystemBars
import ani.dantotsu.loadData
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.LangSet
import ani.dantotsu.saveData
import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.CurrentNovelReaderSettings import ani.dantotsu.settings.CurrentNovelReaderSettings
import ani.dantotsu.settings.CurrentReaderSettings import ani.dantotsu.settings.CurrentReaderSettings
import ani.dantotsu.settings.ReaderSettings import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.tryWith import ani.dantotsu.tryWith
@ -52,8 +52,13 @@ import com.vipulog.ebookreader.RelocationInfo
import com.vipulog.ebookreader.TocItem import com.vipulog.ebookreader.TocItem
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.* import java.util.*
import kotlin.math.min import kotlin.math.min
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -63,9 +68,6 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
private lateinit var binding: ActivityNovelReaderBinding private lateinit var binding: ActivityNovelReaderBinding
private val scope = lifecycleScope private val scope = lifecycleScope
lateinit var settings: ReaderSettings
private lateinit var uiSettings: UserInterfaceSettings
private var notchHeight: Int? = null private var notchHeight: Int? = null
var loaded = false var loaded = false
@ -78,6 +80,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
val themes = ArrayList<ReaderTheme>() val themes = ArrayList<ReaderTheme>()
var defaultSettings = CurrentNovelReaderSettings()
init { init {
val forestTheme = ReaderTheme( val forestTheme = ReaderTheme(
@ -171,16 +175,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
return return
} }
LangSet.setLocale(this)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
binding = ActivityNovelReaderBinding.inflate(layoutInflater) binding = ActivityNovelReaderBinding.inflate(layoutInflater)
setContentView(binding.root) 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() setupViews()
setupBackPressedHandler() setupBackPressedHandler()
@ -286,12 +286,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
binding.bookReader.getAppearance { binding.bookReader.getAppearance {
currentTheme = it currentTheme = it
themes.add(0, it) themes.add(0, it)
settings.defaultLN = defaultSettings =
loadData("${sanitizedBookId}_current_settings") ?: settings.defaultLN loadReaderSettings("${sanitizedBookId}_current_settings") ?: defaultSettings
applySettings() applySettings()
} }
val cfi = loadData<String>("${sanitizedBookId}_progress") val cfi = PrefManager.getCustomVal("${sanitizedBookId}_progress", null as String?)
cfi?.let { binding.bookReader.goto(it) } cfi?.let { binding.bookReader.goto(it) }
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE
@ -304,7 +304,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
binding.novelReaderSlider.value = info.fraction.toFloat() binding.novelReaderSlider.value = info.fraction.toFloat()
val pos = info.tocItem?.let { item -> toc.indexOfFirst { it == item } } val pos = info.tocItem?.let { item -> toc.indexOfFirst { it == item } }
if (pos != null) binding.novelReaderChapterSelect.setSelection(pos) 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) { return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_PAGE_UP -> { KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_PAGE_UP -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP) if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP)
if (!settings.defaultLN.volumeButtons) if (!defaultSettings.volumeButtons)
return false return false
if (event.action == KeyEvent.ACTION_DOWN) { if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeUp?.invoke() onVolumeUp?.invoke()
@ -349,7 +349,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_PAGE_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_PAGE_DOWN -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) if (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
if (!settings.defaultLN.volumeButtons) if (!defaultSettings.volumeButtons)
return false return false
if (event.action == KeyEvent.ACTION_DOWN) { if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeDown?.invoke() onVolumeDown?.invoke()
@ -365,18 +365,18 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
fun applySettings() { fun applySettings() {
saveData("${sanitizedBookId}_current_settings", settings.defaultLN) saveReaderSettings("${sanitizedBookId}_current_settings", defaultSettings)
hideBars() hideBars()
if (settings.defaultLN.useOledTheme) { if (defaultSettings.useOledTheme) {
themes.forEach { theme -> themes.forEach { theme ->
theme.darkBg = Color.parseColor("#000000") theme.darkBg = Color.parseColor("#000000")
} }
} }
currentTheme = 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 -> { CurrentNovelReaderSettings.Layouts.PAGED -> {
currentTheme?.flow = ReaderFlow.PAGINATED currentTheme?.flow = ReaderFlow.PAGINATED
} }
@ -387,22 +387,22 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
} }
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
when (settings.defaultLN.dualPageMode) { when (defaultSettings.dualPageMode) {
CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1 CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1
CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2 CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2
CurrentReaderSettings.DualPageModes.Force -> requestedOrientation = CurrentReaderSettings.DualPageModes.Force -> requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} }
currentTheme?.lineHeight = settings.defaultLN.lineHeight currentTheme?.lineHeight = defaultSettings.lineHeight
currentTheme?.gap = settings.defaultLN.margin currentTheme?.gap = defaultSettings.margin
currentTheme?.maxInlineSize = settings.defaultLN.maxInlineSize currentTheme?.maxInlineSize = defaultSettings.maxInlineSize
currentTheme?.maxBlockSize = settings.defaultLN.maxBlockSize currentTheme?.maxBlockSize = defaultSettings.maxBlockSize
currentTheme?.useDark = settings.defaultLN.useDarkTheme currentTheme?.useDark = defaultSettings.useDarkTheme
currentTheme?.let { binding.bookReader.setAppearance(it) } 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) else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} }
@ -432,7 +432,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
fun handleController(shouldShow: Boolean? = null) { fun handleController(shouldShow: Boolean? = null) {
if (!loaded) return if (!loaded) return
if (!settings.showSystemBars) { if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
hideBars() hideBars()
applyNotchMargin() applyNotchMargin()
} }
@ -465,7 +465,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
private fun checkNotch() { 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 val displayCutout = window.decorView.rootWindowInsets.displayCutout
if (displayCutout != null) { if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) { 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() { 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as NovelReaderActivity val activity = requireActivity() as NovelReaderActivity
val settings = activity.settings.defaultLN val settings = activity.defaultSettings
val themeLabels = activity.themes.map { it.name } val themeLabels = activity.themes.map { it.name }
binding.themeSelect.adapter = binding.themeSelect.adapter =
NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels) NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels)

View file

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

View file

@ -4,12 +4,13 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.loadData
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
class ListViewModel : ViewModel() { 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>>>() private val lists = MutableLiveData<MutableMap<String, ArrayList<Media>>>()
fun getLists(): LiveData<MutableMap<String, ArrayList<Media>>> = lists fun getLists(): LiveData<MutableMap<String, ArrayList<Media>>> = lists

View file

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

View file

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

View file

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

View file

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

View file

@ -96,6 +96,7 @@ query {
} }
} }
} }
else -> { else -> {
res?.body?.string() 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 package ani.dantotsu.others
import ani.dantotsu.R
import ani.dantotsu.client import ani.dantotsu.client
import ani.dantotsu.currContext
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.snackString
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
object MalScraper { object MalScraper {
@ -50,7 +46,7 @@ object MalScraper {
} }
} }
} catch (e: Exception) { } 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.initActivity
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast import ani.dantotsu.toast
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -52,7 +51,7 @@ class ImageSearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
LangSet.setLocale(this)
initActivity(this) initActivity(this)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
binding = ActivityImageSearchBinding.inflate(layoutInflater) binding = ActivityImageSearchBinding.inflate(layoutInflater)

View file

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

View file

@ -1,19 +1,21 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.content.Context
import ani.dantotsu.Lazier import ani.dantotsu.Lazier
import ani.dantotsu.lazyList 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 eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
object AnimeSources : WatchSources() { object AnimeSources : WatchSources() {
override var list: List<Lazier<BaseParser>> = emptyList() 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) { suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) {
val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) pinnedAnimeSources =
pinnedAnimeSources = sharedPrefs.getStringSet("pinned_anime_sources", emptySet()) ?: emptySet() PrefManager.getNullableVal<List<String>>(PrefName.AnimeSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow // Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first() val initialExtensions = fromExtensions.first()
@ -24,7 +26,10 @@ object AnimeSources : WatchSources() {
// Update as StateFlow emits new values // Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->
list = sortPinnedAnimeSources(createParsersFromExtensions(extensions), pinnedAnimeSources) + Lazier( list = sortPinnedAnimeSources(
createParsersFromExtensions(extensions),
pinnedAnimeSources
) + Lazier(
{ OfflineAnimeParser() }, { OfflineAnimeParser() },
"Downloaded" "Downloaded"
) )
@ -34,7 +39,7 @@ object AnimeSources : WatchSources() {
fun performReorderAnimeSources() { fun performReorderAnimeSources() {
//remove the downloaded source from the list to avoid duplicates //remove the downloaded source from the list to avoid duplicates
list = list.filter { it.name != "Downloaded" } list = list.filter { it.name != "Downloaded" }
list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier( list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier(
{ OfflineAnimeParser() }, { OfflineAnimeParser() },
"Downloaded" "Downloaded"
) )
@ -47,13 +52,17 @@ object AnimeSources : WatchSources() {
} }
} }
private fun sortPinnedAnimeSources(Sources: List<Lazier<BaseParser>>, pinnedAnimeSources: Set<String>): List<Lazier<BaseParser>> { private fun sortPinnedAnimeSources(
//find the pinned sources sources: List<Lazier<BaseParser>>,
val pinnedSources = Sources.filter { pinnedAnimeSources.contains(it.name) } pinnedAnimeSources: List<String>
//find the unpinned sources ): List<Lazier<BaseParser>> {
val unpinnedSources = Sources.filter { !pinnedAnimeSources.contains(it.name) } val pinnedSourcesMap = sources.filter { pinnedAnimeSources.contains(it.name) }
//put the pinned sources at the top of the list .associateBy { it.name }
return pinnedSources + unpinnedSources 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.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.currContext
import ani.dantotsu.logger import ani.dantotsu.logger
import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.media.anime.AnimeNameAdapter
import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.ImageData
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.snackString import ani.dantotsu.snackString
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource 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.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode 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.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException 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.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
@ -71,8 +74,78 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val name = extension.name override val name = extension.name
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl = extension.sources.first().name
override val isDubAvailableSeparately = false
override val isNSFW = extension.isNsfw 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( override suspend fun loadEpisodes(
animeLink: String, animeLink: String,
extra: Map<String, String>?, extra: Map<String, String>?,
@ -106,6 +179,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
} }
it it
} }
} else if (episodesAreIncrementing(res)) {
res.sortedBy { it.episode_number }
} else { } else {
var episodeCounter = 1f var episodeCounter = 1f
// Group by season, sort within each season, and then renumber while keeping episode number 0 as is // 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 } res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
seasonGroups.keys.sortedBy { it.toInt() } seasonGroups.keys.sortedBy { it.toInt() }
.flatMap { season -> .flatMap { season ->
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode -> seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0 if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
val potentialNumber = val potentialNumber =
AnimeNameAdapter.findEpisodeNumber(episode.name) AnimeNameAdapter.findEpisodeNumber(episode.name)
if (potentialNumber != null) { if (potentialNumber != null) {
episode.episode_number = potentialNumber episode.episode_number = potentialNumber
} else { } else {
episode.episode_number = episodeCounter episode.episode_number = episodeCounter
}
episodeCounter++
} }
episodeCounter++ episode
} } ?: emptyList()
episode }
} ?: emptyList()
}
} }
return sortedEpisodes.map { SEpisodeToEpisode(it) } return sortedEpisodes.map { SEpisodeToEpisode(it) }
} catch (e: Exception) { } catch (e: Exception) {
@ -135,6 +210,19 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
return emptyList() 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( override suspend fun loadVideoServers(
episodeLink: String, episodeLink: String,
extra: Map<String, String>?, extra: Map<String, String>?,
@ -177,7 +265,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
} catch (e: CloudflareBypassException) { } catch (e: CloudflareBypassException) {
logger("Exception in search: $e") logger("Exception in search: $e")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackString( "Failed to bypass Cloudflare") snackString("Failed to bypass Cloudflare")
} }
emptyList() emptyList()
} catch (e: Exception) { } catch (e: Exception) {
@ -626,8 +714,6 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
// If the format is still undetermined, log an error // If the format is still undetermined, log an error
if (format == null) { if (format == null) {
logger("Unknown video format: $videoUrl") logger("Unknown video format: $videoUrl")
//FirebaseCrashlytics.getInstance()
// .recordException(Exception("Unknown video format: $videoUrl"))
format = VideoType.CONTAINER format = VideoType.CONTAINER
} }
val headersMap: Map<String, String> = val headersMap: Map<String, String> =

View file

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

View file

@ -1,19 +1,21 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.content.Context
import ani.dantotsu.Lazier import ani.dantotsu.Lazier
import ani.dantotsu.lazyList 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 eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
object MangaSources : MangaReadSources() { object MangaSources : MangaReadSources() {
override var list: List<Lazier<BaseParser>> = emptyList() 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) { suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) pinnedMangaSources =
pinnedMangaSources = sharedPrefs.getStringSet("pinned_manga_sources", emptySet()) ?: emptySet() PrefManager.getNullableVal<List<String>>(PrefName.MangaSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow // Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first() val initialExtensions = fromExtensions.first()
@ -24,7 +26,10 @@ object MangaSources : MangaReadSources() {
// Update as StateFlow emits new values // Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->
list = sortPinnedMangaSources(createParsersFromExtensions(extensions), pinnedMangaSources) + Lazier( list = sortPinnedMangaSources(
createParsersFromExtensions(extensions),
pinnedMangaSources
) + Lazier(
{ OfflineMangaParser() }, { OfflineMangaParser() },
"Downloaded" "Downloaded"
) )
@ -47,21 +52,21 @@ object MangaSources : MangaReadSources() {
} }
} }
private fun sortPinnedMangaSources(Sources: List<Lazier<BaseParser>>, pinnedMangaSources: Set<String>): List<Lazier<BaseParser>> { private fun sortPinnedMangaSources(
//find the pinned sources sources: List<Lazier<BaseParser>>,
val pinnedSources = Sources.filter { pinnedMangaSources.contains(it.name) } pinnedMangaSources: List<String>
//find the unpinned sources ): List<Lazier<BaseParser>> {
val unpinnedSources = Sources.filter { !pinnedMangaSources.contains(it.name) } val pinnedSourcesMap = sources.filter { pinnedMangaSources.contains(it.name) }
//put the pinned sources at the top of the list .associateBy { it.name }
return pinnedSources + unpinnedSources val orderedPinnedSources = pinnedMangaSources.mapNotNull { name ->
pinnedSourcesMap[name]
}
val unpinnedSources = sources.filterNot { pinnedMangaSources.contains(it.name) }
return orderedPinnedSources + unpinnedSources
} }
} }
object HMangaSources : MangaReadSources() { object HMangaSources : MangaReadSources() {
val aList: List<Lazier<BaseParser>> = lazyList() private val aList: List<Lazier<BaseParser>> = lazyList()
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
//todo
}
override val list = listOf(aList, MangaSources.list).flatten() 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.Lazier
import ani.dantotsu.parsers.novel.DynamicNovelParser import ani.dantotsu.parsers.novel.DynamicNovelParser
import ani.dantotsu.parsers.novel.NovelExtension 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.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
object NovelSources : NovelReadSources() { object NovelSources : NovelReadSources() {
override var list: List<Lazier<BaseParser>> = emptyList() override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedNovelSources: List<String> = emptyList()
suspend fun init(fromExtensions: StateFlow<List<NovelExtension.Installed>>) { suspend fun init(fromExtensions: StateFlow<List<NovelExtension.Installed>>) {
pinnedNovelSources =
PrefManager.getNullableVal<List<String>>(PrefName.NovelSourcesOrder, null)
?: emptyList()
// Initialize with the first value from StateFlow // Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first() val initialExtensions = fromExtensions.first()
list = createParsersFromExtensions(initialExtensions) + Lazier( list = createParsersFromExtensions(initialExtensions) + Lazier(
@ -20,13 +27,25 @@ object NovelSources : NovelReadSources() {
// Update as StateFlow emits new values // Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->
list = createParsersFromExtensions(extensions) + Lazier( list = sortPinnedNovelSources(
createParsersFromExtensions(extensions),
pinnedNovelSources
) + Lazier(
{ OfflineNovelParser() }, { OfflineNovelParser() },
"Downloaded" "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>> { private fun createParsersFromExtensions(extensions: List<NovelExtension.Installed>): List<Lazier<BaseParser>> {
Log.d("NovelSources", "createParsersFromExtensions") Log.d("NovelSources", "createParsersFromExtensions")
Log.d("NovelSources", extensions.toString()) Log.d("NovelSources", extensions.toString())
@ -35,4 +54,17 @@ object NovelSources : NovelReadSources() {
Lazier({ DynamicNovelParser(extension) }, name) 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 name = "Offline"
override val saveName = "Offline" override val saveName = "Offline"
override val hostUrl = "Offline" override val hostUrl = "Offline"
override val isDubAvailableSeparately = false
override val isNSFW = false override val isNSFW = false
override suspend fun loadEpisodes( override suspend fun loadEpisodes(
@ -119,7 +118,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
val sublist = getSubtitle( val sublist = getSubtitle(
videoServer.extraData?.get("title") ?: "", videoServer.extraData?.get("title") ?: "",
videoServer.extraData?.get("episode") ?: "" videoServer.extraData?.get("episode") ?: ""
)?: emptyList() ) ?: emptyList()
//we need to return a "fake" video so that the app doesn't crash //we need to return a "fake" video so that the app doesn't crash
val video = Video( val video = Video(
null, null,

View file

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

View file

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

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