Compare commits

..

8 Commits

Author SHA1 Message Date
rebel onion
61406ebf2c Merge branch 'dev' into custom-download-location 2024-04-04 04:03:34 -05:00
rebelonion
33ed647832 feat: changing the downloads dir 2024-04-04 02:21:43 -05:00
rebelonion
79113cb04a fix: notification incrementing 2024-04-04 01:15:18 -05:00
rebelonion
93172153b7 chore: clean manifest 2024-04-04 01:03:26 -05:00
rebelonion
9e6f71feb2 feat: novel to new system | load freezing 2024-04-04 01:02:37 -05:00
rebelonion
bf73bbb98b fix: offline page for new download system 2024-04-03 23:53:03 -05:00
rebelonion
fb903c56aa fix: send headers to ffmpeg
ffmpeg can be a real bitch to work with
2024-04-02 16:21:48 -05:00
rebelonion
bcbcf1a829 feat: custom downloader (novel broken) 2024-04-02 08:04:24 -05:00
327 changed files with 5389 additions and 29026 deletions

View File

@@ -48,11 +48,9 @@ jobs:
echo "COMMIT_LOG=${COMMIT_LOGS}" >> $GITHUB_ENV echo "COMMIT_LOG=${COMMIT_LOGS}" >> $GITHUB_ENV
# Debugging: Print the variable to check its content # Debugging: Print the variable to check its content
echo "$COMMIT_LOGS" echo "$COMMIT_LOGS"
echo "$COMMIT_LOGS" > commit_log.txt
shell: /usr/bin/bash -e {0} shell: /usr/bin/bash -e {0}
env: env:
CI: true CI: true
continue-on-error: true
- name: Save Current SHA for Next Run - name: Save Current SHA for Next Run
run: echo ${{ github.sha }} > last_sha.txt run: echo ${{ github.sha }} > last_sha.txt
@@ -77,7 +75,7 @@ jobs:
- name: List files in the directory - name: List files in the directory
run: ls -l run: ls -l
- name: Make gradlew executable - name: Make gradlew executable
run: chmod +x ./gradlew run: chmod +x ./gradlew
@@ -85,11 +83,9 @@ jobs:
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 }} run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
- name: Upload a Build Artifact - name: Upload a Build Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4.3.1
with: with:
name: Dantotsu name: Dantotsu
retention-days: 5
compression-level: 9
path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk" path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk"
- name: Upload APK to Discord and Telegram - name: Upload APK to Discord and Telegram
@@ -103,7 +99,7 @@ jobs:
if [ ${#commit_messages} -gt $max_length ]; then if [ ${#commit_messages} -gt $max_length ]; then
commit_messages="${commit_messages:0:$max_length}... (truncated)" commit_messages="${commit_messages:0:$max_length}... (truncated)"
fi fi
contentbody=$( jq -nc --arg msg "Alpha-Build: <@&1225347048321191996> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' ) contentbody=$( jq -nc --arg msg "Alpha-Build: <@714249925248024617> **$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 }} curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }}
#Telegram #Telegram
@@ -117,13 +113,18 @@ jobs:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
- name: Upload Current SHA as Artifact - name: Upload Current SHA as Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: last-sha name: last-sha
path: last_sha.txt path: last_sha.txt
- name: Upload Commit log as Artifact
uses: actions/upload-artifact@v4 - name: Delete Old Pre-Releases
id: delete-pre-releases
uses: sgpublic/delete-release-action@master
with: with:
name: commit-log pre-release-drop: true
path: commit_log.txt pre-release-keep-count: 3
pre-release-drop-tag: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -31,6 +31,3 @@ output.json
#other #other
scripts/ scripts/
#crowdin
crowdin.yml

View File

@@ -21,7 +21,14 @@ android {
versionName "3.0.0" versionName "3.0.0"
versionCode 300000000 versionCode 300000000
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
splits {
abi {
enable true
reset()
include 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
} }
flavorDimensions += "store" flavorDimensions += "store"
@@ -81,9 +88,9 @@ android {
dependencies { dependencies {
// FireBase // FireBase
googleImplementation platform('com.google.firebase:firebase-bom:32.8.1') googleImplementation platform('com.google.firebase:firebase-bom:32.7.4')
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.6.2' googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1'
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.4' googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2'
// Core // Core
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.browser:browser:1.8.0'
@@ -111,7 +118,7 @@ dependencies {
implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'jp.wasabeef:glide-transformations:4.3.0'
// Exoplayer // Exoplayer
ext.exo_version = '1.3.1' ext.exo_version = '1.3.0'
implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-exoplayer:$exo_version"
implementation "androidx.media3:media3-ui:$exo_version" implementation "androidx.media3:media3-ui:$exo_version"
implementation "androidx.media3:media3-exoplayer-hls:$exo_version" implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
@@ -131,17 +138,17 @@ dependencies {
implementation 'com.github.VipulOG:ebook-reader:0.1.6' implementation 'com.github.VipulOG:ebook-reader:0.1.6'
implementation 'androidx.paging:paging-runtime-ktx:3.2.1' implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
implementation 'com.github.eltos:simpledialogfragments:v3.7' implementation 'com.github.eltos:simpledialogfragments:v3.7'
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.1' implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc'
// Markwon // Markwon
ext.markwon_version = '4.6.2' ext.markwon_version = '4.6.2'
implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:editor:$markwon_version" implementation "io.noties.markwon:editor:$markwon_version"
implementation "io.noties.markwon:ext-strikethrough:$markwon_version" implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
implementation "io.noties.markwon:ext-tables:$markwon_version" implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:ext-tasklist:$markwon_version" implementation "io.noties.markwon:ext-tasklist:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version" implementation "io.noties.markwon:html:$markwon_version"
implementation "io.noties.markwon:image-glide:$markwon_version" implementation "io.noties.markwon:image-glide:$markwon_version"
// Groupie // Groupie
ext.groupie_version = '2.10.1' ext.groupie_version = '2.10.1'
@@ -151,6 +158,9 @@ dependencies {
// String Matching // String Matching
implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation group: 'com.arthenica', name: 'ffmpeg-kit-full-gpl', version: '6.0-2.LTS'
//implementation 'com.github.yausername.youtubedl-android:library:0.15.0'
// Aniyomi // Aniyomi
implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'

View File

@@ -5,12 +5,12 @@ import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.app
class FirebaseCrashlytics : CrashlyticsInterface { class FirebaseCrashlytics : CrashlyticsInterface {
override fun initialize(context: Context) { override fun initialize(context: Context) {
FirebaseApp.initializeApp(context) FirebaseApp.initializeApp(context)
} }
override fun logException(e: Throwable) { override fun logException(e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e) FirebaseCrashlytics.getInstance().recordException(e)
} }

View File

@@ -85,18 +85,13 @@ object AppUpdater {
setPositiveButton(currContext()!!.getString(R.string.lets_go)) { setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
MainScope().launch(Dispatchers.IO) { MainScope().launch(Dispatchers.IO) {
try { try {
val apks = client.get("https://api.github.com/repos/$repo/releases/tags/v$version")
client.get("https://api.github.com/repos/$repo/releases/tags/v$version") .parsed<GithubResponse>().assets?.find {
.parsed<GithubResponse>().assets?.filter { it.browserDownloadURL.endsWith("apk")
it.browserDownloadURL.endsWith( }?.browserDownloadURL.apply {
".apk" if (this != null) activity.downloadUpdate(version, this)
) else openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
} }
val apkToDownload = apks?.first()
apkToDownload?.browserDownloadURL.apply {
if (this != null) activity.downloadUpdate(version, this)
else openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
}
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
@@ -116,25 +111,24 @@ object AppUpdater {
} }
private fun compareVersion(version: String): Boolean { private fun compareVersion(version: String): Boolean {
return when (BuildConfig.BUILD_TYPE) {
"debug" -> BuildConfig.VERSION_NAME != version
"alpha" -> false
else -> {
fun toDouble(list: List<String>): Double {
return list.mapIndexed { i: Int, s: String ->
when (i) {
0 -> s.toDouble() * 100
1 -> s.toDouble() * 10
2 -> s.toDouble()
else -> s.toDoubleOrNull() ?: 0.0
}
}.sum()
}
val new = toDouble(version.split(".")) if (BuildConfig.DEBUG) {
val curr = toDouble(BuildConfig.VERSION_NAME.split(".")) return BuildConfig.VERSION_NAME != version
new > curr } else {
fun toDouble(list: List<String>): Double {
return list.mapIndexed { i: Int, s: String ->
when (i) {
0 -> s.toDouble() * 100
1 -> s.toDouble() * 10
2 -> s.toDouble()
else -> s.toDoubleOrNull() ?: 0.0
}
}.sum()
} }
val new = toDouble(version.split("."))
val curr = toDouble(BuildConfig.VERSION_NAME.split("."))
return new > curr
} }
} }

View File

@@ -2,8 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="go.server.gojni" />
<uses-feature <uses-feature
android:name="android.software.leanback" android:name="android.software.leanback"
android:required="false" /> android:required="false" />
@@ -40,17 +38,6 @@
android:name="android.permission.READ_APP_SPECIFIC_LOCALES" android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<!-- ExoPlayer: Bluetooth Headsets -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- ExoPlayer: Bluetooth Headsets -->
<queries> <queries>
<package android:name="idm.internet.download.manager.plus" /> <package android:name="idm.internet.download.manager.plus" />
<package android:name="idm.internet.download.manager" /> <package android:name="idm.internet.download.manager" />
@@ -92,7 +79,7 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
</activity> </activity>
<receiver <receiver
android:name=".widgets.statistics.ProfileStatsWidget" android:name=".widgets.statistics.ProfileStatsWidget"
android:exported="false"> android:exported="false">
@@ -136,33 +123,6 @@
<activity <activity
android:name=".settings.SettingsActivity" android:name=".settings.SettingsActivity"
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsAboutActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsAccountActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsAnimeActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsCommonActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsExtensionsActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsAddonActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsMangaActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsNotificationActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".settings.SettingsThemeActivity"
android:parentActivityName=".MainActivity" />
<activity <activity
android:name=".settings.ExtensionsActivity" android:name=".settings.ExtensionsActivity"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
@@ -171,7 +131,7 @@
android:name=".widgets.statistics.ProfileStatsConfigure" android:name=".widgets.statistics.ProfileStatsConfigure"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter> </intent-filter>
</activity> </activity>
@@ -434,11 +394,6 @@
android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService" android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
android:exported="true" android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" /> android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".addons.torrent.ServerService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:stopWithTask="true" />
<meta-data <meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"

View File

@@ -6,8 +6,6 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.aniyomi.anime.custom.AppModule import ani.dantotsu.aniyomi.anime.custom.AppModule
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.connections.comments.CommentsAPI
@@ -43,9 +41,6 @@ class App : MultiDexApplication() {
private lateinit var animeExtensionManager: AnimeExtensionManager private lateinit var animeExtensionManager: AnimeExtensionManager
private lateinit var mangaExtensionManager: MangaExtensionManager private lateinit var mangaExtensionManager: MangaExtensionManager
private lateinit var novelExtensionManager: NovelExtensionManager private lateinit var novelExtensionManager: NovelExtensionManager
private lateinit var torrentAddonManager: TorrentAddonManager
private lateinit var downloadAddonManager: DownloadAddonManager
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base) super.attachBaseContext(base)
MultiDex.install(this) MultiDex.install(this)
@@ -101,8 +96,6 @@ class App : MultiDexApplication() {
animeExtensionManager = Injekt.get() animeExtensionManager = Injekt.get()
mangaExtensionManager = Injekt.get() mangaExtensionManager = Injekt.get()
novelExtensionManager = Injekt.get() novelExtensionManager = Injekt.get()
torrentAddonManager = Injekt.get()
downloadAddonManager = Injekt.get()
val animeScope = CoroutineScope(Dispatchers.Default) val animeScope = CoroutineScope(Dispatchers.Default)
animeScope.launch { animeScope.launch {
@@ -122,11 +115,6 @@ class App : MultiDexApplication() {
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}") Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
NovelSources.init(novelExtensionManager.installedExtensionsFlow) NovelSources.init(novelExtensionManager.installedExtensionsFlow)
} }
val addonScope = CoroutineScope(Dispatchers.Default)
addonScope.launch {
torrentAddonManager.init()
downloadAddonManager.init()
}
val commentsScope = CoroutineScope(Dispatchers.Default) val commentsScope = CoroutineScope(Dispatchers.Default)
commentsScope.launch { commentsScope.launch {
CommentsAPI.fetchAuthToken() CommentsAPI.fetchAuthToken()

View File

@@ -16,6 +16,7 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.Resources.getSystem import android.content.res.Resources.getSystem
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
@@ -67,9 +68,12 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.math.MathUtils.clamp import androidx.core.math.MathUtils.clamp
@@ -88,7 +92,6 @@ import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.BuildConfig.APPLICATION_ID import ani.dantotsu.BuildConfig.APPLICATION_ID
import ani.dantotsu.connections.anilist.Genre import ani.dantotsu.connections.anilist.Genre
import ani.dantotsu.connections.anilist.api.FuzzyDate import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.bakaupdates.MangaUpdates
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.ItemCountDownBinding import ani.dantotsu.databinding.ItemCountDownBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@@ -99,7 +102,6 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.util.CountUpTimer
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
@@ -132,12 +134,9 @@ import io.noties.markwon.html.TagHandlerNoOp
import io.noties.markwon.image.AsyncDrawable import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.glide.GlideImagesPlugin import io.noties.markwon.image.glide.GlideImagesPlugin
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -185,10 +184,9 @@ fun currActivity(): Activity? {
var loadMedia: Int? = null var loadMedia: Int? = null
var loadIsMAL = false var loadIsMAL = false
val Int.toPx val Int.toPx get() = TypedValue.applyDimension(
get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), getSystem().displayMetrics ).toInt()
).toInt()
fun initActivity(a: Activity) { fun initActivity(a: Activity) {
val window = a.window val window = a.window
@@ -217,8 +215,7 @@ fun initActivity(a: Activity) {
window.decorView window.decorView
).hide(WindowInsetsCompat.Type.statusBars()) ).hide(WindowInsetsCompat.Type.statusBars())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && statusBarHeight == 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && statusBarHeight == 0
&& a.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && a.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
) {
window.decorView.rootWindowInsets?.displayCutout?.apply { window.decorView.rootWindowInsets?.displayCutout?.apply {
if (boundingRects.size > 0) { if (boundingRects.size > 0) {
statusBarHeight = min(boundingRects[0].width(), boundingRects[0].height()) statusBarHeight = min(boundingRects[0].width(), boundingRects[0].height())
@@ -294,12 +291,7 @@ fun ViewGroup.setBaseline(navBar: AnimatedBottomBar, overlayView: View) {
navBar.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) navBar.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
overlayView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) overlayView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
clipToPadding = false clipToPadding = false
setPadding( setPadding(paddingLeft, paddingTop, paddingRight, navBarHeight + navBar.measuredHeight + overlayView.measuredHeight)
paddingLeft,
paddingTop,
paddingRight,
navBarHeight + navBar.measuredHeight + overlayView.measuredHeight
)
} }
fun Activity.reloadActivity() { fun Activity.reloadActivity() {
@@ -309,19 +301,23 @@ fun Activity.reloadActivity() {
initActivity(this) initActivity(this)
} }
fun Activity.restartApp() { fun Context.restartApp(view: View) {
val mainIntent = Intent.makeRestartActivityTask( val mainIntent = Intent.makeRestartActivityTask(
packageManager.getLaunchIntentForPackage(this.packageName)!!.component packageManager.getLaunchIntentForPackage(this.packageName)!!.component
) )
val component = val component = ComponentName(this@restartApp.packageName, this@restartApp::class.qualifiedName!!)
ComponentName(this@restartApp.packageName, this@restartApp::class.qualifiedName!!) Snackbar.make(view, R.string.restart_app, Snackbar.LENGTH_INDEFINITE).apply {
try { setAction(R.string.do_it) {
startActivity(Intent().setComponent(component)) this.dismiss()
} catch (e: Exception) { try {
startActivity(mainIntent) startActivity(Intent().setComponent(component))
} catch (anything: Exception) {
startActivity(mainIntent)
}
Runtime.getRuntime().exit(0)
}
show()
} }
finishAndRemoveTask()
PrefManager.setCustomVal("reload", true)
} }
open class BottomSheetDialogFragment : BottomSheetDialogFragment() { open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
@@ -468,7 +464,7 @@ class InputFilterMinMax(
} }
class ZoomOutPageTransformer : class ZoomOutPageTransformer() :
ViewPager2.PageTransformer { ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) { override fun transformPage(view: View, position: Float) {
if (position == 0.0f && PrefManager.getVal(PrefName.LayoutAnimations)) { if (position == 0.0f && PrefManager.getVal(PrefName.LayoutAnimations)) {
@@ -957,8 +953,7 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
fun countDown(media: Media, view: ViewGroup) { fun countDown(media: Media, view: ViewGroup) {
if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null
&& (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 28.toLong() && (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 28.toLong()) {
) {
val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false) val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
view.addView(v.root, 0) view.addView(v.root, 0)
v.mediaCountdownText.text = v.mediaCountdownText.text =
@@ -990,50 +985,6 @@ fun countDown(media: Media, view: ViewGroup) {
} }
} }
fun sinceWhen(media: Media, view: ViewGroup) {
if (media.status != "RELEASING" && media.status != "HIATUS") return
CoroutineScope(Dispatchers.IO).launch {
MangaUpdates().search(media.mangaName(), media.startDate)?.let {
val latestChapter = MangaUpdates.getLatestChapter(view.context, it)
val timeSince = (System.currentTimeMillis() -
(it.metadata.series.lastUpdated!!.timestamp * 1000)) / 1000
withContext(Dispatchers.Main) {
val v =
ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
view.addView(v.root, 0)
v.mediaCountdownText.text =
currActivity()?.getString(R.string.chapter_release_timeout, latestChapter)
object : CountUpTimer(86400000) {
override fun onTick(second: Int) {
val a = second + timeSince
v.mediaCountdown.text = currActivity()?.getString(
R.string.time_format,
a / 86400,
a % 86400 / 3600,
a % 86400 % 3600 / 60,
a % 86400 % 3600 % 60
)
}
override fun onFinish() {
// The legend will never die.
}
}.start()
}
}
}
}
fun displayTimer(media: Media, view: ViewGroup) {
when {
media.anime != null -> countDown(media, view)
media.format == "MANGA" || media.format == "ONE_SHOT" -> sinceWhen(media, view)
else -> {} // No timer yet
}
}
fun MutableMap<String, Genre>.checkId(id: Int): Boolean { fun MutableMap<String, Genre>.checkId(id: Int): Boolean {
this.forEach { this.forEach {
if (it.value.id == id) { if (it.value.id == id) {
@@ -1350,11 +1301,7 @@ fun blurImage(imageView: ImageView, banner: String?) {
if (!(context as Activity).isDestroyed) { if (!(context as Activity).isDestroyed) {
val url = PrefManager.getVal<String>(PrefName.ImageUrl).ifEmpty { banner } val url = PrefManager.getVal<String>(PrefName.ImageUrl).ifEmpty { banner }
Glide.with(context as Context) Glide.with(context as Context)
.load( .load(GlideUrl(url))
if (banner.startsWith("http")) GlideUrl(url) else if (banner.startsWith("content://")) Uri.parse(
url
) else File(url)
)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(400) .diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(400)
.apply(RequestOptions.bitmapTransform(BlurTransformation(radius, sampling))) .apply(RequestOptions.bitmapTransform(BlurTransformation(radius, sampling)))
.into(imageView) .into(imageView)
@@ -1440,4 +1387,4 @@ fun buildMarkwon(
})) }))
.build() .build()
return markwon return markwon
} }

View File

@@ -3,6 +3,7 @@ package ani.dantotsu
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
@@ -19,6 +20,7 @@ import android.view.ViewGroup
import android.view.animation.AnticipateInterpolator import android.view.animation.AnticipateInterpolator
import android.widget.TextView import android.widget.TextView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -33,14 +35,14 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.Download
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import ani.dantotsu.addons.torrent.ServerService
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistHomeViewModel import ani.dantotsu.connections.anilist.AnilistHomeViewModel
import ani.dantotsu.databinding.ActivityMainBinding import ani.dantotsu.databinding.ActivityMainBinding
import ani.dantotsu.databinding.SplashScreenBinding import ani.dantotsu.databinding.SplashScreenBinding
import ani.dantotsu.download.video.Helper
import ani.dantotsu.home.AnimeFragment import ani.dantotsu.home.AnimeFragment
import ani.dantotsu.home.HomeFragment import ani.dantotsu.home.HomeFragment
import ani.dantotsu.home.LoginFragment import ani.dantotsu.home.LoginFragment
@@ -68,13 +70,11 @@ import com.google.android.material.textfield.TextInputEditText
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
import tachiyomi.core.util.lang.launchIO
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.Serializable import java.io.Serializable
@@ -87,7 +87,6 @@ class MainActivity : AppCompatActivity() {
private var load = false private var load = false
@kotlin.OptIn(DelicateCoroutinesApi::class)
@SuppressLint("InternalInsetResource", "DiscouragedApi") @SuppressLint("InternalInsetResource", "DiscouragedApi")
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -300,7 +299,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
var launched = false
intent.extras?.let { extras -> intent.extras?.let { extras ->
val fragmentToLoad = extras.getString("FRAGMENT_TO_LOAD") val fragmentToLoad = extras.getString("FRAGMENT_TO_LOAD")
val mediaId = extras.getInt("mediaId", -1) val mediaId = extras.getInt("mediaId", -1)
@@ -313,7 +311,6 @@ class MainActivity : AppCompatActivity() {
putExtra("mediaId", mediaId) putExtra("mediaId", mediaId)
putExtra("commentId", commentId) putExtra("commentId", commentId)
} }
launched = true
startActivity(detailIntent) startActivity(detailIntent)
} else if (fragmentToLoad == "FEED" && activityId != -1) { } else if (fragmentToLoad == "FEED" && activityId != -1) {
val feedIntent = Intent(this, FeedActivity::class.java).apply { val feedIntent = Intent(this, FeedActivity::class.java).apply {
@@ -321,7 +318,6 @@ class MainActivity : AppCompatActivity() {
putExtra("activityId", activityId) putExtra("activityId", activityId)
} }
launched = true
startActivity(feedIntent) startActivity(feedIntent)
} else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) { } else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) {
Logger.log("MainActivity, onCreate: $activityId") Logger.log("MainActivity, onCreate: $activityId")
@@ -329,7 +325,6 @@ class MainActivity : AppCompatActivity() {
putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS") putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS")
putExtra("activityId", activityId) putExtra("activityId", activityId)
} }
launched = true
startActivity(notificationIntent) startActivity(notificationIntent)
} }
} }
@@ -383,7 +378,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
//Load Data //Load Data
if (!load && !launched) { if (!load) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
model.loadMain(this@MainActivity) model.loadMain(this@MainActivity)
val id = intent.extras?.getInt("mediaId", 0) val id = intent.extras?.getInt("mediaId", 0)
@@ -454,26 +449,16 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
/*lifecycleScope.launch(Dispatchers.IO) { //simple cleanup
val torrentManager = Injekt.get<TorrentAddonManager>() val index = Helper.downloadManager(this@MainActivity).downloadIndex
fun startTorrent() { val downloadCursor = index.getDownloads()
if (torrentManager.isAvailable() && PrefManager.getVal(PrefName.TorrentEnabled)) { while (downloadCursor.moveToNext()) {
launchIO { val download = downloadCursor.download
if (!ServerService.isRunning()) { if (download.state == Download.STATE_FAILED) {
ServerService.start() Helper.downloadManager(this@MainActivity).removeDownload(download.request.id)
}
} }
} }
} }*/ //TODO: remove this
if (torrentManager.isInitialized.value == false) {
torrentManager.isInitialized.observe(this) {
if (it) {
startTorrent()
}
}
} else {
startTorrent()
}
} }
override fun onRestart() { override fun onRestart() {
@@ -484,7 +469,7 @@ class MainActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 8 else 32 val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 8 else 32
val params: ViewGroup.MarginLayoutParams = val params : ViewGroup.MarginLayoutParams =
binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams
params.updateMargins(bottom = margin.toPx) params.updateMargins(bottom = margin.toPx)
} }

View File

@@ -1,15 +0,0 @@
package ani.dantotsu.addons
abstract class Addon {
abstract val name: String
abstract val pkgName: String
abstract val versionName: String
abstract val versionCode: Long
abstract class Installed(
override val name: String,
override val pkgName: String,
override val versionName: String,
override val versionCode: Long,
) : Addon()
}

View File

@@ -1,129 +0,0 @@
package ani.dantotsu.addons
import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import ani.dantotsu.Mapper
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.logError
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.others.AppUpdater
import ani.dantotsu.settings.InstallerSteps
import ani.dantotsu.toast
import ani.dantotsu.util.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement
import rx.android.schedulers.AndroidSchedulers
class AddonDownloader {
companion object {
private suspend fun check(repo: String): Pair<String, String> {
return try {
val res = client.get("https://api.github.com/repos/$repo/releases")
.parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<AppUpdater.GithubResponse>(it)
}
val r = res.maxByOrNull {
it.timeStamp()
} ?: throw Exception("No Pre Release Found")
val v = r.tagName.substringAfter("v", "")
val md = r.body ?: ""
val version = v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
Logger.log("Git Version : $version")
Pair(md, version)
} catch (e: Exception) {
Logger.log("Error checking for update")
Logger.log(e)
Pair("", "")
}
}
suspend fun hasUpdate(repo: String, currentVersion: String): Boolean {
val (_, version) = check(repo)
return compareVersion(version, currentVersion)
}
suspend fun update(
activity: Activity,
manager: AddonManager<*>,
repo: String,
currentVersion: String
) {
val (_, version) = check(repo)
if (!compareVersion(version, currentVersion)) {
toast(activity.getString(R.string.no_update_found))
return
}
MainScope().launch(Dispatchers.IO) {
try {
val apks =
client.get("https://api.github.com/repos/$repo/releases/tags/v$version")
.parsed<AppUpdater.GithubResponse>().assets?.filter {
it.browserDownloadURL.endsWith(
".apk"
)
}
val apkToDownload =
apks?.find { it.browserDownloadURL.contains(getCurrentABI()) }
?: apks?.find { it.browserDownloadURL.contains("universal") }
?: apks?.first()
apkToDownload?.browserDownloadURL.apply {
if (this != null) {
val notificationManager =
activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val installerSteps = InstallerSteps(notificationManager, activity)
manager.install(this)
.observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ installStep -> installerSteps.onInstallStep(installStep) {} },
{ error -> installerSteps.onError(error) {} },
{ installerSteps.onComplete {} }
)
} else openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
}
} catch (e: Exception) {
logError(e)
}
}
}
/**
* Returns the ABI that the app is most likely running on.
* @return The primary ABI for the device.
*/
private fun getCurrentABI(): String {
return if (Build.SUPPORTED_ABIS.isNotEmpty()) {
Build.SUPPORTED_ABIS[0]
} else "Unknown"
}
private fun compareVersion(newVersion: String, oldVersion: String): Boolean {
fun toDouble(list: List<String>): Double {
return try {
list.mapIndexed { i: Int, s: String ->
when (i) {
0 -> s.toDouble() * 100
1 -> s.toDouble() * 10
2 -> s.toDouble()
else -> s.toDoubleOrNull() ?: 0.0
}
}.sum()
} catch (e: NumberFormatException) {
0.0
}
}
val new = toDouble(newVersion.split("."))
val curr = toDouble(oldVersion.split("."))
return new > curr
}
}
}

View File

@@ -1,11 +0,0 @@
package ani.dantotsu.addons
interface AddonListener {
fun onAddonInstalled(result: LoadResult?)
fun onAddonUpdated(result: LoadResult?)
fun onAddonUninstalled(pkgName: String)
enum class ListenerAction {
INSTALL, UPDATE, UNINSTALL
}
}

View File

@@ -1,143 +0,0 @@
package ani.dantotsu.addons
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.pm.PackageInfoCompat
import ani.dantotsu.addons.download.DownloadAddon
import ani.dantotsu.addons.download.DownloadAddonApi
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.download.DownloadLoadResult
import ani.dantotsu.addons.torrent.TorrentAddon
import ani.dantotsu.addons.torrent.TorrentAddonApi
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.addons.torrent.TorrentLoadResult
import ani.dantotsu.media.AddonType
import ani.dantotsu.util.Logger
import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.system.getApplicationIcon
class AddonLoader {
companion object {
fun loadExtension(
context: Context,
packageName: String,
className: String,
type: AddonType
): LoadResult? {
val pkgManager = context.packageManager
val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(ExtensionLoader.PACKAGE_FLAGS.toLong()))
} else {
pkgManager.getInstalledPackages(ExtensionLoader.PACKAGE_FLAGS)
}
val extPkgs = installedPkgs.filter {
isPackageAnExtension(
packageName,
it
)
}
if (extPkgs.isEmpty()) return null
if (extPkgs.size > 1) throw IllegalStateException("Multiple extensions with the same package name found")
val pkgName = extPkgs.first().packageName
val pkgInfo = extPkgs.first()
val appInfo = try {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
Logger.log(error)
throw error
}
val extName =
pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Dantotsu: ")
val versionName = pkgInfo.versionName
val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo)
if (versionName.isNullOrEmpty()) {
Logger.log("Missing versionName for extension $extName")
throw IllegalStateException("Missing versionName for extension $extName")
}
val classLoader =
PathClassLoader(appInfo.sourceDir, appInfo.nativeLibraryDir, context.classLoader)
val loadedClass = try {
Class.forName(className, false, classLoader)
} catch (e: ClassNotFoundException) {
Logger.log("Extension load error: $extName ($className)")
Logger.log(e)
throw e
} catch (e: NoClassDefFoundError) {
Logger.log("Extension load error: $extName ($className)")
Logger.log(e)
throw e
} catch (e: Exception) {
Logger.log("Extension load error: $extName ($className)")
Logger.log(e)
throw e
}
val instance = loadedClass.getDeclaredConstructor().newInstance()
return when (type) {
AddonType.TORRENT -> {
val extension = instance as? TorrentAddonApi
?: throw IllegalStateException("Extension is not a TorrentAddonApi")
TorrentLoadResult.Success(
TorrentAddon.Installed(
name = extName,
pkgName = pkgName,
versionName = versionName,
versionCode = versionCode,
extension = extension,
icon = context.getApplicationIcon(pkgName),
)
)
}
AddonType.DOWNLOAD -> {
val extension = instance as? DownloadAddonApi
?: throw IllegalStateException("Extension is not a DownloadAddonApi")
DownloadLoadResult.Success(
DownloadAddon.Installed(
name = extName,
pkgName = pkgName,
versionName = versionName,
versionCode = versionCode,
extension = extension,
icon = context.getApplicationIcon(pkgName),
)
)
}
}
}
fun loadFromPkgName(context: Context, packageName: String, type: AddonType): LoadResult? {
return when (type) {
AddonType.TORRENT -> loadExtension(
context,
packageName,
TorrentAddonManager.TORRENT_CLASS,
type
)
AddonType.DOWNLOAD -> loadExtension(
context,
packageName,
DownloadAddonManager.DOWNLOAD_CLASS,
type
)
}
}
private fun isPackageAnExtension(type: String, pkgInfo: PackageInfo): Boolean {
return pkgInfo.packageName.equals(type)
}
}
}

View File

@@ -1,46 +0,0 @@
package ani.dantotsu.addons
import android.content.Context
import ani.dantotsu.media.AddonType
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import rx.Observable
abstract class AddonManager<T : Addon.Installed>(
private val context: Context
) {
abstract var extension: T?
abstract var name: String
abstract var type: AddonType
protected val installer by lazy { ExtensionInstaller(context) }
var hasUpdate: Boolean = false
protected set
protected var onListenerAction: ((AddonListener.ListenerAction) -> Unit)? = null
abstract suspend fun init()
abstract fun isAvailable(): Boolean
abstract fun getVersion(): String?
abstract fun getPackageName(): String?
abstract fun hadError(context: Context): String?
abstract fun updateInstallStep(id: Long, step: InstallStep)
abstract fun setInstalling(id: Long)
fun uninstall() {
getPackageName()?.let {
installer.uninstallApk(it)
}
}
fun addListenerAction(action: (AddonListener.ListenerAction) -> Unit) {
onListenerAction = action
}
fun removeListenerAction() {
onListenerAction = null
}
fun install(url: String): Observable<InstallStep> {
return installer.downloadAndInstall(url, getPackageName() ?: "", name, type)
}
}

View File

@@ -1,8 +0,0 @@
package ani.dantotsu.addons
abstract class LoadResult {
abstract class Success : LoadResult()
}

View File

@@ -1,133 +0,0 @@
package ani.dantotsu.addons.download
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import ani.dantotsu.addons.AddonListener
import ani.dantotsu.addons.AddonLoader
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.media.AddonType
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver.Companion.filter
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver.Companion.getPackageNameFromIntent
import kotlinx.coroutines.DelicateCoroutinesApi
import tachiyomi.core.util.lang.launchNow
internal class AddonInstallReceiver : BroadcastReceiver() {
private var listener: AddonListener? = null
private var type: AddonType? = null
/**
* Registers this broadcast receiver
*/
fun register(context: Context) {
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_EXPORTED)
}
fun setListener(listener: AddonListener, type: AddonType): AddonInstallReceiver {
this.listener = listener
this.type = type
return this
}
/**
* Called when one of the events of the [filter] is received. When the package is an extension,
* it's loaded in background and it notifies the [listener] when finished.
*/
@OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent?) {
if (intent == null) return
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED -> {
if (ExtensionInstallReceiver.isReplacing(intent)) return
launchNow {
when (type) {
AddonType.DOWNLOAD -> {
getPackageNameFromIntent(intent)?.let { packageName ->
if (packageName != DownloadAddonManager.DOWNLOAD_PACKAGE) return@launchNow
listener?.onAddonInstalled(
AddonLoader.loadFromPkgName(
context,
packageName,
AddonType.DOWNLOAD
)
)
}
}
AddonType.TORRENT -> {
getPackageNameFromIntent(intent)?.let { packageName ->
if (packageName != TorrentAddonManager.TORRENT_PACKAGE) return@launchNow
listener?.onAddonInstalled(
AddonLoader.loadFromPkgName(
context,
packageName,
AddonType.TORRENT
)
)
}
}
else -> {}
}
}
}
Intent.ACTION_PACKAGE_REPLACED -> {
if (ExtensionInstallReceiver.isReplacing(intent)) return
launchNow {
when (type) {
AddonType.DOWNLOAD -> {
getPackageNameFromIntent(intent)?.let { packageName ->
if (packageName != DownloadAddonManager.DOWNLOAD_PACKAGE) return@launchNow
listener?.onAddonUpdated(
AddonLoader.loadFromPkgName(
context,
packageName,
AddonType.DOWNLOAD
)
)
}
}
AddonType.TORRENT -> {
getPackageNameFromIntent(intent)?.let { packageName ->
if (packageName != TorrentAddonManager.TORRENT_PACKAGE) return@launchNow
listener?.onAddonUpdated(
AddonLoader.loadFromPkgName(
context,
packageName,
AddonType.TORRENT
)
)
}
}
else -> {}
}
}
}
Intent.ACTION_PACKAGE_REMOVED -> {
if (ExtensionInstallReceiver.isReplacing(intent)) return
getPackageNameFromIntent(intent)?.let { packageName ->
when (type) {
AddonType.DOWNLOAD -> {
if (packageName != DownloadAddonManager.DOWNLOAD_PACKAGE) return
listener?.onAddonUninstalled(packageName)
}
AddonType.TORRENT -> {
if (packageName != TorrentAddonManager.TORRENT_PACKAGE) return
listener?.onAddonUninstalled(packageName)
}
else -> {}
}
}
}
}
}
}

View File

@@ -1,18 +0,0 @@
package ani.dantotsu.addons.download
import android.graphics.drawable.Drawable
import ani.dantotsu.addons.Addon
sealed class DownloadAddon : Addon() {
data class Installed(
override val name: String,
override val pkgName: String,
override val versionName: String,
override val versionCode: Long,
val extension: DownloadAddonApi,
val icon: Drawable?,
val hasUpdate: Boolean = false,
) : Addon.Installed(name, pkgName, versionName, versionCode)
}

View File

@@ -1,21 +0,0 @@
package ani.dantotsu.addons.download
import android.content.Context
import android.net.Uri
interface DownloadAddonApi {
fun cancelDownload(sessionId: Long)
fun setDownloadPath(context: Context, uri: Uri): String
suspend fun executeFFProbe(request: String, logCallback: (String) -> Unit)
suspend fun executeFFMpeg(request: String, statCallback: (Double) -> Unit): Long
fun getState(sessionId: Long): String
fun getStackTrace(sessionId: Long): String?
fun hadError(sessionId: Long): Boolean
}

View File

@@ -1,133 +0,0 @@
package ani.dantotsu.addons.download
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import ani.dantotsu.R
import ani.dantotsu.addons.AddonDownloader
import ani.dantotsu.addons.AddonListener
import ani.dantotsu.addons.AddonLoader
import ani.dantotsu.addons.AddonManager
import ani.dantotsu.addons.LoadResult
import ani.dantotsu.media.AddonType
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.InstallStep
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DownloadAddonManager(
private val context: Context
) : AddonManager<DownloadAddon.Installed>(context) {
override var extension: DownloadAddon.Installed? = null
override var name: String = "Download Addon"
override var type = AddonType.DOWNLOAD
private val _isInitialized = MutableLiveData<Boolean>().apply { value = false }
val isInitialized: LiveData<Boolean> = _isInitialized
private var error: String? = null
override suspend fun init() {
extension = null
error = null
hasUpdate = false
withContext(Dispatchers.Main) {
_isInitialized.value = false
}
AddonInstallReceiver()
.setListener(InstallationListener(), type)
.register(context)
try {
val result = AddonLoader.loadExtension(
context,
DOWNLOAD_PACKAGE,
DOWNLOAD_CLASS,
AddonType.DOWNLOAD
) as? DownloadLoadResult
result?.let {
if (it is DownloadLoadResult.Success) {
extension = it.extension
hasUpdate = AddonDownloader.hasUpdate(REPO, it.extension.versionName)
}
}
withContext(Dispatchers.Main) {
_isInitialized.value = true
}
} catch (e: Exception) {
Logger.log("Error initializing Download extension")
Logger.log(e)
error = e.message
}
}
override fun isAvailable(): Boolean {
return extension?.extension != null
}
override fun getVersion(): String? {
return extension?.versionName
}
override fun getPackageName(): String? {
return extension?.pkgName
}
override fun hadError(context: Context): String? {
return if (isInitialized.value == true) {
if (error != null) {
error
} else if (extension != null) {
context.getString(R.string.loaded_successfully)
} else {
null
}
} else {
null
}
}
private inner class InstallationListener : AddonListener {
override fun onAddonInstalled(result: LoadResult?) {
if (result is DownloadLoadResult.Success) {
extension = result.extension
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.INSTALL)
}
}
override fun onAddonUpdated(result: LoadResult?) {
if (result is DownloadLoadResult.Success) {
extension = result.extension
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.UPDATE)
}
}
override fun onAddonUninstalled(pkgName: String) {
if (extension?.pkgName == pkgName) {
extension = null
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.UNINSTALL)
}
}
}
override fun updateInstallStep(id: Long, step: InstallStep) {
installer.updateInstallStep(id, step)
}
override fun setInstalling(id: Long) {
installer.updateInstallStep(id, InstallStep.Installing)
}
companion object {
const val DOWNLOAD_PACKAGE = "dantotsu.downloadAddon"
const val DOWNLOAD_CLASS = "ani.dantotsu.downloadAddon.DownloadAddon"
const val REPO = "rebelonion/Dantotsu-Download-Addon"
}
}

View File

@@ -1,7 +0,0 @@
package ani.dantotsu.addons.download
import ani.dantotsu.addons.LoadResult
open class DownloadLoadResult : LoadResult() {
class Success(val extension: DownloadAddon.Installed) : DownloadLoadResult()
}

View File

@@ -1,16 +0,0 @@
package ani.dantotsu.addons.torrent
import android.graphics.drawable.Drawable
import ani.dantotsu.addons.Addon
sealed class TorrentAddon : Addon() {
data class Installed(
override val name: String,
override val pkgName: String,
override val versionName: String,
override val versionCode: Long,
val extension: TorrentAddonApi,
val icon: Drawable?,
val hasUpdate: Boolean = false,
) : Addon.Installed(name, pkgName, versionName, versionCode)
}

View File

@@ -1,24 +0,0 @@
package ani.dantotsu.addons.torrent
import eu.kanade.tachiyomi.data.torrentServer.model.Torrent
interface TorrentAddonApi {
fun startServer(path: String)
fun stopServer()
fun echo(): String
fun removeTorrent(torrent: String)
fun addTorrent(
link: String,
title: String,
poster: String,
data: String,
save: Boolean,
): Torrent
fun getLink(torrent: Torrent, index: Int): String
}

View File

@@ -1,137 +0,0 @@
package ani.dantotsu.addons.torrent
import android.content.Context
import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import ani.dantotsu.R
import ani.dantotsu.addons.AddonDownloader.Companion.hasUpdate
import ani.dantotsu.addons.AddonListener
import ani.dantotsu.addons.AddonLoader
import ani.dantotsu.addons.AddonManager
import ani.dantotsu.addons.LoadResult
import ani.dantotsu.addons.download.AddonInstallReceiver
import ani.dantotsu.media.AddonType
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.InstallStep
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class TorrentAddonManager(
private val context: Context
) : AddonManager<TorrentAddon.Installed>(context) {
override var extension: TorrentAddon.Installed? = null
override var name: String = "Torrent Addon"
override var type: AddonType = AddonType.TORRENT
var torrentHash: String? = null
private val _isInitialized = MutableLiveData<Boolean>().apply { value = false }
val isInitialized: LiveData<Boolean> = _isInitialized
private var error: String? = null
override suspend fun init() {
extension = null
error = null
hasUpdate = false
withContext(Dispatchers.Main) {
_isInitialized.value = false
}
if (Build.VERSION.SDK_INT < 23) {
Logger.log("Torrent extension is not supported on this device.")
error = context.getString(R.string.torrent_extension_not_supported)
return
}
AddonInstallReceiver()
.setListener(InstallationListener(), type)
.register(context)
try {
val result = AddonLoader.loadExtension(
context,
TORRENT_PACKAGE,
TORRENT_CLASS,
type
) as TorrentLoadResult?
result?.let {
if (it is TorrentLoadResult.Success) {
extension = it.extension
hasUpdate = hasUpdate(REPO, it.extension.versionName)
}
}
withContext(Dispatchers.Main) {
_isInitialized.value = true
}
} catch (e: Exception) {
Logger.log("Error initializing torrent extension")
Logger.log(e)
error = e.message
}
}
override fun isAvailable(): Boolean {
return extension?.extension != null
}
override fun getVersion(): String? {
return extension?.versionName
}
override fun getPackageName(): String? {
return extension?.pkgName
}
override fun hadError(context: Context): String? {
return if (isInitialized.value == true) {
if (error != null) {
error
} else if (extension != null) {
context.getString(R.string.loaded_successfully)
} else {
null
}
} else {
null
}
}
private inner class InstallationListener : AddonListener {
override fun onAddonInstalled(result: LoadResult?) {
if (result is TorrentLoadResult.Success) {
extension = result.extension
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.INSTALL)
}
}
override fun onAddonUpdated(result: LoadResult?) {
if (result is TorrentLoadResult.Success) {
extension = result.extension
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.UPDATE)
}
}
override fun onAddonUninstalled(pkgName: String) {
if (pkgName == TORRENT_PACKAGE) {
extension = null
hasUpdate = false
onListenerAction?.invoke(AddonListener.ListenerAction.UNINSTALL)
}
}
}
override fun updateInstallStep(id: Long, step: InstallStep) {
installer.updateInstallStep(id, step)
}
override fun setInstalling(id: Long) {
installer.updateInstallStep(id, InstallStep.Installing)
}
companion object {
const val TORRENT_PACKAGE = "dantotsu.torrentAddon"
const val TORRENT_CLASS = "ani.dantotsu.torrentAddon.TorrentAddon"
const val REPO = "rebelonion/Dantotsu-Torrent-Addon"
}
}

View File

@@ -1,7 +0,0 @@
package ani.dantotsu.addons.torrent
import ani.dantotsu.addons.LoadResult
open class TorrentLoadResult : LoadResult() {
class Success(val extension: TorrentAddon.Installed) : TorrentLoadResult()
}

View File

@@ -1,168 +0,0 @@
package ani.dantotsu.addons.torrent
import android.app.ActivityManager
import android.app.Application
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import ani.dantotsu.R
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_TORRENT_SERVER
import eu.kanade.tachiyomi.data.notification.Notifications.ID_TORRENT_SERVER
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notificationBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.coroutines.EmptyCoroutineContext
class ServerService : Service() {
private val serviceScope = CoroutineScope(EmptyCoroutineContext)
private val applicationContext = Injekt.get<Application>()
private val extension = Injekt.get<TorrentAddonManager>().extension!!.extension
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int,
): Int {
intent?.let {
if (it.action != null) {
when (it.action) {
ACTION_START -> {
startServer()
notification(applicationContext)
return START_STICKY
}
ACTION_STOP -> {
stopServer()
return START_NOT_STICKY
}
}
}
}
return START_NOT_STICKY
}
private fun startServer() {
serviceScope.launch {
val echo = extension.echo()
if (echo == "") {
extension.startServer(filesDir.absolutePath)
}
}
}
private fun stopServer() {
serviceScope.launch {
extension.stopServer()
applicationContext.cancelNotification(ID_TORRENT_SERVER)
stopSelf()
}
}
private fun notification(context: Context) {
val exitPendingIntent =
PendingIntent.getService(
applicationContext,
0,
Intent(applicationContext, ServerService::class.java).apply {
action = ACTION_STOP
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
val builder = context.notificationBuilder(CHANNEL_TORRENT_SERVER) {
setSmallIcon(R.drawable.notification_icon)
setContentText("Torrent Server")
setContentTitle("Server is running…")
setAutoCancel(false)
setOngoing(true)
setUsesChronometer(true)
addAction(
R.drawable.ic_circle_cancel,
"Stop",
exitPendingIntent,
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
ID_TORRENT_SERVER,
builder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
} else {
startForeground(ID_TORRENT_SERVER, builder.build())
}
}
companion object {
const val ACTION_START = "start_torrent_server"
const val ACTION_STOP = "stop_torrent_server"
fun isRunning(): Boolean {
with(Injekt.get<Application>().getSystemService(ACTIVITY_SERVICE) as ActivityManager) {
@Suppress("DEPRECATION") // We only need our services
getRunningServices(Int.MAX_VALUE).forEach {
if (ServerService::class.java.name.equals(it.service.className)) {
return true
}
}
}
return false
}
fun start() {
try {
val intent =
Intent(Injekt.get<Application>(), ServerService::class.java).apply {
action = ACTION_START
}
Injekt.get<Application>().startService(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun stop() {
try {
val intent =
Intent(Injekt.get<Application>(), ServerService::class.java).apply {
action = ACTION_STOP
}
Injekt.get<Application>().startService(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun wait(timeout: Int = -1): Boolean {
var count = 0
if (timeout < 0) {
count = -20
}
var echo = Injekt.get<TorrentAddonManager>().extension?.extension?.echo()
while (echo == "") {
Thread.sleep(1000)
count++
if (count > timeout) {
return false
}
echo = Injekt.get<TorrentAddonManager>().extension?.extension?.echo()
}
Logger.log("ServerService: Server started: $echo")
return true
}
}
}

View File

@@ -6,8 +6,6 @@ import androidx.annotation.OptIn
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider import androidx.media3.database.StandaloneDatabaseProvider
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
@@ -40,13 +38,10 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { DownloadsManager(app) } addSingletonFactory { DownloadsManager(app) }
addSingletonFactory { NetworkHelper(app) } addSingletonFactory { NetworkHelper(app) }
addSingletonFactory { NetworkHelper(app).client }
addSingletonFactory { AnimeExtensionManager(app) } addSingletonFactory { AnimeExtensionManager(app) }
addSingletonFactory { MangaExtensionManager(app) } addSingletonFactory { MangaExtensionManager(app) }
addSingletonFactory { NovelExtensionManager(app) } addSingletonFactory { NovelExtensionManager(app) }
addSingletonFactory { TorrentAddonManager(app) }
addSingletonFactory { DownloadAddonManager(app) }
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) } addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) } addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }

View File

@@ -3,6 +3,7 @@ package ani.dantotsu.connections.anilist
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.client import ani.dantotsu.client
@@ -13,6 +14,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.toast import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import java.util.Calendar import java.util.Calendar
@@ -199,9 +201,7 @@ object Anilist {
toast("Rate limited. Try after $retry seconds") toast("Rate limited. Try after $retry seconds")
throw Exception("Rate limited after $retry seconds") throw Exception("Rate limited after $retry seconds")
} }
if (!json.text.startsWith("{")) { if (!json.text.startsWith("{")) {throw Exception(currContext()?.getString(R.string.anilist_down))}
throw Exception(currContext()?.getString(R.string.anilist_down))
}
if (show) Logger.log("Anilist Response: ${json.text}") if (show) Logger.log("Anilist Response: ${json.text}")
json.parsed() json.parsed()
} else null } else null

View File

@@ -20,7 +20,6 @@ import ani.dantotsu.media.Character
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.Studio import ani.dantotsu.media.Studio
import ani.dantotsu.others.MalScraper import ani.dantotsu.others.MalScraper
import ani.dantotsu.profile.User
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
@@ -73,14 +72,14 @@ class AnilistQueries {
media.cameFromContinue = false media.cameFromContinue = false
val query = val query =
"""{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}""" """{Media(id:${media.id}){id favourites popularity mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}}"""
runBlocking { runBlocking {
val anilist = async { val anilist = async {
var response = executeQuery<Query.Media>(query, force = true, show = true) var response = executeQuery<Query.Media>(query, force = true, show = true)
if (response != null) { if (response != null) {
fun parse() { fun parse() {
val fetchedMedia = response?.data?.media ?: return val fetchedMedia = response?.data?.media ?: return
val user = response?.data?.page
media.source = fetchedMedia.source?.toString() media.source = fetchedMedia.source?.toString()
media.countryOfOrigin = fetchedMedia.countryOfOrigin media.countryOfOrigin = fetchedMedia.countryOfOrigin
media.format = fetchedMedia.format?.toString() media.format = fetchedMedia.format?.toString()
@@ -140,15 +139,7 @@ class AnilistQueries {
?: "SUPPORTING" ?: "SUPPORTING"
else -> i.role.toString() else -> i.role.toString()
}, }
voiceActor = i.voiceActors?.map {
Author(
id = it.id,
name = it.name?.userPreferred,
image = it.image?.large,
role = it.languageV2
)
} as ArrayList<Author>
) )
) )
} }
@@ -162,7 +153,7 @@ class AnilistQueries {
Author( Author(
id = id, id = id,
name = i.node?.name?.userPreferred, name = i.node?.name?.userPreferred,
image = i.node?.image?.large, image = i.node?.image?.medium,
role = when (i.role.toString()) { role = when (i.role.toString()) {
"MAIN" -> currContext()?.getString(R.string.main_role) "MAIN" -> currContext()?.getString(R.string.main_role)
?: "MAIN" ?: "MAIN"
@@ -209,24 +200,7 @@ class AnilistQueries {
} }
} }
} }
if (user?.mediaList?.isNotEmpty() == true) {
media.users = user.mediaList?.mapNotNull {
it.user?.let { user ->
if (user.id != Anilist.userid) {
User(
user.id,
user.name ?: "Unknown",
user.avatar?.large,
"",
it.status?.toString(),
it.score,
it.progress,
fetchedMedia.episodes ?: fetchedMedia.chapters,
)
} else null
}
}?.toCollection(arrayListOf()) ?: arrayListOf()
}
if (fetchedMedia.mediaListEntry != null) { if (fetchedMedia.mediaListEntry != null) {
fetchedMedia.mediaListEntry?.apply { fetchedMedia.mediaListEntry?.apply {
media.userProgress = progress media.userProgress = progress
@@ -1061,157 +1035,63 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
} }
return null return null
} }
private val onListAnime = (if(PrefManager.getVal(PrefName.IncludeAnimeList)) "" else "onList:false").replace("\"", "")
private val onListAnime = private val isAdult = (if (PrefManager.getVal(PrefName.AdultOnly)) "isAdult:true" else "").replace("\"", "")
(if (PrefManager.getVal(PrefName.IncludeAnimeList)) "" else "onList:false").replace( private fun recentAnimeUpdates(): String{
"\"", return """Page(page:1,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${System.currentTimeMillis() / 1000 - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}"""
""
)
private val isAdult =
(if (PrefManager.getVal(PrefName.AdultOnly)) "isAdult:true" else "").replace("\"", "")
private fun recentAnimeUpdates(page: Int): String {
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${System.currentTimeMillis() / 1000 - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}"""
} }
private fun trendingMovies(): String{
private fun trendingMovies(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: ANIME, format: MOVIE, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: ANIME, format: MOVIE, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun topRatedAnime(): String{
private fun topRatedAnime(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun mostFavAnime(): String{
private fun mostFavAnime(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
suspend fun loadAnimeList(): Query.AnimeList?{
suspend fun loadAnimeList(): Map<String, ArrayList<Media>> { return executeQuery<Query.AnimeList>(
val list = mutableMapOf<String, ArrayList<Media>>() """{
fun query(): String { recentUpdates:${recentAnimeUpdates()}
return """{ trendingMovies:${trendingMovies()}
recentUpdates:${recentAnimeUpdates(1)} topRated:${topRatedAnime()}
recentUpdates2:${recentAnimeUpdates(2)} mostFav:${mostFavAnime()}
trendingMovies:${trendingMovies(1)} }""".trimIndent(), force = true
trendingMovies2:${trendingMovies(2)}
topRated:${topRatedAnime(1)}
topRated2:${topRatedAnime(2)}
mostFav:${mostFavAnime(1)}
mostFav2:${mostFavAnime(2)}
}""".trimIndent()
}
executeQuery<Query.AnimeList>(query(), force = true)?.data?.apply {
val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly)
val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly)
val idArr = mutableListOf<Int>()
list["recentUpdates"] = recentUpdates?.airingSchedules?.mapNotNull { i ->
i.media?.let {
if (!idArr.contains(it.id))
if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) {
idArr.add(it.id)
Media(it)
} else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) {
idArr.add(it.id)
Media(it)
} else if ((listOnly && it.mediaListEntry != null)) {
idArr.add(it.id)
Media(it)
} else null
else null
}
} as ArrayList<Media>
list["trendingMovies"] = trendingMovies?.media?.map { Media(it) } as ArrayList<Media>
list["topRated"] = topRated?.media?.map { Media(it) } as ArrayList<Media>
list["mostFav"] = mostFav?.media?.map { Media(it) } as ArrayList<Media>
list["recentUpdates"]?.addAll(recentUpdates2?.airingSchedules?.mapNotNull { i ->
i.media?.let {
if (!idArr.contains(it.id))
if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) {
idArr.add(it.id)
Media(it)
} else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) {
idArr.add(it.id)
Media(it)
} else if ((listOnly && it.mediaListEntry != null)) {
idArr.add(it.id)
Media(it)
} else null
else null
}
} as ArrayList<Media>)
list["trendingMovies"]?.addAll(trendingMovies2?.media?.map { Media(it) } as ArrayList<Media>)
list["topRated"]?.addAll(topRated2?.media?.map { Media(it) } as ArrayList<Media>)
list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) } as ArrayList<Media>)
}
return list
}
private val onListManga =
(if (PrefManager.getVal(PrefName.IncludeMangaList)) "" else "onList:false").replace(
"\"",
""
) )
private fun trendingManga(page: Int): String {
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA,countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private val onListManga = (if(PrefManager.getVal(PrefName.IncludeMangaList)) "" else "onList:false").replace("\"", "")
private fun trendingManhwa(page: Int): String { private fun trendingManga(): String{
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, countryOfOrigin:KR, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA,countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun trendingManhwa(): String{
private fun trendingNovel(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, countryOfOrigin:KR, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, format: NOVEL, countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun trendingNovel(): String{
private fun topRatedManga(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, format: NOVEL, countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun topRatedManga(): String{
private fun mostFavManga(page: Int): String { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
} }
private fun mostFavManga(): String{
suspend fun loadMangaList(): Map<String, ArrayList<Media>> { return """Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}"""
val list = mutableMapOf<String, ArrayList<Media>>()
fun query(): String {
return """{
trendingManga:${trendingManga(1)}
trendingManga2:${trendingManga(2)}
trendingManhwa:${trendingManhwa(1)}
trendingManhwa2:${trendingManhwa(2)}
trendingNovel:${trendingNovel(1)}
trendingNovel2:${trendingNovel(2)}
topRated:${topRatedManga(1)}
topRated2:${topRatedManga(2)}
mostFav:${mostFavManga(1)}
mostFav2:${mostFavManga(2)}
}""".trimIndent()
}
executeQuery<Query.MangaList>(query(), force = true)?.data?.apply {
list["trendingManga"] = trendingManga?.media?.map { Media(it) } as ArrayList<Media>
list["trendingManhwa"] = trendingManhwa?.media?.map { Media(it) } as ArrayList<Media>
list["trendingNovel"] = trendingNovel?.media?.map { Media(it) } as ArrayList<Media>
list["topRated"] = topRated?.media?.map { Media(it) } as ArrayList<Media>
list["mostFav"] = mostFav?.media?.map { Media(it) } as ArrayList<Media>
list["trendingManga"]?.addAll(trendingManga2?.media?.map { Media(it) } as ArrayList<Media>)
list["trendingManhwa"]?.addAll(trendingManhwa2?.media?.map { Media(it) } as ArrayList<Media>)
list["trendingNovel"]?.addAll(trendingNovel2?.media?.map { Media(it) } as ArrayList<Media>)
list["topRated"]?.addAll(topRated2?.media?.map { Media(it) } as ArrayList<Media>)
list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) } as ArrayList<Media>)
}
return list
} }
suspend fun loadMangaList(): Query.MangaList?{
return executeQuery<Query.MangaList>(
"""{
trendingManga:${trendingManga()}
trendingManhwa:${trendingManhwa()}
trendingNovel:${trendingNovel()}
topRated:${topRatedManga()}
mostFav:${mostFavManga()}
}""".trimIndent(), force = true
)
}
suspend fun recentlyUpdated( suspend fun recentlyUpdated(
greater: Long = 0, greater: Long = 0,
lesser: Long = System.currentTimeMillis() / 1000 - 10000 lesser: Long = System.currentTimeMillis() / 1000 - 10000
): MutableList<Media> { ): MutableList<Media>? {
suspend fun execute(page: Int = 1): Page? { suspend fun execute(page: Int = 1): Page? {
val query = """{ val query = """{
Page(page:$page,perPage:50) { Page(page:$page,perPage:50) {
@@ -1258,26 +1138,25 @@ Page(page:$page,perPage:50) {
}""".replace("\n", " ").replace(""" """, "") }""".replace("\n", " ").replace(""" """, "")
return executeQuery<Query.Page>(query, force = true)?.data?.page return executeQuery<Query.Page>(query, force = true)?.data?.page
} }
var i = 1
var i = 1 val list = mutableListOf<Media>()
val list = mutableListOf<Media>() var res: Page? = null
var res: Page? = null suspend fun next() {
suspend fun next() { res = execute(i)
res = execute(i) list.addAll(res?.airingSchedules?.mapNotNull { j ->
list.addAll(res?.airingSchedules?.mapNotNull { j -> j.media?.let {
j.media?.let { if (it.countryOfOrigin == "JP" && (if (!Anilist.adult) it.isAdult == false else true)) {
if (it.countryOfOrigin == "JP" && (if (!Anilist.adult) it.isAdult == false else true)) { Media(it).apply { relation = "${j.episode},${j.airingAt}" }
Media(it).apply { relation = "${j.episode},${j.airingAt}" } } else null
} else null }
} } ?: listOf())
} ?: listOf()) }
}
next()
while (res?.pageInfo?.hasNextPage == true) {
next() next()
i++ while (res?.pageInfo?.hasNextPage == true) {
} next()
return list.reversed().toMutableList() i++
}
return list.reversed().toMutableList()
} }
suspend fun getCharacterDetails(character: Character): Character { suspend fun getCharacterDetails(character: Character): Character {
@@ -1463,39 +1342,19 @@ Page(page:$page,perPage:50) {
} }
} }
} }
characters(page: $page,sort:FAVOURITES_DESC) {
pageInfo{
hasNextPage
}
nodes{
id
name {
first
middle
last
full
native
userPreferred
}
image {
large
medium
}
}
}
} }
}""".replace("\n", " ").replace(""" """, "") }""".replace("\n", " ").replace(""" """, "")
var hasNextPage = true var hasNextPage = true
val yearMedia = mutableMapOf<String, ArrayList<Media>>() val yearMedia = mutableMapOf<String, ArrayList<Media>>()
var page = 0 var page = 0
val characters = arrayListOf<Character>()
while (hasNextPage) { while (hasNextPage) {
page++ page++
val query = executeQuery<Query.Author>( hasNextPage = executeQuery<Query.Author>(
query(page), force = true query(page),
)?.data?.author force = true
hasNextPage = query?.staffMedia?.let { )?.data?.author?.staffMedia?.let {
it.edges?.forEach { i -> it.edges?.forEach { i ->
i.node?.apply { i.node?.apply {
val status = status.toString() val status = status.toString()
@@ -1510,20 +1369,6 @@ Page(page:$page,perPage:50) {
} }
it.pageInfo?.hasNextPage == true it.pageInfo?.hasNextPage == true
} ?: false } ?: false
query?.characters?.let {
it.nodes?.forEach { i ->
characters.add(
Character(
i.id,
i.name?.userPreferred,
i.image?.large,
i.image?.medium,
"",
false
)
)
}
}
} }
if (yearMedia.contains("CANCELLED")) { if (yearMedia.contains("CANCELLED")) {
@@ -1531,7 +1376,6 @@ Page(page:$page,perPage:50) {
yearMedia.remove("CANCELLED") yearMedia.remove("CANCELLED")
yearMedia["CANCELLED"] = a yearMedia["CANCELLED"] = a
} }
author.character = characters
author.yearMedia = yearMedia author.yearMedia = yearMedia
return author return author
} }
@@ -1603,11 +1447,7 @@ Page(page:$page,perPage:50) {
} }
suspend fun getNotifications( suspend fun getNotifications(id: Int, page: Int = 1, resetNotification: Boolean = true): NotificationResponse? {
id: Int,
page: Int = 1,
resetNotification: Boolean = true
): NotificationResponse? {
val reset = if (resetNotification) "true" else "false" val reset = if (resetNotification) "true" else "false"
val res = executeQuery<NotificationResponse>( val res = executeQuery<NotificationResponse>(
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
@@ -1622,12 +1462,7 @@ Page(page:$page,perPage:50) {
return res return res
} }
suspend fun getFeed( suspend fun getFeed(userId: Int?, global: Boolean = false, page: Int = 1, activityId: Int? = null): FeedResponse? {
userId: Int?,
global: Boolean = false,
page: Int = 1,
activityId: Int? = null
): FeedResponse? {
val filter = if (activityId != null) "id:$activityId," val filter = if (activityId != null) "id:$activityId,"
else if (userId != null) "userId:$userId," else if (userId != null) "userId:$userId,"
else if (global) "isFollowing:false,hasRepliesOrTypeText:true," else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
@@ -1656,26 +1491,14 @@ Page(page:$page,perPage:50) {
.filter { it.timeUntilAiring != null } .filter { it.timeUntilAiring != null }
} }
suspend fun isUserFav( suspend fun isUserFav(favType: AnilistMutations.FavType, id: Int): Boolean { //anilist isFavourite is broken, so we need to check it manually
favType: AnilistMutations.FavType, val res = getUserProfile(Anilist.userid?: return false)
id: Int
): Boolean { //anilist isFavourite is broken, so we need to check it manually
val res = getUserProfile(Anilist.userid ?: return false)
return when (favType) { return when (favType) {
AnilistMutations.FavType.ANIME -> res?.data?.user?.favourites?.anime?.nodes?.any { it.id == id } AnilistMutations.FavType.ANIME -> res?.data?.user?.favourites?.anime?.nodes?.any { it.id == id } ?: false
?: false AnilistMutations.FavType.MANGA -> res?.data?.user?.favourites?.manga?.nodes?.any { it.id == id } ?: false
AnilistMutations.FavType.CHARACTER -> res?.data?.user?.favourites?.characters?.nodes?.any { it.id == id } ?: false
AnilistMutations.FavType.MANGA -> res?.data?.user?.favourites?.manga?.nodes?.any { it.id == id } AnilistMutations.FavType.STAFF -> res?.data?.user?.favourites?.staff?.nodes?.any { it.id == id } ?: false
?: false AnilistMutations.FavType.STUDIO -> res?.data?.user?.favourites?.studios?.nodes?.any { it.id == id } ?: false
AnilistMutations.FavType.CHARACTER -> res?.data?.user?.favourites?.characters?.nodes?.any { it.id == id }
?: false
AnilistMutations.FavType.STAFF -> res?.data?.user?.favourites?.staff?.nodes?.any { it.id == id }
?: false
AnilistMutations.FavType.STUDIO -> res?.data?.user?.favourites?.studios?.nodes?.any { it.id == id }
?: false
} }
} }

View File

@@ -5,6 +5,8 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.webkit.internal.ApiFeature.P
import androidx.webkit.internal.StartupApiFeature
import ani.dantotsu.BuildConfig import ani.dantotsu.BuildConfig
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.discord.Discord
@@ -187,29 +189,46 @@ class AnilistAnimeViewModel : ViewModel() {
var loaded: Boolean = false var loaded: Boolean = false
private val updated: MutableLiveData<MutableList<Media>> = private val updated: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getUpdated(): LiveData<MutableList<Media>> = updated fun getUpdated(): LiveData<MutableList<Media>> = updated
private val popularMovies: MutableLiveData<MutableList<Media>> = private val popularMovies: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getMovies(): LiveData<MutableList<Media>> = popularMovies fun getMovies(): LiveData<MutableList<Media>> = popularMovies
private val topRatedAnime: MutableLiveData<MutableList<Media>> = private val topRatedAnime: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getTopRated(): LiveData<MutableList<Media>> = topRatedAnime fun getTopRated(): LiveData<MutableList<Media>> = topRatedAnime
private val mostFavAnime: MutableLiveData<MutableList<Media>> = private val mostFavAnime: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getMostFav(): LiveData<MutableList<Media>> = mostFavAnime fun getMostFav(): LiveData<MutableList<Media>> = mostFavAnime
suspend fun loadAll() { suspend fun loadAll() {
val list = Anilist.query.loadAnimeList() val res = Anilist.query.loadAnimeList()?.data
updated.postValue(list["recentUpdates"])
popularMovies.postValue(list["trendingMovies"]) val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly)
topRatedAnime.postValue(list["topRated"]) val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly)
mostFavAnime.postValue(list["mostFav"]) res?.apply{
val idArr = mutableListOf<Int>()
updated.postValue(recentUpdates?.airingSchedules?.mapNotNull {i ->
i.media?.let {
if (!idArr.contains(it.id))
if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) {
idArr.add(it.id)
Media(it)
}else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)){
idArr.add(it.id)
Media(it)
}else if ((listOnly && it.mediaListEntry != null)) {
idArr.add(it.id)
Media(it)
}else null
else null
}
}?.toMutableList() ?: arrayListOf())
popularMovies.postValue(trendingMovies?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
topRatedAnime.postValue(topRated?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
mostFavAnime.postValue(mostFav?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
}
} }
} }
@@ -284,35 +303,33 @@ class AnilistMangaViewModel : ViewModel() {
private val popularManga: MutableLiveData<MutableList<Media>> = private val popularManga: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getPopularManga(): LiveData<MutableList<Media>> = popularManga fun getPopularManga(): LiveData<MutableList<Media>> = popularManga
private val popularManhwa: MutableLiveData<MutableList<Media>> = private val popularManhwa: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getPopularManhwa(): LiveData<MutableList<Media>> = popularManhwa fun getPopularManhwa(): LiveData<MutableList<Media>> = popularManhwa
private val popularNovel: MutableLiveData<MutableList<Media>> = private val popularNovel: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getPopularNovel(): LiveData<MutableList<Media>> = popularNovel fun getPopularNovel(): LiveData<MutableList<Media>> = popularNovel
private val topRatedManga: MutableLiveData<MutableList<Media>> = private val topRatedManga: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getTopRated(): LiveData<MutableList<Media>> = topRatedManga fun getTopRated(): LiveData<MutableList<Media>> = topRatedManga
private val mostFavManga: MutableLiveData<MutableList<Media>> = private val mostFavManga: MutableLiveData<MutableList<Media>> =
MutableLiveData<MutableList<Media>>(null) MutableLiveData<MutableList<Media>>(null)
fun getMostFav(): LiveData<MutableList<Media>> = mostFavManga fun getMostFav(): LiveData<MutableList<Media>> = mostFavManga
suspend fun loadAll() { suspend fun loadAll() {
val list = Anilist.query.loadMangaList() val response = Anilist.query.loadMangaList()?.data
popularManga.postValue(list["trendingManga"])
popularManhwa.postValue(list["trendingManhwa"]) response?.apply {
popularNovel.postValue(list["trendingNovel"]) popularManga.postValue(trendingManga?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
topRatedManga.postValue(list["topRated"]) popularManhwa.postValue(trendingManhwa?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
mostFavManga.postValue(list["mostFav"]) popularNovel.postValue(trendingNovel?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
topRatedManga.postValue(topRated?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
mostFavManga.postValue(mostFav?.media?.map { Media(it) }?.toMutableList() ?: arrayListOf())
}
} }
} }

View File

@@ -4,6 +4,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.logError import ani.dantotsu.logError
import ani.dantotsu.util.Logger
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity

View File

@@ -11,7 +11,7 @@ data class SearchResults(
var onList: Boolean? = null, var onList: Boolean? = null,
var perPage: Int? = null, var perPage: Int? = null,
var search: String? = null, var search: String? = null,
var countryOfOrigin: String? = null, var countryOfOrigin :String? = null,
var sort: String? = null, var sort: String? = null,
var genres: MutableList<String>? = null, var genres: MutableList<String>? = null,
var excludedGenres: MutableList<String>? = null, var excludedGenres: MutableList<String>? = null,

View File

@@ -55,7 +55,7 @@ data class CharacterConnection(
@SerialName("nodes") var nodes: List<Character>?, @SerialName("nodes") var nodes: List<Character>?,
// The pagination information // The pagination information
@SerialName("pageInfo") var pageInfo: PageInfo?, // @SerialName("pageInfo") var pageInfo: PageInfo?,
) : java.io.Serializable ) : java.io.Serializable
@Serializable @Serializable
@@ -72,7 +72,7 @@ data class CharacterEdge(
@SerialName("name") var name: String?, @SerialName("name") var name: String?,
// The voice actors of the character // The voice actors of the character
@SerialName("voiceActors") var voiceActors: List<Staff>?, // @SerialName("voiceActors") var voiceActors: List<Staff>?,
// The voice actors of the character with role date // The voice actors of the character with role date
// @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?, // @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?,

View File

@@ -24,9 +24,7 @@ class Query {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("Media") @SerialName("Media")
val media: ani.dantotsu.connections.anilist.api.Media?, val media: ani.dantotsu.connections.anilist.api.Media?
@SerialName("Page")
val page: ani.dantotsu.connections.anilist.api.Page?
) )
} }
@@ -149,10 +147,8 @@ class Query {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?, @SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?,
@SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User? @SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User?)
)
} }
@Serializable @Serializable
data class AnimeList( data class AnimeList(
@SerialName("data") @SerialName("data")
@@ -161,16 +157,11 @@ class Query {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?, @SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("recentUpdates2") val recentUpdates2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingMovies2") val trendingMovies2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?,
) )
} }
@Serializable @Serializable
data class MangaList( data class MangaList(
@SerialName("data") @SerialName("data")
@@ -179,18 +170,12 @@ class Query {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingManga2") val trendingManga2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingManhwa2") val trendingManhwa2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("trendingNovel2") val trendingNovel2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?,
) )
} }
@Serializable @Serializable
data class ToggleFollow( data class ToggleFollow(
@SerialName("data") @SerialName("data")
@@ -321,13 +306,13 @@ class Query {
val statistics: NNUserStatisticTypes, val statistics: NNUserStatisticTypes,
@SerialName("siteUrl") @SerialName("siteUrl")
val siteUrl: String, val siteUrl: String,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class NNUserStatisticTypes( data class NNUserStatisticTypes(
@SerialName("anime") var anime: NNUserStatistics, @SerialName("anime") var anime: NNUserStatistics,
@SerialName("manga") var manga: NNUserStatistics @SerialName("manga") var manga: NNUserStatistics
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class NNUserStatistics( data class NNUserStatistics(
@@ -338,9 +323,9 @@ class Query {
@SerialName("episodesWatched") var episodesWatched: Int, @SerialName("episodesWatched") var episodesWatched: Int,
@SerialName("chaptersRead") var chaptersRead: Int, @SerialName("chaptersRead") var chaptersRead: Int,
@SerialName("volumesRead") var volumesRead: Int, @SerialName("volumesRead") var volumesRead: Int,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserFavourites( data class UserFavourites(
@SerialName("anime") @SerialName("anime")
val anime: UserMediaFavouritesCollection, val anime: UserMediaFavouritesCollection,
@@ -352,13 +337,13 @@ class Query {
val staff: UserStaffFavouritesCollection, val staff: UserStaffFavouritesCollection,
@SerialName("studios") @SerialName("studios")
val studios: UserStudioFavouritesCollection, val studios: UserStudioFavouritesCollection,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserMediaFavouritesCollection( data class UserMediaFavouritesCollection(
@SerialName("nodes") @SerialName("nodes")
val nodes: List<UserMediaImageFavorite>, val nodes: List<UserMediaImageFavorite>,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserMediaImageFavorite( data class UserMediaImageFavorite(
@@ -366,13 +351,13 @@ class Query {
val id: Int, val id: Int,
@SerialName("coverImage") @SerialName("coverImage")
val coverImage: MediaCoverImage val coverImage: MediaCoverImage
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserCharacterFavouritesCollection( data class UserCharacterFavouritesCollection(
@SerialName("nodes") @SerialName("nodes")
val nodes: List<UserCharacterImageFavorite>, val nodes: List<UserCharacterImageFavorite>,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserCharacterImageFavorite( data class UserCharacterImageFavorite(
@@ -384,19 +369,19 @@ class Query {
val image: CharacterImage, val image: CharacterImage,
@SerialName("isFavourite") @SerialName("isFavourite")
val isFavourite: Boolean val isFavourite: Boolean
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserStaffFavouritesCollection( data class UserStaffFavouritesCollection(
@SerialName("nodes") @SerialName("nodes")
val nodes: List<UserCharacterImageFavorite>, //downstream it's the same as character val nodes: List<UserCharacterImageFavorite>, //downstream it's the same as character
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserStudioFavouritesCollection( data class UserStudioFavouritesCollection(
@SerialName("nodes") @SerialName("nodes")
val nodes: List<UserStudioFavorite>, val nodes: List<UserStudioFavorite>,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserStudioFavorite( data class UserStudioFavorite(
@@ -404,7 +389,7 @@ class Query {
val id: Int, val id: Int,
@SerialName("name") @SerialName("name")
val name: String, val name: String,
) : java.io.Serializable ): java.io.Serializable
//---------------------------------------- //----------------------------------------
// Statistics // Statistics
@@ -413,12 +398,12 @@ class Query {
data class StatisticsResponse( data class StatisticsResponse(
@SerialName("data") @SerialName("data")
val data: Data val data: Data
) : java.io.Serializable { ): java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("User") @SerialName("User")
val user: StatisticsUser? val user: StatisticsUser?
) : java.io.Serializable ): java.io.Serializable
} }
@Serializable @Serializable

View File

@@ -21,7 +21,6 @@ enum class NotificationType(val value: String) {
MEDIA_DATA_CHANGE("MEDIA_DATA_CHANGE"), MEDIA_DATA_CHANGE("MEDIA_DATA_CHANGE"),
MEDIA_MERGE("MEDIA_MERGE"), MEDIA_MERGE("MEDIA_MERGE"),
MEDIA_DELETION("MEDIA_DELETION"), MEDIA_DELETION("MEDIA_DELETION"),
//custom //custom
COMMENT_REPLY("COMMENT_REPLY"), COMMENT_REPLY("COMMENT_REPLY"),
} }
@@ -85,9 +84,9 @@ data class Notification(
@SerialName("createdAt") @SerialName("createdAt")
val createdAt: Int, val createdAt: Int,
@SerialName("media") @SerialName("media")
val media: Media? = null, val media: ani.dantotsu.connections.anilist.api.Media? = null,
@SerialName("user") @SerialName("user")
val user: User? = null, val user: ani.dantotsu.connections.anilist.api.User? = null,
@SerialName("message") @SerialName("message")
val message: MessageActivity? = null, val message: MessageActivity? = null,
@SerialName("activity") @SerialName("activity")

View File

@@ -93,7 +93,6 @@ data class StaffConnection(
// The pagination information // The pagination information
// @SerialName("pageInfo") var pageInfo: PageInfo?, // @SerialName("pageInfo") var pageInfo: PageInfo?,
) )
@Serializable @Serializable
data class StaffImage( data class StaffImage(
// The character's image of media at its largest size // The character's image of media at its largest size
@@ -102,7 +101,6 @@ data class StaffImage(
// The character's image of media at medium size // The character's image of media at medium size
@SerialName("medium") var medium: String?, @SerialName("medium") var medium: String?,
) : java.io.Serializable ) : java.io.Serializable
@Serializable @Serializable
data class StaffEdge( data class StaffEdge(
var role: String?, var role: String?,

View File

@@ -111,7 +111,7 @@ data class UserAvatar(
// The avatar of user at medium size // The avatar of user at medium size
@SerialName("medium") var medium: String?, @SerialName("medium") var medium: String?,
) : java.io.Serializable ): java.io.Serializable
@Serializable @Serializable
data class UserStatisticTypes( data class UserStatisticTypes(

View File

@@ -1,128 +0,0 @@
package ani.dantotsu.connections.bakaupdates
import android.content.Context
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import okio.ByteString.Companion.encode
import org.json.JSONException
import org.json.JSONObject
import java.nio.charset.Charset
class MangaUpdates {
private val Int?.dateFormat get() = String.format("%02d", this)
private val apiUrl = "https://api.mangaupdates.com/v1/releases/search"
suspend fun search(title: String, startDate: FuzzyDate?): MangaUpdatesResponse.Results? {
return tryWithSuspend {
val query = JSONObject().apply {
try {
put("search", title.encode(Charset.forName("UTF-8")))
startDate?.let {
put(
"start_date",
"${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
)
}
put("include_metadata", true)
} catch (e: JSONException) {
e.printStackTrace()
}
}
val res = client.post(apiUrl, json = query).parsed<MangaUpdatesResponse>()
coroutineScope {
res.results?.map {
async(Dispatchers.IO) {
Logger.log(it.toString())
}
}
}?.awaitAll()
res.results?.first {
it.metadata.series.lastUpdated?.timestamp != null
&& (it.metadata.series.latestChapter != null
|| (it.record.volume.isNullOrBlank() && it.record.chapter != null))
}
}
}
companion object {
fun getLatestChapter(context: Context, results: MangaUpdatesResponse.Results): String {
return results.metadata.series.latestChapter?.let {
context.getString(R.string.chapter_number, it)
} ?: results.record.chapter!!.substringAfterLast("-").trim().let { chapter ->
chapter.takeIf {
it.toIntOrNull() == null
} ?: context.getString(R.string.chapter_number, chapter.toInt())
}
}
}
@Serializable
data class MangaUpdatesResponse(
@SerialName("total_hits")
val totalHits: Int?,
@SerialName("page")
val page: Int?,
@SerialName("per_page")
val perPage: Int?,
val results: List<Results>? = null
) {
@Serializable
data class Results(
val record: Record,
val metadata: MetaData
) {
@Serializable
data class Record(
@SerialName("id")
val id: Int,
@SerialName("title")
val title: String,
@SerialName("volume")
val volume: String?,
@SerialName("chapter")
val chapter: String?,
@SerialName("release_date")
val releaseDate: String
)
@Serializable
data class MetaData(
val series: Series
) {
@Serializable
data class Series(
@SerialName("series_id")
val seriesId: Long?,
@SerialName("title")
val title: String?,
@SerialName("latest_chapter")
val latestChapter: Int?,
@SerialName("last_updated")
val lastUpdated: LastUpdated?
) {
@Serializable
data class LastUpdated(
@SerialName("timestamp")
val timestamp: Long,
@SerialName("as_rfc3339")
val asRfc3339: String,
@SerialName("as_string")
val asString: String
)
}
}
}
}
}

View File

@@ -32,12 +32,7 @@ object CommentsAPI {
var isMod: Boolean = false var isMod: Boolean = false
var totalVotes: Int = 0 var totalVotes: Int = 0
suspend fun getCommentsForId( suspend fun getCommentsForId(id: Int, page: Int = 1, tag: Int?, sort: String?): CommentResponse? {
id: Int,
page: Int = 1,
tag: Int?,
sort: String?
): CommentResponse? {
var url = "$ADDRESS/comments/$id/$page" var url = "$ADDRESS/comments/$id/$page"
val request = requestBuilder() val request = requestBuilder()
tag?.let { tag?.let {
@@ -404,7 +399,7 @@ object CommentsAPI {
null null
} }
val message = parsed?.message ?: reason ?: error val message = parsed?.message ?: reason ?: error
val fullMessage = if (code == 500) message else "$code: $message" val fullMessage = if(code == 500) message else "$code: $message"
toast(fullMessage) toast(fullMessage)
} }

View File

@@ -7,7 +7,6 @@ class CrashlyticsStub : CrashlyticsInterface {
override fun initialize(context: Context) { override fun initialize(context: Context) {
//no-op //no-op
} }
override fun logException(e: Throwable) { override fun logException(e: Throwable) {
Logger.log(e) Logger.log(e)
} }

View File

@@ -71,6 +71,4 @@ object Discord {
const val application_Id = "1163925779692912771" const val application_Id = "1163925779692912771"
const val small_Image: String = const val small_Image: String =
"mp:external/GJEe4hKzr8w56IW6ZKQz43HFVEo8pOtA_C-dJiWwxKo/https/cdn.discordapp.com/app-icons/1163925779692912771/f6b42d41dfdf0b56fcc79d4a12d2ac66.png" "mp:external/GJEe4hKzr8w56IW6ZKQz43HFVEo8pOtA_C-dJiWwxKo/https/cdn.discordapp.com/app-icons/1163925779692912771/f6b42d41dfdf0b56fcc79d4a12d2ac66.png"
const val small_Image_AniList: String =
"mp:external/rHOIjjChluqQtGyL_UHk6Z4oAqiVYlo_B7HSGPLSoUg/%3Fsize%3D128/https/cdn.discordapp.com/icons/210521487378087947/a_f54f910e2add364a3da3bb2f2fce0c72.webp"
} }

View File

@@ -63,7 +63,7 @@ class DiscordService : Service() {
PowerManager.PARTIAL_WAKE_LOCK, PowerManager.PARTIAL_WAKE_LOCK,
"discordRPC:backgroundPresence" "discordRPC:backgroundPresence"
) )
wakeLock.acquire(30 * 60 * 1000L /*30 minutes*/) wakeLock.acquire(30*60*1000L /*30 minutes*/)
log("WakeLock Acquired") log("WakeLock Acquired")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel( val serviceChannel = NotificationChannel(
@@ -402,8 +402,7 @@ class DiscordService : Service() {
Thread.sleep(heartbeat.toLong()) Thread.sleep(heartbeat.toLong())
heartbeatSend(webSocket, sequence) heartbeatSend(webSocket, sequence)
log("WebSocket: Heartbeat Sent") log("WebSocket: Heartbeat Sent")
} catch (ignored: InterruptedException) { } catch (ignored: InterruptedException) { }
}
} }
} }

View File

@@ -71,8 +71,8 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
assets = Activity.Assets( assets = Activity.Assets(
largeImage = data.largeImage?.url?.discordUrl(), largeImage = data.largeImage?.url?.discordUrl(),
largeText = data.largeImage?.label, largeText = data.largeImage?.label,
smallImage = if (PrefManager.getVal(PrefName.ShowAniListIcon)) Discord.small_Image_AniList.discordUrl() else Discord.small_Image.discordUrl(), smallImage = data.smallImage?.url?.discordUrl(),
smallText = if (PrefManager.getVal(PrefName.ShowAniListIcon)) "Anilist" else "Dantotsu", smallText = data.smallImage?.label
), ),
buttons = data.buttons.map { it.label }, buttons = data.buttons.map { it.label },
metadata = Activity.Metadata( metadata = Activity.Metadata(

View File

@@ -22,81 +22,51 @@ class Contributors {
.parsed<JsonArray>().map { .parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<GithubResponse>(it) Mapper.json.decodeFromJsonElement<GithubResponse>(it)
} }
res.forEach { res.find { it.login == "rebelonion"}?.let { first ->
if (it.login == "SunglassJerry") return@forEach
val role = when (it.login) {
"rebelonion" -> "Owner & Maintainer"
"sneazy-ibo" -> "Contributor & Comment Moderator"
"WaiWhat" -> "Icon Designer"
else -> "Contributor"
}
developers = developers.plus( developers = developers.plus(
Developer( Developer(
it.login, first.login,
it.avatarUrl, first.avatarUrl,
role, "Owner and Maintainer",
it.htmlUrl first.htmlUrl
) )
) ).plus(arrayOf(
} Developer(
developers = developers.plus( "Wai What",
arrayOf( "https://avatars.githubusercontent.com/u/149729762?v=4",
"Icon Designer",
"https://github.com/WaiWhat"
),
Developer( Developer(
"MarshMeadow", "MarshMeadow",
"https://avatars.githubusercontent.com/u/88599122?v=4", "https://avatars.githubusercontent.com/u/88599122?v=4",
"Beta Icon Designer & Website Maintainer", "Beta Icon Designer",
"https://github.com/MarshMeadow?tab=repositories" "https://github.com/MarshMeadow?tab=repositories"
), ),
Developer( Developer(
"Zaxx69", "Zaxx69",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6342562-kxE8m4i7KUMK.png", "https://avatars.githubusercontent.com/u/138523882?v=4",
"Telegram Admin", "Telegram Admin",
"https://anilist.co/user/6342562" "https://github.com/Zaxx69"
), ),
Developer( Developer(
"Arif Alam", "Arif Alam",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6011177-2n994qtayiR9.jpg", "https://avatars.githubusercontent.com/u/70383209?v=4",
"Discord & Comment Moderator", "Head Discord Moderator",
"https://anilist.co/user/6011177" "https://youtube.com/watch?v=dQw4w9WgXcQ"
), )
))
}
res.filter {it.login != "rebelonion"}.forEach {
developers = developers.plus(
Developer( Developer(
"SunglassJeery", it.login,
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b5804776-FEKfP5wbz2xv.png", it.avatarUrl,
"Head Discord & Comment Moderator", "Contributor",
"https://anilist.co/user/5804776" it.htmlUrl
), )
Developer(
"Excited",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6131921-toSoGWmKbRA1.png",
"Comment Moderator",
"https://anilist.co/user/6131921"
),
Developer(
"Gurjshan",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6363228-rWQ3Pl3WuxzL.png",
"Comment Moderator",
"https://anilist.co/user/6363228"
),
Developer(
"NekoMimi",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6244220-HOpImMGMQAxW.jpg",
"Comment Moderator",
"https://anilist.co/user/6244220"
),
Developer(
"Zaidsenior",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6049773-8cjYeUOFUguv.jpg",
"Comment Moderator",
"https://anilist.co/user/6049773"
),
Developer(
"hastsu",
"https://cdn.discordapp.com/avatars/602422545077108749/20b4a6efa4314550e4ed51cdbe4fef3d.webp?size=160",
"Comment Moderator",
"https://anilist.co/user/6183359"
),
) )
) }
} }
return developers return developers
} }

View File

@@ -1,7 +1,9 @@
package ani.dantotsu.connections.github package ani.dantotsu.connections.github
import ani.dantotsu.Mapper import ani.dantotsu.Mapper
import ani.dantotsu.R
import ani.dantotsu.client import ani.dantotsu.client
import ani.dantotsu.getAppString
import ani.dantotsu.settings.Developer import ani.dantotsu.settings.Developer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -15,11 +17,10 @@ class Forks {
fun getForks(): Array<Developer> { fun getForks(): Array<Developer> {
var forks = arrayOf<Developer>() var forks = arrayOf<Developer>()
runBlocking(Dispatchers.IO) { runBlocking(Dispatchers.IO) {
val res = val res = client.get("https://api.github.com/repos/rebelonion/Dantotsu/forks")
client.get("https://api.github.com/repos/rebelonion/Dantotsu/forks?sort=stargazers") .parsed<JsonArray>().map {
.parsed<JsonArray>().map { Mapper.json.decodeFromJsonElement<GithubResponse>(it)
Mapper.json.decodeFromJsonElement<GithubResponse>(it) }
}
res.forEach { res.forEach {
forks = forks.plus( forks = forks.plus(
Developer( Developer(

View File

@@ -3,7 +3,7 @@ package ani.dantotsu.download
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import ani.dantotsu.media.Media import ani.dantotsu.download.DownloadsManager.Companion.findValidName
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
@@ -12,6 +12,7 @@ import ani.dantotsu.util.Logger
import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.deleteRecursively import com.anggrayudi.storage.file.deleteRecursively
import com.anggrayudi.storage.file.findFolder import com.anggrayudi.storage.file.findFolder
import com.anggrayudi.storage.file.moveFileTo
import com.anggrayudi.storage.file.moveFolderTo import com.anggrayudi.storage.file.moveFolderTo
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
@@ -19,7 +20,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.xdrop.fuzzywuzzy.FuzzySearch
import java.io.Serializable import java.io.Serializable
class DownloadsManager(private val context: Context) { class DownloadsManager(private val context: Context) {
@@ -53,14 +53,10 @@ class DownloadsManager(private val context: Context) {
saveDownloads() saveDownloads()
} }
fun removeDownload( fun removeDownload(downloadedType: DownloadedType, onFinished: () -> Unit) {
downloadedType: DownloadedType,
toast: Boolean = true,
onFinished: () -> Unit
) {
downloadsList.remove(downloadedType) downloadsList.remove(downloadedType)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
removeDirectory(downloadedType, toast) removeDirectory(downloadedType)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onFinished() onFinished()
} }
@@ -131,12 +127,7 @@ class DownloadsManager(private val context: Context) {
} }
} }
fun moveDownloadsDir( fun moveDownloadsDir(context: Context, oldUri: Uri, newUri: Uri, finished: (Boolean, String) -> Unit) {
context: Context,
oldUri: Uri,
newUri: Uri,
finished: (Boolean, String) -> Unit
) {
try { try {
if (oldUri == newUri) { if (oldUri == newUri) {
finished(false, "Source and destination are the same") finished(false, "Source and destination are the same")
@@ -150,41 +141,18 @@ class DownloadsManager(private val context: Context) {
DocumentFile.fromTreeUri(context, newUri) ?: throw Exception("New base is null") DocumentFile.fromTreeUri(context, newUri) ?: throw Exception("New base is null")
val folder = val folder =
oldBase.findFolder(BASE_LOCATION) ?: throw Exception("Base folder not found") oldBase.findFolder(BASE_LOCATION) ?: throw Exception("Base folder not found")
folder.moveFolderTo(context, newBase, false, BASE_LOCATION, object : folder.moveFolderTo(context, newBase, false, BASE_LOCATION, object:
FolderCallback() { FolderCallback() {
override fun onFailed(errorCode: ErrorCode) { override fun onFailed(errorCode: ErrorCode) {
when (errorCode) { when (errorCode) {
ErrorCode.CANCELED -> finished(false, "Move canceled") ErrorCode.CANCELED -> finished(false, "Move canceled")
ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> finished( ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> finished(false, "Cannot create file in target")
false, ErrorCode.INVALID_TARGET_FOLDER -> finished(true, "Invalid target folder") // seems to still work
"Cannot create file in target" ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> finished(false, "No space left on target path")
)
ErrorCode.INVALID_TARGET_FOLDER -> finished(
true,
"Invalid target folder"
) // seems to still work
ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> finished(
false,
"No space left on target path"
)
ErrorCode.UNKNOWN_IO_ERROR -> finished(false, "Unknown IO error") ErrorCode.UNKNOWN_IO_ERROR -> finished(false, "Unknown IO error")
ErrorCode.SOURCE_FOLDER_NOT_FOUND -> finished( ErrorCode.SOURCE_FOLDER_NOT_FOUND -> finished(false, "Source folder not found")
false, ErrorCode.STORAGE_PERMISSION_DENIED -> finished(false, "Storage permission denied")
"Source folder not found" ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> finished(false, "Target folder cannot have same path with source folder")
)
ErrorCode.STORAGE_PERMISSION_DENIED -> finished(
false,
"Storage permission denied"
)
ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> finished(
false,
"Target folder cannot have same path with source folder"
)
else -> finished(false, "Failed to move downloads: $errorCode") else -> finished(false, "Failed to move downloads: $errorCode")
} }
Logger.log("Failed to move downloads: $errorCode") Logger.log("Failed to move downloads: $errorCode")
@@ -196,7 +164,7 @@ class DownloadsManager(private val context: Context) {
super.onCompleted(result) super.onCompleted(result)
} }
}) })
} }
} catch (e: Exception) { } catch (e: Exception) {
snackString("Error: ${e.message}") snackString("Error: ${e.message}")
@@ -217,16 +185,17 @@ class DownloadsManager(private val context: Context) {
} }
} }
private fun removeDirectory(downloadedType: DownloadedType, toast: Boolean) { private fun removeDirectory(downloadedType: DownloadedType) {
val baseDirectory = getBaseDirectory(context, downloadedType.type) val baseDirectory = getBaseDirectory(context, downloadedType.type)
val directory = val directory =
baseDirectory?.findFolder(downloadedType.title)?.findFolder(downloadedType.chapter) baseDirectory?.findFolder(downloadedType.title)?.findFolder(downloadedType.chapter)
downloadsList.remove(downloadedType)
// Check if the directory exists and delete it recursively // Check if the directory exists and delete it recursively
if (directory?.exists() == true) { if (directory?.exists() == true) {
val deleted = directory.deleteRecursively(context, false) val deleted = directory.deleteRecursively(context, false)
if (deleted) { if (deleted) {
if (toast) snackString("Successfully deleted") snackString("Successfully deleted")
} else { } else {
snackString("Failed to delete directory") snackString("Failed to delete directory")
} }
@@ -257,7 +226,11 @@ class DownloadsManager(private val context: Context) {
private const val MANGA_SUB_LOCATION = "Manga" private const val MANGA_SUB_LOCATION = "Manga"
private const val ANIME_SUB_LOCATION = "Anime" private const val ANIME_SUB_LOCATION = "Anime"
private const val NOVEL_SUB_LOCATION = "Novel" private const val NOVEL_SUB_LOCATION = "Novel"
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
fun String?.findValidName(): String {
return this?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
}
/** /**
* Get and create a base directory for the given type * Get and create a base directory for the given type
@@ -265,6 +238,7 @@ class DownloadsManager(private val context: Context) {
* @param type the type of media * @param type the type of media
* @return the base directory * @return the base directory
*/ */
private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? { private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? {
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir)) val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
if (baseDirectory == Uri.EMPTY) return null if (baseDirectory == Uri.EMPTY) return null
@@ -309,12 +283,7 @@ class DownloadsManager(private val context: Context) {
} }
} }
fun getDirSize( fun getDirSize(context: Context, type: MediaType, title: String, chapter: String? = null): Long {
context: Context,
type: MediaType,
title: String,
chapter: String? = null
): Long {
val directory = getSubDirectory(context, type, false, title, chapter) ?: return 0 val directory = getSubDirectory(context, type, false, title, chapter) ?: return 0
var size = 0L var size = 0L
directory.listFiles().forEach { directory.listFiles().forEach {
@@ -323,19 +292,6 @@ class DownloadsManager(private val context: Context) {
return size return size
} }
fun addNoMedia(context: Context) {
val baseDirectory = getBaseDirectory(context) ?: return
if (baseDirectory.findFile(".nomedia") == null) {
baseDirectory.createFile("application/octet-stream", ".nomedia")
}
}
private fun getBaseDirectory(context: Context): DocumentFile? {
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
if (baseDirectory == Uri.EMPTY) return null
return DocumentFile.fromTreeUri(context, baseDirectory)
}
private fun DocumentFile.findOrCreateFolder( private fun DocumentFile.findOrCreateFolder(
name: String, overwrite: Boolean name: String, overwrite: Boolean
): DocumentFile? { ): DocumentFile? {
@@ -347,27 +303,9 @@ class DownloadsManager(private val context: Context) {
} }
} }
private const val RATIO_THRESHOLD = 95
fun Media.compareName(name: String): Boolean {
val mainName = mainName().findValidName().lowercase()
val ratio = FuzzySearch.ratio(mainName, name.lowercase())
return ratio > RATIO_THRESHOLD
}
fun String.compareName(name: String): Boolean {
val mainName = findValidName().lowercase()
val compareName = name.findValidName().lowercase()
val ratio = FuzzySearch.ratio(mainName, compareName)
return ratio > RATIO_THRESHOLD
}
} }
} }
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
private fun String?.findValidName(): String {
return this?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
}
data class DownloadedType( data class DownloadedType(
val pTitle: String, val pChapter: String, val type: MediaType val pTitle: String, val pChapter: String, val type: MediaType
) : Serializable { ) : Serializable {

View File

@@ -19,9 +19,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
@@ -34,10 +32,13 @@ import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.toast
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.anggrayudi.storage.file.forceDelete import com.anggrayudi.storage.file.forceDelete
import com.anggrayudi.storage.file.openOutputStream import com.anggrayudi.storage.file.openOutputStream
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.SessionState
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
@@ -73,7 +74,6 @@ class AnimeDownloaderService : Service() {
private val mutex = Mutex() private val mutex = Mutex()
private var isCurrentlyProcessing = false private var isCurrentlyProcessing = false
private var currentTasks: MutableList<AnimeDownloadTask> = mutableListOf() private var currentTasks: MutableList<AnimeDownloadTask> = mutableListOf()
private val ffExtension = Injekt.get<DownloadAddonManager>().extension?.extension
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
// This is only required for bound services. // This is only required for bound services.
@@ -82,11 +82,6 @@ class AnimeDownloaderService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (ffExtension == null) {
toast(getString(R.string.download_addon_not_found))
stopSelf()
return
}
notificationManager = NotificationManagerCompat.from(this) notificationManager = NotificationManagerCompat.from(this)
builder = builder =
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply { NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
@@ -168,7 +163,7 @@ class AnimeDownloaderService : Service() {
.map { it.sessionId }.toMutableList() .map { it.sessionId }.toMutableList()
sessionIds.addAll(currentTasks.filter { it.getTaskName() == taskName }.map { it.sessionId }) sessionIds.addAll(currentTasks.filter { it.getTaskName() == taskName }.map { it.sessionId })
sessionIds.forEach { sessionIds.forEach {
ffExtension!!.cancelDownload(it) FFmpegKit.cancel(it)
} }
currentTasks.removeAll { it.getTaskName() == taskName } currentTasks.removeAll { it.getTaskName() == taskName }
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
@@ -203,6 +198,7 @@ class AnimeDownloaderService : Service() {
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
suspend fun download(task: AnimeDownloadTask) { suspend fun download(task: AnimeDownloadTask) {
try { try {
//val downloadManager = Helper.downloadManager(this@AnimeDownloaderService)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(
@@ -232,44 +228,61 @@ class AnimeDownloaderService : Service() {
var percent = 0 var percent = 0
var totalLength = 0.0 var totalLength = 0.0
val path = ffExtension!!.setDownloadPath( val path = FFmpegKitConfig.getSafParameterForWrite(
this@AnimeDownloaderService, this@AnimeDownloaderService,
outputFile.uri outputFile.uri
) )
val headersStringBuilder = StringBuilder() val headersStringBuilder = StringBuilder().append(" ")
task.video.file.headers.forEach { task.video.file.headers.forEach {
headersStringBuilder.append("\"${it.key}: ${it.value}\"\'\r\n\'") headersStringBuilder.append("\"${it.key}: ${it.value}\"\'\r\n\'")
} }
if (!task.video.file.headers.containsKey("User-Agent")) { //headers should never be empty now headersStringBuilder.append(" ")
headersStringBuilder.append("\"").append("User-Agent: ") FFprobeKit.executeAsync(
.append(defaultHeaders["User-Agent"]).append("\"\'\r\n\'") "-headers $headersStringBuilder -i ${task.video.file.url} -show_entries format=duration -v quiet -of csv=\"p=0\"",
} {
val probeRequest = Logger.log("FFprobeKit: $it")
"-headers $headersStringBuilder -i ${task.video.file.url} -show_entries format=duration -v quiet -of csv=\"p=0\"" }, {
ffExtension.executeFFProbe( if (it.message.toDoubleOrNull() != null) {
probeRequest totalLength = it.message.toDouble()
) { }
if (it.toDoubleOrNull() != null) { })
totalLength = it.toDouble()
}
}
var request = "-headers"
val headers = headersStringBuilder.toString() val headers = headersStringBuilder.toString()
var request = "-headers $headers " if (task.video.file.headers.isNotEmpty()) {
request += headers
}
request += "-i ${task.video.file.url} -c copy -bsf:a aac_adtstoasc -tls_verify 0 $path -v trace" request += "-i ${task.video.file.url} -c copy -bsf:a aac_adtstoasc -tls_verify 0 $path -v trace"
Logger.log("Request: $request") println("Request: $request")
val ffTask = val ffTask =
ffExtension.executeFFMpeg(request) { FFmpegKit.executeAsync(request,
{ session ->
val state: SessionState = session.state
val returnCode = session.returnCode
// CALLED WHEN SESSION IS EXECUTED
Logger.log(
java.lang.String.format(
"FFmpeg process exited with state %s and rc %s.%s",
state,
returnCode,
session.failStackTrace
)
)
}, {
// CALLED WHEN SESSION PRINTS LOGS
Logger.log(it.message)
}) {
// CALLED WHEN SESSION GENERATES STATISTICS // CALLED WHEN SESSION GENERATES STATISTICS
val timeInMilliseconds = it val timeInMilliseconds = it.time
if (timeInMilliseconds > 0 && totalLength > 0) { if (timeInMilliseconds > 0 && totalLength > 0) {
percent = ((it / 1000) / totalLength * 100).toInt() percent = ((it.time / 1000) / totalLength * 100).toInt()
} }
Logger.log("Statistics: $it") Logger.log("Statistics: $it")
} }
task.sessionId = ffTask task.sessionId = ffTask.sessionId
currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId = currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId =
ffTask ffTask.sessionId
saveMediaInfo(task) saveMediaInfo(task)
task.subtitle?.let { task.subtitle?.let {
@@ -285,8 +298,8 @@ class AnimeDownloaderService : Service() {
} }
// periodically check if the download is complete // periodically check if the download is complete
while (ffExtension.getState(ffTask) != "COMPLETED") { while (ffTask.state != SessionState.COMPLETED) {
if (ffExtension.getState(ffTask) == "FAILED") { if (ffTask.state == SessionState.FAILED) {
Logger.log("Download failed") Logger.log("Download failed")
builder.setContentText( builder.setContentText(
"${ "${
@@ -297,15 +310,14 @@ class AnimeDownloaderService : Service() {
} Download failed" } Download failed"
) )
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
toast("${getTaskName(task.title, task.episode)} Download failed") snackString("${getTaskName(task.title, task.episode)} Download failed")
Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}") Logger.log("Download failed: ${ffTask.failStackTrace}")
downloadsManager.removeDownload( downloadsManager.removeDownload(
DownloadedType( DownloadedType(
task.title, task.title,
task.episode, task.episode,
MediaType.ANIME, MediaType.ANIME,
), )
false
) {} ) {}
Injekt.get<CrashlyticsInterface>().logException( Injekt.get<CrashlyticsInterface>().logException(
Exception( Exception(
@@ -333,8 +345,8 @@ class AnimeDownloaderService : Service() {
} }
kotlinx.coroutines.delay(2000) kotlinx.coroutines.delay(2000)
} }
if (ffExtension.getState(ffTask) == "COMPLETED") { if (ffTask.state == SessionState.COMPLETED) {
if (ffExtension.hadError(ffTask)) { if (ffTask.returnCode.isValueError) {
Logger.log("Download failed") Logger.log("Download failed")
builder.setContentText( builder.setContentText(
"${ "${

View File

@@ -33,7 +33,7 @@ import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName import ani.dantotsu.download.DownloadsManager.Companion.findValidName
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
@@ -175,7 +175,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
// Get the OfflineAnimeModel that was clicked // Get the OfflineAnimeModel that was clicked
val item = adapter.getItem(position) as OfflineAnimeModel val item = adapter.getItem(position) as OfflineAnimeModel
val media = val media =
downloadManager.animeDownloadedTypes.firstOrNull { it.title.compareName(item.title) } downloadManager.animeDownloadedTypes.firstOrNull { it.title == item.title.findValidName() }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
val mediaModel = getMedia(it) val mediaModel = getMedia(it)

View File

@@ -30,7 +30,6 @@ import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@@ -169,8 +168,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
// Get the OfflineMangaModel that was clicked // Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel val item = adapter.getItem(position) as OfflineMangaModel
val media = val media =
downloadManager.mangaDownloadedTypes.firstOrNull { it.title.compareName(item.title) } downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title.compareName(item.title) } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
ContextCompat.startActivity( ContextCompat.startActivity(

View File

@@ -9,6 +9,7 @@ import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.os.Build import android.os.Build
import android.os.Environment
import android.os.IBinder import android.os.IBinder
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
@@ -49,6 +50,8 @@ import okio.sink
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -267,9 +270,7 @@ class NovelDownloaderService : Service() {
task.coverUrl?.let { task.coverUrl?.let {
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") } file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
} }
val outputStream = val outputStream = this@NovelDownloaderService.contentResolver.openOutputStream(file.uri) ?: throw Exception("Could not open OutputStream")
this@NovelDownloaderService.contentResolver.openOutputStream(file.uri)
?: throw Exception("Could not open OutputStream")
val sink = outputStream.sink().buffer() val sink = outputStream.sink().buffer()
val responseBody = response.body val responseBody = response.body
@@ -357,7 +358,7 @@ class NovelDownloaderService : Service() {
private fun saveMediaInfo(task: DownloadTask) { private fun saveMediaInfo(task: DownloadTask) {
launchIO { launchIO {
val directory = val directory =
getSubDirectory( DownloadsManager.getSubDirectory(
this@NovelDownloaderService, this@NovelDownloaderService,
MediaType.NOVEL, MediaType.NOVEL,
false, false,

View File

@@ -7,23 +7,49 @@ import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.HttpDataSource
import androidx.media3.datasource.cache.NoOpCacheEvictor
import androidx.media3.datasource.cache.SimpleCache
import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.offline.Download
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 ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.download.anime.AnimeServiceDataSingleton
import ani.dantotsu.logError
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.okHttpClient
import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.parsers.VideoType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.io.IOException
import java.util.concurrent.Executors
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
object Helper { object Helper {

View File

@@ -101,7 +101,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
ContextCompat.startActivity( ContextCompat.startActivity(
view.context, view.context,
Intent(view.context, ProfileActivity::class.java) Intent(view.context, ProfileActivity::class.java)
.putExtra("userId", Anilist.userid), null .putExtra("userId", Anilist.userid),null
) )
false false
} }
@@ -110,8 +110,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
trendingBinding.searchBar.performClick() trendingBinding.searchBar.performClick()
} }
trendingBinding.notificationCount.visibility = trendingBinding.notificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString() trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
listOf( listOf(
@@ -168,8 +167,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
trendingBinding.trendingProgressBar.visibility = View.GONE trendingBinding.trendingProgressBar.visibility = View.GONE
trendingBinding.trendingViewPager.adapter = adaptor trendingBinding.trendingViewPager.adapter = adaptor
trendingBinding.trendingViewPager.offscreenPageLimit = 3 trendingBinding.trendingViewPager.offscreenPageLimit = 3
trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
RecyclerView.OVER_SCROLL_NEVER
trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer()) trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper()) trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable { trendRun = Runnable {
@@ -197,7 +195,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
fun updateRecent(adaptor: MediaAdaptor) { fun updateRecent(adaptor: MediaAdaptor) {
binding.apply { binding.apply{
init( init(
adaptor, adaptor,
animeUpdatedRecyclerView, animeUpdatedRecyclerView,
@@ -212,9 +210,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
} }
fun updateMovies(adaptor: MediaAdaptor) { fun updateMovies(adaptor: MediaAdaptor) {
binding.apply { binding.apply{
init( init(
adaptor, adaptor,
animeMoviesRecyclerView, animeMoviesRecyclerView,
@@ -225,7 +222,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
fun updateTopRated(adaptor: MediaAdaptor) { fun updateTopRated(adaptor: MediaAdaptor) {
binding.apply { binding.apply{
init( init(
adaptor, adaptor,
animeTopRatedRecyclerView, animeTopRatedRecyclerView,
@@ -234,9 +231,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
) )
} }
} }
fun updateMostFav(adaptor: MediaAdaptor) { fun updateMostFav(adaptor: MediaAdaptor) {
binding.apply { binding.apply{
init( init(
adaptor, adaptor,
animeMostFavRecyclerView, animeMostFavRecyclerView,
@@ -245,8 +241,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
) )
} }
} }
fun init(adaptor: MediaAdaptor,recyclerView: RecyclerView, progress: View, title: View){
fun init(adaptor: MediaAdaptor, recyclerView: RecyclerView, progress: View, title: View) {
progress.visibility = View.GONE progress.visibility = View.GONE
recyclerView.adapter = adaptor recyclerView.adapter = adaptor
recyclerView.layoutManager = recyclerView.layoutManager =
@@ -261,7 +256,6 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
recyclerView.layoutAnimation = recyclerView.layoutAnimation =
LayoutAnimationController(setSlideIn(), 0.25f) LayoutAnimationController(setSlideIn(), 0.25f)
} }
fun updateAvatar() { fun updateAvatar() {
if (Anilist.avatar != null && ready.value == true) { if (Anilist.avatar != null && ready.value == true) {
trendingBinding.userAvatar.loadImage(Anilist.avatar) trendingBinding.userAvatar.loadImage(Anilist.avatar)

View File

@@ -81,10 +81,7 @@ class HomeFragment : Fragment() {
binding.homeUserChaptersRead.text = Anilist.chapterRead.toString() binding.homeUserChaptersRead.text = Anilist.chapterRead.toString()
binding.homeUserAvatar.loadImage(Anilist.avatar) binding.homeUserAvatar.loadImage(Anilist.avatar)
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations) val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
blurImage( blurImage(if (bannerAnimations) binding.homeUserBg else binding.homeUserBgNoKen, Anilist.bg)
if (bannerAnimations) binding.homeUserBg else binding.homeUserBgNoKen,
Anilist.bg
)
binding.homeUserDataProgressBar.visibility = View.GONE binding.homeUserDataProgressBar.visibility = View.GONE
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0 binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
@@ -131,7 +128,7 @@ class HomeFragment : Fragment() {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
ContextCompat.startActivity( ContextCompat.startActivity(
requireContext(), Intent(requireContext(), ProfileActivity::class.java) requireContext(), Intent(requireContext(), ProfileActivity::class.java)
.putExtra("userId", Anilist.userid), null .putExtra("userId", Anilist.userid),null
) )
false false
} }
@@ -379,7 +376,6 @@ class HomeFragment : Fragment() {
} }
} }
} }
override fun onResume() { override fun onResume() {
if (!model.loaded) Refresh.activity[1]!!.postValue(true) if (!model.loaded) Refresh.activity[1]!!.postValue(true)
if (_binding != null) { if (_binding != null) {

View File

@@ -172,13 +172,7 @@ class MangaFragment : Fragment() {
} }
model.getPopularManhwa().observe(viewLifecycleOwner) { model.getPopularManhwa().observe(viewLifecycleOwner) {
if (it != null) { if (it != null) {
mangaPageAdapter.updateTrendingManhwa( mangaPageAdapter.updateTrendingManhwa(MediaAdaptor(0, it, requireActivity()))
MediaAdaptor(
0,
it,
requireActivity()
)
)
} }
} }
model.getTopRated().observe(viewLifecycleOwner) { model.getTopRated().observe(viewLifecycleOwner) {

View File

@@ -101,7 +101,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
ContextCompat.startActivity( ContextCompat.startActivity(
view.context, view.context,
Intent(view.context, ProfileActivity::class.java) Intent(view.context, ProfileActivity::class.java)
.putExtra("userId", Anilist.userid), null .putExtra("userId", Anilist.userid),null
) )
false false
} }
@@ -156,8 +156,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
trendingBinding.trendingProgressBar.visibility = View.GONE trendingBinding.trendingProgressBar.visibility = View.GONE
trendingBinding.trendingViewPager.adapter = adaptor trendingBinding.trendingViewPager.adapter = adaptor
trendingBinding.trendingViewPager.offscreenPageLimit = 3 trendingBinding.trendingViewPager.offscreenPageLimit = 3
trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
RecyclerView.OVER_SCROLL_NEVER
trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer()) trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper()) trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable { trendRun = Runnable {
@@ -192,7 +191,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
) )
} }
} }
fun updateTrendingManhwa(adaptor: MediaAdaptor) { fun updateTrendingManhwa(adaptor: MediaAdaptor) {
binding.apply { binding.apply {
init( init(
@@ -203,7 +201,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
) )
} }
} }
fun updateNovel(adaptor: MediaAdaptor) { fun updateNovel(adaptor: MediaAdaptor) {
binding.apply { binding.apply {
init( init(
@@ -215,7 +212,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
} }
} }
fun updateTopRated(adaptor: MediaAdaptor) { fun updateTopRated(adaptor: MediaAdaptor) {
binding.apply { binding.apply {
init( init(
@@ -226,7 +222,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
) )
} }
} }
fun updateMostFav(adaptor: MediaAdaptor) { fun updateMostFav(adaptor: MediaAdaptor) {
binding.apply { binding.apply {
init( init(
@@ -239,8 +234,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
mangaPopular.startAnimation(setSlideUp()) mangaPopular.startAnimation(setSlideUp())
} }
} }
fun init(adaptor: MediaAdaptor,recyclerView: RecyclerView, progress: View, title: View){
fun init(adaptor: MediaAdaptor, recyclerView: RecyclerView, progress: View, title: View) {
progress.visibility = View.GONE progress.visibility = View.GONE
recyclerView.adapter = adaptor recyclerView.adapter = adaptor
recyclerView.layoutManager = recyclerView.layoutManager =

View File

@@ -3,10 +3,9 @@ package ani.dantotsu.media
import java.io.Serializable import java.io.Serializable
data class Author( data class Author(
var id: Int, val id: Int,
var name: String?, val name: String?,
var image: String?, val image: String?,
var role: String?, val role: String?,
var yearMedia: MutableMap<String, ArrayList<Media>>? = null, var yearMedia: MutableMap<String, ArrayList<Media>>? = null
var character: ArrayList<Character>? = null
) : Serializable ) : Serializable

View File

@@ -12,7 +12,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.EmptyAdapter import ani.dantotsu.EmptyAdapter
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
@@ -33,6 +32,7 @@ class AuthorActivity : AppCompatActivity() {
private val model: OtherDetailsViewModel by viewModels() private val model: OtherDetailsViewModel by viewModels()
private var author: Author? = null private var author: Author? = null
private var loaded = false private var loaded = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -55,15 +55,14 @@ class AuthorActivity : AppCompatActivity() {
binding.studioClose.setOnClickListener { binding.studioClose.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
model.getAuthor().observe(this) { model.getAuthor().observe(this) {
if (it != null) { if (it != null) {
author = it author = it
loaded = true loaded = true
binding.studioProgressBar.visibility = View.GONE binding.studioProgressBar.visibility = View.GONE
binding.studioRecycler.visibility = View.VISIBLE binding.studioRecycler.visibility = View.VISIBLE
if (author!!.yearMedia.isNullOrEmpty()) {
binding.studioRecycler.visibility = View.GONE
}
val titlePosition = arrayListOf<Int>() val titlePosition = arrayListOf<Int>()
val concatAdapter = ConcatAdapter() val concatAdapter = ConcatAdapter()
val map = author!!.yearMedia ?: return@observe val map = author!!.yearMedia ?: return@observe
@@ -90,19 +89,9 @@ class AuthorActivity : AppCompatActivity() {
concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true)) concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true))
concatAdapter.addAdapter(EmptyAdapter(empty)) concatAdapter.addAdapter(EmptyAdapter(empty))
} }
binding.studioRecycler.adapter = concatAdapter binding.studioRecycler.adapter = concatAdapter
binding.studioRecycler.layoutManager = gridLayoutManager binding.studioRecycler.layoutManager = gridLayoutManager
binding.charactersRecycler.visibility = View.VISIBLE
binding.charactersText.visibility = View.VISIBLE
binding.charactersRecycler.adapter =
CharacterAdapter(author!!.character ?: arrayListOf())
binding.charactersRecycler.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
if (author!!.character.isNullOrEmpty()) {
binding.charactersRecycler.visibility = View.GONE
binding.charactersText.visibility = View.GONE
}
} }
} }
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) } val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) }

View File

@@ -15,7 +15,7 @@ import ani.dantotsu.setAnimation
import java.io.Serializable import java.io.Serializable
class AuthorAdapter( class AuthorAdapter(
private val authorList: ArrayList<Author>, private val authorList: ArrayList<Author>
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() { ) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
val binding = val binding =
@@ -23,7 +23,7 @@ class AuthorAdapter(
return AuthorViewHolder(binding) return AuthorViewHolder(binding)
} }
override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) { override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
setAnimation(binding.root.context, holder.binding.root) setAnimation(binding.root.context, holder.binding.root)
val author = authorList[position] val author = authorList[position]

View File

@@ -14,6 +14,5 @@ data class Character(
var age: String? = null, var age: String? = null,
var gender: String? = null, var gender: String? = null,
var dateOfBirth: FuzzyDate? = null, var dateOfBirth: FuzzyDate? = null,
var roles: ArrayList<Media>? = null, var roles: ArrayList<Media>? = null
val voiceActor: ArrayList<Author>? = null,
) : Serializable ) : Serializable

View File

@@ -28,7 +28,6 @@ class CharacterAdapter(
setAnimation(binding.root.context, holder.binding.root) setAnimation(binding.root.context, holder.binding.root)
val character = characterList[position] val character = characterList[position]
val whitespace = "${character.role} " val whitespace = "${character.role} "
character.voiceActor
binding.itemCompactRelation.text = whitespace binding.itemCompactRelation.text = whitespace
binding.itemCompactImage.loadImage(character.image) binding.itemCompactImage.loadImage(character.image)
binding.itemCompactTitle.text = character.name binding.itemCompactTitle.text = character.name

View File

@@ -95,8 +95,7 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
} }
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
character.isFav = character.isFav = Anilist.query.isUserFav(AnilistMutations.FavType.CHARACTER, character.id)
Anilist.query.isUserFav(AnilistMutations.FavType.CHARACTER, character.id)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.characterFav.setImageResource( binding.characterFav.setImageResource(

View File

@@ -2,9 +2,7 @@ package ani.dantotsu.media
import android.app.Activity import android.app.Activity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
@@ -29,28 +27,16 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
"${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") + "${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
(if (character.gender != "null") (if (character.gender != "null")
currActivity()!!.getString(R.string.gender) + " " + when (character.gender) { currActivity()!!.getString(R.string.gender) + " " + when (character.gender) {
currActivity()!!.getString(R.string.male) -> currActivity()!!.getString( currActivity()!!.getString(R.string.male) -> currActivity()!!.getString(R.string.male)
R.string.male currActivity()!!.getString(R.string.female) -> currActivity()!!.getString(R.string.female)
) else -> character.gender
} else "") + "\n" + character.description
currActivity()!!.getString(R.string.female) -> currActivity()!!.getString(
R.string.female
)
else -> character.gender
} else "") + "\n" + character.description
binding.characterDesc.isTextSelectable binding.characterDesc.isTextSelectable
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create()) val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create())
.usePlugin(SpoilerPlugin()).build() .usePlugin(SpoilerPlugin()).build()
markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||")) markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||"))
binding.voiceActorRecycler.adapter = AuthorAdapter(character.voiceActor ?: arrayListOf())
binding.voiceActorRecycler.layoutManager = LinearLayoutManager(
activity, LinearLayoutManager.HORIZONTAL, false
)
if (binding.voiceActorRecycler.adapter!!.itemCount == 0) {
binding.voiceActorContainer.visibility = View.GONE
}
} }
override fun getItemCount(): Int = 1 override fun getItemCount(): Int = 1

View File

@@ -7,7 +7,6 @@ import ani.dantotsu.connections.anilist.api.MediaList
import ani.dantotsu.connections.anilist.api.MediaType import ani.dantotsu.connections.anilist.api.MediaType
import ani.dantotsu.media.anime.Anime import ani.dantotsu.media.anime.Anime
import ani.dantotsu.media.manga.Manga import ani.dantotsu.media.manga.Manga
import ani.dantotsu.profile.User
import java.io.Serializable import java.io.Serializable
import ani.dantotsu.connections.anilist.api.Media as ApiMedia import ani.dantotsu.connections.anilist.api.Media as ApiMedia
@@ -67,7 +66,7 @@ data class Media(
var sequel: Media? = null, var sequel: Media? = null,
var relations: ArrayList<Media>? = null, var relations: ArrayList<Media>? = null,
var recommendations: ArrayList<Media>? = null, var recommendations: ArrayList<Media>? = null,
var users: ArrayList<User>? = null,
var vrvId: String? = null, var vrvId: String? = null,
var crunchySlug: String? = null, var crunchySlug: String? = null,
@@ -100,7 +99,7 @@ data class Media(
startDate = apiMedia.startDate, startDate = apiMedia.startDate,
endDate = apiMedia.endDate, endDate = apiMedia.endDate,
favourites = apiMedia.favourites, favourites = apiMedia.favourites,
timeUntilAiring = apiMedia.nextAiringEpisode?.timeUntilAiring?.let { it.toLong() * 1000 }, timeUntilAiring = apiMedia.nextAiringEpisode?.timeUntilAiring?.let { it.toLong() * 1000},
anime = if (apiMedia.type == MediaType.ANIME) Anime( anime = if (apiMedia.type == MediaType.ANIME) Anime(
totalEpisodes = apiMedia.episodes, totalEpisodes = apiMedia.episodes,
nextAiringEpisode = apiMedia.nextAiringEpisode?.episode?.minus(1) nextAiringEpisode = apiMedia.nextAiringEpisode?.episode?.minus(1)
@@ -115,8 +114,7 @@ data class Media(
this.userScore = mediaList.score?.toInt() ?: 0 this.userScore = mediaList.score?.toInt() ?: 0
this.userStatus = mediaList.status?.toString() this.userStatus = mediaList.status?.toString()
this.userUpdatedAt = mediaList.updatedAt?.toLong() this.userUpdatedAt = mediaList.updatedAt?.toLong()
this.genres = this.genres = mediaList.media?.genres?.toMutableList() as? ArrayList<String>? ?: arrayListOf()
mediaList.media?.genres?.toMutableList() as? ArrayList<String>? ?: arrayListOf()
} }
constructor(mediaEdge: MediaEdge) : this(mediaEdge.node!!) { constructor(mediaEdge: MediaEdge) : this(mediaEdge.node!!) {

View File

@@ -149,22 +149,14 @@ class MediaAdaptor(
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score) (if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
) )
if (media.anime != null) { if (media.anime != null) {
val itemTotal = " " + if ((media.anime.totalEpisodes val itemTotal = " " + if ((media.anime.totalEpisodes ?: 0) != 1) currActivity()!!.getString(R.string.episode_plural) else currActivity()!!.getString(R.string.episode_singular)
?: 0) != 1
) currActivity()!!.getString(R.string.episode_plural) else currActivity()!!.getString(
R.string.episode_singular
)
b.itemTotal.text = itemTotal b.itemTotal.text = itemTotal
b.itemCompactTotal.text = b.itemCompactTotal.text =
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
?: "??").toString()) else (media.anime.totalEpisodes ?: "??").toString()) else (media.anime.totalEpisodes
?: "??").toString() ?: "??").toString()
} else if (media.manga != null) { } else if (media.manga != null) {
val itemTotal = " " + if ((media.manga.totalChapters val itemTotal = " " + if ((media.manga.totalChapters ?: 0) != 1) currActivity()!!.getString(R.string.chapter_plural) else currActivity()!!.getString(R.string.chapter_singular)
?: 0) != 1
) currActivity()!!.getString(R.string.chapter_plural) else currActivity()!!.getString(
R.string.chapter_singular
)
b.itemTotal.text = itemTotal b.itemTotal.text = itemTotal
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}" b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
} }
@@ -191,10 +183,7 @@ class MediaAdaptor(
AccelerateDecelerateInterpolator() AccelerateDecelerateInterpolator()
) )
) )
blurImage( blurImage(if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen , media.banner ?: media.cover)
if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen,
media.banner ?: media.cover
)
b.itemCompactOngoing.isVisible = b.itemCompactOngoing.isVisible =
media.status == currActivity()!!.getString(R.string.status_releasing) media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName
@@ -243,10 +232,7 @@ class MediaAdaptor(
AccelerateDecelerateInterpolator() AccelerateDecelerateInterpolator()
) )
) )
blurImage( blurImage(if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen , media.banner ?: media.cover)
if (bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen,
media.banner ?: media.cover
)
b.itemCompactOngoing.isVisible = b.itemCompactOngoing.isVisible =
media.status == currActivity()!!.getString(R.string.status_releasing) media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName

View File

@@ -109,9 +109,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
// Ui init // Ui init
initActivity(this) initActivity(this)
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight }
bottomMargin = navBarHeight
}
val oldMargin = binding.mediaViewPager.marginBottom val oldMargin = binding.mediaViewPager.marginBottom
AndroidBug5497Workaround.assistActivity(this) { AndroidBug5497Workaround.assistActivity(this) {
if (it) { if (it) {
@@ -127,11 +125,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
} }
} }
val navBarRightMargin = if (resources.configuration.orientation == val navBarRightMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
) navBarHeight else 0
val navBarBottomMargin = if (resources.configuration.orientation == val navBarBottomMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
) 0 else navBarHeight
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
rightMargin = navBarRightMargin rightMargin = navBarRightMargin
bottomMargin = navBarBottomMargin bottomMargin = navBarBottomMargin
@@ -349,13 +345,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
adult = media.isAdult adult = media.isAdult
if (media.anime != null) { if (media.anime != null) {
viewPager.adapter = viewPager.adapter =
ViewPagerAdapter( ViewPagerAdapter(supportFragmentManager, lifecycle, SupportedMedia.ANIME, media, intent.getIntExtra("commentId", -1))
supportFragmentManager,
lifecycle,
SupportedMedia.ANIME,
media,
intent.getIntExtra("commentId", -1)
)
} else if (media.manga != null) { } else if (media.manga != null) {
viewPager.adapter = ViewPagerAdapter( viewPager.adapter = ViewPagerAdapter(
supportFragmentManager, supportFragmentManager,
@@ -378,8 +368,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
} else { } else {
navBar.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read) navBar.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read)
} }
val commentTab = val commentTab = navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
navBar.addTab(infoTab) navBar.addTab(infoTab)
navBar.addTab(watchTab) navBar.addTab(watchTab)
navBar.addTab(commentTab) navBar.addTab(commentTab)
@@ -423,12 +412,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
val rightMargin = if (resources.configuration.orientation == val rightMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
) navBarHeight else 0
val bottomMargin = if (resources.configuration.orientation == val bottomMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
) 0 else navBarHeight val params : ViewGroup.MarginLayoutParams =
val params: ViewGroup.MarginLayoutParams =
navBar.layoutParams as ViewGroup.MarginLayoutParams navBar.layoutParams as ViewGroup.MarginLayoutParams
params.updateMargins(right = rightMargin, bottom = bottomMargin) params.updateMargins(right = rightMargin, bottom = bottomMargin)
} }
@@ -461,7 +448,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
SupportedMedia.MANGA -> MangaReadFragment() SupportedMedia.MANGA -> MangaReadFragment()
SupportedMedia.NOVEL -> NovelReadFragment() SupportedMedia.NOVEL -> NovelReadFragment()
} }
2 -> { 2 -> {
val fragment = CommentsFragment() val fragment = CommentsFragment()
val bundle = Bundle() val bundle = Bundle()

View File

@@ -56,11 +56,9 @@ class MediaDetailsViewModel : ViewModel() {
media.anime != null -> { media.anime != null -> {
AnimeSources.list.size - 1 AnimeSources.list.size - 1
} }
media.format == "MANGA" || media.format == "ONE_SHOT" -> { media.format == "MANGA" || media.format == "ONE_SHOT" -> {
MangaSources.list.size - 1 MangaSources.list.size - 1
} }
else -> { else -> {
NovelSources.list.size - 1 NovelSources.list.size - 1
} }

View File

@@ -27,6 +27,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.GenresViewModel import ani.dantotsu.connections.anilist.GenresViewModel
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
import ani.dantotsu.countDown
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.databinding.ActivityGenreBinding import ani.dantotsu.databinding.ActivityGenreBinding
import ani.dantotsu.databinding.FragmentMediaInfoBinding import ani.dantotsu.databinding.FragmentMediaInfoBinding
@@ -37,7 +38,6 @@ import ani.dantotsu.databinding.ItemTitleRecyclerBinding
import ani.dantotsu.databinding.ItemTitleSearchBinding import ani.dantotsu.databinding.ItemTitleSearchBinding
import ani.dantotsu.databinding.ItemTitleTextBinding import ani.dantotsu.databinding.ItemTitleTextBinding
import ani.dantotsu.databinding.ItemTitleTrailerBinding import ani.dantotsu.databinding.ItemTitleTrailerBinding
import ani.dantotsu.displayTimer
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.px import ani.dantotsu.px
@@ -145,8 +145,7 @@ class MediaInfoFragment : Fragment() {
} }
binding.mediaInfoDurationContainer.visibility = View.VISIBLE binding.mediaInfoDurationContainer.visibility = View.VISIBLE
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
val seasonInfo = val seasonInfo = "${(media.anime.season ?: "??")} ${(media.anime.seasonYear ?: "??")}"
"${(media.anime.season ?: "??")} ${(media.anime.seasonYear ?: "??")}"
binding.mediaInfoSeason.text = seasonInfo binding.mediaInfoSeason.text = seasonInfo
if (media.anime.mainStudio != null) { if (media.anime.mainStudio != null) {
@@ -183,9 +182,9 @@ class MediaInfoFragment : Fragment() {
} }
binding.mediaInfoTotalTitle.setText(R.string.total_eps) binding.mediaInfoTotalTitle.setText(R.string.total_eps)
val infoTotal = if (media.anime.nextAiringEpisode != null) val infoTotal = if (media.anime.nextAiringEpisode != null)
"${media.anime.nextAiringEpisode} | ${media.anime.totalEpisodes ?: "~"}" "${media.anime.nextAiringEpisode} | ${media.anime.totalEpisodes ?: "~"}"
else else
(media.anime.totalEpisodes ?: "~").toString() (media.anime.totalEpisodes ?: "~").toString()
binding.mediaInfoTotal.text = infoTotal binding.mediaInfoTotal.text = infoTotal
} else if (media.manga != null) { } else if (media.manga != null) {
@@ -214,8 +213,7 @@ class MediaInfoFragment : Fragment() {
(media.description ?: "null").replace("\\n", "<br>").replace("\\\"", "\""), (media.description ?: "null").replace("\\n", "<br>").replace("\\\"", "\""),
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
val infoDesc = val infoDesc = tripleTab + if (desc.toString() != "null") desc else getString(R.string.no_description_available)
tripleTab + if (desc.toString() != "null") desc else getString(R.string.no_description_available)
binding.mediaInfoDescription.text = infoDesc binding.mediaInfoDescription.text = infoDesc
binding.mediaInfoDescription.setOnClickListener { binding.mediaInfoDescription.setOnClickListener {
@@ -227,7 +225,8 @@ class MediaInfoFragment : Fragment() {
.setDuration(400).start() .setDuration(400).start()
} }
} }
displayTimer(media, binding.mediaInfoContainer)
countDown(media, binding.mediaInfoContainer)
val parent = _binding?.mediaInfoContainer!! val parent = _binding?.mediaInfoContainer!!
val screenWidth = resources.displayMetrics.run { widthPixels / density } val screenWidth = resources.displayMetrics.run { widthPixels / density }
@@ -572,23 +571,6 @@ class MediaInfoFragment : Fragment() {
parent.addView(root) parent.addView(root)
} }
} }
if (!media.users.isNullOrEmpty() && !offline) {
ItemTitleRecyclerBinding.inflate(
LayoutInflater.from(context),
parent,
false
).apply {
itemTitle.setText(R.string.social)
itemRecycler.adapter =
MediaSocialAdapter(media.users!!)
itemRecycler.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
parent.addView(root)
}
}
} }
} }

View File

@@ -1,70 +0,0 @@
package ani.dantotsu.media
import android.annotation.SuppressLint
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemFollowerGridBinding
import ani.dantotsu.loadImage
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.User
import ani.dantotsu.setAnimation
class MediaSocialAdapter(private val user: ArrayList<User>) :
RecyclerView.Adapter<MediaSocialAdapter.DeveloperViewHolder>() {
inner class DeveloperViewHolder(val binding: ItemFollowerGridBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeveloperViewHolder {
return DeveloperViewHolder(
ItemFollowerGridBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: DeveloperViewHolder, position: Int) {
holder.binding.apply {
val user = user[position]
val score = user.score?.div(10.0) ?: 0.0
setAnimation(root.context, root)
profileUserName.text = user.name
profileInfo.apply {
text = when (user.status) {
"CURRENT" -> "WATCHING"
else -> user.status ?: ""
}
visibility = View.VISIBLE
}
profileCompactUserProgress.text = user.progress.toString()
profileCompactScore.text = score.toString()
profileCompactTotal.text = " | ${user.totalEpisodes ?: "~"}"
profileUserAvatar.loadImage(user.pfp)
val scoreDrawable = if (score == 0.0) R.drawable.score else R.drawable.user_score
profileCompactScoreBG.apply {
visibility = View.VISIBLE
background = ContextCompat.getDrawable(root.context, scoreDrawable)
}
profileCompactProgressContainer.visibility = View.VISIBLE
profileUserAvatar.setOnClickListener {
val intent = Intent(root.context, ProfileActivity::class.java).apply {
putExtra("userId", user.id)
}
ContextCompat.startActivity(root.context, intent, null)
}
}
}
override fun getItemCount(): Int = user.size
}

View File

@@ -1,15 +1,11 @@
package ani.dantotsu.media package ani.dantotsu.media
interface Type { enum class MediaType {
fun asText(): String
}
enum class MediaType : Type {
ANIME, ANIME,
MANGA, MANGA,
NOVEL; NOVEL;
override fun asText(): String { fun asText(): String {
return when (this) { return when (this) {
ANIME -> "Anime" ANIME -> "Anime"
MANGA -> "Manga" MANGA -> "Manga"
@@ -18,38 +14,12 @@ enum class MediaType : Type {
} }
companion object { companion object {
fun fromText(string: String): MediaType? { fun fromText(string : String): MediaType {
return when (string) { return when (string) {
"Anime" -> ANIME "Anime" -> ANIME
"Manga" -> MANGA "Manga" -> MANGA
"Novel" -> NOVEL "Novel" -> NOVEL
else -> { else -> { ANIME }
null
}
}
}
}
}
enum class AddonType : Type {
TORRENT,
DOWNLOAD;
override fun asText(): String {
return when (this) {
TORRENT -> "Torrent"
DOWNLOAD -> "Download"
}
}
companion object {
fun fromText(string: String): AddonType? {
return when (string) {
"Torrent" -> TORRENT
"Download" -> DOWNLOAD
else -> {
null
}
} }
} }
} }

View File

@@ -78,10 +78,8 @@ class SearchActivity : AppCompatActivity() {
source = intent.getStringExtra("source"), source = intent.getStringExtra("source"),
countryOfOrigin = intent.getStringExtra("country"), countryOfOrigin = intent.getStringExtra("country"),
season = intent.getStringExtra("season"), season = intent.getStringExtra("season"),
seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra("seasonYear") seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra("seasonYear")?.toIntOrNull() else null,
?.toIntOrNull() else null, startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra("seasonYear")?.toIntOrNull() else null,
startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra("seasonYear")
?.toIntOrNull() else null,
results = mutableListOf(), results = mutableListOf(),
hasNextPage = false hasNextPage = false
) )

View File

@@ -13,6 +13,7 @@ import android.view.animation.AlphaAnimation
import android.view.animation.Animation import android.view.animation.Animation
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.PopupMenu import android.widget.PopupMenu
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
@@ -59,7 +60,6 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
} }
binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0) binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
val binding = val binding =
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@@ -129,42 +129,36 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_popular -> { R.id.sort_by_popular -> {
activity.result.sort = Anilist.sortBy[1] activity.result.sort = Anilist.sortBy[1]
activity.updateChips.invoke() activity.updateChips.invoke()
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_trending -> { R.id.sort_by_trending -> {
activity.result.sort = Anilist.sortBy[2] activity.result.sort = Anilist.sortBy[2]
activity.updateChips.invoke() activity.updateChips.invoke()
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_recent -> { R.id.sort_by_recent -> {
activity.result.sort = Anilist.sortBy[3] activity.result.sort = Anilist.sortBy[3]
activity.updateChips.invoke() activity.updateChips.invoke()
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_a_z -> { R.id.sort_by_a_z -> {
activity.result.sort = Anilist.sortBy[4] activity.result.sort = Anilist.sortBy[4]
activity.updateChips.invoke() activity.updateChips.invoke()
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_z_a -> { R.id.sort_by_z_a -> {
activity.result.sort = Anilist.sortBy[5] activity.result.sort = Anilist.sortBy[5]
activity.updateChips.invoke() activity.updateChips.invoke()
activity.search() activity.search()
updateFilterTextViewDrawable() updateFilterTextViewDrawable()
} }
R.id.sort_by_pure_pain -> { R.id.sort_by_pure_pain -> {
activity.result.sort = Anilist.sortBy[6] activity.result.sort = Anilist.sortBy[6]
activity.updateChips.invoke() activity.updateChips.invoke()
@@ -331,10 +325,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
} }
class SearchChipAdapter( class SearchChipAdapter(val activity: SearchActivity, private val searchAdapter: SearchAdapter) :
val activity: SearchActivity,
private val searchAdapter: SearchAdapter
) :
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() { RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
private var chips = activity.result.toChipList() private var chips = activity.result.toChipList()

View File

@@ -105,8 +105,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
setSortByFilterImage() setSortByFilterImage()
binding.resetSearchFilter.setOnClickListener { binding.resetSearchFilter.setOnClickListener {
val rotateAnimation = val rotateAnimation = ObjectAnimator.ofFloat(binding.resetSearchFilter, "rotation", 180f, 540f)
ObjectAnimator.ofFloat(binding.resetSearchFilter, "rotation", 180f, 540f)
rotateAnimation.duration = 500 rotateAnimation.duration = 500
rotateAnimation.interpolator = AccelerateDecelerateInterpolator() rotateAnimation.interpolator = AccelerateDecelerateInterpolator()
rotateAnimation.start() rotateAnimation.start()
@@ -114,8 +113,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
} }
binding.resetSearchFilter.setOnLongClickListener { binding.resetSearchFilter.setOnLongClickListener {
val rotateAnimation = val rotateAnimation = ObjectAnimator.ofFloat(binding.resetSearchFilter, "rotation", 180f, 540f)
ObjectAnimator.ofFloat(binding.resetSearchFilter, "rotation", 180f, 540f)
rotateAnimation.duration = 500 rotateAnimation.duration = 500
rotateAnimation.interpolator = AccelerateDecelerateInterpolator() rotateAnimation.interpolator = AccelerateDecelerateInterpolator()
rotateAnimation.start() rotateAnimation.start()
@@ -127,10 +125,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
activity.result.apply { activity.result.apply {
status = status = binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null } source = binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
source =
binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
format = binding.searchFormat.text.toString().ifBlank { null } format = binding.searchFormat.text.toString().ifBlank { null }
season = binding.searchSeason.text.toString().ifBlank { null } season = binding.searchSeason.text.toString().ifBlank { null }
startYear = binding.searchYear.text.toString().toIntOrNull() startYear = binding.searchYear.text.toString().toIntOrNull()
@@ -210,25 +206,21 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts) binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts)
startBounceZoomAnimation(binding.countryFilter) startBounceZoomAnimation(binding.countryFilter)
} }
R.id.country_china -> { R.id.country_china -> {
activity.result.countryOfOrigin = "CN" activity.result.countryOfOrigin = "CN"
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts) binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts)
startBounceZoomAnimation(binding.countryFilter) startBounceZoomAnimation(binding.countryFilter)
} }
R.id.country_south_korea -> { R.id.country_south_korea -> {
activity.result.countryOfOrigin = "KR" activity.result.countryOfOrigin = "KR"
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts) binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts)
startBounceZoomAnimation(binding.countryFilter) startBounceZoomAnimation(binding.countryFilter)
} }
R.id.country_japan -> { R.id.country_japan -> {
activity.result.countryOfOrigin = "JP" activity.result.countryOfOrigin = "JP"
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts) binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts)
startBounceZoomAnimation(binding.countryFilter) startBounceZoomAnimation(binding.countryFilter)
} }
R.id.country_taiwan -> { R.id.country_taiwan -> {
activity.result.countryOfOrigin = "TW" activity.result.countryOfOrigin = "TW"
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts) binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts)
@@ -265,8 +257,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
binding.searchFilterCancel.setOnClickListener { binding.searchFilterCancel.setOnClickListener {
dismiss() dismiss()
} }
val format = val format = if (activity.result.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
if (activity.result.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
binding.searchStatus.setText(activity.result.status?.replace("_", " ")) binding.searchStatus.setText(activity.result.status?.replace("_", " "))
binding.searchStatus.setAdapter( binding.searchStatus.setAdapter(
ArrayAdapter( ArrayAdapter(

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
class SubtitleDownloader { class SubtitleDownloader {

View File

@@ -17,11 +17,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.countDown
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.displayTimer
import ani.dantotsu.isOnline import ani.dantotsu.isOnline
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@@ -330,7 +330,6 @@ class AnimeWatchAdapter(
0 0
) )
} }
val chipText = "${names[limit * (position)]} - ${names[last - 1]}" val chipText = "${names[limit * (position)]} - ${names[last - 1]}"
chip.text = chipText chip.text = chipText
chip.setTextColor( chip.setTextColor(
@@ -413,12 +412,10 @@ class AnimeWatchAdapter(
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
binding.animeSourceContinueText.text = binding.animeSourceContinueText.text =
currActivity()!!.getString( currActivity()!!.getString(R.string.continue_episode, ep.number, if (ep.filler)
R.string.continue_episode, ep.number, if (ep.filler) currActivity()!!.getString(R.string.filler_tag)
currActivity()!!.getString(R.string.filler_tag) else
else "", cleanedTitle)
"", cleanedTitle
)
binding.animeSourceContinue.setOnClickListener { binding.animeSourceContinue.setOnClickListener {
fragment.onEpisodeClick(continueEp) fragment.onEpisodeClick(continueEp)
} }
@@ -444,14 +441,11 @@ class AnimeWatchAdapter(
if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) { if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) {
if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) {
val nextIndex = media.selected!!.sourceIndex + 1 val nextIndex = media.selected!!.sourceIndex + 1
binding.animeSource.setText( binding.animeSource.setText(binding.animeSource.adapter
binding.animeSource.adapter .getItem(nextIndex).toString(), false)
.getItem(nextIndex).toString(), false
)
fragment.onSourceChange(nextIndex).apply { fragment.onSourceChange(nextIndex).apply {
binding.animeSourceTitle.text = showUserText binding.animeSourceTitle.text = showUserText
showUserTextListener = showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
{ MainScope().launch { binding.animeSourceTitle.text = it } }
binding.animeSourceDubbed.isChecked = selectDub binding.animeSourceDubbed.isChecked = selectDub
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
setLanguageList(0, nextIndex) setLanguageList(0, nextIndex)
@@ -506,7 +500,8 @@ class AnimeWatchAdapter(
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : inner class ViewHolder(val binding: ItemAnimeWatchBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
init { init {
displayTimer(media, binding.animeSourceContainer) //Timer
countDown(media, binding.animeSourceContainer)
} }
} }
} }

View File

@@ -14,6 +14,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
@@ -24,6 +25,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -33,15 +35,14 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName import ani.dantotsu.download.DownloadsManager.Companion.findValidName
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.dp import ani.dantotsu.dp
import ani.dantotsu.isOnline
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.subscription.SubscriptionHelper import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
@@ -199,16 +200,10 @@ class AnimeWatchFragment : Fragment() {
ConcatAdapter(headerAdapter, episodeAdapter) ConcatAdapter(headerAdapter, episodeAdapter)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val offline = awaitAll(
!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) async { model.loadKitsuEpisodes(media) },
if (offline) { async { model.loadFillerEpisodes(media) }
media.selected!!.sourceIndex = model.watchSources!!.list.lastIndex )
} else {
awaitAll(
async { model.loadKitsuEpisodes(media) },
async { model.loadFillerEpisodes(media) }
)
}
model.loadEpisodes(media, media.selected!!.sourceIndex) model.loadEpisodes(media, media.selected!!.sourceIndex)
} }
loaded = true loaded = true
@@ -430,27 +425,17 @@ class AnimeWatchFragment : Fragment() {
} }
fun onAnimeEpisodeDownloadClick(i: String) { fun onAnimeEpisodeDownloadClick(i: String) {
activity?.let { activity?.let{
if (!hasDirAccess(it)) { if (!hasDirAccess(it)) {
(it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success -> (it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success ->
if (success) { if (success) {
model.onEpisodeClick( model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
media,
i,
requireActivity().supportFragmentManager,
isDownload = true
)
} else { } else {
snackString(getString(R.string.download_permission_required)) snackString("Permission is required to download")
} }
} }
} else { } else {
model.onEpisodeClick( model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
media,
i,
requireActivity().supportFragmentManager,
isDownload = true
)
} }
} }
} }
@@ -487,6 +472,10 @@ class AnimeWatchFragment : Fragment() {
) )
) { ) {
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i) val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
val id = PrefManager.getAnimeDownloadPreferences().getString(
taskName,
""
) ?: ""
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply() PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
episodeAdapter.deleteDownload(i) episodeAdapter.deleteDownload(i)
} }
@@ -553,7 +542,7 @@ class AnimeWatchFragment : Fragment() {
episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView)) episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView))
episodeAdapter.notifyItemRangeInserted(0, arr.size) episodeAdapter.notifyItemRangeInserted(0, arr.size)
for (download in downloadManager.animeDownloadedTypes) { for (download in downloadManager.animeDownloadedTypes) {
if (media.compareName(download.title)) { if (download.title == media.mainName().findValidName()) {
episodeAdapter.stopDownload(download.chapter) episodeAdapter.stopDownload(download.chapter)
} }
} }

View File

@@ -17,7 +17,9 @@ import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding
import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.databinding.ItemEpisodeListBinding
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getDirSize import ani.dantotsu.download.DownloadsManager.Companion.getDirSize
import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType

View File

@@ -48,8 +48,6 @@ import android.widget.ImageButton
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -62,16 +60,12 @@ import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE
import androidx.media3.common.C.TRACK_TYPE_AUDIO
import androidx.media3.common.C.TRACK_TYPE_TEXT
import androidx.media3.common.C.TRACK_TYPE_VIDEO import androidx.media3.common.C.TRACK_TYPE_VIDEO
import androidx.media3.common.Format
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.PlaybackParameters import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.TrackGroup
import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@@ -134,13 +128,13 @@ import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.parsers.VideoExtractor import ani.dantotsu.parsers.VideoExtractor
import ani.dantotsu.parsers.VideoType import ani.dantotsu.parsers.VideoType
import ani.dantotsu.px
import ani.dantotsu.settings.PlayerSettingsActivity import ani.dantotsu.settings.PlayerSettingsActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toPx
import ani.dantotsu.toast import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
@@ -159,12 +153,10 @@ import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Calendar import java.util.Calendar
import java.util.Locale
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.set
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -197,7 +189,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private lateinit var exoSettings: ImageButton private lateinit var exoSettings: ImageButton
private lateinit var exoSubtitle: ImageButton private lateinit var exoSubtitle: ImageButton
private lateinit var exoSubtitleView: SubtitleView private lateinit var exoSubtitleView: SubtitleView
private lateinit var exoAudioTrack: ImageButton
private lateinit var exoRotate: ImageButton private lateinit var exoRotate: ImageButton
private lateinit var exoSpeed: ImageButton private lateinit var exoSpeed: ImageButton
private lateinit var exoScreen: ImageButton private lateinit var exoScreen: ImageButton
@@ -220,16 +211,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var orientationListener: OrientationEventListener? = null private var orientationListener: OrientationEventListener? = null
private var downloadId: String? = null private var downloadId: String? = null
private var hasExtSubtitles = false
companion object { companion object {
var initialized = false var initialized = false
lateinit var media: Media lateinit var media: Media
private const val DEFAULT_MIN_BUFFER_MS = 600000
private const val DEFAULT_MAX_BUFFER_MS = 600000
private const val BUFFER_FOR_PLAYBACK_MS = 2500
private const val BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000
} }
private lateinit var episode: Episode private lateinit var episode: Episode
@@ -297,7 +282,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY = playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY =
(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8.toPx)).dp (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8f.px)).dp
exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> { exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginEnd = marginEnd =
if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0 if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
@@ -387,31 +372,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
6 -> ResourcesCompat.getFont(this, R.font.blocky) 6 -> ResourcesCompat.getFont(this, R.font.blocky)
else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold) else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
} }
val fontSize = PrefManager.getVal<Int>(PrefName.FontSize).toFloat() playerView.subtitleView?.setStyle(
CaptionStyleCompat(
playerView.subtitleView?.let { subtitles -> primaryColor,
subtitles.setApplyEmbeddedStyles(false) subBackground,
subtitles.setApplyEmbeddedFontSizes(false) subWindow,
outline,
subtitles.setStyle( secondaryColor,
CaptionStyleCompat( font
primaryColor,
subBackground,
subWindow,
outline,
secondaryColor,
font
)
) )
)
subtitles.alpha =
when (PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
true -> PrefManager.getVal(PrefName.SubAlpha)
false -> 0f
}
subtitles.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -444,7 +414,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoSource = playerView.findViewById(R.id.exo_source) exoSource = playerView.findViewById(R.id.exo_source)
exoSettings = playerView.findViewById(R.id.exo_settings) exoSettings = playerView.findViewById(R.id.exo_settings)
exoSubtitle = playerView.findViewById(R.id.exo_sub) exoSubtitle = playerView.findViewById(R.id.exo_sub)
exoAudioTrack = playerView.findViewById(R.id.exo_audio)
exoSubtitleView = playerView.findViewById(androidx.media3.ui.R.id.exo_subtitles) exoSubtitleView = playerView.findViewById(androidx.media3.ui.R.id.exo_subtitles)
exoRotate = playerView.findViewById(R.id.exo_rotate) exoRotate = playerView.findViewById(R.id.exo_rotate)
exoSpeed = playerView.findViewById(androidx.media3.ui.R.id.exo_playback_speed) exoSpeed = playerView.findViewById(androidx.media3.ui.R.id.exo_playback_speed)
@@ -512,8 +481,17 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
it.visibility = View.GONE it.visibility = View.GONE
} }
} }
setupSubFormatting(playerView) setupSubFormatting(playerView)
playerView.subtitleView?.alpha = when (PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
true -> PrefManager.getVal(PrefName.SubAlpha)
false -> 0f
}
val fontSize = PrefManager.getVal<Int>(PrefName.FontSize).toFloat()
playerView.subtitleView?.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
if (savedInstanceState != null) { if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(resumeWindow) currentWindow = savedInstanceState.getInt(resumeWindow)
playbackPosition = savedInstanceState.getLong(resumePosition) playbackPosition = savedInstanceState.getLong(resumePosition)
@@ -925,8 +903,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
isFastForwarding = true isFastForwarding = true
exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2) exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2)
fastForward.visibility = View.VISIBLE fastForward.visibility = View.VISIBLE
val speedText = "${exoPlayer.playbackParameters.speed}x" fastForward.text = "${exoPlayer.playbackParameters.speed}x"
fastForward.text = speedText
} }
fun stopFastForward() { fun stopFastForward() {
@@ -1219,7 +1196,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
putExtra("subtitle", subtitle) putExtra("subtitle", subtitle)
} }
exoPlayer.pause() exoPlayer.pause()
onChangeSettings.launch(intent) startActivity(intent)
} }
//Speed //Speed
@@ -1375,6 +1352,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return
extractor = ext extractor = ext
video = ext.videos.getOrNull(episode.selectedVideo) ?: return video = ext.videos.getOrNull(episode.selectedVideo) ?: return
subtitle = intent.getSerialized("subtitle") subtitle = intent.getSerialized("subtitle")
?: when (val subLang: String? = ?: when (val subLang: String? =
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) { PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) {
@@ -1391,23 +1369,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
//Subtitles //Subtitles
hasExtSubtitles = ext.subtitles.isNotEmpty() exoSubtitle.isVisible = ext.subtitles.isNotEmpty()
if (hasExtSubtitles) { exoSubtitle.setOnClickListener {
exoSubtitle.isVisible = hasExtSubtitles subClick()
exoSubtitle.setOnClickListener {
subClick()
}
} }
val sub: MutableList<MediaItem.SubtitleConfiguration> = var sub: MediaItem.SubtitleConfiguration? = null
emptyList<MediaItem.SubtitleConfiguration>().toMutableList() if (subtitle != null) {
ext.subtitles.forEach { subtitle ->
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url
//var localFile: String? = null //var localFile: String? = null
if (subtitle.type == SubtitleType.UNKNOWN) { if (subtitle?.type == SubtitleType.UNKNOWN) {
runBlocking { runBlocking {
val type = SubtitleDownloader.loadSubtitleType(subtitleUrl) val type = SubtitleDownloader.loadSubtitleType(subtitle!!.file.url)
val fileUri = Uri.parse(subtitleUrl) val fileUri = Uri.parse(subtitle!!.file.url)
sub += MediaItem.SubtitleConfiguration sub = MediaItem.SubtitleConfiguration
.Builder(fileUri) .Builder(fileUri)
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.setMimeType( .setMimeType(
@@ -1419,17 +1392,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
) )
.setId("69") .setId("69")
.setLanguage(subtitle.language)
.build() .build()
} }
println("sub: $sub") println("sub: $sub")
} else { } else {
val subUri = Uri.parse(subtitleUrl) val subUri = Uri.parse(subtitle!!.file.url)
sub += MediaItem.SubtitleConfiguration sub = MediaItem.SubtitleConfiguration
.Builder(subUri) .Builder(subUri)
.setSelectionFlags(C.SELECTION_FLAG_FORCED) .setSelectionFlags(C.SELECTION_FLAG_FORCED)
.setMimeType( .setMimeType(
when (subtitle.type) { when (subtitle?.type) {
SubtitleType.VTT -> MimeTypes.TEXT_VTT SubtitleType.VTT -> MimeTypes.TEXT_VTT
SubtitleType.ASS -> MimeTypes.TEXT_SSA SubtitleType.ASS -> MimeTypes.TEXT_SSA
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
@@ -1437,7 +1409,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
) )
.setId("69") .setId("69")
.setLanguage(subtitle.language)
.build() .build()
} }
} }
@@ -1479,6 +1450,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
val downloadedMediaItem = if (ext.server.offline) { val downloadedMediaItem = if (ext.server.offline) {
val key = ext.server.name
val titleName = ext.server.name.split("/").first() val titleName = ext.server.name.split("/").first()
val episodeName = ext.server.name.split("/").last() val episodeName = ext.server.name.split("/").last()
@@ -1507,20 +1479,24 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType) val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
Logger.log("url: ${video!!.file.url}") Logger.log("url: ${video!!.file.url}")
Logger.log("mimeType: $mimeType") Logger.log("mimeType: $mimeType")
builder.setSubtitleConfigurations(sub)
if (sub != null) {
val listofnotnullsubs = listOfNotNull(sub)
builder.setSubtitleConfigurations(listofnotnullsubs)
}
builder.build() builder.build()
} else { } else {
if (sub.isNotEmpty()) { val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon()
val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon() if (sub != null) {
val addLanguage = sub[0].buildUpon().setLanguage("en").build() val listofnotnullsubs = listOfNotNull(sub)
val addLanguage = listofnotnullsubs[0].buildUpon().setLanguage("en").build()
addedSubsDownloadedMediaItem.setSubtitleConfigurations(listOf(addLanguage)) addedSubsDownloadedMediaItem.setSubtitleConfigurations(listOf(addLanguage))
episode.selectedSubtitle = 0 episode.selectedSubtitle = 0
addedSubsDownloadedMediaItem.build()
} else {
downloadedMediaItem
} }
addedSubsDownloadedMediaItem.build()
} }
//Source //Source
exoSource.setOnClickListener { exoSource.setOnClickListener {
sourceClick() sourceClick()
@@ -1528,22 +1504,22 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//Quality Track //Quality Track
trackSelector = DefaultTrackSelector(this) trackSelector = DefaultTrackSelector(this)
val parameters = trackSelector.buildUponParameters() trackSelector.setParameters(
.setAllowVideoMixedMimeTypeAdaptiveness(true) trackSelector.buildUponParameters()
.setAllowVideoNonSeamlessAdaptiveness(true) .setAllowVideoMixedMimeTypeAdaptiveness(true)
.setSelectUndeterminedTextLanguage(true) .setAllowVideoNonSeamlessAdaptiveness(true)
.setAllowAudioMixedMimeTypeAdaptiveness(true) .setSelectUndeterminedTextLanguage(true)
.setAllowMultipleAdaptiveSelections(true) .setAllowAudioMixedMimeTypeAdaptiveness(true)
.setPreferredTextLanguage(subtitle?.language ?: Locale.getDefault().language) .setAllowMultipleAdaptiveSelections(true)
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE) .setPreferredTextLanguage(subtitle?.language ?: "en")
.setRendererDisabled(TRACK_TYPE_VIDEO, false) .setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
.setRendererDisabled(TRACK_TYPE_AUDIO, false) .setRendererDisabled(TRACK_TYPE_VIDEO, false)
.setRendererDisabled(TRACK_TYPE_TEXT, false) .setRendererDisabled(C.TRACK_TYPE_AUDIO, false)
.setMaxVideoSize(1, 1) .setRendererDisabled(C.TRACK_TYPE_TEXT, false)
// .setOverrideForType(TrackSelectionOverride(trackSelector, TRACK_TYPE_VIDEO)) .setMaxVideoSize(1, 1)
if (PrefManager.getVal(PrefName.SettingsPreferDub)) //.setOverrideForType(
parameters.setPreferredAudioLanguage(Locale.getDefault().language) // TrackSelectionOverride(trackSelector, 2))
trackSelector.setParameters(parameters) )
if (playbackPosition != 0L && !changingServer && !PrefManager.getVal<Boolean>(PrefName.AlwaysContinue)) { if (playbackPosition != 0L && !changingServer && !PrefManager.getVal<Boolean>(PrefName.AlwaysContinue)) {
val time = String.format( val time = String.format(
@@ -1573,18 +1549,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
}.show() }.show()
dialog.window?.setDimAmount(0.8f) dialog.window?.setDimAmount(0.8f)
} } else buildExoplayer()
if (!this::exoPlayer.isInitialized) buildExoplayer()
val isDisabled = (subtitle == null && hasExtSubtitles)
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(TRACK_TYPE_TEXT, isDisabled)
.build()
} }
private fun buildExoplayer() { private fun buildExoplayer() {
//Player //Player
val DEFAULT_MIN_BUFFER_MS = 600000
val DEFAULT_MAX_BUFFER_MS = 600000
val BUFFER_FOR_PLAYBACK_MS = 2500
val BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000
val loadControl = DefaultLoadControl.Builder() val loadControl = DefaultLoadControl.Builder()
.setBackBuffer(1000 * 60 * 2, true) .setBackBuffer(1000 * 60 * 2, true)
.setBufferDurationsMs( .setBufferDurationsMs(
@@ -1615,6 +1589,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
playerView.player = exoPlayer playerView.player = exoPlayer
try { try {
val rightNow = Calendar.getInstance() val rightNow = Calendar.getInstance()
mediaSession = MediaSession.Builder(this, exoPlayer) mediaSession = MediaSession.Builder(this, exoPlayer)
@@ -1627,11 +1602,32 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.addListener(this) exoPlayer.addListener(this)
exoPlayer.addAnalyticsListener(EventLogger()) exoPlayer.addAnalyticsListener(EventLogger())
isInitialized = true isInitialized = true
if (!hasExtSubtitles && !PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
onSetTrackGroupOverride(dummyTrack, TRACK_TYPE_TEXT)
}
} }
/*private fun selectSubtitleTrack() { saving this for later
// Get the current track groups
val trackGroups = exoPlayer.currentTrackGroups
// Prepare a track selector parameters builder
val parametersBuilder = DefaultTrackSelector.ParametersBuilder(this)
// Iterate through the track groups to find the subtitle tracks
for (i in 0 until trackGroups.length) {
val trackGroup = trackGroups[i]
for (j in 0 until trackGroup.length) {
val trackMetadata = trackGroup.getFormat(j)
// Check if the track is a subtitle track
if (MimeTypes.isText(trackMetadata.sampleMimeType)) {
parametersBuilder.setRendererDisabled(i, false) // Enable the renderer for this track group
parametersBuilder.setSelectionOverride(i, trackGroups, DefaultTrackSelector.SelectionOverride(j, 0)) // Override to select this track
break
}
}
}
// Apply the track selector parameters to select the subtitle
trackSelector.setParameters(parametersBuilder)
}*/
private fun releasePlayer() { private fun releasePlayer() {
isPlayerPlaying = exoPlayer.playWhenReady isPlayerPlaying = exoPlayer.playWhenReady
@@ -1806,8 +1802,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
if (isInitialized) { if (isInitialized) {
val playerCurrentTime = exoPlayer.currentPosition / 1000 val playerCurrentTime = exoPlayer.currentPosition / 1000
currentTimeStamp = model.timeStamps.value?.find { timestamp -> currentTimeStamp = model.timeStamps.value?.find { timestamp ->
timestamp.interval.startTime < playerCurrentTime timestamp.interval.startTime < playerCurrentTime && playerCurrentTime < (timestamp.interval.endTime - 1)
&& playerCurrentTime < (timestamp.interval.endTime - 1)
} }
val new = currentTimeStamp val new = currentTimeStamp
@@ -1830,7 +1825,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
if (new == null) { if (new == null) {
skipTimeButton.visibility = View.GONE skipTimeButton.visibility = View.GONE
exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0 exoSkip.visibility = View.VISIBLE
disappeared = false disappeared = false
functionstarted = false functionstarted = false
cancelTimer() cancelTimer()
@@ -1839,7 +1834,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
override fun onFinish() { override fun onFinish() {
skipTimeButton.visibility = View.GONE skipTimeButton.visibility = View.GONE
exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0 exoSkip.visibility = View.VISIBLE
disappeared = true disappeared = true
functionstarted = false functionstarted = false
cancelTimer() cancelTimer()
@@ -1860,7 +1855,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.seekTo((new.interval.endTime * 1000).toLong()) exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
} }
} }
} }
if (PrefManager.getVal(PrefName.AutoSkipOPED) && (new.skipType == "op" || new.skipType == "ed") if (PrefManager.getVal(PrefName.AutoSkipOPED) && (new.skipType == "op" || new.skipType == "ed")
&& !skippedTimeStamps.contains(new) && !skippedTimeStamps.contains(new)
@@ -1868,13 +1862,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.seekTo((new.interval.endTime * 1000).toLong()) exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
skippedTimeStamps.add(new) skippedTimeStamps.add(new)
} }
if (PrefManager.getVal(PrefName.AutoSkipRecap) && new.skipType == "recap" && !skippedTimeStamps.contains(
new
)
) {
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
skippedTimeStamps.add(new)
}
new.skipType.getType() new.skipType.getType()
} else { } else {
disappeared = false disappeared = false
@@ -1889,95 +1876,43 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}, 500) }, 500)
} }
fun onSetTrackGroupOverride(trackGroup: Tracks.Group, type: @C.TrackType Int, index: Int = 0) {
val isDisabled = trackGroup.getTrackFormat(0).language == "none"
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(TRACK_TYPE_TEXT, isDisabled)
.setOverrideForType(
TrackSelectionOverride(trackGroup.mediaTrackGroup, index)
)
.build()
if (type == TRACK_TYPE_TEXT) setupSubFormatting(playerView)
playerView.subtitleView?.alpha = when (isDisabled) {
false -> PrefManager.getVal(PrefName.SubAlpha)
true -> 0f
}
}
private val dummyTrack = Tracks.Group(
TrackGroup("Dummy Track", Format.Builder().apply { setLanguage("none") }.build()),
true,
intArrayOf(1),
booleanArrayOf(false)
)
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
val audioTracks: ArrayList<Tracks.Group> = arrayListOf()
val subTracks: ArrayList<Tracks.Group> = arrayListOf(dummyTrack)
tracks.groups.forEach { tracks.groups.forEach {
println( println("Track__: $it")
"Track__: $it\nTrack__: ${it.length}\nTrack__: ${it.isSelected}\n" + println("Track__: ${it.length}")
"Track__: ${it.type}\nTrack__: ${it.mediaTrackGroup.id}" println("Track__: ${it.isSelected}")
) println("Track__: ${it.type}")
when (it.type) { println("Track__: ${it.mediaTrackGroup.id}")
TRACK_TYPE_AUDIO -> { if (it.type == 3 && it.mediaTrackGroup.id == "1:") {
if (it.isSupported(true)) audioTracks.add(it) playerView.player?.trackSelectionParameters =
} playerView.player?.trackSelectionParameters?.buildUpon()
?.setOverrideForType(
TRACK_TYPE_TEXT -> { TrackSelectionOverride(it.mediaTrackGroup, it.length - 1)
if (!hasExtSubtitles) { )
if (it.isSupported(true)) subTracks.add(it) ?.build()!!
return@forEach } else if (it.type == 3) {
} playerView.player?.trackSelectionParameters =
} playerView.player?.trackSelectionParameters?.buildUpon()
?.addOverride(
TrackSelectionOverride(it.mediaTrackGroup, listOf())
)
?.build()!!
} }
} }
exoAudioTrack.isVisible = audioTracks.size > 1 println("Track: ${tracks.groups.size}")
exoAudioTrack.setOnClickListener {
TrackGroupDialogFragment(this, audioTracks, TRACK_TYPE_AUDIO)
.show(supportFragmentManager, "dialog")
}
if (!hasExtSubtitles) {
exoSubtitle.isVisible = subTracks.size > 1
exoSubtitle.setOnClickListener {
TrackGroupDialogFragment(this, subTracks, TRACK_TYPE_TEXT)
.show(supportFragmentManager, "dialog")
}
}
}
private val onChangeSettings = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { _: ActivityResult ->
if (!hasExtSubtitles) {
exoPlayer.currentTracks.groups.forEach { trackGroup ->
when (trackGroup.type) {
TRACK_TYPE_TEXT -> {
if (PrefManager.getVal(PrefName.Subtitles)) {
onSetTrackGroupOverride(trackGroup, TRACK_TYPE_TEXT)
} else {
onSetTrackGroupOverride(dummyTrack, TRACK_TYPE_TEXT)
}
}
else -> {}
}
}
}
if (isInitialized) exoPlayer.play()
} }
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
when (error.errorCode) { when (error.errorCode) {
PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> { -> {
toast("Source Exception : ${error.message}") toast("Source Exception : ${error.message}")
isPlayerPlaying = true isPlayerPlaying = true
sourceClick() sourceClick()
} }
else -> { else
-> {
toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}") toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}")
Injekt.get<CrashlyticsInterface>().logException(error) Injekt.get<CrashlyticsInterface>().logException(error)
} }
@@ -1987,6 +1922,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var isBuffering = true private var isBuffering = true
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == ExoPlayer.STATE_READY) { if (playbackState == ExoPlayer.STATE_READY) {
exoPlayer.play() exoPlayer.play()
if (episodeLength == 0f) { if (episodeLength == 0f) {
episodeLength = exoPlayer.duration.toFloat() episodeLength = exoPlayer.duration.toFloat()
@@ -2041,7 +1977,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
@SuppressLint("UnsafeIntentLaunch")
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
finishAndRemoveTask() finishAndRemoveTask()
@@ -2071,11 +2006,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
// Cast // Cast
private fun cast() { private fun cast() {
val videoURL = video?.file?.url ?: return val videoURL = video?.file?.url ?: return
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url
val shareVideo = Intent(Intent.ACTION_VIEW) val shareVideo = Intent(Intent.ACTION_VIEW)
shareVideo.setDataAndType(Uri.parse(videoURL), "video/*") shareVideo.setDataAndType(Uri.parse(videoURL), "video/*")
shareVideo.setPackage("com.instantbits.cast.webvideo") shareVideo.setPackage("com.instantbits.cast.webvideo")
if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl) if (subtitle != null) shareVideo.putExtra("subtitle", subtitle!!.file.url)
shareVideo.putExtra( shareVideo.putExtra(
"title", "title",
media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex] media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex]
@@ -2271,4 +2205,4 @@ class CustomCastButton : MediaRouteButton {
true true
} }
} }
} }

View File

@@ -24,7 +24,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
@@ -51,12 +50,9 @@ import ani.dantotsu.snackString
import ani.dantotsu.tryWith import ani.dantotsu.tryWith
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import tachiyomi.core.util.lang.launchIO
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DecimalFormat import java.text.DecimalFormat
@@ -234,12 +230,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
} }
private val externalPlayerResult = registerForActivityResult( private val externalPlayerResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
) { result: ActivityResult ->
Logger.log(result.data.toString()) Logger.log(result.data.toString())
} }
private fun exportMagnetIntent(episode: Episode, video: Video): Intent { private fun exportMagnetIntent(episode: Episode, video: Video) : Intent {
val amnis = "com.amnis" val amnis = "com.amnis"
return Intent(Intent.ACTION_VIEW).apply { return Intent(Intent.ACTION_VIEW).apply {
component = ComponentName(amnis, "$amnis.gui.player.PlayerActivity") component = ComponentName(amnis, "$amnis.gui.player.PlayerActivity")
@@ -257,70 +252,32 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
fun startExoplayer(media: Media) { fun startExoplayer(media: Media) {
prevEpisode = null prevEpisode = null
dismiss()
episode?.let { ep -> episode?.let { ep ->
val video = ep.extractors?.find { val video = ep.extractors?.find {
it.server.name == ep.selectedExtractor it.server.name == ep.selectedExtractor
}?.videos?.getOrNull(ep.selectedVideo) }?.videos?.getOrNull(ep.selectedVideo)
video?.file?.url?.let { url -> video?.file?.url?.let { url ->
if (url.startsWith("magnet:") || url.endsWith(".torrent")) { if (url.startsWith("magnet:")) {
val torrentExtension = Injekt.get<TorrentAddonManager>() try {
if (torrentExtension.isAvailable()) { externalPlayerResult.launch(exportMagnetIntent(ep, video))
val activity = currActivity() ?: requireActivity() } catch (e: ActivityNotFoundException) {
launchIO { val amnis = "com.amnis"
val extension = torrentExtension.extension!!.extension
torrentExtension.torrentHash?.let {
extension.removeTorrent(it)
}
val index = if (url.contains("index=")) {
url.substringAfter("index=").toIntOrNull() ?: 0
} else 0
Logger.log("Sending: ${url}, ${video.quality}, $index")
val currentTorrent = extension.addTorrent(
url, video.quality.toString(), "", "", false
)
torrentExtension.torrentHash = currentTorrent.hash
video.file.url = extension.getLink(currentTorrent, index)
Logger.log("Received: ${video.file.url}")
if (launch == true) {
Intent(activity, ExoplayerView::class.java).apply {
ExoplayerView.media = media
ExoplayerView.initialized = true
startActivity(this)
}
} else {
model.setEpisode(
media.anime!!.episodes!![media.anime.selectedEpisode!!]!!,
"startExo no launch"
)
}
dismiss()
}
} else {
try { try {
externalPlayerResult.launch(exportMagnetIntent(ep, video)) startActivity(Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=$amnis"))
)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
val amnis = "com.amnis" startActivity(Intent(
try { Intent.ACTION_VIEW,
startActivity( Uri.parse("https://play.google.com/store/apps/details?id=$amnis")
Intent( ))
Intent.ACTION_VIEW,
Uri.parse("market://details?id=$amnis")
)
)
dismiss()
} catch (e: ActivityNotFoundException) {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=$amnis")
)
)
}
} }
} }
return return
@@ -328,7 +285,6 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
} }
} }
dismiss()
if (launch!! || model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)) { if (launch!! || model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)) {
stopAddingToList() stopAddingToList()
val intent = Intent(activity, ExoplayerView::class.java) val intent = Intent(activity, ExoplayerView::class.java)
@@ -447,11 +403,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
SubtitleDownloader.downloadSubtitle( SubtitleDownloader.downloadSubtitle(
requireContext(), requireContext(),
subtitleToDownload!!.file.url, subtitleToDownload!!.file.url,
DownloadedType( DownloadedType(media!!.mainName(), media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.number, MediaType.ANIME)
media!!.mainName(),
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.number,
MediaType.ANIME
)
) )
} }
} }
@@ -481,37 +433,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!! val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
val selectedVideo = val selectedVideo =
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
val subtitles = extractor.subtitles
val subtitleNames = subtitles.map { it.language } val subtitleNames = subtitles.map { it.language }
var subtitleToDownload: Subtitle? = null var subtitleToDownload: Subtitle? = null
val activity = currActivity() ?: requireActivity() val activity = currActivity()?:requireActivity()
selectedVideo?.file?.url?.let { url ->
if (url.startsWith("magnet:") || url.endsWith(".torrent")) {
val torrentExtension = Injekt.get<TorrentAddonManager>()
if (!torrentExtension.isAvailable()) {
snackString("Torrent Extension not available")
return@setSafeOnClickListener
}
runBlocking {
withContext(Dispatchers.IO) {
val extension = torrentExtension.extension!!.extension
torrentExtension.torrentHash?.let {
extension.removeTorrent(it)
}
val index = if (url.contains("index=")) {
url.substringAfter("index=").toIntOrNull() ?: 0
} else 0
Logger.log("Sending: ${url}, ${selectedVideo.quality}, $index")
val currentTorrent = extension.addTorrent(
url, selectedVideo.quality.toString(), "", "", false
)
torrentExtension.torrentHash = currentTorrent.hash
selectedVideo.file.url =
extension.getLink(currentTorrent, index)
Logger.log("Received: ${selectedVideo.file.url}")
}
}
}
}
if (subtitles.isNotEmpty()) { if (subtitles.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle("Download Subtitle") .setTitle("Download Subtitle")
@@ -585,13 +510,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
if (video.format == VideoType.CONTAINER) { if (video.format == VideoType.CONTAINER) {
binding.urlSize.isVisible = video.size != null binding.urlSize.isVisible = video.size != null
// if video size is null or 0, show "Unknown Size" else show the size in MB // if video size is null or 0, show "Unknown Size" else show the size in MB
val sizeText = getString( val sizeText = getString(R.string.mb_size, "${if (video.extraNote != null) " : " else ""}${
R.string.mb_size, "${if (video.extraNote != null) " : " else ""}${ if (video.size == 0.0) getString(R.string.size_unknown) else DecimalFormat("#.##").format(video.size ?: 0)
if (video.size == 0.0) getString(R.string.size_unknown) else DecimalFormat("#.##").format( }")
video.size ?: 0
)
}"
)
binding.urlSize.text = sizeText binding.urlSize.text = sizeText
} }
binding.urlNote.visibility = View.VISIBLE binding.urlNote.visibility = View.VISIBLE

View File

@@ -67,11 +67,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
binding.subtitleTitle.setText(R.string.none) binding.subtitleTitle.setText(R.string.none)
model.getMedia().observe(viewLifecycleOwner) { media -> model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id val mediaID: Int = media.id
val selSubs = PrefManager.getNullableCustomVal( val selSubs = PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
"subLang_${mediaID}",
null,
String::class.java
)
if (episode.selectedSubtitle != null && selSubs != "None") { if (episode.selectedSubtitle != null && selSubs != "None") {
binding.root.setCardBackgroundColor(TRANSPARENT) binding.root.setCardBackgroundColor(TRANSPARENT)
} }
@@ -111,11 +107,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
model.getMedia().observe(viewLifecycleOwner) { media -> model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id val mediaID: Int = media.id
val selSubs: String? = val selSubs: String? =
PrefManager.getNullableCustomVal( PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
"subLang_${mediaID}",
null,
String::class.java
)
if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) { if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) {
binding.root.setCardBackgroundColor(TRANSPARENT) binding.root.setCardBackgroundColor(TRANSPARENT)
} }

View File

@@ -1,119 +0,0 @@
package ani.dantotsu.media.anime
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.media3.common.C.TRACK_TYPE_AUDIO
import androidx.media3.common.C.TrackType
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetSubtitlesBinding
import ani.dantotsu.databinding.ItemSubtitleTextBinding
import java.util.Locale
@OptIn(UnstableApi::class)
class TrackGroupDialogFragment(
instance: ExoplayerView, trackGroups: ArrayList<Tracks.Group>, type: @TrackType Int
) : BottomSheetDialogFragment() {
private var _binding: BottomSheetSubtitlesBinding? = null
private val binding get() = _binding!!
private var instance: ExoplayerView
private var trackGroups: ArrayList<Tracks.Group>
private var type: @TrackType Int
init {
this.instance = instance
this.trackGroups = trackGroups
this.type = type
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = BottomSheetSubtitlesBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (type == TRACK_TYPE_AUDIO) binding.selectionTitle.text = getString(R.string.audio_tracks)
binding.subtitlesRecycler.layoutManager = LinearLayoutManager(requireContext())
binding.subtitlesRecycler.adapter = TrackGroupAdapter()
}
inner class TrackGroupAdapter : RecyclerView.Adapter<TrackGroupAdapter.StreamViewHolder>() {
inner class StreamViewHolder(val binding: ItemSubtitleTextBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
StreamViewHolder(
ItemSubtitleTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
@OptIn(UnstableApi::class)
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
val binding = holder.binding
trackGroups[position].let { trackGroup ->
when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) {
null -> {
binding.subtitleTitle.text =
getString(R.string.unknown_track, "Track $position")
}
"none" -> {
binding.subtitleTitle.text = getString(R.string.disabled_track)
}
else -> {
val locale = if (language.contains("-")) {
val parts = language.split("-")
try {
Locale(parts[0], parts[1])
} catch (ignored: Exception) {
null
}
} else {
try {
Locale(language)
} catch (ignored: Exception) {
null
}
}
binding.subtitleTitle.text = locale?.let {
"[${it.language}] ${it.displayName}"
} ?: getString(R.string.unknown_track, language)
}
}
if (trackGroup.isSelected) {
val selected = "${binding.subtitleTitle.text}"
binding.subtitleTitle.text = selected
}
binding.root.setOnClickListener {
dismiss()
instance.onSetTrackGroupOverride(trackGroup, type)
}
}
}
override fun getItemCount(): Int = trackGroups.size
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}

View File

@@ -60,8 +60,7 @@ class CommentItem(
override fun bind(viewBinding: ItemCommentsBinding, position: Int) { override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
binding = viewBinding binding = viewBinding
setAnimation(binding.root.context, binding.root) setAnimation(binding.root.context, binding.root)
viewBinding.commentRepliesList.layoutManager = viewBinding.commentRepliesList.layoutManager = LinearLayoutManager(commentsFragment.activity)
LinearLayoutManager(commentsFragment.activity)
viewBinding.commentRepliesList.adapter = adapter viewBinding.commentRepliesList.adapter = adapter
val isUserComment = CommentsAPI.userId == comment.userId val isUserComment = CommentsAPI.userId == comment.userId
val levelColor = getAvatarColor(comment.totalVotes, backgroundColor) val levelColor = getAvatarColor(comment.totalVotes, backgroundColor)
@@ -113,20 +112,16 @@ class CommentItem(
viewBinding.commentUserName.setOnClickListener { viewBinding.commentUserName.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
commentsFragment.activity, commentsFragment.activity, Intent(commentsFragment.activity, ProfileActivity::class.java)
Intent(commentsFragment.activity, ProfileActivity::class.java)
.putExtra("userId", comment.userId.toInt()) .putExtra("userId", comment.userId.toInt())
.putExtra("userLVL", "[${levelColor.second}]"), .putExtra("userLVL","[${levelColor.second}]"), null
null
) )
} }
viewBinding.commentUserAvatar.setOnClickListener { viewBinding.commentUserAvatar.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
commentsFragment.activity, commentsFragment.activity, Intent(commentsFragment.activity, ProfileActivity::class.java)
Intent(commentsFragment.activity, ProfileActivity::class.java)
.putExtra("userId", comment.userId.toInt()) .putExtra("userId", comment.userId.toInt())
.putExtra("userLVL", "[${levelColor.second}]"), .putExtra("userLVL","[${levelColor.second}]"), null
null
) )
} }
viewBinding.commentText.setOnLongClickListener { viewBinding.commentText.setOnLongClickListener {
@@ -148,10 +143,8 @@ class CommentItem(
viewBinding.commentInfo.setOnClickListener { viewBinding.commentInfo.setOnClickListener {
val popup = PopupMenu(commentsFragment.requireContext(), viewBinding.commentInfo) val popup = PopupMenu(commentsFragment.requireContext(), viewBinding.commentInfo)
popup.menuInflater.inflate(R.menu.profile_details_menu, popup.menu) popup.menuInflater.inflate(R.menu.profile_details_menu, popup.menu)
popup.menu.findItem(R.id.commentDelete)?.isVisible = popup.menu.findItem(R.id.commentDelete)?.isVisible = isUserComment || CommentsAPI.isAdmin || CommentsAPI.isMod
isUserComment || CommentsAPI.isAdmin || CommentsAPI.isMod popup.menu.findItem(R.id.commentBanUser)?.isVisible = (CommentsAPI.isAdmin || CommentsAPI.isMod) && !isUserComment
popup.menu.findItem(R.id.commentBanUser)?.isVisible =
(CommentsAPI.isAdmin || CommentsAPI.isMod) && !isUserComment
popup.menu.findItem(R.id.commentReport)?.isVisible = !isUserComment popup.menu.findItem(R.id.commentReport)?.isVisible = !isUserComment
popup.setOnMenuItemClickListener { item -> popup.setOnMenuItemClickListener { item ->
when (item.itemId) { when (item.itemId) {
@@ -280,16 +273,12 @@ class CommentItem(
} }
fun replying(isReplying: Boolean) { fun replying(isReplying: Boolean) {
binding.commentReply.text = binding.commentReply.text = if (isReplying) commentsFragment.activity.getString(R.string.cancel) else "Reply"
if (isReplying) commentsFragment.activity.getString(R.string.cancel) else "Reply"
this.isReplying = isReplying this.isReplying = isReplying
} }
fun editing(isEditing: Boolean) { fun editing(isEditing: Boolean) {
binding.commentEdit.text = binding.commentEdit.text = if (isEditing) commentsFragment.activity.getString(R.string.cancel) else commentsFragment.activity.getString(R.string.edit)
if (isEditing) commentsFragment.activity.getString(R.string.cancel) else commentsFragment.activity.getString(
R.string.edit
)
this.isEditing = isEditing this.isEditing = isEditing
} }
@@ -297,7 +286,7 @@ class CommentItem(
subCommentIds.add(id) subCommentIds.add(id)
} }
private fun removeSubCommentIds() { private fun removeSubCommentIds(){
subCommentIds.forEach { id -> subCommentIds.forEach { id ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList() val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList()
@@ -317,13 +306,11 @@ class CommentItem(
viewBinding.commentUpVote.alpha = 1f viewBinding.commentUpVote.alpha = 1f
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24) viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
} }
-1 -> { -1 -> {
viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24) viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_active_24) viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_active_24)
viewBinding.commentDownVote.alpha = 1f viewBinding.commentDownVote.alpha = 1f
} }
else -> { else -> {
viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24) viewBinding.commentUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24) viewBinding.commentDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
@@ -368,8 +355,7 @@ class CommentItem(
private fun getAvatarColor(voteCount: Int, backgroundColor: Int): Pair<Int, Int> { private fun getAvatarColor(voteCount: Int, backgroundColor: Int): Pair<Int, Int> {
val level = if (voteCount < 0) 0 else sqrt(abs(voteCount.toDouble()) / 0.8).toInt() val level = if (voteCount < 0) 0 else sqrt(abs(voteCount.toDouble()) / 0.8).toInt()
val colorString = val colorString = if (level > usernameColors.size - 1) usernameColors[usernameColors.size - 1] else usernameColors[level]
if (level > usernameColors.size - 1) usernameColors[usernameColors.size - 1] else usernameColors[level]
var color = Color.parseColor(colorString) var color = Color.parseColor(colorString)
val ratio = getContrastRatio(color, backgroundColor) val ratio = getContrastRatio(color, backgroundColor)
if (ratio < 4.5) { if (ratio < 4.5) {
@@ -387,17 +373,16 @@ class CommentItem(
* @param callback the callback to call when the user clicks yes * @param callback the callback to call when the user clicks yes
*/ */
private fun dialogBuilder(title: String, message: String, callback: () -> Unit) { private fun dialogBuilder(title: String, message: String, callback: () -> Unit) {
val alertDialog = val alertDialog = android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup)
android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup) .setTitle(title)
.setTitle(title) .setMessage(message)
.setMessage(message) .setPositiveButton("Yes") { dialog, _ ->
.setPositiveButton("Yes") { dialog, _ -> callback()
callback() dialog.dismiss()
dialog.dismiss() }
} .setNegativeButton("No") { dialog, _ ->
.setNegativeButton("No") { dialog, _ -> dialog.dismiss()
dialog.dismiss() }
}
val dialog = alertDialog.show() val dialog = alertDialog.show()
dialog?.window?.setDimAmount(0.8f) dialog?.window?.setDimAmount(0.8f)
} }

View File

@@ -75,10 +75,7 @@ class CommentsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as MediaDetailsActivity activity = requireActivity() as MediaDetailsActivity
binding.commentsListContainer.setBaseline( binding.commentsListContainer.setBaseline(activity.navBar, activity.binding.commentInputLayout)
activity.navBar,
activity.binding.commentInputLayout
)
//get the media id from the intent //get the media id from the intent
val mediaId = arguments?.getInt("mediaId") ?: -1 val mediaId = arguments?.getInt("mediaId") ?: -1
@@ -304,7 +301,7 @@ class CommentsFragment : Fragment() {
activity.binding.commentLabel.setOnClickListener { activity.binding.commentLabel.setOnClickListener {
//alert dialog to enter a number, with a cancel and ok button //alert dialog to enter a number, with a cancel and ok button
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) val alertDialog = android.app.AlertDialog.Builder(activity, R.style.MyPopup)
.setTitle("Enter a chapter/episode number tag") .setTitle("Enter a chapter/episode number tag")
.setView(R.layout.dialog_edittext) .setView(R.layout.dialog_edittext)
.setPositiveButton("OK") { dialog, _ -> .setPositiveButton("OK") { dialog, _ ->
@@ -580,7 +577,7 @@ class CommentsFragment : Fragment() {
* Called when the user tries to comment for the first time * Called when the user tries to comment for the first time
*/ */
private fun showCommentRulesDialog() { private fun showCommentRulesDialog() {
val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) val alertDialog = android.app.AlertDialog.Builder(activity, R.style.MyPopup)
.setTitle("Commenting Rules") .setTitle("Commenting Rules")
.setMessage( .setMessage(
"I WILL BAN YOU WITHOUT HESITATION\n" + "I WILL BAN YOU WITHOUT HESITATION\n" +

View File

@@ -343,7 +343,6 @@ class MangaChapterAdapter(
fun updateType(t: Int) { fun updateType(t: Int) {
type = t type = t
} }
private fun formatDate(timestamp: Long?): String { private fun formatDate(timestamp: Long?): String {
timestamp ?: return "" // Return empty string if timestamp is null timestamp ?: return "" // Return empty string if timestamp is null
@@ -367,7 +366,6 @@ class MangaChapterAdapter(
else -> "Just now" else -> "Just now"
} }
} }
1L -> "1 day ago" 1L -> "1 day ago"
in 2..6 -> "$daysDifference days ago" in 2..6 -> "$daysDifference days ago"
else -> SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(targetDate) else -> SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(targetDate)

View File

@@ -229,7 +229,7 @@ class MangaReadAdapter(
refresh = true refresh = true
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
.putExtra("url", url) .putExtra("url", url)
startActivity(fragment.requireContext(), intent, null) ContextCompat.startActivity(fragment.requireContext(), intent, null)
} }
} }
} }
@@ -258,10 +258,8 @@ class MangaReadAdapter(
dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1 dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1
dialogBinding.scanlatorNo.text = "${options.count()}" dialogBinding.scanlatorNo.text = "${options.count()}"
dialogBinding.animeScanlatorTop.setOnClickListener { dialogBinding.animeScanlatorTop.setOnClickListener {
val dialogView2 = val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) val checkboxContainer = dialogView2.findViewById<LinearLayout>(R.id.checkboxContainer)
val checkboxContainer =
dialogView2.findViewById<LinearLayout>(R.id.checkboxContainer)
val tickAllButton = dialogView2.findViewById<ImageButton>(R.id.toggleButton) val tickAllButton = dialogView2.findViewById<ImageButton>(R.id.toggleButton)
// Function to get the right image resource for the toggle button // Function to get the right image resource for the toggle button
@@ -443,11 +441,7 @@ class MangaReadAdapter(
if (media.manga?.chapters != null) { if (media.manga?.chapters != null) {
val chapters = media.manga.chapters!!.keys.toTypedArray() val chapters = media.manga.chapters!!.keys.toTypedArray()
val anilistEp = (media.userProgress ?: 0).plus(1) val anilistEp = (media.userProgress ?: 0).plus(1)
val appEp = PrefManager.getNullableCustomVal( val appEp = PrefManager.getNullableCustomVal("${media.id}_current_chp", null, String::class.java)
"${media.id}_current_chp",
null,
String::class.java
)
?.toIntOrNull() ?: 1 ?.toIntOrNull() ?: 1
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
val filteredChapters = chapters.filter { chapterKey -> val filteredChapters = chapters.filter { chapterKey ->
@@ -476,11 +470,7 @@ class MangaReadAdapter(
val ep = media.manga.chapters!![continueEp]!! val ep = media.manga.chapters!![continueEp]!!
binding.itemEpisodeImage.loadImage(media.banner ?: media.cover) binding.itemEpisodeImage.loadImage(media.banner ?: media.cover)
binding.animeSourceContinueText.text = binding.animeSourceContinueText.text =
currActivity()!!.getString( currActivity()!!.getString(R.string.continue_chapter, ep.number, if (!ep.title.isNullOrEmpty()) ep.title else "")
R.string.continue_chapter,
ep.number,
if (!ep.title.isNullOrEmpty()) ep.title else ""
)
binding.animeSourceContinue.setOnClickListener { binding.animeSourceContinue.setOnClickListener {
fragment.onMangaChapterClick(continueEp) fragment.onMangaChapterClick(continueEp)
} }
@@ -501,14 +491,11 @@ class MangaReadAdapter(
if (!sourceFound && PrefManager.getVal(PrefName.SearchSources)) { if (!sourceFound && PrefManager.getVal(PrefName.SearchSources)) {
if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) {
val nextIndex = media.selected!!.sourceIndex + 1 val nextIndex = media.selected!!.sourceIndex + 1
binding.animeSource.setText( binding.animeSource.setText(binding.animeSource.adapter
binding.animeSource.adapter .getItem(nextIndex).toString(), false)
.getItem(nextIndex).toString(), false
)
fragment.onSourceChange(nextIndex).apply { fragment.onSourceChange(nextIndex).apply {
binding.animeSourceTitle.text = showUserText binding.animeSourceTitle.text = showUserText
showUserTextListener = showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
{ MainScope().launch { binding.animeSourceTitle.text = it } }
setLanguageList(0, nextIndex) setLanguageList(0, nextIndex)
} }
subscribeButton(false) subscribeButton(false)

View File

@@ -16,6 +16,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -34,16 +35,15 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName import ani.dantotsu.download.DownloadsManager.Companion.findValidName
import ani.dantotsu.download.manga.MangaDownloaderService import ani.dantotsu.download.manga.MangaDownloaderService
import ani.dantotsu.download.manga.MangaServiceDataSingleton import ani.dantotsu.download.manga.MangaServiceDataSingleton
import ani.dantotsu.dp import ani.dantotsu.dp
import ani.dantotsu.isOnline
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.subscription.SubscriptionHelper import ani.dantotsu.notifications.subscription.SubscriptionHelper
@@ -194,7 +194,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
) )
for (download in downloadManager.mangaDownloadedTypes) { for (download in downloadManager.mangaDownloadedTypes) {
if (media.compareName(download.title)) { if (download.title == media.mainName().findValidName()) {
chapterAdapter.stopDownload(download.chapter) chapterAdapter.stopDownload(download.chapter)
} }
} }
@@ -203,10 +203,6 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
ConcatAdapter(headerAdapter, chapterAdapter) ConcatAdapter(headerAdapter, chapterAdapter)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val offline =
!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
if (offline) media.selected!!.sourceIndex =
model.mangaReadSources!!.list.lastIndex
model.loadMangaChapters(media, media.selected!!.sourceIndex) model.loadMangaChapters(media, media.selected!!.sourceIndex)
} }
loaded = true loaded = true
@@ -495,7 +491,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
if (success) { if (success) {
continueDownload() continueDownload()
} else { } else {
snackString(getString(R.string.download_permission_required)) snackString("Permission is required to download")
} }
} }
} else { } else {

View File

@@ -42,8 +42,7 @@ abstract class BaseImageAdapter(
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
images = if (settings.layout == CurrentReaderSettings.Layouts.PAGED images = if (settings.layout == CurrentReaderSettings.Layouts.PAGED
&& settings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP && settings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP) {
) {
chapterImages.reversed() chapterImages.reversed()
} else { } else {
chapterImages chapterImages

View File

@@ -56,8 +56,8 @@ import ani.dantotsu.isOnline
import ani.dantotsu.logError import ani.dantotsu.logError
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaSingleton import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
@@ -129,12 +129,10 @@ class MangaReaderActivity : AppCompatActivity() {
var sliding = false var sliding = false
var isAnimating = false var isAnimating = false
private val directionRLBT private val directionRLBT get() = defaultSettings.direction == RIGHT_TO_LEFT
get() = defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP
|| defaultSettings.direction == BOTTOM_TO_TOP private val directionPagedBT get() = defaultSettings.layout == CurrentReaderSettings.Layouts.PAGED
private val directionPagedBT && defaultSettings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP
get() = defaultSettings.layout == CurrentReaderSettings.Layouts.PAGED
&& defaultSettings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) {
@@ -231,7 +229,7 @@ class MangaReaderActivity : AppCompatActivity() {
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 } binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 }
?: 1)) ?: 1))
else else
if (defaultSettings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP) { if (defaultSettings.direction == CurrentReaderSettings.Directions.BOTTOM_TO_TOP ) {
binding.mangaReaderPager.currentItem = binding.mangaReaderPager.currentItem =
(maxChapterPage.toInt() - value.toInt()) / (dualPage { 2 } ?: 1) (maxChapterPage.toInt() - value.toInt()) / (dualPage { 2 } ?: 1)
} else { } else {
@@ -347,11 +345,7 @@ class MangaReaderActivity : AppCompatActivity() {
if (currentChapterIndex > 0) change(currentChapterIndex - 1) if (currentChapterIndex > 0) change(currentChapterIndex - 1)
else snackString(getString(R.string.first_chapter)) else snackString(getString(R.string.first_chapter))
} else { } else {
if (chaptersArr.size > currentChapterIndex + 1) progress { if (chaptersArr.size > currentChapterIndex + 1) progress { change(currentChapterIndex + 1) }
change(
currentChapterIndex + 1
)
}
else snackString(getString(R.string.next_chapter_not_found)) else snackString(getString(R.string.next_chapter_not_found))
} }
} }
@@ -361,11 +355,7 @@ class MangaReaderActivity : AppCompatActivity() {
} }
binding.mangaReaderPreviousChapter.setOnClickListener { binding.mangaReaderPreviousChapter.setOnClickListener {
if (directionRLBT) { if (directionRLBT) {
if (chaptersArr.size > currentChapterIndex + 1) progress { if (chaptersArr.size > currentChapterIndex + 1) progress { change(currentChapterIndex + 1) }
change(
currentChapterIndex + 1
)
}
else snackString(getString(R.string.next_chapter_not_found)) else snackString(getString(R.string.next_chapter_not_found))
} else { } else {
if (currentChapterIndex > 0) change(currentChapterIndex - 1) if (currentChapterIndex > 0) change(currentChapterIndex - 1)
@@ -382,15 +372,11 @@ class MangaReaderActivity : AppCompatActivity() {
currentChapterIndex = chaptersArr.indexOf(chap.number) currentChapterIndex = chaptersArr.indexOf(chap.number)
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex) binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
if (directionRLBT) { if (directionRLBT) {
binding.mangaReaderNextChap.text = binding.mangaReaderNextChap.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: "" binding.mangaReaderPrevChap.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
binding.mangaReaderPrevChap.text =
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
} else { } else {
binding.mangaReaderNextChap.text = binding.mangaReaderNextChap.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: "" binding.mangaReaderPrevChap.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
binding.mangaReaderPrevChap.text =
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
} }
applySettings() applySettings()
val context = this val context = this
@@ -403,12 +389,10 @@ class MangaReaderActivity : AppCompatActivity() {
"nothing" -> mutableListOf( "nothing" -> mutableListOf(
RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""), RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""),
) )
"dantotsu" -> mutableListOf( "dantotsu" -> mutableListOf(
RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""), RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""),
RPC.Link("Read on Dantotsu", getString(R.string.dantotsu)) RPC.Link("Read on Dantotsu", getString(R.string.dantotsu))
) )
"anilist" -> { "anilist" -> {
val userId = PrefManager.getVal<String>(PrefName.AnilistUserId) val userId = PrefManager.getVal<String>(PrefName.AnilistUserId)
val anilistLink = "https://anilist.co/user/$userId/" val anilistLink = "https://anilist.co/user/$userId/"
@@ -417,7 +401,6 @@ class MangaReaderActivity : AppCompatActivity() {
RPC.Link("View My AniList", anilistLink) RPC.Link("View My AniList", anilistLink)
) )
} }
else -> mutableListOf() else -> mutableListOf()
} }
val presence = RPC.createPresence( val presence = RPC.createPresence(
@@ -428,12 +411,8 @@ class MangaReaderActivity : AppCompatActivity() {
details = chap.title?.takeIf { it.isNotEmpty() } details = chap.title?.takeIf { it.isNotEmpty() }
?: getString(R.string.chapter_num, chap.number), ?: getString(R.string.chapter_num, chap.number),
state = "${chap.number}/${media.manga?.totalChapters ?: "??"}", state = "${chap.number}/${media.manga?.totalChapters ?: "??"}",
largeImage = media.cover?.let { cover -> largeImage = media.cover?.let { cover -> RPC.Link(media.userPreferredName, cover) },
RPC.Link( smallImage = RPC.Link("Dantotsu", Discord.small_Image),
media.userPreferredName,
cover
)
},
buttons = buttons buttons = buttons
) )
) )
@@ -940,12 +919,7 @@ class MangaReaderActivity : AppCompatActivity() {
isAnimating = true isAnimating = true
ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 1f, 0f) ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 1f, 0f)
.setDuration(controllerDuration).start() .setDuration(controllerDuration).start()
ObjectAnimator.ofFloat( ObjectAnimator.ofFloat(binding.mangaReaderBottomLayout, "translationY", 0f, 128f)
binding.mangaReaderBottomLayout,
"translationY",
0f,
128f
)
.apply { interpolator = overshoot;duration = controllerDuration;start() } .apply { interpolator = overshoot;duration = controllerDuration;start() }
ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", 0f, -128f) ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", 0f, -128f)
.apply { interpolator = overshoot;duration = controllerDuration;start() } .apply { interpolator = overshoot;duration = controllerDuration;start() }

View File

@@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Parcelable import android.os.Parcelable
@@ -12,13 +13,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
@@ -26,22 +27,19 @@ import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.novel.NovelDownloaderService import ani.dantotsu.download.novel.NovelDownloaderService
import ani.dantotsu.download.novel.NovelServiceDataSingleton import ani.dantotsu.download.novel.NovelServiceDataSingleton
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import ani.dantotsu.util.StoragePermissions
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
class NovelReadFragment : Fragment(), class NovelReadFragment : Fragment(),
DownloadTriggerCallback, DownloadTriggerCallback,
@@ -64,40 +62,26 @@ class NovelReadFragment : Fragment(),
override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) { override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) {
Logger.log("novel link: ${novelDownloadPackage.link}") Logger.log("novel link: ${novelDownloadPackage.link}")
activity?.let { val downloadTask = NovelDownloaderService.DownloadTask(
fun continueDownload() { title = media.mainName(),
val downloadTask = NovelDownloaderService.DownloadTask( chapter = novelDownloadPackage.novelName,
title = media.mainName(), downloadLink = novelDownloadPackage.link,
chapter = novelDownloadPackage.novelName, originalLink = novelDownloadPackage.originalLink,
downloadLink = novelDownloadPackage.link, sourceMedia = media,
originalLink = novelDownloadPackage.originalLink, coverUrl = novelDownloadPackage.coverUrl,
sourceMedia = media, retries = 2,
coverUrl = novelDownloadPackage.coverUrl, )
retries = 2, NovelServiceDataSingleton.downloadQueue.offer(downloadTask)
) CoroutineScope(Dispatchers.IO).launch {
NovelServiceDataSingleton.downloadQueue.offer(downloadTask)
CoroutineScope(Dispatchers.IO).launch {
if (!NovelServiceDataSingleton.isServiceRunning) { if (!NovelServiceDataSingleton.isServiceRunning) {
val intent = Intent(context, NovelDownloaderService::class.java) val intent = Intent(context, NovelDownloaderService::class.java)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
ContextCompat.startForegroundService(requireContext(), intent) ContextCompat.startForegroundService(requireContext(), intent)
}
NovelServiceDataSingleton.isServiceRunning = true
}
} }
NovelServiceDataSingleton.isServiceRunning = true
} }
if (!StoragePermissions.hasDirAccess(it)) {
(it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success ->
if (success) {
continueDownload()
} else {
snackString(getString(R.string.download_permission_required))
}
}
} else {
continueDownload()
}
} }
} }
@@ -113,12 +97,7 @@ class NovelReadFragment : Fragment(),
) { ) {
try { try {
val directory = val directory =
DownloadsManager.getSubDirectory( DownloadsManager.getSubDirectory(context?:currContext()!!, MediaType.NOVEL, false, novel.name)
context ?: currContext()!!,
MediaType.NOVEL,
false,
novel.name
)
val file = directory?.findFile(novel.name) val file = directory?.findFile(novel.name)
if (file?.exists() == false) return false if (file?.exists() == false) return false
val fileUri = file?.uri ?: return false val fileUri = file?.uri ?: return false

View File

@@ -181,7 +181,7 @@ class NovelResponseAdapter(
if (position != -1) { if (position != -1) {
list[position].extra?.remove("0") list[position].extra?.remove("0")
list[position].extra?.set("0", "Downloading: $progress%") list[position].extra?.set("0", "Downloading: $progress%")
Logger.log("updateDownloadProgress: $progress, position: $position") Logger.log( "updateDownloadProgress: $progress, position: $position")
notifyItemChanged(position) notifyItemChanged(position)
} }
} }

View File

@@ -292,11 +292,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
applySettings() applySettings()
} }
val cfi = PrefManager.getNullableCustomVal( val cfi = PrefManager.getNullableCustomVal("${sanitizedBookId}_progress", null, String::class.java)
"${sanitizedBookId}_progress",
null,
String::class.java
)
cfi?.let { binding.bookReader.goto(it) } cfi?.let { binding.bookReader.goto(it) }
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE

View File

@@ -70,10 +70,8 @@ class ListActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
val anime = intent.getBooleanExtra("anime", true) val anime = intent.getBooleanExtra("anime", true)
binding.listTitle.text = getString( binding.listTitle.text = getString(R.string.user_list, intent.getStringExtra("username"),
R.string.user_list, intent.getStringExtra("username"), if (anime) getString(R.string.anime) else getString(R.string.manga))
if (anime) getString(R.string.anime) else getString(R.string.manga)
)
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) { override fun onTabSelected(tab: TabLayout.Tab?) {
this@ListActivity.selectedTabIdx = tab?.position ?: 0 this@ListActivity.selectedTabIdx = tab?.position ?: 0
@@ -160,8 +158,7 @@ class ListActivity : AppCompatActivity() {
} }
binding.filter.setOnClickListener { binding.filter.setOnClickListener {
val genres = val genres = PrefManager.getVal<Set<String>>(PrefName.GenresList).toMutableSet().sorted()
PrefManager.getVal<Set<String>>(PrefName.GenresList).toMutableSet().sorted()
val popup = PopupMenu(this, it) val popup = PopupMenu(this, it)
popup.menu.add("All") popup.menu.add("All")
genres.forEach { genre -> genres.forEach { genre ->

View File

@@ -5,9 +5,9 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.notifications.anilist.AnilistNotificationReceiver import ani.dantotsu.notifications.anilist.AnilistNotificationReceiver
import ani.dantotsu.notifications.comment.CommentNotificationReceiver import ani.dantotsu.notifications.comment.CommentNotificationReceiver
import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.notifications.subscription.SubscriptionNotificationReceiver import ani.dantotsu.notifications.subscription.SubscriptionNotificationReceiver
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName

View File

@@ -5,9 +5,9 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger

View File

@@ -21,16 +21,11 @@ interface TaskScheduler {
for (taskType in TaskType.entries) { for (taskType in TaskType.entries) {
val interval = when (taskType) { val interval = when (taskType) {
TaskType.COMMENT_NOTIFICATION -> CommentNotificationWorker.checkIntervals[PrefManager.getVal( TaskType.COMMENT_NOTIFICATION -> CommentNotificationWorker.checkIntervals[PrefManager.getVal(
PrefName.CommentNotificationInterval PrefName.CommentNotificationInterval)]
)]
TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal( TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal(
PrefName.AnilistNotificationInterval PrefName.AnilistNotificationInterval)]
)]
TaskType.SUBSCRIPTION_NOTIFICATION -> SubscriptionNotificationWorker.checkIntervals[PrefManager.getVal( TaskType.SUBSCRIPTION_NOTIFICATION -> SubscriptionNotificationWorker.checkIntervals[PrefManager.getVal(
PrefName.SubscriptionNotificationInterval PrefName.SubscriptionNotificationInterval)]
)]
} }
scheduleRepeatingTask(taskType, interval) scheduleRepeatingTask(taskType, interval)
} }
@@ -67,7 +62,6 @@ interface TaskScheduler {
} }
} }
} }
enum class TaskType { enum class TaskType {
COMMENT_NOTIFICATION, COMMENT_NOTIFICATION,
ANILIST_NOTIFICATION, ANILIST_NOTIFICATION,

View File

@@ -66,7 +66,6 @@ class MediaNameFetch {
val type = object : TypeToken<MediaResponse>() {}.type val type = object : TypeToken<MediaResponse>() {}.type
return gson.fromJson(response, type) return gson.fromJson(response, type)
} }
data class ReturnedData(val title: String, val coverImage: String, val color: String) data class ReturnedData(val title: String, val coverImage: String, val color: String)
data class MediaResponse(val data: Map<String, MediaItem>) data class MediaResponse(val data: Map<String, MediaItem>)

View File

@@ -32,7 +32,7 @@ class SubscriptionHelper {
return data return data
} }
private fun saveSelected(mediaId: Int, data: Selected) { private fun saveSelected( mediaId: Int, data: Selected) {
PrefManager.setCustomVal("${mediaId}-select", data) PrefManager.setCustomVal("${mediaId}-select", data)
} }

View File

@@ -5,10 +5,7 @@ import android.graphics.Rect
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
class AndroidBug5497Workaround private constructor( class AndroidBug5497Workaround private constructor(activity: Activity, private val callback: (Boolean) -> Unit) {
activity: Activity,
private val callback: (Boolean) -> Unit
) {
private val mChildOfContent: View private val mChildOfContent: View
private var usableHeightPrevious = 0 private var usableHeightPrevious = 0
private val frameLayoutParams: FrameLayout.LayoutParams private val frameLayoutParams: FrameLayout.LayoutParams

View File

@@ -1,5 +1,6 @@
package ani.dantotsu.others package ani.dantotsu.others
import android.app.DownloadManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -7,6 +8,7 @@ import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
@@ -17,6 +19,9 @@ import ani.dantotsu.parsers.Book
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.toast import ani.dantotsu.toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File import java.io.File
object Download { object Download {

View File

@@ -2,10 +2,10 @@ package ani.dantotsu.others
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.client import ani.dantotsu.client
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.anime.Episode import ani.dantotsu.media.anime.Episode
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import com.google.gson.Gson import com.google.gson.Gson
import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.NiceResponse
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View File

@@ -56,10 +56,9 @@ class OutlineTextView : AppCompatTextView {
setStrokeWidth(strokeWidth) setStrokeWidth(strokeWidth)
} }
private val Float.toPx private val Float.toPx get() = TypedValue.applyDimension(
get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics )
)
private fun setStrokeWidth(width: Float) { private fun setStrokeWidth(width: Float) {
strokeWidth = width.toPx strokeWidth = width.toPx

View File

@@ -12,8 +12,8 @@ import ani.dantotsu.R
class Xpandable @JvmOverloads constructor( class Xpandable @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) { ) : LinearLayout(context, attrs) {
private var expanded: Boolean = false var expanded: Boolean = false
private var listeners: ArrayList<OnChangeListener> = arrayListOf() private var listener: OnChangeListener? = null
init { init {
context.withStyledAttributes(attrs, R.styleable.Xpandable) { context.withStyledAttributes(attrs, R.styleable.Xpandable) {
@@ -50,9 +50,7 @@ class Xpandable @JvmOverloads constructor(
} }
} }
postDelayed({ postDelayed({
listeners.forEach { listener?.onRetract()
it.onRetract()
}
}, 300) }, 300)
} }
@@ -66,19 +64,13 @@ class Xpandable @JvmOverloads constructor(
} }
} }
postDelayed({ postDelayed({
listeners.forEach { listener?.onExpand()
it.onExpand()
}
}, 300) }, 300)
} }
@Suppress("unused") @Suppress("unused")
fun addOnChangeListener(listener: OnChangeListener) { fun setOnChangeListener(listener: OnChangeListener) {
listeners.add(listener) this.listener = listener
}
fun removeListener(listener: OnChangeListener) {
listeners.remove(listener)
} }
interface OnChangeListener { interface OnChangeListener {

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