diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index e6b3672cea..cd1d259b4e 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -16,12 +16,50 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v3 + with: + fetch-depth: 0 + + + - name: Download last SHA artifact + uses: dawidd6/action-download-artifact@v3 + with: + workflow: beta.yml + name: last-sha + path: . + + continue-on-error: true + + - name: Get Commits Since Last Run + run: | + if [ -f last_sha.txt ]; then + LAST_SHA=$(cat last_sha.txt) + else + # Fallback to first commit if no previous SHA available + LAST_SHA=$(git rev-list --max-parents=0 HEAD) + fi + echo "Commits since $LAST_SHA:" + # Accumulate commit logs in a shell variable + COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"%h - %s") + # URL-encode the newline characters for GitHub Actions + COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}" + COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}" + COMMIT_LOGS="${COMMIT_LOGS//$'\r'/'%0D'}" + # Append the encoded commit logs to the COMMIT_LOG environment variable + echo "COMMIT_LOG=${COMMIT_LOGS}" >> $GITHUB_ENV + # Debugging: Print the variable to check its content + echo "$COMMIT_LOGS" + shell: /usr/bin/bash -e {0} + env: + CI: true + + - name: Save Current SHA for Next Run + run: echo ${{ github.sha }} > last_sha.txt - name: Set variables run: | VER=$(grep -E -o "versionName \".*\"" app/build.gradle | sed -e 's/versionName //g' | tr -d '"') SHA=${{ github.sha }} - VERSION="$VER.${SHA:0:7}" + VERSION="$VER+${SHA:0:7}" echo "Version $VERSION" echo "VERSION=$VERSION" >> $GITHUB_ENV @@ -31,30 +69,54 @@ jobs: distribution: 'temurin' java-version: 17 cache: gradle - + - name: Decode Keystore File run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore - + - name: List files in the directory run: ls -l - + - name: Make gradlew executable run: chmod +x ./gradlew - + - name: Build with Gradle - run: ./gradlew assembleDebug -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} - + run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} + - name: Upload a Build Artifact uses: actions/upload-artifact@v3.0.0 with: name: Dantotsu - path: "app/build/outputs/apk/debug/app-debug.apk" - - - name: Upload APK to Discord + path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk" + + - name: Upload APK to Discord and Telegram shell: bash run: | - contentbody=$( jq -Rsa . <<< "${{ github.event.head_commit.message }}" ) - curl -F "payload_json={\"content\":\" Debug-Build: <@719439449423085569> **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }} + #Discord + commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g') + # Truncate commit messages if they are too long + max_length=1900 # Adjust this value as needed + if [ ${#commit_messages} -gt $max_length ]; then + commit_messages="${commit_messages:0:$max_length}... (truncated)" + fi + contentbody=$( jq -nc --arg msg "Alpha-Build: <@719439449423085569> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' ) + curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }} + + #Telegram + curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ + -F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ + -F "caption=[Alpha-Build: ${VERSION}] Change logs :${commit_messages}" \ + https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument + + env: + COMMIT_LOG: ${{ env.COMMIT_LOG }} + VERSION: ${{ env.VERSION }} + + - name: Upload Current SHA as Artifact + uses: actions/upload-artifact@v2 + with: + name: last-sha + path: last_sha.txt + - name: Delete Old Pre-Releases id: delete-pre-releases diff --git a/app/.gitignore b/app/.gitignore index 92118fcdea..6a334e3068 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,4 +1,8 @@ /build /debug /debug/output-metadata.json +/alpha +/alpha/output-metadata.json +/google/* +/fdroid/* /release \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index df3a788932..1762fa5413 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,9 @@ plugins { id 'com.android.application' - id 'com.google.gms.google-services' - id 'com.google.firebase.crashlytics' id 'kotlin-android' id 'kotlinx-serialization' id 'org.jetbrains.kotlin.android' id 'com.google.devtools.ksp' - } def gitCommitHash = providers.exec { @@ -20,14 +17,39 @@ android { applicationId "ani.dantotsu" minSdk 23 targetSdk 34 - versionCode ((System.currentTimeMillis() / 60000).toInteger()) - versionName "2.0.0-beta01-iv1" + versionCode((System.currentTimeMillis() / 60000).toInteger()) + versionName "2.1.0" + versionCode 210000000 signingConfig signingConfigs.debug } + flavorDimensions "store" + productFlavors { + fdroid { + // F-Droid specific configuration + dimension "store" + versionNameSuffix "-fdroid" + } + google { + // Google Play specific configuration + dimension "store" + isDefault true + apply plugin: 'com.google.gms.google-services' + apply plugin: 'com.google.firebase.crashlytics' + } + } + buildTypes { + alpha { + applicationIdSuffix ".beta" // keep as beta by popular request + versionNameSuffix "-alpha01" + manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"] + debuggable System.getenv("CI") == null + isDefault true + } debug { applicationIdSuffix ".beta" + versionNameSuffix "-beta01" manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta", icon_placeholder_round: "@mipmap/ic_launcher_beta_round"] debuggable System.getenv("CI") == null } @@ -39,6 +61,7 @@ android { } buildFeatures { viewBinding true + buildConfig true } compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -52,6 +75,11 @@ android { } dependencies { + + // FireBase + googleImplementation platform('com.google.firebase:firebase-bom:32.2.3') + googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.0' + googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.1' // Core implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.browser:browser:1.7.0' @@ -67,7 +95,7 @@ dependencies { implementation 'com.github.Blatzar:NiceHttp:0.4.4' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2' implementation 'androidx.preference:preference-ktx:1.2.1' - implementation 'androidx.webkit:webkit:1.9.0' + implementation 'androidx.webkit:webkit:1.10.0' // Glide ext.glide_version = '4.16.0' @@ -77,13 +105,8 @@ dependencies { implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version" implementation 'jp.wasabeef:glide-transformations:4.3.0' -// FireBase - implementation platform('com.google.firebase:firebase-bom:32.2.3') - implementation 'com.google.firebase:firebase-analytics-ktx:21.5.0' - implementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.0' - // Exoplayer - ext.exo_version = '1.2.0' + ext.exo_version = '1.2.1' implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-ui:$exo_version" implementation "androidx.media3:media3-exoplayer-hls:$exo_version" diff --git a/app/google-services.json b/app/google-services.json index 7e54b99aff..b53de23d4b 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -43,6 +43,25 @@ } } }, + { + "client_info": { + "mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf", + "android_client_info": { + "package_name": "ani.dantotsu.alpha" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCiXo_q4S2ofA5oCztsoLnlDqJi3GtTJjY" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, { "client_info": { "mobilesdk_app_id": "1:1039200814590:android:40e14720ee97917e1aacaf", diff --git a/app/src/alpha/res/drawable/anim_splash.xml b/app/src/alpha/res/drawable/anim_splash.xml new file mode 100644 index 0000000000..ac1d1ee91f --- /dev/null +++ b/app/src/alpha/res/drawable/anim_splash.xml @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/alpha/res/values/strings.xml b/app/src/alpha/res/values/strings.xml new file mode 100644 index 0000000000..9a2e65a240 --- /dev/null +++ b/app/src/alpha/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Dantotsu α + diff --git a/app/src/main/res/drawable/anim_splash_beta.xml b/app/src/debug/res/drawable/anim_splash.xml similarity index 86% rename from app/src/main/res/drawable/anim_splash_beta.xml rename to app/src/debug/res/drawable/anim_splash.xml index 3338cb411b..43638d8de3 100644 --- a/app/src/main/res/drawable/anim_splash_beta.xml +++ b/app/src/debug/res/drawable/anim_splash.xml @@ -1,5 +1,4 @@ - + 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 128 128 L 640 128 L 640 639.96 L 128 639.96 Z" + 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" /> @@ -43,12 +42,12 @@ android:rotation="-90"> + 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" /> + 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 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 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" /> + android:scaleY="1.2" /> + 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" /> @@ -100,9 +99,9 @@ android:translateX="-360"> + 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" /> + 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" /> + android:scaleY="3"> + 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" /> @@ -138,19 +137,19 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -158,177 +157,177 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -336,19 +335,19 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -356,21 +355,21 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/fdroid/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt b/app/src/fdroid/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt new file mode 100644 index 0000000000..467fe71a31 --- /dev/null +++ b/app/src/fdroid/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt @@ -0,0 +1,9 @@ +package ani.dantotsu.connections.crashlytics + +class CrashlyticsFactory { + companion object { + fun createCrashlytics(): CrashlyticsInterface { + return CrashlyticsStub() + } + } +} \ No newline at end of file diff --git a/app/src/fdroid/java/ani/dantotsu/others/AppUpdater.kt b/app/src/fdroid/java/ani/dantotsu/others/AppUpdater.kt new file mode 100644 index 0000000000..bb9893f606 --- /dev/null +++ b/app/src/fdroid/java/ani/dantotsu/others/AppUpdater.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/google/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt b/app/src/google/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt new file mode 100644 index 0000000000..2a3715d96d --- /dev/null +++ b/app/src/google/java/ani/dantotsu/connections/crashlytics/CrashlyticsFactory.kt @@ -0,0 +1,9 @@ +package ani.dantotsu.connections.crashlytics + +class CrashlyticsFactory { + companion object { + fun createCrashlytics(): CrashlyticsInterface { + return FirebaseCrashlytics() + } + } +} \ No newline at end of file diff --git a/app/src/google/java/ani/dantotsu/connections/crashlytics/FirebaseCrashlytics.kt b/app/src/google/java/ani/dantotsu/connections/crashlytics/FirebaseCrashlytics.kt new file mode 100644 index 0000000000..64ab524ffe --- /dev/null +++ b/app/src/google/java/ani/dantotsu/connections/crashlytics/FirebaseCrashlytics.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/AppUpdater.kt b/app/src/google/java/ani/dantotsu/others/AppUpdater.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/others/AppUpdater.kt rename to app/src/google/java/ani/dantotsu/others/AppUpdater.kt index eea658e02f..3c64edbe75 100644 --- a/app/src/main/java/ani/dantotsu/others/AppUpdater.kt +++ b/app/src/google/java/ani/dantotsu/others/AppUpdater.kt @@ -15,6 +15,7 @@ import androidx.core.content.FileProvider import androidx.core.content.getSystemService import androidx.fragment.app.FragmentActivity import ani.dantotsu.* +import ani.dantotsu.settings.saving.PrefManager import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.Dispatchers @@ -50,7 +51,7 @@ object AppUpdater { } logger("Git Version : $version") - val dontShow = loadData("dont_ask_for_update_$version") ?: false + val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false) if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread { CustomBottomDialog.newInstance().apply { setTitleText( @@ -71,7 +72,7 @@ object AppUpdater { false ) { isChecked -> if (isChecked) { - saveData("dont_ask_for_update_$version", true) + PrefManager.setCustomVal("dont_ask_for_update_$version", true) } } setPositiveButton(currContext()!!.getString(R.string.lets_go)) { @@ -186,7 +187,7 @@ object AppUpdater { return true } - fun openApk(context: Context, uri: Uri) { + private fun openApk(context: Context, uri: Uri) { try { uri.path?.let { val contentUri = FileProvider.getUriForFile( diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c61e37dd07..145c6febb4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,8 @@ android:required="false" /> - @@ -51,7 +52,7 @@ android:icon="${icon_placeholder}" android:label="@string/app_name" android:largeHeap="true" - android:requestLegacyExternalStorage="true" + android:requestLegacyExternalStorage="false" android:roundIcon="${icon_placeholder_round}" android:supportsRtl="true" android:theme="@style/Theme.Dantotsu" @@ -289,13 +290,13 @@ + android:exported="true" + android:permission="android.permission.BIND_REMOTEVIEWS" /> - + @@ -317,19 +318,22 @@ android:name=".download.novel.NovelDownloaderService" android:exported="false" android:foregroundServiceType="dataSync" /> - - + - + \ No newline at end of file diff --git a/app/src/main/ic_launcher_alpha-playstore.png b/app/src/main/ic_launcher_alpha-playstore.png new file mode 100644 index 0000000000..fc55f0acae Binary files /dev/null and b/app/src/main/ic_launcher_alpha-playstore.png differ diff --git a/app/src/main/java/ani/dantotsu/App.kt b/app/src/main/java/ani/dantotsu/App.kt index 5b6869ea99..5c11e74f75 100644 --- a/app/src/main/java/ani/dantotsu/App.kt +++ b/app/src/main/java/ani/dantotsu/App.kt @@ -8,16 +8,16 @@ import androidx.multidex.MultiDex import androidx.multidex.MultiDexApplication import ani.dantotsu.aniyomi.anime.custom.AppModule import ani.dantotsu.aniyomi.anime.custom.PreferenceModule +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.others.DisabledReports import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.MangaSources import ani.dantotsu.parsers.NovelSources import ani.dantotsu.parsers.novel.NovelExtensionManager import ani.dantotsu.settings.SettingsActivity +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.google.android.material.color.DynamicColors -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager @@ -51,36 +51,35 @@ class App : MultiDexApplication() { override fun onCreate() { super.onCreate() - val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false) + + PrefManager.init(this) + Injekt.importModule(AppModule(this)) + Injekt.importModule(PreferenceModule(this)) + + val crashlytics = Injekt.get() + crashlytics.initialize(this) + + val useMaterialYou: Boolean = PrefManager.getVal(PrefName.UseMaterialYou) if (useMaterialYou) { DynamicColors.applyToActivitiesIfAvailable(this) - //TODO: HarmonizedColors } registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks) - Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports) - getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getBoolean("shared_user_id", true).let { + crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports) + (PrefManager.getVal(PrefName.SharedUserID) as Boolean).let { if (!it) return@let - val dUsername = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getString("discord_username", null) - val aUsername = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getString("anilist_username", null) - if (dUsername != null || aUsername != null) { - Firebase.crashlytics.setUserId("$dUsername - $aUsername") + val dUsername = PrefManager.getVal(PrefName.DiscordUserName, null as String?) + val aUsername = PrefManager.getVal(PrefName.AnilistUserName, null as String?) + if (dUsername != null) { + crashlytics.setCustomKey("dUsername", dUsername) + } + if (aUsername != null) { + crashlytics.setCustomKey("aUsername", aUsername) } } - FirebaseCrashlytics.getInstance().setCustomKey("device Info", SettingsActivity.getDeviceInfo()) + crashlytics.setCustomKey("device Info", SettingsActivity.getDeviceInfo()) + - Injekt.importModule(AppModule(this)) - Injekt.importModule(PreferenceModule(this)) initializeNetwork(baseContext) @@ -97,13 +96,13 @@ class App : MultiDexApplication() { animeScope.launch { animeExtensionManager.findAvailableExtensions() logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}") - AnimeSources.init(animeExtensionManager.installedExtensionsFlow, this@App) + AnimeSources.init(animeExtensionManager.installedExtensionsFlow) } val mangaScope = CoroutineScope(Dispatchers.Default) mangaScope.launch { mangaExtensionManager.findAvailableExtensions() logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}") - MangaSources.init(mangaExtensionManager.installedExtensionsFlow, this@App) + MangaSources.init(mangaExtensionManager.installedExtensionsFlow) } val novelScope = CoroutineScope(Dispatchers.Default) novelScope.launch { diff --git a/app/src/main/java/ani/dantotsu/Functions.kt b/app/src/main/java/ani/dantotsu/Functions.kt index 9578aed668..623b8aa74b 100644 --- a/app/src/main/java/ani/dantotsu/Functions.kt +++ b/app/src/main/java/ani/dantotsu/Functions.kt @@ -11,11 +11,12 @@ import android.content.ClipboardManager import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources.getSystem import android.graphics.Bitmap import android.graphics.Color -import android.graphics.drawable.ColorDrawable +import android.Manifest import android.media.MediaScannerConnection import android.net.ConnectivityManager import android.net.NetworkCapabilities.* @@ -31,8 +32,11 @@ import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.animation.* import android.widget.* +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.FileProvider import androidx.core.math.MathUtils.clamp @@ -45,12 +49,15 @@ import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.BuildConfig.APPLICATION_ID import ani.dantotsu.connections.anilist.Genre import ani.dantotsu.connections.anilist.api.FuzzyDate +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.ItemCountDownBinding import ani.dantotsu.media.Media import ani.dantotsu.parsers.ShowResponse -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.settings.saving.internal.PreferenceKeystore +import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt import ani.dantotsu.subcriptions.NotificationClickReceiver -import ani.dantotsu.themes.ThemeManager import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade @@ -60,10 +67,11 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.internal.ViewUtils import com.google.android.material.snackbar.Snackbar -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.* import nl.joery.animatedbottombar.AnimatedBottomBar +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.* import java.lang.Runnable import java.lang.reflect.Field @@ -105,63 +113,22 @@ fun logger(e: Any?, print: Boolean = true) { println(e) } -fun saveData(fileName: String, data: Any?, context: Context? = null) { - tryWith { - val a = context ?: currContext() - if (a != null) { - val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE) - val os = ObjectOutputStream(fos) - os.writeObject(data) - os.close() - fos.close() - } - } -} - -@Suppress("UNCHECKED_CAST") -fun loadData(fileName: String, context: Context? = null, toast: Boolean = true): T? { - val a = context ?: currContext() - try { - if (a?.fileList() != null) - if (fileName in a.fileList()) { - val fileIS: FileInputStream = a.openFileInput(fileName) - val objIS = ObjectInputStream(fileIS) - val data = objIS.readObject() as T - objIS.close() - fileIS.close() - return data - } - } catch (e: Exception) { - if (toast) snackString(a?.getString(R.string.error_loading_data, fileName)) - //try to delete the file - try { - a?.deleteFile(fileName) - } catch (e: Exception) { - FirebaseCrashlytics.getInstance().log("Failed to delete file $fileName") - FirebaseCrashlytics.getInstance().recordException(e) - } - e.printStackTrace() - } - return null -} fun initActivity(a: Activity) { val window = a.window WindowCompat.setDecorFitsSystemWindows(window, false) - val uiSettings = loadData("ui_settings", toast = false) - ?: UserInterfaceSettings().apply { - saveData("ui_settings", this) - } - uiSettings.darkMode.apply { + val darkMode = PrefManager.getVal(PrefName.DarkMode) + val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode) + darkMode.apply { AppCompatDelegate.setDefaultNightMode( when (this) { - true -> AppCompatDelegate.MODE_NIGHT_YES - false -> AppCompatDelegate.MODE_NIGHT_NO + 2 -> AppCompatDelegate.MODE_NIGHT_YES + 1 -> AppCompatDelegate.MODE_NIGHT_NO else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } ) } - if (uiSettings.immersiveMode) { + if (immersiveMode) { if (navBarHeight == 0) { ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content)) ?.apply { @@ -216,7 +183,11 @@ open class BottomSheetDialogFragment : BottomSheetDialogFragment() { } val typedValue = TypedValue() val theme = requireContext().theme - theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurfaceInverse, typedValue, true) + theme.resolveAttribute( + com.google.android.material.R.attr.colorSurface, + typedValue, + true + ) window.navigationBarColor = typedValue.data } @@ -325,20 +296,20 @@ class InputFilterMinMax( } -class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) : +class ZoomOutPageTransformer() : ViewPager2.PageTransformer { override fun transformPage(view: View, position: Float) { - if (position == 0.0f && uiSettings.layoutAnimations) { + if (position == 0.0f && PrefManager.getVal(PrefName.LayoutAnimations)) { setAnimation( view.context, view, - uiSettings, 300, floatArrayOf(1.3f, 1f, 1.3f, 1f), 0.5f to 0f ) ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f) - .setDuration((200 * uiSettings.animationSpeed).toLong()).start() + .setDuration((200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong()) + .start() } } } @@ -346,12 +317,11 @@ class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) : fun setAnimation( context: Context, viewToAnimate: View, - uiSettings: UserInterfaceSettings, duration: Long = 150, list: FloatArray = floatArrayOf(0.0f, 1.0f, 0.0f, 1.0f), pivot: Pair = 0.5f to 0.5f ) { - if (uiSettings.layoutAnimations) { + if (PrefManager.getVal(PrefName.LayoutAnimations)) { val anim = ScaleAnimation( list[0], list[1], @@ -362,7 +332,7 @@ fun setAnimation( Animation.RELATIVE_TO_SELF, pivot.second ) - anim.duration = (duration * uiSettings.animationSpeed).toLong() + anim.duration = (duration * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong() anim.setInterpolator(context, R.anim.over_shoot) viewToAnimate.startAnimation(anim) } @@ -615,7 +585,7 @@ fun openLinkInBrowser(link: String?) { } } -fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) { +fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Activity) { FileProvider.getUriForFile( context, "$APPLICATION_ID.provider", @@ -627,6 +597,105 @@ fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Context) { ) } +fun savePrefsToDownloads( + title: String, + serialized: String, + context: Activity, + password: CharArray? = null +) { + FileProvider.getUriForFile( + context, + "$APPLICATION_ID.provider", + if (password != null) { + savePrefs( + serialized, + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath, + title, + context, + password + ) ?: return + } else { + savePrefs( + serialized, + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath, + title, + context + ) ?: return + } + ) +} + +fun savePrefs(serialized: String, path: String, title: String, context: Context): File? { + var file = File(path, "$title.ani") + var counter = 1 + while (file.exists()) { + file = File(path, "${title}_${counter}.ani") + counter++ + } + + return try { + file.writeText(serialized) + scanFile(file.absolutePath, context) + toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath))) + file + } catch (e: Exception) { + snackString("Failed to save settings: ${e.localizedMessage}") + null + } +} + +fun savePrefs( + serialized: String, + path: String, + title: String, + context: Context, + password: CharArray +): File? { + var file = File(path, "$title.ani") + var counter = 1 + while (file.exists()) { + file = File(path, "${title}_${counter}.sani") + counter++ + } + + val salt = generateSalt() + + return try { + val encryptedData = PreferenceKeystore.encryptWithPassword(password, serialized, salt) + + // Combine salt and encrypted data + val dataToSave = salt + encryptedData + + file.writeBytes(dataToSave) + scanFile(file.absolutePath, context) + toast(String.format(context.getString(R.string.saved_to_path, file.absolutePath))) + file + } catch (e: Exception) { + snackString("Failed to save settings: ${e.localizedMessage}") + null + } +} + +fun downloadsPermission(activity: AppCompatActivity): Boolean { + val permissions = arrayOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + val requiredPermissions = permissions.filter { + ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED + }.toTypedArray() + + return if (requiredPermissions.isNotEmpty()) { + ActivityCompat.requestPermissions(activity, requiredPermissions, DOWNLOADS_PERMISSION_REQUEST_CODE) + false + } else { + true + } +} + +private const val DOWNLOADS_PERMISSION_REQUEST_CODE = 100 + fun shareImage(title: String, bitmap: Bitmap, context: Context) { val contentUri = FileProvider.getUriForFile( @@ -743,10 +812,11 @@ fun MutableMap.checkGenreTime(genre: String): Boolean { return true } -fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply { - if (uiSettings.layoutAnimations) { +fun setSlideIn() = AnimationSet(false).apply { + if (PrefManager.getVal(PrefName.LayoutAnimations)) { var animation: Animation = AlphaAnimation(0.0f, 1.0f) - animation.duration = (500 * uiSettings.animationSpeed).toLong() + val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed) + animation.duration = (500 * animationSpeed).toLong() animation.interpolator = AccelerateDecelerateInterpolator() addAnimation(animation) @@ -757,16 +827,17 @@ fun setSlideIn(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply { Animation.RELATIVE_TO_SELF, 0f ) - animation.duration = (750 * uiSettings.animationSpeed).toLong() + animation.duration = (750 * animationSpeed).toLong() animation.interpolator = OvershootInterpolator(1.1f) addAnimation(animation) } } -fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply { - if (uiSettings.layoutAnimations) { +fun setSlideUp() = AnimationSet(false).apply { + if (PrefManager.getVal(PrefName.LayoutAnimations)) { var animation: Animation = AlphaAnimation(0.0f, 1.0f) - animation.duration = (500 * uiSettings.animationSpeed).toLong() + val animationSpeed: Float = PrefManager.getVal(PrefName.AnimationSpeed) + animation.duration = (500 * animationSpeed).toLong() animation.interpolator = AccelerateDecelerateInterpolator() addAnimation(animation) @@ -777,7 +848,7 @@ fun setSlideUp(uiSettings: UserInterfaceSettings) = AnimationSet(false).apply { Animation.RELATIVE_TO_SELF, 0f ) - animation.duration = (750 * uiSettings.animationSpeed).toLong() + animation.duration = (750 * animationSpeed).toLong() animation.interpolator = OvershootInterpolator(1.1f) addAnimation(animation) } @@ -839,7 +910,7 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul } } catch (e: Exception) { logger(e.stackTraceToString()) - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) } } @@ -964,8 +1035,7 @@ const val INCOGNITO_CHANNEL_ID = 26 fun incognitoNotification(context: Context) { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val incognito = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("incognito", false) + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) if (incognito) { val intent = Intent(context, NotificationClickReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 2e8cfccee4..8f5dd79f59 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -2,7 +2,6 @@ package ani.dantotsu import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.content.Context import android.content.Intent import android.graphics.drawable.Animatable import android.graphics.drawable.GradientDrawable @@ -45,15 +44,13 @@ import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.others.CustomBottomDialog -import ani.dantotsu.others.LangSet -import ani.dantotsu.others.SharedPreferenceBooleanLiveData -import ani.dantotsu.parsers.novel.NovelExtensionManager -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefManager.asLiveBool +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription import ani.dantotsu.themes.ThemeManager import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager -import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.Dispatchers @@ -73,18 +70,16 @@ class MainActivity : AppCompatActivity() { private val scope = lifecycleScope private var load = false - private var uiSettings = UserInterfaceSettings() - @SuppressLint("InternalInsetResource", "DiscouragedApi") @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) { ThemeManager(this).applyTheme() - LangSet.setLocale(this) + super.onCreate(savedInstanceState) //get FRAGMENT_CLASS_NAME from intent - val FRAGMENT_CLASS_NAME = intent.getStringExtra("FRAGMENT_CLASS_NAME") + val fragment = intent.getStringExtra("FRAGMENT_CLASS_NAME") binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -98,12 +93,8 @@ class MainActivity : AppCompatActivity() { backgroundDrawable.setColor(semiTransparentColor) _bottomBar.background = backgroundDrawable } - val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - val colorOverflow = sharedPreferences.getBoolean("colorOverflow", false) - if (!colorOverflow) { - _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) + _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) - } val offset = try { val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android") @@ -114,11 +105,10 @@ class MainActivity : AppCompatActivity() { val layoutParams = binding.incognito.layoutParams as ViewGroup.MarginLayoutParams layoutParams.topMargin = 11 * offset / 12 binding.incognito.layoutParams = layoutParams - incognitoLiveData = SharedPreferenceBooleanLiveData( - sharedPreferences, - "incognito", + incognitoLiveData = PrefManager.getLiveVal( + PrefName.Incognito, false - ) + ).asLiveBool() incognitoLiveData.observe(this) { if (it) { val slideDownAnim = ObjectAnimator.ofFloat( @@ -162,7 +152,9 @@ class MainActivity : AppCompatActivity() { } val preferences: SourcePreferences = Injekt.get() - if (preferences.animeExtensionUpdatesCount().get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0) { + if (preferences.animeExtensionUpdatesCount() + .get() > 0 || preferences.mangaExtensionUpdatesCount().get() > 0 + ) { Toast.makeText( this, "You have extension updates available!", @@ -213,24 +205,22 @@ class MainActivity : AppCompatActivity() { binding.root.doOnAttach { initActivity(this) - uiSettings = loadData("ui_settings") ?: uiSettings - selectedOption = if (FRAGMENT_CLASS_NAME != null) { - when (FRAGMENT_CLASS_NAME) { + selectedOption = if (fragment != null) { + when (fragment) { AnimeFragment::class.java.name -> 0 HomeFragment::class.java.name -> 1 MangaFragment::class.java.name -> 2 else -> 1 } } else { - uiSettings.defaultStartUpTab + PrefManager.getVal(PrefName.DefaultStartUpTab) } binding.includedNavbar.navbarContainer.updateLayoutParams { bottomMargin = navBarHeight } } - val offlineMode = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("offlineMode", false) + val offlineMode: Boolean = PrefManager.getVal(PrefName.OfflineMode) if (!isOnline(this)) { snackString(this@MainActivity.getString(R.string.no_internet_connection)) startActivity(Intent(this, NoInternet::class.java)) @@ -251,7 +241,7 @@ class MainActivity : AppCompatActivity() { mainViewPager.isUserInputEnabled = false mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) - mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) + mainViewPager.setPageTransformer(ZoomOutPageTransformer()) navbar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { override fun onTabSelected( @@ -305,7 +295,7 @@ class MainActivity : AppCompatActivity() { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (loadData("allow_opening_links", this) != true) { + if (!(PrefManager.getVal(PrefName.AllowOpeningLinks) as Boolean)) { CustomBottomDialog.newInstance().apply { title = "Allow Dantotsu to automatically open Anilist & MAL Links?" val md = "Open settings & click +Add Links & select Anilist & Mal urls" @@ -317,18 +307,19 @@ class MainActivity : AppCompatActivity() { }) setNegativeButton(this@MainActivity.getString(R.string.no)) { - saveData("allow_opening_links", true, this@MainActivity) + PrefManager.setVal(PrefName.AllowOpeningLinks, true) dismiss() } setPositiveButton(this@MainActivity.getString(R.string.yes)) { - saveData("allow_opening_links", true, this@MainActivity) + PrefManager.setVal(PrefName.AllowOpeningLinks, true) tryWith(true) { startActivity( Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS) .setData(Uri.parse("package:$packageName")) ) } + dismiss() } }.show(supportFragmentManager, "dialog") } @@ -342,7 +333,7 @@ class MainActivity : AppCompatActivity() { while (downloadCursor.moveToNext()) { val download = downloadCursor.download Log.e("Downloader", download.request.uri.toString()) - Log.e("Downloader", download.request.id.toString()) + Log.e("Downloader", download.request.id) Log.e("Downloader", download.request.mimeType.toString()) Log.e("Downloader", download.request.data.size.toString()) Log.e("Downloader", download.bytesDownloaded.toString()) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt index 19e45a589f..c3a190087f 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt +++ b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt @@ -2,11 +2,11 @@ package ani.dantotsu.aniyomi.anime.custom import android.app.Application -import android.content.Context import androidx.annotation.OptIn import androidx.core.content.ContextCompat import androidx.media3.common.util.UnstableApi import androidx.media3.database.StandaloneDatabaseProvider +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.download.DownloadsManager import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.parsers.novel.NovelExtensionManager @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.network.NetworkHelper -import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager import kotlinx.serialization.json.Json @@ -36,7 +35,7 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { DownloadsManager(app) } - addSingletonFactory { NetworkHelper(app, get()) } + addSingletonFactory { NetworkHelper(app) } addSingletonFactory { AnimeExtensionManager(app) } addSingletonFactory { MangaExtensionManager(app) } @@ -45,9 +44,6 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { AndroidAnimeSourceManager(app, get()) } addSingletonFactory { AndroidMangaSourceManager(app, get()) } - val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - addSingleton(sharedPreferences) - addSingletonFactory { Json { ignoreUnknownKeys = true @@ -57,6 +53,10 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { StandaloneDatabaseProvider(app) } + addSingletonFactory { + ani.dantotsu.connections.crashlytics.CrashlyticsFactory.createCrashlytics() + } + addSingletonFactory { MangaCache() } ContextCompat.getMainExecutor(app).execute { @@ -72,13 +72,6 @@ class PreferenceModule(val application: Application) : InjektModule { AndroidPreferenceStore(application) } - addSingletonFactory { - NetworkPreferences( - preferenceStore = get(), - verboseLogging = false, - ) - } - addSingletonFactory { SourcePreferences(get()) } diff --git a/app/src/main/java/ani/dantotsu/connections/UpdateProgress.kt b/app/src/main/java/ani/dantotsu/connections/UpdateProgress.kt index fb08c59d7e..acfd0cdbb3 100644 --- a/app/src/main/java/ani/dantotsu/connections/UpdateProgress.kt +++ b/app/src/main/java/ani/dantotsu/connections/UpdateProgress.kt @@ -6,14 +6,15 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.mal.MAL import ani.dantotsu.currContext import ani.dantotsu.media.Media +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch fun updateProgress(media: Media, number: String) { - val incognito = currContext()?.getSharedPreferences("Dantotsu", 0) - ?.getBoolean("incognito", false) ?: false + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) if (!incognito) { if (Anilist.userid != null) { CoroutineScope(Dispatchers.IO).launch { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt index 77325d0b76..117b2fa829 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt @@ -8,8 +8,10 @@ import ani.dantotsu.R import ani.dantotsu.client import ani.dantotsu.currContext import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.toast import ani.dantotsu.tryWithSuspend -import java.io.File import java.util.Calendar object Anilist { @@ -28,6 +30,8 @@ object Anilist { var genres: ArrayList? = null var tags: Map>? = null + var rateLimitReset: Long = 0 + val sortBy = listOf( "SCORE_DESC", "POPULARITY_DESC", @@ -94,15 +98,12 @@ object Anilist { } } - fun getSavedToken(context: Context): Boolean { - if ("anilistToken" in context.fileList()) { - token = File(context.filesDir, "anilistToken").readText() - return true - } - return false + fun getSavedToken(): Boolean { + token = PrefManager.getVal(PrefName.AnilistToken, null as String?) + return !token.isNullOrEmpty() } - fun removeSavedToken(context: Context) { + fun removeSavedToken() { token = null username = null adult = false @@ -111,9 +112,7 @@ object Anilist { bg = null episodesWatched = null chapterRead = null - if ("anilistToken" in context.fileList()) { - File(context.filesDir, "anilistToken").delete() - } + PrefManager.removeVal(PrefName.AnilistToken) } suspend inline fun executeQuery( @@ -125,6 +124,11 @@ object Anilist { cache: Int? = null ): T? { return tryWithSuspend { + if (rateLimitReset > System.currentTimeMillis() / 1000) { + toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") + throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") + } + val data = mapOf( "query" to query, "variables" to variables @@ -143,6 +147,16 @@ object Anilist { data = data, cacheTime = cache ?: 10 ) + if (json.code == 429) { + val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1 + val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0 + if (retry > 0) { + rateLimitReset = passedLimitReset + } + + toast("Rate limited. Try after $retry seconds") + throw Exception("Rate limited after $retry seconds") + } if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down)) if (show) println("Response : ${json.text}") json.parsed() diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 5c344b5937..14bb86b729 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -1,7 +1,7 @@ package ani.dantotsu.connections.anilist import android.app.Activity -import android.content.Context +import android.util.Base64 import ani.dantotsu.R import ani.dantotsu.checkGenreTime import ani.dantotsu.checkId @@ -12,18 +12,23 @@ import ani.dantotsu.connections.anilist.api.Page import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.currContext import ani.dantotsu.isOnline -import ani.dantotsu.loadData import ani.dantotsu.logError import ani.dantotsu.media.Author import ani.dantotsu.media.Character import ani.dantotsu.media.Media import ani.dantotsu.media.Studio import ani.dantotsu.others.MalScraper -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.Serializable import kotlin.system.measureTimeMillis class AnilistQueries { @@ -35,12 +40,7 @@ class AnilistQueries { }.also { println("time : $it") } val user = response?.data?.user ?: return false - currContext()?.let { - it.getSharedPreferences(it.getString(R.string.preference_file_key), Context.MODE_PRIVATE) - .edit() - .putString("anilist_username", user.name) - .apply() - } + PrefManager.setVal(PrefName.AnilistUserName, user.name) Anilist.userid = user.id Anilist.username = user.name @@ -281,8 +281,8 @@ class AnilistQueries { } statuses.forEach { repeat(it) } - val set = loadData>("continue_$type") - if (set != null) { + val set = PrefManager.getCustomVal>("continue_$type", setOf()).toMutableSet() + if (set.isNotEmpty()) { set.reversed().forEach { if (map.containsKey(it)) returnArray.add(map[it]!!) } @@ -355,7 +355,11 @@ class AnilistQueries { } private suspend fun bannerImage(type: String): String? { - var image = loadData("banner_$type") + //var image = loadData("banner_$type") + val image: BannerImage? = BannerImage( + PrefManager.getCustomVal("banner_${type}_url", null), + PrefManager.getCustomVal("banner_${type}_time", 0L) + ) if (image == null || image.checkTime()) { val response = executeQuery("""{ MediaListCollection(userId: ${Anilist.userid}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """) @@ -366,13 +370,9 @@ class AnilistQueries { else null } }?.flatten()?.randomOrNull() ?: return null - - image = BannerImage( - random, - System.currentTimeMillis() - ) - saveData("banner_$type", image) - return image.url + PrefManager.setCustomVal("banner_${type}_url", random) + PrefManager.setCustomVal("banner_${type}_time", System.currentTimeMillis()) + return random } else return image.url } @@ -419,11 +419,17 @@ class AnilistQueries { sorted["Favourites"] = favMedia(anime) sorted["Favourites"]?.sortWith(compareBy { it.userFavOrder }) + //favMedia doesn't fill userProgress, so we need to fill it manually by searching :( + sorted["Favourites"]?.forEach { fav -> + all.find { it.id == fav.id }?.let { + fav.userProgress = it.userProgress + } + } sorted["All"] = all - val listsort = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getString("sort_order", "score") - val sort = listsort ?: sortOrder ?: options?.rowOrder + val listSort: String = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder) + else PrefManager.getVal(PrefName.MangaListSortOrder) + val sort = listSort ?: sortOrder ?: options?.rowOrder for (i in sorted.keys) { when (sort) { "score" -> sorted[i]?.sortWith { b, a -> @@ -444,11 +450,19 @@ class AnilistQueries { } - suspend fun getGenresAndTags(activity: Activity): Boolean { - var genres: ArrayList? = loadData("genres_list", activity) - var tags: Map>? = loadData("tags_map", activity) + suspend fun getGenresAndTags(): Boolean { + var genres: ArrayList? = PrefManager.getVal>(PrefName.GenresList) + .toMutableList() as ArrayList? + val adultTags = PrefManager.getVal>(PrefName.TagsListIsAdult).toMutableList() + val nonAdultTags = + PrefManager.getVal>(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( """{GenreCollection}""", force = true, @@ -458,7 +472,7 @@ class AnilistQueries { forEach { genres?.add(it) } - saveData("genres_list", genres!!) + PrefManager.setVal(PrefName.GenresList, genres?.toSet()) } } if (tags == null) { @@ -476,10 +490,11 @@ class AnilistQueries { true to adult, false to good ) - saveData("tags_map", tags) + PrefManager.setVal(PrefName.TagsListIsAdult, adult.toSet()) + PrefManager.setVal(PrefName.TagsListNonAdult, good.toSet()) } } - return if (genres != null && tags != null) { + return if (!genres.isNullOrEmpty() && tags != null) { Anilist.genres = genres Anilist.tags = tags true @@ -496,8 +511,37 @@ class AnilistQueries { } } + private fun saveSerializableMap(prefKey: String, map: Map) { + 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 loadSerializableMap(prefKey: String): Map? { + 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 + } + } catch (e: Exception) { + return null + } + } + private suspend fun getGenreThumbnail(genre: String): Genre? { - val genres = loadData>("genre_thumb") ?: mutableMapOf() + val genres: MutableMap = + loadSerializableMap("genre_thumb")?.toMutableMap() + ?: mutableMapOf() if (genres.checkGenreTime(genre)) { try { val genreQuery = @@ -510,7 +554,7 @@ class AnilistQueries { it.bannerImage!!, System.currentTimeMillis() ) - saveData("genre_thumb", genres) + saveSerializableMap("genre_thumb", genres) return genres[genre] } } @@ -665,7 +709,7 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: page = pageInfo.currentPage.toString().toIntOrNull() ?: 0, hasNextPage = pageInfo.hasNextPage == true, ) - } else snackString(currContext()?.getString(R.string.empty_response)) + } return null } @@ -723,7 +767,7 @@ Page(page:$page,perPage:50) { if (smaller) { val response = execute()?.airingSchedules ?: return null val idArr = mutableListOf() - val listOnly = loadData("recently_list_only") ?: false + val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly) return response.mapNotNull { i -> i.media?.let { if (!idArr.contains(it.id)) diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index 4cc8451f36..5e3bacb9e7 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -5,12 +5,14 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import ani.dantotsu.BuildConfig import ani.dantotsu.R import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.mal.MAL -import ani.dantotsu.loadData import ani.dantotsu.media.Media import ani.dantotsu.others.AppUpdater +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.tryWithSuspend import kotlinx.coroutines.CoroutineScope @@ -19,12 +21,8 @@ import kotlinx.coroutines.launch suspend fun getUserId(context: Context, block: () -> Unit) { CoroutineScope(Dispatchers.IO).launch { - val sharedPref = context.getSharedPreferences( - context.getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ) - val token = sharedPref.getString("discord_token", null) - val userid = sharedPref.getString("discord_id", null) + val token = PrefManager.getVal(PrefName.DiscordToken, null as String?) + val userid = PrefManager.getVal(PrefName.DiscordId, null as String?) if (userid == null && token != null) { /*if (!Discord.getUserData()) snackString(context.getString(R.string.error_loading_discord_user_data))*/ @@ -100,11 +98,13 @@ class AnilistHomeViewModel : ViewModel() { suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations()) suspend fun loadMain(context: FragmentActivity) { - Anilist.getSavedToken(context) + Anilist.getSavedToken() MAL.getSavedToken(context) Discord.getSavedToken(context) - if (loadData("check_update") != false) AppUpdater.check(context) - genres.postValue(Anilist.query.getGenresAndTags(context)) + if (!BuildConfig.FLAVOR.contains("fdroid")) { + if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context) + } + genres.postValue(Anilist.query.getGenresAndTags()) } val empty = MutableLiveData(null) diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Login.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Login.kt index fdba6d3a84..7512fc5d55 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Login.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Login.kt @@ -1,29 +1,26 @@ package ani.dantotsu.connections.anilist -import android.content.Context import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import ani.dantotsu.logError import ani.dantotsu.logger -import ani.dantotsu.others.LangSet +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.startMainActivity import ani.dantotsu.themes.ThemeManager class Login : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() val data: Uri? = intent?.data logger(data.toString()) try { Anilist.token = Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value - val filename = "anilistToken" - this.openFileOutput(filename, Context.MODE_PRIVATE).use { - it.write(Anilist.token!!.toByteArray()) - } + PrefManager.setVal(PrefName.AnilistToken, Anilist.token ?: "") } catch (e: Exception) { logError(e) } diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt index 30be1706ba..98d63d27ed 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt @@ -5,14 +5,13 @@ import android.net.Uri import android.os.Bundle import androidx.core.os.bundleOf import ani.dantotsu.loadMedia -import ani.dantotsu.others.LangSet import ani.dantotsu.startMainActivity import ani.dantotsu.themes.ThemeManager class UrlMedia : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() var id: Int? = intent?.extras?.getInt("media", 0) ?: 0 var isMAL = false diff --git a/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsInterface.kt b/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsInterface.kt new file mode 100644 index 0000000000..54307d245c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsInterface.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsStub.kt b/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsStub.kt new file mode 100644 index 0000000000..524d42bf51 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/connections/crashlytics/CrashlyticsStub.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt b/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt index 9545030552..c151850d2c 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt @@ -3,9 +3,10 @@ package ani.dantotsu.connections.discord import android.content.Context import android.content.Intent import android.widget.TextView -import androidx.core.content.edit import ani.dantotsu.R import ani.dantotsu.others.CustomBottomDialog +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast import ani.dantotsu.tryWith import io.noties.markwon.Markwon @@ -18,37 +19,20 @@ object Discord { var userid: String? = null var avatar: String? = null - const val TOKEN = "discord_token" fun getSavedToken(context: Context): Boolean { - val sharedPref = context.getSharedPreferences( - context.getString(R.string.preference_file_key), - Context.MODE_PRIVATE + token = PrefManager.getVal( + PrefName.DiscordToken, null as String? ) - token = sharedPref.getString(TOKEN, null) return token != null } fun saveToken(context: Context, token: String) { - val sharedPref = context.getSharedPreferences( - context.getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ) - sharedPref.edit { - putString(TOKEN, token) - commit() - } + PrefManager.setVal(PrefName.DiscordToken, token) } fun removeSavedToken(context: Context) { - val sharedPref = context.getSharedPreferences( - context.getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ) - sharedPref.edit { - remove(TOKEN) - commit() - } + PrefManager.removeVal(PrefName.DiscordToken) tryWith(true) { val dir = File(context.filesDir?.parentFile, "app_webview") diff --git a/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt b/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt index 3506ea259f..761dbeac47 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt @@ -24,6 +24,8 @@ import ani.dantotsu.R import ani.dantotsu.connections.discord.serializers.Presence import ani.dantotsu.connections.discord.serializers.User import ani.dantotsu.isOnline +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonParser @@ -149,19 +151,11 @@ class DiscordService : Service() { } fun saveProfile(response: String) { - val sharedPref = baseContext.getSharedPreferences( - baseContext.getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ) val user = json.decodeFromString(response).d.user log("User data: $user") - with(sharedPref.edit()) { - putString("discord_username", user.username) - putString("discord_id", user.id) - putString("discord_avatar", user.avatar) - apply() - } - + PrefManager.setVal(PrefName.DiscordUserName, user.username) + PrefManager.setVal(PrefName.DiscordId, user.id) + PrefManager.setVal(PrefName.DiscordAvatar, user.avatar) } override fun onBind(p0: Intent?): IBinder? = null @@ -318,17 +312,13 @@ class DiscordService : Service() { } fun getToken(context: Context): String { - val sharedPref = context.getSharedPreferences( - context.getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ) - val token = sharedPref.getString(Discord.TOKEN, null) - if (token == null) { + val token = PrefManager.getVal(PrefName.DiscordToken, null as String?) + return if (token == null) { log("WebSocket: Token not found") errorNotification("Could not set the presence", "token not found") - return "" + "" } else { - return token + token } } diff --git a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt index 98048844b8..676f11e210 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt @@ -11,7 +11,6 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import ani.dantotsu.R import ani.dantotsu.connections.discord.Discord.saveToken -import ani.dantotsu.others.LangSet import ani.dantotsu.startMainActivity import ani.dantotsu.themes.ThemeManager @@ -20,7 +19,7 @@ class Login : AppCompatActivity() { @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val process = getProcessName() diff --git a/app/src/main/java/ani/dantotsu/connections/mal/Login.kt b/app/src/main/java/ani/dantotsu/connections/mal/Login.kt index 9ec2ca5ab8..c06baf3835 100644 --- a/app/src/main/java/ani/dantotsu/connections/mal/Login.kt +++ b/app/src/main/java/ani/dantotsu/connections/mal/Login.kt @@ -8,9 +8,9 @@ import ani.dantotsu.R import ani.dantotsu.client import ani.dantotsu.connections.mal.MAL.clientId import ani.dantotsu.connections.mal.MAL.saveResponse -import ani.dantotsu.loadData import ani.dantotsu.logError -import ani.dantotsu.others.LangSet +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.startMainActivity import ani.dantotsu.themes.ThemeManager @@ -21,12 +21,12 @@ import kotlinx.coroutines.launch class Login : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() try { val data: Uri = intent?.data ?: throw Exception(getString(R.string.mal_login_uri_not_found)) - val codeChallenge: String = loadData("malCodeChallenge", this) + val codeChallenge = PrefManager.getVal(PrefName.MALCodeChallenge, null as String?) ?: throw Exception(getString(R.string.mal_login_code_challenge_not_found)) val code = data.getQueryParameter("code") ?: throw Exception(getString(R.string.mal_login_code_not_present)) diff --git a/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt b/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt index 0391cd47d0..9eaffdca70 100644 --- a/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt +++ b/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt @@ -9,13 +9,12 @@ import androidx.fragment.app.FragmentActivity import ani.dantotsu.R import ani.dantotsu.client import ani.dantotsu.currContext -import ani.dantotsu.loadData import ani.dantotsu.openLinkInBrowser -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.tryWithSuspend import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import java.io.File import java.security.SecureRandom object MAL { @@ -34,7 +33,7 @@ object MAL { .replace("/", "_") .replace("\n", "") - saveData("malCodeChallenge", codeChallenge, context) + PrefManager.setVal(PrefName.MALCodeChallenge, codeChallenge) val request = "https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$clientId&code_challenge=$codeChallenge" try { @@ -47,11 +46,9 @@ object MAL { } } - private const val MAL_TOKEN = "malToken" - private suspend fun refreshToken(): ResponseToken? { return tryWithSuspend { - val token = loadData(MAL_TOKEN) + val token = PrefManager.getNullableVal(PrefName.MALToken, null) ?: throw Exception(currContext()?.getString(R.string.refresh_token_load_failed)) val res = client.post( "https://myanimelist.net/v1/oauth2/token", @@ -69,8 +66,9 @@ object MAL { suspend fun getSavedToken(context: FragmentActivity): Boolean { return tryWithSuspend(false) { - var res: ResponseToken = loadData(MAL_TOKEN, context) - ?: return@tryWithSuspend false + var res: ResponseToken = + PrefManager.getNullableVal(PrefName.MALToken, null) + ?: return@tryWithSuspend false if (System.currentTimeMillis() > res.expiresIn) res = refreshToken() ?: throw Exception(currContext()?.getString(R.string.refreshing_token_failed)) @@ -84,14 +82,12 @@ object MAL { username = null userid = null avatar = null - if (MAL_TOKEN in context.fileList()) { - File(context.filesDir, MAL_TOKEN).delete() - } + PrefManager.removeVal(PrefName.MALToken) } fun saveResponse(res: ResponseToken) { res.expiresIn += System.currentTimeMillis() - saveData(MAL_TOKEN, res) + PrefManager.setVal(PrefName.MALToken, res) } @Serializable @@ -100,6 +96,10 @@ object MAL { @SerialName("expires_in") var expiresIn: Long, @SerialName("access_token") val accessToken: String, @SerialName("refresh_token") val refreshToken: String, - ) : java.io.Serializable + ) : java.io.Serializable { + companion object { + private const val serialVersionUID = 1L + } + } } diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt index ef7765628c..0cbfa1bc0c 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt @@ -1,17 +1,16 @@ package ani.dantotsu.download import android.content.Context -import android.content.SharedPreferences import android.os.Environment import android.widget.Toast +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.io.File import java.io.Serializable class DownloadsManager(private val context: Context) { - private val prefs: SharedPreferences = - context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE) private val gson = Gson() private val downloadsList = loadDownloads().toMutableList() @@ -24,11 +23,11 @@ class DownloadsManager(private val context: Context) { private fun saveDownloads() { val jsonString = gson.toJson(downloadsList) - prefs.edit().putString("downloads_key", jsonString).apply() + PrefManager.setVal(PrefName.DownloadsKeys, jsonString) } private fun loadDownloads(): List { - val jsonString = prefs.getString("downloads_key", null) + val jsonString = PrefManager.getVal(PrefName.DownloadsKeys, null as String?) return if (jsonString != null) { val type = object : TypeToken>() {}.type gson.fromJson(jsonString, type) @@ -75,9 +74,11 @@ class DownloadsManager(private val context: Context) { DownloadedType.Type.MANGA -> { downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA } } + DownloadedType.Type.ANIME -> { downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME } } + DownloadedType.Type.NOVEL -> { downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL } } @@ -252,7 +253,12 @@ class DownloadsManager(private val context: Context) { const val mangaLocation = "Dantotsu/Manga" const val animeLocation = "Dantotsu/Anime" - fun getDirectory(context: Context, type: DownloadedType.Type, title: String, chapter: String? = null): File { + fun getDirectory( + context: Context, + type: DownloadedType.Type, + title: String, + chapter: String? = null + ): File { return if (type == DownloadedType.Type.MANGA) { if (chapter != null) { File( diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index a69ef446d1..dd0198ec22 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -21,6 +21,7 @@ import androidx.media3.exoplayer.offline.DownloadManager import androidx.media3.exoplayer.offline.DownloadService import ani.dantotsu.FileUrl import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currActivity import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager @@ -32,8 +33,8 @@ import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.media.anime.AnimeWatchFragment import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Video +import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.animesource.model.SAnime @@ -84,7 +85,7 @@ class AnimeDownloaderService : Service() { builder = NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply { setContentTitle("Anime Download Progress") - setSmallIcon(R.drawable.ic_round_download_24) + setSmallIcon(R.drawable.ic_download_24) priority = NotificationCompat.PRIORITY_DEFAULT setOnlyAlertOnce(true) } @@ -224,8 +225,6 @@ class AnimeDownloaderService : Service() { notificationManager.notify(NOTIFICATION_ID, builder.build()) } - broadcastDownloadStarted(task.episode) - currActivity()?.let { Helper.downloadVideo( it, @@ -276,7 +275,7 @@ class AnimeDownloaderService : Service() { DownloadedType.Type.ANIME, ) ) - FirebaseCrashlytics.getInstance().recordException( + Injekt.get().logException( Exception( "Anime Download failed:" + " ${download.failureReason}" + @@ -294,10 +293,7 @@ class AnimeDownloaderService : Service() { builder.setContentText("${task.title} - ${task.episode} Download completed") notificationManager.notify(NOTIFICATION_ID, builder.build()) snackString("${task.title} - ${task.episode} Download completed") - getSharedPreferences( - getString(R.string.anime_downloads), - Context.MODE_PRIVATE - ).edit().putString( + PrefManager.getAnimeDownloadPreferences().edit().putString( task.getTaskName(), task.video.file.url ).apply() @@ -335,7 +331,7 @@ class AnimeDownloaderService : Service() { logger("Exception while downloading file: ${e.message}") snackString("Exception while downloading file: ${e.message}") e.printStackTrace() - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) } broadcastDownloadFailed(task.episode) } diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt index 4cd57f050f..ef0c4148f9 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt @@ -12,6 +12,8 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.cardview.widget.CardView import ani.dantotsu.R +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName class OfflineAnimeAdapter( @@ -22,8 +24,7 @@ class OfflineAnimeAdapter( private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater private var originalItems: List = items - private var style = - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0) + private var style: Int = PrefManager.getVal(PrefName.OfflineView) override fun getCount(): Int { return items.size @@ -105,8 +106,7 @@ class OfflineAnimeAdapter( } fun notifyNewGrid() { - style = - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0) + style = PrefManager.getVal(PrefName.OfflineView) notifyDataSetChanged() } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt index e52647e250..4c91a43b85 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt @@ -1,11 +1,8 @@ package ani.dantotsu.download.anime -import android.animation.ObjectAnimator -import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Environment import android.text.Editable @@ -16,7 +13,6 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AlphaAnimation import android.view.animation.LayoutAnimationController -import android.view.animation.OvershootInterpolator import android.widget.AbsListView import android.widget.AutoCompleteTextView import android.widget.GridView @@ -25,34 +21,30 @@ import android.widget.TextView import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView -import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat -import androidx.core.util.Pair -import androidx.core.view.ViewCompat import androidx.core.view.marginBottom import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi import ani.dantotsu.R import ani.dantotsu.bottomBar +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currActivity import ani.dantotsu.currContext import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.navBarHeight import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.SettingsDialogFragment -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString -import ani.dantotsu.statusBarHeight import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.textfield.TextInputLayout -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.animesource.model.SAnime @@ -64,8 +56,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File -import kotlin.math.max -import kotlin.math.min class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { @@ -73,9 +63,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { private var downloads: List = listOf() private lateinit var gridView: GridView private lateinit var adapter: OfflineAnimeAdapter - private lateinit var total : TextView - private var uiSettings: UserInterfaceSettings = - loadData("ui_settings") ?: UserInterfaceSettings() + private lateinit var total: TextView override fun onCreateView( inflater: LayoutInflater, @@ -101,15 +89,12 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineANIME) dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") } - if (!uiSettings.immersiveMode) { + if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) { view.rootView.fitsSystemWindows = true } - val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("colorOverflow", false) ?: false - if (!colorOverflow) { - textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() - materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) - } + + textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000 + materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000) val searchView = view.findViewById(R.id.animeSearchBarText) searchView.addTextChangedListener(object : TextWatcher { @@ -123,8 +108,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { onSearchQuery(s.toString()) } }) - var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getInt("offline_view", 0) + var style: Int = PrefManager.getVal(PrefName.OfflineView) val layoutList = view.findViewById(R.id.downloadedList) val layoutcompact = view.findViewById(R.id.downloadedGrid) var selected = when (style) { @@ -143,8 +127,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { layoutList.setOnClickListener { selected(it as ImageView) style = 0 - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putInt("offline_view", style!!)?.apply() + PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView) adapter.notifyNewGrid() @@ -154,15 +137,15 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { layoutcompact.setOnClickListener { selected(it as ImageView) style = 1 - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putInt("offline_view", style!!)?.apply() + PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView1) adapter.notifyNewGrid() grid() } - gridView = if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1) + gridView = + if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1) total = view.findViewById(R.id.total) grid() return view @@ -195,13 +178,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { requireActivity(), Intent(requireContext(), MediaDetailsActivity::class.java) .putExtra("download", true), - ActivityOptionsCompat.makeSceneTransitionAnimation( - requireActivity(), - Pair.create( - requireActivity().findViewById(R.id.itemCompactImage), - ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage)) - ), - ).toBundle() + null ) } ?: run { snackString("no media found") @@ -220,11 +197,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { builder.setMessage("Are you sure you want to delete ${item.title}?") builder.setPositiveButton("Yes") { _, _ -> downloadManager.removeMedia(item.title, type) - val mediaIds = requireContext().getSharedPreferences( - getString(R.string.anime_downloads), - Context.MODE_PRIVATE - ) - ?.all?.filter { it.key.contains(item.title) }?.values ?: emptySet() + val mediaIds = + PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values + ?: emptySet() if (mediaIds.isEmpty()) { snackString("No media found") // if this happens, terrible things have happened } @@ -252,41 +227,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - var height = statusBarHeight - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout - if (displayCutout != null) { - if (displayCutout.boundingRects.size > 0) { - height = max( - statusBarHeight, - min( - displayCutout.boundingRects[0].width(), - displayCutout.boundingRects[0].height() - ) - ) - } - } - } val scrollTop = view.findViewById(R.id.mangaPageScrollTop) - scrollTop.translationY = - -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() - val visible = false - - fun animate() { - val start = if (visible) 0f else 1f - val end = if (!visible) 0f else 1f - ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply { - duration = 300 - interpolator = OvershootInterpolator(2f) - start() - } - ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply { - duration = 300 - interpolator = OvershootInterpolator(2f) - start() - } - } - scrollTop.setOnClickListener { gridView.smoothScrollToPositionFromTop(0, 0) } @@ -306,7 +247,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { totalItemCount: Int ) { val first = view.getChildAt(0) - val visibility = first != null && first.top < -height + val visibility = first != null && first.top < 0 + scrollTop.translationY = + -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE } }) @@ -340,8 +283,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() val newAnimeDownloads = mutableListOf() for (title in animeTitles) { - val _downloads = downloadManager.animeDownloadedTypes.filter { it.title == title } - val download = _downloads.first() + val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title } + val download = tDownloads.first() val offlineAnimeModel = loadOfflineAnimeModel(download) newAnimeDownloads += offlineAnimeModel } @@ -349,12 +292,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { } private fun getMedia(downloadedType: DownloadedType): Media? { - val type = if (downloadedType.type == DownloadedType.Type.ANIME) { - "Anime" - } else if (downloadedType.type == DownloadedType.Type.MANGA) { - "Manga" - } else { - "Novel" + val type = when (downloadedType.type) { + DownloadedType.Type.MANGA -> "Manga" + DownloadedType.Type.ANIME -> "Anime" + else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -379,18 +320,16 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { } catch (e: Exception) { logger("Error loading media.json: ${e.message}") logger(e.printStackTrace()) - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) null } } private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel { - val type = if (downloadedType.type == DownloadedType.Type.MANGA) { - "Manga" - } else if (downloadedType.type == DownloadedType.Type.ANIME) { - "Anime" - } else { - "Novel" + val type = when (downloadedType.type) { + DownloadedType.Type.MANGA -> "Manga" + DownloadedType.Type.ANIME -> "Anime" + else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -398,8 +337,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { ) //load media.json and convert to media class with gson try { - val media = File(directory, "media.json") - val mediaJson = media.readText() val mediaModel = getMedia(downloadedType)!! val cover = File(directory, "cover.jpg") val coverUri: Uri? = if (cover.exists()) { @@ -439,7 +376,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { } catch (e: Exception) { logger("Error loading media.json: ${e.message}") logger(e.printStackTrace()) - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) return OfflineAnimeModel( "unknown", "0", @@ -448,8 +385,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { "??", "movie", "hmm", - false, - false, + isOngoing = false, + isUserScored = false, null, null ) diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 0a1337dfeb..de91960ca8 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -18,6 +18,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.logger @@ -29,7 +30,6 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROG import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS @@ -76,7 +76,7 @@ class MangaDownloaderService : Service() { notificationManager = NotificationManagerCompat.from(this) builder = NotificationCompat.Builder(this, CHANNEL_DOWNLOADER_PROGRESS).apply { setContentTitle("Manga Download Progress") - setSmallIcon(R.drawable.ic_round_download_24) + setSmallIcon(R.drawable.ic_download_24) priority = NotificationCompat.PRIORITY_DEFAULT setOnlyAlertOnce(true) setProgress(0, 0, false) @@ -253,7 +253,7 @@ class MangaDownloaderService : Service() { } catch (e: Exception) { logger("Exception while downloading file: ${e.message}") snackString("Exception while downloading file: ${e.message}") - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) broadcastDownloadFailed(task.chapter) } } @@ -283,7 +283,7 @@ class MangaDownloaderService : Service() { } catch (e: Exception) { println("Exception while saving image: ${e.message}") snackString("Exception while saving image: ${e.message}") - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) } } diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt index 4ec6ab4343..4ef12c03b8 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt @@ -11,6 +11,8 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.cardview.widget.CardView import ani.dantotsu.R +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName class OfflineMangaAdapter( @@ -21,8 +23,7 @@ class OfflineMangaAdapter( private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater private var originalItems: List = items - private var style = - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0) + private var style: Int = PrefManager.getVal(PrefName.OfflineView) override fun getCount(): Int { return items.size @@ -104,8 +105,7 @@ class OfflineMangaAdapter( } fun notifyNewGrid() { - style = - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0) + style = PrefManager.getVal(PrefName.OfflineView) notifyDataSetChanged() } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index 3c3b5e62a3..7bd43c9ad6 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -1,10 +1,7 @@ package ani.dantotsu.download.manga -import android.animation.ObjectAnimator -import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Environment import android.text.Editable @@ -15,7 +12,6 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AlphaAnimation import android.view.animation.LayoutAnimationController -import android.view.animation.OvershootInterpolator import android.widget.AbsListView import android.widget.AutoCompleteTextView import android.widget.GridView @@ -23,33 +19,29 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView -import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat -import androidx.core.util.Pair -import androidx.core.view.ViewCompat import androidx.core.view.marginBottom import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.bottomBar +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currActivity import ani.dantotsu.currContext import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.navBarHeight import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.SettingsDialogFragment -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString -import ani.dantotsu.statusBarHeight import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.textfield.TextInputLayout -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.source.model.SChapter @@ -57,8 +49,6 @@ import eu.kanade.tachiyomi.source.model.SChapterImpl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File -import kotlin.math.max -import kotlin.math.min class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { @@ -67,8 +57,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { private lateinit var gridView: GridView private lateinit var adapter: OfflineMangaAdapter private lateinit var total: TextView - private var uiSettings: UserInterfaceSettings = - loadData("ui_settings") ?: UserInterfaceSettings() override fun onCreateView( inflater: LayoutInflater, @@ -94,15 +82,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineMANGA) dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") } - if (!uiSettings.immersiveMode) { + if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) { view.rootView.fitsSystemWindows = true } - val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("colorOverflow", false) ?: false - if (!colorOverflow) { - textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() - materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) - } + + textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000 + materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000) val searchView = view.findViewById(R.id.animeSearchBarText) searchView.addTextChangedListener(object : TextWatcher { @@ -116,8 +101,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { onSearchQuery(s.toString()) } }) - var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getInt("offline_view", 0) + var style: Int = PrefManager.getVal(PrefName.OfflineView) val layoutList = view.findViewById(R.id.downloadedList) val layoutcompact = view.findViewById(R.id.downloadedGrid) var selected = when (style) { @@ -136,8 +120,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { layoutList.setOnClickListener { selected(it as ImageView) style = 0 - requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("offline_view", style!!).apply() + PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView) adapter.notifyNewGrid() @@ -148,8 +131,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { layoutcompact.setOnClickListener { selected(it as ImageView) style = 1 - requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("offline_view", style!!).apply() + PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView1) adapter.notifyNewGrid() @@ -180,19 +162,13 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title } media?.let { + ContextCompat.startActivity( requireActivity(), Intent(requireContext(), MediaDetailsActivity::class.java) .putExtra("media", getMedia(it)) .putExtra("download", true), - ActivityOptionsCompat.makeSceneTransitionAnimation( - requireActivity(), - Pair.create( - gridView.getChildAt(position) - .findViewById(R.id.itemCompactImage), - ViewCompat.getTransitionName(requireActivity().findViewById(R.id.itemCompactImage)) - ) - ).toBundle() + null ) } ?: run { snackString("no media found") @@ -236,41 +212,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initActivity(requireActivity()) - var height = statusBarHeight - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout - if (displayCutout != null) { - if (displayCutout.boundingRects.size > 0) { - height = max( - statusBarHeight, - min( - displayCutout.boundingRects[0].width(), - displayCutout.boundingRects[0].height() - ) - ) - } - } - } - val scrollTop = view.findViewById(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() - } - } + val scrollTop = view.findViewById(R.id.mangaPageScrollTop) scrollTop.setOnClickListener { gridView.smoothScrollToPositionFromTop(0, 0) } @@ -290,8 +233,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { totalItemCount: Int ) { val first = view.getChildAt(0) - val visibility = first != null && first.top < -height + val visibility = first != null && first.top < 0 scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE + scrollTop.translationY = + -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() } }) @@ -324,8 +269,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val newMangaDownloads = mutableListOf() for (title in mangaTitles) { - val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title } - val download = _downloads.first() + val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title } + val download = tDownloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newMangaDownloads += offlineMangaModel } @@ -333,8 +278,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val newNovelDownloads = mutableListOf() for (title in novelTitles) { - val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title } - val download = _downloads.first() + val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title } + val download = tDownloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newNovelDownloads += offlineMangaModel } @@ -343,12 +288,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } private fun getMedia(downloadedType: DownloadedType): Media? { - val type = if (downloadedType.type == DownloadedType.Type.MANGA) { - "Manga" - } else if (downloadedType.type == DownloadedType.Type.ANIME) { - "Anime" - } else { - "Novel" + val type = when (downloadedType.type) { + DownloadedType.Type.MANGA -> "Manga" + DownloadedType.Type.ANIME -> "Anime" + else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -367,18 +310,16 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } catch (e: Exception) { logger("Error loading media.json: ${e.message}") logger(e.printStackTrace()) - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) null } } private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { - val type = if (downloadedType.type == DownloadedType.Type.MANGA) { - "Manga" - } else if (downloadedType.type == DownloadedType.Type.ANIME) { - "Anime" - } else { - "Novel" + val type = when (downloadedType.type) { + DownloadedType.Type.MANGA -> "Manga" + DownloadedType.Type.ANIME -> "Anime" + else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -386,8 +327,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { ) //load media.json and convert to media class with gson try { - val media = File(directory, "media.json") - val mediaJson = media.readText() val mediaModel = getMedia(downloadedType)!! val cover = File(directory, "cover.jpg") val coverUri: Uri? = if (cover.exists()) { @@ -421,7 +360,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } catch (e: Exception) { logger("Error loading media.json: ${e.message}") logger(e.printStackTrace()) - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) return OfflineMangaModel( "unknown", "0", @@ -429,8 +368,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { "??", "movie", "hmm", - false, - false, + isOngoing = false, + isUserScored = false, null, null ) diff --git a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt index d729ef5789..f6a7e2ed10 100644 --- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt @@ -17,13 +17,13 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.logger import ani.dantotsu.media.Media import ani.dantotsu.media.novel.NovelReadFragment import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.data.notification.Notifications @@ -75,7 +75,7 @@ class NovelDownloaderService : Service() { builder = NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply { setContentTitle("Novel Download Progress") - setSmallIcon(R.drawable.ic_round_download_24) + setSmallIcon(R.drawable.ic_download_24) priority = NotificationCompat.PRIORITY_DEFAULT setOnlyAlertOnce(true) setProgress(0, 0, false) @@ -342,7 +342,7 @@ class NovelDownloaderService : Service() { } catch (e: Exception) { logger("Exception while downloading .epub: ${e.message}") snackString("Exception while downloading .epub: ${e.message}") - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) broadcastDownloadFailed(task.originalLink) } } diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index 25d0d47669..7f92eda6f8 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -13,11 +13,9 @@ import android.util.Log import androidx.annotation.OptIn import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.core.content.ContextCompat.getString import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.MimeTypes -import androidx.media3.common.TrackSelectionParameters import androidx.media3.common.util.UnstableApi import androidx.media3.database.StandaloneDatabaseProvider import androidx.media3.datasource.DataSource @@ -31,7 +29,6 @@ import androidx.media3.exoplayer.offline.DownloadHelper import androidx.media3.exoplayer.offline.DownloadManager import androidx.media3.exoplayer.offline.DownloadService import androidx.media3.exoplayer.scheduler.Requirements -import androidx.media3.ui.TrackSelectionDialogBuilder import ani.dantotsu.R import ani.dantotsu.defaultHeaders import ani.dantotsu.download.DownloadedType @@ -45,6 +42,7 @@ import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.SubtitleType import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.VideoType +import ani.dantotsu.settings.saving.PrefManager import eu.kanade.tachiyomi.network.NetworkHelper import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -231,19 +229,13 @@ object Helper { DownloadService.sendRemoveDownload( context, ExoplayerDownloadService::class.java, - context.getSharedPreferences( - getString(context, R.string.anime_downloads), - Context.MODE_PRIVATE - ).getString( + PrefManager.getAnimeDownloadPreferences().getString( animeDownloadTask.getTaskName(), "" ) ?: "", false ) - context.getSharedPreferences( - getString(context, R.string.anime_downloads), - Context.MODE_PRIVATE - ).edit() + PrefManager.getAnimeDownloadPreferences().edit() .remove(animeDownloadTask.getTaskName()) .apply() downloadsManger.removeDownload( diff --git a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt index 53e917487f..f3baeb3f4d 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.home import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle @@ -28,13 +27,13 @@ import ani.dantotsu.connections.anilist.AnilistAnimeViewModel import ani.dantotsu.connections.anilist.SearchResults import ani.dantotsu.connections.anilist.getUserId import ani.dantotsu.databinding.FragmentAnimeBinding -import ani.dantotsu.loadData import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.ProgressAdapter import ani.dantotsu.media.SearchActivity import ani.dantotsu.navBarHeight import ani.dantotsu.px -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.Dispatchers @@ -50,9 +49,6 @@ class AnimeFragment : Fragment() { private val binding get() = _binding!! private lateinit var animePageAdapter: AnimePageAdapter - private var uiSettings: UserInterfaceSettings = - loadData("ui_settings") ?: UserInterfaceSettings() - val model: AnilistAnimeViewModel by activityViewModels() override fun onCreateView( @@ -217,7 +213,7 @@ class AnimeFragment : Fragment() { if (it != null) { animePageAdapter.updateTrending( MediaAdaptor( - if (uiSettings.smallView) 3 else 2, + if (PrefManager.getVal(PrefName.SmallView)) 3 else 2, it, requireActivity(), viewPager = animePageAdapter.trendingViewPager @@ -268,8 +264,11 @@ class AnimeFragment : Fragment() { model.loaded = true model.loadTrending(1) model.loadUpdated() - model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("popular_list", false)) + model.loadPopular( + "ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal( + PrefName.PopularAnimeList + ) + ) } live.postValue(false) _binding?.animeRefresh?.isRefreshing = false diff --git a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt index 69f02ab041..295b49b27e 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt @@ -1,6 +1,5 @@ package ani.dantotsu.home -import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper @@ -22,7 +21,6 @@ import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemAnimePageBinding -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.media.CalendarActivity import ani.dantotsu.media.GenreActivity @@ -33,7 +31,8 @@ import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSlideIn import ani.dantotsu.setSlideUp import ani.dantotsu.settings.SettingsDialogFragment -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import com.google.android.material.card.MaterialCardView import com.google.android.material.textfield.TextInputLayout @@ -44,8 +43,6 @@ class AnimePageAdapter : RecyclerView.Adapter { + if (PrefManager.getVal(PrefName.SmallView)) binding.animeTrendingContainer.updateLayoutParams { bottomMargin = (-108f).px } @@ -133,14 +125,12 @@ class AnimePageAdapter : RecyclerView.Adapter onIncludeListClick.invoke(isChecked) - currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putBoolean("popular_list", isChecked)?.apply() + PrefManager.setVal(PrefName.PopularAnimeList, isChecked) } if (ready.value == false) ready.postValue(true) @@ -179,12 +169,12 @@ class AnimePageAdapter : RecyclerView.Adapter("ui_settings") ?: UserInterfaceSettings() fun load() { if (activity != null && _binding != null) lifecycleScope.launch(Dispatchers.Main) { binding.homeUserName.text = Anilist.username binding.homeUserEpisodesWatched.text = Anilist.episodesWatched.toString() binding.homeUserChaptersRead.text = Anilist.chapterRead.toString() binding.homeUserAvatar.loadImage(Anilist.avatar) - if (!uiSettings.bannerAnimations) binding.homeUserBg.pause() + if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause() binding.homeUserBg.loadImage(Anilist.bg) binding.homeUserDataProgressBar.visibility = View.GONE @@ -98,14 +97,14 @@ class HomeFragment : Fragment() { ) } - binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings)) + binding.homeUserAvatarContainer.startAnimation(setSlideUp()) binding.homeUserDataContainer.visibility = View.VISIBLE binding.homeUserDataContainer.layoutAnimation = - LayoutAnimationController(setSlideUp(uiSettings), 0.25f) + LayoutAnimationController(setSlideUp(), 0.25f) binding.homeAnimeList.visibility = View.VISIBLE binding.homeMangaList.visibility = View.VISIBLE binding.homeListContainer.layoutAnimation = - LayoutAnimationController(setSlideIn(uiSettings), 0.25f) + LayoutAnimationController(setSlideIn(), 0.25f) } else { snackString(currContext()?.getString(R.string.please_reload)) @@ -127,7 +126,7 @@ class HomeFragment : Fragment() { binding.homeTopContainer.updatePadding(top = statusBarHeight) var reached = false - val duration = (uiSettings.animationSpeed * 200).toLong() + val duration = ((PrefManager.getVal(PrefName.AnimationSpeed) as Float) * 200).toLong() binding.homeScroll.setOnScrollChangeListener { _, _, _, _, _ -> if (!binding.homeScroll.canScrollVertically(1)) { reached = true @@ -206,13 +205,13 @@ class HomeFragment : Fragment() { ) recyclerView.visibility = View.VISIBLE recyclerView.layoutAnimation = - LayoutAnimationController(setSlideIn(uiSettings), 0.25f) + LayoutAnimationController(setSlideIn(), 0.25f) } else { empty.visibility = View.VISIBLE } title.visibility = View.VISIBLE - title.startAnimation(setSlideUp(uiSettings)) + title.startAnimation(setSlideUp()) progress.visibility = View.GONE } } @@ -295,12 +294,12 @@ class HomeFragment : Fragment() { binding.homeRecommended ) - binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings)) + binding.homeUserAvatarContainer.startAnimation(setSlideUp()) model.empty.observe(viewLifecycleOwner) { binding.homeDantotsuContainer.visibility = if (it == true) View.VISIBLE else View.GONE (binding.homeDantotsuIcon.drawable as Animatable).start() - binding.homeDantotsuContainer.startAnimation(setSlideUp(uiSettings)) + binding.homeDantotsuContainer.startAnimation(setSlideUp()) binding.homeDantotsuIcon.setSafeOnClickListener { (binding.homeDantotsuIcon.drawable as Animatable).start() } @@ -330,8 +329,6 @@ class HomeFragment : Fragment() { live.observe(viewLifecycleOwner) { if (it) { scope.launch { - uiSettings = - loadData("ui_settings") ?: UserInterfaceSettings() withContext(Dispatchers.IO) { //Get userData First getUserId(requireContext()) { @@ -340,8 +337,10 @@ class HomeFragment : Fragment() { model.loaded = true model.setListImages() var empty = true + val homeLayoutShow: List = + PrefManager.getVal(PrefName.HomeLayoutShow) (array.indices).forEach { i -> - if (uiSettings.homeLayoutShow[i]) { + if (homeLayoutShow.elementAt(i)) { array[i].run() empty = false } else withContext(Dispatchers.Main) { diff --git a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt index 58bb2fc905..8efa8ac391 100644 --- a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt @@ -1,14 +1,23 @@ package ani.dantotsu.home +import android.app.AlertDialog +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.FragmentLoginBinding import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.settings.saving.internal.PreferenceKeystore +import ani.dantotsu.settings.saving.internal.PreferencePackager +import ani.dantotsu.toast +import com.google.android.material.textfield.TextInputEditText class LoginFragment : Fragment() { @@ -29,5 +38,99 @@ class LoginFragment : Fragment() { binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) } binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) } binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) } + + val openDocumentLauncher = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + try { + val jsonString = + requireActivity().contentResolver.openInputStream(uri)?.readBytes() + ?: throw Exception("Error reading file") + val name = + DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings" + //.sani is encrypted, .ani is not + if (name.endsWith(".sani")) { + passwordAlertDialog() { password -> + if (password != null) { + val salt = jsonString.copyOfRange(0, 16) + val encrypted = jsonString.copyOfRange(16, jsonString.size) + val decryptedJson = try { + PreferenceKeystore.decryptWithPassword( + password, + encrypted, + salt + ) + } catch (e: Exception) { + toast("Incorrect password") + return@passwordAlertDialog + } + if (PreferencePackager.unpack(decryptedJson)) + restartApp() + } else { + toast("Password cannot be empty") + } + } + } else if (name.endsWith(".ani")) { + val decryptedJson = jsonString.toString(Charsets.UTF_8) + if (PreferencePackager.unpack(decryptedJson)) + restartApp() + } else { + toast("Invalid file type") + } + } catch (e: Exception) { + e.printStackTrace() + toast("Error importing settings") + } + } + } + + binding.importSettingsButton.setOnClickListener { + openDocumentLauncher.launch(arrayOf("*/*")) + } } + + private fun passwordAlertDialog(callback: (CharArray?) -> Unit) { + val password = CharArray(16).apply { fill('0') } + + // Inflate the dialog layout + val dialogView = + LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null) + dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" + val subtitleTextView = dialogView.findViewById(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(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) + } + } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt index 8fc18f9c8c..efe39ed62c 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.home import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.content.Context import android.os.Build import android.os.Bundle import android.view.LayoutInflater @@ -26,12 +25,12 @@ import ani.dantotsu.connections.anilist.AnilistMangaViewModel import ani.dantotsu.connections.anilist.SearchResults import ani.dantotsu.connections.anilist.getUserId import ani.dantotsu.databinding.FragmentMangaBinding -import ani.dantotsu.loadData import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.ProgressAdapter import ani.dantotsu.navBarHeight import ani.dantotsu.px -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.Dispatchers @@ -46,9 +45,6 @@ class MangaFragment : Fragment() { private val binding get() = _binding!! private lateinit var mangaPageAdapter: MangaPageAdapter - private var uiSettings: UserInterfaceSettings = - loadData("ui_settings") ?: UserInterfaceSettings() - val model: AnilistMangaViewModel by activityViewModels() override fun onCreateView( @@ -175,7 +171,7 @@ class MangaFragment : Fragment() { if (it != null) { mangaPageAdapter.updateTrending( MediaAdaptor( - if (uiSettings.smallView) 3 else 2, + if (PrefManager.getVal(PrefName.SmallView)) 3 else 2, it, requireActivity(), viewPager = mangaPageAdapter.trendingViewPager @@ -242,8 +238,11 @@ class MangaFragment : Fragment() { model.loaded = true model.loadTrending() model.loadTrendingNovel() - model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("popular_list", false) ) + model.loadPopular( + "MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal( + PrefName.PopularMangaList + ) + ) } live.postValue(false) _binding?.mangaRefresh?.isRefreshing = false diff --git a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt index ec4a50342b..6cedbb8071 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt @@ -1,6 +1,5 @@ package ani.dantotsu.home -import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper @@ -22,7 +21,6 @@ import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemMangaPageBinding -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.media.GenreActivity import ani.dantotsu.media.MediaAdaptor @@ -32,7 +30,8 @@ import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSlideIn import ani.dantotsu.setSlideUp import ani.dantotsu.settings.SettingsDialogFragment -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import com.google.android.material.card.MaterialCardView import com.google.android.material.textfield.TextInputLayout @@ -43,8 +42,6 @@ class MangaPageAdapter : RecyclerView.Adapter { + if (PrefManager.getVal(PrefName.SmallView)) binding.mangaTrendingContainer.updateLayoutParams { bottomMargin = (-108f).px } @@ -126,14 +118,12 @@ class MangaPageAdapter : RecyclerView.Adapter onIncludeListClick.invoke(isChecked) - currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putBoolean("popular_list", isChecked)?.apply() + PrefManager.setVal(PrefName.PopularMangaList, isChecked) } if (ready.value == false) ready.postValue(true) @@ -169,10 +159,10 @@ class MangaPageAdapter : RecyclerView.Adapter(R.id.navbar) + val bottomBar = findViewById(R.id.navbar) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val backgroundDrawable = _bottomBar.background as GradientDrawable + val backgroundDrawable = bottomBar.background as GradientDrawable val currentColor = backgroundDrawable.color?.defaultColor ?: 0 val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt() backgroundDrawable.setColor(semiTransparentColor) - _bottomBar.background = backgroundDrawable + bottomBar.background = backgroundDrawable } - val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("colorOverflow", false) - if (!colorOverflow) { - _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) + bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) - } var doubleBackToExitPressedOnce = false onBackPressedDispatcher.addCallback(this) { @@ -76,8 +68,7 @@ class NoInternet : AppCompatActivity() { binding.root.doOnAttach { initActivity(this) - uiSettings = loadData("ui_settings") ?: uiSettings - selectedOption = uiSettings.defaultStartUpTab + selectedOption = PrefManager.getVal(PrefName.DefaultStartUpTab) binding.includedNavbar.navbarContainer.updateLayoutParams { bottomMargin = navBarHeight @@ -89,7 +80,7 @@ class NoInternet : AppCompatActivity() { val mainViewPager = binding.viewpager mainViewPager.isUserInputEnabled = false mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) - mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) + mainViewPager.setPageTransformer(ZoomOutPageTransformer()) navbar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { override fun onTabSelected( diff --git a/app/src/main/java/ani/dantotsu/media/AuthorActivity.kt b/app/src/main/java/ani/dantotsu/media/AuthorActivity.kt index 9188b540f0..22bc54f17b 100644 --- a/app/src/main/java/ani/dantotsu/media/AuthorActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/AuthorActivity.kt @@ -18,7 +18,6 @@ import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityAuthorBinding import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet import ani.dantotsu.others.getSerialized import ani.dantotsu.px import ani.dantotsu.statusBarHeight @@ -36,7 +35,7 @@ class AuthorActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityAuthorBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt index 77035416c4..e6bc8b3b3f 100644 --- a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt @@ -16,11 +16,10 @@ import androidx.lifecycle.lifecycleScope import ani.dantotsu.R import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityListBinding -import ani.dantotsu.loadData import ani.dantotsu.media.user.ListViewPagerAdapter import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import com.google.android.material.tabs.TabLayout @@ -38,7 +37,7 @@ class CalendarActivity : AppCompatActivity() { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityListBinding.inflate(layoutInflater) @@ -67,8 +66,7 @@ class CalendarActivity : AppCompatActivity() { binding.listTitle.setTextColor(primaryTextColor) binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor) binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor) - val uiSettings = loadData("ui_settings") ?: UserInterfaceSettings() - if (!uiSettings.immersiveMode) { + if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) { this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg_inv) binding.root.fitsSystemWindows = true diff --git a/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt b/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt index 632230230a..d8c4961ce9 100644 --- a/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt @@ -11,10 +11,8 @@ import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.databinding.ItemCharacterBinding -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.setAnimation -import ani.dantotsu.settings.UserInterfaceSettings import java.io.Serializable class CharacterAdapter( @@ -26,13 +24,10 @@ class CharacterAdapter( return CharacterViewHolder(binding) } - private val uiSettings = - loadData("ui_settings") ?: UserInterfaceSettings() - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) { val binding = holder.binding - setAnimation(binding.root.context, holder.binding.root, uiSettings) + setAnimation(binding.root.context, holder.binding.root) val character = characterList[position] binding.itemCompactRelation.text = character.role + " " binding.itemCompactImage.loadImage(character.image) diff --git a/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt index 3ccba6e1fe..6c987096a3 100644 --- a/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt @@ -17,14 +17,13 @@ import ani.dantotsu.R import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityCharacterBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.navBarHeight import ani.dantotsu.others.ImageViewDialog -import ani.dantotsu.others.LangSet import ani.dantotsu.others.getSerialized import ani.dantotsu.px -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import com.google.android.material.appbar.AppBarLayout @@ -38,22 +37,21 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang private val model: OtherDetailsViewModel by viewModels() private lateinit var character: Character private var loaded = false - val uiSettings = loadData("ui_settings") ?: UserInterfaceSettings() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityCharacterBinding.inflate(layoutInflater) setContentView(binding.root) initActivity(this) screenWidth = resources.displayMetrics.run { widthPixels / density } - if (uiSettings.immersiveMode) this.window.statusBarColor = + if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor = ContextCompat.getColor(this, R.color.status) val banner = - if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen + if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen banner.updateLayoutParams { height += statusBarHeight } binding.characterClose.updateLayoutParams { topMargin += statusBarHeight } @@ -136,16 +134,16 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang binding.characterCover.visibility = if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE - + val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode) if (percentage >= percent && !isCollapsed) { isCollapsed = true - if (uiSettings.immersiveMode) this.window.statusBarColor = + if (immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg) binding.characterAppBar.setBackgroundResource(R.color.nav_bg) } if (percentage <= percent && isCollapsed) { isCollapsed = false - if (uiSettings.immersiveMode) this.window.statusBarColor = + if (immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.status) binding.characterAppBar.setBackgroundResource(R.color.bg) } diff --git a/app/src/main/java/ani/dantotsu/media/GenreActivity.kt b/app/src/main/java/ani/dantotsu/media/GenreActivity.kt index e5960502f4..0c49ec11d6 100644 --- a/app/src/main/java/ani/dantotsu/media/GenreActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/GenreActivity.kt @@ -12,9 +12,9 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.GenresViewModel import ani.dantotsu.databinding.ActivityGenreBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import kotlinx.coroutines.Dispatchers @@ -27,7 +27,7 @@ class GenreActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityGenreBinding.inflate(layoutInflater) setContentView(binding.root) @@ -54,7 +54,9 @@ class GenreActivity : AppCompatActivity() { GridLayoutManager(this, (screenWidth / 156f).toInt()) lifecycleScope.launch(Dispatchers.IO) { - model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) { + model.loadGenres( + Anilist.genres ?: loadLocalGenres() ?: arrayListOf() + ) { MainScope().launch { adapter.addGenre(it) } @@ -62,4 +64,14 @@ class GenreActivity : AppCompatActivity() { } } } + + private fun loadLocalGenres(): ArrayList? { + val genres = PrefManager.getVal>(PrefName.GenresList) + .toMutableList() as ArrayList? + return if (genres.isNullOrEmpty()) { + null + } else { + genres + } + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt index e3cd6618d8..c17a5ec1cc 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt @@ -26,7 +26,8 @@ import ani.dantotsu.databinding.ItemMediaCompactBinding import ani.dantotsu.databinding.ItemMediaLargeBinding import ani.dantotsu.databinding.ItemMediaPageBinding import ani.dantotsu.databinding.ItemMediaPageSmallBinding -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.model.GlideUrl @@ -44,9 +45,6 @@ class MediaAdaptor( private val viewPager: ViewPager2? = null, ) : RecyclerView.Adapter() { - private val uiSettings = - loadData("ui_settings") ?: UserInterfaceSettings() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (type) { 0 -> MediaViewHolder( @@ -91,7 +89,7 @@ class MediaAdaptor( when (type) { 0 -> { val b = (holder as MediaViewHolder).binding - setAnimation(activity, b.root, uiSettings) + setAnimation(activity, b.root) val media = mediaList?.getOrNull(position) if (media != null) { b.itemCompactImage.loadImage(media.cover) @@ -135,7 +133,7 @@ class MediaAdaptor( 1 -> { val b = (holder as MediaLargeViewHolder).binding - setAnimation(activity, b.root, uiSettings) + setAnimation(activity, b.root) val media = mediaList?.get(position) if (media != null) { b.itemCompactImage.loadImage(media.cover) @@ -178,16 +176,17 @@ class MediaAdaptor( val b = (holder as MediaPageViewHolder).binding val media = mediaList?.get(position) if (media != null) { + val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations) b.itemCompactImage.loadImage(media.cover) - if (uiSettings.bannerAnimations) + if (bannerAnimations) b.itemCompactBanner.setTransitionGenerator( RandomTransitionGenerator( - (10000 + 15000 * (uiSettings.animationSpeed)).toLong(), + (10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed)) as Float)).toLong(), AccelerateDecelerateInterpolator() ) ) val banner = - if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen + if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen val context = b.itemCompactBanner.context if (!(context as Activity).isDestroyed) Glide.with(context as Context) @@ -234,16 +233,17 @@ class MediaAdaptor( val b = (holder as MediaPageSmallViewHolder).binding val media = mediaList?.get(position) if (media != null) { + val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations) b.itemCompactImage.loadImage(media.cover) - if (uiSettings.bannerAnimations) + if (bannerAnimations) b.itemCompactBanner.setTransitionGenerator( RandomTransitionGenerator( - (10000 + 15000 * (uiSettings.animationSpeed)).toLong(), + (10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(), AccelerateDecelerateInterpolator() ) ) val banner = - if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen + if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen val context = b.itemCompactBanner.context if (!(context as Activity).isDestroyed) Glide.with(context as Context) @@ -396,10 +396,8 @@ class MediaAdaptor( if (itemCompactImage != null) { ActivityOptionsCompat.makeSceneTransitionAnimation( activity, - Pair.create( - itemCompactImage, - ViewCompat.getTransitionName(activity.findViewById(R.id.itemCompactImage))!! - ), + itemCompactImage, + ViewCompat.getTransitionName(itemCompactImage)!! ).toBundle() } else { null diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 432daa5e4e..77224111aa 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.content.Context import android.content.Intent import android.os.Bundle import android.text.SpannableStringBuilder @@ -35,7 +34,6 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.copyToClipboard import ani.dantotsu.databinding.ActivityMediaBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.media.anime.AnimeWatchFragment import ani.dantotsu.media.manga.MangaReadFragment @@ -44,8 +42,8 @@ import ani.dantotsu.navBarHeight import ani.dantotsu.openLinkInBrowser import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.getSerialized -import ani.dantotsu.saveData -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager @@ -65,7 +63,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi private val scope = lifecycleScope private val model: MediaDetailsViewModel by viewModels() private lateinit var tabLayout: NavigationBarView - private lateinit var uiSettings: UserInterfaceSettings var selected = 0 var anime = true private var adult = false @@ -90,7 +87,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi //Ui init initActivity(this) - uiSettings = loadData("ui_settings") ?: UserInterfaceSettings() binding.mediaBanner.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } @@ -111,20 +107,21 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi onBackPressedDispatcher.onBackPressed() } - if (uiSettings.bannerAnimations) { + val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations) + if (bannerAnimations) { val adi = AccelerateDecelerateInterpolator() val generator = RandomTransitionGenerator( - (10000 + 15000 * (uiSettings.animationSpeed)).toLong(), + (10000 + 15000 * ((PrefManager.getVal(PrefName.AnimationSpeed) as Float))).toLong(), adi ) binding.mediaBanner.setTransitionGenerator(generator) } val banner = - if (uiSettings.bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen + if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen val viewPager = binding.mediaViewPager tabLayout = binding.mediaTab as NavigationBarView viewPager.isUserInputEnabled = false - viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) + viewPager.setPageTransformer(ZoomOutPageTransformer()) val isDownload = intent.getBooleanExtra("download", false) @@ -141,7 +138,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi banner.loadImage(media.banner ?: media.cover, 400) val gestureDetector = GestureDetector(this, object : GesturesListener() { override fun onDoubleClick(event: MotionEvent) { - if (!uiSettings.bannerAnimations) + if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) snackString(getString(R.string.enable_banner_animations)) else { binding.mediaBanner.restart() @@ -159,11 +156,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi } }) banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true } - if (this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("incognito", false)) { + if (PrefManager.getVal(PrefName.Incognito)) { binding.mediaTitle.text = " ${media.userPreferredName}" binding.incognito.visibility = View.VISIBLE - }else { + } else { binding.mediaTitle.text = media.userPreferredName } binding.mediaTitle.setOnLongClickListener { @@ -284,7 +280,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi } else snackString(getString(R.string.please_login_anilist)) } binding.mediaAddToList.setOnLongClickListener { - saveData("${media.id}_progressDialog", true) + PrefManager.setCustomVal( + "${media.id}_progressDialog", + true, + ) snackString(getString(R.string.auto_update_reset)) true } @@ -345,7 +344,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi viewPager.setCurrentItem(selected, false) val sel = model.loadSelected(media, isDownload) sel.window = selected - model.saveSelected(media.id, sel, this) + model.saveSelected(media.id, sel) true } @@ -354,7 +353,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi viewPager.setCurrentItem(selected, false) if (model.continueMedia == null && media.cameFromContinue) { - model.continueMedia = loadData("continue_media") ?: true + model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia) selected = 1 } @@ -395,7 +394,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi } override fun onResume() { - tabLayout.selectedItemId = idFromSelect() + if (this::tabLayout.isInitialized) { + tabLayout.selectedItemId = idFromSelect() + } super.onResume() } @@ -437,7 +438,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi binding.mediaCover.visibility = if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE - val duration = (200 * uiSettings.animationSpeed).toLong() + val duration = (200 * (PrefManager.getVal(PrefName.AnimationSpeed) as Float)).toLong() val typedValue = TypedValue() this@MediaDetailsActivity.theme.resolveAttribute( com.google.android.material.R.attr.colorSecondary, @@ -467,7 +468,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi .start() ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f) .setDuration(duration).start() - if (uiSettings.bannerAnimations) binding.mediaBanner.resume() + if (PrefManager.getVal(PrefName.BannerAnimations)) binding.mediaBanner.resume() } if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue( false @@ -491,10 +492,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi init { enabled(true) - scope.launch { - delay(100) //TODO: a listener would be better - clicked() - } image.setOnClickListener { if (pressable && !disabled) { pressable = false diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index cb5304726a..8f10863fb0 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -1,7 +1,5 @@ package ani.dantotsu.media -import android.app.Activity -import android.content.SharedPreferences import android.os.Handler import android.os.Looper import androidx.fragment.app.FragmentManager @@ -11,7 +9,6 @@ import androidx.lifecycle.ViewModel import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext -import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.anime.Episode import ani.dantotsu.media.anime.SelectorDialogFragment @@ -28,35 +25,32 @@ import ani.dantotsu.parsers.NovelSources import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.VideoExtractor import ani.dantotsu.parsers.WatchSources -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.tryWithSuspend import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get class MediaDetailsViewModel : ViewModel() { val scrolledToTop = MutableLiveData(true) - fun saveSelected(id: Int, data: Selected, activity: Activity? = null) { - saveData("$id-select", data, activity) + fun saveSelected(id: Int, data: Selected) { + PrefManager.setCustomVal("Selected-$id", data) } fun loadSelected(media: Media, isDownload: Boolean = false): Selected { - val sharedPreferences = Injekt.get() - val data = loadData("${media.id}-select") ?: Selected().let { - it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) { - true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0) - else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0) - } - it.preferDub = loadData("settings_prefer_dub") ?: false - saveSelected(media.id, it) - it - } + val data = + PrefManager.getNullableCustomVal("Selected-${media.id}", null, Selected::class.java) + ?: Selected().let { + it.sourceIndex = 0 + it.preferDub = PrefManager.getVal(PrefName.SettingsPreferDub) + saveSelected(media.id, it) + it + } if (isDownload) { data.sourceIndex = if (media.anime != null) { AnimeSources.list.size - 1 diff --git a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt index c3c290aa60..e3d4a36376 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle @@ -26,6 +25,8 @@ import ani.dantotsu.* import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.GenresViewModel import ani.dantotsu.databinding.* +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.Dispatchers @@ -60,7 +61,7 @@ class MediaInfoFragment : Fragment() { @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val model: MediaDetailsViewModel by activityViewModels() - val offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("offlineMode", false) || !isOnline(requireContext()) + val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE binding.mediaInfoContainer.updateLayoutParams { bottomMargin += 128f.px + navBarHeight } @@ -94,8 +95,31 @@ class MediaInfoFragment : Fragment() { binding.mediaInfoStart.text = media.startDate?.toString() ?: "??" binding.mediaInfoEnd.text = media.endDate?.toString() ?: "??" if (media.anime != null) { - binding.mediaInfoDuration.text = - if (media.anime.episodeDuration != null) media.anime.episodeDuration.toString() else "??" + val episodeDuration = media.anime.episodeDuration + + binding.mediaInfoDuration.text = when { + episodeDuration != null -> { + val hours = episodeDuration / 60 + val minutes = episodeDuration % 60 + + val formattedDuration = buildString { + if (hours > 0) { + append("$hours hour") + if (hours > 1) append("s") + } + + if (minutes > 0) { + if (hours > 0) append(", ") + append("$minutes min") + if (minutes > 1) append("s") + } + } + + formattedDuration + } + + else -> "??" + } binding.mediaInfoDurationContainer.visibility = View.VISIBLE binding.mediaInfoSeasonContainer.visibility = View.VISIBLE binding.mediaInfoSeason.text = @@ -464,7 +488,7 @@ class MediaInfoFragment : Fragment() { parent.addView(bindi.root) } - if (!media.recommendations.isNullOrEmpty() && !offline ) { + if (!media.recommendations.isNullOrEmpty() && !offline) { val bind = ItemTitleRecyclerBinding.inflate( LayoutInflater.from(context), parent, diff --git a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt index 4c71664b5d..2962a5275a 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt @@ -16,7 +16,8 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.AnilistSearch import ani.dantotsu.connections.anilist.SearchResults import ani.dantotsu.databinding.ActivitySearchBinding -import ani.dantotsu.others.LangSet +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.themes.ThemeManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -33,13 +34,14 @@ class SearchActivity : AppCompatActivity() { private lateinit var mediaAdaptor: MediaAdaptor private lateinit var progressAdapter: ProgressAdapter private lateinit var concatAdapter: ConcatAdapter + private lateinit var headerAdaptor: SearchAdapter lateinit var result: SearchResults lateinit var updateChips: (() -> Unit) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivitySearchBinding.inflate(layoutInflater) setContentView(binding.root) @@ -51,7 +53,7 @@ class SearchActivity : AppCompatActivity() { bottom = navBarHeight + 80f.px ) - style = loadData("searchStyle") ?: 0 + style = PrefManager.getVal(PrefName.SearchStyle) var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false) if (!listOnly!!) listOnly = null @@ -76,7 +78,7 @@ class SearchActivity : AppCompatActivity() { progressAdapter = ProgressAdapter(searched = model.searched) mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true) - val headerAdaptor = SearchAdapter(this) + headerAdaptor = SearchAdapter(this, model.searchResults.type) val gridSize = (screenWidth / 120f).toInt() val gridLayoutManager = GridLayoutManager(this, gridSize) @@ -154,9 +156,18 @@ class SearchActivity : AppCompatActivity() { } } + fun emptyMediaAdapter() { + searchTimer.cancel() + searchTimer.purge() + mediaAdaptor.notifyItemRangeRemoved(0, model.searchResults.results.size) + model.searchResults.results.clear() + progressAdapter.bar?.visibility = View.GONE + } + private var searchTimer = Timer() private var loading = false fun search() { + headerAdaptor.setHistoryVisibility(false) val size = model.searchResults.results.size model.searchResults.results.clear() binding.searchRecyclerView.post { @@ -188,6 +199,7 @@ class SearchActivity : AppCompatActivity() { var state: Parcelable? = null override fun onPause() { + headerAdaptor.addHistory() super.onPause() state = binding.searchRecyclerView.layoutManager?.onSaveInstanceState() } diff --git a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt index 18fe0a4c2c..ced90bc4ae 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt @@ -1,7 +1,6 @@ package ani.dantotsu.media import android.annotation.SuppressLint -import android.content.Context import android.graphics.drawable.Drawable import android.text.Editable import android.text.TextWatcher @@ -9,6 +8,8 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.view.animation.AlphaAnimation +import android.view.animation.Animation import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity @@ -19,19 +20,25 @@ import androidx.recyclerview.widget.RecyclerView.HORIZONTAL import ani.dantotsu.App.Companion.context import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist -import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemSearchHeaderBinding -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.google.android.material.checkbox.MaterialCheckBox.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch -class SearchAdapter(private val activity: SearchActivity) : +class SearchAdapter(private val activity: SearchActivity, private val type: String) : RecyclerView.Adapter() { private val itemViewType = 6969 var search: Runnable? = null var requestFocus: Runnable? = null private var textWatcher: TextWatcher? = null + private lateinit var searchHistoryAdapter: SearchHistoryAdapter + private lateinit var binding: ItemSearchHeaderBinding override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder { val binding = @@ -41,8 +48,13 @@ class SearchAdapter(private val activity: SearchActivity) : @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) { - val binding = holder.binding + binding = holder.binding + searchHistoryAdapter = SearchHistoryAdapter(type) { + binding.searchBarText.setText(it) + } + binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context) + binding.searchHistoryList.adapter = searchHistoryAdapter val imm: InputMethodManager = activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager @@ -60,8 +72,7 @@ class SearchAdapter(private val activity: SearchActivity) : } binding.searchBar.hint = activity.result.type - if (currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("incognito", false ) == true){ + if (PrefManager.getVal(PrefName.Incognito)) { val startIconDrawableRes = R.drawable.ic_incognito_24 val startIconDrawable: Drawable? = context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) } @@ -103,7 +114,18 @@ class SearchAdapter(private val activity: SearchActivity) : override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - searchTitle() + if (s.toString().isBlank()) { + activity.emptyMediaAdapter() + CoroutineScope(Dispatchers.IO).launch { + delay(200) + activity.runOnUiThread { + setHistoryVisibility(true) + } + } + } else { + setHistoryVisibility(false) + searchTitle() + } } } binding.searchBarText.addTextChangedListener(textWatcher) @@ -126,14 +148,14 @@ class SearchAdapter(private val activity: SearchActivity) : it.alpha = 1f binding.searchResultList.alpha = 0.33f activity.style = 0 - saveData("searchStyle", 0) + PrefManager.setVal(PrefName.SearchStyle, 0) activity.recycler() } binding.searchResultList.setOnClickListener { it.alpha = 1f binding.searchResultGrid.alpha = 0.33f activity.style = 1 - saveData("searchStyle", 1) + PrefManager.setVal(PrefName.SearchStyle, 1) activity.recycler() } @@ -176,6 +198,40 @@ class SearchAdapter(private val activity: SearchActivity) : requestFocus = Runnable { binding.searchBarText.requestFocus() } } + fun setHistoryVisibility(visible: Boolean) { + if (visible) { + binding.searchResultLayout.startAnimation(fadeOutAnimation()) + binding.searchHistoryList.startAnimation(fadeInAnimation()) + binding.searchResultLayout.visibility = View.GONE + binding.searchHistoryList.visibility = View.VISIBLE + } else { + if (binding.searchResultLayout.visibility != View.VISIBLE) { + binding.searchResultLayout.startAnimation(fadeInAnimation()) + binding.searchHistoryList.startAnimation(fadeOutAnimation()) + } + binding.searchResultLayout.visibility = View.VISIBLE + binding.searchHistoryList.visibility = View.GONE + } + } + + private fun fadeInAnimation(): Animation { + return AlphaAnimation(0f, 1f).apply { + duration = 150 + fillAfter = true + } + } + + private fun fadeOutAnimation(): Animation { + return AlphaAnimation(1f, 0f).apply { + duration = 150 + fillAfter = true + } + } + + + fun addHistory() { + searchHistoryAdapter.add(binding.searchBarText.text.toString()) + } override fun getItemCount(): Int = 1 diff --git a/app/src/main/java/ani/dantotsu/media/SearchFilterBottomDialog.kt b/app/src/main/java/ani/dantotsu/media/SearchFilterBottomDialog.kt index 79fd87126a..297f82894c 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchFilterBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchFilterBottomDialog.kt @@ -17,6 +17,7 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.BottomSheetSearchFilterBinding import ani.dantotsu.databinding.ItemChipBinding import com.google.android.material.chip.Chip +import java.util.Calendar class SearchFilterBottomDialog : BottomSheetDialogFragment() { private var _binding: BottomSheetSearchFilterBinding? = null @@ -103,7 +104,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() { ArrayAdapter( binding.root.context, R.layout.item_dropdown, - (1970 until 2025).map { it.toString() }.reversed().toTypedArray() + (1970 until Calendar.getInstance().get(Calendar.YEAR) + 2).map { it.toString() } + .reversed().toTypedArray() ) ) } diff --git a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt new file mode 100644 index 0000000000..f1519e2b62 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt @@ -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( + DIFF_CALLBACK_INSTALLED + ) { + private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null + private var searchHistory: MutableSet? = 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()).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() { + override fun areItemsTheSame( + oldItem: String, + newItem: String + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: String, + newItem: String + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/app/src/main/java/ani/dantotsu/media/StudioActivity.kt b/app/src/main/java/ani/dantotsu/media/StudioActivity.kt index 53e04a37c3..ac28cd5f1e 100644 --- a/app/src/main/java/ani/dantotsu/media/StudioActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/StudioActivity.kt @@ -18,7 +18,6 @@ import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityStudioBinding import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet import ani.dantotsu.others.getSerialized import ani.dantotsu.px import ani.dantotsu.statusBarHeight @@ -36,7 +35,7 @@ class StudioActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityStudioBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt index d74a89294d..e5ebd81b02 100644 --- a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt +++ b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt @@ -45,9 +45,18 @@ class SubtitleDownloader { } //actually downloads lol - suspend fun downloadSubtitle(context: Context, url: String, downloadedType: DownloadedType) { + suspend fun downloadSubtitle( + context: Context, + url: String, + downloadedType: DownloadedType + ) { try { - val directory = DownloadsManager.getDirectory(context, downloadedType.type, downloadedType.title, downloadedType.chapter) + val directory = DownloadsManager.getDirectory( + context, + downloadedType.type, + downloadedType.title, + downloadedType.chapter + ) if (!directory.exists()) { //just in case directory.mkdirs() } diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt index f072a549b5..a30da85fdf 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt @@ -1,5 +1,6 @@ package ani.dantotsu.media.anime +import java.util.Locale import java.util.regex.Matcher import java.util.regex.Pattern @@ -9,7 +10,57 @@ class AnimeNameAdapter { "(episode|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*" const val failedEpisodeNumberRegex = "(? "sub" + SubDubType.DUB -> "dub" + SubDubType.NULL -> "" + } + val toggledCasePreserved = + if (subdub?.get(0)?.isUpperCase() == true || soft?.get(0) + ?.isUpperCase() == true + ) toggled.replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.ROOT + ) else it.toString() + } else toggled + + subdubMatcher.replaceFirst(toggledCasePreserved + bed) + } else { + null + } + } + + fun getSubDub(text: String): SubDubType { + val subdubPattern: Pattern = Pattern.compile(subdubRegex, Pattern.CASE_INSENSITIVE) + val subdubMatcher: Matcher = subdubPattern.matcher(text) + + return if (subdubMatcher.find()) { + val subdub = subdubMatcher.group(2)?.lowercase(Locale.ROOT) + when (subdub) { + "sub" -> SubDubType.SUB + "dub" -> SubDubType.DUB + else -> SubDubType.NULL + } + } else { + SubDubType.NULL + } + } + + enum class SubDubType { + SUB, DUB, NULL + } fun findSeasonNumber(text: String): Int? { val seasonPattern: Pattern = Pattern.compile(seasonRegex, Pattern.CASE_INSENSITIVE) diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index 2f70a2a9c8..0ad6ecafe3 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -1,7 +1,6 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint -import android.content.Context import android.content.Intent import android.net.Uri import android.view.LayoutInflater @@ -27,6 +26,8 @@ import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.DynamicAnimeParser import ani.dantotsu.parsers.WatchSources +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import com.google.android.material.chip.Chip @@ -59,7 +60,7 @@ class AnimeWatchAdapter( _binding = binding //Youtube - if (media.anime!!.youtube != null && fragment.uiSettings.showYtButton) { + if (media.anime!!.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) { binding.animeSourceYT.visibility = View.VISIBLE binding.animeSourceYT.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube)) @@ -90,11 +91,9 @@ class AnimeWatchAdapter( null ) } - val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE + val offline = if (!isOnline(binding.root.context) || PrefManager.getVal( + PrefName.OfflineMode ) - ?.getBoolean("offlineMode", false) == true ) View.GONE else View.VISIBLE binding.animeSourceNameContainer.visibility = offline @@ -113,7 +112,7 @@ class AnimeWatchAdapter( binding.animeSourceTitle.text = showUserText showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately) View.VISIBLE else View.GONE + if (isDubAvailableSeparately()) View.VISIBLE else View.GONE } } @@ -133,7 +132,7 @@ class AnimeWatchAdapter( binding.animeSourceDubbed.isChecked = selectDub changing = false binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately) View.VISIBLE else View.GONE + if (isDubAvailableSeparately()) View.VISIBLE else View.GONE source = i setLanguageList(0, i) } @@ -154,7 +153,7 @@ class AnimeWatchAdapter( binding.animeSourceDubbed.isChecked = selectDub changing = false binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately) View.VISIBLE else View.GONE + if (isDubAvailableSeparately()) View.VISIBLE else View.GONE setLanguageList(i, source) } subscribeButton(false) @@ -199,7 +198,8 @@ class AnimeWatchAdapter( var refresh = false var run = false var reversed = media.selected!!.recyclerReversed - var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView + var style = + media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" dialogBinding.animeSourceTop.setOnClickListener { @@ -357,7 +357,9 @@ class AnimeWatchAdapter( val episodes = media.anime.episodes!!.keys.toTypedArray() val anilistEp = (media.userProgress ?: 0).plus(1) - val appEp = loadData("${media.id}_current_ep")?.toIntOrNull() ?: 1 + val appEp = + PrefManager.getCustomVal("${media.id}_current_ep", "")?.toIntOrNull() + ?: 1 var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() if (episodes.contains(continueEp)) { @@ -369,7 +371,10 @@ class AnimeWatchAdapter( media.id, continueEp ) - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > fragment.playerSettings.watchPercentage) { + if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal( + PrefName.WatchPercentage + ) + ) { val e = episodes.indexOf(continueEp) if (e != -1 && e + 1 < episodes.size) { continueEp = episodes[e + 1] @@ -396,7 +401,10 @@ class AnimeWatchAdapter( fragment.onEpisodeClick(continueEp) } if (fragment.continueEp) { - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < fragment.playerSettings.watchPercentage) { + if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal( + PrefName.WatchPercentage + ) + ) { binding.animeSourceContinue.performClick() fragment.continueEp = false } diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index 487bf20718..9d720020fa 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -25,6 +25,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.DownloadService import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.FragmentAnimeWatchBinding @@ -39,9 +40,9 @@ import ani.dantotsu.others.LanguageMapper import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.HAnimeSources -import ani.dantotsu.settings.PlayerSettings -import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId @@ -84,8 +85,6 @@ class AnimeWatchFragment : Fragment() { var continueEp: Boolean = false var loaded = false - lateinit var playerSettings: PlayerSettings - lateinit var uiSettings: UserInterfaceSettings override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -118,12 +117,6 @@ class AnimeWatchFragment : Fragment() { var maxGridSize = (screenWidth / 100f).roundToInt() maxGridSize = max(4, maxGridSize - (maxGridSize % 2)) - playerSettings = - loadData("player_settings", toast = false) - ?: PlayerSettings().apply { saveData("player_settings", this) } - uiSettings = loadData("ui_settings", toast = false) - ?: UserInterfaceSettings().apply { saveData("ui_settings", this) } - val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize) gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { @@ -144,6 +137,23 @@ class AnimeWatchFragment : Fragment() { binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.ScrollTop.setOnClickListener { + binding.animeSourceRecycler.scrollToPosition(10) + binding.animeSourceRecycler.smoothScrollToPosition(0) + } + binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val position = gridLayoutManager.findFirstVisibleItemPosition() + if (position > 2) { + binding.ScrollTop.translationY = -navBarHeight.toFloat() + binding.ScrollTop.visibility = View.VISIBLE + } else { + binding.ScrollTop.visibility = View.GONE + } + } + }) model.scrolledToTop.observe(viewLifecycleOwner) { if (it) binding.animeSourceRecycler.scrollToPosition(0) } @@ -155,7 +165,7 @@ class AnimeWatchFragment : Fragment() { media.selected = model.loadSelected(media) subscribed = - SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id) + SubscriptionHelper.getSubscriptions().containsKey(media.id) style = media.selected!!.recyclerStyle reverse = media.selected!!.recyclerReversed @@ -172,7 +182,7 @@ class AnimeWatchFragment : Fragment() { headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!) episodeAdapter = EpisodeAdapter( - style ?: uiSettings.animeDefaultView, + style ?: PrefManager.getVal(PrefName.AnimeDefaultView), media, this, offlineMode = offlineMode @@ -273,7 +283,7 @@ class AnimeWatchFragment : Fragment() { model.watchSources?.get(selected.sourceIndex)?.showUserTextListener = null selected.sourceIndex = i selected.server = null - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected return model.watchSources?.get(i)!! } @@ -281,7 +291,7 @@ class AnimeWatchFragment : Fragment() { fun onLangChange(i: Int) { val selected = model.loadSelected(media) selected.langIndex = i - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected } @@ -289,7 +299,7 @@ class AnimeWatchFragment : Fragment() { val selected = model.loadSelected(media) model.watchSources?.get(selected.sourceIndex)?.selectDub = checked selected.preferDub = checked - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode( @@ -308,7 +318,7 @@ class AnimeWatchFragment : Fragment() { reverse = rev media.selected!!.recyclerStyle = style media.selected!!.recyclerReversed = reverse - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) reload() } @@ -316,7 +326,7 @@ class AnimeWatchFragment : Fragment() { media.selected!!.chip = i start = s end = e - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) reload() } @@ -364,12 +374,10 @@ class AnimeWatchFragment : Fragment() { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray() - var selectedIndex = 0 val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> - selectedIndex = which - selectedSetting = allSettings[selectedIndex] + .setSingleChoiceItems(names, -1) { dialog, which -> + selectedSetting = allSettings[which] itemSelected = true dialog.dismiss() @@ -419,7 +427,7 @@ class AnimeWatchFragment : Fragment() { fun onEpisodeClick(i: String) { model.continueMedia = false - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) model.onEpisodeClick(media, i, requireActivity().supportFragmentManager) } @@ -458,17 +466,11 @@ class AnimeWatchFragment : Fragment() { ) ) val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i) - val id = requireContext().getSharedPreferences( - ContextCompat.getString(requireContext(), R.string.anime_downloads), - Context.MODE_PRIVATE - ).getString( + val id = PrefManager.getAnimeDownloadPreferences().getString( taskName, "" ) ?: "" - requireContext().getSharedPreferences( - ContextCompat.getString(requireContext(), R.string.anime_downloads), - Context.MODE_PRIVATE - ).edit().remove(taskName).apply() + PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply() DownloadService.sendRemoveDownload( requireContext(), ExoplayerDownloadService::class.java, @@ -520,7 +522,7 @@ class AnimeWatchFragment : Fragment() { selected.latest = media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) headerAdapter.handleEpisodes() val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex) episodeAdapter.offlineMode = isDownloaded @@ -536,7 +538,7 @@ class AnimeWatchFragment : Fragment() { arr = (arr.reversed() as? ArrayList) ?: arr } episodeAdapter.arr = arr - episodeAdapter.updateType(style ?: uiSettings.animeDefaultView) + episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView)) episodeAdapter.notifyItemRangeInserted(0, arr.size) for (download in downloadManager.animeDownloadedTypes) { if (download.title == media.mainName()) { diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 980f9d0084..78f4fbef7d 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -2,14 +2,12 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint import android.app.AlertDialog -import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.LinearInterpolator import android.widget.LinearLayout import androidx.annotation.OptIn -import androidx.core.content.ContextCompat import androidx.lifecycle.coroutineScope import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.DownloadIndex @@ -22,6 +20,7 @@ import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.video.Helper import ani.dantotsu.media.Media +import ani.dantotsu.settings.saving.PrefManager import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl import kotlinx.coroutines.delay @@ -30,8 +29,8 @@ import kotlin.math.ln import kotlin.math.pow fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) { - val curr = loadData("${mediaId}_${ep}") - val max = loadData("${mediaId}_${ep}_max") + val curr = PrefManager.getNullableCustomVal("${mediaId}_${ep}", null, Long::class.java) + val max = PrefManager.getNullableCustomVal("${mediaId}_${ep}_max", null, Long::class.java) if (curr != null && max != null) { cont.visibility = View.VISIBLE val div = curr.toFloat() / max.toFloat() @@ -110,7 +109,7 @@ class EpisodeAdapter( when (holder) { is EpisodeListViewHolder -> { val binding = holder.binding - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } @@ -129,7 +128,7 @@ class EpisodeAdapter( binding.itemEpisodeDesc.visibility = if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE binding.itemEpisodeDesc.text = ep.desc ?: "" - holder.bind(ep.number, ep.downloadProgress , ep.desc) + holder.bind(ep.number, ep.downloadProgress, ep.desc) if (media.userProgress != null) { if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) { @@ -159,7 +158,7 @@ class EpisodeAdapter( is EpisodeGridViewHolder -> { val binding = holder.binding - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } @@ -202,7 +201,7 @@ class EpisodeAdapter( is EpisodeCompactViewHolder -> { val binding = holder.binding - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeFillerView.visibility = if (ep.filler) View.VISIBLE else View.GONE @@ -253,10 +252,7 @@ class EpisodeAdapter( media.mainName(), episodeNumber ) - val id = fragment.requireContext().getSharedPreferences( - ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads), - Context.MODE_PRIVATE - ).getString( + val id = PrefManager.getAnimeDownloadPreferences().getString( taskName, "" ) ?: "" @@ -391,9 +387,10 @@ class EpisodeAdapter( }, 1000) } else { binding.itemDownloadStatus.visibility = View.GONE - binding.itemEpisodeDesc.visibility = if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE + binding.itemEpisodeDesc.visibility = + if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE // Show download icon - binding.itemDownload.setImageResource(R.drawable.ic_circle_add) + binding.itemDownload.setImageResource(R.drawable.ic_download_24) binding.itemDownload.rotation = 0f } diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 29aae29fb7..cbe39704ca 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -65,6 +65,7 @@ import androidx.mediarouter.app.MediaRouteButton import ani.dantotsu.* import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.discord.DiscordService import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton @@ -77,13 +78,12 @@ import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.others.AniSkip import ani.dantotsu.others.AniSkip.getType -import ani.dantotsu.others.LangSet import ani.dantotsu.others.ResettableTimer import ani.dantotsu.others.getSerialized import ani.dantotsu.parsers.* -import ani.dantotsu.settings.PlayerSettings import ani.dantotsu.settings.PlayerSettingsActivity -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.themes.ThemeManager import com.bumptech.glide.Glide import com.google.android.gms.cast.framework.CastButtonFactory @@ -91,7 +91,6 @@ import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.material.slider.Slider -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.lagradost.nicehttp.ignoreAllSSLErrors import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -99,6 +98,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import okhttp3.internal.immutableListOf +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.util.* import java.util.concurrent.* import kotlin.math.max @@ -132,7 +133,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private lateinit var exoSubtitle: ImageButton private lateinit var exoSubtitleView: SubtitleView private lateinit var exoRotate: ImageButton - private lateinit var exoQuality: ImageButton private lateinit var exoSpeed: ImageButton private lateinit var exoScreen: ImageButton private lateinit var exoNext: ImageButton @@ -148,9 +148,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private lateinit var skipTimeText: TextView private lateinit var timeStampText: TextView private lateinit var animeTitle: TextView - private lateinit var videoName: TextView private lateinit var videoInfo: TextView - private lateinit var serverInfo: TextView private lateinit var episodeTitle: Spinner private var orientationListener: OrientationEventListener? = null @@ -187,9 +185,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private var pipEnabled = false private var aspectRatio = Rational(16, 9) - var settings = PlayerSettings() - private var uiSettings = UserInterfaceSettings() - private val handler = Handler(Looper.getMainLooper()) val model: MediaDetailsViewModel by viewModels() @@ -243,8 +238,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } - private fun setupSubFormatting(playerView: PlayerView, settings: PlayerSettings) { - val primaryColor = when (settings.primaryColor) { + private fun setupSubFormatting(playerView: PlayerView) { + val primaryColor = when (PrefManager.getVal(PrefName.PrimaryColor)) { 0 -> Color.BLACK 1 -> Color.DKGRAY 2 -> Color.GRAY @@ -259,7 +254,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL 11 -> Color.TRANSPARENT else -> Color.WHITE } - val secondaryColor = when (settings.secondaryColor) { + val secondaryColor = when (PrefManager.getVal(PrefName.SecondaryColor)) { 0 -> Color.BLACK 1 -> Color.DKGRAY 2 -> Color.GRAY @@ -274,14 +269,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL 11 -> Color.TRANSPARENT else -> Color.BLACK } - val outline = when (settings.outline) { + val outline = when (PrefManager.getVal(PrefName.Outline)) { 0 -> EDGE_TYPE_OUTLINE // Normal 1 -> EDGE_TYPE_DEPRESSED // Shine 2 -> EDGE_TYPE_DROP_SHADOW // Drop shadow 3 -> EDGE_TYPE_NONE // No outline else -> EDGE_TYPE_OUTLINE // Normal } - val subBackground = when (settings.subBackground) { + val subBackground = when (PrefManager.getVal(PrefName.SubBackground)) { 0 -> Color.TRANSPARENT 1 -> Color.BLACK 2 -> Color.DKGRAY @@ -296,7 +291,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL 11 -> Color.MAGENTA else -> Color.TRANSPARENT } - val subWindow = when (settings.subWindow) { + val subWindow = when (PrefManager.getVal(PrefName.SubWindow)) { 0 -> Color.TRANSPARENT 1 -> Color.BLACK 2 -> Color.DKGRAY @@ -311,7 +306,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL 11 -> Color.MAGENTA else -> Color.TRANSPARENT } - val font = when (settings.font) { + val font = when (PrefManager.getVal(PrefName.Font)) { 0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold) 1 -> ResourcesCompat.getFont(this, R.font.poppins_bold) 2 -> ResourcesCompat.getFont(this, R.font.poppins) @@ -334,7 +329,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityExoplayerBinding.inflate(layoutInflater) setContentView(binding.root) @@ -357,21 +352,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL finishAndRemoveTask() } - settings = loadData("player_settings") ?: PlayerSettings().apply { - saveData( - "player_settings", - this - ) - } - uiSettings = loadData("ui_settings") ?: UserInterfaceSettings().apply { - saveData( - "ui_settings", - this - ) - } - playerView = findViewById(R.id.player_view) - exoQuality = playerView.findViewById(R.id.exo_quality) exoPlay = playerView.findViewById(androidx.media3.ui.R.id.exo_play) exoSource = playerView.findViewById(R.id.exo_source) exoSettings = playerView.findViewById(R.id.exo_settings) @@ -406,21 +387,23 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL }, AUDIO_CONTENT_TYPE_MOVIE, AUDIOFOCUS_GAIN) if (System.getInt(contentResolver, System.ACCELEROMETER_ROTATION, 0) != 1) { - orientationListener = - object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { - override fun onOrientationChanged(orientation: Int) { - if (orientation in 45..135) { - if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility = - View.VISIBLE - rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - } else if (orientation in 225..315) { - if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility = - View.VISIBLE - rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + if (PrefManager.getVal(PrefName.RotationPlayer)) { + orientationListener = + object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { + override fun onOrientationChanged(orientation: Int) { + if (orientation in 45..135) { + if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility = + View.VISIBLE + rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + } else if (orientation in 225..315) { + if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility = + View.VISIBLE + rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } } } - } - orientationListener?.enable() + orientationListener?.enable() + } requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE exoRotate.setOnClickListener { @@ -429,14 +412,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } - setupSubFormatting(playerView, settings) + setupSubFormatting(playerView) - playerView.subtitleView?.alpha = when (settings.subtitles) { + playerView.subtitleView?.alpha = when (PrefManager.getVal(PrefName.Subtitles)) { true -> 1f false -> 0f } - val fontSize = settings.fontSize.toFloat() + val fontSize = PrefManager.getVal(PrefName.FontSize).toFloat() playerView.subtitleView?.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize) if (savedInstanceState != null) { @@ -469,17 +452,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } else View.GONE } - exoSkipOpEd.alpha = if (settings.autoSkipOPED) 1f else 0.3f + exoSkipOpEd.alpha = if (PrefManager.getVal(PrefName.AutoSkipOPED)) 1f else 0.3f exoSkipOpEd.setOnClickListener { - settings.autoSkipOPED = if (settings.autoSkipOPED) { + if (PrefManager.getVal(PrefName.AutoSkipOPED)) { snackString(getString(R.string.disabled_auto_skip)) - false + PrefManager.setVal(PrefName.AutoSkipOPED, false) } else { snackString(getString(R.string.auto_skip)) - true + PrefManager.setVal(PrefName.AutoSkipOPED, true) } - saveData("player_settings", settings) - exoSkipOpEd.alpha = if (settings.autoSkipOPED) 1f else 0.3f + exoSkipOpEd.alpha = if (PrefManager.getVal(PrefName.AutoSkipOPED)) 1f else 0.3f } //Play Pause @@ -506,7 +488,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL // Picture-in-picture if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { pipEnabled = - packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && settings.pip + packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && PrefManager.getVal( + PrefName.Pip + ) if (pipEnabled) { exoPip.visibility = View.VISIBLE exoPip.setOnClickListener { @@ -539,14 +523,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } //Skip Time Button - if (settings.skipTime > 0) { - exoSkip.findViewById(R.id.exo_skip_time).text = settings.skipTime.toString() + var skipTime = PrefManager.getVal(PrefName.SkipTime) + if (skipTime > 0) { + exoSkip.findViewById(R.id.exo_skip_time).text = skipTime.toString() exoSkip.setOnClickListener { if (isInitialized) - exoPlayer.seekTo(exoPlayer.currentPosition + settings.skipTime * 1000) + exoPlayer.seekTo(exoPlayer.currentPosition + skipTime * 1000) } exoSkip.setOnLongClickListener { - val dialog = Dialog(this, R.style.DialogTheme) + val dialog = Dialog(this, R.style.MyPopup) dialog.setContentView(R.layout.item_seekbar_dialog) dialog.setCancelable(true) dialog.setCanceledOnTouchOutside(true) @@ -554,18 +539,19 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) - if (settings.skipTime <= 120) { - dialog.findViewById(R.id.seekbar).value = settings.skipTime.toFloat() + if (skipTime <= 120) { + dialog.findViewById(R.id.seekbar).value = skipTime.toFloat() } else { dialog.findViewById(R.id.seekbar).value = 120f } dialog.findViewById(R.id.seekbar).addOnChangeListener { _, value, _ -> - settings.skipTime = value.toInt() - saveData(player, settings) + skipTime = value.toInt() + //saveData(player, settings) + PrefManager.setVal(PrefName.SkipTime, skipTime) playerView.findViewById(R.id.exo_skip_time).text = - settings.skipTime.toString() + skipTime.toString() dialog.findViewById(R.id.seekbar_value).text = - settings.skipTime.toString() + skipTime.toString() } dialog.findViewById(R.id.seekbar) .addOnSliderTouchListener(object : Slider.OnSliderTouchListener { @@ -577,7 +563,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL dialog.findViewById(R.id.seekbar_title).text = getString(R.string.skip_time) dialog.findViewById(R.id.seekbar_value).text = - settings.skipTime.toString() + skipTime.toString() @Suppress("DEPRECATION") dialog.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION dialog.show() @@ -587,7 +573,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL exoSkip.visibility = View.GONE } - val gestureSpeed = (300 * uiSettings.animationSpeed).toLong() + val gestureSpeed = (300 * PrefManager.getVal(PrefName.AnimationSpeed)).toLong() //Player UI Visibility Handler val brightnessRunnable = Runnable { if (exoBrightnessCont.alpha == 1f) @@ -617,7 +603,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } }) val overshoot = AnimationUtils.loadInterpolator(this, R.anim.over_shoot) - val controllerDuration = (uiSettings.animationSpeed * 200).toLong() + val controllerDuration = (300 * PrefManager.getVal(PrefName.AnimationSpeed)).toLong() fun handleController() { if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) !isInPictureInPictureMode else true) { if (playerView.isControllerFullyVisible) { @@ -702,13 +688,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL var seekTimesR = 0 fun seek(forward: Boolean, event: MotionEvent? = null) { + val seekTime = PrefManager.getVal(PrefName.SeekTime) val (card, text) = if (forward) { - forwardText.text = "+${settings.seekTime * ++seekTimesF}" - handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + settings.seekTime * 1000) } + forwardText.text = "+${seekTime * ++seekTimesF}" + handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) } fastForwardCard to forwardText } else { - rewindText.text = "-${settings.seekTime * ++seekTimesR}" - handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - settings.seekTime * 1000) } + rewindText.text = "-${seekTime * ++seekTimesR}" + handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) } fastRewindCard to rewindText } @@ -763,7 +750,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } - if (!settings.doubleTap) { + if (!PrefManager.getVal(PrefName.DoubleTap)) { playerView.findViewById(R.id.exo_fast_forward_button_cont).visibility = View.VISIBLE playerView.findViewById(R.id.exo_fast_rewind_button_cont).visibility = @@ -784,10 +771,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL keyMap[KEYCODE_DPAD_LEFT] = { seek(false) } //Screen Gestures - if (settings.gestures || settings.doubleTap) { + if (PrefManager.getVal(PrefName.Gestures) || PrefManager.getVal(PrefName.DoubleTap)) { fun doubleTap(forward: Boolean, event: MotionEvent) { - if (!locked && isInitialized && settings.doubleTap) { + if (!locked && isInitialized && PrefManager.getVal(PrefName.DoubleTap)) { seek(forward, event) } } @@ -844,7 +831,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL isFastForwarding = true exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2) fastForward.visibility = View.VISIBLE - fastForward.text = ("${exoPlayer.playbackParameters.speed}x") + fastForward.text = "${exoPlayer.playbackParameters.speed}x" } fun stopFastForward() { @@ -858,7 +845,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL //FastRewind (Left Panel) val fastRewindDetector = GestureDetector(this, object : GesturesListener() { override fun onLongClick(event: MotionEvent) { - if (settings.fastforward) fastForward() + if (PrefManager.getVal(PrefName.FastForward)) fastForward() } override fun onDoubleClick(event: MotionEvent) { @@ -866,7 +853,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } override fun onScrollYClick(y: Float) { - if (!locked && settings.gestures) { + if (!locked && PrefManager.getVal(PrefName.Gestures)) { exoBrightness.value = clamp(exoBrightness.value + y / 100, 0f, 10f) if (exoBrightnessCont.visibility != View.VISIBLE) { exoBrightnessCont.visibility = View.VISIBLE @@ -890,7 +877,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL //FastForward (Right Panel) val fastForwardDetector = GestureDetector(this, object : GesturesListener() { override fun onLongClick(event: MotionEvent) { - if (settings.fastforward) fastForward() + if (PrefManager.getVal(PrefName.FastForward)) fastForward() } override fun onDoubleClick(event: MotionEvent) { @@ -898,7 +885,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } override fun onScrollYClick(y: Float) { - if (!locked && settings.gestures) { + if (!locked && PrefManager.getVal(PrefName.Gestures)) { exoVolume.value = clamp(exoVolume.value + y / 100, 0f, 10f) if (exoVolumeCont.visibility != View.VISIBLE) { exoVolumeCont.visibility = View.VISIBLE @@ -926,21 +913,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL title = media.userPreferredName episodes = media.anime?.episodes ?: return startMainActivity(this) - videoName = playerView.findViewById(R.id.exo_video_name) videoInfo = playerView.findViewById(R.id.exo_video_info) - serverInfo = playerView.findViewById(R.id.exo_server_info) - - if (!settings.videoInfo) { - videoName.visibility = View.GONE - videoInfo.visibility = View.GONE - serverInfo.visibility = View.GONE - } else { - videoName.isSelected = true - } model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources - serverInfo.text = model.watchSources!!.names.getOrNull(media.selected!!.sourceIndex) - ?: model.watchSources!!.names[0] model.epChanged.observe(this) { epChanging = !it @@ -964,10 +939,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL fun change(index: Int) { if (isInitialized) { changingServer = false - saveData( + PrefManager.setCustomVal( "${media.id}_${episodeArr[currentEpisodeIndex]}", - exoPlayer.currentPosition, - this + exoPlayer.currentPosition ) exoPlayer.seekTo(0) val prev = episodeArr[currentEpisodeIndex] @@ -1024,14 +998,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL currentEpisodeIndex = episodeArr.indexOf(ep.number) episodeTitle.setSelection(currentEpisodeIndex) if (isInitialized) releasePlayer() - playbackPosition = loadData("${media.id}_${ep.number}", this) ?: 0 + playbackPosition = PrefManager.getCustomVal( + "${media.id}_${ep.number}", + 0 + ) initPlayer() preloading = false val context = this - - val incognito = baseContext.getSharedPreferences("Dantotsu", MODE_PRIVATE) - .getBoolean("incognito", false) - if (isOnline(context) && Discord.token != null && !incognito) { + val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) + if ((isOnline(context) && !offline) && Discord.token != null && !incognito) { lifecycleScope.launch { val presence = RPC.createPresence(RPC.Companion.RPCData( applicationId = Discord.application_Id, @@ -1075,7 +1051,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } //FullScreen - isFullscreen = loadData("${media.id}_fullscreenInt", this) ?: isFullscreen + isFullscreen = PrefManager.getCustomVal("${media.id}_fullscreenInt", isFullscreen) playerView.resizeMode = when (isFullscreen) { 0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT 1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM @@ -1099,11 +1075,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL else -> "Original" } ) - saveData("${media.id}_fullscreenInt", isFullscreen, this) + PrefManager.setCustomVal("${media.id}_fullscreenInt", isFullscreen) } //Cast - if (settings.cast) { + if (PrefManager.getVal(PrefName.Cast)) { playerView.findViewById(R.id.exo_cast).apply { visibility = View.VISIBLE try { @@ -1121,10 +1097,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL //Settings exoSettings.setOnClickListener { - saveData( + PrefManager.setCustomVal( "${media.id}_${media.anime!!.selectedEpisode}", - exoPlayer.currentPosition, - this + exoPlayer.currentPosition ) val intent = Intent(this, PlayerSettingsActivity::class.java).apply { putExtra("subtitle", subtitle) @@ -1135,7 +1110,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL //Speed val speeds = - if (settings.cursedSpeeds) + if (PrefManager.getVal(PrefName.CursedSpeeds)) arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f) else arrayOf( @@ -1155,16 +1130,20 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL ) val speedsName = speeds.map { "${it}x" }.toTypedArray() - var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed + //var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed + var curSpeed = PrefManager.getCustomVal( + "${media.id}_speed", + PrefManager.getVal(PrefName.DefaultSpeed) + ) playbackParameters = PlaybackParameters(speeds[curSpeed]) var speed: Float val speedDialog = - AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.speed)) + AlertDialog.Builder(this, R.style.MyPopup).setTitle(getString(R.string.speed)) exoSpeed.setOnClickListener { val dialog = speedDialog.setSingleChoiceItems(speedsName, curSpeed) { dialog, i -> if (isInitialized) { - saveData("${media.id}_speed", i, this) + PrefManager.setCustomVal("${media.id}_speed", i) speed = speeds[i] curSpeed = i playbackParameters = PlaybackParameters(speed) @@ -1177,7 +1156,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } speedDialog.setOnCancelListener { hideSystemBars() } - if (settings.autoPlay) { + if (PrefManager.getVal(PrefName.AutoPlay)) { var touchTimer = Timer() fun touched() { interacted = true @@ -1198,8 +1177,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } - isFullscreen = settings.resize - playerView.resizeMode = when (settings.resize) { + isFullscreen = PrefManager.getVal(PrefName.Resize) + playerView.resizeMode = when (isFullscreen) { 0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT 1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM 2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL @@ -1207,39 +1186,52 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } preloading = false - val incognito = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("incognito", false) ?: false + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) val showProgressDialog = - if (settings.askIndividual) loadData("${media.id}_progressDialog") - ?: true else false - if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true) + if (PrefManager.getVal(PrefName.AskIndividualPlayer)) PrefManager.getCustomVal( + "${media.id}_ProgressDialog", + true + ) else false + if (!incognito && showProgressDialog && Anilist.userid != null && if (media.isAdult) PrefManager.getVal( + PrefName.UpdateForHPlayer + ) else true + ) { AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.auto_update, media.userPreferredName)) .apply { - if (incognito) { - setMessage(getString(R.string.incognito_will_not_update)) - } setOnCancelListener { hideSystemBars() } setCancelable(false) setPositiveButton(getString(R.string.yes)) { dialog, _ -> - saveData("${media.id}_progressDialog", false) - saveData("${media.id}_save_progress", true) + PrefManager.setCustomVal( + "${media.id}_ProgressDialog", + false + ) + PrefManager.setCustomVal( + "${media.id}_save_progress", + true + ) dialog.dismiss() model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke") } setNegativeButton(getString(R.string.no)) { dialog, _ -> - saveData("${media.id}_progressDialog", false) - saveData("${media.id}_save_progress", false) + PrefManager.setCustomVal( + "${media.id}_ProgressDialog", + false + ) + PrefManager.setCustomVal( + "${media.id}_save_progress", + false + ) toast(getString(R.string.reset_auto_update)) dialog.dismiss() model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke") } show() } - else model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke") + } else model.setEpisode(episodes[media.anime!!.selectedEpisode!!]!!, "invoke") //Start the recursive Fun - if (settings.timeStampsEnabled) + if (PrefManager.getVal(PrefName.TimeStampsEnabled)) updateTimeStamp() } @@ -1247,12 +1239,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private fun initPlayer() { checkNotch() - saveData("${media.id}_current_ep", media.anime!!.selectedEpisode!!, this) + PrefManager.setCustomVal( + "${media.id}_current_ep", + media.anime!!.selectedEpisode!! + ) - val set = loadData>("continue_ANIME", this) ?: mutableSetOf() - if (set.contains(media.id)) set.remove(media.id) - set.add(media.id) - saveData("continue_ANIME", set, this) + val list = PrefManager.getVal>(PrefName.ContinuedAnime).toMutableList() + if (list.contains(media.id)) list.remove(media.id) + list.add(media.id) + PrefManager.setVal(PrefName.ContinuedAnime, list.toList()) lifecycleScope.launch(Dispatchers.IO) { extractor?.onVideoStopped(video) @@ -1263,7 +1258,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL video = ext.videos.getOrNull(episode.selectedVideo) ?: return subtitle = intent.getSerialized("subtitle") - ?: when (val subLang: String? = loadData("subLang_${media.id}", this)) { + ?: when (val subLang: String? = + PrefManager.getCustomVal("subLang_${media.id}", null as String?)) { null -> { when (episode.selectedSubtitle) { null -> null @@ -1305,7 +1301,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } println("sub: $sub") } else { - val subUri = Uri.parse((subtitle!!.file.url)) + val subUri = Uri.parse(subtitle!!.file.url) sub = MediaItem.SubtitleConfiguration .Builder(subUri) .setSelectionFlags(C.SELECTION_FLAG_FORCED) @@ -1342,7 +1338,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } dataSource } - val dafuckDataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString())) + val dafuckDataSourceFactory = + DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString())) cacheFactory = CacheDataSource.Factory().apply { setCache(Helper.getSimpleCache(this@ExoplayerView)) if (ext.server.offline) { @@ -1361,7 +1358,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL val downloadedMediaItem = if (ext.server.offline) { val key = ext.server.name - downloadId = getSharedPreferences(getString(R.string.anime_downloads), MODE_PRIVATE) + downloadId = PrefManager.getAnimeDownloadPreferences() .getString(key, null) if (downloadId != null) { Helper.downloadManager(this) @@ -1413,16 +1410,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL .setRendererDisabled(TRACK_TYPE_VIDEO, false) .setRendererDisabled(C.TRACK_TYPE_AUDIO, false) .setRendererDisabled(C.TRACK_TYPE_TEXT, false) - .setMinVideoSize( - loadData("maxWidth", this) ?: 720, - loadData("maxHeight", this) ?: 480 - ) .setMaxVideoSize(1, 1) //.setOverrideForType( // TrackSelectionOverride(trackSelector, 2)) ) - if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) { + if (playbackPosition != 0L && !changingServer && !PrefManager.getVal(PrefName.AlwaysContinue)) { val time = String.format( "%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(playbackPosition), TimeUnit.MILLISECONDS.toMinutes(playbackPosition) - TimeUnit.HOURS.toMinutes( @@ -1436,7 +1429,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL ) ) ) - val dialog = AlertDialog.Builder(this, R.style.DialogTheme) + val dialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.continue_from, time)).apply { setCancelable(false) setPositiveButton(getString(R.string.yes)) { d, _ -> @@ -1464,9 +1457,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL this.playbackParameters = this@ExoplayerView.playbackParameters setMediaItem(mediaItem) prepare() - loadData("${media.id}_${media.anime!!.selectedEpisode}_max")?.apply { - if (this <= playbackPosition) playbackPosition = max(0, this - 5) - } + PrefManager.getCustomVal( + "${media.id}_${media.anime!!.selectedEpisode}_max", + Long.MAX_VALUE + ) + .takeIf { it != Long.MAX_VALUE } + ?.let { if (it <= playbackPosition) playbackPosition = max(0, it - 5) } seekTo(playbackPosition) } playerView.player = exoPlayer @@ -1539,8 +1535,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL changingServer = true media.selected!!.server = null - saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this) - model.saveSelected(media.id, media.selected!!, this) + PrefManager.setCustomVal( + "${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition + ) + model.saveSelected(media.id, media.selected!!) model.onEpisodeClick( media, episode.number, this.supportFragmentManager, launch = false @@ -1548,8 +1546,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } private fun subClick() { - saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this) - model.saveSelected(media.id, media.selected!!, this) + PrefManager.setCustomVal( + "${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition + ) + model.saveSelected(media.id, media.selected!!) SubtitleDialogFragment().show(supportFragmentManager, "dialog") } @@ -1561,10 +1561,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL playerView.player?.pause() } if (exoPlayer.currentPosition > 5000) { - saveData( + PrefManager.setCustomVal( "${media.id}_${media.anime!!.selectedEpisode}", - exoPlayer.currentPosition, - this + exoPlayer.currentPosition ) } } @@ -1572,7 +1571,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL override fun onResume() { super.onResume() - LangSet.setLocale(this) orientationListener?.enable() hideSystemBars() if (isInitialized) { @@ -1590,7 +1588,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private var wasPlaying = false override fun onWindowFocusChanged(hasFocus: Boolean) { - if (settings.focusPause && !epChanging) { + if (PrefManager.getVal(PrefName.FocusPause) && !epChanging) { if (isInitialized && !hasFocus) wasPlaying = exoPlayer.isPlaying if (hasFocus) { if (isInitialized && wasPlaying) exoPlayer.play() @@ -1614,19 +1612,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL override fun onRenderedFirstFrame() { super.onRenderedFirstFrame() - saveData("${media.id}_${media.anime!!.selectedEpisode}_max", exoPlayer.duration, this) + PrefManager.setCustomVal( + "${media.id}_${media.anime!!.selectedEpisode}_max", + exoPlayer.duration + ) val height = (exoPlayer.videoFormat ?: return).height val width = (exoPlayer.videoFormat ?: return).width - if (video?.format != VideoType.CONTAINER) { - saveData("maxHeight", height) - saveData("maxWidth", width) - } - aspectRatio = Rational(width, height) - videoName.text = episode.selectedExtractor - videoInfo.text = "$width x $height" + videoInfo.text = "Quality: ${height}p" if (exoPlayer.duration < playbackPosition) exoPlayer.seekTo(0) @@ -1637,14 +1632,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL exoPlayer.seekTo(0) } - if (!isTimeStampsLoaded && settings.timeStampsEnabled) { + if (!isTimeStampsLoaded && PrefManager.getVal(PrefName.TimeStampsEnabled)) { val dur = exoPlayer.duration lifecycleScope.launch(Dispatchers.IO) { model.loadTimeStamps( media.idMAL, media.anime?.selectedEpisode?.trim()?.toIntOrNull(), dur / 1000, - settings.useProxyForTimeStamps + PrefManager.getVal(PrefName.UseProxyForTimeStamps) ) } } @@ -1654,7 +1649,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private var preloading = false private fun updateProgress() { if (isInitialized) { - if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) { + if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > PrefManager.getVal( + PrefName.WatchPercentage + ) + ) { preloading = true nextEpisode(false) { i -> val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode @@ -1685,7 +1683,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL val new = currentTimeStamp timeStampText.text = if (new != null) { - if (settings.showTimeStampButton) { + if (PrefManager.getVal(PrefName.ShowTimeStampButton)) { skipTimeButton.visibility = View.VISIBLE exoSkip.visibility = View.GONE skipTimeText.text = new.skipType.getType() @@ -1693,7 +1691,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL exoPlayer.seekTo((new.interval.endTime * 1000).toLong()) } } - if (settings.autoSkipOPED && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains( + if (PrefManager.getVal(PrefName.AutoSkipOPED) && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains( new ) ) { @@ -1703,7 +1701,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL new.skipType.getType() } else { skipTimeButton.visibility = View.GONE - if (settings.skipTime > 0) exoSkip.visibility = View.VISIBLE + if (PrefManager.getVal(PrefName.SkipTime) > 0) exoSkip.visibility = + View.VISIBLE "" } } @@ -1736,13 +1735,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } println("Track: ${tracks.groups.size}") - if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE - else { - exoQuality.visibility = View.VISIBLE - exoQuality.setOnClickListener { - initPopupQuality().show() - } - } } override fun onPlayerError(error: PlaybackException) { @@ -1757,7 +1749,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL else -> { toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}") - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) } } } @@ -1772,7 +1764,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } isBuffering = playbackState == Player.STATE_BUFFERING - if (playbackState == Player.STATE_ENDED && settings.autoPlay) { + if (playbackState == Player.STATE_ENDED && PrefManager.getVal(PrefName.AutoPlay)) { if (interacted) exoNext.performClick() else toast(getString(R.string.autoplay_cancelled)) } @@ -1780,8 +1772,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } private fun updateAniProgress() { - if (exoPlayer.currentPosition / episodeLength > settings.watchPercentage && Anilist.userid != null) - if (loadData("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true) { + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) + if (!incognito && exoPlayer.currentPosition / episodeLength > PrefManager.getVal( + PrefName.WatchPercentage + ) && Anilist.userid != null + ) + if (PrefManager.getCustomVal( + "${media.id}_save_progress", + true + ) && (if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHPlayer) else true) + ) { media.anime!!.selectedEpisode?.apply { updateProgress(media, this) } @@ -1794,7 +1794,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL while (isFiller) { if (episodeArr.size > currentEpisodeIndex + i) { isFiller = - if (settings.autoSkipFiller) episodes[episodeArr[currentEpisodeIndex + i]]?.filler + if (PrefManager.getVal(PrefName.AutoSkipFiller)) episodes[episodeArr[currentEpisodeIndex + i]]?.filler ?: false else false if (!isFiller) runnable.invoke(i) i++ @@ -1830,20 +1830,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL finishAndRemoveTask() } - // QUALITY SELECTOR - private fun initPopupQuality(): Dialog { - - val trackSelectionDialogBuilder = - TrackSelectionDialogBuilder(this, "Available Qualities", exoPlayer, TRACK_TYPE_VIDEO) - trackSelectionDialogBuilder.setTheme(R.style.DialogTheme) - trackSelectionDialogBuilder.setTrackNameProvider { - if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)" - } - val trackDialog = trackSelectionDialogBuilder.build() - trackDialog.setOnDismissListener { hideSystemBars() } - return trackDialog - } - // Cast private fun cast() { val videoURL = video?.file?.url ?: return @@ -1905,7 +1891,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL orientationListener?.enable() } if (isInitialized) { - saveData("${media.id}_${episode.number}", exoPlayer.currentPosition, this) + PrefManager.setCustomVal( + "${media.id}_${episode.number}", + exoPlayer.currentPosition + ) if (wasPlaying) exoPlayer.play() } } @@ -1988,7 +1977,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } override fun onCastSessionAvailable() { - if (isCastApiAvailable) { + if (isCastApiAvailable && !this.isDestroyed) { startCastPlayer() } } diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt index 688222c585..a00253eae0 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -8,7 +8,6 @@ import android.graphics.Color import android.net.Uri import android.os.Bundle import android.util.TypedValue -import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -18,6 +17,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.BottomSheetSelectorBinding import ani.dantotsu.databinding.ItemStreamBinding import ani.dantotsu.databinding.ItemUrlBinding @@ -28,11 +28,14 @@ import ani.dantotsu.others.Download.download import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.VideoExtractor import ani.dantotsu.parsers.VideoType -import com.google.firebase.crashlytics.FirebaseCrashlytics +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.DecimalFormat @@ -93,7 +96,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { binding.selectorAutoText.text = selected binding.selectorCancel.setOnClickListener { media!!.selected!!.server = null - model.saveSelected(media!!.id, media!!.selected!!, requireActivity()) + model.saveSelected(media!!.id, media!!.selected!!) tryWith { dismiss() } @@ -142,11 +145,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { } binding.selectorRecyclerView.adapter = null binding.selectorProgressBar.visibility = View.VISIBLE - makeDefault = loadData("make_default") ?: true + makeDefault = PrefManager.getVal(PrefName.MakeDefault) binding.selectorMakeDefault.isChecked = makeDefault binding.selectorMakeDefault.setOnClickListener { makeDefault = binding.selectorMakeDefault.isChecked - saveData("make_default", makeDefault) + PrefManager.setVal(PrefName.MakeDefault, makeDefault) } binding.selectorRecyclerView.layoutManager = LinearLayoutManager( @@ -265,7 +268,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = 0 startExoplayer(media!!) } catch (e: Exception) { - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) } } @@ -300,96 +303,88 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { extractor.server.name media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo = position - binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!! - val selectedVideo = - if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null - - val subtitles = extractor.subtitles - val subtitleNames = subtitles.map { it.language } - var subtitleToDownload: Subtitle? = null - if (subtitles.isNotEmpty()) { - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle("Download Subtitle") - .setSingleChoiceItems( - subtitleNames.toTypedArray(), - -1 - ) { dialog, which -> - subtitleToDownload = subtitles[which] - } - .setPositiveButton("Download") { _, _ -> - dialog?.dismiss() - if (selectedVideo != null) { - Helper.startAnimeDownloadService( - currActivity()!!, - media!!.mainName(), - episode.number, - selectedVideo, - subtitleToDownload, - media, - episode.thumb?.url ?: media!!.banner ?: media!!.cover - ) - } else { - snackString("No Video Selected") - } - } - .setNegativeButton("Skip") { dialog, _ -> - subtitleToDownload = null - if (selectedVideo != null) { - Helper.startAnimeDownloadService( - currActivity()!!, - media!!.mainName(), - episode.number, - selectedVideo, - subtitleToDownload, - media, - episode.thumb?.url ?: media!!.banner ?: media!!.cover - ) - } else { - snackString("No Video Selected") - } - dialog.dismiss() - } - .setNeutralButton("Cancel") { dialog, _ -> - subtitleToDownload = null - dialog.dismiss() - } - .show() - alertDialog.window?.setDimAmount(0.8f) - - } else { - if (selectedVideo != null) { - Helper.startAnimeDownloadService( - requireActivity(), - media!!.mainName(), - episode.number, - selectedVideo, - subtitleToDownload, - media, - episode.thumb?.url ?: media!!.banner ?: media!!.cover - ) - } else { - snackString("No Video Selected") - } - } - dismiss() - } - binding.urlDownload.setOnLongClickListener { - binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - if ((loadData("settings_download_manager") ?: 0) != 0) { - media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor = - extractor.server.name - media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo = - position + if ((PrefManager.getVal(PrefName.DownloadManager) as Int) != 0) { download( requireActivity(), media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!, media!!.userPreferredName ) } else { - snackString("No Download Manager Selected") + val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!! + val selectedVideo = + if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null + val subtitles = extractor.subtitles + val subtitleNames = subtitles.map { it.language } + var subtitleToDownload: Subtitle? = null + if (subtitles.isNotEmpty()) { + val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) + .setTitle("Download Subtitle") + .setSingleChoiceItems( + subtitleNames.toTypedArray(), + -1 + ) { dialog, which -> + subtitleToDownload = subtitles[which] + } + .setPositiveButton("Download") { _, _ -> + dialog?.dismiss() + if (selectedVideo != null) { + Helper.startAnimeDownloadService( + currActivity()!!, + media!!.mainName(), + episode.number, + selectedVideo, + subtitleToDownload, + media, + episode.thumb?.url ?: media!!.banner ?: media!!.cover + ) + broadcastDownloadStarted(episode.number) + } else { + snackString("No Video Selected") + } + } + .setNegativeButton("Skip") { dialog, _ -> + subtitleToDownload = null + if (selectedVideo != null) { + Helper.startAnimeDownloadService( + currActivity()!!, + media!!.mainName(), + episode.number, + selectedVideo, + subtitleToDownload, + media, + episode.thumb?.url ?: media!!.banner ?: media!!.cover + ) + broadcastDownloadStarted(episode.number) + } else { + snackString("No Video Selected") + } + dialog.dismiss() + } + .setNeutralButton("Cancel") { dialog, _ -> + subtitleToDownload = null + dialog.dismiss() + } + .show() + alertDialog.window?.setDimAmount(0.8f) + + } else { + if (selectedVideo != null) { + Helper.startAnimeDownloadService( + requireActivity(), + media!!.mainName(), + episode.number, + selectedVideo, + subtitleToDownload, + media, + episode.thumb?.url ?: media!!.banner ?: media!!.cover + ) + broadcastDownloadStarted(episode.number) + } else { + snackString("No Video Selected") + } + } } - true + dismiss() } if (video.format == VideoType.CONTAINER) { binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE @@ -404,6 +399,13 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { binding.urlQuality.text = extractor.server.name } + private fun broadcastDownloadStarted(episodeNumber: String) { + val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply { + putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) + } + requireActivity().sendBroadcast(intent) + } + override fun getItemCount(): Int = extractor.videos.size private inner class UrlViewHolder(val binding: ItemUrlBinding) : @@ -422,7 +424,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { if (makeDefault) { media!!.selected!!.server = extractor.server.name media!!.selected!!.video = bindingAdapterPosition - model.saveSelected(media!!.id, media!!.selected!!, requireActivity()) + model.saveSelected(media!!.id, media!!.selected!!) } startExoplayer(media!!) } diff --git a/app/src/main/java/ani/dantotsu/media/anime/SubtitleDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SubtitleDialogFragment.kt index 6cc971574f..3fae8c7981 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SubtitleDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SubtitleDialogFragment.kt @@ -15,10 +15,9 @@ import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetSubtitlesBinding import ani.dantotsu.databinding.ItemSubtitleTextBinding -import ani.dantotsu.loadData import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.parsers.Subtitle -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager class SubtitleDialogFragment : BottomSheetDialogFragment() { private var _binding: BottomSheetSubtitlesBinding? = null @@ -69,7 +68,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() { binding.subtitleTitle.setText(R.string.none) model.getMedia().observe(viewLifecycleOwner) { media -> val mediaID: Int = media.id - val selSubs: String? = loadData("subLang_${mediaID}", activity) + val selSubs = PrefManager.getCustomVal("subLang_${mediaID}", null) if (episode.selectedSubtitle != null && selSubs != "None") { binding.root.setCardBackgroundColor(TRANSPARENT) } @@ -79,7 +78,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() { model.setEpisode(episode, "Subtitle") model.getMedia().observe(viewLifecycleOwner) { media -> val mediaID: Int = media.id - saveData("subLang_${mediaID}", "None", activity) + PrefManager.setCustomVal("subLang_${mediaID}", "None") } dismiss() } @@ -108,7 +107,8 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() { } model.getMedia().observe(viewLifecycleOwner) { media -> val mediaID: Int = media.id - val selSubs: String? = loadData("subLang_${mediaID}", activity) + val selSubs: String? = + PrefManager.getCustomVal("subLang_${mediaID}", null) if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) { binding.root.setCardBackgroundColor(TRANSPARENT) } @@ -119,7 +119,10 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() { model.setEpisode(episode, "Subtitle") model.getMedia().observe(viewLifecycleOwner) { media -> val mediaID: Int = media.id - saveData("subLang_${mediaID}", subtitles[position - 1].language, activity) + PrefManager.setCustomVal( + "subLang_${mediaID}", + subtitles[position - 1].language + ) } dismiss() } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt index 6d0b36779b..5467676512 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt @@ -166,7 +166,7 @@ class MangaChapterAdapter( }, 1000) } else { // Show download icon - binding.itemDownload.setImageResource(R.drawable.ic_circle_add) + binding.itemDownload.setImageResource(R.drawable.ic_download_24) binding.itemDownload.rotation = 0f } @@ -261,7 +261,7 @@ class MangaChapterAdapter( when (holder) { is ChapterCompactViewHolder -> { val binding = holder.binding - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) val ep = arr[position] val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt() binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: ep.number @@ -287,7 +287,7 @@ class MangaChapterAdapter( val binding = holder.binding val ep = arr[position] holder.bind(ep.number, ep.progress) - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) binding.itemChapterNumber.text = ep.number if (ep.progress.isNullOrEmpty()) { binding.itemChapterTitle.visibility = View.GONE diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index da0866b26c..e990f94e1f 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media.manga import android.annotation.SuppressLint import android.app.AlertDialog -import android.content.Context import android.content.Intent import android.view.LayoutInflater import android.view.View @@ -28,6 +27,8 @@ import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.MangaReadSources import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import com.google.android.material.chip.Chip @@ -69,12 +70,9 @@ class MangaReadAdapter( null ) } - val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ) - ?.getBoolean("offlineMode", false) == true - ) View.GONE else View.VISIBLE + val offline = + if (!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) + ) View.GONE else View.VISIBLE binding.animeSourceNameContainer.visibility = offline binding.animeSourceSettings.visibility = offline @@ -163,7 +161,8 @@ class MangaReadAdapter( var refresh = false var run = false var reversed = media.selected!!.recyclerReversed - var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.mangaDefaultView + var style = + media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView) dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" dialogBinding.animeSourceTop.setOnClickListener { @@ -392,7 +391,8 @@ class MangaReadAdapter( if (media.manga?.chapters != null) { val chapters = media.manga.chapters!!.keys.toTypedArray() val anilistEp = (media.userProgress ?: 0).plus(1) - val appEp = loadData("${media.id}_current_chp")?.toIntOrNull() ?: 1 + val appEp = PrefManager.getCustomVal("${media.id}_current_chp", null) + ?.toIntOrNull() ?: 1 var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() val filteredChapters = chapters.filter { chapterKey -> val chapter = media.manga.chapters!![chapterKey]!! diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 3153bb6a72..e21d7fc799 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -26,6 +26,7 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.FragmentAnimeWatchBinding @@ -42,8 +43,9 @@ import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaParser import ani.dantotsu.parsers.MangaSources -import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId @@ -86,9 +88,6 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { var continueEp: Boolean = false var loaded = false - val uiSettings = loadData("ui_settings", toast = false) - ?: UserInterfaceSettings().apply { saveData("ui_settings", this) } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -139,6 +138,23 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.ScrollTop.setOnClickListener { + binding.animeSourceRecycler.scrollToPosition(10) + binding.animeSourceRecycler.smoothScrollToPosition(0) + } + binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val position = gridLayoutManager.findFirstVisibleItemPosition() + if (position > 2) { + binding.ScrollTop.translationY = -navBarHeight.toFloat() + binding.ScrollTop.visibility = View.VISIBLE + } else { + binding.ScrollTop.visibility = View.GONE + } + } + }) model.scrolledToTop.observe(viewLifecycleOwner) { if (it) binding.animeSourceRecycler.scrollToPosition(0) } @@ -154,7 +170,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { media.selected = model.loadSelected(media) subscribed = - SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id) + SubscriptionHelper.getSubscriptions().containsKey(media.id) style = media.selected!!.recyclerStyle reverse = media.selected!!.recyclerReversed @@ -165,10 +181,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!) headerAdapter.scanlatorSelectionListener = this chapterAdapter = - MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this) + MangaChapterAdapter( + style ?: PrefManager.getVal(PrefName.MangaDefaultView), media, this + ) for (download in downloadManager.mangaDownloadedTypes) { - chapterAdapter.stopDownload(download.chapter) + if (download.title == media.mainName()) { + chapterAdapter.stopDownload(download.chapter) + } } binding.animeSourceRecycler.adapter = @@ -284,7 +304,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { model.mangaReadSources?.get(selected.sourceIndex)?.showUserTextListener = null selected.sourceIndex = i selected.server = null - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected return model.mangaReadSources?.get(i)!! } @@ -292,14 +312,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { fun onLangChange(i: Int) { val selected = model.loadSelected(media) selected.langIndex = i - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected } fun onScanlatorChange(list: List) { val selected = model.loadSelected(media) selected.scanlators = list - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected } @@ -312,7 +332,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { reverse = rev media.selected!!.recyclerStyle = style media.selected!!.recyclerReversed = reverse - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) reload() } @@ -320,7 +340,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { media.selected!!.chip = i start = s end = e - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) reload() } @@ -368,12 +388,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray() - var selectedIndex = 0 val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> - selectedIndex = which - selectedSetting = allSettings[selectedIndex] + .setSingleChoiceItems(names, -1) { dialog, which -> + selectedSetting = allSettings[which] itemSelected = true dialog.dismiss() @@ -420,7 +438,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { model.continueMedia = false media.manga?.chapters?.get(i)?.let { media.manga?.selectedChapter = i - model.saveSelected(media.id, media.selected!!, requireActivity()) + model.saveSelected(media.id, media.selected!!) ChapterLoaderDialog.newInstance(it, true) .show(requireActivity().supportFragmentManager, "dialog") } @@ -558,7 +576,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { selected.latest = media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) headerAdapter.handleChapters() chapterAdapter.notifyItemRangeRemoved(0, chapterAdapter.arr.size) var arr: ArrayList = arrayListOf() @@ -572,7 +590,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { arr = (arr.reversed() as? ArrayList) ?: arr } chapterAdapter.arr = arr - chapterAdapter.updateType(style ?: uiSettings.mangaDefaultView) + chapterAdapter.updateType(style ?: PrefManager.getVal(PrefName.MangaDefaultView)) chapterAdapter.notifyItemRangeInserted(0, arr.size) } diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt index 952db6195c..c313d71dfe 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt @@ -33,8 +33,7 @@ abstract class BaseImageAdapter( val activity: MangaReaderActivity, chapter: MangaChapter ) : RecyclerView.Adapter() { - val settings = activity.settings.default - val uiSettings = activity.uiSettings + val settings = activity.defaultSettings val images = chapter.images() @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt index 58b48038a8..92beb46926 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt @@ -14,6 +14,8 @@ import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView @@ -83,7 +85,7 @@ open class ImageAdapter( imageView.minScale = scale ObjectAnimator.ofFloat(parent, "alpha", 0f, 1f) - .setDuration((400 * uiSettings.animationSpeed).toLong()) + .setDuration((400 * PrefManager.getVal(PrefName.AnimationSpeed)).toLong()) .start() progress.visibility = View.GONE diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt index c396088de2..8e59713e89 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt @@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.discord.DiscordService import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton @@ -41,27 +42,29 @@ import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaNameAdapter import ani.dantotsu.others.ImageViewDialog -import ani.dantotsu.others.LangSet import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaImage import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.settings.CurrentReaderSettings import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon import ani.dantotsu.settings.CurrentReaderSettings.Directions.* import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.* import ani.dantotsu.settings.CurrentReaderSettings.Layouts.* -import ani.dantotsu.settings.ReaderSettings -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.themes.ThemeManager import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream import java.util.* import kotlin.math.min import kotlin.properties.Delegates @@ -74,6 +77,8 @@ class MangaReaderActivity : AppCompatActivity() { private val model: MediaDetailsViewModel by viewModels() private val scope = lifecycleScope + var defaultSettings = CurrentReaderSettings() + private lateinit var media: Media private lateinit var chapter: MangaChapter private lateinit var chapters: MutableMap @@ -84,13 +89,9 @@ class MangaReaderActivity : AppCompatActivity() { private var isContVisible = false private var showProgressDialog = true - //private var progressDialog: AlertDialog.Builder? = null private var maxChapterPage = 0L private var currentChapterPage = 0L - lateinit var settings: ReaderSettings - lateinit var uiSettings: UserInterfaceSettings - private var notchHeight: Int? = null private var imageAdapter: BaseImageAdapter? = null @@ -98,10 +99,8 @@ class MangaReaderActivity : AppCompatActivity() { var sliding = false var isAnimating = false - private var rpc: RPC? = null - override fun onAttachedToWindow() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !settings.showSystemBars) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal(PrefName.ShowSystemBars)) { val displayCutout = window.decorView.rootWindowInsets.displayCutout if (displayCutout != null) { if (displayCutout.boundingRects.size > 0) { @@ -123,7 +122,7 @@ class MangaReaderActivity : AppCompatActivity() { } private fun hideBars() { - if (!settings.showSystemBars) hideSystemBars() + if (PrefManager.getVal(PrefName.ShowSystemBars)) hideSystemBars() } override fun onDestroy() { @@ -138,28 +137,20 @@ class MangaReaderActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) ThemeManager(this).applyTheme() binding = ActivityMangaReaderBinding.inflate(layoutInflater) setContentView(binding.root) - binding.mangaReaderBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } + defaultSettings = loadReaderSettings("reader_settings") ?: defaultSettings + onBackPressedDispatcher.addCallback(this) { progress { finish() } } - settings = loadData("reader_settings", this) - ?: ReaderSettings().apply { saveData("reader_settings", this) } - uiSettings = loadData("ui_settings", this) ?: UserInterfaceSettings().apply { - saveData( - "ui_settings", - this - ) - } - controllerDuration = (uiSettings.animationSpeed * 200).toLong() + controllerDuration = (PrefManager.getVal(PrefName.AnimationSpeed) * 200).toLong() hideBars() @@ -182,7 +173,7 @@ class MangaReaderActivity : AppCompatActivity() { binding.mangaReaderSlider.addOnChangeListener { _, value, fromUser -> if (fromUser) { sliding = true - if (settings.default.layout != PAGED) + if (defaultSettings.layout != PAGED) binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 } ?: 1)) else @@ -205,34 +196,30 @@ class MangaReaderActivity : AppCompatActivity() { else model.getMedia().value ?: return model.setMedia(media) - if (settings.autoDetectWebtoon && media.countryOfOrigin != "JP") applyWebtoon(settings.default) - settings.default = loadData("${media.id}_current_settings") ?: settings.default + if (PrefManager.getVal(PrefName.AutoDetectWebtoon) && media.countryOfOrigin != "JP") applyWebtoon( + defaultSettings + ) + defaultSettings = loadReaderSettings("${media.id}_current_settings") ?: defaultSettings chapters = media.manga?.chapters ?: return chapter = chapters[media.manga!!.selectedChapter] ?: return model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources - binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE + binding.mangaReaderSource.visibility = + if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE if (model.mangaReadSources!!.names.isEmpty()) { //try to reload sources try { - if (media.isAdult) { - val mangaSources = MangaSources - val scope = lifecycleScope - scope.launch(Dispatchers.IO) { - mangaSources.init(Injekt.get().installedExtensionsFlow, this@MangaReaderActivity) - } - model.mangaReadSources = mangaSources - } else { - val mangaSources = HMangaSources - val scope = lifecycleScope - scope.launch(Dispatchers.IO) { - mangaSources.init(Injekt.get().installedExtensionsFlow) - } - model.mangaReadSources = mangaSources + val mangaSources = MangaSources + val scope = lifecycleScope + scope.launch(Dispatchers.IO) { + mangaSources.init( + Injekt.get().installedExtensionsFlow + ) } + model.mangaReadSources = mangaSources } catch (e: Exception) { - Firebase.crashlytics.recordException(e) + Injekt.get().logException(e) logError(e) } } @@ -255,13 +242,18 @@ class MangaReaderActivity : AppCompatActivity() { } showProgressDialog = - if (settings.askIndividual) loadData("${media.id}_progressDialog") - ?: true else false + if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal( + "${media.id}_progressDialog", + true + ) else false //Chapter Change fun change(index: Int) { mangaCache.clear() - saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this) + PrefManager.setCustomVal( + "${media.id}_${chaptersArr[currentChapterIndex]}", + currentChapterPage + ) ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!) .show(supportFragmentManager, "dialog") } @@ -310,7 +302,7 @@ class MangaReaderActivity : AppCompatActivity() { chapter = chap media.manga!!.selectedChapter = chapter.number media.selected = model.loadSelected(media) - saveData("${media.id}_current_chp", chap.number, this) + PrefManager.setCustomVal("${media.id}_current_chp", chap.number) currentChapterIndex = chaptersArr.indexOf(chap.number) binding.mangaReaderChapterSelect.setSelection(currentChapterIndex) binding.mangaReaderNextChap.text = @@ -319,9 +311,9 @@ class MangaReaderActivity : AppCompatActivity() { chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: "" applySettings() val context = this - val incognito = context.getSharedPreferences("Dantotsu", 0) - ?.getBoolean("incognito", false) ?: false - if (isOnline(context) && Discord.token != null && !incognito) { + val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) + if ((isOnline(context) && !offline) && Discord.token != null && !incognito) { lifecycleScope.launch { val presence = RPC.createPresence( RPC.Companion.RPCData( @@ -369,7 +361,7 @@ class MangaReaderActivity : AppCompatActivity() { private val snapHelper = PagerSnapHelper() fun dualPage(callback: () -> T): T? { - return when (settings.default.dualPageMode) { + return when (defaultSettings.dualPageMode) { No -> null Automatic -> { val orientation = resources.configuration.orientation @@ -384,29 +376,29 @@ class MangaReaderActivity : AppCompatActivity() { @SuppressLint("ClickableViewAccessibility") fun applySettings() { - saveData("${media.id}_current_settings", settings.default) + saveReaderSettings("${media.id}_current_settings", defaultSettings) hideBars() //true colors SubsamplingScaleImageView.setPreferredBitmapConfig( - if (settings.default.trueColors) Bitmap.Config.ARGB_8888 + if (defaultSettings.trueColors) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 ) //keep screen On - if (settings.default.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + if (defaultSettings.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) binding.mangaReaderPager.unregisterOnPageChangeCallback(pageChangeCallback) - currentChapterPage = loadData("${media.id}_${chapter.number}", this) ?: 1 + currentChapterPage = PrefManager.getCustomVal("${media.id}_${chapter.number}", 1L) val chapImages = chapter.images() maxChapterPage = 0 if (chapImages.isNotEmpty()) { maxChapterPage = chapImages.size.toLong() - saveData("${media.id}_${chapter.number}_max", maxChapterPage) + PrefManager.setCustomVal("${media.id}_${chapter.number}_max", maxChapterPage) imageAdapter = dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter) @@ -421,15 +413,15 @@ class MangaReaderActivity : AppCompatActivity() { binding.mangaReaderSlider.visibility = View.GONE } binding.mangaReaderPageNumber.text = - if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage" + if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage" } val currentPage = currentChapterPage.toInt() - if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) { + if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) { binding.mangaReaderSwipy.vertical = true - if (settings.default.direction == TOP_TO_BOTTOM) { + if (defaultSettings.direction == TOP_TO_BOTTOM) { binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter) binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) @@ -466,7 +458,7 @@ class MangaReaderActivity : AppCompatActivity() { } } else { binding.mangaReaderSwipy.vertical = false - if (settings.default.direction == RIGHT_TO_LEFT) { + if (defaultSettings.direction == RIGHT_TO_LEFT) { binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter) binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) @@ -503,11 +495,11 @@ class MangaReaderActivity : AppCompatActivity() { } } - if (settings.default.layout != PAGED) { + if (defaultSettings.layout != PAGED) { binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled = - settings.default.rotation + defaultSettings.rotation val detector = GestureDetectorCompat(this, object : GesturesListener() { override fun onLongPress(e: MotionEvent) { @@ -530,7 +522,7 @@ class MangaReaderActivity : AppCompatActivity() { val page = chapter.dualPages().getOrNull(pos) ?: return@dualPage false val nextPage = page.second - if (settings.default.direction != LEFT_TO_RIGHT && nextPage != null) + if (defaultSettings.direction != LEFT_TO_RIGHT && nextPage != null) onImageLongClicked(pos * 2, nextPage, page.first, callback) else onImageLongClicked(pos * 2, page.first, nextPage, callback) @@ -552,11 +544,11 @@ class MangaReaderActivity : AppCompatActivity() { val manager = PreloadLinearLayoutManager( this, - if (settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP) + if (defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP) RecyclerView.VERTICAL else RecyclerView.HORIZONTAL, - !(settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == LEFT_TO_RIGHT) + !(defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == LEFT_TO_RIGHT) ) manager.preloadItemCount = 5 @@ -575,7 +567,7 @@ class MangaReaderActivity : AppCompatActivity() { addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) { - settings.default.apply { + defaultSettings.apply { if ( ((direction == TOP_TO_BOTTOM || direction == BOTTOM_TO_TOP) && (!v.canScrollVertically(-1) || !v.canScrollVertically(1))) @@ -594,25 +586,25 @@ class MangaReaderActivity : AppCompatActivity() { super.onScrolled(v, dx, dy) } }) - if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) + if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) updatePadding(0, 128f.px, 0, 128f.px) else updatePadding(128f.px, 0, 128f.px, 0) snapHelper.attachToRecyclerView( - if (settings.default.layout == CONTINUOUS_PAGED) this + if (defaultSettings.layout == CONTINUOUS_PAGED) this else null ) onVolumeUp = { - if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) + if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) smoothScrollBy(0, -500) else smoothScrollBy(-500, 0) } onVolumeDown = { - if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) + if ((defaultSettings.direction == TOP_TO_BOTTOM || defaultSettings.direction == BOTTOM_TO_TOP)) smoothScrollBy(0, 500) else smoothScrollBy(500, 0) @@ -627,11 +619,11 @@ class MangaReaderActivity : AppCompatActivity() { visibility = View.VISIBLE adapter = imageAdapter layoutDirection = - if (settings.default.direction == BOTTOM_TO_TOP || settings.default.direction == RIGHT_TO_LEFT) + if (defaultSettings.direction == BOTTOM_TO_TOP || defaultSettings.direction == RIGHT_TO_LEFT) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR orientation = - if (settings.default.direction == LEFT_TO_RIGHT || settings.default.direction == RIGHT_TO_LEFT) + if (defaultSettings.direction == LEFT_TO_RIGHT || defaultSettings.direction == RIGHT_TO_LEFT) ViewPager2.ORIENTATION_HORIZONTAL else ViewPager2.ORIENTATION_VERTICAL registerOnPageChangeCallback(pageChangeCallback) @@ -654,7 +646,7 @@ class MangaReaderActivity : AppCompatActivity() { return when (event.keyCode) { KEYCODE_VOLUME_UP, KEYCODE_DPAD_UP, KEYCODE_PAGE_UP -> { if (event.keyCode == KEYCODE_VOLUME_UP) - if (!settings.default.volumeButtons) + if (!defaultSettings.volumeButtons) return false if (event.action == ACTION_DOWN) { onVolumeUp?.invoke() @@ -664,7 +656,7 @@ class MangaReaderActivity : AppCompatActivity() { KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> { if (event.keyCode == KEYCODE_VOLUME_DOWN) - if (!settings.default.volumeButtons) + if (!defaultSettings.volumeButtons) return false if (event.action == ACTION_DOWN) { onVolumeDown?.invoke() @@ -711,14 +703,14 @@ class MangaReaderActivity : AppCompatActivity() { fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) { var pressLocation = pressPos.CENTER if (!sliding) { - if (event != null && settings.default.layout == PAGED) { + if (event != null && defaultSettings.layout == PAGED) { if (event.action != MotionEvent.ACTION_UP) return val x = event.rawX.toInt() val y = event.rawY.toInt() val screenWidth = Resources.getSystem().displayMetrics.widthPixels //if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left - if (screenWidth / 5 in (x + 1).. screenWidth - screenWidth / 5 && y > screenWidth / 5) { - pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) { + pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT) { pressPos.LEFT } else { pressPos.RIGHT @@ -758,12 +750,12 @@ class MangaReaderActivity : AppCompatActivity() { } } - if (!settings.showSystemBars) { + if (!PrefManager.getVal(PrefName.ShowSystemBars)) { hideBars() checkNotch() } //horizontal scrollbar - if (settings.default.horizontalScrollBar) { + if (defaultSettings.horizontalScrollBar) { binding.mangaReaderSliderContainer.updateLayoutParams { height = ViewGroup.LayoutParams.WRAP_CONTENT width = ViewGroup.LayoutParams.WRAP_CONTENT @@ -790,7 +782,7 @@ class MangaReaderActivity : AppCompatActivity() { } } binding.mangaReaderSlider.layoutDirection = - if (settings.default.direction == RIGHT_TO_LEFT || settings.default.direction == BOTTOM_TO_TOP) + if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR shouldShow?.apply { isContVisible = !this } @@ -828,9 +820,9 @@ class MangaReaderActivity : AppCompatActivity() { fun updatePageNumber(page: Long) { if (currentChapterPage != page) { currentChapterPage = page - saveData("${media.id}_${chapter.number}", page, this) + PrefManager.setCustomVal("${media.id}_${chapter.number}", page) binding.mangaReaderPageNumber.text = - if (settings.default.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage" + if (defaultSettings.hidePageNumbers) "" else "${currentChapterPage}/$maxChapterPage" if (!sliding) binding.mangaReaderSlider.apply { value = clamp(currentChapterPage.toFloat(), 1f, valueTo) } @@ -851,31 +843,27 @@ class MangaReaderActivity : AppCompatActivity() { private fun progress(runnable: Runnable) { if (maxChapterPage - currentChapterPage <= 1 && Anilist.userid != null) { showProgressDialog = - if (settings.askIndividual) loadData("${media.id}_progressDialog") - ?: true else false - if (showProgressDialog) { + if (PrefManager.getVal(PrefName.AskIndividualReader)) PrefManager.getCustomVal( + "${media.id}_progressDialog", + true + ) + else false + val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) + if (showProgressDialog && !incognito) { val dialogView = layoutInflater.inflate(R.layout.item_custom_dialog, null) val checkbox = dialogView.findViewById(R.id.dialog_checkbox) checkbox.text = getString(R.string.dont_ask_again, media.userPreferredName) checkbox.setOnCheckedChangeListener { _, isChecked -> - saveData("${media.id}_progressDialog", !isChecked) + PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked) showProgressDialog = !isChecked } - val incognito = - currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("incognito", false) ?: false AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.title_update_progress)) - .apply { - if (incognito) { - setMessage(getString(R.string.incognito_will_not_update)) - } - } .setView(dialogView) .setCancelable(false) .setPositiveButton(getString(R.string.yes)) { dialog, _ -> - saveData("${media.id}_save_progress", true) + PrefManager.setCustomVal("${media.id}_save_progress", true) updateProgress( media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!) @@ -885,7 +873,7 @@ class MangaReaderActivity : AppCompatActivity() { runnable.run() } .setNegativeButton(getString(R.string.no)) { dialog, _ -> - saveData("${media.id}_save_progress", false) + PrefManager.setCustomVal("${media.id}_save_progress", false) dialog.dismiss() runnable.run() } @@ -893,7 +881,11 @@ class MangaReaderActivity : AppCompatActivity() { .create() .show() } else { - if (loadData("${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(PrefName.UpdateForHReader) else true + ) updateProgress( media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!) @@ -906,6 +898,51 @@ class MangaReaderActivity : AppCompatActivity() { } } + + @Suppress("UNCHECKED_CAST") + private fun 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().log("Failed to delete file $fileName") + Injekt.get().logException(e) + } + e.printStackTrace() + } + return null + } + + private fun saveReaderSettings(fileName: String, data: Any?, context: Context? = null) { + tryWith { + val a = context ?: currContext() + if (a != null) { + val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE) + val os = ObjectOutputStream(fos) + os.writeObject(data) + os.close() + fos.close() + } + } + } + fun getTransformation(mangaImage: MangaImage): BitmapTransformation? { return model.loadTransformation(mangaImage, media.selected!!.sourceIndex) } @@ -916,7 +953,7 @@ class MangaReaderActivity : AppCompatActivity() { img2: MangaImage?, callback: ((ImageViewDialog) -> Unit)? = null ): Boolean { - if (!settings.default.longClickImage) return false + if (!defaultSettings.longClickImage) return false val title = "(Page ${pos + 1}${if (img2 != null) "-${pos + 2}" else ""}) ${ chaptersTitleArr.getOrNull(currentChapterIndex)?.replace(" : ", " - ") ?: "" } [${media.userPreferredName}]" @@ -930,8 +967,8 @@ class MangaReaderActivity : AppCompatActivity() { val parserTransformation2 = getTransformation(img2) if (parserTransformation2 != null) transforms2.add(parserTransformation2) } - val threshold = settings.default.cropBorderThreshold - if (settings.default.cropBorders) { + val threshold = defaultSettings.cropBorderThreshold + if (defaultSettings.cropBorders) { transforms1.add(RemoveBordersTransformation(true, threshold)) transforms1.add(RemoveBordersTransformation(false, threshold)) if (img2 != null) { diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ReaderSettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ReaderSettingsDialogFragment.kt index fc399e19ce..c9c42aab02 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ReaderSettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ReaderSettingsDialogFragment.kt @@ -26,7 +26,7 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val activity = requireActivity() as MangaReaderActivity - val settings = activity.settings.default + val settings = activity.defaultSettings binding.readerDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal] diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt index 0c654c16c4..369f446e7d 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt @@ -26,14 +26,11 @@ import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.novel.NovelDownloaderService import ani.dantotsu.download.novel.NovelServiceDataSingleton -import ani.dantotsu.loadData import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.novel.novelreader.NovelReaderActivity import ani.dantotsu.navBarHeight import ani.dantotsu.parsers.ShowResponse -import ani.dantotsu.saveData -import ani.dantotsu.settings.UserInterfaceSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -61,9 +58,6 @@ class NovelReadFragment : Fragment(), private var continueEp: Boolean = false var loaded = false - val uiSettings = loadData("ui_settings", toast = false) - ?: UserInterfaceSettings().apply { saveData("ui_settings", this) } - override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) { Log.e("downloadTrigger", novelDownloadPackage.link) val downloadTask = NovelDownloaderService.DownloadTask( @@ -253,7 +247,7 @@ class NovelReadFragment : Fragment(), if (save) { val selected = model.loadSelected(media) selected.server = query - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) } } } @@ -263,7 +257,7 @@ class NovelReadFragment : Fragment(), selected.sourceIndex = i source = i selected.server = null - model.saveSelected(media.id, selected, requireActivity()) + model.saveSelected(media.id, selected) media.selected = selected } diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt index 6d57037456..0ab80e14cb 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -35,7 +35,7 @@ class NovelResponseAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding val novel = list[position] - setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) + setAnimation(fragment.requireContext(), holder.binding.root) val cover = GlideUrl(novel.coverUrl.url) { novel.coverUrl.headers } Glide.with(binding.itemEpisodeImage).load(cover).override(400, 0) @@ -104,7 +104,7 @@ class NovelResponseAdapter( binding.root.setOnLongClickListener { val builder = androidx.appcompat.app.AlertDialog.Builder( fragment.requireContext(), - R.style.DialogTheme + R.style.MyPopup ) builder.setTitle("Delete ${novel.name}?") builder.setMessage("Are you sure you want to delete ${novel.name}?") diff --git a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt index b62032fae1..b77cdc7783 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt @@ -2,6 +2,7 @@ package ani.dantotsu.media.novel.novelreader import android.animation.ObjectAnimator import android.annotation.SuppressLint +import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.graphics.Color @@ -28,17 +29,16 @@ import androidx.webkit.WebViewCompat import ani.dantotsu.GesturesListener import ani.dantotsu.NoPaddingArrayAdapter import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface +import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivityNovelReaderBinding import ani.dantotsu.hideSystemBars -import ani.dantotsu.loadData import ani.dantotsu.others.ImageViewDialog -import ani.dantotsu.others.LangSet -import ani.dantotsu.saveData import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.CurrentNovelReaderSettings import ani.dantotsu.settings.CurrentReaderSettings -import ani.dantotsu.settings.ReaderSettings -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.themes.ThemeManager import ani.dantotsu.tryWith @@ -52,8 +52,13 @@ import com.vipulog.ebookreader.RelocationInfo import com.vipulog.ebookreader.TocItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream import java.util.* import kotlin.math.min import kotlin.properties.Delegates @@ -63,9 +68,6 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { private lateinit var binding: ActivityNovelReaderBinding private val scope = lifecycleScope - lateinit var settings: ReaderSettings - private lateinit var uiSettings: UserInterfaceSettings - private var notchHeight: Int? = null var loaded = false @@ -78,6 +80,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { val themes = ArrayList() + var defaultSettings = CurrentNovelReaderSettings() + init { val forestTheme = ReaderTheme( @@ -171,16 +175,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { return } - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityNovelReaderBinding.inflate(layoutInflater) setContentView(binding.root) - settings = loadData("reader_settings", this) - ?: ReaderSettings().apply { saveData("reader_settings", this) } - uiSettings = loadData("ui_settings", this) - ?: UserInterfaceSettings().also { saveData("ui_settings", it) } - controllerDuration = (uiSettings.animationSpeed * 200).toLong() + controllerDuration = (PrefManager.getVal(PrefName.AnimationSpeed) * 200).toLong() setupViews() setupBackPressedHandler() @@ -286,12 +286,12 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { binding.bookReader.getAppearance { currentTheme = it themes.add(0, it) - settings.defaultLN = - loadData("${sanitizedBookId}_current_settings") ?: settings.defaultLN + defaultSettings = + loadReaderSettings("${sanitizedBookId}_current_settings") ?: defaultSettings applySettings() } - val cfi = loadData("${sanitizedBookId}_progress") + val cfi = PrefManager.getCustomVal("${sanitizedBookId}_progress", null as String?) cfi?.let { binding.bookReader.goto(it) } binding.progress.visibility = View.GONE @@ -304,7 +304,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { binding.novelReaderSlider.value = info.fraction.toFloat() val pos = info.tocItem?.let { item -> toc.indexOfFirst { it == item } } if (pos != null) binding.novelReaderChapterSelect.setSelection(pos) - saveData("${sanitizedBookId}_progress", info.cfi) + PrefManager.setCustomVal("${sanitizedBookId}_progress", info.cfi) } @@ -339,7 +339,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { return when (event.keyCode) { KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_PAGE_UP -> { if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP) - if (!settings.defaultLN.volumeButtons) + if (!defaultSettings.volumeButtons) return false if (event.action == KeyEvent.ACTION_DOWN) { onVolumeUp?.invoke() @@ -349,7 +349,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_PAGE_DOWN -> { if (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) - if (!settings.defaultLN.volumeButtons) + if (!defaultSettings.volumeButtons) return false if (event.action == KeyEvent.ACTION_DOWN) { onVolumeDown?.invoke() @@ -365,18 +365,18 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { fun applySettings() { - saveData("${sanitizedBookId}_current_settings", settings.defaultLN) + saveReaderSettings("${sanitizedBookId}_current_settings", defaultSettings) hideBars() - if (settings.defaultLN.useOledTheme) { + if (defaultSettings.useOledTheme) { themes.forEach { theme -> theme.darkBg = Color.parseColor("#000000") } } currentTheme = - themes.first { it.name.equals(settings.defaultLN.currentThemeName, ignoreCase = true) } + themes.first { it.name.equals(defaultSettings.currentThemeName, ignoreCase = true) } - when (settings.defaultLN.layout) { + when (defaultSettings.layout) { CurrentNovelReaderSettings.Layouts.PAGED -> { currentTheme?.flow = ReaderFlow.PAGINATED } @@ -387,22 +387,22 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { } requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER - when (settings.defaultLN.dualPageMode) { + when (defaultSettings.dualPageMode) { CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1 CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2 CurrentReaderSettings.DualPageModes.Force -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } - currentTheme?.lineHeight = settings.defaultLN.lineHeight - currentTheme?.gap = settings.defaultLN.margin - currentTheme?.maxInlineSize = settings.defaultLN.maxInlineSize - currentTheme?.maxBlockSize = settings.defaultLN.maxBlockSize - currentTheme?.useDark = settings.defaultLN.useDarkTheme + currentTheme?.lineHeight = defaultSettings.lineHeight + currentTheme?.gap = defaultSettings.margin + currentTheme?.maxInlineSize = defaultSettings.maxInlineSize + currentTheme?.maxBlockSize = defaultSettings.maxBlockSize + currentTheme?.useDark = defaultSettings.useDarkTheme currentTheme?.let { binding.bookReader.setAppearance(it) } - if (settings.defaultLN.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + if (defaultSettings.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } @@ -432,7 +432,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { fun handleController(shouldShow: Boolean? = null) { if (!loaded) return - if (!settings.showSystemBars) { + if (!PrefManager.getVal(PrefName.ShowSystemBars)) { hideBars() applyNotchMargin() } @@ -465,7 +465,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { private fun checkNotch() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !settings.showSystemBars) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal(PrefName.ShowSystemBars)) { val displayCutout = window.decorView.rootWindowInsets.displayCutout if (displayCutout != null) { if (displayCutout.boundingRects.size > 0) { @@ -486,8 +486,53 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener { } } + @Suppress("UNCHECKED_CAST") + private fun 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().log("Failed to delete file $fileName") + Injekt.get().logException(e) + } + e.printStackTrace() + } + return null + } + + private fun saveReaderSettings(fileName: String, data: Any?, context: Context? = null) { + tryWith { + val a = context ?: currContext() + if (a != null) { + val fos: FileOutputStream = a.openFileOutput(fileName, Context.MODE_PRIVATE) + val os = ObjectOutputStream(fos) + os.writeObject(data) + os.close() + fos.close() + } + } + } private fun hideBars() { - if (!settings.showSystemBars) hideSystemBars() + if (!PrefManager.getVal(PrefName.ShowSystemBars)) { + hideSystemBars() + } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt index 760c7eb3d8..67fc2ff3c2 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt @@ -30,7 +30,7 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val activity = requireActivity() as NovelReaderActivity - val settings = activity.settings.defaultLN + val settings = activity.defaultSettings val themeLabels = activity.themes.map { it.name } binding.themeSelect.adapter = NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels) diff --git a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt index a6d044e18f..7db5f057bd 100644 --- a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt @@ -1,7 +1,6 @@ package ani.dantotsu.media.user import android.annotation.SuppressLint -import android.content.Context import android.os.Bundle import android.util.TypedValue import android.view.View @@ -17,12 +16,10 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import ani.dantotsu.R import ani.dantotsu.Refresh -import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivityListBinding -import ani.dantotsu.loadData import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet -import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import com.google.android.material.tabs.TabLayout @@ -39,7 +36,7 @@ class ListActivity : AppCompatActivity() { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityListBinding.inflate(layoutInflater) @@ -67,8 +64,7 @@ class ListActivity : AppCompatActivity() { binding.listTitle.setTextColor(primaryTextColor) binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor) binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor) - val uiSettings = loadData("ui_settings") ?: UserInterfaceSettings() - if (!uiSettings.immersiveMode) { + if (!PrefManager.getVal(PrefName.ImmersiveMode)) { this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg_inv) binding.root.fitsSystemWindows = true @@ -154,8 +150,10 @@ class ListActivity : AppCompatActivity() { R.id.release -> "release" else -> null } - currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putString("sort_order", sort)?.apply() + PrefManager.setVal( + if (anime) PrefName.AnimeListSortOrder else PrefName.MangaListSortOrder, + sort ?: "" + ) binding.listProgressBar.visibility = View.VISIBLE binding.listViewPager.adapter = null scope.launch { diff --git a/app/src/main/java/ani/dantotsu/media/user/ListViewModel.kt b/app/src/main/java/ani/dantotsu/media/user/ListViewModel.kt index 87071791f3..06fd29a9fc 100644 --- a/app/src/main/java/ani/dantotsu/media/user/ListViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/user/ListViewModel.kt @@ -4,12 +4,13 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import ani.dantotsu.connections.anilist.Anilist -import ani.dantotsu.loadData import ani.dantotsu.media.Media +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.tryWithSuspend class ListViewModel : ViewModel() { - var grid = MutableLiveData(loadData("listGrid") ?: true) + var grid = MutableLiveData(PrefManager.getVal(PrefName.ListGrid)) private val lists = MutableLiveData>>() fun getLists(): LiveData>> = lists diff --git a/app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt b/app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt index 02779ab28d..5807cb4886 100644 --- a/app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt +++ b/app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt @@ -1,6 +1,5 @@ package ani.dantotsu.offline -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -11,6 +10,8 @@ import ani.dantotsu.R import ani.dantotsu.databinding.FragmentOfflineBinding import ani.dantotsu.isOnline import ani.dantotsu.navBarHeight +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.startMainActivity import ani.dantotsu.statusBarHeight @@ -26,8 +27,7 @@ class OfflineFragment : Fragment() { topMargin = statusBarHeight bottomMargin = navBarHeight } - offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("offlineMode", false) ?: false + offline = PrefManager.getVal(PrefName.OfflineMode) binding.noInternet.text = if (offline) "Offline Mode" else getString(R.string.no_internet) binding.refreshButton.visibility = if (offline) View.GONE else View.VISIBLE @@ -41,7 +41,6 @@ class OfflineFragment : Fragment() { override fun onResume() { super.onResume() - offline = requireContext().getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("offlineMode", false) ?: false + offline = PrefManager.getVal(PrefName.OfflineMode) } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/CustomBottomDialog.kt b/app/src/main/java/ani/dantotsu/others/CustomBottomDialog.kt index d95d56cb41..d5e794bd65 100644 --- a/app/src/main/java/ani/dantotsu/others/CustomBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/others/CustomBottomDialog.kt @@ -2,12 +2,10 @@ package ani.dantotsu.others import android.graphics.Color import android.os.Bundle -import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import ani.dantotsu.BottomSheetDialogFragment -import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetCustomBinding open class CustomBottomDialog : BottomSheetDialogFragment() { diff --git a/app/src/main/java/ani/dantotsu/others/DisabledReports.kt b/app/src/main/java/ani/dantotsu/others/DisabledReports.kt index ed8f5813f9..c25956f07f 100644 --- a/app/src/main/java/ani/dantotsu/others/DisabledReports.kt +++ b/app/src/main/java/ani/dantotsu/others/DisabledReports.kt @@ -1,5 +1,5 @@ package ani.dantotsu.others const val DisabledReports = false -//Setting this to false, will allow sending crash reports to Dantotsu's Firebase Crashlytics +//Setting this to false, will allow sending crash reports to Dantotsu's Firebase CrashlyticsInterface //if you want a custom build without crash reporting, set this to true \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/Download.kt b/app/src/main/java/ani/dantotsu/others/Download.kt index 8a8b496ab9..3a266af44f 100644 --- a/app/src/main/java/ani/dantotsu/others/Download.kt +++ b/app/src/main/java/ani/dantotsu/others/Download.kt @@ -14,9 +14,10 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.currContext import ani.dantotsu.defaultHeaders -import ani.dantotsu.loadData import ani.dantotsu.media.anime.Episode import ani.dantotsu.parsers.Book +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -36,7 +37,7 @@ object Download { private fun getDownloadDir(context: Context): File { val direct: File - if (loadData("sd_dl") == true) { + if (PrefManager.getVal(PrefName.SdDl)) { val arrayOfFiles = ContextCompat.getExternalFilesDirs(context, null) val parentDirectory = arrayOfFiles[1].toString() direct = File(parentDirectory) @@ -92,7 +93,7 @@ object Download { if (!file.url.startsWith("http")) toast(context.getString(R.string.invalid_url)) else - when (loadData("settings_download_manager", context, false) ?: 0) { + when (PrefManager.getVal(PrefName.DownloadManager) as Int) { 1 -> oneDM(context, file, notif ?: fileName) 2 -> adm(context, file, fileName, folder) else -> defaultDownload(context, file, fileName, folder, notif ?: fileName) @@ -117,7 +118,7 @@ object Download { request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) val arrayOfFiles = ContextCompat.getExternalFilesDirs(context, null) - if (loadData("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) { + if (PrefManager.getVal(PrefName.SdDl) && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) { val parentDirectory = arrayOfFiles[1].toString() + folder val direct = File(parentDirectory) if (!direct.exists()) direct.mkdirs() diff --git a/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt b/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt index aa792d9ffb..e11715c739 100644 --- a/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt +++ b/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetImageBinding +import ani.dantotsu.downloadsPermission import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap @@ -98,7 +100,8 @@ class ImageViewDialog : BottomSheetDialogFragment() { binding.bottomImageShare.isEnabled = true binding.bottomImageSave.isEnabled = true binding.bottomImageSave.setOnClickListener { - saveImageToDownloads(title, bitmap, requireActivity()) + if (downloadsPermission(context as AppCompatActivity)) + saveImageToDownloads(title, bitmap, requireActivity()) } binding.bottomImageShare.setOnClickListener { shareImage(title, bitmap, requireContext()) diff --git a/app/src/main/java/ani/dantotsu/others/Kitsu.kt b/app/src/main/java/ani/dantotsu/others/Kitsu.kt index bee4beeea7..374f0dd0eb 100644 --- a/app/src/main/java/ani/dantotsu/others/Kitsu.kt +++ b/app/src/main/java/ani/dantotsu/others/Kitsu.kt @@ -96,6 +96,7 @@ query { } } } + else -> { res?.body?.string() } diff --git a/app/src/main/java/ani/dantotsu/others/LangSet.kt b/app/src/main/java/ani/dantotsu/others/LangSet.kt deleted file mode 100644 index 3e7857dade..0000000000 --- a/app/src/main/java/ani/dantotsu/others/LangSet.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/MalScraper.kt b/app/src/main/java/ani/dantotsu/others/MalScraper.kt index 648f3a4a31..9ae38ccfe2 100644 --- a/app/src/main/java/ani/dantotsu/others/MalScraper.kt +++ b/app/src/main/java/ani/dantotsu/others/MalScraper.kt @@ -1,11 +1,7 @@ package ani.dantotsu.others -import ani.dantotsu.R import ani.dantotsu.client -import ani.dantotsu.currContext import ani.dantotsu.media.Media -import ani.dantotsu.snackString -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.withTimeout object MalScraper { @@ -50,7 +46,7 @@ object MalScraper { } } } catch (e: Exception) { - // if (e is TimeoutCancellationException) snackString(currContext()?.getString(R.string.error_loading_mal_data)) + // if (e is TimeoutCancellationException) snackString(currContext()?.getString(R.string.error_loading_mal_data)) } } } diff --git a/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt b/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt index 282c17fc40..4f70998af7 100644 --- a/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt +++ b/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt @@ -19,7 +19,6 @@ import ani.dantotsu.databinding.ActivityImageSearchBinding import ani.dantotsu.initActivity import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast import kotlinx.coroutines.Dispatchers @@ -52,7 +51,7 @@ class ImageSearchActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + initActivity(this) ThemeManager(this).applyTheme() binding = ActivityImageSearchBinding.inflate(layoutInflater) diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt index 0687b69f7c..8914c44038 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt @@ -5,13 +5,11 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.asyncMap import ani.dantotsu.currContext -import ani.dantotsu.loadData import ani.dantotsu.others.MalSyncBackup -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.tryWithSuspend import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode -import kotlin.properties.Delegates /** * An abstract class for creating a new Source @@ -163,7 +161,7 @@ abstract class AnimeParser : BaseParser() { * * **NOTE : do not forget to override `search` if the site does not support only dub search** * **/ - open val isDubAvailableSeparately by Delegates.notNull() + open fun isDubAvailableSeparately(sourceLang: Int? = null): Boolean = false /** * The app changes this, depending on user's choice. @@ -182,8 +180,12 @@ abstract class AnimeParser : BaseParser() { * **/ override suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? { checkIfVariablesAreEmpty() - val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else "" - var loaded = loadData("${saveName}${dub}_$mediaId") + val dub = if (isDubAvailableSeparately()) "_${if (selectDub) "dub" else "sub"}" else "" + var loaded = PrefManager.getNullableCustomVal( + "${saveName}${dub}_$mediaId", + null, + ShowResponse::class.java + ) if (loaded == null && malSyncBackupName.isNotEmpty()) loaded = MalSyncBackup.get(mediaId, malSyncBackupName, selectDub) ?.also { saveShowResponse(mediaId, it, true) } @@ -200,8 +202,8 @@ abstract class AnimeParser : BaseParser() { ) } : ${response.name}" ) - val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else "" - saveData("${saveName}${dub}_$mediaId", response) + val dub = if (isDubAvailableSeparately()) "_${if (selectDub) "dub" else "sub"}" else "" + PrefManager.setCustomVal("${saveName}${dub}_$mediaId", response) } } } @@ -209,8 +211,6 @@ abstract class AnimeParser : BaseParser() { class EmptyAnimeParser : AnimeParser() { override val name: String = "None" override val saveName: String = "None" - - override val isDubAvailableSeparately: Boolean = false override suspend fun loadEpisodes( animeLink: String, extra: Map?, diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index 11a69c3101..9806767aee 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -1,19 +1,21 @@ package ani.dantotsu.parsers -import android.content.Context import ani.dantotsu.Lazier import ani.dantotsu.lazyList +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first object AnimeSources : WatchSources() { override var list: List> = emptyList() - var pinnedAnimeSources: Set = emptySet() + var pinnedAnimeSources: List = emptyList() - suspend fun init(fromExtensions: StateFlow>, context: Context) { - val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - pinnedAnimeSources = sharedPrefs.getStringSet("pinned_anime_sources", emptySet()) ?: emptySet() + suspend fun init(fromExtensions: StateFlow>) { + pinnedAnimeSources = + PrefManager.getNullableVal>(PrefName.AnimeSourcesOrder, null) + ?: emptyList() // Initialize with the first value from StateFlow val initialExtensions = fromExtensions.first() @@ -24,7 +26,10 @@ object AnimeSources : WatchSources() { // Update as StateFlow emits new values fromExtensions.collect { extensions -> - list = sortPinnedAnimeSources(createParsersFromExtensions(extensions), pinnedAnimeSources) + Lazier( + list = sortPinnedAnimeSources( + createParsersFromExtensions(extensions), + pinnedAnimeSources + ) + Lazier( { OfflineAnimeParser() }, "Downloaded" ) @@ -34,7 +39,7 @@ object AnimeSources : WatchSources() { fun performReorderAnimeSources() { //remove the downloaded source from the list to avoid duplicates list = list.filter { it.name != "Downloaded" } - list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier( + list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier( { OfflineAnimeParser() }, "Downloaded" ) @@ -47,13 +52,17 @@ object AnimeSources : WatchSources() { } } - private fun sortPinnedAnimeSources(Sources: List>, pinnedAnimeSources: Set): List> { - //find the pinned sources - val pinnedSources = Sources.filter { pinnedAnimeSources.contains(it.name) } - //find the unpinned sources - val unpinnedSources = Sources.filter { !pinnedAnimeSources.contains(it.name) } - //put the pinned sources at the top of the list - return pinnedSources + unpinnedSources + private fun sortPinnedAnimeSources( + sources: List>, + pinnedAnimeSources: List + ): List> { + val pinnedSourcesMap = sources.filter { pinnedAnimeSources.contains(it.name) } + .associateBy { it.name } + val orderedPinnedSources = pinnedAnimeSources.mapNotNull { name -> + pinnedSourcesMap[name] + } + val unpinnedSources = sources.filterNot { pinnedAnimeSources.contains(it.name) } + return orderedPinnedSources + unpinnedSources } } diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 4ef6aee20c..761bd94948 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -10,12 +10,14 @@ import android.os.Build import android.os.Environment import android.provider.MediaStore import ani.dantotsu.FileUrl +import ani.dantotsu.currContext import ani.dantotsu.logger import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.snackString import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode @@ -26,6 +28,7 @@ import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException +import eu.kanade.tachiyomi.source.anime.getPreferenceKey import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter @@ -71,8 +74,78 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { override val name = extension.name override val saveName = extension.name override val hostUrl = extension.sources.first().name - override val isDubAvailableSeparately = false override val isNSFW = extension.isNsfw + override var selectDub: Boolean + get() = getDub() + set(value) { + setDub(value) + } + + private fun getDub(): Boolean { + val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource + ?: return false + currContext()?.let { context -> + val sharedPreferences = + context.getSharedPreferences( + configurableSource.getPreferenceKey(), + Context.MODE_PRIVATE + ) + sharedPreferences.all.filterValues { AnimeNameAdapter.getSubDub(it.toString()) != AnimeNameAdapter.Companion.SubDubType.NULL } + .forEach { value -> + return when (AnimeNameAdapter.getSubDub(value.value.toString())) { + AnimeNameAdapter.Companion.SubDubType.SUB -> false + AnimeNameAdapter.Companion.SubDubType.DUB -> true + AnimeNameAdapter.Companion.SubDubType.NULL -> false + } + } + } + return false + } + + fun setDub(setDub: Boolean) { + val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource + ?: return + val type = when (setDub) { + true -> AnimeNameAdapter.Companion.SubDubType.DUB + false -> AnimeNameAdapter.Companion.SubDubType.SUB + } + currContext()?.let { context -> + val sharedPreferences = + context.getSharedPreferences( + configurableSource.getPreferenceKey(), + Context.MODE_PRIVATE + ) + sharedPreferences.all.filterValues { AnimeNameAdapter.getSubDub(it.toString()) != AnimeNameAdapter.Companion.SubDubType.NULL } + .forEach { value -> + val setValue = AnimeNameAdapter.setSubDub(value.value.toString(), type) + if (setValue != null) { + sharedPreferences.edit().putString(value.key, setValue).apply() + } + } + } + } + + override fun isDubAvailableSeparately(sourceLang: Int?): Boolean { + val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource + ?: return false + currContext()?.let { context -> + logger("isDubAvailableSeparately: ${configurableSource.getPreferenceKey()}") + val sharedPreferences = + context.getSharedPreferences( + configurableSource.getPreferenceKey(), + Context.MODE_PRIVATE + ) + sharedPreferences.all.filterValues { + AnimeNameAdapter.setSubDub( + it.toString(), + AnimeNameAdapter.Companion.SubDubType.NULL + ) != null + } + .forEach { _ -> return true } + } + return false + } + override suspend fun loadEpisodes( animeLink: String, extra: Map?, @@ -106,6 +179,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } it } + } else if (episodesAreIncrementing(res)) { + res.sortedBy { it.episode_number } } else { var episodeCounter = 1f // Group by season, sort within each season, and then renumber while keeping episode number 0 as is @@ -113,20 +188,20 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 } seasonGroups.keys.sortedBy { it.toInt() } .flatMap { season -> - seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode -> - if (episode.episode_number != 0f) { // Skip renumbering for episode number 0 - val potentialNumber = - AnimeNameAdapter.findEpisodeNumber(episode.name) - if (potentialNumber != null) { - episode.episode_number = potentialNumber - } else { - episode.episode_number = episodeCounter + seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode -> + if (episode.episode_number != 0f) { // Skip renumbering for episode number 0 + val potentialNumber = + AnimeNameAdapter.findEpisodeNumber(episode.name) + if (potentialNumber != null) { + episode.episode_number = potentialNumber + } else { + episode.episode_number = episodeCounter + } + episodeCounter++ } - episodeCounter++ - } - episode - } ?: emptyList() - } + episode + } ?: emptyList() + } } return sortedEpisodes.map { SEpisodeToEpisode(it) } } catch (e: Exception) { @@ -135,6 +210,19 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { return emptyList() } + private fun episodesAreIncrementing(episodes: List): Boolean { + val sortedEpisodes = episodes.sortedBy { it.episode_number } + val takenNumbers = mutableListOf() + sortedEpisodes.forEach { + if (it.episode_number !in takenNumbers) { + takenNumbers.add(it.episode_number) + } else { + return false + } + } + return true + } + override suspend fun loadVideoServers( episodeLink: String, extra: Map?, @@ -177,7 +265,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } catch (e: CloudflareBypassException) { logger("Exception in search: $e") withContext(Dispatchers.Main) { - snackString( "Failed to bypass Cloudflare") + snackString("Failed to bypass Cloudflare") } emptyList() } catch (e: Exception) { @@ -626,8 +714,6 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { // If the format is still undetermined, log an error if (format == null) { logger("Unknown video format: $videoUrl") - //FirebaseCrashlytics.getInstance() - // .recordException(Exception("Unknown video format: $videoUrl")) format = VideoType.CONTAINER } val headersMap: Map = diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt index 40ce4601ea..e61c68901f 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt @@ -3,10 +3,9 @@ package ani.dantotsu.parsers import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.currContext -import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.Media -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.source.model.SManga import me.xdrop.fuzzywuzzy.FuzzySearch @@ -135,7 +134,11 @@ abstract class BaseParser { * **/ open suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? { checkIfVariablesAreEmpty() - return loadData("${saveName}_$mediaId") + return PrefManager.getNullableCustomVal( + "${saveName}_$mediaId", + null, + ShowResponse::class.java + ) } /** @@ -151,7 +154,7 @@ abstract class BaseParser { ) } : ${response.name}" ) - saveData("${saveName}_$mediaId", response) + PrefManager.setCustomVal("${saveName}_$mediaId", response) } } diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt index a6da1540bc..34c3e0a00d 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt @@ -1,19 +1,21 @@ package ani.dantotsu.parsers -import android.content.Context import ani.dantotsu.Lazier import ani.dantotsu.lazyList +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first object MangaSources : MangaReadSources() { override var list: List> = emptyList() - var pinnedMangaSources: Set = emptySet() + var pinnedMangaSources: List = emptyList() - suspend fun init(fromExtensions: StateFlow>, context: Context) { - val sharedPrefs = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - pinnedMangaSources = sharedPrefs.getStringSet("pinned_manga_sources", emptySet()) ?: emptySet() + suspend fun init(fromExtensions: StateFlow>) { + pinnedMangaSources = + PrefManager.getNullableVal>(PrefName.MangaSourcesOrder, null) + ?: emptyList() // Initialize with the first value from StateFlow val initialExtensions = fromExtensions.first() @@ -24,7 +26,10 @@ object MangaSources : MangaReadSources() { // Update as StateFlow emits new values fromExtensions.collect { extensions -> - list = sortPinnedMangaSources(createParsersFromExtensions(extensions), pinnedMangaSources) + Lazier( + list = sortPinnedMangaSources( + createParsersFromExtensions(extensions), + pinnedMangaSources + ) + Lazier( { OfflineMangaParser() }, "Downloaded" ) @@ -47,21 +52,21 @@ object MangaSources : MangaReadSources() { } } - private fun sortPinnedMangaSources(Sources: List>, pinnedMangaSources: Set): List> { - //find the pinned sources - val pinnedSources = Sources.filter { pinnedMangaSources.contains(it.name) } - //find the unpinned sources - val unpinnedSources = Sources.filter { !pinnedMangaSources.contains(it.name) } - //put the pinned sources at the top of the list - return pinnedSources + unpinnedSources + private fun sortPinnedMangaSources( + sources: List>, + pinnedMangaSources: List + ): List> { + val pinnedSourcesMap = sources.filter { pinnedMangaSources.contains(it.name) } + .associateBy { it.name } + val orderedPinnedSources = pinnedMangaSources.mapNotNull { name -> + pinnedSourcesMap[name] + } + val unpinnedSources = sources.filterNot { pinnedMangaSources.contains(it.name) } + return orderedPinnedSources + unpinnedSources } } object HMangaSources : MangaReadSources() { - val aList: List> = lazyList() - suspend fun init(fromExtensions: StateFlow>) { - //todo - } - + private val aList: List> = lazyList() override val list = listOf(aList, MangaSources.list).flatten() } diff --git a/app/src/main/java/ani/dantotsu/parsers/NovelSources.kt b/app/src/main/java/ani/dantotsu/parsers/NovelSources.kt index e5298bceb7..8fb66650d4 100644 --- a/app/src/main/java/ani/dantotsu/parsers/NovelSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/NovelSources.kt @@ -4,13 +4,20 @@ import android.util.Log import ani.dantotsu.Lazier import ani.dantotsu.parsers.novel.DynamicNovelParser import ani.dantotsu.parsers.novel.NovelExtension +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first object NovelSources : NovelReadSources() { override var list: List> = emptyList() + var pinnedNovelSources: List = emptyList() suspend fun init(fromExtensions: StateFlow>) { + pinnedNovelSources = + PrefManager.getNullableVal>(PrefName.NovelSourcesOrder, null) + ?: emptyList() + // Initialize with the first value from StateFlow val initialExtensions = fromExtensions.first() list = createParsersFromExtensions(initialExtensions) + Lazier( @@ -20,13 +27,25 @@ object NovelSources : NovelReadSources() { // Update as StateFlow emits new values fromExtensions.collect { extensions -> - list = createParsersFromExtensions(extensions) + Lazier( + list = sortPinnedNovelSources( + createParsersFromExtensions(extensions), + pinnedNovelSources + ) + Lazier( { OfflineNovelParser() }, "Downloaded" ) } } + fun performReorderNovelSources() { + //remove the downloaded source from the list to avoid duplicates + list = list.filter { it.name != "Downloaded" } + list = sortPinnedNovelSources(list, pinnedNovelSources) + Lazier( + { OfflineNovelParser() }, + "Downloaded" + ) + } + private fun createParsersFromExtensions(extensions: List): List> { Log.d("NovelSources", "createParsersFromExtensions") Log.d("NovelSources", extensions.toString()) @@ -35,4 +54,17 @@ object NovelSources : NovelReadSources() { Lazier({ DynamicNovelParser(extension) }, name) } } + + private fun sortPinnedNovelSources( + parsers: List>, + pinnedSources: List + ): List> { + 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 + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt index 0f8c79243e..1c9c3b6c7a 100644 --- a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt @@ -21,7 +21,6 @@ class OfflineAnimeParser : AnimeParser() { override val name = "Offline" override val saveName = "Offline" override val hostUrl = "Offline" - override val isDubAvailableSeparately = false override val isNSFW = false override suspend fun loadEpisodes( @@ -119,7 +118,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() { val sublist = getSubtitle( videoServer.extraData?.get("title") ?: "", videoServer.extraData?.get("episode") ?: "" - )?: emptyList() + ) ?: emptyList() //we need to return a "fake" video so that the app doesn't crash val video = Video( null, diff --git a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt index 582a7cb4bf..ef164b3404 100644 --- a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt +++ b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt @@ -62,6 +62,7 @@ data class VideoServer( ) : Serializable { constructor(name: String, embedUrl: String, extraData: Map? = null) : this(name, FileUrl(embedUrl), extraData) + constructor(name: String, offline: Boolean, extraData: Map?) : this(name, FileUrl(""), extraData, null, offline) diff --git a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionGithubApi.kt b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionGithubApi.kt index ea23fbeb58..4381ee2792 100644 --- a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionGithubApi.kt +++ b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionGithubApi.kt @@ -2,8 +2,9 @@ package ani.dantotsu.parsers.novel import android.content.Context -import ani.dantotsu.currContext import ani.dantotsu.logger +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult @@ -26,9 +27,7 @@ class NovelExtensionGithubApi { private val novelExtensionManager: NovelExtensionManager by injectLazy() private val json: Json by injectLazy() - private val lastExtCheck: Long = - currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getLong("last_ext_check", 0) ?: 0 + private val lastExtCheck: Long = PrefManager.getVal(PrefName.NovelLastExtCheck) private var requiresFallbackSource = false @@ -86,8 +85,7 @@ class NovelExtensionGithubApi { novelExtensionManager.availableExtensionsFlow.value } else { findExtensions().also { - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putLong("last_ext_check", Date().time)?.apply() + PrefManager.setVal(PrefName.NovelLastExtCheck, Date().time) } } diff --git a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt index 3c26530cb5..94574e205b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt +++ b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt @@ -6,12 +6,14 @@ import android.content.pm.PackageManager.GET_SIGNATURES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.os.Build import android.util.Log +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.logger import ani.dantotsu.parsers.NovelInterface import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import dalvik.system.PathClassLoader import eu.kanade.tachiyomi.util.lang.Hash +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.File import java.util.Locale @@ -141,7 +143,7 @@ internal object NovelExtensionLoader { listOfNotNull(novelInterfaceInstance) } catch (e: Exception) { e.printStackTrace() - FirebaseCrashlytics.getInstance().recordException(e) + Injekt.get().logException(e) emptyList() } } diff --git a/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt index e46b1d711e..a6d6c39e11 100644 --- a/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt @@ -12,13 +12,13 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding import ani.dantotsu.settings.paging.AnimeExtensionAdapter import ani.dantotsu.settings.paging.AnimeExtensionsViewModel import ani.dantotsu.settings.paging.AnimeExtensionsViewModelFactory import ani.dantotsu.settings.paging.OnAnimeInstallClickListener import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension @@ -71,6 +71,10 @@ class AnimeExtensionsFragment : Fragment(), viewModel.setSearchQuery(query ?: "") } + override fun notifyDataChanged() { + viewModel.invalidatePager() + } + override fun onInstallClick(pkg: AnimeExtension.Available) { val context = requireContext() if (isAdded) { @@ -93,7 +97,7 @@ class AnimeExtensionsFragment : Fragment(), notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) val builder = NotificationCompat.Builder( context, Notifications.CHANNEL_DOWNLOADER_ERROR @@ -110,7 +114,7 @@ class AnimeExtensionsFragment : Fragment(), context, Notifications.CHANNEL_DOWNLOADER_PROGRESS ) - .setSmallIcon(R.drawable.ic_round_download_24) + .setSmallIcon(R.drawable.ic_download_24) .setContentTitle("Installation complete") .setContentText("The extension has been successfully installed.") .setPriority(NotificationCompat.PRIORITY_LOW) diff --git a/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt index b1cd429bda..eae49c8d37 100644 --- a/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt +++ b/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt @@ -1,23 +1,29 @@ package ani.dantotsu.settings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import java.io.Serializable data class CurrentNovelReaderSettings( - var currentThemeName: String = "Default", - var layout: Layouts = Layouts.PAGED, - var dualPageMode: CurrentReaderSettings.DualPageModes = CurrentReaderSettings.DualPageModes.Automatic, - var lineHeight: Float = 1.4f, - var margin: Float = 0.06f, - var justify: Boolean = true, - var hyphenation: Boolean = true, - var useDarkTheme: Boolean = false, - var useOledTheme: Boolean = false, - var invert: Boolean = false, - var maxInlineSize: Int = 720, - var maxBlockSize: Int = 1440, - var horizontalScrollBar: Boolean = true, - var keepScreenOn: Boolean = false, - var volumeButtons: Boolean = false, + var currentThemeName: String = PrefManager.getVal(PrefName.CurrentThemeName), + var layout: Layouts = Layouts[PrefManager.getVal(PrefName.LayoutNovel)] + ?: Layouts.PAGED, + var dualPageMode: CurrentReaderSettings.DualPageModes = CurrentReaderSettings.DualPageModes[PrefManager.getVal( + PrefName.DualPageModeNovel + )] + ?: CurrentReaderSettings.DualPageModes.Automatic, + var lineHeight: Float = PrefManager.getVal(PrefName.LineHeight), + var margin: Float = PrefManager.getVal(PrefName.Margin), + var justify: Boolean = PrefManager.getVal(PrefName.Justify), + var hyphenation: Boolean = PrefManager.getVal(PrefName.Hyphenation), + var useDarkTheme: Boolean = PrefManager.getVal(PrefName.UseDarkThemeNovel), + var useOledTheme: Boolean = PrefManager.getVal(PrefName.UseOledThemeNovel), + var invert: Boolean = PrefManager.getVal(PrefName.Invert), + var maxInlineSize: Int = PrefManager.getVal(PrefName.MaxInlineSize), + var maxBlockSize: Int = PrefManager.getVal(PrefName.MaxBlockSize), + var horizontalScrollBar: Boolean = PrefManager.getVal(PrefName.HorizontalScrollBarNovel), + var keepScreenOn: Boolean = PrefManager.getVal(PrefName.KeepScreenOnNovel), + var volumeButtons: Boolean = PrefManager.getVal(PrefName.VolumeButtonsNovel) ) : Serializable { enum class Layouts(val string: String) { diff --git a/app/src/main/java/ani/dantotsu/settings/CurrentReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/CurrentReaderSettings.kt index bfa2b4d835..2aca1b3e93 100644 --- a/app/src/main/java/ani/dantotsu/settings/CurrentReaderSettings.kt +++ b/app/src/main/java/ani/dantotsu/settings/CurrentReaderSettings.kt @@ -1,23 +1,28 @@ package ani.dantotsu.settings +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import java.io.Serializable data class CurrentReaderSettings( - var direction: Directions = Directions.TOP_TO_BOTTOM, - var layout: Layouts = Layouts.CONTINUOUS, - var dualPageMode: DualPageModes = DualPageModes.Automatic, - var overScrollMode: Boolean = true, - var trueColors: Boolean = false, - var rotation: Boolean = true, - var padding: Boolean = true, - var hidePageNumbers: Boolean = false, - var horizontalScrollBar: Boolean = true, - var keepScreenOn: Boolean = false, - var volumeButtons: Boolean = false, - var wrapImages: Boolean = false, - var longClickImage: Boolean = true, - var cropBorders: Boolean = false, - var cropBorderThreshold: Int = 10, + var direction: Directions = Directions[PrefManager.getVal(PrefName.Direction)] + ?: Directions.TOP_TO_BOTTOM, + var layout: Layouts = Layouts[PrefManager.getVal(PrefName.LayoutReader)] + ?: Layouts.CONTINUOUS, + var dualPageMode: DualPageModes = DualPageModes[PrefManager.getVal(PrefName.DualPageModeReader)] + ?: DualPageModes.Automatic, + var overScrollMode: Boolean = PrefManager.getVal(PrefName.OverScrollMode), + var trueColors: Boolean = PrefManager.getVal(PrefName.TrueColors), + var rotation: Boolean = PrefManager.getVal(PrefName.Rotation), + var padding: Boolean = PrefManager.getVal(PrefName.Padding), + var hidePageNumbers: Boolean = PrefManager.getVal(PrefName.HidePageNumbers), + var horizontalScrollBar: Boolean = PrefManager.getVal(PrefName.HorizontalScrollBar), + var keepScreenOn: Boolean = PrefManager.getVal(PrefName.KeepScreenOn), + var volumeButtons: Boolean = PrefManager.getVal(PrefName.VolumeButtonsReader), + var wrapImages: Boolean = PrefManager.getVal(PrefName.WrapImages), + var longClickImage: Boolean = PrefManager.getVal(PrefName.LongClickImage), + var cropBorders: Boolean = PrefManager.getVal(PrefName.CropBorders), + var cropBorderThreshold: Int = PrefManager.getVal(PrefName.CropBorderThreshold) ) : Serializable { enum class Directions { diff --git a/app/src/main/java/ani/dantotsu/settings/DevelopersAdapter.kt b/app/src/main/java/ani/dantotsu/settings/DevelopersAdapter.kt index 7375d101e8..7f42017d47 100644 --- a/app/src/main/java/ani/dantotsu/settings/DevelopersAdapter.kt +++ b/app/src/main/java/ani/dantotsu/settings/DevelopersAdapter.kt @@ -4,15 +4,12 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.databinding.ItemDeveloperBinding -import ani.dantotsu.loadData import ani.dantotsu.loadImage import ani.dantotsu.openLinkInBrowser import ani.dantotsu.setAnimation class DevelopersAdapter(private val developers: Array) : RecyclerView.Adapter() { - private val uiSettings = - loadData("ui_settings") ?: UserInterfaceSettings() inner class DeveloperViewHolder(val binding: ItemDeveloperBinding) : RecyclerView.ViewHolder(binding.root) { @@ -35,7 +32,7 @@ class DevelopersAdapter(private val developers: Array) : override fun onBindViewHolder(holder: DeveloperViewHolder, position: Int) { val b = holder.binding - setAnimation(b.root.context, b.root, uiSettings) + setAnimation(b.root.context, b.root) val dev = developers[position] b.devName.text = dev.name b.devProfile.loadImage(dev.pfp) diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 04701603c9..5300bbcdad 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -1,6 +1,7 @@ package ani.dantotsu.settings import android.annotation.SuppressLint +import android.app.AlertDialog import android.os.Build.* import android.os.Build.VERSION.* import android.os.Bundle @@ -16,7 +17,9 @@ import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.ActivityExtensionsBinding -import ani.dantotsu.others.LangSet +import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.themes.ThemeManager import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator @@ -27,7 +30,7 @@ class ExtensionsActivity : AppCompatActivity() { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityExtensionsBinding.inflate(layoutInflater) setContentView(binding.root) @@ -62,6 +65,9 @@ class ExtensionsActivity : AppCompatActivity() { searchView.setText("") searchView.clearFocus() tabLayout.clearFocus() + if (tab.text?.contains("Installed") == true) binding.languageselect.visibility = + View.GONE + else binding.languageselect.visibility = View.VISIBLE viewPager.updateLayoutParams { height = ViewGroup.LayoutParams.MATCH_PARENT } @@ -112,20 +118,30 @@ class ExtensionsActivity : AppCompatActivity() { } }) - initActivity(this) - binding.languageselect.visibility = View.GONE - /* TODO - binding.languageselect.setOnClickListener { - val popup = PopupMenu(this, it) - popup.inflate(R.menu.launguage_selector_menu) - popup.setOnMenuItemClickListener { menuItem -> - true - } - popup.setOnDismissListener { - } - popup.show() - }*/ + binding.languageselect.setOnClickListener { + val languageOptions = + LanguageMapper.Companion.Language.entries.map { it.name }.toTypedArray() + val builder = AlertDialog.Builder(currContext(), R.style.MyPopup) + val listOrder: String = PrefManager.getVal(PrefName.LangSort) + val index = LanguageMapper.Companion.Language.entries.toTypedArray() + .indexOfFirst { it.code == listOrder } + builder.setTitle("Language") + builder.setSingleChoiceItems(languageOptions, index) { dialog, i -> + PrefManager.setVal( + PrefName.LangSort, + LanguageMapper.Companion.Language.entries[i].code + ) + val currentFragment = + supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") + if (currentFragment is SearchQueryHandler) { + currentFragment.notifyDataChanged() + } + dialog.dismiss() + } + val dialog = builder.show() + dialog.window?.setDimAmount(0.8f) + } binding.settingsContainer.updateLayoutParams { topMargin = statusBarHeight bottomMargin = navBarHeight @@ -138,4 +154,5 @@ class ExtensionsActivity : AppCompatActivity() { interface SearchQueryHandler { fun updateContentBasedOnQuery(query: String?) + fun notifyDataChanged() } diff --git a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt index 74d9525d8f..49656f5593 100644 --- a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt @@ -7,7 +7,6 @@ import ani.dantotsu.R import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivityFaqBinding import ani.dantotsu.initActivity -import ani.dantotsu.others.LangSet import ani.dantotsu.themes.ThemeManager class FAQActivity : AppCompatActivity() { @@ -32,7 +31,7 @@ class FAQActivity : AppCompatActivity() { currContext()?.getString(R.string.answer_17) ?: "" ), Triple( - R.drawable.ic_round_download_24, + R.drawable.ic_download_24, currContext()?.getString(R.string.question_3) ?: "", currContext()?.getString(R.string.answer_3) ?: "" ), @@ -101,7 +100,7 @@ class FAQActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityFaqBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/ani/dantotsu/settings/FAQAdapter.kt b/app/src/main/java/ani/dantotsu/settings/FAQAdapter.kt index f854bd8908..6642541783 100644 --- a/app/src/main/java/ani/dantotsu/settings/FAQAdapter.kt +++ b/app/src/main/java/ani/dantotsu/settings/FAQAdapter.kt @@ -6,7 +6,6 @@ import android.widget.TextView import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.databinding.ItemQuestionBinding -import ani.dantotsu.loadData import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.setAnimation import io.noties.markwon.Markwon @@ -17,8 +16,6 @@ class FAQAdapter( private val manager: FragmentManager ) : RecyclerView.Adapter() { - private val uiSettings = - loadData("ui_settings") ?: UserInterfaceSettings() inner class FAQViewHolder(val binding: ItemQuestionBinding) : RecyclerView.ViewHolder(binding.root) @@ -35,7 +32,7 @@ class FAQAdapter( override fun onBindViewHolder(holder: FAQViewHolder, position: Int) { val b = holder.binding.root - setAnimation(b.context, b, uiSettings) + setAnimation(b.context, b) val faq = questions[position] b.text = faq.second b.setCompoundDrawablesWithIntrinsicBounds(faq.first, 0, 0, 0) diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt index 6f83009a7f..97bde2833a 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt @@ -17,19 +17,23 @@ import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding -import ani.dantotsu.loadData +import ani.dantotsu.logger import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -38,15 +42,17 @@ import kotlinx.coroutines.launch import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Collections import java.util.Locale + class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { private var _binding: FragmentAnimeExtensionsBinding? = null private val binding get() = _binding!! private lateinit var extensionsRecyclerView: RecyclerView - private val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) private val animeExtensionManager: AnimeExtensionManager = Injekt.get() private val extensionsAdapter = AnimeExtensionsAdapter( { pkg -> @@ -139,7 +145,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error val builder = NotificationCompat.Builder( context, @@ -173,6 +179,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { }, skipIcons ) + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -184,22 +191,82 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) extensionsRecyclerView.adapter = extensionsAdapter + val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val newList = extensionsAdapter.currentList.toMutableList() + val fromPosition = viewHolder.absoluteAdapterPosition + val toPosition = target.absoluteAdapterPosition + if (fromPosition < toPosition) { //probably need to switch to a recyclerview adapter + for (i in fromPosition until toPosition) { + Collections.swap(newList, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(newList, i, i - 1) + } + } + extensionsAdapter.submitList(newList) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + viewHolder?.itemView?.elevation = 8f + viewHolder?.itemView?.translationZ = 8f + } + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) { + super.clearView(recyclerView, viewHolder) + extensionsAdapter.updatePref() + viewHolder.itemView.elevation = 0f + viewHolder.itemView.translationZ = 0f + } + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(extensionsRecyclerView) + lifecycleScope.launch { animeExtensionManager.installedExtensionsFlow.collect { extensions -> - extensionsAdapter.updateData(extensions) + extensionsAdapter.updateData(sortToAnimeSourcesList(extensions)) } } - val extensionsRecyclerView: RecyclerView = binding.allAnimeExtensionsRecyclerView return binding.root } + + private fun sortToAnimeSourcesList(inpt: List): List { + val sourcesMap = inpt.associateBy { it.name } + val orderedSources = AnimeSources.pinnedAnimeSources.mapNotNull { name -> + sourcesMap[name] + } + return orderedSources + inpt.filter { !AnimeSources.pinnedAnimeSources.contains(it.name) } + } + override fun onDestroyView() { super.onDestroyView();_binding = null } override fun updateContentBasedOnQuery(query: String?) { - extensionsAdapter.filter(query ?: "", animeExtensionManager.installedExtensionsFlow.value) + extensionsAdapter.filter( + query ?: "", + sortToAnimeSourcesList(animeExtensionManager.installedExtensionsFlow.value) + ) + } + + override fun notifyDataChanged() { // Do nothing } private class AnimeExtensionsAdapter( @@ -211,7 +278,14 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { ) { fun updateData(newExtensions: List) { - submitList(newExtensions) // Use submitList instead of manual list handling + submitList(newExtensions) + } + + fun updatePref() { + val map = currentList.map { it.name } + PrefManager.setVal(PrefName.AnimeSourcesOrder, map) + AnimeSources.pinnedAnimeSources = map + AnimeSources.performReorderAnimeSources() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -222,7 +296,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val extension = getItem(position) // Use getItem() from ListAdapter + val extension = getItem(position) val nsfw = if (extension.isNsfw) "(18+)" else "" val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) holder.extensionNameTextView.text = extension.name @@ -241,7 +315,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { holder.settingsImageView.setOnClickListener { onSettingsClicked(extension) } - holder.card.setOnLongClickListener { + holder.closeTextView.setOnLongClickListener { onUninstallClicked(extension, true) true } @@ -254,7 +328,8 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { filteredList.add(extension) } } - submitList(filteredList) + if (filteredList != currentList) + submitList(filteredList) } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { @@ -264,7 +339,6 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: ImageView = view.findViewById(R.id.closeTextView) - val card = view.findViewById(R.id.extensionCardView) } companion object { diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt index 1ad5228a54..a102c76c06 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt @@ -18,19 +18,22 @@ import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentMangaExtensionsBinding -import ani.dantotsu.loadData import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.parsers.MangaSources import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.extension.manga.model.MangaExtension @@ -39,13 +42,14 @@ import kotlinx.coroutines.launch import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Collections import java.util.Locale class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { private var _binding: FragmentMangaExtensionsBinding? = null private val binding get() = _binding!! private lateinit var extensionsRecyclerView: RecyclerView - private val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) private val mangaExtensionManager: MangaExtensionManager = Injekt.get() private val extensionsAdapter = MangaExtensionsAdapter( { pkg -> @@ -138,7 +142,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error val builder = NotificationCompat.Builder( context, @@ -156,7 +160,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { context, Notifications.CHANNEL_DOWNLOADER_PROGRESS ) - .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) + .setSmallIcon(R.drawable.ic_check) .setContentTitle("Update complete") .setContentText("The extension has been successfully updated.") .setPriority(NotificationCompat.PRIORITY_LOW) @@ -183,36 +187,93 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) extensionsRecyclerView.adapter = extensionsAdapter + val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val newList = extensionsAdapter.currentList.toMutableList() + val fromPosition = viewHolder.absoluteAdapterPosition + val toPosition = target.absoluteAdapterPosition + if (fromPosition < toPosition) { //probably need to switch to a recyclerview adapter + for (i in fromPosition until toPosition) { + Collections.swap(newList, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(newList, i, i - 1) + } + } + extensionsAdapter.submitList(newList) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + viewHolder?.itemView?.elevation = 8f + viewHolder?.itemView?.translationZ = 8f + } + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) { + super.clearView(recyclerView, viewHolder) + extensionsAdapter.updatePref() + viewHolder.itemView.elevation = 0f + viewHolder.itemView.translationZ = 0f + } + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(extensionsRecyclerView) + lifecycleScope.launch { mangaExtensionManager.installedExtensionsFlow.collect { extensions -> - extensionsAdapter.updateData(extensions) + extensionsAdapter.updateData(sortToMangaSourcesList(extensions)) } } - val extensionsRecyclerView: RecyclerView = binding.allMangaExtensionsRecyclerView return binding.root } + private fun sortToMangaSourcesList(inpt: List): List { + val sourcesMap = inpt.associateBy { it.name } + val orderedSources = MangaSources.pinnedMangaSources.mapNotNull { name -> + sourcesMap[name] + } + return orderedSources + inpt.filter { !MangaSources.pinnedMangaSources.contains(it.name) } + } + override fun onDestroyView() { super.onDestroyView();_binding = null } override fun updateContentBasedOnQuery(query: String?) { - extensionsAdapter.filter(query ?: "", mangaExtensionManager.installedExtensionsFlow.value) + extensionsAdapter.filter( + query ?: "", + sortToMangaSourcesList(mangaExtensionManager.installedExtensionsFlow.value) + ) + } + + override fun notifyDataChanged() { // Do nothing } private class MangaExtensionsAdapter( private val onSettingsClicked: (MangaExtension.Installed) -> Unit, private val onUninstallClicked: (MangaExtension.Installed, Boolean) -> Unit, - skipIcons: Boolean + val skipIcons: Boolean ) : ListAdapter( DIFF_CALLBACK_INSTALLED ) { - val skipIcons = skipIcons - fun updateData(newExtensions: List) { - submitList(newExtensions) // Use submitList instead of manual list handling + submitList(newExtensions) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -221,7 +282,14 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { return ViewHolder(view) } - @SuppressLint("SetTextI18n") + fun updatePref() { + val map = currentList.map { it.name }.toList() + PrefManager.setVal(PrefName.MangaSourcesOrder, map) + MangaSources.pinnedMangaSources = map + MangaSources.performReorderMangaSources() + } + + @SuppressLint("SetTextI18n", "ClickableViewAccessibility") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) // Use getItem() from ListAdapter val nsfw = if (extension.isNsfw) "(18+)" else "" @@ -242,11 +310,6 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { holder.settingsImageView.setOnClickListener { onSettingsClicked(extension) } - - holder.card.setOnLongClickListener { - onUninstallClicked(extension, true) - true - } } fun filter(query: String, currentList: List) { @@ -256,7 +319,8 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { filteredList.add(extension) } } - submitList(filteredList) + if (filteredList != currentList) + submitList(filteredList) } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { @@ -266,7 +330,6 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: ImageView = view.findViewById(R.id.closeTextView) - val card: View = view.findViewById(R.id.extensionCardView) } companion object { diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt index 76b84bca55..8353596d0e 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt @@ -1,5 +1,6 @@ package ani.dantotsu.settings +import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.os.Bundle @@ -14,33 +15,37 @@ import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currContext import ani.dantotsu.databinding.FragmentNovelExtensionsBinding -import ani.dantotsu.loadData import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.parsers.NovelSources import ani.dantotsu.parsers.novel.NovelExtension import ani.dantotsu.parsers.novel.NovelExtensionManager +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.launch import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Collections import java.util.Locale class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { private var _binding: FragmentNovelExtensionsBinding? = null private val binding get() = _binding!! private lateinit var extensionsRecyclerView: RecyclerView - val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) private val novelExtensionManager: NovelExtensionManager = Injekt.get() private val extensionsAdapter = NovelExtensionsAdapter( - { pkg -> + { _ -> Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT) .show() }, @@ -66,7 +71,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) Log.e("NovelExtensionsAdapter", "Error: ", error) // Log the error val builder = NotificationCompat.Builder( context, @@ -84,7 +89,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { context, Notifications.CHANNEL_DOWNLOADER_PROGRESS ) - .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) + .setSmallIcon(R.drawable.ic_check) .setContentTitle("Update complete") .setContentText("The extension has been successfully updated.") .setPriority(NotificationCompat.PRIORITY_LOW) @@ -111,37 +116,101 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) extensionsRecyclerView.adapter = extensionsAdapter + val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val newList = extensionsAdapter.currentList.toMutableList() + val fromPosition = viewHolder.absoluteAdapterPosition + val toPosition = target.absoluteAdapterPosition + if (fromPosition < toPosition) { //probably need to switch to a recyclerview adapter + for (i in fromPosition until toPosition) { + Collections.swap(newList, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(newList, i, i - 1) + } + } + extensionsAdapter.submitList(newList) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + viewHolder?.itemView?.elevation = 8f + viewHolder?.itemView?.translationZ = 8f + } + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) { + super.clearView(recyclerView, viewHolder) + extensionsAdapter.updatePref() + viewHolder.itemView.elevation = 0f + viewHolder.itemView.translationZ = 0f + } + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(extensionsRecyclerView) + lifecycleScope.launch { novelExtensionManager.installedExtensionsFlow.collect { extensions -> - extensionsAdapter.updateData(extensions) + extensionsAdapter.updateData(sortToNovelSourcesList(extensions)) } } - val extensionsRecyclerView: RecyclerView = binding.allNovelExtensionsRecyclerView return binding.root } + private fun sortToNovelSourcesList(inpt: List): List { + val sourcesMap = inpt.associateBy { it.name } + val orderedSources = NovelSources.pinnedNovelSources.mapNotNull { name -> + sourcesMap[name] + } + return orderedSources + inpt.filter { !NovelSources.pinnedNovelSources.contains(it.name) } + } + + override fun onDestroyView() { super.onDestroyView();_binding = null } override fun updateContentBasedOnQuery(query: String?) { - extensionsAdapter.filter(query ?: "", novelExtensionManager.installedExtensionsFlow.value) + extensionsAdapter.filter( + query ?: "", + sortToNovelSourcesList(novelExtensionManager.installedExtensionsFlow.value) + ) + } + + override fun notifyDataChanged() { // do nothing } private class NovelExtensionsAdapter( private val onSettingsClicked: (NovelExtension.Installed) -> Unit, private val onUninstallClicked: (NovelExtension.Installed, Boolean) -> Unit, - skipIcons: Boolean + val skipIcons: Boolean ) : ListAdapter( DIFF_CALLBACK_INSTALLED ) { - val skipIcons = skipIcons - fun updateData(newExtensions: List) { - Log.d("NovelExtensionsAdapter", "updateData: $newExtensions") - submitList(newExtensions) // Use submitList instead of manual list handling + submitList(newExtensions) + } + + fun updatePref() { + val map = currentList.map { it.name } + PrefManager.setVal(PrefName.NovelSourcesOrder, map) + NovelSources.pinnedNovelSources = map + NovelSources.performReorderNovelSources() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -151,6 +220,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { return ViewHolder(view) } + @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) // Use getItem() from ListAdapter val nsfw = "" @@ -171,7 +241,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { holder.settingsImageView.setOnClickListener { onSettingsClicked(extension) } - holder.card.setOnLongClickListener { + holder.closeTextView.setOnLongClickListener { onUninstallClicked(extension, true) true } @@ -184,7 +254,8 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { filteredList.add(extension) } } - submitList(filteredList) + if (filteredList != currentList) + submitList(filteredList) } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { @@ -194,7 +265,6 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: ImageView = view.findViewById(R.id.closeTextView) - val card = view.findViewById(R.id.extensionCardView) } companion object { diff --git a/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt index e3d14ee611..6f7276ad08 100644 --- a/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt @@ -12,13 +12,13 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentMangaExtensionsBinding import ani.dantotsu.settings.paging.MangaExtensionAdapter import ani.dantotsu.settings.paging.MangaExtensionsViewModel import ani.dantotsu.settings.paging.MangaExtensionsViewModelFactory import ani.dantotsu.settings.paging.OnMangaInstallClickListener import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.extension.manga.model.MangaExtension @@ -72,6 +72,10 @@ class MangaExtensionsFragment : Fragment(), viewModel.setSearchQuery(query ?: "") } + override fun notifyDataChanged() { + viewModel.invalidatePager() + } + override fun onInstallClick(pkg: MangaExtension.Available) { if (isAdded) { // Check if the fragment is currently added to its activity val context = requireContext() @@ -94,7 +98,7 @@ class MangaExtensionsFragment : Fragment(), notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) val builder = NotificationCompat.Builder( context, Notifications.CHANNEL_DOWNLOADER_ERROR @@ -111,7 +115,7 @@ class MangaExtensionsFragment : Fragment(), context, Notifications.CHANNEL_DOWNLOADER_PROGRESS ) - .setSmallIcon(R.drawable.ic_round_download_24) + .setSmallIcon(R.drawable.ic_download_24) .setContentTitle("Installation complete") .setContentText("The extension has been successfully installed.") .setPriority(NotificationCompat.PRIORITY_LOW) diff --git a/app/src/main/java/ani/dantotsu/settings/NovelExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/NovelExtensionsFragment.kt index a700ddedf9..ffcc17e9df 100644 --- a/app/src/main/java/ani/dantotsu/settings/NovelExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/NovelExtensionsFragment.kt @@ -13,6 +13,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R +import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentNovelExtensionsBinding import ani.dantotsu.parsers.novel.NovelExtension import ani.dantotsu.parsers.novel.NovelExtensionManager @@ -21,7 +22,6 @@ import ani.dantotsu.settings.paging.NovelExtensionsViewModel import ani.dantotsu.settings.paging.NovelExtensionsViewModelFactory import ani.dantotsu.settings.paging.OnNovelInstallClickListener import ani.dantotsu.snackString -import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -73,6 +73,10 @@ class NovelExtensionsFragment : Fragment(), viewModel.setSearchQuery(query ?: "") } + override fun notifyDataChanged() { + viewModel.invalidatePager() + } + override fun onInstallClick(pkg: NovelExtension.Available) { if (isAdded) { // Check if the fragment is currently added to its activity val context = requireContext() @@ -95,7 +99,7 @@ class NovelExtensionsFragment : Fragment(), notificationManager.notify(1, builder.build()) }, { error -> - FirebaseCrashlytics.getInstance().recordException(error) + Injekt.get().logException(error) val builder = NotificationCompat.Builder( context, Notifications.CHANNEL_DOWNLOADER_ERROR @@ -112,7 +116,7 @@ class NovelExtensionsFragment : Fragment(), context, Notifications.CHANNEL_DOWNLOADER_PROGRESS ) - .setSmallIcon(R.drawable.ic_round_download_24) + .setSmallIcon(R.drawable.ic_download_24) .setContentTitle("Installation complete") .setContentText("The extension has been successfully installed.") .setPriority(NotificationCompat.PRIORITY_LOW) diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt deleted file mode 100644 index 83037c4c42..0000000000 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt +++ /dev/null @@ -1,50 +0,0 @@ -package ani.dantotsu.settings - -import java.io.Serializable - -data class PlayerSettings( - //Video - var videoInfo: Boolean = true, - var defaultSpeed: Int = 5, - var cursedSpeeds: Boolean = false, - var resize: Int = 0, - - //Subtitles - var subtitles: Boolean = true, - var primaryColor: Int = 4, - var secondaryColor: Int = 0, - var outline: Int = 0, - var subBackground: Int = 0, - var subWindow: Int = 0, - var font: Int = 0, - var fontSize: Int = 20, - var locale: Int = 2, - - //TimeStamps - var timeStampsEnabled: Boolean = true, - var useProxyForTimeStamps: Boolean = false, - var showTimeStampButton: Boolean = true, - - //Auto - var autoSkipOPED: Boolean = false, - var autoPlay: Boolean = true, - var autoSkipFiller: Boolean = false, - - //Update Progress - var askIndividual: Boolean = true, - var updateForH: Boolean = false, - var watchPercentage: Float = 0.8f, - - //Behaviour - var alwaysContinue: Boolean = true, - var focusPause: Boolean = true, - var gestures: Boolean = true, - var doubleTap: Boolean = true, - var fastforward: Boolean = true, - var seekTime: Int = 10, - var skipTime: Int = 85, - - //Other - var cast: Boolean = true, - var pip: Boolean = true -) : Serializable diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt index 8816707909..9ae30a274c 100644 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt @@ -14,13 +14,12 @@ import androidx.core.widget.addTextChangedListener import ani.dantotsu.R import ani.dantotsu.databinding.ActivityPlayerSettingsBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.media.Media import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet import ani.dantotsu.others.getSerialized import ani.dantotsu.parsers.Subtitle -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager @@ -39,7 +38,7 @@ class PlayerSettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityPlayerSettingsBinding.inflate(layoutInflater) setContentView(binding.root) @@ -62,39 +61,12 @@ class PlayerSettingsActivity : AppCompatActivity() { bottomMargin = navBarHeight } - val settings = loadData(player, toast = false) ?: PlayerSettings().apply { - saveData( - player, - this - ) - } binding.playerSettingsBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } //Video - binding.playerSettingsVideoInfo.isChecked = settings.videoInfo - binding.playerSettingsVideoInfo.setOnCheckedChangeListener { _, isChecked -> - settings.videoInfo = isChecked - saveData(player, settings) - } - - binding.playerSettingsQualityHeight.setText( - (loadData("maxHeight", toast = false) ?: 480).toString() - ) - binding.playerSettingsQualityHeight.addTextChangedListener { - val height = binding.playerSettingsQualityHeight.text.toString().toIntOrNull() - saveData("maxHeight", height) - } - binding.playerSettingsQualityWidth.setText( - (loadData("maxWidth", toast = false) ?: 720).toString() - ) - binding.playerSettingsQualityWidth.addTextChangedListener { - val height = binding.playerSettingsQualityWidth.text.toString().toIntOrNull() - saveData("maxWidth", height) - } - val speeds = arrayOf( @@ -113,128 +85,126 @@ class PlayerSettingsActivity : AppCompatActivity() { 2f ) val cursedSpeeds = arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f) - var curSpeedArr = if (settings.cursedSpeeds) cursedSpeeds else speeds + var curSpeedArr = if (PrefManager.getVal(PrefName.CursedSpeeds)) cursedSpeeds else speeds var speedsName = curSpeedArr.map { "${it}x" }.toTypedArray() binding.playerSettingsSpeed.text = - getString(R.string.default_playback_speed, speedsName[settings.defaultSpeed]) - val speedDialog = AlertDialog.Builder(this, R.style.DialogTheme) + getString( + R.string.default_playback_speed, + speedsName[PrefManager.getVal(PrefName.DefaultSpeed)] + ) + val speedDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.default_speed)) binding.playerSettingsSpeed.setOnClickListener { val dialog = - speedDialog.setSingleChoiceItems(speedsName, settings.defaultSpeed) { dialog, i -> - settings.defaultSpeed = i + speedDialog.setSingleChoiceItems( + speedsName, + PrefManager.getVal(PrefName.DefaultSpeed) + ) { dialog, i -> + PrefManager.setVal(PrefName.DefaultSpeed, i) binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[i]) - saveData(player, settings) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) } - binding.playerSettingsCursedSpeeds.isChecked = settings.cursedSpeeds + binding.playerSettingsCursedSpeeds.isChecked = PrefManager.getVal(PrefName.CursedSpeeds) binding.playerSettingsCursedSpeeds.setOnCheckedChangeListener { _, isChecked -> - settings.cursedSpeeds = isChecked - curSpeedArr = if (settings.cursedSpeeds) cursedSpeeds else speeds - settings.defaultSpeed = if (settings.cursedSpeeds) 0 else 5 + PrefManager.setVal(PrefName.CursedSpeeds, isChecked) + curSpeedArr = if (isChecked) cursedSpeeds else speeds + val newDefaultSpeed = if (isChecked) 0 else 5 + PrefManager.setVal(PrefName.DefaultSpeed, newDefaultSpeed) speedsName = curSpeedArr.map { "${it}x" }.toTypedArray() binding.playerSettingsSpeed.text = - getString(R.string.default_playback_speed, speedsName[settings.defaultSpeed]) - saveData(player, settings) + getString( + R.string.default_playback_speed, + speedsName[PrefManager.getVal(PrefName.DefaultSpeed)] + ) } - //Time Stamp - binding.playerSettingsTimeStamps.isChecked = settings.timeStampsEnabled + + // Time Stamp + binding.playerSettingsTimeStamps.isChecked = PrefManager.getVal(PrefName.TimeStampsEnabled) binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked -> - settings.timeStampsEnabled = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.TimeStampsEnabled, isChecked) } - binding.playerSettingsTimeStampsProxy.isChecked = settings.useProxyForTimeStamps + binding.playerSettingsTimeStampsProxy.isChecked = + PrefManager.getVal(PrefName.UseProxyForTimeStamps) binding.playerSettingsTimeStampsProxy.setOnCheckedChangeListener { _, isChecked -> - settings.useProxyForTimeStamps = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.UseProxyForTimeStamps, isChecked) } - binding.playerSettingsShowTimeStamp.isChecked = settings.showTimeStampButton + binding.playerSettingsShowTimeStamp.isChecked = + PrefManager.getVal(PrefName.ShowTimeStampButton) binding.playerSettingsShowTimeStamp.setOnCheckedChangeListener { _, isChecked -> - settings.showTimeStampButton = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.ShowTimeStampButton, isChecked) } - - //Auto - binding.playerSettingsAutoSkipOpEd.isChecked = settings.autoSkipOPED + // Auto + binding.playerSettingsAutoSkipOpEd.isChecked = PrefManager.getVal(PrefName.AutoSkipOPED) binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked -> - settings.autoSkipOPED = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.AutoSkipOPED, isChecked) } - binding.playerSettingsAutoPlay.isChecked = settings.autoPlay + binding.playerSettingsAutoPlay.isChecked = PrefManager.getVal(PrefName.AutoPlay) binding.playerSettingsAutoPlay.setOnCheckedChangeListener { _, isChecked -> - settings.autoPlay = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.AutoPlay, isChecked) } - binding.playerSettingsAutoSkip.isChecked = settings.autoSkipFiller + + binding.playerSettingsAutoSkip.isChecked = PrefManager.getVal(PrefName.AutoSkipFiller) binding.playerSettingsAutoSkip.setOnCheckedChangeListener { _, isChecked -> - settings.autoSkipFiller = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.AutoSkipFiller, isChecked) } //Update Progress - binding.playerSettingsAskUpdateProgress.isChecked = settings.askIndividual + binding.playerSettingsAskUpdateProgress.isChecked = + PrefManager.getVal(PrefName.AskIndividualPlayer) binding.playerSettingsAskUpdateProgress.setOnCheckedChangeListener { _, isChecked -> - settings.askIndividual = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.AskIndividualPlayer, isChecked) } - binding.playerSettingsAskUpdateHentai.isChecked = settings.updateForH + binding.playerSettingsAskUpdateHentai.isChecked = + PrefManager.getVal(PrefName.UpdateForHPlayer) binding.playerSettingsAskUpdateHentai.setOnCheckedChangeListener { _, isChecked -> - settings.updateForH = isChecked + PrefManager.setVal(PrefName.UpdateForHPlayer, isChecked) if (isChecked) snackString(getString(R.string.very_bold)) - saveData(player, settings) } binding.playerSettingsCompletePercentage.value = - (settings.watchPercentage * 100).roundToInt().toFloat() + (PrefManager.getVal(PrefName.WatchPercentage) * 100).roundToInt().toFloat() binding.playerSettingsCompletePercentage.addOnChangeListener { _, value, _ -> - settings.watchPercentage = value / 100 - saveData(player, settings) + PrefManager.setVal(PrefName.WatchPercentage, value / 100) } //Behaviour - binding.playerSettingsAlwaysContinue.isChecked = settings.alwaysContinue + binding.playerSettingsAlwaysContinue.isChecked = PrefManager.getVal(PrefName.AlwaysContinue) binding.playerSettingsAlwaysContinue.setOnCheckedChangeListener { _, isChecked -> - settings.alwaysContinue = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.AlwaysContinue, isChecked) } - binding.playerSettingsPauseVideo.isChecked = settings.focusPause + binding.playerSettingsPauseVideo.isChecked = PrefManager.getVal(PrefName.FocusPause) binding.playerSettingsPauseVideo.setOnCheckedChangeListener { _, isChecked -> - settings.focusPause = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.FocusPause, isChecked) } - binding.playerSettingsVerticalGestures.isChecked = settings.gestures + binding.playerSettingsVerticalGestures.isChecked = PrefManager.getVal(PrefName.Gestures) binding.playerSettingsVerticalGestures.setOnCheckedChangeListener { _, isChecked -> - settings.gestures = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.Gestures, isChecked) } - binding.playerSettingsDoubleTap.isChecked = settings.doubleTap + binding.playerSettingsDoubleTap.isChecked = PrefManager.getVal(PrefName.DoubleTap) binding.playerSettingsDoubleTap.setOnCheckedChangeListener { _, isChecked -> - settings.doubleTap = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.DoubleTap, isChecked) } - binding.playerSettingsFastForward.isChecked = settings.fastforward + binding.playerSettingsFastForward.isChecked = PrefManager.getVal(PrefName.FastForward) binding.playerSettingsFastForward.setOnCheckedChangeListener { _, isChecked -> - settings.fastforward = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.FastForward, isChecked) } - binding.playerSettingsSeekTime.value = settings.seekTime.toFloat() + binding.playerSettingsSeekTime.value = PrefManager.getVal(PrefName.SeekTime).toFloat() binding.playerSettingsSeekTime.addOnChangeListener { _, value, _ -> - settings.seekTime = value.toInt() - saveData(player, settings) + PrefManager.setVal(PrefName.SeekTime, value.toInt()) } - binding.exoSkipTime.setText(settings.skipTime.toString()) + binding.exoSkipTime.setText(PrefManager.getVal(PrefName.SkipTime).toString()) binding.exoSkipTime.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { binding.exoSkipTime.clearFocus() @@ -244,8 +214,7 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.exoSkipTime.addTextChangedListener { val time = binding.exoSkipTime.text.toString().toIntOrNull() if (time != null) { - settings.skipTime = time - saveData(player, settings) + PrefManager.setVal(PrefName.SkipTime, time) } } @@ -253,32 +222,37 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.playerSettingsPiP.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { visibility = View.VISIBLE - isChecked = settings.pip + isChecked = PrefManager.getVal(PrefName.Pip) setOnCheckedChangeListener { _, isChecked -> - settings.pip = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.Pip, isChecked) } } else visibility = View.GONE } - binding.playerSettingsCast.isChecked = settings.cast + binding.playerSettingsCast.isChecked = PrefManager.getVal(PrefName.Cast) binding.playerSettingsCast.setOnCheckedChangeListener { _, isChecked -> - settings.cast = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.Cast, isChecked) + } + + binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer) + binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.RotationPlayer, isChecked) } val resizeModes = arrayOf("Original", "Zoom", "Stretch") - val resizeDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val resizeDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.default_resize_mode)) binding.playerResizeMode.setOnClickListener { - val dialog = - resizeDialog.setSingleChoiceItems(resizeModes, settings.resize) { dialog, count -> - settings.resize = count - saveData(player, settings) - dialog.dismiss() - }.show() + val dialog = resizeDialog.setSingleChoiceItems( + resizeModes, + PrefManager.getVal(PrefName.Resize) + ) { dialog, count -> + PrefManager.setVal(PrefName.Resize, count) + dialog.dismiss() + }.show() dialog.window?.setDimAmount(0.8f) } + fun restartApp() { Snackbar.make( binding.root, @@ -333,10 +307,9 @@ class PlayerSettingsActivity : AppCompatActivity() { false -> 0.5f } } - binding.subSwitch.isChecked = settings.subtitles + binding.subSwitch.isChecked = PrefManager.getVal(PrefName.Subtitles) binding.subSwitch.setOnCheckedChangeListener { _, isChecked -> - settings.subtitles = isChecked - saveData(player, settings) + PrefManager.setVal(PrefName.Subtitles, isChecked) toggleSubOptions(isChecked) restartApp() } @@ -354,15 +327,14 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val primaryColorDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val primaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.primary_sub_color)) binding.videoSubColorPrimary.setOnClickListener { val dialog = primaryColorDialog.setSingleChoiceItems( colorsPrimary, - settings.primaryColor + PrefManager.getVal(PrefName.PrimaryColor) ) { dialog, count -> - settings.primaryColor = count - saveData(player, settings) + PrefManager.setVal(PrefName.PrimaryColor, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) @@ -381,29 +353,27 @@ class PlayerSettingsActivity : AppCompatActivity() { "Magenta", "Transparent" ) - val secondaryColorDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val secondaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.outline_sub_color)) binding.videoSubColorSecondary.setOnClickListener { val dialog = secondaryColorDialog.setSingleChoiceItems( colorsSecondary, - settings.secondaryColor + PrefManager.getVal(PrefName.SecondaryColor) ) { dialog, count -> - settings.secondaryColor = count - saveData(player, settings) + PrefManager.setVal(PrefName.SecondaryColor, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) } val typesOutline = arrayOf("Outline", "Shine", "Drop Shadow", "None") - val outlineDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val outlineDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.outline_type)) binding.videoSubOutline.setOnClickListener { val dialog = outlineDialog.setSingleChoiceItems( typesOutline, - settings.outline + PrefManager.getVal(PrefName.Outline) ) { dialog, count -> - settings.outline = count - saveData(player, settings) + PrefManager.setVal(PrefName.Outline, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) @@ -422,15 +392,14 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subBackgroundDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.outline_sub_color)) binding.videoSubColorBackground.setOnClickListener { val dialog = subBackgroundDialog.setSingleChoiceItems( colorsSubBackground, - settings.subBackground + PrefManager.getVal(PrefName.SubBackground) ) { dialog, count -> - settings.subBackground = count - saveData(player, settings) + PrefManager.setVal(PrefName.SubBackground, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) @@ -450,15 +419,14 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subWindowDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.outline_sub_color)) binding.videoSubColorWindow.setOnClickListener { val dialog = subWindowDialog.setSingleChoiceItems( colorsSubWindow, - settings.subWindow + PrefManager.getVal(PrefName.SubWindow) ) { dialog, count -> - settings.subWindow = count - saveData(player, settings) + PrefManager.setVal(PrefName.SubWindow, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) @@ -471,17 +439,19 @@ class PlayerSettingsActivity : AppCompatActivity() { "Century Gothic", "Century Gothic Bold" ) - val fontDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val fontDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.subtitle_font)) binding.videoSubFont.setOnClickListener { - val dialog = fontDialog.setSingleChoiceItems(fonts, settings.font) { dialog, count -> - settings.font = count - saveData(player, settings) + val dialog = fontDialog.setSingleChoiceItems( + fonts, + PrefManager.getVal(PrefName.Font) + ) { dialog, count -> + PrefManager.setVal(PrefName.Font, count) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) } - binding.subtitleFontSize.setText(settings.fontSize.toString()) + binding.subtitleFontSize.setText(PrefManager.getVal(PrefName.FontSize).toString()) binding.subtitleFontSize.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { binding.subtitleFontSize.clearFocus() @@ -491,10 +461,9 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.subtitleFontSize.addTextChangedListener { val size = binding.subtitleFontSize.text.toString().toIntOrNull() if (size != null) { - settings.fontSize = size - saveData(player, settings) + PrefManager.setVal(PrefName.FontSize, size) } } - toggleSubOptions(settings.subtitles) + toggleSubOptions(PrefManager.getVal(PrefName.Subtitles)) } } diff --git a/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt deleted file mode 100644 index e0a91af8bb..0000000000 --- a/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ani.dantotsu.settings - -import java.io.Serializable - -data class ReaderSettings( - var showSource: Boolean = true, - var showSystemBars: Boolean = false, - - var autoDetectWebtoon: Boolean = true, - var default: CurrentReaderSettings = CurrentReaderSettings(), - var defaultLN: CurrentNovelReaderSettings = CurrentNovelReaderSettings(), - - var askIndividual: Boolean = true, - var updateForH: Boolean = false -) : Serializable \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt index 33a6ddf20e..f3846245f0 100644 --- a/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt @@ -7,20 +7,20 @@ import androidx.core.view.updateLayoutParams import ani.dantotsu.R import ani.dantotsu.databinding.ActivityReaderSettingsBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager class ReaderSettingsActivity : AppCompatActivity() { lateinit var binding: ActivityReaderSettingsBinding - private val reader = "reader_settings" + private var defaultSettings = CurrentReaderSettings() + private var defaultSettingsLN = CurrentNovelReaderSettings() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityReaderSettingsBinding.inflate(layoutInflater) setContentView(binding.root) @@ -31,34 +31,24 @@ class ReaderSettingsActivity : AppCompatActivity() { bottomMargin = navBarHeight } - val settings = loadData(reader, toast = false) ?: ReaderSettings().apply { - saveData( - reader, - this - ) - } - binding.readerSettingsBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } //Manga Settings - binding.readerSettingsSourceName.isChecked = settings.showSource + binding.readerSettingsSourceName.isChecked = PrefManager.getVal(PrefName.ShowSource) binding.readerSettingsSourceName.setOnCheckedChangeListener { _, isChecked -> - settings.showSource = isChecked - saveData(reader, settings) + PrefManager.setVal(PrefName.ShowSource, isChecked) } - binding.readerSettingsSystemBars.isChecked = settings.showSystemBars + binding.readerSettingsSystemBars.isChecked = PrefManager.getVal(PrefName.ShowSystemBars) binding.readerSettingsSystemBars.setOnCheckedChangeListener { _, isChecked -> - settings.showSystemBars = isChecked - saveData(reader, settings) + PrefManager.setVal(PrefName.ShowSystemBars, isChecked) } //Default Manga - binding.readerSettingsAutoWebToon.isChecked = settings.autoDetectWebtoon + binding.readerSettingsAutoWebToon.isChecked = PrefManager.getVal(PrefName.AutoDetectWebtoon) binding.readerSettingsAutoWebToon.setOnCheckedChangeListener { _, isChecked -> - settings.autoDetectWebtoon = isChecked - saveData(reader, settings) + PrefManager.setVal(PrefName.AutoDetectWebtoon, isChecked) } @@ -69,8 +59,8 @@ class ReaderSettingsActivity : AppCompatActivity() { ) binding.readerSettingsLayoutText.text = - resources.getStringArray(R.array.manga_layouts)[settings.default.layout.ordinal] - var selectedLayout = layoutList[settings.default.layout.ordinal] + resources.getStringArray(R.array.manga_layouts)[defaultSettings.layout.ordinal] + var selectedLayout = layoutList[defaultSettings.layout.ordinal] selectedLayout.alpha = 1f layoutList.forEachIndexed { index, imageButton -> @@ -78,25 +68,25 @@ class ReaderSettingsActivity : AppCompatActivity() { selectedLayout.alpha = 0.33f selectedLayout = imageButton selectedLayout.alpha = 1f - settings.default.layout = + defaultSettings.layout = CurrentReaderSettings.Layouts[index] ?: CurrentReaderSettings.Layouts.CONTINUOUS binding.readerSettingsLayoutText.text = - resources.getStringArray(R.array.manga_layouts)[settings.default.layout.ordinal] - saveData(reader, settings) + resources.getStringArray(R.array.manga_layouts)[defaultSettings.layout.ordinal] + PrefManager.setVal(PrefName.LayoutReader, defaultSettings.layout.ordinal) } } binding.readerSettingsDirectionText.text = - resources.getStringArray(R.array.manga_directions)[settings.default.direction.ordinal] - binding.readerSettingsDirection.rotation = 90f * (settings.default.direction.ordinal) + resources.getStringArray(R.array.manga_directions)[defaultSettings.direction.ordinal] + binding.readerSettingsDirection.rotation = 90f * (defaultSettings.direction.ordinal) binding.readerSettingsDirection.setOnClickListener { - settings.default.direction = - CurrentReaderSettings.Directions[settings.default.direction.ordinal + 1] + defaultSettings.direction = + CurrentReaderSettings.Directions[defaultSettings.direction.ordinal + 1] ?: CurrentReaderSettings.Directions.TOP_TO_BOTTOM binding.readerSettingsDirectionText.text = - resources.getStringArray(R.array.manga_directions)[settings.default.direction.ordinal] - binding.readerSettingsDirection.rotation = 90f * (settings.default.direction.ordinal) - saveData(reader, settings) + resources.getStringArray(R.array.manga_directions)[defaultSettings.direction.ordinal] + binding.readerSettingsDirection.rotation = 90f * (defaultSettings.direction.ordinal) + PrefManager.setVal(PrefName.Direction, defaultSettings.direction.ordinal) } val dualList = listOf( @@ -105,8 +95,8 @@ class ReaderSettingsActivity : AppCompatActivity() { binding.readerSettingsDualForce ) - binding.readerSettingsDualPageText.text = settings.default.dualPageMode.toString() - var selectedDual = dualList[settings.default.dualPageMode.ordinal] + binding.readerSettingsDualPageText.text = defaultSettings.dualPageMode.toString() + var selectedDual = dualList[defaultSettings.dualPageMode.ordinal] selectedDual.alpha = 1f dualList.forEachIndexed { index, imageButton -> @@ -114,75 +104,78 @@ class ReaderSettingsActivity : AppCompatActivity() { selectedDual.alpha = 0.33f selectedDual = imageButton selectedDual.alpha = 1f - settings.default.dualPageMode = CurrentReaderSettings.DualPageModes[index] + defaultSettings.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic - binding.readerSettingsDualPageText.text = settings.default.dualPageMode.toString() - saveData(reader, settings) + binding.readerSettingsDualPageText.text = defaultSettings.dualPageMode.toString() + PrefManager.setVal( + PrefName.DualPageModeReader, + defaultSettings.dualPageMode.ordinal + ) } } - binding.readerSettingsTrueColors.isChecked = settings.default.trueColors + binding.readerSettingsTrueColors.isChecked = defaultSettings.trueColors binding.readerSettingsTrueColors.setOnCheckedChangeListener { _, isChecked -> - settings.default.trueColors = isChecked - saveData(reader, settings) + defaultSettings.trueColors = isChecked + PrefManager.setVal(PrefName.TrueColors, isChecked) } - binding.readerSettingsCropBorders.isChecked = settings.default.cropBorders + binding.readerSettingsCropBorders.isChecked = defaultSettings.cropBorders binding.readerSettingsCropBorders.setOnCheckedChangeListener { _, isChecked -> - settings.default.cropBorders = isChecked - saveData(reader, settings) + defaultSettings.cropBorders = isChecked + PrefManager.setVal(PrefName.CropBorders, isChecked) } - binding.readerSettingsImageRotation.isChecked = settings.default.rotation + binding.readerSettingsImageRotation.isChecked = defaultSettings.rotation binding.readerSettingsImageRotation.setOnCheckedChangeListener { _, isChecked -> - settings.default.rotation = isChecked - saveData(reader, settings) + defaultSettings.rotation = isChecked + PrefManager.setVal(PrefName.Rotation, isChecked) } - binding.readerSettingsHorizontalScrollBar.isChecked = settings.default.horizontalScrollBar + binding.readerSettingsHorizontalScrollBar.isChecked = defaultSettings.horizontalScrollBar binding.readerSettingsHorizontalScrollBar.setOnCheckedChangeListener { _, isChecked -> - settings.default.horizontalScrollBar = isChecked - saveData(reader, settings) + defaultSettings.horizontalScrollBar = isChecked + PrefManager.setVal(PrefName.HorizontalScrollBar, isChecked) } - binding.readerSettingsPadding.isChecked = settings.default.padding + binding.readerSettingsPadding.isChecked = defaultSettings.padding binding.readerSettingsPadding.setOnCheckedChangeListener { _, isChecked -> - settings.default.padding = isChecked - saveData(reader, settings) + defaultSettings.padding = isChecked + PrefManager.setVal(PrefName.Padding, isChecked) } - binding.readerSettingsKeepScreenOn.isChecked = settings.default.keepScreenOn + binding.readerSettingsKeepScreenOn.isChecked = defaultSettings.keepScreenOn binding.readerSettingsKeepScreenOn.setOnCheckedChangeListener { _, isChecked -> - settings.default.keepScreenOn = isChecked - saveData(reader, settings) + defaultSettings.keepScreenOn = isChecked + PrefManager.setVal(PrefName.KeepScreenOn, isChecked) } - binding.readerSettingsHidePageNumbers.isChecked = settings.default.hidePageNumbers + binding.readerSettingsHidePageNumbers.isChecked = defaultSettings.hidePageNumbers binding.readerSettingsHidePageNumbers.setOnCheckedChangeListener { _, isChecked -> - settings.default.hidePageNumbers = isChecked - saveData(reader, settings) + defaultSettings.hidePageNumbers = isChecked + PrefManager.setVal(PrefName.HidePageNumbers, isChecked) } - binding.readerSettingsOverscroll.isChecked = settings.default.overScrollMode + binding.readerSettingsOverscroll.isChecked = defaultSettings.overScrollMode binding.readerSettingsOverscroll.setOnCheckedChangeListener { _, isChecked -> - settings.default.overScrollMode = isChecked - saveData(reader, settings) + defaultSettings.overScrollMode = isChecked + PrefManager.setVal(PrefName.OverScrollMode, isChecked) } - binding.readerSettingsVolumeButton.isChecked = settings.default.volumeButtons + binding.readerSettingsVolumeButton.isChecked = defaultSettings.volumeButtons binding.readerSettingsVolumeButton.setOnCheckedChangeListener { _, isChecked -> - settings.default.volumeButtons = isChecked - saveData(reader, settings) + defaultSettings.volumeButtons = isChecked + PrefManager.setVal(PrefName.VolumeButtonsReader, isChecked) } - binding.readerSettingsWrapImages.isChecked = settings.default.wrapImages + binding.readerSettingsWrapImages.isChecked = defaultSettings.wrapImages binding.readerSettingsWrapImages.setOnCheckedChangeListener { _, isChecked -> - settings.default.wrapImages = isChecked - saveData(reader, settings) + defaultSettings.wrapImages = isChecked + PrefManager.setVal(PrefName.WrapImages, isChecked) } - binding.readerSettingsLongClickImage.isChecked = settings.default.longClickImage + binding.readerSettingsLongClickImage.isChecked = defaultSettings.longClickImage binding.readerSettingsLongClickImage.setOnCheckedChangeListener { _, isChecked -> - settings.default.longClickImage = isChecked - saveData(reader, settings) + defaultSettings.longClickImage = isChecked + PrefManager.setVal(PrefName.LongClickImage, isChecked) } //LN settings @@ -191,8 +184,8 @@ class ReaderSettingsActivity : AppCompatActivity() { binding.LNcontinuous ) - binding.LNlayoutText.text = settings.defaultLN.layout.string - var selectedLN = layoutListLN[settings.defaultLN.layout.ordinal] + binding.LNlayoutText.text = defaultSettingsLN.layout.string + var selectedLN = layoutListLN[defaultSettingsLN.layout.ordinal] selectedLN.alpha = 1f layoutListLN.forEachIndexed { index, imageButton -> @@ -200,10 +193,10 @@ class ReaderSettingsActivity : AppCompatActivity() { selectedLN.alpha = 0.33f selectedLN = imageButton selectedLN.alpha = 1f - settings.defaultLN.layout = CurrentNovelReaderSettings.Layouts[index] + defaultSettingsLN.layout = CurrentNovelReaderSettings.Layouts[index] ?: CurrentNovelReaderSettings.Layouts.PAGED - binding.LNlayoutText.text = settings.defaultLN.layout.string - saveData(reader, settings) + binding.LNlayoutText.text = defaultSettingsLN.layout.string + PrefManager.setVal(PrefName.LayoutNovel, defaultSettingsLN.layout.ordinal) } } @@ -213,8 +206,8 @@ class ReaderSettingsActivity : AppCompatActivity() { binding.LNdualForce ) - binding.LNdualPageText.text = settings.defaultLN.dualPageMode.toString() - var selectedDualLN = dualListLN[settings.defaultLN.dualPageMode.ordinal] + binding.LNdualPageText.text = defaultSettingsLN.dualPageMode.toString() + var selectedDualLN = dualListLN[defaultSettingsLN.dualPageMode.ordinal] selectedDualLN.alpha = 1f dualListLN.forEachIndexed { index, imageButton -> @@ -222,143 +215,146 @@ class ReaderSettingsActivity : AppCompatActivity() { selectedDualLN.alpha = 0.33f selectedDualLN = imageButton selectedDualLN.alpha = 1f - settings.defaultLN.dualPageMode = CurrentReaderSettings.DualPageModes[index] + defaultSettingsLN.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic - binding.LNdualPageText.text = settings.defaultLN.dualPageMode.toString() - saveData(reader, settings) + binding.LNdualPageText.text = defaultSettingsLN.dualPageMode.toString() + PrefManager.setVal( + PrefName.DualPageModeNovel, + defaultSettingsLN.dualPageMode.ordinal + ) } } - binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString()) + binding.LNlineHeight.setText(defaultSettingsLN.lineHeight.toString()) binding.LNlineHeight.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) { val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f - settings.defaultLN.lineHeight = value + defaultSettingsLN.lineHeight = value binding.LNlineHeight.setText(value.toString()) - saveData(reader, settings) + PrefManager.setVal(PrefName.LineHeight, value) } } binding.LNincrementLineHeight.setOnClickListener { val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f - settings.defaultLN.lineHeight = value + 0.1f - binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString()) - saveData(reader, settings) + defaultSettingsLN.lineHeight = value + 0.1f + binding.LNlineHeight.setText(defaultSettingsLN.lineHeight.toString()) + PrefManager.setVal(PrefName.LineHeight, defaultSettingsLN.lineHeight) } binding.LNdecrementLineHeight.setOnClickListener { val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f - settings.defaultLN.lineHeight = value - 0.1f - binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString()) - saveData(reader, settings) + defaultSettingsLN.lineHeight = value - 0.1f + binding.LNlineHeight.setText(defaultSettingsLN.lineHeight.toString()) + PrefManager.setVal(PrefName.LineHeight, defaultSettingsLN.lineHeight) } - binding.LNmargin.setText(settings.defaultLN.margin.toString()) + binding.LNmargin.setText(defaultSettingsLN.margin.toString()) binding.LNmargin.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) { val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f - settings.defaultLN.margin = value + defaultSettingsLN.margin = value binding.LNmargin.setText(value.toString()) - saveData(reader, settings) + PrefManager.setVal(PrefName.Margin, value) } } binding.LNincrementMargin.setOnClickListener { val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f - settings.defaultLN.margin = value + 0.01f - binding.LNmargin.setText(settings.defaultLN.margin.toString()) - saveData(reader, settings) + defaultSettingsLN.margin = value + 0.01f + binding.LNmargin.setText(defaultSettingsLN.margin.toString()) + PrefManager.setVal(PrefName.Margin, defaultSettingsLN.margin) } binding.LNdecrementMargin.setOnClickListener { val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f - settings.defaultLN.margin = value - 0.01f - binding.LNmargin.setText(settings.defaultLN.margin.toString()) - saveData(reader, settings) + defaultSettingsLN.margin = value - 0.01f + binding.LNmargin.setText(defaultSettingsLN.margin.toString()) + PrefManager.setVal(PrefName.Margin, defaultSettingsLN.margin) } - binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString()) + binding.LNmaxInlineSize.setText(defaultSettingsLN.maxInlineSize.toString()) binding.LNmaxInlineSize.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) { val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxInlineSize = value + defaultSettingsLN.maxInlineSize = value binding.LNmaxInlineSize.setText(value.toString()) - saveData(reader, settings) + PrefManager.setVal(PrefName.MaxInlineSize, value) } } binding.LNincrementMaxInlineSize.setOnClickListener { val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxInlineSize = value + 10 - binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString()) - saveData(reader, settings) + defaultSettingsLN.maxInlineSize = value + 10 + binding.LNmaxInlineSize.setText(defaultSettingsLN.maxInlineSize.toString()) + PrefManager.setVal(PrefName.MaxInlineSize, defaultSettingsLN.maxInlineSize) } binding.LNdecrementMaxInlineSize.setOnClickListener { val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxInlineSize = value - 10 - binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString()) - saveData(reader, settings) + defaultSettingsLN.maxInlineSize = value - 10 + binding.LNmaxInlineSize.setText(defaultSettingsLN.maxInlineSize.toString()) + PrefManager.setVal(PrefName.MaxInlineSize, defaultSettingsLN.maxInlineSize) } - binding.LNmaxBlockSize.setText(settings.defaultLN.maxBlockSize.toString()) + binding.LNmaxBlockSize.setText(defaultSettingsLN.maxBlockSize.toString()) binding.LNmaxBlockSize.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) { val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxBlockSize = value + defaultSettingsLN.maxBlockSize = value binding.LNmaxBlockSize.setText(value.toString()) - saveData(reader, settings) + PrefManager.setVal(PrefName.MaxBlockSize, value) } } binding.LNincrementMaxBlockSize.setOnClickListener { val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxInlineSize = value + 10 - binding.LNmaxBlockSize.setText(settings.defaultLN.maxInlineSize.toString()) - saveData(reader, settings) + defaultSettingsLN.maxInlineSize = value + 10 + binding.LNmaxBlockSize.setText(defaultSettingsLN.maxInlineSize.toString()) + PrefManager.setVal(PrefName.MaxBlockSize, defaultSettingsLN.maxInlineSize) } binding.LNdecrementMaxBlockSize.setOnClickListener { val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720 - settings.defaultLN.maxBlockSize = value - 10 - binding.LNmaxBlockSize.setText(settings.defaultLN.maxBlockSize.toString()) - saveData(reader, settings) + defaultSettingsLN.maxBlockSize = value - 10 + binding.LNmaxBlockSize.setText(defaultSettingsLN.maxBlockSize.toString()) + PrefManager.setVal(PrefName.MaxBlockSize, defaultSettingsLN.maxBlockSize) } - binding.LNuseDarkTheme.isChecked = settings.defaultLN.useDarkTheme + binding.LNuseDarkTheme.isChecked = defaultSettingsLN.useDarkTheme binding.LNuseDarkTheme.setOnCheckedChangeListener { _, isChecked -> - settings.defaultLN.useDarkTheme = isChecked - saveData(reader, settings) + defaultSettingsLN.useDarkTheme = isChecked + PrefManager.setVal(PrefName.UseDarkThemeNovel, isChecked) } - binding.LNuseOledTheme.isChecked = settings.defaultLN.useOledTheme + binding.LNuseOledTheme.isChecked = defaultSettingsLN.useOledTheme binding.LNuseOledTheme.setOnCheckedChangeListener { _, isChecked -> - settings.defaultLN.useOledTheme = isChecked - saveData(reader, settings) + defaultSettingsLN.useOledTheme = isChecked + PrefManager.setVal(PrefName.UseOledThemeNovel, isChecked) } - binding.LNkeepScreenOn.isChecked = settings.defaultLN.keepScreenOn + binding.LNkeepScreenOn.isChecked = defaultSettingsLN.keepScreenOn binding.LNkeepScreenOn.setOnCheckedChangeListener { _, isChecked -> - settings.defaultLN.keepScreenOn = isChecked - saveData(reader, settings) + defaultSettingsLN.keepScreenOn = isChecked + PrefManager.setVal(PrefName.KeepScreenOnNovel, isChecked) } - binding.LNvolumeButton.isChecked = settings.defaultLN.volumeButtons + binding.LNvolumeButton.isChecked = defaultSettingsLN.volumeButtons binding.LNvolumeButton.setOnCheckedChangeListener { _, isChecked -> - settings.defaultLN.volumeButtons = isChecked - saveData(reader, settings) + defaultSettingsLN.volumeButtons = isChecked + PrefManager.setVal(PrefName.VolumeButtonsNovel, isChecked) } //Update Progress - binding.readerSettingsAskUpdateProgress.isChecked = settings.askIndividual + binding.readerSettingsAskUpdateProgress.isChecked = + PrefManager.getVal(PrefName.AskIndividualReader) binding.readerSettingsAskUpdateProgress.setOnCheckedChangeListener { _, isChecked -> - settings.askIndividual = isChecked - saveData(reader, settings) + PrefManager.setVal(PrefName.AskIndividualReader, isChecked) } - binding.readerSettingsAskUpdateDoujins.isChecked = settings.updateForH + binding.readerSettingsAskUpdateDoujins.isChecked = + PrefManager.getVal(PrefName.UpdateForHReader) binding.readerSettingsAskUpdateDoujins.setOnCheckedChangeListener { _, isChecked -> - settings.updateForH = isChecked + PrefManager.setVal(PrefName.UpdateForHReader, isChecked) if (isChecked) snackString(getString(R.string.very_bold)) - saveData(reader, settings) } } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index d140224b8e..44c8ac67eb 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -2,52 +2,75 @@ package ani.dantotsu.settings import android.annotation.SuppressLint import android.app.AlertDialog -import android.content.Context import android.content.Intent -import android.graphics.Color import android.graphics.drawable.Animatable -import android.os.Build.* -import android.os.Build.VERSION.* +import android.os.Build.BRAND +import android.os.Build.DEVICE +import android.os.Build.SUPPORTED_ABIS +import android.os.Build.VERSION.CODENAME +import android.os.Build.VERSION.RELEASE +import android.os.Build.VERSION.SDK_INT import android.os.Bundle +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams +import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.DownloadService -import ani.dantotsu.* +import ani.dantotsu.BuildConfig +import ani.dantotsu.R +import ani.dantotsu.Refresh import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.mal.MAL +import ani.dantotsu.copyToClipboard +import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivitySettingsBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.video.ExoplayerDownloadService +import ani.dantotsu.downloadsPermission +import ani.dantotsu.initActivity +import ani.dantotsu.loadImage +import ani.dantotsu.logger +import ani.dantotsu.navBarHeight +import ani.dantotsu.openLinkInBrowser import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.CustomBottomDialog -import ani.dantotsu.others.LangSet -import ani.dantotsu.parsers.AnimeSources -import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.pop +import ani.dantotsu.savePrefsToDownloads +import ani.dantotsu.setSafeOnClickListener +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.settings.saving.internal.Location +import ani.dantotsu.settings.saving.internal.PreferenceKeystore +import ani.dantotsu.settings.saving.internal.PreferencePackager +import ani.dantotsu.snackString +import ani.dantotsu.startMainActivity +import ani.dantotsu.statusBarHeight import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.toast import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText import eltos.simpledialogfragment.SimpleDialog import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE import eltos.simpledialogfragment.color.SimpleColorDialog import eu.kanade.domain.base.BasePreferences -import eu.kanade.tachiyomi.network.NetworkPreferences import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.Dispatchers @@ -64,20 +87,61 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } lateinit var binding: ActivitySettingsBinding private val extensionInstaller = Injekt.get().extensionInstaller() - private val networkPreferences = Injekt.get() private var cursedCounter = 0 @OptIn(UnstableApi::class) - @SuppressLint("SetTextI18n") + @SuppressLint("SetTextI18n", "Recycle") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) ThemeManager(this).applyTheme() binding = ActivitySettingsBinding.inflate(layoutInflater) setContentView(binding.root) initActivity(this) + val openDocumentLauncher = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + try { + val jsonString = contentResolver.openInputStream(uri)?.readBytes() + ?: throw Exception("Error reading file") + val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings" + //.sani is encrypted, .ani is not + if (name.endsWith(".sani")) { + passwordAlertDialog(false) { 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("Unknown file type") + } + } catch (e: Exception) { + e.printStackTrace() + toast("Error importing settings") + } + } + } + binding.settingsVersion.text = getString(R.string.version_current, BuildConfig.VERSION_NAME) binding.settingsVersion.setOnLongClickListener { copyToClipboard(getDeviceInfo(), false) @@ -96,26 +160,16 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene onBackPressedDispatcher.onBackPressed() } - binding.settingsUseMaterialYou.isChecked = - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean( - "use_material_you", - false - ) + binding.settingsUseMaterialYou.isChecked = PrefManager.getVal(PrefName.UseMaterialYou) binding.settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putBoolean("use_material_you", isChecked).apply() + PrefManager.setVal(PrefName.UseMaterialYou, isChecked) if (isChecked) binding.settingsUseCustomTheme.isChecked = false restartApp() } - binding.settingsUseCustomTheme.isChecked = - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean( - "use_custom_theme", - false - ) + binding.settingsUseCustomTheme.isChecked = PrefManager.getVal(PrefName.UseCustomTheme) binding.settingsUseCustomTheme.setOnCheckedChangeListener { _, isChecked -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putBoolean("use_custom_theme", isChecked).apply() + PrefManager.setVal(PrefName.UseCustomTheme, isChecked) if (isChecked) { binding.settingsUseMaterialYou.isChecked = false } @@ -123,26 +177,19 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene restartApp() } - binding.settingsUseSourceTheme.isChecked = - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean( - "use_source_theme", - false - ) + binding.settingsUseSourceTheme.isChecked = PrefManager.getVal(PrefName.UseSourceTheme) binding.settingsUseSourceTheme.setOnCheckedChangeListener { _, isChecked -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putBoolean("use_source_theme", isChecked).apply() + PrefManager.setVal(PrefName.UseSourceTheme, isChecked) + restartApp() } - binding.settingsUseOLED.isChecked = - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_oled", false) + binding.settingsUseOLED.isChecked = PrefManager.getVal(PrefName.UseOLED) binding.settingsUseOLED.setOnCheckedChangeListener { _, isChecked -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putBoolean("use_oled", isChecked).apply() + PrefManager.setVal(PrefName.UseOLED, isChecked) restartApp() } - val themeString = - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!! + val themeString: String = PrefManager.getVal(PrefName.Theme) binding.themeSwitcher.setText( themeString.substring(0, 1) + themeString.substring(1).lowercase() ) @@ -151,13 +198,12 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene ArrayAdapter( this, R.layout.item_dropdown, - ThemeManager.Companion.Theme.values() + ThemeManager.Companion.Theme.entries .map { it.theme.substring(0, 1) + it.theme.substring(1).lowercase() }) ) binding.themeSwitcher.setOnItemClickListener { _, _, i, _ -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putString("theme", ThemeManager.Companion.Theme.values()[i].theme).apply() + PrefManager.setVal(PrefName.Theme, ThemeManager.Companion.Theme.entries[i].theme) //ActivityHelper.shouldRefreshMainActivity = true binding.themeSwitcher.clearFocus() restartApp() @@ -166,10 +212,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene binding.customTheme.setOnClickListener { - val originalColor = getSharedPreferences("Dantotsu", MODE_PRIVATE).getInt( - "custom_theme_int", - Color.parseColor("#6200EE") - ) + val originalColor: Int = PrefManager.getVal(PrefName.CustomThemeInt) class CustomColorDialog : SimpleColorDialog() { //idk where to put it override fun onPositiveButtonClick() { @@ -190,91 +233,81 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene .show(this, tag) } - val animeSource = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getInt("settings_def_anime_source_s_r", 0) - if (AnimeSources.names.isNotEmpty() && animeSource in 0 until AnimeSources.names.size) { - binding.animeSource.setText(AnimeSources.names[animeSource], false) - - } - - binding.animeSource.setAdapter( - ArrayAdapter( - this, - R.layout.item_dropdown, - AnimeSources.names - ) - ) - - binding.animeSource.setOnItemClickListener { _, _, i, _ -> - //saveData("settings_def_anime_source_s", i) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("settings_def_anime_source_s_r", i).apply() - binding.animeSource.clearFocus() - } - - binding.settingsPinnedAnimeSources.setOnClickListener { - val animeSourcesWithoutDownloadsSource = AnimeSources.list.filter { it.name != "Downloaded" } - val names = animeSourcesWithoutDownloadsSource.map { it.name } - val pinnedSourcesBoolean = animeSourcesWithoutDownloadsSource.map { it.name in AnimeSources.pinnedAnimeSources } - val pinnedSourcesOriginal = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getStringSet("pinned_anime_sources", null) - val pinnedSources = pinnedSourcesOriginal?.toMutableSet() ?: mutableSetOf() - val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Pinned Anime Sources") - .setMultiChoiceItems( - names.toTypedArray(), - pinnedSourcesBoolean.toBooleanArray() - ) { _, which, isChecked -> - if (isChecked) { - pinnedSources.add(AnimeSources.names[which]) - } else { - pinnedSources.remove(AnimeSources.names[which]) - } - } - .setPositiveButton("OK") { dialog, _ -> - val oldDefaultSourceIndex = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getInt("settings_def_anime_source_s_r", 0) - val oldName = AnimeSources.names[oldDefaultSourceIndex] - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putStringSet("pinned_anime_sources", pinnedSources).apply() - AnimeSources.pinnedAnimeSources = pinnedSources - AnimeSources.performReorderAnimeSources() - val newDefaultSourceIndex = AnimeSources.names.indexOf(oldName) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("settings_def_anime_source_s_r", newDefaultSourceIndex).apply() - dialog.dismiss() - } - .create() - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) - } - binding.settingsPlayer.setOnClickListener { startActivity(Intent(this, PlayerSettingsActivity::class.java)) } val managers = arrayOf("Default", "1DM", "ADM") val downloadManagerDialog = - AlertDialog.Builder(this, R.style.DialogTheme).setTitle("Download Manager") - var downloadManager = loadData("settings_download_manager") ?: 0 + AlertDialog.Builder(this, R.style.MyPopup).setTitle("Download Manager") + var downloadManager: Int = PrefManager.getVal(PrefName.DownloadManager) binding.settingsDownloadManager.setOnClickListener { val dialog = downloadManagerDialog.setSingleChoiceItems( managers, downloadManager ) { dialog, count -> downloadManager = count - saveData("settings_download_manager", downloadManager) + PrefManager.setVal(PrefName.DownloadManager, downloadManager) dialog.dismiss() }.show() dialog.window?.setDimAmount(0.8f) } + binding.importExportSettings.setOnClickListener { + downloadsPermission(this) + val selectedArray = mutableListOf(false) + val filteredLocations = Location.entries.filter { it.exportable } + selectedArray.addAll(List(filteredLocations.size - 1) { false }) + val dialog = AlertDialog.Builder(this, R.style.MyPopup) + .setTitle("Import/Export Settings") + .setMultiChoiceItems( + filteredLocations.map { it.name }.toTypedArray(), + selectedArray.toBooleanArray() + ) { _, which, isChecked -> + selectedArray[which] = isChecked + } + .setPositiveButton("Import...") { dialog, _ -> + openDocumentLauncher.launch(arrayOf("*/*")) + dialog.dismiss() + } + .setNegativeButton("Export...") { dialog, _ -> + if (!selectedArray.contains(true)) { + toast("No location selected") + return@setNegativeButton + } + dialog.dismiss() + val selected = + filteredLocations.filterIndexed { index, _ -> selectedArray[index] } + if (selected.contains(Location.Protected)) { + passwordAlertDialog(true) { password -> + if (password != null) { + savePrefsToDownloads( + "DantotsuSettings", + PrefManager.exportAllPrefs(selected), + this@SettingsActivity, + password + ) + } else { + toast("Password cannot be empty") + } + } + } else { + savePrefsToDownloads( + "DantotsuSettings", + PrefManager.exportAllPrefs(selected), + this@SettingsActivity, + null + ) + } + } + .setNeutralButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + binding.purgeAnimeDownloads.setOnClickListener { val dialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle("Purge Anime Downloads") @@ -341,30 +374,29 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } } - binding.skipExtensionIcons.isChecked = loadData("skip_extension_icons") ?: false + binding.skipExtensionIcons.isChecked = PrefManager.getVal(PrefName.SkipExtensionIcons) binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked -> - saveData("skip_extension_icons", isChecked) + PrefManager.getVal(PrefName.SkipExtensionIcons, isChecked) } - binding.NSFWExtension.isChecked = loadData("NSFWExtension") ?: true + binding.NSFWExtension.isChecked = PrefManager.getVal(PrefName.NSFWExtension) binding.NSFWExtension.setOnCheckedChangeListener { _, isChecked -> - saveData("NSFWExtension", isChecked) + PrefManager.setVal(PrefName.NSFWExtension, isChecked) } binding.userAgent.setOnClickListener { val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) val editText = dialogView.findViewById(R.id.userAgentTextBox) - editText.setText(networkPreferences.defaultUserAgent().get()) + editText.setText(PrefManager.getVal(PrefName.DefaultUserAgent)) val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle("User Agent") .setView(dialogView) .setPositiveButton("OK") { dialog, _ -> - networkPreferences.defaultUserAgent().set(editText.text.toString()) + PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString()) dialog.dismiss() } .setNeutralButton("Reset") { dialog, _ -> - networkPreferences.defaultUserAgent() - .set("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0") + PrefManager.removeVal(PrefName.DefaultUserAgent) editText.setText("") dialog.dismiss() } @@ -394,139 +426,63 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene "Shecan", "Libre" ) - binding.settingsExtensionDns.setText(exDns[networkPreferences.dohProvider().get()], false) + binding.settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) binding.settingsExtensionDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, exDns)) binding.settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> - networkPreferences.dohProvider().set(i) + PrefManager.setVal(PrefName.DohProvider, i) binding.settingsExtensionDns.clearFocus() Toast.makeText(this, "Restart app to apply changes", Toast.LENGTH_LONG).show() } - binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false + binding.settingsDownloadInSd.isChecked = PrefManager.getVal(PrefName.SdDl) binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { val arrayOfFiles = ContextCompat.getExternalFilesDirs(this, null) if (arrayOfFiles.size > 1 && arrayOfFiles[1] != null) { - saveData("sd_dl", true) + PrefManager.setVal(PrefName.SdDl, true) } else { binding.settingsDownloadInSd.isChecked = false - saveData("sd_dl", false) + PrefManager.setVal(PrefName.SdDl, true) snackString(getString(R.string.noSdFound)) } - } else saveData("sd_dl", false) + } else PrefManager.setVal(PrefName.SdDl, true) } - binding.settingsContinueMedia.isChecked = loadData("continue_media") ?: true + binding.settingsContinueMedia.isChecked = PrefManager.getVal(PrefName.ContinueMedia) binding.settingsContinueMedia.setOnCheckedChangeListener { _, isChecked -> - saveData("continue_media", isChecked) + PrefManager.setVal(PrefName.ContinueMedia, isChecked) } - binding.settingsRecentlyListOnly.isChecked = loadData("recently_list_only") ?: false + binding.settingsRecentlyListOnly.isChecked = PrefManager.getVal(PrefName.RecentlyListOnly) binding.settingsRecentlyListOnly.setOnCheckedChangeListener { _, isChecked -> - saveData("recently_list_only", isChecked) + PrefManager.setVal(PrefName.RecentlyListOnly, isChecked) } - binding.settingsShareUsername.isChecked = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getBoolean("shared_user_id", true) + binding.settingsShareUsername.isChecked = PrefManager.getVal(PrefName.SharedUserID) binding.settingsShareUsername.setOnCheckedChangeListener { _, isChecked -> - getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).edit().putBoolean("shared_user_id", isChecked).apply() + PrefManager.setVal(PrefName.SharedUserID, isChecked) } - binding.settingsPreferDub.isChecked = loadData("settings_prefer_dub") ?: false + binding.settingsPreferDub.isChecked = PrefManager.getVal(PrefName.SettingsPreferDub) binding.settingsPreferDub.setOnCheckedChangeListener { _, isChecked -> - saveData("settings_prefer_dub", isChecked) - } - - //val mangaSource = loadData("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0 - val mangaSource = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getInt("settings_def_manga_source_s_r", 0) - if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) { - binding.mangaSource.setText(MangaSources.names[mangaSource], false) - } - - // Set up the dropdown adapter. - binding.mangaSource.setAdapter( - ArrayAdapter( - this, - R.layout.item_dropdown, - MangaSources.names - ) - ) - - // Set up the item click listener for the dropdown. - binding.mangaSource.setOnItemClickListener { _, _, i, _ -> - //saveData("settings_def_manga_source_s", i) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("settings_def_manga_source_s_r", i).apply() - binding.mangaSource.clearFocus() - } - - binding.settingsPinnedMangaSources.setOnClickListener { - val mangaSourcesWithoutDownloadsSource = MangaSources.list.filter { it.name != "Downloaded" } - val names = mangaSourcesWithoutDownloadsSource.map { it.name } - val pinnedSourcesBoolean = mangaSourcesWithoutDownloadsSource.map { it.name in MangaSources.pinnedMangaSources } - val pinnedSourcesOriginal = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getStringSet("pinned_manga_sources", null) - val pinnedSources = pinnedSourcesOriginal?.toMutableSet() ?: mutableSetOf() - val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Pinned Manga Sources") - .setMultiChoiceItems( - names.toTypedArray(), - pinnedSourcesBoolean.toBooleanArray() - ) { _, which, isChecked -> - if (isChecked) { - pinnedSources.add(MangaSources.names[which]) - } else { - pinnedSources.remove(MangaSources.names[which]) - } - } - .setPositiveButton("OK") { dialog, _ -> - val oldDefaultSourceIndex = getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getInt("settings_def_manga_source_s_r", 0) - val oldName = MangaSources.names[oldDefaultSourceIndex] - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putStringSet("pinned_manga_sources", pinnedSources).apply() - MangaSources.pinnedMangaSources = pinnedSources - MangaSources.performReorderMangaSources() - val newDefaultSourceIndex = MangaSources.names.indexOf(oldName) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("settings_def_manga_source_s_r", newDefaultSourceIndex).apply() - dialog.dismiss() - } - .create() - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + PrefManager.setVal(PrefName.SettingsPreferDub, isChecked) } binding.settingsReader.setOnClickListener { startActivity(Intent(this, ReaderSettingsActivity::class.java)) } - val uiSettings: UserInterfaceSettings = - loadData("ui_settings", toast = false) - ?: UserInterfaceSettings().apply { saveData("ui_settings", this) } - var previous: View = when (uiSettings.darkMode) { - null -> binding.settingsUiAuto - true -> binding.settingsUiDark - false -> binding.settingsUiLight + var previous: View = when (PrefManager.getVal(PrefName.DarkMode)) { + 0 -> binding.settingsUiAuto + 1 -> binding.settingsUiLight + 2 -> binding.settingsUiDark + else -> binding.settingsUiAuto } previous.alpha = 1f - fun uiTheme(mode: Boolean?, current: View) { + fun uiTheme(mode: Int, current: View) { previous.alpha = 0.33f previous = current current.alpha = 1f - uiSettings.darkMode = mode - saveData("ui_settings", uiSettings) + PrefManager.setVal(PrefName.DarkMode, mode) Refresh.all() finish() startActivity(Intent(this, SettingsActivity::class.java)) @@ -534,54 +490,52 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } binding.settingsUiAuto.setOnClickListener { - uiTheme(null, it) + uiTheme(0, it) } binding.settingsUiLight.setOnClickListener { binding.settingsUseOLED.isChecked = false - uiTheme(false, it) + uiTheme(1, it) } binding.settingsUiDark.setOnClickListener { - uiTheme(true, it) + uiTheme(2, it) } - var previousStart: View = when (uiSettings.defaultStartUpTab) { + var previousStart: View = when (PrefManager.getVal(PrefName.DefaultStartUpTab)) { 0 -> binding.uiSettingsAnime 1 -> binding.uiSettingsHome 2 -> binding.uiSettingsManga else -> binding.uiSettingsHome } previousStart.alpha = 1f - fun uiTheme(mode: Int, current: View) { + fun uiDefault(mode: Int, current: View) { previousStart.alpha = 0.33f previousStart = current current.alpha = 1f - uiSettings.defaultStartUpTab = mode - saveData("ui_settings", uiSettings) + PrefManager.setVal(PrefName.DefaultStartUpTab, mode) initActivity(this) } binding.uiSettingsAnime.setOnClickListener { - uiTheme(0, it) + uiDefault(0, it) } binding.uiSettingsHome.setOnClickListener { - uiTheme(1, it) + uiDefault(1, it) } binding.uiSettingsManga.setOnClickListener { - uiTheme(2, it) + uiDefault(2, it) } - binding.settingsShowYt.isChecked = uiSettings.showYtButton + binding.settingsShowYt.isChecked = PrefManager.getVal(PrefName.ShowYtButton) binding.settingsShowYt.setOnCheckedChangeListener { _, isChecked -> - uiSettings.showYtButton = isChecked - saveData("ui_settings", uiSettings) + PrefManager.setVal(PrefName.ShowYtButton, isChecked) } - var previousEp: View = when (uiSettings.animeDefaultView) { + var previousEp: View = when (PrefManager.getVal(PrefName.AnimeDefaultView)) { 0 -> binding.settingsEpList 1 -> binding.settingsEpGrid 2 -> binding.settingsEpCompact @@ -592,8 +546,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene previousEp.alpha = 0.33f previousEp = current current.alpha = 1f - uiSettings.animeDefaultView = mode - saveData("ui_settings", uiSettings) + PrefManager.setVal(PrefName.AnimeDefaultView, mode) } binding.settingsEpList.setOnClickListener { @@ -608,7 +561,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene uiEp(2, it) } - var previousChp: View = when (uiSettings.mangaDefaultView) { + var previousChp: View = when (PrefManager.getVal(PrefName.MangaDefaultView)) { 0 -> binding.settingsChpList 1 -> binding.settingsChpCompact else -> binding.settingsChpList @@ -618,8 +571,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene previousChp.alpha = 0.33f previousChp = current current.alpha = 1f - uiSettings.mangaDefaultView = mode - saveData("ui_settings", uiSettings) + PrefManager.setVal(PrefName.MangaDefaultView, mode) } binding.settingsChpList.setOnClickListener { @@ -667,13 +619,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene Toast.makeText(this, "youwu have been cuwsed :pwayge:", Toast.LENGTH_LONG).show() val url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" openLinkInBrowser(url) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean( - "use_cursed_lang", - getSharedPreferences( - "Dantotsu", - Context.MODE_PRIVATE - ).getBoolean("use_cursed_lang", false).not() - ).apply() + //PrefManager.setVal(PrefName.SomethingSpecial, !PrefManager.getVal(PrefName.SomethingSpecial, false)) } else { snackString(array[(Math.random() * array.size).toInt()], this) } @@ -701,7 +647,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } } - var curTime = loadData("subscriptions_time_s") ?: defaultTime + var curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime) val timeNames = timeMinutes.map { val mins = it % 60 val hours = it / 60 @@ -710,14 +656,14 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene }.toTypedArray() binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[curTime]) - val speedDialog = AlertDialog.Builder(this, R.style.DialogTheme) + val speedDialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(R.string.subscriptions_checking_time) binding.settingsSubscriptionsTime.setOnClickListener { val dialog = speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i -> curTime = i binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[i]) - saveData("subscriptions_time_s", curTime) + PrefManager.setVal(PrefName.SubscriptionsTimeS, curTime) dialog.dismiss() startSubscription(true) }.show() @@ -730,9 +676,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } binding.settingsNotificationsCheckingSubscriptions.isChecked = - loadData("subscription_checking_notifications") ?: true + PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked -> - saveData("subscription_checking_notifications", isChecked) + PrefManager.setVal(PrefName.SubscriptionCheckingNotifications, isChecked) if (isChecked) Notifications.createChannel( this, @@ -750,26 +696,31 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } - binding.settingsCheckUpdate.isChecked = loadData("check_update") ?: true + binding.settingsCheckUpdate.isChecked = PrefManager.getVal(PrefName.CheckUpdate) binding.settingsCheckUpdate.setOnCheckedChangeListener { _, isChecked -> - saveData("check_update", isChecked) + PrefManager.setVal(PrefName.CheckUpdate, isChecked) if (!isChecked) { snackString(getString(R.string.long_click_to_check_update)) } } - binding.settingsLogo.setOnLongClickListener { - lifecycleScope.launch(Dispatchers.IO) { - AppUpdater.check(this@SettingsActivity, true) + if (!BuildConfig.FLAVOR.contains("fdroid")) { + binding.settingsLogo.setOnLongClickListener { + lifecycleScope.launch(Dispatchers.IO) { + AppUpdater.check(this@SettingsActivity, true) + } + true } - true - } - binding.settingsCheckUpdate.setOnLongClickListener { - lifecycleScope.launch(Dispatchers.IO) { - AppUpdater.check(this@SettingsActivity, true) + binding.settingsCheckUpdate.setOnLongClickListener { + lifecycleScope.launch(Dispatchers.IO) { + AppUpdater.check(this@SettingsActivity, true) + } + true } - true + } else { + binding.settingsCheckUpdate.visibility = View.GONE + binding.settingsShareUsername.visibility = View.GONE } binding.settingsAccountHelp.setOnClickListener { @@ -791,7 +742,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene if (Anilist.token != null) { binding.settingsAnilistLogin.setText(R.string.logout) binding.settingsAnilistLogin.setOnClickListener { - Anilist.removeSavedToken(it.context) + Anilist.removeSavedToken() restartMainActivity.isEnabled = true reload() } @@ -834,18 +785,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } if (Discord.token != null) { - val id = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getString("discord_id", null) - val avatar = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getString("discord_avatar", null) - val username = getSharedPreferences( - getString(R.string.preference_file_key), - Context.MODE_PRIVATE - ).getString("discord_username", null) + val id = PrefManager.getVal(PrefName.DiscordId, null as String?) + val avatar = PrefManager.getVal(PrefName.DiscordAvatar, null as String?) + val username = PrefManager.getVal(PrefName.DiscordUserName, null as String?) if (id != null && avatar != null) { binding.settingsDiscordAvatar.loadImage("https://cdn.discordapp.com/avatars/$id/$avatar.png") } @@ -900,8 +842,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene if (which == BUTTON_POSITIVE) { if (dialogTag == "colorPicker") { val color = extras.getInt(SimpleColorDialog.COLOR) - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putInt("custom_theme_int", color).apply() + PrefManager.setVal(PrefName.CustomThemeInt, color) logger("Custom Theme: $color") } } @@ -927,6 +868,45 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } } + private fun passwordAlertDialog(isExporting: Boolean, callback: (CharArray?) -> Unit) { + val password = CharArray(16).apply { fill('0') } + + // Inflate the dialog layout + val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_user_agent, null) + dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" + val subtitleTextView = dialogView.findViewById(R.id.subtitle) + subtitleTextView?.visibility = View.VISIBLE + if (!isExporting) + subtitleTextView?.text = "Enter your password to decrypt the file" + + val dialog = AlertDialog.Builder(this, R.style.MyPopup) + .setTitle("Enter Password") + .setView(dialogView) + .setPositiveButton("OK", null) + .setNegativeButton("Cancel") { dialog, _ -> + password.fill('0') + dialog.dismiss() + callback(null) + } + .create() + + dialog.window?.setDimAmount(0.8f) + dialog.show() + + // Override the positive button here + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val editText = dialog.findViewById(R.id.userAgentTextBox) + if (editText?.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + dialog.dismiss() + callback(password) + } else { + toast("Password cannot be empty") + } + } + } + + companion object { fun getDeviceInfo(): String { return """ diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index 4834125935..7b349929dc 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -1,6 +1,5 @@ package ani.dantotsu.settings -import android.content.Context import android.content.Intent import android.graphics.Color import android.os.Bundle @@ -26,7 +25,11 @@ import ani.dantotsu.offline.OfflineFragment import ani.dantotsu.openLinkInBrowser import ani.dantotsu.others.imagesearch.ImageSearchActivity import ani.dantotsu.setSafeOnClickListener +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.startMainActivity +import java.util.Timer +import kotlin.concurrent.schedule class SettingsDialogFragment : BottomSheetDialogFragment() { private var _binding: BottomSheetSettingsBinding? = null @@ -59,7 +62,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { if (Anilist.token != null) { binding.settingsLogin.setText(R.string.logout) binding.settingsLogin.setOnClickListener { - Anilist.removeSavedToken(it.context) + Anilist.removeSavedToken() dismiss() startMainActivity(requireActivity()) } @@ -75,14 +78,10 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { } binding.settingsIncognito.isChecked = - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean( - "incognito", - false - ) ?: false + PrefManager.getVal(PrefName.Incognito) binding.settingsIncognito.setOnCheckedChangeListener { _, isChecked -> - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putBoolean("incognito", isChecked)?.apply() + PrefManager.setVal(PrefName.Incognito, isChecked) incognitoNotification(requireContext()) } binding.settingsExtensionSettings.setSafeOnClickListener { @@ -102,60 +101,59 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { dismiss() } - binding.settingsDownloads.isChecked = - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("offlineMode", false) ?: false + binding.settingsDownloads.isChecked = PrefManager.getVal(PrefName.OfflineMode) binding.settingsDownloads.setOnCheckedChangeListener { _, isChecked -> - when (pageType) { - PageType.MANGA -> { - val intent = Intent(activity, NoInternet::class.java) - intent.putExtra( - "FRAGMENT_CLASS_NAME", - OfflineMangaFragment::class.java.name - ) - startActivity(intent) + Timer().schedule(300) { + when (pageType) { + PageType.MANGA -> { + val intent = Intent(activity, NoInternet::class.java) + intent.putExtra( + "FRAGMENT_CLASS_NAME", + OfflineMangaFragment::class.java.name + ) + startActivity(intent) + } + + PageType.ANIME -> { + val intent = Intent(activity, NoInternet::class.java) + intent.putExtra( + "FRAGMENT_CLASS_NAME", + OfflineAnimeFragment::class.java.name + ) + startActivity(intent) + } + + PageType.HOME -> { + val intent = Intent(activity, NoInternet::class.java) + intent.putExtra("FRAGMENT_CLASS_NAME", OfflineFragment::class.java.name) + startActivity(intent) + } + + PageType.OfflineMANGA -> { + val intent = Intent(activity, MainActivity::class.java) + intent.putExtra("FRAGMENT_CLASS_NAME", MangaFragment::class.java.name) + startActivity(intent) + } + + PageType.OfflineHOME -> { + val intent = Intent(activity, MainActivity::class.java) + intent.putExtra( + "FRAGMENT_CLASS_NAME", + if (Anilist.token != null) HomeFragment::class.java.name else LoginFragment::class.java.name + ) + startActivity(intent) + } + + PageType.OfflineANIME -> { + val intent = Intent(activity, MainActivity::class.java) + intent.putExtra("FRAGMENT_CLASS_NAME", AnimeFragment::class.java.name) + startActivity(intent) + } } - PageType.ANIME -> { - val intent = Intent(activity, NoInternet::class.java) - intent.putExtra( - "FRAGMENT_CLASS_NAME", - OfflineAnimeFragment::class.java.name - ) - startActivity(intent) - } - - PageType.HOME -> { - val intent = Intent(activity, NoInternet::class.java) - intent.putExtra("FRAGMENT_CLASS_NAME", OfflineFragment::class.java.name) - startActivity(intent) - } - - PageType.OfflineMANGA -> { - val intent = Intent(activity, MainActivity::class.java) - intent.putExtra("FRAGMENT_CLASS_NAME", MangaFragment::class.java.name) - startActivity(intent) - } - - PageType.OfflineHOME -> { - val intent = Intent(activity, MainActivity::class.java) - intent.putExtra( - "FRAGMENT_CLASS_NAME", - if (Anilist.token != null) HomeFragment::class.java.name else LoginFragment::class.java.name - ) - startActivity(intent) - } - - PageType.OfflineANIME -> { - val intent = Intent(activity, MainActivity::class.java) - intent.putExtra("FRAGMENT_CLASS_NAME", AnimeFragment::class.java.name) - startActivity(intent) - } + dismiss() + PrefManager.setVal(PrefName.OfflineMode, isChecked) } - - dismiss() - context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit() - ?.putBoolean("offlineMode", isChecked)?.apply() } } @@ -177,4 +175,4 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { return fragment } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt deleted file mode 100644 index 39dd18b180..0000000000 --- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ani.dantotsu.settings - -import java.io.Serializable - -data class UserInterfaceSettings( - var darkMode: Boolean? = null, - var showYtButton: Boolean = true, - var animeDefaultView: Int = 0, - var mangaDefaultView: Int = 0, - - //App - var immersiveMode: Boolean = false, - var smallView: Boolean = true, - var defaultStartUpTab: Int = 1, - var homeLayoutShow: MutableList = mutableListOf( - true, - false, - false, - true, - false, - false, - true - ), - - //Animations - var bannerAnimations: Boolean = true, - var layoutAnimations: Boolean = true, - var animationSpeed: Float = 1f - -) : Serializable \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt index d3499d5c83..2c5fc60285 100644 --- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt @@ -9,10 +9,9 @@ import androidx.core.view.updateLayoutParams import ani.dantotsu.R import ani.dantotsu.databinding.ActivityUserInterfaceSettingsBinding import ani.dantotsu.initActivity -import ani.dantotsu.loadData import ani.dantotsu.navBarHeight -import ani.dantotsu.others.LangSet -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import com.google.android.material.snackbar.Snackbar @@ -22,7 +21,7 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { private val ui = "ui_settings" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater) setContentView(binding.root) @@ -33,52 +32,47 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { bottomMargin = navBarHeight } - val settings = loadData(ui, toast = false) - ?: UserInterfaceSettings().apply { saveData(ui, this) } - binding.uiSettingsBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } val views = resources.getStringArray(R.array.home_layouts) binding.uiSettingsHomeLayout.setOnClickListener { - val dialog = AlertDialog.Builder(this, R.style.DialogTheme) + val dialog = AlertDialog.Builder(this, R.style.MyPopup) .setTitle(getString(R.string.home_layout_show)).apply { setMultiChoiceItems( views, - settings.homeLayoutShow.toBooleanArray() + PrefManager.getVal>(PrefName.HomeLayoutShow).toBooleanArray() ) { _, i, value -> - settings.homeLayoutShow[i] = value - saveData(ui, settings) + val set = PrefManager.getVal>(PrefName.HomeLayoutShow) + .toMutableList() + set[i] = value + PrefManager.setVal(PrefName.HomeLayoutShow, set) } }.show() dialog.window?.setDimAmount(0.8f) } - binding.uiSettingsSmallView.isChecked = settings.smallView + binding.uiSettingsSmallView.isChecked = PrefManager.getVal(PrefName.SmallView) binding.uiSettingsSmallView.setOnCheckedChangeListener { _, isChecked -> - settings.smallView = isChecked - saveData(ui, settings) + PrefManager.setVal(PrefName.SmallView, isChecked) restartApp() } - binding.uiSettingsImmersive.isChecked = settings.immersiveMode + binding.uiSettingsImmersive.isChecked = PrefManager.getVal(PrefName.ImmersiveMode) binding.uiSettingsImmersive.setOnCheckedChangeListener { _, isChecked -> - settings.immersiveMode = isChecked - saveData(ui, settings) + PrefManager.setVal(PrefName.ImmersiveMode, isChecked) restartApp() } - binding.uiSettingsBannerAnimation.isChecked = settings.bannerAnimations + binding.uiSettingsBannerAnimation.isChecked = PrefManager.getVal(PrefName.BannerAnimations) binding.uiSettingsBannerAnimation.setOnCheckedChangeListener { _, isChecked -> - settings.bannerAnimations = isChecked - saveData(ui, settings) + PrefManager.setVal(PrefName.BannerAnimations, isChecked) restartApp() } - binding.uiSettingsLayoutAnimation.isChecked = settings.layoutAnimations + binding.uiSettingsLayoutAnimation.isChecked = PrefManager.getVal(PrefName.LayoutAnimations) binding.uiSettingsLayoutAnimation.setOnCheckedChangeListener { _, isChecked -> - settings.layoutAnimations = isChecked - saveData(ui, settings) + PrefManager.setVal(PrefName.LayoutAnimations, isChecked) restartApp() } @@ -94,10 +88,10 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { 0f to 0f ) val mapReverse = map.map { it.value to it.key }.toMap() - binding.uiSettingsAnimationSpeed.value = mapReverse[settings.animationSpeed] ?: 1f + binding.uiSettingsAnimationSpeed.value = + mapReverse[PrefManager.getVal(PrefName.AnimationSpeed)] ?: 1f binding.uiSettingsAnimationSpeed.addOnChangeListener { _, value, _ -> - settings.animationSpeed = map[value] ?: 1f - saveData(ui, settings) + PrefManager.setVal(PrefName.AnimationSpeed, map[value] ?: 1f) restartApp() } diff --git a/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt b/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt index 4caf9e21d0..c7fd31a66b 100644 --- a/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt +++ b/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt @@ -1,6 +1,7 @@ package ani.dantotsu.settings.extensionprefs import android.content.Context +import android.content.SharedPreferences import android.os.Bundle import android.util.TypedValue import androidx.core.os.bundleOf @@ -46,7 +47,7 @@ class AnimeSourcePreferencesFragment : PreferenceFragmentCompat() { onCloseAction?.invoke() } - private fun populateAnimePreferenceScreen(): PreferenceScreen { + fun populateAnimePreferenceScreen(): PreferenceScreen { val sourceId = requireArguments().getLong(SOURCE_ID) val source = Injekt.get().get(sourceId) as? ConfigurableAnimeSource ?: error("Source with id: $sourceId not found!") @@ -93,3 +94,35 @@ class AnimeSourcePreferencesFragment : PreferenceFragmentCompat() { private const val SOURCE_ID = "source_id" } } + +class InitialAnimeSourcePreferencesFragment( + val sharedPreferences: SharedPreferences, + val source: ConfigurableAnimeSource, + val currContext: Context +) : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceScreen = try { + populateAnimePreferenceScreen() + } catch (e: Exception) { + snackString(e.message ?: "Unknown error") + preferenceManager.createPreferenceScreen(requireContext()) + } + //set background color + val color = TypedValue() + requireContext().theme.resolveAttribute( + com.google.android.material.R.attr.backgroundColor, + color, + true + ) + view?.setBackgroundColor(color.data) + } + + + fun populateAnimePreferenceScreen(): PreferenceScreen { + val dataStore = SharedPreferencesDataStore(sharedPreferences) + preferenceManager.preferenceDataStore = dataStore + val sourceScreen = preferenceManager.createPreferenceScreen(requireContext()) + source.setupPreferenceScreen(sourceScreen) + return sourceScreen + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt index 46efaf5a9d..53fe43cb08 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt @@ -19,8 +19,9 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.ItemExtensionAllBinding -import ani.dantotsu.loadData import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.bumptech.glide.Glide import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension @@ -75,7 +76,9 @@ class AnimeExtensionsViewModel( prefetchDistance = 15 ) ) { - AnimeExtensionPagingSource(available, installed, query) + val aEPS = AnimeExtensionPagingSource(available, installed, query) + currentPagingSource = aEPS + aEPS }.flow }.cachedIn(viewModelScope) } @@ -92,15 +95,17 @@ class AnimeExtensionPagingSource( val availableExtensions = availableExtensionsFlow.filterNot { it.pkgName in installedExtensions } val query = searchQuery - val isNsfwEnabled: Boolean = loadData("NSFWExtension") ?: true + val isNsfwEnabled: Boolean = PrefManager.getVal(PrefName.NSFWExtension) val filteredExtensions = if (query.isEmpty()) { availableExtensions } else { availableExtensions.filter { it.name.contains(query, ignoreCase = true) } } - val filternfsw = - if (isNsfwEnabled) filteredExtensions else filteredExtensions.filterNot { it.isNsfw } + val lang: String = PrefManager.getVal(PrefName.LangSort) + val langFilter = + if (lang != "all") filteredExtensions.filter { it.lang == lang } else filteredExtensions + val filternfsw = if (isNsfwEnabled) langFilter else langFilter.filterNot { it.isNsfw } return try { val sublist = filternfsw.subList( fromIndex = position, @@ -126,7 +131,7 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen DIFF_CALLBACK ) { - private val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt index ed95c2be64..140eb549b0 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt @@ -19,8 +19,9 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.ItemExtensionAllBinding -import ani.dantotsu.loadData import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.bumptech.glide.Glide import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.extension.manga.model.MangaExtension @@ -74,7 +75,9 @@ class MangaExtensionsViewModel( prefetchDistance = 15 ) ) { - MangaExtensionPagingSource(available, installed, query) + val mEPS = MangaExtensionPagingSource(available, installed, query) + currentPagingSource = mEPS + mEPS }.flow }.cachedIn(viewModelScope) } @@ -92,14 +95,16 @@ class MangaExtensionPagingSource( val availableExtensions = availableExtensionsFlow.filterNot { it.pkgName in installedExtensions } val query = searchQuery - val isNsfwEnabled: Boolean = loadData("NSFWExtension") ?: true + val isNsfwEnabled: Boolean = PrefManager.getVal(PrefName.NSFWExtension) val filteredExtensions = if (query.isEmpty()) { availableExtensions } else { availableExtensions.filter { it.name.contains(query, ignoreCase = true) } } - val filternfsw = - if (isNsfwEnabled) filteredExtensions else filteredExtensions.filterNot { it.isNsfw } + val lang: String = PrefManager.getVal(PrefName.LangSort) + val langFilter = + if (lang != "all") filteredExtensions.filter { it.lang == lang } else filteredExtensions + val filternfsw = if (isNsfwEnabled) langFilter else langFilter.filterNot { it.isNsfw } return try { val sublist = filternfsw.subList( fromIndex = position, @@ -125,7 +130,7 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen DIFF_CALLBACK ) { - private val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt index 91dcffd600..46379b76f4 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt @@ -18,10 +18,11 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.ItemExtensionAllBinding -import ani.dantotsu.loadData import ani.dantotsu.others.LanguageMapper import ani.dantotsu.parsers.novel.NovelExtension import ani.dantotsu.parsers.novel.NovelExtensionManager +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.bumptech.glide.Glide import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -75,7 +76,9 @@ class NovelExtensionsViewModel( prefetchDistance = 15 ) ) { - NovelExtensionPagingSource(available, installed, query) + val nEPS = NovelExtensionPagingSource(available, installed, query) + currentPagingSource = nEPS + nEPS }.flow }.cachedIn(viewModelScope) } @@ -93,7 +96,7 @@ class NovelExtensionPagingSource( val availableExtensions = availableExtensionsFlow.filterNot { it.pkgName in installedExtensions } val query = searchQuery - val isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: true + val isNsfwEnabled: Boolean = PrefManager.getVal(PrefName.NSFWExtension) val filteredExtensions = if (query.isEmpty()) { availableExtensions } else { @@ -130,7 +133,7 @@ class NovelExtensionAdapter(private val clickListener: OnNovelInstallClickListen DIFF_CALLBACK ) { - private val skipIcons = loadData("skip_extension_icons") ?: false + private val skipIcons: Boolean = PrefManager.getVal(PrefName.SkipExtensionIcons) companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt new file mode 100644 index 0000000000..f66d13538c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt @@ -0,0 +1,400 @@ +package ani.dantotsu.settings.saving + +import android.content.Context +import android.content.SharedPreferences +import android.util.Base64 +import ani.dantotsu.settings.saving.internal.Compat +import ani.dantotsu.settings.saving.internal.Location +import ani.dantotsu.settings.saving.internal.PreferencePackager +import ani.dantotsu.snackString +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream + +object PrefManager { + + private var generalPreferences: SharedPreferences? = null + private var playerPreferences: SharedPreferences? = null + private var readerPreferences: SharedPreferences? = null + private var irrelevantPreferences: SharedPreferences? = null + private var animeDownloadsPreferences: SharedPreferences? = null + private var protectedPreferences: SharedPreferences? = null + + fun init(context: Context) { //must be called in Application class or will crash + generalPreferences = + context.getSharedPreferences(Location.General.location, Context.MODE_PRIVATE) + playerPreferences = + context.getSharedPreferences(Location.Player.location, Context.MODE_PRIVATE) + readerPreferences = + context.getSharedPreferences(Location.Reader.location, Context.MODE_PRIVATE) + irrelevantPreferences = + context.getSharedPreferences(Location.Irrelevant.location, Context.MODE_PRIVATE) + animeDownloadsPreferences = + context.getSharedPreferences(Location.AnimeDownloads.location, Context.MODE_PRIVATE) + protectedPreferences = + context.getSharedPreferences(Location.Protected.location, Context.MODE_PRIVATE) + Compat.importOldPrefs(context) + } + + @Suppress("UNCHECKED_CAST") + fun setVal(prefName: PrefName, value: T?) { + val pref = getPrefLocation(prefName.data.prefLocation) + with(pref.edit()) { + when (value) { + is Boolean -> putBoolean(prefName.name, value) + is Int -> putInt(prefName.name, value) + is Float -> putFloat(prefName.name, value) + is Long -> putLong(prefName.name, value) + is String -> putString(prefName.name, value) + is Set<*> -> putStringSet(prefName.name, value as Set) + null -> remove(prefName.name) + else -> serializeClass(prefName.name, value, prefName.data.prefLocation) + } + apply() + } + } + + @Suppress("UNCHECKED_CAST") + fun getVal(prefName: PrefName, default: T): T { + return try { + val pref = getPrefLocation(prefName.data.prefLocation) + when (prefName.data.type) { + Boolean::class -> pref.getBoolean(prefName.name, default as Boolean) as T + Int::class -> pref.getInt(prefName.name, default as Int) as T + Float::class -> pref.getFloat(prefName.name, default as Float) as T + Long::class -> pref.getLong(prefName.name, default as Long) as T + String::class -> pref.getString(prefName.name, default as String?) as T + Set::class -> pref.getStringSet(prefName.name, default as Set) as T + + List::class -> deserializeClass( + prefName.name, + default, + prefName.data.prefLocation + ) as T + + else -> throw IllegalArgumentException("Type not supported") + } + } catch (e: Exception) { + default + } + } + + @Suppress("UNCHECKED_CAST") + fun getVal(prefName: PrefName): T { + return try { + val pref = getPrefLocation(prefName.data.prefLocation) + when (prefName.data.type) { + Boolean::class -> pref.getBoolean( + prefName.name, + prefName.data.default as Boolean + ) as T + + Int::class -> pref.getInt(prefName.name, prefName.data.default as Int) as T + Float::class -> pref.getFloat(prefName.name, prefName.data.default as Float) as T + Long::class -> pref.getLong(prefName.name, prefName.data.default as Long) as T + String::class -> pref.getString( + prefName.name, + prefName.data.default as String? + ) as T + + Set::class -> pref.getStringSet( + prefName.name, + prefName.data.default as Set + ) as T + + List::class -> deserializeClass( + prefName.name, + prefName.data.default, + prefName.data.prefLocation + ) as T + + else -> throw IllegalArgumentException("Type not supported") + } + } catch (e: Exception) { + prefName.data.default as T + } + } + + @Suppress("UNCHECKED_CAST") + fun getNullableVal( + prefName: PrefName, + default: T? + ): T? { //Strings don't necessarily need to use this one + return try { + val pref = getPrefLocation(prefName.data.prefLocation) + when (prefName.data.type) { + Boolean::class -> pref.getBoolean(prefName.name, default as Boolean) as T? + Int::class -> pref.getInt(prefName.name, default as Int) as T? + Float::class -> pref.getFloat(prefName.name, default as Float) as T? + Long::class -> pref.getLong(prefName.name, default as Long) as T? + String::class -> pref.getString(prefName.name, default as String?) as T? + Set::class -> pref.getStringSet(prefName.name, default as Set) as T? + + else -> deserializeClass(prefName.name, default, prefName.data.prefLocation) + } + } catch (e: Exception) { + default + } + } + + @Suppress("UNCHECKED_CAST") + fun getCustomVal(key: String, default: T): T { + return try { + when (default) { + is Boolean -> irrelevantPreferences!!.getBoolean(key, default) as T + is Int -> irrelevantPreferences!!.getInt(key, default) as T + is Float -> irrelevantPreferences!!.getFloat(key, default) as T + is Long -> irrelevantPreferences!!.getLong(key, default) as T + is String -> irrelevantPreferences!!.getString(key, default) as T + is Set<*> -> irrelevantPreferences!!.getStringSet(key, default as Set) as T + else -> throw IllegalArgumentException("Type not supported") + } + } catch (e: Exception) { + default + } + } + + @Suppress("UNCHECKED_CAST") + fun getNullableCustomVal(key: String, default: T?, clazz: Class): T? { + return try { + when { + clazz.isAssignableFrom(Boolean::class.java) -> irrelevantPreferences!!.getBoolean( + key, + default as? Boolean ?: false + ) as T? + + clazz.isAssignableFrom(Int::class.java) -> irrelevantPreferences!!.getInt( + key, + default as? Int ?: 0 + ) as T? + + clazz.isAssignableFrom(Float::class.java) -> irrelevantPreferences!!.getFloat( + key, + default as? Float ?: 0f + ) as T? + + clazz.isAssignableFrom(Long::class.java) -> irrelevantPreferences!!.getLong( + key, + default as? Long ?: 0L + ) as T? + + clazz.isAssignableFrom(String::class.java) -> irrelevantPreferences!!.getString( + key, + default as? String + ) as T? + + clazz.isAssignableFrom(Set::class.java) -> irrelevantPreferences!!.getStringSet( + key, + default as? Set ?: setOf() + ) as T? + + else -> deserializeClass(key, default, Location.Irrelevant) + } + } catch (e: Exception) { + default + } + } + + + fun removeVal(prefName: PrefName) { + val pref = getPrefLocation(prefName.data.prefLocation) + with(pref.edit()) { + remove(prefName.name) + apply() + } + } + + @Suppress("UNCHECKED_CAST") + fun setCustomVal(key: String, value: T?) { + //for custom force irrelevant + with(irrelevantPreferences!!.edit()) { + when (value) { + is Boolean -> putBoolean(key, value as Boolean) + is Int -> putInt(key, value as Int) + is Float -> putFloat(key, value as Float) + is Long -> putLong(key, value as Long) + is String -> putString(key, value as String) + is Set<*> -> putStringSet(key, value as Set) + null -> remove(key) + else -> serializeClass(key, value, Location.Irrelevant) + } + apply() + } + } + + fun removeCustomVal(key: String) { + //for custom force irrelevant + with(irrelevantPreferences!!.edit()) { + remove(key) + apply() + } + } + + @Suppress("UNCHECKED_CAST") + fun getLiveVal(prefName: PrefName, default: T): SharedPreferenceLiveData { + val pref = getPrefLocation(prefName.data.prefLocation) + return when (prefName.data.type) { + Boolean::class -> SharedPreferenceBooleanLiveData( + pref, + prefName.name, + default as Boolean + ) as SharedPreferenceLiveData + + Int::class -> SharedPreferenceIntLiveData( + pref, + prefName.name, + default as Int + ) as SharedPreferenceLiveData + + Float::class -> SharedPreferenceFloatLiveData( + pref, + prefName.name, + default as Float + ) as SharedPreferenceLiveData + + Long::class -> SharedPreferenceLongLiveData( + pref, + prefName.name, + default as Long + ) as SharedPreferenceLiveData + + String::class -> SharedPreferenceStringLiveData( + pref, + prefName.name, + default as String + ) as SharedPreferenceLiveData + + Set::class -> SharedPreferenceStringSetLiveData( + pref, + prefName.name, + default as Set + ) as SharedPreferenceLiveData + + else -> throw IllegalArgumentException("Type not supported") + } + } + + fun SharedPreferenceLiveData<*>.asLiveBool(): SharedPreferenceBooleanLiveData = + this as? SharedPreferenceBooleanLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + + fun SharedPreferenceLiveData<*>.asLiveInt(): SharedPreferenceIntLiveData = + this as? SharedPreferenceIntLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + + fun SharedPreferenceLiveData<*>.asLiveFloat(): SharedPreferenceFloatLiveData = + this as? SharedPreferenceFloatLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + + fun SharedPreferenceLiveData<*>.asLiveLong(): SharedPreferenceLongLiveData = + this as? SharedPreferenceLongLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + + fun SharedPreferenceLiveData<*>.asLiveString(): SharedPreferenceStringLiveData = + this as? SharedPreferenceStringLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + + fun SharedPreferenceLiveData<*>.asLiveStringSet(): SharedPreferenceStringSetLiveData = + this as? SharedPreferenceStringSetLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData>") + + fun getAnimeDownloadPreferences(): SharedPreferences = + animeDownloadsPreferences!! //needs to be used externally + + fun exportAllPrefs(prefLocation: List): String { + return PreferencePackager.pack( + prefLocation.associateWith { getPrefLocation(it) } + ) + } + + + /** + * @param prefs Map of preferences to import + * @param prefLocation Location to import to + * @return true if successful, false if error + */ + + @Suppress("UNCHECKED_CAST") + fun importAllPrefs(prefs: Map, prefLocation: Location): Boolean { + val pref = getPrefLocation(prefLocation) + var hadError = false + pref.edit().clear().apply() + with(pref.edit()) { + prefs.forEach { (key, value) -> + when (value) { + is Boolean -> putBoolean(key, value) + is Int -> putInt(key, value) + is Float -> putFloat(key, value) + is Long -> putLong(key, value) + is String -> putString(key, value) + is HashSet<*> -> putStringSet(key, value as Set) + is ArrayList<*> -> putStringSet(key, arrayListToSet(value)) + is Set<*> -> putStringSet(key, value as Set) + else -> hadError = true + } + } + apply() + return if (hadError) { + snackString("Error importing preferences") + false + } else { + snackString("Preferences imported") + true + } + } + } + + private fun arrayListToSet(arrayList: ArrayList<*>): Set { + return arrayList.map { it.toString() }.toSet() + } + + private fun getPrefLocation(prefLoc: Location): SharedPreferences { + return when (prefLoc) { + Location.General -> generalPreferences + Location.UI -> generalPreferences + Location.Player -> playerPreferences + Location.Reader -> readerPreferences + Location.NovelReader -> readerPreferences + Location.Irrelevant -> irrelevantPreferences + Location.AnimeDownloads -> animeDownloadsPreferences + Location.Protected -> protectedPreferences + }!! + } + + private fun serializeClass(key: String, value: T, location: Location) { + val pref = getPrefLocation(location) + try { + val bos = ByteArrayOutputStream() + ObjectOutputStream(bos).use { oos -> + oos.writeObject(value) + } + + val serialized = Base64.encodeToString(bos.toByteArray(), Base64.DEFAULT) + pref.edit().putString(key, serialized).apply() + } catch (e: Exception) { + snackString("Error serializing preference: ${e.message}") + } + } + + @Suppress("UNCHECKED_CAST") + private fun deserializeClass(key: String, default: T?, location: Location): T? { + return try { + val pref = getPrefLocation(location) + val serialized = pref.getString(key, null) + if (serialized != null) { + val data = Base64.decode(serialized, Base64.DEFAULT) + val bis = ByteArrayInputStream(data) + val ois = ObjectInputStream(bis) + val obj = ois.readObject() as T? + obj + } else { + default + } + } catch (e: Exception) { + snackString("Error deserializing preference: ${e.message}") + e.printStackTrace() + default + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt new file mode 100644 index 0000000000..09027a3a14 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -0,0 +1,164 @@ +package ani.dantotsu.settings.saving + +import android.graphics.Color +import ani.dantotsu.connections.mal.MAL +import ani.dantotsu.settings.saving.internal.Location +import ani.dantotsu.settings.saving.internal.Pref + +enum class PrefName(val data: Pref) { //TODO: Split this into multiple files + //General + SharedUserID(Pref(Location.General, Boolean::class, true)), + OfflineView(Pref(Location.General, Int::class, 0)), + DownloadManager(Pref(Location.General, Int::class, 0)), + NSFWExtension(Pref(Location.General, Boolean::class, false)), + SdDl(Pref(Location.General, Boolean::class, false)), + ContinueMedia(Pref(Location.General, Boolean::class, true)), + RecentlyListOnly(Pref(Location.General, Boolean::class, false)), + SettingsPreferDub(Pref(Location.General, Boolean::class, false)), + SubscriptionsTimeS(Pref(Location.General, Int::class, 0)), + SubscriptionCheckingNotifications(Pref(Location.General, Boolean::class, true)), + CheckUpdate(Pref(Location.General, Boolean::class, true)), + VerboseLogging(Pref(Location.General, Boolean::class, false)), + DohProvider(Pref(Location.General, Int::class, 0)), + DefaultUserAgent( + Pref( + Location.General, + String::class, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0" + ) + ), + AnimeSourcesOrder(Pref(Location.General, List::class, listOf())), + AnimeSearchHistory(Pref(Location.General, Set::class, setOf())), + MangaSourcesOrder(Pref(Location.General, List::class, listOf())), + MangaSearchHistory(Pref(Location.General, Set::class, setOf())), + NovelSourcesOrder(Pref(Location.General, List::class, listOf())), + + //User Interface + UseOLED(Pref(Location.UI, Boolean::class, false)), + UseCustomTheme(Pref(Location.UI, Boolean::class, false)), + CustomThemeInt(Pref(Location.UI, Int::class, Color.parseColor("#6200EE"))), + UseSourceTheme(Pref(Location.UI, Boolean::class, false)), + UseMaterialYou(Pref(Location.UI, Boolean::class, false)), + Theme(Pref(Location.UI, String::class, "PURPLE")), + SkipExtensionIcons(Pref(Location.UI, Boolean::class, false)), + DarkMode(Pref(Location.UI, Int::class, 0)), + ShowYtButton(Pref(Location.UI, Boolean::class, true)), + AnimeDefaultView(Pref(Location.UI, Int::class, 0)), + MangaDefaultView(Pref(Location.UI, Int::class, 0)), + ImmersiveMode(Pref(Location.UI, Boolean::class, false)), + SmallView(Pref(Location.UI, Boolean::class, true)), + DefaultStartUpTab(Pref(Location.UI, Int::class, 1)), + HomeLayoutShow( + Pref( + Location.UI, + List::class, + listOf(true, false, false, true, false, false, true) + ) + ), + BannerAnimations(Pref(Location.UI, Boolean::class, true)), + LayoutAnimations(Pref(Location.UI, Boolean::class, true)), + AnimationSpeed(Pref(Location.UI, Float::class, 1f)), + ListGrid(Pref(Location.UI, Boolean::class, true)), + PopularMangaList(Pref(Location.UI, Boolean::class, true)), + PopularAnimeList(Pref(Location.UI, Boolean::class, true)), + AnimeListSortOrder(Pref(Location.UI, String::class, "score")), + MangaListSortOrder(Pref(Location.UI, String::class, "score")), + + //Player + DefaultSpeed(Pref(Location.Player, Int::class, 5)), + CursedSpeeds(Pref(Location.Player, Boolean::class, false)), + Resize(Pref(Location.Player, Int::class, 0)), + Subtitles(Pref(Location.Player, Boolean::class, true)), + PrimaryColor(Pref(Location.Player, Int::class, 4)), + SecondaryColor(Pref(Location.Player, Int::class, 0)), + Outline(Pref(Location.Player, Int::class, 0)), + SubBackground(Pref(Location.Player, Int::class, 0)), + SubWindow(Pref(Location.Player, Int::class, 0)), + Font(Pref(Location.Player, Int::class, 0)), + FontSize(Pref(Location.Player, Int::class, 20)), + Locale(Pref(Location.Player, Int::class, 2)), + TimeStampsEnabled(Pref(Location.Player, Boolean::class, true)), + UseProxyForTimeStamps(Pref(Location.Player, Boolean::class, false)), + ShowTimeStampButton(Pref(Location.Player, Boolean::class, true)), + AutoSkipOPED(Pref(Location.Player, Boolean::class, false)), + AutoPlay(Pref(Location.Player, Boolean::class, true)), + AutoSkipFiller(Pref(Location.Player, Boolean::class, false)), + AskIndividualPlayer(Pref(Location.Player, Boolean::class, true)), + UpdateForHPlayer(Pref(Location.Player, Boolean::class, false)), + WatchPercentage(Pref(Location.Player, Float::class, 0.8f)), + AlwaysContinue(Pref(Location.Player, Boolean::class, true)), + FocusPause(Pref(Location.Player, Boolean::class, true)), + Gestures(Pref(Location.Player, Boolean::class, true)), + DoubleTap(Pref(Location.Player, Boolean::class, true)), + FastForward(Pref(Location.Player, Boolean::class, true)), + SeekTime(Pref(Location.Player, Int::class, 10)), + SkipTime(Pref(Location.Player, Int::class, 85)), + Cast(Pref(Location.Player, Boolean::class, true)), + Pip(Pref(Location.Player, Boolean::class, true)), + RotationPlayer(Pref(Location.Player, Boolean::class, true)), + ContinuedAnime(Pref(Location.Player, List::class, listOf())), + + //Reader + ShowSource(Pref(Location.Reader, Boolean::class, true)), + ShowSystemBars(Pref(Location.Reader, Boolean::class, false)), + AutoDetectWebtoon(Pref(Location.Reader, Boolean::class, true)), + AskIndividualReader(Pref(Location.Reader, Boolean::class, true)), + UpdateForHReader(Pref(Location.Reader, Boolean::class, false)), + Direction(Pref(Location.Reader, Int::class, 0)), + LayoutReader(Pref(Location.Reader, Int::class, 2)), + DualPageModeReader(Pref(Location.Reader, Int::class, 1)), + OverScrollMode(Pref(Location.Reader, Boolean::class, true)), + TrueColors(Pref(Location.Reader, Boolean::class, false)), + Rotation(Pref(Location.Reader, Boolean::class, true)), + Padding(Pref(Location.Reader, Boolean::class, true)), + HidePageNumbers(Pref(Location.Reader, Boolean::class, false)), + HorizontalScrollBar(Pref(Location.Reader, Boolean::class, true)), + KeepScreenOn(Pref(Location.Reader, Boolean::class, false)), + VolumeButtonsReader(Pref(Location.Reader, Boolean::class, false)), + WrapImages(Pref(Location.Reader, Boolean::class, false)), + LongClickImage(Pref(Location.Reader, Boolean::class, true)), + CropBorders(Pref(Location.Reader, Boolean::class, false)), + CropBorderThreshold(Pref(Location.Reader, Int::class, 10)), + + //Novel Reader + CurrentThemeName(Pref(Location.NovelReader, String::class, "Default")), + LayoutNovel(Pref(Location.NovelReader, Int::class, 0)), + DualPageModeNovel(Pref(Location.NovelReader, Int::class, 1)), + LineHeight(Pref(Location.NovelReader, Float::class, 1.4f)), + Margin(Pref(Location.NovelReader, Float::class, 0.06f)), + Justify(Pref(Location.NovelReader, Boolean::class, true)), + Hyphenation(Pref(Location.NovelReader, Boolean::class, true)), + UseDarkThemeNovel(Pref(Location.NovelReader, Boolean::class, false)), + UseOledThemeNovel(Pref(Location.NovelReader, Boolean::class, false)), + Invert(Pref(Location.NovelReader, Boolean::class, false)), + MaxInlineSize(Pref(Location.NovelReader, Int::class, 720)), + MaxBlockSize(Pref(Location.NovelReader, Int::class, 1440)), + HorizontalScrollBarNovel(Pref(Location.NovelReader, Boolean::class, true)), + KeepScreenOnNovel(Pref(Location.NovelReader, Boolean::class, false)), + VolumeButtonsNovel(Pref(Location.NovelReader, Boolean::class, false)), + + //Irrelevant + Incognito(Pref(Location.Irrelevant, Boolean::class, false)), + OfflineMode(Pref(Location.Irrelevant, Boolean::class, false)), + DownloadsKeys(Pref(Location.Irrelevant, String::class, "")), + NovelLastExtCheck(Pref(Location.Irrelevant, Long::class, 0L)), + SomethingSpecial(Pref(Location.Irrelevant, Boolean::class, false)), + AllowOpeningLinks(Pref(Location.Irrelevant, Boolean::class, false)), + SearchStyle(Pref(Location.Irrelevant, Int::class, 0)), + HasUpdatedPrefs(Pref(Location.Irrelevant, Boolean::class, false)), + LangSort(Pref(Location.Irrelevant, String::class, "all")), + GenresList(Pref(Location.Irrelevant, Set::class, setOf())), + TagsListIsAdult(Pref(Location.Irrelevant, Set::class, setOf())), + TagsListNonAdult(Pref(Location.Irrelevant, Set::class, setOf())), + MakeDefault(Pref(Location.Irrelevant, Boolean::class, true)), + + //Protected + DiscordToken(Pref(Location.Protected, String::class, "")), + DiscordId(Pref(Location.Protected, String::class, "")), + DiscordUserName(Pref(Location.Protected, String::class, "")), + DiscordAvatar(Pref(Location.Protected, String::class, "")), + AnilistToken(Pref(Location.Protected, String::class, "")), + AnilistUserName(Pref(Location.Protected, String::class, "")), + MALCodeChallenge(Pref(Location.Protected, String::class, "")), + MALToken(Pref(Location.Protected, MAL.ResponseToken::class, "")), +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/SharedPreferenceLiveData.kt b/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt similarity index 99% rename from app/src/main/java/ani/dantotsu/others/SharedPreferenceLiveData.kt rename to app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt index 70c9b4f75f..037fe6f691 100644 --- a/app/src/main/java/ani/dantotsu/others/SharedPreferenceLiveData.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.others +package ani.dantotsu.settings.saving import android.content.SharedPreferences import androidx.lifecycle.LiveData diff --git a/app/src/main/java/ani/dantotsu/settings/saving/internal/Compat.kt b/app/src/main/java/ani/dantotsu/settings/saving/internal/Compat.kt new file mode 100644 index 0000000000..68392e793c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/internal/Compat.kt @@ -0,0 +1,18 @@ +package ani.dantotsu.settings.saving.internal + +import android.content.Context +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName + +class Compat { + companion object { + fun importOldPrefs(context: Context) { + if (PrefManager.getVal(PrefName.HasUpdatedPrefs)) return + val oldPrefs = context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE) + val jsonString = oldPrefs.getString("downloads_key", null) + PrefManager.setVal(PrefName.DownloadsKeys, jsonString) + oldPrefs.edit().clear().apply() + PrefManager.setVal(PrefName.HasUpdatedPrefs, true) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceInternal.kt b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceInternal.kt new file mode 100644 index 0000000000..c55f274a04 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceInternal.kt @@ -0,0 +1,21 @@ +package ani.dantotsu.settings.saving.internal + +import kotlin.reflect.KClass + + +data class Pref( + val prefLocation: Location, + val type: KClass<*>, + val default: Any +) + +enum class Location(val location: String, val exportable: Boolean) { + General("ani.dantotsu.general", true), + UI("ani.dantotsu.ui", true), + Player("ani.dantotsu.player", true), + Reader("ani.dantotsu.reader", true), + NovelReader("ani.dantotsu.novelReader", true), + Irrelevant("ani.dantotsu.irrelevant", false), + AnimeDownloads("animeDownloads", false), //different for legacy reasons + Protected("ani.dantotsu.protected", true), +} diff --git a/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceKeystore.kt b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceKeystore.kt new file mode 100644 index 0000000000..c790f9d150 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferenceKeystore.kt @@ -0,0 +1,75 @@ +package ani.dantotsu.settings.saving.internal + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec + +//used to encrypt and decrypt json strings on import and export +class PreferenceKeystore { + companion object { + fun generateKey(alias: String) { + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") + + keyGenerator.init( + KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setRandomizedEncryptionRequired(false) + .build() + ) + keyGenerator.generateKey() + } + + fun encryptWithPassword( + password: CharArray, + plaintext: String, + salt: ByteArray + ): ByteArray { + val secretKey = deriveKeyFromPassword(password, salt) + val cipher = + Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}") + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8)) + } + + fun decryptWithPassword( + password: CharArray, + ciphertext: ByteArray, + salt: ByteArray + ): String { + val secretKey = deriveKeyFromPassword(password, salt) + val cipher = + Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}") + cipher.init( + Cipher.DECRYPT_MODE, + secretKey, + IvParameterSpec(ByteArray(16)) + ) // Use the correct IV + return cipher.doFinal(ciphertext).toString(Charsets.UTF_8) + } + + fun generateSalt(): ByteArray { + val random = SecureRandom() + val salt = ByteArray(16) + random.nextBytes(salt) + return salt + } + + private fun deriveKeyFromPassword(password: CharArray, salt: ByteArray): SecretKey { + val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + val spec = PBEKeySpec(password, salt, 10000, 256) + return factory.generateSecret(spec) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt new file mode 100644 index 0000000000..d37424b757 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt @@ -0,0 +1,96 @@ +package ani.dantotsu.settings.saving.internal + +import android.content.SharedPreferences +import ani.dantotsu.settings.saving.PrefManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class PreferencePackager { + //map one or more preference maps for import/export + + companion object { + + /** + * @return a json string of the packed preferences + */ + fun pack(map: Map): String { + val prefsMap = packagePreferences(map) + val gson = Gson() + return gson.toJson(prefsMap) + } + + /** + * @return true if successful, false if error + */ + fun unpack(decryptedJson: String): Boolean { + val gson = Gson() + val type = object : + TypeToken>>>() {}.type //oh god... + val rawPrefsMap: Map>> = + gson.fromJson(decryptedJson, type) + + + val deserializedMap = mutableMapOf>() + + rawPrefsMap.forEach { (prefName, prefValueMap) -> + val innerMap = mutableMapOf() + + prefValueMap.forEach { (key, typeValueMap) -> + + val typeName = typeValueMap["type"] as? String + val value = typeValueMap["value"] + + innerMap[key] = + when (typeName) { //wierdly null sometimes so cast to string + "kotlin.Int" -> (value as? Double)?.toInt() + "kotlin.String" -> value.toString() + "kotlin.Boolean" -> value as? Boolean + "kotlin.Float" -> value.toString().toFloatOrNull() + "kotlin.Long" -> (value as? Double)?.toLong() + "java.util.HashSet" -> value as? ArrayList<*> + else -> null + } + } + deserializedMap[prefName] = innerMap + } + return unpackagePreferences(deserializedMap) + } + + /** + * @return a map of location names to a map of preference names to their values + */ + private fun packagePreferences(map: Map): Map> { + val result = mutableMapOf>() + for ((location, preferences) in map) { + val prefMap = mutableMapOf() + preferences.all.forEach { (key, value) -> + val typeValueMap = mapOf( + "type" to value?.javaClass?.kotlin?.qualifiedName, + "value" to value + ) + prefMap[key] = typeValueMap + } + result[location.name] = prefMap + } + return result + } + + /** + * @return true if successful, false if error + */ + private fun unpackagePreferences(map: Map>): Boolean { + var success = true + map.forEach { (location, prefMap) -> + val locationEnum = locationFromString(location) + if (!PrefManager.importAllPrefs(prefMap, locationEnum)) + success = false + } + return success + } + + private fun locationFromString(location: String): Location { + val loc = Location.entries.find { it.name == location } + return loc ?: throw IllegalArgumentException("Location not found") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt b/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt index a326c531de..c1c17e1feb 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt @@ -7,8 +7,9 @@ import android.content.Context import android.content.Intent import ani.dantotsu.currContext import ani.dantotsu.isOnline -import ani.dantotsu.loadData import ani.dantotsu.logger +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes @@ -43,7 +44,7 @@ class AlarmReceiver : BroadcastReceiver() { PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - val curTime = loadData("subscriptions_time_s", context) ?: defaultTime + val curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime) if (timeMinutes[curTime] > 0) alarmManager.setRepeating( diff --git a/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt b/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt index 6b6e20b2a6..410d9b2393 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt @@ -5,14 +5,14 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import ani.dantotsu.INCOGNITO_CHANNEL_ID +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName class NotificationClickReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { - context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit() - .putBoolean("incognito", false) - .apply() + PrefManager.setVal(PrefName.Incognito, false) val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(INCOGNITO_CHANNEL_ID) diff --git a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt b/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt index 83b3bd7059..050953ceba 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt @@ -8,6 +8,8 @@ import androidx.core.app.NotificationManagerCompat import ani.dantotsu.* import ani.dantotsu.parsers.Episode import ani.dantotsu.parsers.MangaChapter +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -35,13 +37,13 @@ class Subscription { currentlyPerforming = true App.context = context - val subscriptions = SubscriptionHelper.getSubscriptions(context) + val subscriptions = SubscriptionHelper.getSubscriptions() var i = 0 val index = subscriptions.map { i++; it.key to i }.toMap() val notificationManager = NotificationManagerCompat.from(context) - val progressEnabled = - loadData("subscription_checking_notifications", context) ?: true + val progressEnabled: Boolean = + PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) val progressNotification = if (progressEnabled) getProgressNotification( context, subscriptions.size diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt index 14ee72b084..1929ce3c8b 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt @@ -3,7 +3,6 @@ package ani.dantotsu.subcriptions import android.content.Context import ani.dantotsu.R import ani.dantotsu.currContext -import ani.dantotsu.loadData import ani.dantotsu.media.Media import ani.dantotsu.media.Selected import ani.dantotsu.media.manga.MangaNameAdapter @@ -15,7 +14,8 @@ import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaChapter import ani.dantotsu.parsers.MangaParser import ani.dantotsu.parsers.MangaSources -import ani.dantotsu.saveData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.tryWithSuspend import kotlinx.coroutines.withTimeoutOrNull @@ -27,23 +27,18 @@ class SubscriptionHelper { isAdult: Boolean, isAnime: Boolean ): Selected { - val sharedPreferences = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - val data = loadData("${mediaId}-select", context) ?: Selected().let { - it.sourceIndex = - if (isAdult) 0 - else if (isAnime) { - sharedPreferences.getInt("settings_def_anime_source_s_r", 0) - } else { - sharedPreferences.getInt("settings_def_manga_source_s_r", 0) + val data = + PrefManager.getNullableCustomVal("${mediaId}-select", null, Selected::class.java) + ?: Selected().let { + it.sourceIndex = 0 + it.preferDub = PrefManager.getVal(PrefName.SettingsPreferDub) + it } - it.preferDub = loadData("settings_prefer_dub", context) ?: false - it - } return data } private fun saveSelected(context: Context, mediaId: Int, data: Selected) { - saveData("$mediaId-select", data, context) + PrefManager.setCustomVal("${mediaId}-select", data) } fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser { @@ -130,12 +125,24 @@ class SubscriptionHelper { ) : java.io.Serializable private const val subscriptions = "subscriptions" - fun getSubscriptions(context: Context): Map = - loadData(subscriptions, context) - ?: mapOf().also { saveData(subscriptions, it, context) } + @Suppress("UNCHECKED_CAST") + fun getSubscriptions(): Map = + (PrefManager.getNullableCustomVal( + subscriptions, + null, + Map::class.java + ) as? Map) + ?: mapOf().also { PrefManager.setCustomVal(subscriptions, it) } + + @Suppress("UNCHECKED_CAST") fun saveSubscription(context: Context, media: Media, subscribed: Boolean) { - val data = loadData>(subscriptions, context)!!.toMutableMap() + val data = PrefManager.getNullableCustomVal( + subscriptions, + null, + Map::class.java + ) as? MutableMap + ?: mutableMapOf() if (subscribed) { if (!data.containsKey(media.id)) { val new = SubscribeMedia( @@ -150,7 +157,7 @@ class SubscriptionHelper { } else { data.remove(media.id) } - saveData(subscriptions, data, context) + PrefManager.setCustomVal(subscriptions, data) } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt index bed58b211a..00c7650f46 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt @@ -8,7 +8,8 @@ import androidx.work.NetworkType import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager import androidx.work.WorkerParameters -import ani.dantotsu.loadData +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes import kotlinx.coroutines.Dispatchers @@ -29,7 +30,7 @@ class SubscriptionWorker(val context: Context, params: WorkerParameters) : private const val SUBSCRIPTION_WORK_NAME = "work_subscription" fun enqueue(context: Context) { - val curTime = loadData("subscriptions_time_s") ?: defaultTime + val curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime) if (timeMinutes[curTime] > 0L) { val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build() diff --git a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt index 4dc1da4f1d..d30863536a 100644 --- a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt +++ b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt @@ -7,22 +7,19 @@ import android.graphics.Bitmap import android.view.Window import android.view.WindowManager import ani.dantotsu.R +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.DynamicColorsOptions class ThemeManager(private val context: Activity) { fun applyTheme(fromImage: Bitmap? = null) { - val useOLED = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("use_oled", false) && isDarkThemeActive(context) - val useCustomTheme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("use_custom_theme", false) - val customTheme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getInt("custom_theme_int", 16712221) - val useSource = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("use_source_theme", false) - val useMaterial = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("use_material_you", false) + val useOLED = PrefManager.getVal(PrefName.UseOLED) && isDarkThemeActive(context) + val useCustomTheme: Boolean = PrefManager.getVal(PrefName.UseCustomTheme) + val customTheme: Int = PrefManager.getVal(PrefName.CustomThemeInt) + val useSource: Boolean = PrefManager.getVal(PrefName.UseSourceTheme) + val useMaterial: Boolean = PrefManager.getVal(PrefName.UseMaterialYou) if (useSource) { val returnedEarly = applyDynamicColors( useMaterial, @@ -40,8 +37,7 @@ class ThemeManager(private val context: Activity) { val returnedEarly = applyDynamicColors(useMaterial, context, useOLED, useCustom = null) if (!returnedEarly) return } - val theme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getString("theme", "PURPLE")!! + val theme: String = PrefManager.getVal(PrefName.Theme) val themeToApply = when (theme) { "BLUE" -> if (useOLED) R.style.Theme_Dantotsu_BlueOLED else R.style.Theme_Dantotsu_Blue diff --git a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringWidgetConfigureActivity.kt b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringWidgetConfigureActivity.kt index 79b928f547..186f5151f1 100644 --- a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringWidgetConfigureActivity.kt +++ b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringWidgetConfigureActivity.kt @@ -9,7 +9,6 @@ import android.view.View import android.widget.EditText import ani.dantotsu.R import ani.dantotsu.databinding.CurrentlyAiringWidgetConfigureBinding -import ani.dantotsu.others.LangSet import ani.dantotsu.themes.ThemeManager /** @@ -46,7 +45,7 @@ class CurrentlyAiringWidgetConfigureActivity : Activity() { private lateinit var binding: CurrentlyAiringWidgetConfigureBinding public override fun onCreate(icicle: Bundle?) { - LangSet.setLocale(this) + ThemeManager(this).applyTheme() super.onCreate(icicle) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt index 38e4d7911b..2b66f10dd0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.anime.util import android.app.Activity import android.content.Intent import android.os.Bundle -import ani.dantotsu.others.LangSet import ani.dantotsu.themes.ThemeManager import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -26,7 +25,7 @@ class AnimeExtensionInstallActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt index e70cd5e5bb..87e6ab6abb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt @@ -24,7 +24,7 @@ class AnimeExtensionInstallService : Service() { override fun onCreate() { val notification = notificationBuilder(Notifications.CHANNEL_EXTENSIONS_UPDATE) { - setSmallIcon(R.drawable.spinner_icon) + setSmallIcon(R.drawable.ic_download_24) setAutoCancel(false) setOngoing(true) setShowWhen(false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt index 0ba16d8424..7424bd3fc0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.manga.util import android.app.Activity import android.content.Intent import android.os.Bundle -import ani.dantotsu.others.LangSet import ani.dantotsu.themes.ThemeManager import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager @@ -26,7 +25,7 @@ class MangaExtensionInstallActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - LangSet.setLocale(this) + ThemeManager(this).applyTheme() val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt index bab1893069..f6cc24cc30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt @@ -24,7 +24,7 @@ class MangaExtensionInstallService : Service() { override fun onCreate() { val notification = notificationBuilder(Notifications.CHANNEL_EXTENSIONS_UPDATE) { - setSmallIcon(R.drawable.spinner_icon) + setSmallIcon(R.drawable.ic_download_24) setAutoCancel(false) setOngoing(true) setShowWhen(false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index e73b97d2e1..4d14f6493f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.network import android.content.Context import android.os.Build import ani.dantotsu.Mapper +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import com.lagradost.nicehttp.Requests import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor @@ -14,8 +16,7 @@ import java.io.File import java.util.concurrent.TimeUnit class NetworkHelper( - context: Context, - private val preferences: NetworkPreferences, + context: Context ) { private val cacheDir = File(context.cacheDir, "network_cache") @@ -30,23 +31,23 @@ class NetworkHelper( CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider) } - private fun baseClientBuilder(callTimout: Int = 2): OkHttpClient.Builder { + private fun baseClientBuilder(callTimeout: Int = 2): OkHttpClient.Builder { val builder = OkHttpClient.Builder() .cookieJar(cookieJar) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) - .callTimeout(callTimout.toLong(), TimeUnit.MINUTES) + .callTimeout(callTimeout.toLong(), TimeUnit.MINUTES) .addInterceptor(UncaughtExceptionInterceptor()) .addInterceptor(userAgentInterceptor) - if (preferences.verboseLogging().get()) { + if (PrefManager.getVal(PrefName.VerboseLogging)) { val httpLoggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.HEADERS } builder.addNetworkInterceptor(httpLoggingInterceptor) } - when (preferences.dohProvider().get()) { + when (PrefManager.getVal(PrefName.DohProvider)) { PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() PREF_DOH_GOOGLE -> builder.dohGoogle() PREF_DOH_ADGUARD -> builder.dohAdGuard() @@ -88,5 +89,5 @@ class NetworkHelper( responseParser = Mapper ) - fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim() + fun defaultUserAgentProvider() = PrefManager.getVal(PrefName.DefaultUserAgent) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt deleted file mode 100644 index 1dce7afdbb..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.network - -import tachiyomi.core.preference.Preference -import tachiyomi.core.preference.PreferenceStore - -class NetworkPreferences( - private val preferenceStore: PreferenceStore, - private val verboseLogging: Boolean = false, -) { - - fun verboseLogging(): Preference { - return preferenceStore.getBoolean("verbose_logging", verboseLogging) - } - - fun dohProvider(): Preference { - return preferenceStore.getInt("doh_provider", 0) - } - - fun defaultUserAgent(): Preference { - return preferenceStore.getString( - "default_user_agent", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0" - ) - } -} diff --git a/app/src/main/res/anim/over_shoot.xml b/app/src/main/res/anim/over_shoot.xml index bd86f9ac23..af9b71c09c 100644 --- a/app/src/main/res/anim/over_shoot.xml +++ b/app/src/main/res/anim/over_shoot.xml @@ -1,3 +1,3 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml index 2ea726e0a8..f546ff431d 100644 --- a/app/src/main/res/anim/slide_down.xml +++ b/app/src/main/res/anim/slide_down.xml @@ -1,7 +1,7 @@ + android:toYDelta="100%" /> diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml index 616a20a3e9..b793a77151 100644 --- a/app/src/main/res/anim/slide_up.xml +++ b/app/src/main/res/anim/slide_up.xml @@ -1,7 +1,7 @@ + android:toYDelta="0%" /> diff --git a/app/src/main/res/color/button_switch_track.xml b/app/src/main/res/color/button_switch_track.xml index b6b00672a3..f169ede01e 100644 --- a/app/src/main/res/color/button_switch_track.xml +++ b/app/src/main/res/color/button_switch_track.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_background_color.xml b/app/src/main/res/color/chip_background_color.xml index b08380027a..def90acaec 100644 --- a/app/src/main/res/color/chip_background_color.xml +++ b/app/src/main/res/color/chip_background_color.xml @@ -1,8 +1,8 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/color/chip_text_color.xml b/app/src/main/res/color/chip_text_color.xml index fbe928c363..0904b084d5 100644 --- a/app/src/main/res/color/chip_text_color.xml +++ b/app/src/main/res/color/chip_text_color.xml @@ -1,7 +1,7 @@ - + - + diff --git a/app/src/main/res/color/tab_layout_icon.xml b/app/src/main/res/color/tab_layout_icon.xml index f110230983..4cdbe759d6 100644 --- a/app/src/main/res/color/tab_layout_icon.xml +++ b/app/src/main/res/color/tab_layout_icon.xml @@ -1,5 +1,5 @@ - - + + diff --git a/app/src/main/res/color/tab_layout_text.xml b/app/src/main/res/color/tab_layout_text.xml index 905c345cfe..cdd181823c 100644 --- a/app/src/main/res/color/tab_layout_text.xml +++ b/app/src/main/res/color/tab_layout_text.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/color/text_input_layout_stroke_color.xml b/app/src/main/res/color/text_input_layout_stroke_color.xml index db77cba809..7055cb6a48 100644 --- a/app/src/main/res/color/text_input_layout_stroke_color.xml +++ b/app/src/main/res/color/text_input_layout_stroke_color.xml @@ -1,7 +1,7 @@ - + - + diff --git a/app/src/main/res/drawable/anim_pause_to_play.xml b/app/src/main/res/drawable/anim_pause_to_play.xml index 80782ef2a1..c9760881d5 100644 --- a/app/src/main/res/drawable/anim_pause_to_play.xml +++ b/app/src/main/res/drawable/anim_pause_to_play.xml @@ -1,5 +1,4 @@ - + android:fillColor="#fff" + android:pathData="M 7 6 C 5.9 6 5 6.9 5 8 L 5 8 C 5 9.1 5.9 10 7 10 L 12 10 L 17 10 C 18.1 10 19 9.1 19 8 C 19 6.9 18.1 6 17 6 C 13.667 6 10.333 6 7 6 M 7 14 C 5.9 14 5 14.9 5 16 C 5 17.1 5.9 18 7 18 L 17 18 C 18.1 18 19 17.1 19 16 C 19 14.9 18.1 14 17 14 L 17 14 L 12 14 L 7 14" /> @@ -28,54 +27,54 @@ + android:valueType="pathType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/anim_play_to_pause.xml b/app/src/main/res/drawable/anim_play_to_pause.xml index dc8138b0d0..05b7158696 100644 --- a/app/src/main/res/drawable/anim_play_to_pause.xml +++ b/app/src/main/res/drawable/anim_play_to_pause.xml @@ -1,5 +1,4 @@ - + android:fillColor="#fff" + android:pathData="M 8 6.82 L 8 17.18 C 8 17.97 8.87 18.45 9.54 18.02 L 17.68 12.84 C 18.3 12.45 18.3 11.55 17.68 11.15 L 9.54 5.98 C 8.87 5.55 8 6.03 8 6.82 Z" /> + android:valueType="pathType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/anim_rewind.xml b/app/src/main/res/drawable/anim_rewind.xml index de77a5a39c..10a32d2d1d 100644 --- a/app/src/main/res/drawable/anim_rewind.xml +++ b/app/src/main/res/drawable/anim_rewind.xml @@ -1,5 +1,4 @@ - + android:fillColor="#eeeeee" + android:pathData="M 28.3 14.9 L 5.7 0.7 C 3.2 -0.8 0 1 0 3.9 L 0 32.3 C 0 35.2 3.2 37 5.7 35.4 L 28.3 21.2 C 30.6 19.8 30.6 16.4 28.3 14.9 Z" /> + android:fillColor="#eeeeee" + android:pathData="M 52.3 14.9 L 29.7 0.7 C 27.2 -0.8 24 1 24 3.9 L 24 32.3 C 24 35.2 27.2 37 29.7 35.4 L 52.3 21.2 C 54.6 19.8 54.6 16.4 52.3 14.9 Z" /> + android:fillColor="#eeeeee" + android:pathData="M 28.3 14.9 L 5.7 0.7 C 3.2 -0.8 0 1 0 3.9 L 0 32.3 C 0 35.2 3.2 37 5.7 35.4 L 28.3 21.2 C 30.6 19.8 30.6 16.4 28.3 14.9 Z" /> @@ -45,78 +44,78 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/anim_skip.xml b/app/src/main/res/drawable/anim_skip.xml index 3dc6dad468..922216e8f1 100644 --- a/app/src/main/res/drawable/anim_skip.xml +++ b/app/src/main/res/drawable/anim_skip.xml @@ -1,5 +1,4 @@ - + android:fillColor="#eeeeee" + android:pathData="M 28.3 14.9 L 5.7 0.7 C 3.2 -0.8 0 1 0 3.9 L 0 32.3 C 0 35.2 3.2 37 5.7 35.4 L 28.3 21.2 C 30.6 19.8 30.6 16.4 28.3 14.9 Z" /> + android:fillColor="#eeeeee" + android:pathData="M 52.3 14.9 L 29.7 0.7 C 27.2 -0.8 24 1 24 3.9 L 24 32.3 C 24 35.2 27.2 37 29.7 35.4 L 52.3 21.2 C 54.6 19.8 54.6 16.4 52.3 14.9 Z" /> + android:fillColor="#eeeeee" + android:pathData="M 28.3 14.9 L 5.7 0.7 C 3.2 -0.8 0 1 0 3.9 L 0 32.3 C 0 35.2 3.2 37 5.7 35.4 L 28.3 21.2 C 30.6 19.8 30.6 16.4 28.3 14.9 Z" /> @@ -45,78 +44,78 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/anim_splash.xml b/app/src/main/res/drawable/anim_splash.xml index f841df4f16..987094b359 100644 --- a/app/src/main/res/drawable/anim_splash.xml +++ b/app/src/main/res/drawable/anim_splash.xml @@ -1,5 +1,4 @@ - + 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 128 128 L 640 128 L 640 639.96 L 128 639.96 Z" + 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" /> @@ -43,12 +42,12 @@ android:rotation="-90"> + 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" /> + 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 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 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" /> + android:scaleY="1.2" /> + 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" /> @@ -100,9 +99,9 @@ android:translateX="-360"> + 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" /> + 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" /> + android:scaleY="3"> + 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" /> @@ -138,19 +137,19 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -158,177 +157,177 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -336,19 +335,19 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> @@ -356,21 +355,21 @@ + android:valueType="floatType" /> + android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/bottom_nav.xml b/app/src/main/res/drawable/bottom_nav.xml index b42dd3ce48..560d286dc0 100644 --- a/app/src/main/res/drawable/bottom_nav.xml +++ b/app/src/main/res/drawable/bottom_nav.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_nav_gray.xml b/app/src/main/res/drawable/bottom_nav_gray.xml index b42dd3ce48..560d286dc0 100644 --- a/app/src/main/res/drawable/bottom_nav_gray.xml +++ b/app/src/main/res/drawable/bottom_nav_gray.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_sheet_background.xml b/app/src/main/res/drawable/bottom_sheet_background.xml new file mode 100644 index 0000000000..77a184e8dc --- /dev/null +++ b/app/src/main/res/drawable/bottom_sheet_background.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/card_outline.xml b/app/src/main/res/drawable/card_outline.xml index 7035667408..ad2e92535f 100644 --- a/app/src/main/res/drawable/card_outline.xml +++ b/app/src/main/res/drawable/card_outline.xml @@ -1,15 +1,19 @@ - + - - + + - + - - + + diff --git a/app/src/main/res/drawable/ic_anilist.xml b/app/src/main/res/drawable/ic_anilist.xml index adc02990f3..14223c58dd 100644 --- a/app/src/main/res/drawable/ic_anilist.xml +++ b/app/src/main/res/drawable/ic_anilist.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="23.97"> + diff --git a/app/src/main/res/drawable/ic_bmc_button.xml b/app/src/main/res/drawable/ic_bmc_button.xml index 4d4032487d..938f634406 100644 --- a/app/src/main/res/drawable/ic_bmc_button.xml +++ b/app/src/main/res/drawable/ic_bmc_button.xml @@ -1,23 +1,71 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml index f97e17d559..e54f665754 100644 --- a/app/src/main/res/drawable/ic_check.xml +++ b/app/src/main/res/drawable/ic_check.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="960" android:viewportHeight="960"> - + diff --git a/app/src/main/res/drawable/ic_circle_add.xml b/app/src/main/res/drawable/ic_circle_add.xml index 4f65263e22..ae35e9219d 100644 --- a/app/src/main/res/drawable/ic_circle_add.xml +++ b/app/src/main/res/drawable/ic_circle_add.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_circle_cancel.xml b/app/src/main/res/drawable/ic_circle_cancel.xml index 72bb239bc1..e92bf6aa8c 100644 --- a/app/src/main/res/drawable/ic_circle_cancel.xml +++ b/app/src/main/res/drawable/ic_circle_cancel.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_circle_check.xml b/app/src/main/res/drawable/ic_circle_check.xml index ea65d8d2ab..86bd6bdc2b 100644 --- a/app/src/main/res/drawable/ic_circle_check.xml +++ b/app/src/main/res/drawable/ic_circle_check.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_dantotsu_round.xml b/app/src/main/res/drawable/ic_dantotsu_round.xml index 5b43bb3443..bff8123c9e 100644 --- a/app/src/main/res/drawable/ic_dantotsu_round.xml +++ b/app/src/main/res/drawable/ic_dantotsu_round.xml @@ -3,40 +3,39 @@ android:height="768dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_discord.xml b/app/src/main/res/drawable/ic_discord.xml index 5aead85f94..38c7d75947 100644 --- a/app/src/main/res/drawable/ic_discord.xml +++ b/app/src/main/res/drawable/ic_discord.xml @@ -3,9 +3,9 @@ android:height="24dp" android:viewportWidth="500" android:viewportHeight="500"> - + diff --git a/app/src/main/res/drawable/ic_download_24.xml b/app/src/main/res/drawable/ic_download_24.xml new file mode 100644 index 0000000000..8c8a51d7dc --- /dev/null +++ b/app/src/main/res/drawable/ic_download_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_extension.xml b/app/src/main/res/drawable/ic_extension.xml index 386c47bf1c..66263c4332 100644 --- a/app/src/main/res/drawable/ic_extension.xml +++ b/app/src/main/res/drawable/ic_extension.xml @@ -1,35 +1,35 @@ - + - + - + - + + C421.979,233.961,409.193,221.176,393.479,221.176z" /> diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml index 215402e61c..acf374f9c6 100644 --- a/app/src/main/res/drawable/ic_github.xml +++ b/app/src/main/res/drawable/ic_github.xml @@ -1,4 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_incognito_24.xml b/app/src/main/res/drawable/ic_incognito_24.xml index 752403ad9a..2dd4092556 100644 --- a/app/src/main/res/drawable/ic_incognito_24.xml +++ b/app/src/main/res/drawable/ic_incognito_24.xml @@ -1,6 +1,12 @@ - - + + android:strokeWidth="1" + android:strokeColor="#00000000" /> diff --git a/app/src/main/res/drawable/ic_internet.xml b/app/src/main/res/drawable/ic_internet.xml index e7b4b2cc75..3b7123a03b 100644 --- a/app/src/main/res/drawable/ic_internet.xml +++ b/app/src/main/res/drawable/ic_internet.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_launcher_alpha_background.xml b/app/src/main/res/drawable/ic_launcher_alpha_background.xml new file mode 100644 index 0000000000..1696d66207 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_alpha_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_alpha_foreground.xml b/app/src/main/res/drawable/ic_launcher_alpha_foreground.xml new file mode 100644 index 0000000000..389bdb23b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_alpha_foreground.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 0b33062453..0ac58521bc 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -3,28 +3,27 @@ android:height="108dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_beta_background.xml b/app/src/main/res/drawable/ic_launcher_beta_background.xml index 657fc6745a..47711abd35 100644 --- a/app/src/main/res/drawable/ic_launcher_beta_background.xml +++ b/app/src/main/res/drawable/ic_launcher_beta_background.xml @@ -3,28 +3,27 @@ android:height="108dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_beta_foreground.xml b/app/src/main/res/drawable/ic_launcher_beta_foreground.xml index f19b9a98f9..389bdb23b0 100644 --- a/app/src/main/res/drawable/ic_launcher_beta_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_beta_foreground.xml @@ -3,20 +3,19 @@ android:height="108dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - - + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index f19b9a98f9..389bdb23b0 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,20 +3,19 @@ android:height="108dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - - + + + + + + diff --git a/app/src/main/res/drawable/ic_myanimelist.xml b/app/src/main/res/drawable/ic_myanimelist.xml index d12a8ddbb0..54dc60bfc2 100644 --- a/app/src/main/res/drawable/ic_myanimelist.xml +++ b/app/src/main/res/drawable/ic_myanimelist.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_page_numbering.xml b/app/src/main/res/drawable/ic_page_numbering.xml index 050818928c..8d95bcee45 100644 --- a/app/src/main/res/drawable/ic_page_numbering.xml +++ b/app/src/main/res/drawable/ic_page_numbering.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_palette.xml b/app/src/main/res/drawable/ic_palette.xml index 581c3ba98c..fa171f1545 100644 --- a/app/src/main/res/drawable/ic_palette.xml +++ b/app/src/main/res/drawable/ic_palette.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml index ecb8624f01..cb5bac6f4f 100644 --- a/app/src/main/res/drawable/ic_pin.xml +++ b/app/src/main/res/drawable/ic_pin.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="960" android:viewportHeight="960"> - + diff --git a/app/src/main/res/drawable/ic_round_accessible_forward_24.xml b/app/src/main/res/drawable/ic_round_accessible_forward_24.xml index d50cad1356..a211356675 100644 --- a/app/src/main/res/drawable/ic_round_accessible_forward_24.xml +++ b/app/src/main/res/drawable/ic_round_accessible_forward_24.xml @@ -1,14 +1,14 @@ - - + android:viewportWidth="24" + android:viewportHeight="24"> + + diff --git a/app/src/main/res/drawable/ic_round_add_circle_24.xml b/app/src/main/res/drawable/ic_round_add_circle_24.xml index 1906afe549..85175feb3f 100644 --- a/app/src/main/res/drawable/ic_round_add_circle_24.xml +++ b/app/src/main/res/drawable/ic_round_add_circle_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_add_circle_outline_24.xml b/app/src/main/res/drawable/ic_round_add_circle_outline_24.xml index 28bd03ffb9..18876e351e 100644 --- a/app/src/main/res/drawable/ic_round_add_circle_outline_24.xml +++ b/app/src/main/res/drawable/ic_round_add_circle_outline_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_alpha_t_box_24.xml b/app/src/main/res/drawable/ic_round_alpha_t_box_24.xml index f9aaabc1e0..d02b36b63f 100644 --- a/app/src/main/res/drawable/ic_round_alpha_t_box_24.xml +++ b/app/src/main/res/drawable/ic_round_alpha_t_box_24.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M9,7V9H11V17H13V9H15V7H9M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z" /> diff --git a/app/src/main/res/drawable/ic_round_amp_stories_24.xml b/app/src/main/res/drawable/ic_round_amp_stories_24.xml index 7e2e61ba5e..9782f0e362 100644 --- a/app/src/main/res/drawable/ic_round_amp_stories_24.xml +++ b/app/src/main/res/drawable/ic_round_amp_stories_24.xml @@ -1,7 +1,16 @@ - - - - + + + + diff --git a/app/src/main/res/drawable/ic_round_animation_24.xml b/app/src/main/res/drawable/ic_round_animation_24.xml index 8851c082cc..31fabc2a7d 100644 --- a/app/src/main/res/drawable/ic_round_animation_24.xml +++ b/app/src/main/res/drawable/ic_round_animation_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_arrow_back_ios_new_24.xml b/app/src/main/res/drawable/ic_round_arrow_back_ios_new_24.xml index adb433ef9f..fa21b7057d 100644 --- a/app/src/main/res/drawable/ic_round_arrow_back_ios_new_24.xml +++ b/app/src/main/res/drawable/ic_round_arrow_back_ios_new_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_art_track_24.xml b/app/src/main/res/drawable/ic_round_art_track_24.xml index 66353a48fc..821e38b06a 100644 --- a/app/src/main/res/drawable/ic_round_art_track_24.xml +++ b/app/src/main/res/drawable/ic_round_art_track_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_audiotrack_24.xml b/app/src/main/res/drawable/ic_round_audiotrack_24.xml index 20ed3ccfdd..acbb5ff56c 100644 --- a/app/src/main/res/drawable/ic_round_audiotrack_24.xml +++ b/app/src/main/res/drawable/ic_round_audiotrack_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_auto_awesome_24.xml b/app/src/main/res/drawable/ic_round_auto_awesome_24.xml index 1c769838d8..e38f23037c 100644 --- a/app/src/main/res/drawable/ic_round_auto_awesome_24.xml +++ b/app/src/main/res/drawable/ic_round_auto_awesome_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_book_24.xml b/app/src/main/res/drawable/ic_round_book_24.xml index 3ab0b826ac..34c687f8d9 100644 --- a/app/src/main/res/drawable/ic_round_book_24.xml +++ b/app/src/main/res/drawable/ic_round_book_24.xml @@ -4,7 +4,7 @@ android:tint="?attr/colorControlNormal" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_brightness_4_24.xml b/app/src/main/res/drawable/ic_round_brightness_4_24.xml index 565dcceb0a..32a6f0a76a 100644 --- a/app/src/main/res/drawable/ic_round_brightness_4_24.xml +++ b/app/src/main/res/drawable/ic_round_brightness_4_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_brightness_auto_24.xml b/app/src/main/res/drawable/ic_round_brightness_auto_24.xml index af592824ec..d12cc1fbbb 100644 --- a/app/src/main/res/drawable/ic_round_brightness_auto_24.xml +++ b/app/src/main/res/drawable/ic_round_brightness_auto_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_brightness_high_24.xml b/app/src/main/res/drawable/ic_round_brightness_high_24.xml index 00d435028b..751ddc6047 100644 --- a/app/src/main/res/drawable/ic_round_brightness_high_24.xml +++ b/app/src/main/res/drawable/ic_round_brightness_high_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_brightness_medium_24.xml b/app/src/main/res/drawable/ic_round_brightness_medium_24.xml index 74e246e13d..3d8a9d2784 100644 --- a/app/src/main/res/drawable/ic_round_brightness_medium_24.xml +++ b/app/src/main/res/drawable/ic_round_brightness_medium_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_calendar_today_24.xml b/app/src/main/res/drawable/ic_round_calendar_today_24.xml index 04e95cdb25..f2bcf66615 100644 --- a/app/src/main/res/drawable/ic_round_calendar_today_24.xml +++ b/app/src/main/res/drawable/ic_round_calendar_today_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_cast_24.xml b/app/src/main/res/drawable/ic_round_cast_24.xml index c0e072f546..1a6d14df9d 100644 --- a/app/src/main/res/drawable/ic_round_cast_24.xml +++ b/app/src/main/res/drawable/ic_round_cast_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_close_24.xml b/app/src/main/res/drawable/ic_round_close_24.xml index 6059f3593d..7836f6bf20 100644 --- a/app/src/main/res/drawable/ic_round_close_24.xml +++ b/app/src/main/res/drawable/ic_round_close_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_collections_bookmark_24.xml b/app/src/main/res/drawable/ic_round_collections_bookmark_24.xml index 6109c4cd55..8c4fb6a001 100644 --- a/app/src/main/res/drawable/ic_round_collections_bookmark_24.xml +++ b/app/src/main/res/drawable/ic_round_collections_bookmark_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_color_24.xml b/app/src/main/res/drawable/ic_round_color_24.xml index 2d430c1601..1ad382e5e7 100644 --- a/app/src/main/res/drawable/ic_round_color_24.xml +++ b/app/src/main/res/drawable/ic_round_color_24.xml @@ -1,10 +1,10 @@ + android:viewportHeight="24"> + android:pathData="M12 3a9 9 0 0 0 0 18c0.83 0 1.5-0.67 1.5-1.5c0-0.39-0.15-0.74-0.39-1.01c-0.23-0.26-0.38-0.61-0.38-0.99c0-0.83 0.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5c0-4.42-4.03-8-9-8zm-5.5 9c-0.83 0-1.5-0.67-1.5-1.5S5.67 9 6.5 9S8 9.67 8 10.5S7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5 0.67 1.5 1.5S10.33 8 9.5 8zm5 0c-0.83 0-1.5-0.67-1.5-1.5S13.67 5 14.5 5s1.5 0.67 1.5 1.5S15.33 8 14.5 8zm3 4c-0.83 0-1.5-0.67-1.5-1.5S16.67 9 17.5 9s1.5 0.67 1.5 1.5s-0.67 1.5-1.5 1.5z" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_round_color_picker_24.xml b/app/src/main/res/drawable/ic_round_color_picker_24.xml index b18b20108a..c4e7980875 100644 --- a/app/src/main/res/drawable/ic_round_color_picker_24.xml +++ b/app/src/main/res/drawable/ic_round_color_picker_24.xml @@ -1,5 +1,10 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_round_date_range_24.xml b/app/src/main/res/drawable/ic_round_date_range_24.xml index ccabff08b0..d393cbc8c5 100644 --- a/app/src/main/res/drawable/ic_round_date_range_24.xml +++ b/app/src/main/res/drawable/ic_round_date_range_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_delete_24.xml b/app/src/main/res/drawable/ic_round_delete_24.xml index 9dd59ef97d..4a978d7612 100644 --- a/app/src/main/res/drawable/ic_round_delete_24.xml +++ b/app/src/main/res/drawable/ic_round_delete_24.xml @@ -1,4 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_dns_24.xml b/app/src/main/res/drawable/ic_round_dns_24.xml index a43abccbbb..adb315f114 100644 --- a/app/src/main/res/drawable/ic_round_dns_24.xml +++ b/app/src/main/res/drawable/ic_round_dns_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_dots_vertical_24.xml b/app/src/main/res/drawable/ic_round_dots_vertical_24.xml index db5b6d107c..9f9d4f5cdb 100644 --- a/app/src/main/res/drawable/ic_round_dots_vertical_24.xml +++ b/app/src/main/res/drawable/ic_round_dots_vertical_24.xml @@ -1,7 +1,13 @@ - - + + android:strokeWidth="2" + android:strokeColor="#000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" /> diff --git a/app/src/main/res/drawable/ic_round_download_24.xml b/app/src/main/res/drawable/ic_round_download_24.xml deleted file mode 100644 index dd79e4cca9..0000000000 --- a/app/src/main/res/drawable/ic_round_download_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_round_edit_note_24.xml b/app/src/main/res/drawable/ic_round_edit_note_24.xml index e363567285..a2fdf2362b 100644 --- a/app/src/main/res/drawable/ic_round_edit_note_24.xml +++ b/app/src/main/res/drawable/ic_round_edit_note_24.xml @@ -1,10 +1,10 @@ + android:viewportHeight="24"> + android:pathData="M14,11c0,0.55 -0.45,1 -1,1H4c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h9C13.55,10 14,10.45 14,11zM3,7c0,0.55 0.45,1 1,1h9c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H4C3.45,6 3,6.45 3,7zM10,15c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h5C9.55,16 10,15.55 10,15zM18.01,12.87l0.71,-0.71c0.39,-0.39 1.02,-0.39 1.41,0l0.71,0.71c0.39,0.39 0.39,1.02 0,1.41l-0.71,0.71L18.01,12.87zM17.3,13.58l-5.16,5.16C12.05,18.83 12,18.95 12,19.09v1.41c0,0.28 0.22,0.5 0.5,0.5h1.41c0.13,0 0.26,-0.05 0.35,-0.15l5.16,-5.16L17.3,13.58z" /> diff --git a/app/src/main/res/drawable/ic_round_equal_24.xml b/app/src/main/res/drawable/ic_round_equal_24.xml new file mode 100644 index 0000000000..8fd70fd5d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_equal_24.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_round_fast_forward_24.xml b/app/src/main/res/drawable/ic_round_fast_forward_24.xml index ad3ea31cc6..a8227be869 100644 --- a/app/src/main/res/drawable/ic_round_fast_forward_24.xml +++ b/app/src/main/res/drawable/ic_round_fast_forward_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_fast_rewind_24.xml b/app/src/main/res/drawable/ic_round_fast_rewind_24.xml index 87580957ac..0e5fba57df 100644 --- a/app/src/main/res/drawable/ic_round_fast_rewind_24.xml +++ b/app/src/main/res/drawable/ic_round_fast_rewind_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_favorite_24.xml b/app/src/main/res/drawable/ic_round_favorite_24.xml index a046b08aea..9a1dabd790 100644 --- a/app/src/main/res/drawable/ic_round_favorite_24.xml +++ b/app/src/main/res/drawable/ic_round_favorite_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_favorite_border_24.xml b/app/src/main/res/drawable/ic_round_favorite_border_24.xml index 1cff7203c2..c4c445ed82 100644 --- a/app/src/main/res/drawable/ic_round_favorite_border_24.xml +++ b/app/src/main/res/drawable/ic_round_favorite_border_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_filter_24.xml b/app/src/main/res/drawable/ic_round_filter_24.xml index 103f077195..dcc324b155 100644 --- a/app/src/main/res/drawable/ic_round_filter_24.xml +++ b/app/src/main/res/drawable/ic_round_filter_24.xml @@ -1,12 +1,27 @@ - - - - + + + + diff --git a/app/src/main/res/drawable/ic_round_filter_alt_24.xml b/app/src/main/res/drawable/ic_round_filter_alt_24.xml index 3f74d40dec..41bf142325 100644 --- a/app/src/main/res/drawable/ic_round_filter_alt_24.xml +++ b/app/src/main/res/drawable/ic_round_filter_alt_24.xml @@ -1,9 +1,9 @@ + android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_round_font_size_24.xml b/app/src/main/res/drawable/ic_round_font_size_24.xml index 9b961072e6..1ca4feb0cc 100644 --- a/app/src/main/res/drawable/ic_round_font_size_24.xml +++ b/app/src/main/res/drawable/ic_round_font_size_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_format_text_24.xml b/app/src/main/res/drawable/ic_round_format_text_24.xml index 0446eeec20..f4e9d5f170 100644 --- a/app/src/main/res/drawable/ic_round_format_text_24.xml +++ b/app/src/main/res/drawable/ic_round_format_text_24.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M18.5,4L19.66,8.35L18.7,8.61C18.25,7.74 17.79,6.87 17.26,6.43C16.73,6 16.11,6 15.5,6H13V16.5C13,17 13,17.5 13.33,17.75C13.67,18 14.33,18 15,18V19H9V18C9.67,18 10.33,18 10.67,17.75C11,17.5 11,17 11,16.5V6H8.5C7.89,6 7.27,6 6.74,6.43C6.21,6.87 5.75,7.74 5.3,8.61L4.34,8.35L5.5,4H18.5Z" /> diff --git a/app/src/main/res/drawable/ic_round_fullscreen_24.xml b/app/src/main/res/drawable/ic_round_fullscreen_24.xml index 0dbedfee2b..e091166217 100644 --- a/app/src/main/res/drawable/ic_round_fullscreen_24.xml +++ b/app/src/main/res/drawable/ic_round_fullscreen_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_grid_view_24.xml b/app/src/main/res/drawable/ic_round_grid_view_24.xml index 0cc8636cf1..b289c173f6 100644 --- a/app/src/main/res/drawable/ic_round_grid_view_24.xml +++ b/app/src/main/res/drawable/ic_round_grid_view_24.xml @@ -1,19 +1,19 @@ - - - - + android:viewportHeight="24"> + + + + diff --git a/app/src/main/res/drawable/ic_round_heart_broken_24.xml b/app/src/main/res/drawable/ic_round_heart_broken_24.xml index 2659dfaaef..778d822f45 100644 --- a/app/src/main/res/drawable/ic_round_heart_broken_24.xml +++ b/app/src/main/res/drawable/ic_round_heart_broken_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_help_24.xml b/app/src/main/res/drawable/ic_round_help_24.xml index 4339edf2a6..1492de1b40 100644 --- a/app/src/main/res/drawable/ic_round_help_24.xml +++ b/app/src/main/res/drawable/ic_round_help_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_high_quality_24.xml b/app/src/main/res/drawable/ic_round_high_quality_24.xml index 4dc95e694d..3cc38ff699 100644 --- a/app/src/main/res/drawable/ic_round_high_quality_24.xml +++ b/app/src/main/res/drawable/ic_round_high_quality_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_home_24.xml b/app/src/main/res/drawable/ic_round_home_24.xml index f8cd095e52..178a340fb4 100644 --- a/app/src/main/res/drawable/ic_round_home_24.xml +++ b/app/src/main/res/drawable/ic_round_home_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_import_contacts_24.xml b/app/src/main/res/drawable/ic_round_import_contacts_24.xml index 6fe25938b7..8d3e1f18b6 100644 --- a/app/src/main/res/drawable/ic_round_import_contacts_24.xml +++ b/app/src/main/res/drawable/ic_round_import_contacts_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_info_24.xml b/app/src/main/res/drawable/ic_round_info_24.xml index 0a8acfbab4..1a4e0e1aab 100644 --- a/app/src/main/res/drawable/ic_round_info_24.xml +++ b/app/src/main/res/drawable/ic_round_info_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_lock_24.xml b/app/src/main/res/drawable/ic_round_lock_24.xml index f1c6a2d810..68cb9c1f56 100644 --- a/app/src/main/res/drawable/ic_round_lock_24.xml +++ b/app/src/main/res/drawable/ic_round_lock_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_lock_open_24.xml b/app/src/main/res/drawable/ic_round_lock_open_24.xml index def7919be3..e6f86a878c 100644 --- a/app/src/main/res/drawable/ic_round_lock_open_24.xml +++ b/app/src/main/res/drawable/ic_round_lock_open_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_movie_filter_24.xml b/app/src/main/res/drawable/ic_round_movie_filter_24.xml index c32b03bbb0..249da89959 100644 --- a/app/src/main/res/drawable/ic_round_movie_filter_24.xml +++ b/app/src/main/res/drawable/ic_round_movie_filter_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_new_releases_24.xml b/app/src/main/res/drawable/ic_round_new_releases_24.xml index 8e8d522df6..84aaf39f1b 100644 --- a/app/src/main/res/drawable/ic_round_new_releases_24.xml +++ b/app/src/main/res/drawable/ic_round_new_releases_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_no_icon_24.xml b/app/src/main/res/drawable/ic_round_no_icon_24.xml index dd5404b10a..7d47748507 100644 --- a/app/src/main/res/drawable/ic_round_no_icon_24.xml +++ b/app/src/main/res/drawable/ic_round_no_icon_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="960" android:viewportHeight="960"> - + diff --git a/app/src/main/res/drawable/ic_round_notifications_active_24.xml b/app/src/main/res/drawable/ic_round_notifications_active_24.xml index 232999afb1..bdc6eb3f5d 100644 --- a/app/src/main/res/drawable/ic_round_notifications_active_24.xml +++ b/app/src/main/res/drawable/ic_round_notifications_active_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_notifications_none_24.xml b/app/src/main/res/drawable/ic_round_notifications_none_24.xml index ea20d697d5..b0b760a542 100644 --- a/app/src/main/res/drawable/ic_round_notifications_none_24.xml +++ b/app/src/main/res/drawable/ic_round_notifications_none_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_nsfw_24.xml b/app/src/main/res/drawable/ic_round_nsfw_24.xml index 7672c9c0bb..3fc1b1142a 100644 --- a/app/src/main/res/drawable/ic_round_nsfw_24.xml +++ b/app/src/main/res/drawable/ic_round_nsfw_24.xml @@ -1,7 +1,19 @@ - - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_round_pause_24.xml b/app/src/main/res/drawable/ic_round_pause_24.xml index a5e498b538..f2d913f974 100644 --- a/app/src/main/res/drawable/ic_round_pause_24.xml +++ b/app/src/main/res/drawable/ic_round_pause_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_person_24.xml b/app/src/main/res/drawable/ic_round_person_24.xml index ee8540d513..4cecf07482 100644 --- a/app/src/main/res/drawable/ic_round_person_24.xml +++ b/app/src/main/res/drawable/ic_round_person_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_photo_size_select_actual_24.xml b/app/src/main/res/drawable/ic_round_photo_size_select_actual_24.xml index d70b496c11..7e6cafeb49 100644 --- a/app/src/main/res/drawable/ic_round_photo_size_select_actual_24.xml +++ b/app/src/main/res/drawable/ic_round_photo_size_select_actual_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_picture_in_picture_alt_24.xml b/app/src/main/res/drawable/ic_round_picture_in_picture_alt_24.xml index 3a0d3952eb..e462dd7ae7 100644 --- a/app/src/main/res/drawable/ic_round_picture_in_picture_alt_24.xml +++ b/app/src/main/res/drawable/ic_round_picture_in_picture_alt_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_play_arrow_24.xml b/app/src/main/res/drawable/ic_round_play_arrow_24.xml index 2e39947682..ab37f2d4a7 100644 --- a/app/src/main/res/drawable/ic_round_play_arrow_24.xml +++ b/app/src/main/res/drawable/ic_round_play_arrow_24.xml @@ -3,7 +3,7 @@ android:height="48dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_play_circle_24.xml b/app/src/main/res/drawable/ic_round_play_circle_24.xml index 3f3648d354..b1ce9ce8a4 100644 --- a/app/src/main/res/drawable/ic_round_play_circle_24.xml +++ b/app/src/main/res/drawable/ic_round_play_circle_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_play_disabled_24.xml b/app/src/main/res/drawable/ic_round_play_disabled_24.xml index 586c403f1e..263ea42d21 100644 --- a/app/src/main/res/drawable/ic_round_play_disabled_24.xml +++ b/app/src/main/res/drawable/ic_round_play_disabled_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_playlist_add_24.xml b/app/src/main/res/drawable/ic_round_playlist_add_24.xml index 82a08b0a0b..5a62dbeef1 100644 --- a/app/src/main/res/drawable/ic_round_playlist_add_24.xml +++ b/app/src/main/res/drawable/ic_round_playlist_add_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_playlist_play_24.xml b/app/src/main/res/drawable/ic_round_playlist_play_24.xml index 4c2be06e6a..28e284324a 100644 --- a/app/src/main/res/drawable/ic_round_playlist_play_24.xml +++ b/app/src/main/res/drawable/ic_round_playlist_play_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_reader_settings.xml b/app/src/main/res/drawable/ic_round_reader_settings.xml index ff787a1d6b..99ddb251c2 100644 --- a/app/src/main/res/drawable/ic_round_reader_settings.xml +++ b/app/src/main/res/drawable/ic_round_reader_settings.xml @@ -1,13 +1,13 @@ - - + android:viewportHeight="24"> + + diff --git a/app/src/main/res/drawable/ic_round_refresh_24.xml b/app/src/main/res/drawable/ic_round_refresh_24.xml index 5cdbeb0776..9a9c070c37 100644 --- a/app/src/main/res/drawable/ic_round_refresh_24.xml +++ b/app/src/main/res/drawable/ic_round_refresh_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_remove_red_eye_24.xml b/app/src/main/res/drawable/ic_round_remove_red_eye_24.xml index a3e222a2d1..cc9f66f120 100644 --- a/app/src/main/res/drawable/ic_round_remove_red_eye_24.xml +++ b/app/src/main/res/drawable/ic_round_remove_red_eye_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_restaurant_24.xml b/app/src/main/res/drawable/ic_round_restaurant_24.xml index 3a35f604d1..ea504ef046 100644 --- a/app/src/main/res/drawable/ic_round_restaurant_24.xml +++ b/app/src/main/res/drawable/ic_round_restaurant_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_screen_rotation_alt_24.xml b/app/src/main/res/drawable/ic_round_screen_rotation_alt_24.xml index 95be860595..3aa7117293 100644 --- a/app/src/main/res/drawable/ic_round_screen_rotation_alt_24.xml +++ b/app/src/main/res/drawable/ic_round_screen_rotation_alt_24.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/app/src/main/res/drawable/ic_round_sd_card_24.xml b/app/src/main/res/drawable/ic_round_sd_card_24.xml index 38e955277f..83fcd2b01c 100644 --- a/app/src/main/res/drawable/ic_round_sd_card_24.xml +++ b/app/src/main/res/drawable/ic_round_sd_card_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_search_24.xml b/app/src/main/res/drawable/ic_round_search_24.xml index c1818d5071..ac66e573f9 100644 --- a/app/src/main/res/drawable/ic_round_search_24.xml +++ b/app/src/main/res/drawable/ic_round_search_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_settings_24.xml b/app/src/main/res/drawable/ic_round_settings_24.xml index 0cb6c0212b..a0b70cd747 100644 --- a/app/src/main/res/drawable/ic_round_settings_24.xml +++ b/app/src/main/res/drawable/ic_round_settings_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_share_24.xml b/app/src/main/res/drawable/ic_round_share_24.xml index 17f4f4a92e..97d628ec60 100644 --- a/app/src/main/res/drawable/ic_round_share_24.xml +++ b/app/src/main/res/drawable/ic_round_share_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_skip_next_24.xml b/app/src/main/res/drawable/ic_round_skip_next_24.xml index 7437d8db3d..adbdcaccd1 100644 --- a/app/src/main/res/drawable/ic_round_skip_next_24.xml +++ b/app/src/main/res/drawable/ic_round_skip_next_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_skip_previous_24.xml b/app/src/main/res/drawable/ic_round_skip_previous_24.xml index 4b0219e849..1faf6ed5a5 100644 --- a/app/src/main/res/drawable/ic_round_skip_previous_24.xml +++ b/app/src/main/res/drawable/ic_round_skip_previous_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_slow_motion_video_24.xml b/app/src/main/res/drawable/ic_round_slow_motion_video_24.xml index c1c015bb23..4034975601 100644 --- a/app/src/main/res/drawable/ic_round_slow_motion_video_24.xml +++ b/app/src/main/res/drawable/ic_round_slow_motion_video_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_sort_24.xml b/app/src/main/res/drawable/ic_round_sort_24.xml index f48f3f55e7..81fc9b1d57 100644 --- a/app/src/main/res/drawable/ic_round_sort_24.xml +++ b/app/src/main/res/drawable/ic_round_sort_24.xml @@ -1,11 +1,10 @@ - + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_round_source_24.xml b/app/src/main/res/drawable/ic_round_source_24.xml index 95e5d40ef5..cc2644a4bf 100644 --- a/app/src/main/res/drawable/ic_round_source_24.xml +++ b/app/src/main/res/drawable/ic_round_source_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_round_space_bar_24.xml b/app/src/main/res/drawable/ic_round_space_bar_24.xml index e6d420a761..de7c507e42 100644 --- a/app/src/main/res/drawable/ic_round_space_bar_24.xml +++ b/app/src/main/res/drawable/ic_round_space_bar_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_star_24.xml b/app/src/main/res/drawable/ic_round_star_24.xml index b95abcebe3..26dffcc268 100644 --- a/app/src/main/res/drawable/ic_round_star_24.xml +++ b/app/src/main/res/drawable/ic_round_star_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_straighten_24.xml b/app/src/main/res/drawable/ic_round_straighten_24.xml index 9dec197d7b..71422db590 100644 --- a/app/src/main/res/drawable/ic_round_straighten_24.xml +++ b/app/src/main/res/drawable/ic_round_straighten_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_subtitles_24.xml b/app/src/main/res/drawable/ic_round_subtitles_24.xml index 1effb287b4..93945230f5 100644 --- a/app/src/main/res/drawable/ic_round_subtitles_24.xml +++ b/app/src/main/res/drawable/ic_round_subtitles_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_swipe_down_alt_24.xml b/app/src/main/res/drawable/ic_round_swipe_down_alt_24.xml index 5b3dd7b57b..820f51938a 100644 --- a/app/src/main/res/drawable/ic_round_swipe_down_alt_24.xml +++ b/app/src/main/res/drawable/ic_round_swipe_down_alt_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_swipe_up_alt_24.xml b/app/src/main/res/drawable/ic_round_swipe_up_alt_24.xml index 65e04cdf03..c32e08f407 100644 --- a/app/src/main/res/drawable/ic_round_swipe_up_alt_24.xml +++ b/app/src/main/res/drawable/ic_round_swipe_up_alt_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_swipe_vertical_24.xml b/app/src/main/res/drawable/ic_round_swipe_vertical_24.xml index 8d6d9721b7..fba9c5038c 100644 --- a/app/src/main/res/drawable/ic_round_swipe_vertical_24.xml +++ b/app/src/main/res/drawable/ic_round_swipe_vertical_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_sync_24.xml b/app/src/main/res/drawable/ic_round_sync_24.xml index 0ea3056c77..4c6094a0a2 100644 --- a/app/src/main/res/drawable/ic_round_sync_24.xml +++ b/app/src/main/res/drawable/ic_round_sync_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_touch_app_24.xml b/app/src/main/res/drawable/ic_round_touch_app_24.xml index 7b31bd4aa0..3a955dc78e 100644 --- a/app/src/main/res/drawable/ic_round_touch_app_24.xml +++ b/app/src/main/res/drawable/ic_round_touch_app_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_translate_24.xml b/app/src/main/res/drawable/ic_round_translate_24.xml index 4cfe337918..cdf42d6839 100644 --- a/app/src/main/res/drawable/ic_round_translate_24.xml +++ b/app/src/main/res/drawable/ic_round_translate_24.xml @@ -1,7 +1,13 @@ - - + + android:strokeWidth="2" + android:strokeColor="#000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" /> diff --git a/app/src/main/res/drawable/ic_round_translate_variant_24.xml b/app/src/main/res/drawable/ic_round_translate_variant_24.xml index 02aae753ee..5d46a22eff 100644 --- a/app/src/main/res/drawable/ic_round_translate_variant_24.xml +++ b/app/src/main/res/drawable/ic_round_translate_variant_24.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M11 1H3C1.9 1 1 1.9 1 3V15L4 12H9V11C9 8.8 10.79 7 13 7V3C13 1.9 12.1 1 11 1M11 4L9.5 4C9.16 5.19 8.54 6.3 7.68 7.26L7.66 7.28L8.92 8.53L8.55 9.54L7 8L4.5 10.5L3.81 9.77L6.34 7.28C5.72 6.59 5.22 5.82 4.86 5H5.85C6.16 5.6 6.54 6.17 7 6.68C7.72 5.88 8.24 4.97 8.57 4L3 4V3H6.5V2H7.5V3H11V4M21 9H13C11.9 9 11 9.9 11 11V18C11 19.1 11.9 20 13 20H20L23 23V11C23 9.9 22.1 9 21 9M19.63 19L18.78 16.75H15.22L14.38 19H12.88L16.25 10H17.75L21.13 19H19.63M17 12L18.22 15.25H15.79L17 12Z" /> diff --git a/app/src/main/res/drawable/ic_round_video_library_24.xml b/app/src/main/res/drawable/ic_round_video_library_24.xml index 1062992aab..0827f78516 100644 --- a/app/src/main/res/drawable/ic_round_video_library_24.xml +++ b/app/src/main/res/drawable/ic_round_video_library_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_video_settings_24.xml b/app/src/main/res/drawable/ic_round_video_settings_24.xml index f94cfe7192..40fed4ef72 100644 --- a/app/src/main/res/drawable/ic_round_video_settings_24.xml +++ b/app/src/main/res/drawable/ic_round_video_settings_24.xml @@ -1,16 +1,16 @@ - - - + android:viewportHeight="24"> + + + diff --git a/app/src/main/res/drawable/ic_round_view_array_24.xml b/app/src/main/res/drawable/ic_round_view_array_24.xml index 537a3a1591..266e92dd80 100644 --- a/app/src/main/res/drawable/ic_round_view_array_24.xml +++ b/app/src/main/res/drawable/ic_round_view_array_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_view_column_24.xml b/app/src/main/res/drawable/ic_round_view_column_24.xml index 7d08338a11..be3439b9c4 100644 --- a/app/src/main/res/drawable/ic_round_view_column_24.xml +++ b/app/src/main/res/drawable/ic_round_view_column_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_view_comfy_24.xml b/app/src/main/res/drawable/ic_round_view_comfy_24.xml index 723e9e4f69..cfae225804 100644 --- a/app/src/main/res/drawable/ic_round_view_comfy_24.xml +++ b/app/src/main/res/drawable/ic_round_view_comfy_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_view_list_24.xml b/app/src/main/res/drawable/ic_round_view_list_24.xml index 2ceb1b91ca..015d1235d7 100644 --- a/app/src/main/res/drawable/ic_round_view_list_24.xml +++ b/app/src/main/res/drawable/ic_round_view_list_24.xml @@ -1,11 +1,11 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_volume_up_24.xml b/app/src/main/res/drawable/ic_round_volume_up_24.xml index 45216f9578..f54e68936a 100644 --- a/app/src/main/res/drawable/ic_round_volume_up_24.xml +++ b/app/src/main/res/drawable/ic_round_volume_up_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_shuffle_24.xml b/app/src/main/res/drawable/ic_shuffle_24.xml index 2bd11b20dd..e3069b0870 100644 --- a/app/src/main/res/drawable/ic_shuffle_24.xml +++ b/app/src/main/res/drawable/ic_shuffle_24.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_skip.xml b/app/src/main/res/drawable/ic_skip.xml index 93da1885e8..a62ff09cf8 100644 --- a/app/src/main/res/drawable/ic_skip.xml +++ b/app/src/main/res/drawable/ic_skip.xml @@ -3,10 +3,10 @@ android:height="36dp" android:viewportWidth="54" android:viewportHeight="36"> - - + + diff --git a/app/src/main/res/drawable/ic_sync.xml b/app/src/main/res/drawable/ic_sync.xml index 4ad75e532a..90b95225ef 100644 --- a/app/src/main/res/drawable/ic_sync.xml +++ b/app/src/main/res/drawable/ic_sync.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml index 4eb70297aa..4827df5569 100644 --- a/app/src/main/res/drawable/ic_telegram.xml +++ b/app/src/main/res/drawable/ic_telegram.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_upi_icon.xml b/app/src/main/res/drawable/ic_upi_icon.xml index 8f28975b82..f9549bee8b 100644 --- a/app/src/main/res/drawable/ic_upi_icon.xml +++ b/app/src/main/res/drawable/ic_upi_icon.xml @@ -1,8 +1,26 @@ - - - - - - + + + + + + diff --git a/app/src/main/res/drawable/item_ongoing.xml b/app/src/main/res/drawable/item_ongoing.xml index 716e3010eb..3bba934b18 100644 --- a/app/src/main/res/drawable/item_ongoing.xml +++ b/app/src/main/res/drawable/item_ongoing.xml @@ -1,5 +1,7 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_score.xml b/app/src/main/res/drawable/item_score.xml index 4328f951b7..4aac572201 100644 --- a/app/src/main/res/drawable/item_score.xml +++ b/app/src/main/res/drawable/item_score.xml @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_type.xml b/app/src/main/res/drawable/item_type.xml index d69837e5fa..c87c112493 100644 --- a/app/src/main/res/drawable/item_type.xml +++ b/app/src/main/res/drawable/item_type.xml @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_user_score.xml b/app/src/main/res/drawable/item_user_score.xml index 9e105d7f3f..181dfe1292 100644 --- a/app/src/main/res/drawable/item_user_score.xml +++ b/app/src/main/res/drawable/item_user_score.xml @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/linear_gradient_bg.xml b/app/src/main/res/drawable/linear_gradient_bg.xml index a711be1da7..302602148a 100644 --- a/app/src/main/res/drawable/linear_gradient_bg.xml +++ b/app/src/main/res/drawable/linear_gradient_bg.xml @@ -1,8 +1,8 @@ + android:type="linear" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/linear_gradient_black.xml b/app/src/main/res/drawable/linear_gradient_black.xml index dca6cc55cf..e719406d81 100644 --- a/app/src/main/res/drawable/linear_gradient_black.xml +++ b/app/src/main/res/drawable/linear_gradient_black.xml @@ -1,8 +1,8 @@ + android:type="linear" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/linear_gradient_black_horizontal.xml b/app/src/main/res/drawable/linear_gradient_black_horizontal.xml index a99d7d8109..f1d2f249a9 100644 --- a/app/src/main/res/drawable/linear_gradient_black_horizontal.xml +++ b/app/src/main/res/drawable/linear_gradient_black_horizontal.xml @@ -1,7 +1,7 @@ + android:type="linear" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/linear_gradient_nav.xml b/app/src/main/res/drawable/linear_gradient_nav.xml index f8e92642a4..2fdc24ba38 100644 --- a/app/src/main/res/drawable/linear_gradient_nav.xml +++ b/app/src/main/res/drawable/linear_gradient_nav.xml @@ -1,8 +1,8 @@ + android:type="linear" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/mono.xml b/app/src/main/res/drawable/mono.xml index 5ce5966a34..aa925d649f 100644 --- a/app/src/main/res/drawable/mono.xml +++ b/app/src/main/res/drawable/mono.xml @@ -3,16 +3,15 @@ android:height="768dp" android:viewportWidth="768" android:viewportHeight="768"> - - - - - + + + + + diff --git a/app/src/main/res/drawable/monochrome.xml b/app/src/main/res/drawable/monochrome.xml index 6fd3cc32d6..471f459c41 100644 --- a/app/src/main/res/drawable/monochrome.xml +++ b/app/src/main/res/drawable/monochrome.xml @@ -1,16 +1,16 @@ + android:viewportHeight="768"> + android:pathData="M125.71,125.71h516.58v516.58h-516.58z" /> + android:pathData="M44.26,128C44.26,173.48 80.53,210.4 125.71,211.63L125.71,128.01L642.29,128.01L642.29,639.97L768,639.97L768,128L44.26,128zM642.29,639.97L125.71,639.97L125.71,639.99L642.29,639.99L642.29,639.97zM125.71,639.97L125.71,556.38C80.54,557.6 44.28,594.5 44.26,639.97L125.71,639.97zM125.71,556.38C126.48,556.35 127.23,556.26 128,556.26L384,556.26C479.14,556.26 556.26,479.13 556.26,384C556.26,288.86 479.13,211.74 384,211.74L128,211.74C127.23,211.74 126.48,211.65 125.71,211.63L125.71,286.18L384,286.18C438.02,286.18 481.82,329.98 481.82,384C481.82,438.03 438.02,481.82 384,481.82L125.71,481.82L125.71,556.38zM125.71,481.82L125.71,286.18L0,286.18L0,481.82L125.71,481.82z" /> + android:pathData="m442,366.7l-76.02,-43.89c-13.32,-7.69 -29.96,1.92 -29.96,17.3v87.78c0,15.38 16.65,24.99 29.96,17.3l76.02,-43.89c13.32,-7.69 13.32,-26.91 0,-34.6Z" /> diff --git a/app/src/main/res/drawable/round_corner.xml b/app/src/main/res/drawable/round_corner.xml index e780a9b3c0..878a3dd0cc 100644 --- a/app/src/main/res/drawable/round_corner.xml +++ b/app/src/main/res/drawable/round_corner.xml @@ -1,5 +1,5 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_top_incognito.xml b/app/src/main/res/drawable/rounded_top_incognito.xml index eb08a05d15..c6ce569117 100644 --- a/app/src/main/res/drawable/rounded_top_incognito.xml +++ b/app/src/main/res/drawable/rounded_top_incognito.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_top_nav.xml b/app/src/main/res/drawable/rounded_top_nav.xml index 2871859ac3..f5a8425bc2 100644 --- a/app/src/main/res/drawable/rounded_top_nav.xml +++ b/app/src/main/res/drawable/rounded_top_nav.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_corner_16dp.xml b/app/src/main/res/drawable/shape_corner_16dp.xml index 9fadb5ae15..5c0126c82f 100644 --- a/app/src/main/res/drawable/shape_corner_16dp.xml +++ b/app/src/main/res/drawable/shape_corner_16dp.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/spinner_icon.xml b/app/src/main/res/drawable/spinner_icon.xml index f67fdfae77..9e50e0f513 100644 --- a/app/src/main/res/drawable/spinner_icon.xml +++ b/app/src/main/res/drawable/spinner_icon.xml @@ -1,24 +1,24 @@ + android:paddingEnd="32dp" + android:paddingRight="0dp" + android:paddingMode="stack"> + android:gravity="end|center_vertical" + tools:ignore="UnusedAttribute" + tools:targetApi="m" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/spinner_icon_manga.xml b/app/src/main/res/drawable/spinner_icon_manga.xml index 26cd044e7e..da89f2f4ea 100644 --- a/app/src/main/res/drawable/spinner_icon_manga.xml +++ b/app/src/main/res/drawable/spinner_icon_manga.xml @@ -1,24 +1,24 @@ + android:paddingEnd="32dp" + android:paddingRight="0dp" + android:paddingMode="stack"> + android:gravity="end|center_vertical" + tools:ignore="UnusedAttribute" + tools:targetApi="m" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ui_bg.xml b/app/src/main/res/drawable/ui_bg.xml index 9c0ea97f56..d105a45629 100644 --- a/app/src/main/res/drawable/ui_bg.xml +++ b/app/src/main/res/drawable/ui_bg.xml @@ -1,32 +1,32 @@ + android:paddingEnd="32dp" + android:paddingRight="0dp" + android:paddingMode="stack" + android:tint="?attr/colorSecondary"> + android:end="32dp" + android:gravity="end|center_vertical" + tools:ignore="UnusedAttribute" + tools:targetApi="m"> + android:toDegrees="180.0" /> \ No newline at end of file diff --git a/app/src/main/res/font/poppins_family.xml b/app/src/main/res/font/poppins_family.xml index 043cc85f4f..df36ea5267 100644 --- a/app/src/main/res/font/poppins_family.xml +++ b/app/src/main/res/font/poppins_family.xml @@ -1,11 +1,11 @@ + app:fontWeight="400" /> + app:fontWeight="600" /> \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_media.xml b/app/src/main/res/layout-land/activity_media.xml index 08f6f37b1c..4c7dd3977c 100644 --- a/app/src/main/res/layout-land/activity_media.xml +++ b/app/src/main/res/layout-land/activity_media.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:orientation="horizontal" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:orientation="horizontal"> + app:itemTextColor="@color/tab_layout_icon" + app:menuGravity="center" /> @@ -173,7 +173,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical" - app:contentScrim="?android:colorBackground" android:ellipsize="marquee" android:focusable="true" android:focusableInTouchMode="true" @@ -187,6 +186,7 @@ android:text="@string/slogan" android:textSize="16sp" android:transitionName="mediaTitle" + app:contentScrim="?android:colorBackground" tools:visibility="gone" /> @@ -315,13 +315,13 @@ android:src="@drawable/ic_round_close_24" tools:ignore="ContentDescription" /> + - + android:paddingTop="16dp" + android:visibility="gone"> diff --git a/app/src/main/res/layout/activity_author.xml b/app/src/main/res/layout/activity_author.xml index 24e7acbbe3..76566c0f69 100644 --- a/app/src/main/res/layout/activity_author.xml +++ b/app/src/main/res/layout/activity_author.xml @@ -1,15 +1,15 @@ + android:layout_height="wrap_content" + android:background="?attr/colorSurface"> diff --git a/app/src/main/res/layout/activity_character.xml b/app/src/main/res/layout/activity_character.xml index 63d92ef02e..1bcdea25fe 100644 --- a/app/src/main/res/layout/activity_character.xml +++ b/app/src/main/res/layout/activity_character.xml @@ -27,6 +27,7 @@ app:layout_collapseMode="parallax" tools:ignore="ContentDescription,ImageContrastCheck" tools:srcCompat="@tools:sample/backgrounds/scenic[0]" /> + + app:layout_anchor="@id/characterTitle" + app:layout_anchorGravity="center_horizontal"> + android:layout_height="match_parent" + android:paddingTop="32dp" /> diff --git a/app/src/main/res/layout/activity_exoplayer.xml b/app/src/main/res/layout/activity_exoplayer.xml index 6f9ad79501..473e92cf25 100644 --- a/app/src/main/res/layout/activity_exoplayer.xml +++ b/app/src/main/res/layout/activity_exoplayer.xml @@ -12,6 +12,5 @@ android:gravity="center" app:animation_enabled="false" app:resize_mode="fit" - app:show_buffering="when_playing"> - + app:show_buffering="when_playing"> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_extensions.xml b/app/src/main/res/layout/activity_extensions.xml index f4611599ad..c5f884b184 100644 --- a/app/src/main/res/layout/activity_extensions.xml +++ b/app/src/main/res/layout/activity_extensions.xml @@ -27,8 +27,7 @@ android:layout_height="wrap_content" app:cardBackgroundColor="@color/nav_bg_inv" app:cardCornerRadius="16dp" - app:cardElevation="0dp" - > + app:cardElevation="0dp"> @@ -47,7 +46,7 @@ + android:layout_weight="1" /> + app:srcCompat="@drawable/ic_round_translate_24" + app:tint="?attr/colorOnBackground" /> - + tools:ignore="UseCompoundDrawables"> @@ -76,11 +74,12 @@ android:layout_width="match_parent" android:layout_height="48dp" app:tabContentStart="32dp" + app:tabGravity="fill" app:tabMode="scrollable" app:tabPaddingEnd="16dp" app:tabPaddingStart="16dp" - app:tabTextAppearance="@style/NavBarText" - app:tabGravity="fill"/> + app:tabTextAppearance="@style/NavBarText" /> + - + android:layout_weight="1" + android:visibility="gone"> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_faq.xml b/app/src/main/res/layout/activity_faq.xml index d280e8a7b3..a46d058576 100644 --- a/app/src/main/res/layout/activity_faq.xml +++ b/app/src/main/res/layout/activity_faq.xml @@ -1,9 +1,9 @@ @@ -11,24 +11,24 @@ android:id="@+id/devsTitle2" android:layout_width="match_parent" android:layout_height="64dp" - android:paddingStart="32dp" android:layout_marginTop="16dp" - android:paddingEnd="64dp" android:fontFamily="@font/poppins" android:gravity="center" + android:paddingStart="32dp" + android:paddingEnd="64dp" android:text="@string/faq" android:textSize="20sp" android:textStyle="bold" - app:drawableTint="?attr/colorOnBackground" - app:drawableStartCompat="@drawable/ic_round_arrow_back_ios_new_24" /> + app:drawableStartCompat="@drawable/ic_round_arrow_back_ios_new_24" + app:drawableTint="?attr/colorOnBackground" /> + app:iconTint="?attr/colorOnPrimary" /> + + + app:srcCompat="@drawable/ic_shuffle_24" + app:tint="?attr/colorOnBackground" /> + app:srcCompat="@drawable/ic_round_sort_24" + app:tint="?attr/colorOnBackground" /> + + tools:ignore="SpeakableTextPresentCheck" /> @@ -43,14 +44,14 @@ android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="center" - app:tint="@color/bg" android:src="@drawable/ic_incognito_24" + app:tint="@color/bg" tools:ignore="ContentDescription" /> + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/activity_manga_reader.xml b/app/src/main/res/layout/activity_manga_reader.xml index f1869d9881..a4162b2576 100644 --- a/app/src/main/res/layout/activity_manga_reader.xml +++ b/app/src/main/res/layout/activity_manga_reader.xml @@ -267,12 +267,12 @@ android:layout_height="wrap_content" android:layout_marginEnd="48dp" android:fontFamily="@font/poppins" - android:singleLine="false" - android:textColor="@color/bg_white" android:shadowColor="#000" android:shadowDx="1" android:shadowDy="1" android:shadowRadius="1" + android:singleLine="false" + android:textColor="@color/bg_white" android:textSize="12sp" tools:ignore="TextContrastCheck" tools:text="@string/popular_anime" /> @@ -289,7 +289,7 @@ android:layout_width="wrap_content" android:layout_height="48dp" android:backgroundTint="#00FFFFFF" - android:src="@drawable/ic_round_download_24" + android:src="@drawable/ic_download_24" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -361,8 +361,8 @@ app:labelStyle="@style/fontTooltip" app:thumbRadius="8dp" app:tickColor="#0000" - app:trackColorInactive="@color/grey_60" app:trackColorActive="?attr/colorPrimary" + app:trackColorInactive="@color/grey_60" app:trackHeight="2dp" /> diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index d1e3a6471c..47ac2dcabd 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -84,8 +84,8 @@ android:scrollHorizontally="false" android:singleLine="false" android:text="@string/slogan" - android:textSize="16sp" android:textColor="?attr/colorOnBackground" + android:textSize="16sp" android:transitionName="mediaTitle" /> @@ -155,7 +155,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical" - app:contentScrim="?android:colorBackground" android:background="?attr/colorSurface" android:ellipsize="marquee" android:focusable="true" @@ -170,6 +169,7 @@ android:text="@string/slogan" android:textSize="16sp" android:transitionName="mediaTitle" + app:contentScrim="?android:colorBackground" tools:visibility="gone" /> + app:layout_behavior="@string/appbar_scrolling_view_behavior"> @@ -315,12 +315,12 @@ tools:ignore="ContentDescription,ImageContrastCheck" tools:srcCompat="@tools:sample/backgrounds/scenic[2]" /> + - + android:paddingTop="16dp" + android:visibility="gone"> diff --git a/app/src/main/res/layout/activity_no_internet.xml b/app/src/main/res/layout/activity_no_internet.xml index eaeac1997b..25440a5010 100644 --- a/app/src/main/res/layout/activity_no_internet.xml +++ b/app/src/main/res/layout/activity_no_internet.xml @@ -1,9 +1,9 @@ + - + android:layout_height="match_parent" + android:paddingTop="32dp"> diff --git a/app/src/main/res/layout/activity_novel_reader.xml b/app/src/main/res/layout/activity_novel_reader.xml index 9bf9e9ae84..27bd87f7c6 100644 --- a/app/src/main/res/layout/activity_novel_reader.xml +++ b/app/src/main/res/layout/activity_novel_reader.xml @@ -105,7 +105,7 @@ android:layout_width="wrap_content" android:layout_height="48dp" android:backgroundTint="#00FFFFFF" - android:src="@drawable/ic_round_download_24" + android:src="@drawable/ic_download_24" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -176,8 +176,8 @@ app:labelStyle="@style/fontTooltip" app:thumbRadius="8dp" app:tickColor="#0000" - app:trackColorInactive="@color/grey_60" app:trackColorActive="?attr/colorPrimary" + app:trackColorInactive="@color/grey_60" app:trackHeight="2dp" /> @@ -301,7 +301,7 @@ + android:layout_height="wrap_content" + android:layout_gravity="center" /> diff --git a/app/src/main/res/layout/activity_player_settings.xml b/app/src/main/res/layout/activity_player_settings.xml index 5a5da5485a..8b76081ccc 100644 --- a/app/src/main/res/layout/activity_player_settings.xml +++ b/app/src/main/res/layout/activity_player_settings.xml @@ -1,9 +1,9 @@ @@ -73,462 +73,6 @@ android:clipToPadding="false" android:orientation="vertical"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -