diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 5445097a..56617b45 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -3,7 +3,10 @@ name: Build APK and Notify Discord
on:
push:
branches:
- - Dunno
+ - dev
+ paths-ignore:
+ - '**/README.md'
+
jobs:
build:
@@ -50,7 +53,7 @@ jobs:
shell: bash
run: |
contentbody=$( jq -Rsa . <<< "${{ github.event.head_commit.message }}" )
- curl -F "payload_json={\"content\":\" everyone **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }}
+ curl -F "payload_json={\"content\":\" Debug-Build **${{ env.VERSION }}**\n\n${contentbody:1:-1}\"}" -F "dantotsu_debug=@app/build/outputs/apk/debug/app-debug.apk" ${{ secrets.DISCORD_WEBHOOK }}
- name: Delete Old Pre-Releases
id: delete-pre-releases
diff --git a/app/build.gradle b/app/build.gradle
index 455f7b47..e776eab1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,7 +21,7 @@ android {
minSdk 23
targetSdk 34
versionCode ((System.currentTimeMillis() / 60000).toInteger())
- versionName "2.0.0-beta00-i"
+ versionName "2.0.0-beta00-iv2"
signingConfig signingConfigs.debug
}
@@ -90,6 +90,9 @@ dependencies {
implementation "androidx.media3:media3-exoplayer-dash:$exo_version"
implementation "androidx.media3:media3-datasource-okhttp:$exo_version"
implementation "androidx.media3:media3-session:$exo_version"
+ //media3 casting
+ implementation "androidx.media3:media3-cast:$exo_version"
+ implementation "androidx.mediarouter:mediarouter:1.6.0"
// UI
implementation 'com.google.android.material:material:1.10.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cd7cec87..c0a14ae7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,8 @@
android:required="false" />
+
@@ -45,7 +47,7 @@
-
+ android:name=".download.video.ExoplayerDownloadService"
+ android:exported="false"
+ android:foregroundServiceType="dataSync">
+
@@ -295,10 +298,16 @@
android:name=".download.novel.NovelDownloaderService"
android:exported="false"
android:foregroundServiceType="dataSync" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/Functions.kt b/app/src/main/java/ani/dantotsu/Functions.kt
index 6a789743..a1035d05 100644
--- a/app/src/main/java/ani/dantotsu/Functions.kt
+++ b/app/src/main/java/ani/dantotsu/Functions.kt
@@ -53,6 +53,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.internal.ViewUtils
import com.google.android.material.snackbar.Snackbar
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.*
import nl.joery.animatedbottombar.AnimatedBottomBar
import java.io.*
@@ -673,7 +674,7 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
@SuppressLint("SetTextI18n")
fun countDown(media: Media, view: ViewGroup) {
- if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null && (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 7.toLong()) {
+ if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null && (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 28.toLong()) {
val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
view.addView(v.root, 0)
v.mediaCountdownText.text =
@@ -783,35 +784,40 @@ fun toast(string: String?) {
}
fun snackString(s: String?, activity: Activity? = null, clipboard: String? = null) {
- if (s != null) {
- (activity ?: currActivity())?.apply {
- runOnUiThread {
- val snackBar = Snackbar.make(
- window.decorView.findViewById(android.R.id.content),
- s,
- Snackbar.LENGTH_SHORT
- )
- snackBar.view.apply {
- updateLayoutParams {
- gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
- width = WRAP_CONTENT
- }
- translationY = -(navBarHeight.dp + 32f)
- translationZ = 32f
- updatePadding(16f.px, right = 16f.px)
- setOnClickListener {
- snackBar.dismiss()
- }
- setOnLongClickListener {
- copyToClipboard(clipboard ?: s, false)
- toast(getString(R.string.copied_to_clipboard))
- true
+ try { //I have no idea why this sometimes crashes for some people...
+ if (s != null) {
+ (activity ?: currActivity())?.apply {
+ runOnUiThread {
+ val snackBar = Snackbar.make(
+ window.decorView.findViewById(android.R.id.content),
+ s,
+ Snackbar.LENGTH_SHORT
+ )
+ snackBar.view.apply {
+ updateLayoutParams {
+ gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
+ width = WRAP_CONTENT
+ }
+ translationY = -(navBarHeight.dp + 32f)
+ translationZ = 32f
+ updatePadding(16f.px, right = 16f.px)
+ setOnClickListener {
+ snackBar.dismiss()
+ }
+ setOnLongClickListener {
+ copyToClipboard(clipboard ?: s, false)
+ toast(getString(R.string.copied_to_clipboard))
+ true
+ }
}
+ snackBar.show()
}
- snackBar.show()
}
+ logger(s)
}
- logger(s)
+ } catch (e: Exception) {
+ logger(e.stackTraceToString())
+ FirebaseCrashlytics.getInstance().recordException(e)
}
}
diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt
index b27ee17d..3b8c4bba 100644
--- a/app/src/main/java/ani/dantotsu/MainActivity.kt
+++ b/app/src/main/java/ani/dantotsu/MainActivity.kt
@@ -11,12 +11,14 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnticipateInterpolator
import android.widget.TextView
import androidx.activity.addCallback
import androidx.activity.viewModels
+import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
@@ -26,11 +28,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.offline.Download
import androidx.viewpager2.adapter.FragmentStateAdapter
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
import ani.dantotsu.databinding.ActivityMainBinding
import ani.dantotsu.databinding.SplashScreenBinding
+import ani.dantotsu.download.video.Helper
import ani.dantotsu.home.AnimeFragment
import ani.dantotsu.home.HomeFragment
import ani.dantotsu.home.LoginFragment
@@ -45,6 +50,7 @@ import ani.dantotsu.themes.ThemeManager
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -60,7 +66,7 @@ class MainActivity : AppCompatActivity() {
private var uiSettings = UserInterfaceSettings()
- override fun onCreate(savedInstanceState: Bundle?) {
+ @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) {
ThemeManager(this).applyTheme()
LangSet.setLocale(this)
super.onCreate(savedInstanceState)
@@ -73,16 +79,10 @@ class MainActivity : AppCompatActivity() {
val backgroundDrawable = _bottomBar.background as GradientDrawable
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
- val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
+ val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xF0000000.toInt()
backgroundDrawable.setColor(semiTransparentColor)
_bottomBar.background = backgroundDrawable
}
- val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
- .getBoolean("colorOverflow", false)
- if (!colorOverflow) {
- _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
-
- }
var doubleBackToExitPressedOnce = false
@@ -242,6 +242,25 @@ class MainActivity : AppCompatActivity() {
}
}
+ GlobalScope.launch(Dispatchers.IO) {
+ val index = Helper.downloadManager(this@MainActivity).downloadIndex
+ val downloadCursor = index.getDownloads()
+ while (downloadCursor.moveToNext()) {
+ val download = downloadCursor.download
+ Log.e("Downloader", download.request.uri.toString())
+ Log.e("Downloader", download.request.id.toString())
+ Log.e("Downloader", download.request.mimeType.toString())
+ Log.e("Downloader", download.request.data.size.toString())
+ Log.e("Downloader", download.bytesDownloaded.toString())
+ Log.e("Downloader", download.state.toString())
+ Log.e("Downloader", download.failureReason.toString())
+
+ if (download.state == Download.STATE_FAILED) { //simple cleanup
+ Helper.downloadManager(this@MainActivity).removeDownload(download.request.id)
+ }
+ }
+ }
+
}
diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt
index 38b9fc4c..10154a36 100644
--- a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt
+++ b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt
@@ -3,7 +3,11 @@ package ani.dantotsu.aniyomi.anime.custom
import android.app.Application
import android.content.Context
+import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.database.StandaloneDatabaseProvider
+import androidx.media3.datasource.cache.SimpleCache
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.parsers.novel.NovelExtensionManager
@@ -27,7 +31,7 @@ import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class AppModule(val app: Application) : InjektModule {
- override fun InjektRegistrar.registerInjectables() {
+ @OptIn(UnstableApi::class) override fun InjektRegistrar.registerInjectables() {
addSingleton(app)
addSingletonFactory { DownloadsManager(app) }
@@ -51,6 +55,8 @@ class AppModule(val app: Application) : InjektModule {
}
}
+ addSingletonFactory { StandaloneDatabaseProvider(app) }
+
addSingletonFactory { MangaCache() }
ContextCompat.getMainExecutor(app).execute {
diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
index a0c1c270..2302a3d6 100644
--- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
+++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
@@ -1,6 +1,7 @@
package ani.dantotsu.connections.anilist
import android.app.Activity
+import android.content.Context
import ani.dantotsu.R
import ani.dantotsu.checkGenreTime
import ani.dantotsu.checkId
@@ -410,8 +411,9 @@ class AnilistQueries {
sorted["Favourites"]?.sortWith(compareBy { it.userFavOrder })
sorted["All"] = all
-
- val sort = sortOrder ?: options?.rowOrder
+ val listsort = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
+ ?.getString("sort_order", "score")
+ val sort = listsort ?: sortOrder ?: options?.rowOrder
for (i in sorted.keys) {
when (sort) {
"score" -> sorted[i]?.sortWith { b, a ->
diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt
index b3536ea4..d6247ec5 100644
--- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt
+++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt
@@ -15,43 +15,43 @@ class DownloadsManager(private val context: Context) {
private val gson = Gson()
private val downloadsList = loadDownloads().toMutableList()
- val mangaDownloads: List
- get() = downloadsList.filter { it.type == Download.Type.MANGA }
- val animeDownloads: List
- get() = downloadsList.filter { it.type == Download.Type.ANIME }
- val novelDownloads: List
- get() = downloadsList.filter { it.type == Download.Type.NOVEL }
+ val mangaDownloadedTypes: List
+ get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA }
+ val animeDownloadedTypes: List
+ get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME }
+ val novelDownloadedTypes: List
+ get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL }
private fun saveDownloads() {
val jsonString = gson.toJson(downloadsList)
prefs.edit().putString("downloads_key", jsonString).apply()
}
- private fun loadDownloads(): List {
+ private fun loadDownloads(): List {
val jsonString = prefs.getString("downloads_key", null)
return if (jsonString != null) {
- val type = object : TypeToken>() {}.type
+ val type = object : TypeToken>() {}.type
gson.fromJson(jsonString, type)
} else {
emptyList()
}
}
- fun addDownload(download: Download) {
- downloadsList.add(download)
+ fun addDownload(downloadedType: DownloadedType) {
+ downloadsList.add(downloadedType)
saveDownloads()
}
- fun removeDownload(download: Download) {
- downloadsList.remove(download)
- removeDirectory(download)
+ fun removeDownload(downloadedType: DownloadedType) {
+ downloadsList.remove(downloadedType)
+ removeDirectory(downloadedType)
saveDownloads()
}
- fun removeMedia(title: String, type: Download.Type) {
- val subDirectory = if (type == Download.Type.MANGA) {
+ fun removeMedia(title: String, type: DownloadedType.Type) {
+ val subDirectory = if (type == DownloadedType.Type.MANGA) {
"Manga"
- } else if (type == Download.Type.ANIME) {
+ } else if (type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
@@ -76,16 +76,16 @@ class DownloadsManager(private val context: Context) {
}
private fun cleanDownloads() {
- cleanDownload(Download.Type.MANGA)
- cleanDownload(Download.Type.ANIME)
- cleanDownload(Download.Type.NOVEL)
+ cleanDownload(DownloadedType.Type.MANGA)
+ cleanDownload(DownloadedType.Type.ANIME)
+ cleanDownload(DownloadedType.Type.NOVEL)
}
- private fun cleanDownload(type: Download.Type) {
+ private fun cleanDownload(type: DownloadedType.Type) {
// remove all folders that are not in the downloads list
- val subDirectory = if (type == Download.Type.MANGA) {
+ val subDirectory = if (type == DownloadedType.Type.MANGA) {
"Manga"
- } else if (type == Download.Type.ANIME) {
+ } else if (type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
@@ -94,18 +94,18 @@ class DownloadsManager(private val context: Context) {
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$subDirectory"
)
- val downloadsSubList = if (type == Download.Type.MANGA) {
- mangaDownloads
- } else if (type == Download.Type.ANIME) {
- animeDownloads
+ val downloadsSubLists = if (type == DownloadedType.Type.MANGA) {
+ mangaDownloadedTypes
+ } else if (type == DownloadedType.Type.ANIME) {
+ animeDownloadedTypes
} else {
- novelDownloads
+ novelDownloadedTypes
}
if (directory.exists()) {
val files = directory.listFiles()
if (files != null) {
for (file in files) {
- if (!downloadsSubList.any { it.title == file.name }) {
+ if (!downloadsSubLists.any { it.title == file.name }) {
val deleted = file.deleteRecursively()
}
}
@@ -122,7 +122,7 @@ class DownloadsManager(private val context: Context) {
}
}
- fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List) //for debugging
+ fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List) //for debugging
{
val jsonString = gson.toJson(downloadsList)
val file = File(
@@ -138,25 +138,33 @@ class DownloadsManager(private val context: Context) {
file.writeText(jsonString)
}
- fun queryDownload(download: Download): Boolean {
- return downloadsList.contains(download)
+ fun queryDownload(downloadedType: DownloadedType): Boolean {
+ return downloadsList.contains(downloadedType)
}
- private fun removeDirectory(download: Download) {
- val directory = if (download.type == Download.Type.MANGA) {
+ fun queryDownload(title: String, chapter: String, type: DownloadedType.Type? = null): Boolean {
+ return if (type == null) {
+ downloadsList.any { it.title == title && it.chapter == chapter }
+ } else {
+ downloadsList.any { it.title == title && it.chapter == chapter && it.type == type }
+ }
+ }
+
+ private fun removeDirectory(downloadedType: DownloadedType) {
+ val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Manga/${download.title}/${download.chapter}"
+ "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
)
- } else if (download.type == Download.Type.ANIME) {
+ } else if (downloadedType.type == DownloadedType.Type.ANIME) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Anime/${download.title}/${download.chapter}"
+ "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
)
} else {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Novel/${download.title}/${download.chapter}"
+ "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
)
}
@@ -173,26 +181,26 @@ class DownloadsManager(private val context: Context) {
}
}
- fun exportDownloads(download: Download) { //copies to the downloads folder available to the user
- val directory = if (download.type == Download.Type.MANGA) {
+ fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user
+ val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Manga/${download.title}/${download.chapter}"
+ "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
)
- } else if (download.type == Download.Type.ANIME) {
+ } else if (downloadedType.type == DownloadedType.Type.ANIME) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Anime/${download.title}/${download.chapter}"
+ "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
)
} else {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/Novel/${download.title}/${download.chapter}"
+ "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
)
}
val destination = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/${download.title}/${download.chapter}"
+ "Dantotsu/${downloadedType.title}/${downloadedType.chapter}"
)
if (directory.exists()) {
val copied = directory.copyRecursively(destination, true)
@@ -206,10 +214,10 @@ class DownloadsManager(private val context: Context) {
}
}
- fun purgeDownloads(type: Download.Type) {
- val directory = if (type == Download.Type.MANGA) {
+ fun purgeDownloads(type: DownloadedType.Type) {
+ val directory = if (type == DownloadedType.Type.MANGA) {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
- } else if (type == Download.Type.ANIME) {
+ } else if (type == DownloadedType.Type.ANIME) {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
} else {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
@@ -237,7 +245,7 @@ class DownloadsManager(private val context: Context) {
}
-data class Download(val title: String, val chapter: String, val type: Type) : Serializable {
+data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable {
enum class Type {
MANGA,
ANIME,
diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt
new file mode 100644
index 00000000..84a960cc
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt
@@ -0,0 +1,455 @@
+package ani.dantotsu.download.anime
+
+import android.Manifest
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.os.Build
+import android.os.Environment
+import android.os.IBinder
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.offline.Download
+import androidx.media3.exoplayer.offline.DownloadManager
+import androidx.media3.exoplayer.offline.DownloadService
+import ani.dantotsu.FileUrl
+import ani.dantotsu.R
+import ani.dantotsu.currActivity
+import ani.dantotsu.download.DownloadedType
+import ani.dantotsu.download.DownloadsManager
+import ani.dantotsu.download.video.Helper
+import ani.dantotsu.download.video.ExoplayerDownloadService
+import ani.dantotsu.logger
+import ani.dantotsu.media.Media
+import ani.dantotsu.media.anime.AnimeWatchFragment
+import ani.dantotsu.parsers.Subtitle
+import ani.dantotsu.parsers.Video
+import ani.dantotsu.snackString
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.google.gson.GsonBuilder
+import com.google.gson.InstanceCreator
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SAnimeImpl
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SChapterImpl
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.io.File
+import java.io.FileOutputStream
+import java.net.HttpURLConnection
+import java.net.URL
+import java.util.Queue
+import java.util.concurrent.ConcurrentLinkedQueue
+
+class AnimeDownloaderService : Service() {
+
+ private lateinit var notificationManager: NotificationManagerCompat
+ private lateinit var builder: NotificationCompat.Builder
+ private val downloadsManager: DownloadsManager = Injekt.get()
+
+ private val downloadJobs = mutableMapOf()
+ private val mutex = Mutex()
+ private var isCurrentlyProcessing = false
+
+ override fun onBind(intent: Intent?): IBinder? {
+ // This is only required for bound services.
+ return null
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ notificationManager = NotificationManagerCompat.from(this)
+ builder = NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
+ setContentTitle("Anime Download Progress")
+ setSmallIcon(R.drawable.ic_round_download_24)
+ priority = NotificationCompat.PRIORITY_DEFAULT
+ setOnlyAlertOnce(true)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ startForeground(
+ NOTIFICATION_ID,
+ builder.build(),
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ )
+ } else {
+ startForeground(NOTIFICATION_ID, builder.build())
+ }
+ ContextCompat.registerReceiver(
+ this,
+ cancelReceiver,
+ IntentFilter(ACTION_CANCEL_DOWNLOAD),
+ ContextCompat.RECEIVER_EXPORTED
+ )
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ AnimeServiceDataSingleton.downloadQueue.clear()
+ downloadJobs.clear()
+ AnimeServiceDataSingleton.isServiceRunning = false
+ unregisterReceiver(cancelReceiver)
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ snackString("Download started")
+ val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+ serviceScope.launch {
+ mutex.withLock {
+ if (!isCurrentlyProcessing) {
+ isCurrentlyProcessing = true
+ processQueue()
+ isCurrentlyProcessing = false
+ }
+ }
+ }
+ return START_NOT_STICKY
+ }
+
+ private fun processQueue() {
+ CoroutineScope(Dispatchers.Default).launch {
+ while (AnimeServiceDataSingleton.downloadQueue.isNotEmpty()) {
+ val task = AnimeServiceDataSingleton.downloadQueue.poll()
+ if (task != null) {
+ val job = launch { download(task) }
+ mutex.withLock {
+ downloadJobs[task.getTaskName()] = job
+ }
+ job.join() // Wait for the job to complete before continuing to the next task
+ mutex.withLock {
+ downloadJobs.remove(task.getTaskName())
+ }
+ updateNotification() // Update the notification after each task is completed
+ }
+ if (AnimeServiceDataSingleton.downloadQueue.isEmpty()) {
+ withContext(Dispatchers.Main) {
+ stopSelf() // Stop the service when the queue is empty
+ }
+ }
+ }
+ }
+ }
+
+ @UnstableApi
+ fun cancelDownload(taskName: String) {
+ CoroutineScope(Dispatchers.Default).launch {
+ mutex.withLock {
+ val url = AnimeServiceDataSingleton.downloadQueue.find { it.getTaskName() == taskName }?.video?.file?.url ?: ""
+ DownloadService.sendRemoveDownload(
+ this@AnimeDownloaderService,
+ ExoplayerDownloadService::class.java,
+ url,
+ false
+ )
+ downloadJobs[taskName]?.cancel()
+ downloadJobs.remove(taskName)
+ AnimeServiceDataSingleton.downloadQueue.removeAll { it.getTaskName() == taskName }
+ updateNotification() // Update the notification after cancellation
+ }
+ }
+ }
+
+ private fun updateNotification() {
+ // Update the notification to reflect the current state of the queue
+ val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size
+ val text = if (pendingDownloads > 0) {
+ "Pending downloads: $pendingDownloads"
+ } else {
+ "All downloads completed"
+ }
+ builder.setContentText(text)
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ }
+
+ @androidx.annotation.OptIn(UnstableApi::class) suspend fun download(task: DownloadTask) {
+ try {
+ val downloadManager = Helper.downloadManager(this@AnimeDownloaderService)
+ withContext(Dispatchers.Main) {
+ val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ContextCompat.checkSelfPermission(
+ this@AnimeDownloaderService,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ } else {
+ true
+ }
+
+ builder.setContentText("Downloading ${task.title} - ${task.episode}")
+ if (notifi) {
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ }
+
+ broadcastDownloadStarted(task.getTaskName())
+
+ currActivity()?.let {
+ Helper.downloadVideo(
+ it,
+ task.video,
+ task.subtitle)
+ }
+
+ saveMediaInfo(task)
+ val downloadStarted = hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
+
+ if (!downloadStarted) {
+ logger("Download failed to start")
+ builder.setContentText("${task.title} - ${task.episode} Download failed to start")
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ snackString("${task.title} - ${task.episode} Download failed to start")
+ broadcastDownloadFailed(task.getTaskName())
+ return@withContext
+ }
+
+
+ // periodically check if the download is complete
+ while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null) {
+ val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
+ if (download != null) {
+ if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) {
+ logger("Download failed")
+ builder.setContentText("${task.title} - ${task.episode} Download failed")
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ snackString("${task.title} - ${task.episode} Download failed")
+ logger("Download failed: ${download.failureReason}")
+ FirebaseCrashlytics.getInstance().recordException(Exception("Anime Download failed:" +
+ " ${download.failureReason}" +
+ " url: ${task.video.file.url}" +
+ " title: ${task.title}" +
+ " episode: ${task.episode}"))
+ broadcastDownloadFailed(task.getTaskName())
+ break
+ }
+ if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) {
+ logger("Download completed")
+ builder.setContentText("${task.title} - ${task.episode} Download completed")
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ snackString("${task.title} - ${task.episode} Download completed")
+ getSharedPreferences(getString(R.string.anime_downloads), Context.MODE_PRIVATE).edit().putString(
+ task.getTaskName(),
+ task.video.file.url
+ ).apply()
+ downloadsManager.addDownload(
+ DownloadedType(
+ task.title,
+ task.episode,
+ DownloadedType.Type.ANIME,
+ )
+ )
+ broadcastDownloadFinished(task.getTaskName())
+ break
+ }
+ if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) {
+ logger("Download stopped")
+ builder.setContentText("${task.title} - ${task.episode} Download stopped")
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ snackString("${task.title} - ${task.episode} Download stopped")
+ break
+ }
+ broadcastDownloadProgress(task.getTaskName(), download.percentDownloaded.toInt())
+ if (notifi) {
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ }
+ }
+ kotlinx.coroutines.delay(2000)
+ }
+ }
+ } catch (e: Exception) {
+ logger("Exception while downloading file: ${e.message}")
+ snackString("Exception while downloading file: ${e.message}")
+ FirebaseCrashlytics.getInstance().recordException(e)
+ broadcastDownloadFailed(task.getTaskName())
+ }
+ }
+
+ @androidx.annotation.OptIn(UnstableApi::class) suspend fun hasDownloadStarted(downloadManager: DownloadManager, task: DownloadTask, timeout: Long): Boolean {
+ val startTime = System.currentTimeMillis()
+ while (System.currentTimeMillis() - startTime < timeout) {
+ val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
+ if (download != null) {
+ return true
+ }
+ // Delay between each poll
+ kotlinx.coroutines.delay(500)
+ }
+ return false
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun saveMediaInfo(task: DownloadTask) {
+ GlobalScope.launch(Dispatchers.IO) {
+ val directory = File(
+ getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
+ "${DownloadsManager.animeLocation}/${task.title}"
+ )
+ val episodeDirectory = File(directory, task.episode)
+ if (!directory.exists()) directory.mkdirs()
+ if (!episodeDirectory.exists()) episodeDirectory.mkdirs()
+
+ val file = File(directory, "media.json")
+ val gson = GsonBuilder()
+ .registerTypeAdapter(SChapter::class.java, InstanceCreator {
+ SChapterImpl() // Provide an instance of SChapterImpl
+ })
+ .registerTypeAdapter(SAnime::class.java, InstanceCreator {
+ SAnimeImpl() // Provide an instance of SAnimeImpl
+ })
+ .registerTypeAdapter(SEpisode::class.java, InstanceCreator {
+ SEpisodeImpl() // Provide an instance of SEpisodeImpl
+ })
+ .create()
+ val mediaJson = gson.toJson(task.sourceMedia)
+ val media = gson.fromJson(mediaJson, Media::class.java)
+ if (media != null) {
+ media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") }
+ media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") }
+ if (task.episodeImage != null) {
+ media.anime?.episodes?.get(task.episode)?.let { episode ->
+ episode.thumb = downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg")?.let {
+ FileUrl(
+ it
+ )
+ }
+ }
+ downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg")
+ }
+
+ val jsonString = gson.toJson(media)
+ withContext(Dispatchers.Main) {
+ file.writeText(jsonString)
+ }
+ }
+ }
+ }
+
+
+ private suspend fun downloadImage(url: String, directory: File, name: String): String? =
+ withContext(Dispatchers.IO) {
+ var connection: HttpURLConnection? = null
+ println("Downloading url $url")
+ try {
+ connection = URL(url).openConnection() as HttpURLConnection
+ connection.connect()
+ if (connection.responseCode != HttpURLConnection.HTTP_OK) {
+ throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
+ }
+
+ val file = File(directory, name)
+ FileOutputStream(file).use { output ->
+ connection.inputStream.use { input ->
+ input.copyTo(output)
+ }
+ }
+ return@withContext file.absolutePath
+ } catch (e: Exception) {
+ e.printStackTrace()
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@AnimeDownloaderService,
+ "Exception while saving ${name}: ${e.message}",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ null
+ } finally {
+ connection?.disconnect()
+ }
+ }
+
+ private fun broadcastDownloadStarted(chapterNumber: String) {
+ val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply {
+ putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber)
+ }
+ sendBroadcast(intent)
+ }
+
+ private fun broadcastDownloadFinished(chapterNumber: String) {
+ val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FINISHED).apply {
+ putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber)
+ }
+ sendBroadcast(intent)
+ }
+
+ private fun broadcastDownloadFailed(chapterNumber: String) {
+ val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FAILED).apply {
+ putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber)
+ }
+ sendBroadcast(intent)
+ }
+
+ private fun broadcastDownloadProgress(chapterNumber: String, progress: Int) {
+ val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_PROGRESS).apply {
+ putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber)
+ putExtra("progress", progress)
+ }
+ sendBroadcast(intent)
+ }
+
+ private val cancelReceiver = object : BroadcastReceiver() {
+ @androidx.annotation.OptIn(UnstableApi::class) override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == ACTION_CANCEL_DOWNLOAD) {
+ val taskName = intent.getStringExtra(EXTRA_TASK_NAME)
+ taskName?.let {
+ cancelDownload(it)
+ }
+ }
+ }
+ }
+
+
+ data class DownloadTask(
+ val title: String,
+ val episode: String,
+ val video: Video,
+ val subtitle: Subtitle? = null,
+ val sourceMedia: Media? = null,
+ val episodeImage: String? = null,
+ val retries: Int = 2,
+ val simultaneousDownloads: Int = 2,
+ ) {
+ fun getTaskName(): String {
+ return "$title - $episode"
+ }
+ }
+
+ companion object {
+ private const val NOTIFICATION_ID = 1103
+ const val ACTION_CANCEL_DOWNLOAD = "action_cancel_download"
+ const val EXTRA_TASK_NAME = "extra_task_name"
+ }
+}
+
+object AnimeServiceDataSingleton {
+ var video: Video? = null
+ var sourceMedia: Media? = null
+ var downloadQueue: Queue = ConcurrentLinkedQueue()
+
+ @Volatile
+ var isServiceRunning: Boolean = false
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt
index fce89d3e..202a4b8f 100644
--- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt
+++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt
@@ -18,7 +18,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import ani.dantotsu.R
-import ani.dantotsu.download.Download
+import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.media.Media
@@ -246,10 +246,10 @@ class MangaDownloaderService : Service() {
saveMediaInfo(task)
downloadsManager.addDownload(
- Download(
+ DownloadedType(
task.title,
task.chapter,
- Download.Type.MANGA
+ DownloadedType.Type.MANGA
)
)
broadcastDownloadFinished(task.chapter)
diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt
index b91bb55a..87dfebed 100644
--- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt
@@ -1,11 +1,13 @@
package ani.dantotsu.download.manga
+import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
+import android.widget.LinearLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import ani.dantotsu.R
@@ -19,6 +21,7 @@ class OfflineMangaAdapter(
private val inflater: LayoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
private var originalItems: List = items
+ private var style = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
override fun getCount(): Int {
return items.size
@@ -32,10 +35,13 @@ class OfflineMangaAdapter(
return position.toLong()
}
+ @SuppressLint("SetTextI18n")
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
- var view = convertView
- if (view == null) {
- view = inflater.inflate(R.layout.item_media_compact, parent, false)
+
+ val view: View = convertView ?: when(style) {
+ 0 -> inflater.inflate(R.layout.item_media_large, parent, false) // large view
+ 1 -> inflater.inflate(R.layout.item_media_compact, parent, false) // compact view
+ else -> inflater.inflate(R.layout.item_media_compact, parent, false) // compact view
}
val item = getItem(position) as OfflineMangaModel
@@ -44,11 +50,31 @@ class OfflineMangaAdapter(
val itemScore = view.findViewById(R.id.itemCompactScore)
val itemScoreBG = view.findViewById(R.id.itemCompactScoreBG)
val ongoing = view.findViewById(R.id.itemCompactOngoing)
+ val totalchapter = view.findViewById(R.id.itemCompactTotal)
+ val type = view.findViewById(R.id.itemCompactRelation)
+ val typeView = view.findViewById(R.id.itemCompactType)
+
+ if (style == 0){
+ val bannerView = view.findViewById(R.id.itemCompactBanner) // for large view
+ val chapters = view.findViewById(R.id.itemTotal)
+ chapters.text = " Chapters"
+ bannerView.setImageURI(item.banner)
+ totalchapter.text = item.totalchapter
+ }
+
+ else if (style == 1){
+ val readchapter = view.findViewById(R.id.itemCompactUserProgress) // for compact view
+ readchapter.text = item.readchapter
+ totalchapter.text = " | " + item.totalchapter
+ }
+
// Bind item data to the views
- // For example:
+ type.text = item.type
+ typeView.visibility = View.VISIBLE
imageView.setImageURI(item.image)
titleTextView.text = item.title
itemScore.text = item.score
+
if (item.isOngoing) {
ongoing.visibility = View.VISIBLE
} else {
@@ -74,4 +100,9 @@ class OfflineMangaAdapter(
this.originalItems = items
notifyDataSetChanged()
}
+
+ fun notifyNewGrid(){
+ style = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
+ notifyDataSetChanged()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt
index e81d2817..116c1267 100644
--- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt
+++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt
@@ -13,16 +13,22 @@ import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.animation.AlphaAnimation
+import android.view.animation.LayoutAnimationController
import android.view.animation.OvershootInterpolator
+import android.widget.AbsListView
import android.widget.AutoCompleteTextView
import android.widget.GridView
+import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.fragment.app.Fragment
import ani.dantotsu.R
+import ani.dantotsu.currActivity
import ani.dantotsu.currContext
-import ani.dantotsu.download.Download
+import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
+import ani.dantotsu.initActivity
import ani.dantotsu.logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
@@ -45,6 +51,7 @@ import kotlin.math.max
import kotlin.math.min
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
+
private val downloadManager = Injekt.get()
private var downloads: List = listOf()
private lateinit var gridView: GridView
@@ -91,24 +98,75 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
override fun afterTextChanged(s: Editable?) {
}
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int, ) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onSearchQuery(s.toString())
}
})
+ var style = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
+ ?.getInt("offline_view", 0)
+ val layoutList = view.findViewById(R.id.downloadedList)
+ val layoutcompact = view.findViewById(R.id.downloadedGrid)
+ var selected = when (style) {
+ 0 -> layoutList
+ 1 -> layoutcompact
+ else -> layoutList
+ }
+ selected.alpha = 1f
- gridView = view.findViewById(R.id.gridView)
+ fun selected(it: ImageView) {
+ selected.alpha = 0.33f
+ selected = it
+ selected.alpha = 1f
+ }
+
+ layoutList.setOnClickListener {
+ selected(it as ImageView)
+ style = 0
+ context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
+ ?.putInt("offline_view", style!!)?.apply()
+ gridView.visibility = View.GONE
+ gridView = view.findViewById(R.id.gridView)
+ gridView.adapter = adapter
+ gridView.scheduleLayoutAnimation()
+ gridView.visibility = View.VISIBLE
+ adapter.notifyNewGrid()
+
+ }
+
+ layoutcompact.setOnClickListener {
+ selected(it as ImageView)
+ style = 1
+ context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
+ ?.putInt("offline_view", style!!)?.apply()
+ gridView.visibility = View.GONE
+ gridView = view.findViewById(R.id.gridView1)
+ gridView.adapter = adapter
+ gridView.scheduleLayoutAnimation()
+ gridView.visibility = View.VISIBLE
+ adapter.notifyNewGrid()
+ }
+
+ gridView = if(style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
+ gridView.visibility = View.VISIBLE
getDownloads()
+
+ val fadeIn = AlphaAnimation(0f, 1f)
+ fadeIn.duration = 200 // animations pog
+ val animation = LayoutAnimationController(fadeIn)
+
+ gridView.layoutAnimation = animation
adapter = OfflineMangaAdapter(requireContext(), downloads, this)
gridView.adapter = adapter
+ gridView.scheduleLayoutAnimation()
gridView.setOnItemClickListener { parent, view, position, id ->
// Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel
val media =
- downloadManager.mangaDownloads.firstOrNull { it.title == item.title }
- ?: downloadManager.novelDownloads.firstOrNull { it.title == item.title }
+ downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
+ ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
media?.let {
startActivity(
Intent(requireContext(), MediaDetailsActivity::class.java)
@@ -123,10 +181,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
gridView.setOnItemLongClickListener { parent, view, position, id ->
// Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel
- val type: Download.Type = if (downloadManager.mangaDownloads.any { it.title == item.title }) {
- Download.Type.MANGA
+ val type: DownloadedType.Type = if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) {
+ DownloadedType.Type.MANGA
} else {
- Download.Type.NOVEL
+ DownloadedType.Type.NOVEL
}
// Alert dialog to confirm deletion
val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup)
@@ -154,6 +212,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ initActivity(requireActivity())
var height = statusBarHeight
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
@@ -187,9 +246,25 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}
scrollTop.setOnClickListener {
- //TODO: scroll to top
+ gridView.smoothScrollToPosition(0)
}
+ // Assuming 'scrollTop' is a view that you want to hide/show
+ scrollTop.visibility = View.GONE
+
+ gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
+ override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
+ // Implement behavior for different scroll states if needed
+ }
+
+ override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
+ val first = view.getChildAt(0)
+ val visibility = first != null && first.top < -height
+ scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
+ }
+ })
+
+
}
override fun onResume() {
@@ -215,19 +290,19 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private fun getDownloads() {
downloads = listOf()
- val mangaTitles = downloadManager.mangaDownloads.map { it.title }.distinct()
+ val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
val newMangaDownloads = mutableListOf()
for (title in mangaTitles) {
- val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
+ val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
val download = _downloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel
}
downloads = newMangaDownloads
- val novelTitles = downloadManager.novelDownloads.map { it.title }.distinct()
+ val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
val newNovelDownloads = mutableListOf()
for (title in novelTitles) {
- val _downloads = downloadManager.novelDownloads.filter { it.title == title }
+ val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
val download = _downloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newNovelDownloads += offlineMangaModel
@@ -236,17 +311,17 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}
- private fun getMedia(download: Download): Media? {
- val type = if (download.type == Download.Type.MANGA) {
+ private fun getMedia(downloadedType: DownloadedType): Media? {
+ val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
- } else if (download.type == Download.Type.ANIME) {
+ } else if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/$type/${download.title}"
+ "Dantotsu/$type/${downloadedType.title}"
)
//load media.json and convert to media class with gson
return try {
@@ -266,40 +341,45 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}
}
- private fun loadOfflineMangaModel(download: Download): OfflineMangaModel {
- val type = if (download.type == Download.Type.MANGA) {
+ private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
+ val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
"Manga"
- } else if (download.type == Download.Type.ANIME) {
+ } else if (downloadedType.type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
- "Dantotsu/$type/${download.title}"
+ "Dantotsu/$type/${downloadedType.title}"
)
//load media.json and convert to media class with gson
try {
val media = File(directory, "media.json")
val mediaJson = media.readText()
- val mediaModel = getMedia(download)!!
+ val mediaModel = getMedia(downloadedType)!!
val cover = File(directory, "cover.jpg")
val coverUri: Uri? = if (cover.exists()) {
Uri.fromFile(cover)
- } else {
- null
- }
+ } else null
+ val banner = File(directory, "banner.jpg")
+ val bannerUri: Uri? = if (banner.exists()) {
+ Uri.fromFile(banner)
+ } else null
val title = mediaModel.nameMAL ?: mediaModel.nameRomaji
val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore
?: 0) else mediaModel.userScore) / 10.0).toString()
- val isOngoing = false
+ val isOngoing = mediaModel.status == currActivity()!!.getString(R.string.status_releasing)
val isUserScored = mediaModel.userScore != 0
- return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
+ val readchapter = (mediaModel.userProgress ?: "~").toString()
+ val totalchapter = "${mediaModel.manga?.totalChapters ?: "??"}"
+ val chapters = " Chapters"
+ return OfflineMangaModel(title, score, totalchapter, readchapter, type, chapters, isOngoing, isUserScored, coverUri , bannerUri )
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
FirebaseCrashlytics.getInstance().recordException(e)
- return OfflineMangaModel("unknown", "0", false, false, null)
+ return OfflineMangaModel("unknown", "0", "??", "??","movie" ,"hmm", false, false, null , null)
}
}
}
diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaModel.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaModel.kt
index 568081ee..3cebc6f6 100644
--- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaModel.kt
+++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaModel.kt
@@ -5,7 +5,12 @@ import android.net.Uri
data class OfflineMangaModel(
val title: String,
val score: String,
+ val totalchapter: String,
+ val readchapter : String,
+ val type: String,
+ val chapters: String,
val isOngoing: Boolean,
val isUserScored: Boolean,
- val image: Uri?
+ val image: Uri?,
+ val banner: Uri?
)
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt
index e987cd25..d729ef57 100644
--- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt
+++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt
@@ -17,7 +17,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import ani.dantotsu.R
-import ani.dantotsu.download.Download
+import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.media.Media
@@ -330,10 +330,10 @@ class NovelDownloaderService : Service() {
saveMediaInfo(task)
downloadsManager.addDownload(
- Download(
+ DownloadedType(
task.title,
task.chapter,
- Download.Type.NOVEL
+ DownloadedType.Type.NOVEL
)
)
broadcastDownloadFinished(task.originalLink)
diff --git a/app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt b/app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt
similarity index 91%
rename from app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt
rename to app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt
index 3a26ac1d..3d7a1802 100644
--- a/app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt
+++ b/app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt
@@ -11,7 +11,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler
import ani.dantotsu.R
@UnstableApi
-class MyDownloadService : DownloadService(1, 1, "download_service", R.string.downloads, 0) {
+class ExoplayerDownloadService : DownloadService(1, 2000, "download_service", R.string.downloads, 0) {
companion object {
private const val JOB_ID = 1
private const val FOREGROUND_NOTIFICATION_ID = 1
diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt
index 2dcf31c6..48909107 100644
--- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt
+++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt
@@ -1,8 +1,19 @@
package ani.dantotsu.download.video
+import android.Manifest
import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.AlertDialog
import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
+import android.os.Build
+import android.util.Log
+import androidx.annotation.OptIn
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.core.content.ContextCompat.getString
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
@@ -15,6 +26,7 @@ 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
@@ -22,7 +34,12 @@ import androidx.media3.exoplayer.scheduler.Requirements
import androidx.media3.ui.TrackSelectionDialogBuilder
import ani.dantotsu.R
import ani.dantotsu.defaultHeaders
+import ani.dantotsu.download.DownloadedType
+import ani.dantotsu.download.DownloadsManager
+import ani.dantotsu.download.anime.AnimeDownloaderService
+import ani.dantotsu.download.anime.AnimeServiceDataSingleton
import ani.dantotsu.logError
+import ani.dantotsu.media.Media
import ani.dantotsu.okHttpClient
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.SubtitleType
@@ -37,6 +54,8 @@ import java.util.concurrent.*
object Helper {
+ private var simpleCache: SimpleCache? = null
+
@SuppressLint("UnsafeOptInUsageError")
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
val dataSourceFactory = DataSource.Factory {
@@ -82,18 +101,18 @@ object Helper {
)
downloadHelper.prepare(object : DownloadHelper.Callback {
override fun onPrepared(helper: DownloadHelper) {
- TrackSelectionDialogBuilder(
- context, "Select thingy", helper.getTracks(0).groups
+ /*TrackSelectionDialogBuilder( TODO: use this for subtitles
+ context, "Select Source", helper.getTracks(0).groups
) { _, overrides ->
val params = TrackSelectionParameters.Builder(context)
overrides.forEach {
params.addOverride(it.value)
}
helper.addTrackSelection(0, params.build())
- MyDownloadService
+ ExoplayerDownloadService
DownloadService.sendAddDownload(
context,
- MyDownloadService::class.java,
+ ExoplayerDownloadService::class.java,
helper.getDownloadRequest(null),
false
)
@@ -103,6 +122,14 @@ object Helper {
if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)"
}
build().show()
+ }*/
+ helper.getDownloadRequest(null).let {
+ DownloadService.sendAddDownload(
+ context,
+ ExoplayerDownloadService::class.java,
+ it,
+ false
+ )
}
}
@@ -114,13 +141,13 @@ object Helper {
private var download: DownloadManager? = null
- private const val DOWNLOAD_CONTENT_DIRECTORY = "downloads"
+ private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads"
@Synchronized
@UnstableApi
fun downloadManager(context: Context): DownloadManager {
return download ?: let {
- val database = StandaloneDatabaseProvider(context)
+ val database = Injekt.get()
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
val dataSourceFactory = DataSource.Factory {
//val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
@@ -133,17 +160,42 @@ object Helper {
}
dataSource
}
- DownloadManager(
+ val threadPoolSize = Runtime.getRuntime().availableProcessors()
+ val executorService = Executors.newFixedThreadPool(threadPoolSize)
+ val downloadManager = DownloadManager(
context,
database,
- SimpleCache(downloadDirectory, NoOpCacheEvictor(), database),
+ getSimpleCache(context),
dataSourceFactory,
- Executor(Runnable::run)
+ executorService
).apply {
requirements =
Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW)
maxParallelDownloads = 3
}
+ downloadManager.addListener( //for testing
+ object : DownloadManager.Listener {
+ override fun onDownloadChanged(
+ downloadManager: DownloadManager,
+ download: Download,
+ finalException: Exception?
+ ) {
+ if (download.state == Download.STATE_COMPLETED) {
+ Log.e("Downloader", "Download Completed")
+ } else if (download.state == Download.STATE_FAILED) {
+ Log.e("Downloader", "Download Failed")
+ } else if (download.state == Download.STATE_STOPPED) {
+ Log.e("Downloader", "Download Stopped")
+ } else if (download.state == Download.STATE_QUEUED) {
+ Log.e("Downloader", "Download Queued")
+ } else if (download.state == Download.STATE_DOWNLOADING) {
+ Log.e("Downloader", "Download Downloading")
+ }
+ }
+ }
+ )
+
+ downloadManager
}
}
@@ -159,4 +211,108 @@ object Helper {
}
return downloadDirectory!!
}
+
+ @OptIn(UnstableApi::class)
+ fun startAnimeDownloadService(
+ context: Context,
+ title: String,
+ episode: String,
+ video: Video,
+ subtitle: Subtitle? = null,
+ sourceMedia: Media? = null,
+ episodeImage: String? = null
+ ) {
+ if (!isNotificationPermissionGranted(context)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ActivityCompat.requestPermissions(
+ context as Activity,
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS),
+ 1
+ )
+ }
+ }
+
+ val downloadTask = AnimeDownloaderService.DownloadTask(
+ title,
+ episode,
+ video,
+ subtitle,
+ sourceMedia,
+ episodeImage
+ )
+
+ val downloadsManger = Injekt.get()
+ val downloadCheck = downloadsManger
+ .queryDownload(title, episode, DownloadedType.Type.ANIME)
+
+ if (downloadCheck) {
+ AlertDialog.Builder(context , R.style.MyPopup)
+ .setTitle("Download Exists")
+ .setMessage("A download for this episode already exists. Do you want to overwrite it?")
+ .setPositiveButton("Yes") { _, _ ->
+ DownloadService.sendRemoveDownload(
+ context,
+ ExoplayerDownloadService::class.java,
+ context.getSharedPreferences(
+ getString(context, R.string.anime_downloads),
+ Context.MODE_PRIVATE
+ ).getString(
+ downloadTask.getTaskName(),
+ ""
+ ) ?: "",
+ false
+ )
+ context.getSharedPreferences(
+ getString(context, R.string.anime_downloads),
+ Context.MODE_PRIVATE
+ ).edit()
+ .remove(downloadTask.getTaskName())
+ .apply()
+ downloadsManger.removeDownload(
+ DownloadedType(
+ title,
+ episode,
+ DownloadedType.Type.ANIME
+ )
+ )
+ AnimeServiceDataSingleton.downloadQueue.offer(downloadTask)
+ if (!AnimeServiceDataSingleton.isServiceRunning) {
+ val intent = Intent(context, AnimeDownloaderService::class.java)
+ ContextCompat.startForegroundService(context, intent)
+ AnimeServiceDataSingleton.isServiceRunning = true
+ }
+ }
+ .setNegativeButton("No") { _, _ -> }
+ .show()
+ } else {
+ AnimeServiceDataSingleton.downloadQueue.offer(downloadTask)
+ if (!AnimeServiceDataSingleton.isServiceRunning) {
+ val intent = Intent(context, AnimeDownloaderService::class.java)
+ ContextCompat.startForegroundService(context, intent)
+ AnimeServiceDataSingleton.isServiceRunning = true
+ }
+ }
+ }
+
+ @OptIn(UnstableApi::class)
+ fun getSimpleCache(context: Context): SimpleCache {
+ return if (simpleCache == null) {
+ val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
+ val database = Injekt.get()
+ simpleCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), database)
+ simpleCache!!
+ } else {
+ simpleCache!!
+ }
+ }
+
+ private fun isNotificationPermissionGranted(context: Context): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return ActivityCompat.checkSelfPermission(
+ context,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+ return true
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt
index 1a08934d..8ecbe0a1 100644
--- a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt
+++ b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt
@@ -2,6 +2,7 @@ package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
+import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
@@ -26,6 +27,7 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
+import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentAnimeBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
@@ -47,16 +49,17 @@ import kotlin.math.min
class AnimeFragment : Fragment() {
private var _binding: FragmentAnimeBinding? = null
private val binding get() = _binding!!
+ private lateinit var animePageAdapter: AnimePageAdapter
private var uiSettings: UserInterfaceSettings =
- loadData("ui_settings") ?: UserInterfaceSettings()
+ loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistAnimeViewModel by activityViewModels()
override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeBinding.inflate(inflater, container, false)
return binding.root
@@ -78,11 +81,11 @@ class AnimeFragment : Fragment() {
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
height = max(
- statusBarHeight,
- min(
- displayCutout.boundingRects[0].width(),
- displayCutout.boundingRects[0].height()
- )
+ statusBarHeight,
+ min(
+ displayCutout.boundingRects[0].width(),
+ displayCutout.boundingRects[0].height()
+ )
)
}
}
@@ -95,18 +98,18 @@ class AnimeFragment : Fragment() {
binding.animePageRecyclerView.updatePaddingRelative(bottom = navBarHeight + 160f.px)
- val animePageAdapter = AnimePageAdapter()
+ animePageAdapter = AnimePageAdapter()
var loading = true
if (model.notSet) {
model.notSet = false
model.searchResults = SearchResults(
- "ANIME",
- isAdult = false,
- onList = false,
- results = mutableListOf(),
- hasNextPage = true,
- sort = Anilist.sortBy[1]
+ "ANIME",
+ isAdult = false,
+ onList = false,
+ results = mutableListOf(),
+ hasNextPage = true,
+ sort = Anilist.sortBy[1]
)
}
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
@@ -174,7 +177,7 @@ class AnimeFragment : Fragment() {
}
binding.animePageRecyclerView.addOnScrollListener(object :
- RecyclerView.OnScrollListener() {
+ RecyclerView.OnScrollListener() {
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
if (!v.canScrollVertically(1)) {
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
@@ -214,19 +217,19 @@ class AnimeFragment : Fragment() {
model.getTrending().observe(viewLifecycleOwner) {
if (it != null) {
animePageAdapter.updateTrending(
- MediaAdaptor(
- if (uiSettings.smallView) 3 else 2,
- it,
- requireActivity(),
- viewPager = animePageAdapter.trendingViewPager
- )
+ MediaAdaptor(
+ if (uiSettings.smallView) 3 else 2,
+ it,
+ requireActivity(),
+ viewPager = animePageAdapter.trendingViewPager
+ )
)
animePageAdapter.updateAvatar()
}
}
}
binding.animePageScrollTop.translationY =
- -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
+ -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
}
}
@@ -244,13 +247,13 @@ class AnimeFragment : Fragment() {
animePageAdapter.onSeasonLongClick = { i ->
val (season, year) = Anilist.currentSeasons[i]
ContextCompat.startActivity(
- requireContext(),
- Intent(requireContext(), SearchActivity::class.java)
- .putExtra("type", "ANIME")
- .putExtra("season", season)
- .putExtra("seasonYear", year.toString())
- .putExtra("search", true),
- null
+ requireContext(),
+ Intent(requireContext(), SearchActivity::class.java)
+ .putExtra("type", "ANIME")
+ .putExtra("season", season)
+ .putExtra("seasonYear", year.toString())
+ .putExtra("search", true),
+ null
)
true
}
@@ -277,6 +280,12 @@ class AnimeFragment : Fragment() {
override fun onResume() {
if (!model.loaded) Refresh.activity[this.hashCode()]!!.postValue(true)
+ if (animePageAdapter.trendingViewPager != null) {
+ animePageAdapter.setIncognito()
+ binding.root.requestApplyInsets()
+ binding.root.requestLayout()
+ }
+
super.onResume()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt
index 4e5b23ad..d843b2dc 100644
--- a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt
@@ -75,12 +75,6 @@ class AnimePageAdapter : RecyclerView.Adapter("ui_settings") ?: UserInterfaceSettings()
+ binding.incognitoTextView.visibility = View.VISIBLE
+ if (!uiSettings.immersiveMode) {
+ binding.root.fitsSystemWindows = true
+ //holy deprecation
+ binding.root.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ binding.root.requestApplyInsets()
+ binding.root.requestLayout()
+ }
+ } else {
+ binding.incognitoTextView.visibility = View.GONE
+ }
super.onResume()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt
index 14870b7f..55ac8b2f 100644
--- a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt
+++ b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt
@@ -2,6 +2,7 @@ package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
+import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
@@ -24,6 +25,7 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistMangaViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
+import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
@@ -43,16 +45,17 @@ import kotlin.math.min
class MangaFragment : Fragment() {
private var _binding: FragmentMangaBinding? = null
private val binding get() = _binding!!
+ private lateinit var mangaPageAdapter: MangaPageAdapter
private var uiSettings: UserInterfaceSettings =
- loadData("ui_settings") ?: UserInterfaceSettings()
+ loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistMangaViewModel by activityViewModels()
override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
): View {
_binding = FragmentMangaBinding.inflate(inflater, container, false)
return binding.root
@@ -73,11 +76,11 @@ class MangaFragment : Fragment() {
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
height = max(
- statusBarHeight,
- min(
- displayCutout.boundingRects[0].width(),
- displayCutout.boundingRects[0].height()
- )
+ statusBarHeight,
+ min(
+ displayCutout.boundingRects[0].width(),
+ displayCutout.boundingRects[0].height()
+ )
)
}
}
@@ -90,23 +93,23 @@ class MangaFragment : Fragment() {
binding.mangaPageRecyclerView.updatePaddingRelative(bottom = navBarHeight + 160f.px)
- val mangaPageAdapter = MangaPageAdapter()
+ mangaPageAdapter = MangaPageAdapter()
var loading = true
if (model.notSet) {
model.notSet = false
model.searchResults = SearchResults(
- "MANGA",
- isAdult = false,
- onList = false,
- results = arrayListOf(),
- hasNextPage = true,
- sort = Anilist.sortBy[1]
+ "MANGA",
+ isAdult = false,
+ onList = false,
+ results = arrayListOf(),
+ hasNextPage = true,
+ sort = Anilist.sortBy[1]
)
}
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
val progressAdaptor = ProgressAdapter(searched = model.searched)
binding.mangaPageRecyclerView.adapter =
- ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
+ ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
val layout = LinearLayoutManager(requireContext())
binding.mangaPageRecyclerView.layoutManager = layout
@@ -132,7 +135,7 @@ class MangaFragment : Fragment() {
}
binding.mangaPageRecyclerView.addOnScrollListener(object :
- RecyclerView.OnScrollListener() {
+ RecyclerView.OnScrollListener() {
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
if (!v.canScrollVertically(1)) {
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
@@ -172,19 +175,19 @@ class MangaFragment : Fragment() {
model.getTrending().observe(viewLifecycleOwner) {
if (it != null) {
mangaPageAdapter.updateTrending(
- MediaAdaptor(
- if (uiSettings.smallView) 3 else 2,
- it,
- requireActivity(),
- viewPager = mangaPageAdapter.trendingViewPager
- )
+ MediaAdaptor(
+ if (uiSettings.smallView) 3 else 2,
+ it,
+ requireActivity(),
+ viewPager = mangaPageAdapter.trendingViewPager
+ )
)
mangaPageAdapter.updateAvatar()
}
}
}
binding.mangaPageScrollTop.translationY =
- -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
+ -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
}
}
@@ -251,6 +254,12 @@ class MangaFragment : Fragment() {
override fun onResume() {
if (!model.loaded) Refresh.activity[this.hashCode()]!!.postValue(true)
+ //make sure mangaPageAdapter is initialized
+ if (mangaPageAdapter.trendingViewPager != null) {
+ mangaPageAdapter.setIncognito()
+ binding.root.requestApplyInsets()
+ binding.root.requestLayout()
+ }
super.onResume()
}
diff --git a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt
index 0b999b1e..b04ebbd2 100644
--- a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt
@@ -75,13 +75,6 @@ class MangaPageAdapter : RecyclerView.Adapter {
@@ -148,6 +141,19 @@ class MangaPageAdapter : RecyclerView.Adapter binding.animeSourceList
- 1 -> binding.animeSourceGrid
- 2 -> binding.animeSourceCompact
- else -> binding.animeSourceList
- }
- selected.alpha = 1f
- fun selected(it: ImageView) {
- selected.alpha = 0.33f
- selected = it
+ //Nested Button
+ binding.animeNestedButton.setOnClickListener {
+ val dialogView =
+ LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
+ val dialogBinding = DialogLayoutBinding.bind(dialogView)
+
+ var run = false
+ var reversed = media.selected!!.recyclerReversed
+ var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
+ dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
+ dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
+ dialogBinding.animeSourceTop.setOnClickListener {
+ reversed = !reversed
+ dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
+ dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
+ run = true
+ }
+ //Grids
+ var selected = when (style) {
+ 0 -> dialogBinding.animeSourceList
+ 1 -> dialogBinding.animeSourceGrid
+ 2 -> dialogBinding.animeSourceCompact
+ else -> dialogBinding.animeSourceList
+ }
+ when (style) {
+ 0 -> dialogBinding.layoutText.text = "List"
+ 1 -> dialogBinding.layoutText.text = "Grid"
+ 2 -> dialogBinding.layoutText.text = "Compact"
+ else -> dialogBinding.animeSourceList
+ }
selected.alpha = 1f
+ fun selected(it: ImageButton) {
+ selected.alpha = 0.33f
+ selected = it
+ selected.alpha = 1f
+ }
+ dialogBinding.animeSourceList.setOnClickListener {
+ selected(it as ImageButton)
+ style = 0
+ dialogBinding.layoutText.text = "List"
+ run = true
+ }
+ dialogBinding.animeSourceGrid.setOnClickListener {
+ selected(it as ImageButton)
+ style = 1
+ dialogBinding.layoutText.text = "Grid"
+ run = true
+ }
+ dialogBinding.animeSourceCompact.setOnClickListener {
+ selected(it as ImageButton)
+ style = 2
+ dialogBinding.layoutText.text = "Compact"
+ run = true
+ }
+
+ //hidden
+ dialogBinding.animeScanlatorContainer.visibility = View.GONE
+ dialogBinding.animeDownloadContainer.visibility = View.GONE
+
+ nestedDialog = AlertDialog.Builder(fragment.requireContext() , R.style.MyPopup)
+ .setTitle("Options")
+ .setView(dialogView)
+ .setPositiveButton("OK") { _, _ ->
+ if (run) fragment.onIconPressed(style, reversed)
+ }
+ .setNegativeButton("Cancel") { _, _ ->
+ }
+ .create()
+ nestedDialog?.show()
}
- binding.animeSourceList.setOnClickListener {
- selected(it as ImageView)
- style = 0
- fragment.onIconPressed(style, reversed)
- }
- binding.animeSourceGrid.setOnClickListener {
- selected(it as ImageView)
- style = 1
- fragment.onIconPressed(style, reversed)
- }
- binding.animeSourceCompact.setOnClickListener {
- selected(it as ImageView)
- style = 2
- fragment.onIconPressed(style, reversed)
- }
- binding.animeScanlatorTop.visibility = View.GONE
- binding.animeDownloadTop.visibility = View.GONE
//Episode Handling
handleEpisodes()
}
@@ -351,12 +392,15 @@ class AnimeWatchAdapter(
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
)
}
- binding?.animeSourceLanguage?.setAdapter(
- ArrayAdapter(
- fragment.requireContext(),
- R.layout.item_dropdown,
- parser.extension.sources.map { it.lang })
+ val adapter = ArrayAdapter(
+ fragment.requireContext(),
+ R.layout.item_dropdown,
+ parser.extension.sources.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
)
+ val items = adapter.count
+ if (items > 1) binding?.animeSourceLanguageContainer?.visibility = View.VISIBLE else binding?.animeSourceLanguageContainer?.visibility = View.GONE
+
+ binding?.animeSourceLanguage?.setAdapter(adapter)
}
}
diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt
index df352220..5fc6635f 100644
--- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt
@@ -23,6 +23,7 @@ import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel
+import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.HAnimeSources
@@ -314,19 +315,19 @@ class AnimeWatchFragment : Fragment() {
if (show) View.GONE else View.VISIBLE
}
}
+ var itemSelected = false
val allSettings = pkg.sources.filterIsInstance()
if (allSettings.isNotEmpty()) {
var selectedSetting = allSettings[0]
if (allSettings.size > 1) {
- val names = allSettings.map { it.lang }.toTypedArray()
+ val names = allSettings.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
- val dialog = AlertDialog.Builder(requireContext())
+ val dialog = AlertDialog.Builder(requireContext() , R.style.MyPopup)
.setTitle("Select a Source")
- .setSingleChoiceItems(names, selectedIndex) { _, which ->
+ .setSingleChoiceItems(names, selectedIndex) { dialog, which ->
selectedIndex = which
- }
- .setPositiveButton("OK") { dialog, _ ->
selectedSetting = allSettings[selectedIndex]
+ itemSelected = true
dialog.dismiss()
// Move the fragment transaction here
@@ -343,10 +344,10 @@ class AnimeWatchFragment : Fragment() {
.commit()
}
}
- .setNegativeButton("Cancel") { dialog, _ ->
- dialog.cancel()
- changeUIVisibility(true)
- return@setNegativeButton
+ .setOnDismissListener {
+ if (!itemSelected) {
+ changeUIVisibility(true)
+ }
}
.show()
dialog.window?.setDimAmount(0.8f)
@@ -424,4 +425,12 @@ class AnimeWatchFragment : Fragment() {
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
}
+ companion object {
+ const val ACTION_DOWNLOAD_STARTED = "ani.dantotsu.ACTION_DOWNLOAD_STARTED"
+ const val ACTION_DOWNLOAD_FINISHED = "ani.dantotsu.ACTION_DOWNLOAD_FINISHED"
+ const val ACTION_DOWNLOAD_FAILED = "ani.dantotsu.ACTION_DOWNLOAD_FAILED"
+ const val ACTION_DOWNLOAD_PROGRESS = "ani.dantotsu.ACTION_DOWNLOAD_PROGRESS"
+ const val EXTRA_EPISODE_NUMBER = "extra_episode_number"
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt b/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt
new file mode 100644
index 00000000..72239043
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt
@@ -0,0 +1,43 @@
+package ani.dantotsu.media.anime
+
+import android.content.Context
+import android.os.Bundle
+import androidx.mediarouter.app.MediaRouteActionProvider
+import androidx.mediarouter.app.MediaRouteChooserDialog
+import androidx.mediarouter.app.MediaRouteChooserDialogFragment
+import androidx.mediarouter.app.MediaRouteControllerDialog
+import androidx.mediarouter.app.MediaRouteControllerDialogFragment
+import androidx.mediarouter.app.MediaRouteDialogFactory
+import ani.dantotsu.R
+
+class CustomCastProvider(context: Context) : MediaRouteActionProvider(context) {
+ init {
+ dialogFactory = CustomCastThemeFactory()
+ }
+}
+
+class CustomCastThemeFactory : MediaRouteDialogFactory() {
+ override fun onCreateChooserDialogFragment(): MediaRouteChooserDialogFragment {
+ return CustomMediaRouterChooserDialogFragment()
+ }
+
+ override fun onCreateControllerDialogFragment(): MediaRouteControllerDialogFragment {
+ return CustomMediaRouteControllerDialogFragment()
+ }
+}
+
+class CustomMediaRouterChooserDialogFragment: MediaRouteChooserDialogFragment() {
+ override fun onCreateChooserDialog(
+ context: Context,
+ savedInstanceState: Bundle?
+ ): MediaRouteChooserDialog =
+ MediaRouteChooserDialog(context, R.style.MyPopup)
+}
+
+class CustomMediaRouteControllerDialogFragment: MediaRouteControllerDialogFragment() {
+ override fun onCreateControllerDialog(
+ context: Context,
+ savedInstanceState: Bundle?
+ ): MediaRouteControllerDialog =
+ MediaRouteControllerDialog(context, R.style.MyPopup)
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/media/anime/Episode.kt b/app/src/main/java/ani/dantotsu/media/anime/Episode.kt
index 14615246..cc4ce613 100644
--- a/app/src/main/java/ani/dantotsu/media/anime/Episode.kt
+++ b/app/src/main/java/ani/dantotsu/media/anime/Episode.kt
@@ -14,7 +14,7 @@ data class Episode(
var selectedExtractor: String? = null,
var selectedVideo: Int = 0,
var selectedSubtitle: Int? = -1,
- var extractors: MutableList? = null,
+ @Transient var extractors: MutableList? = null,
@Transient var extractorCallback: ((VideoExtractor) -> Unit)? = null,
var allStreams: Boolean = false,
var watched: Long? = null,
diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
index c7cd6426..8643d9de 100644
--- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
+++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
@@ -4,6 +4,7 @@ import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
+import android.app.DownloadManager
import android.app.PictureInPictureParams
import android.app.PictureInPictureUiState
import android.content.ActivityNotFoundException
@@ -14,7 +15,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.Animatable
-import android.hardware.Sensor
import android.hardware.SensorManager
import android.media.AudioManager
import android.media.AudioManager.*
@@ -96,11 +96,17 @@ import java.util.concurrent.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
-
+import androidx.media3.cast.SessionAvailabilityListener
+import androidx.media3.cast.CastPlayer
+import androidx.media3.exoplayer.offline.Download
+import androidx.mediarouter.app.MediaRouteButton
+import ani.dantotsu.download.video.Helper
+import com.google.android.gms.cast.framework.CastButtonFactory
+import com.google.android.gms.cast.framework.CastContext
@UnstableApi
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
-class ExoplayerView : AppCompatActivity(), Player.Listener {
+class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener {
private val resumeWindow = "resumeWindow"
private val resumePosition = "resumePosition"
@@ -108,6 +114,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
private val playerOnPlay = "playerOnPlay"
private lateinit var exoPlayer: ExoPlayer
+ private lateinit var castPlayer: CastPlayer
+ private lateinit var castContext: CastContext
private lateinit var trackSelector: DefaultTrackSelector
private lateinit var cacheFactory: CacheDataSource.Factory
private lateinit var playbackParameters: PlaybackParameters
@@ -145,6 +153,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
private var orientationListener: OrientationEventListener? = null
+ private var downloadId: String? = null
+
companion object {
var initialized = false
lateinit var media: Media
@@ -328,6 +338,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
setContentView(binding.root)
//Initialize
+
+ castContext = CastContext.getSharedInstance(this)
+ castPlayer = CastPlayer(castContext)
+ castPlayer.setSessionAvailabilityListener(this)
+
WindowCompat.setDecorFitsSystemWindows(window, false)
hideSystemBars()
@@ -387,7 +402,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
orientationListener =
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) {
- println(orientation)
if (orientation in 45..135) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
View.VISIBLE
@@ -466,12 +480,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
if (isInitialized) {
isPlayerPlaying = exoPlayer.isPlaying
(exoPlay.drawable as Animatable?)?.start()
- if (isPlayerPlaying) {
+ if (isPlayerPlaying || castPlayer.isPlaying) {
Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay)
exoPlayer.pause()
+ castPlayer.pause()
} else {
- Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
- exoPlayer.play()
+ if (!castPlayer.isPlaying && castPlayer.currentMediaItem != null) {
+ Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
+ castPlayer.play()
+ } else if (!isPlayerPlaying) {
+ Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
+ exoPlayer.play()
+ }
}
}
}
@@ -1074,11 +1094,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
//Cast
if (settings.cast) {
- playerView.findViewById(R.id.exo_cast).apply {
+ playerView.findViewById(R.id.exo_cast).apply {
visibility = View.VISIBLE
- setSafeOnClickListener {
- cast()
- }
+ CastButtonFactory.setUpMediaRouteButton(context, this)
+ dialogFactory = CustomCastThemeFactory()
}
}
@@ -1101,7 +1120,21 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
if (settings.cursedSpeeds)
arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f)
else
- arrayOf(0.25f, 0.33f, 0.5f, 0.66f, 0.75f, 1f, 1.15f, 1.25f, 1.33f, 1.5f, 1.66f, 1.75f, 2f)
+ arrayOf(
+ 0.25f,
+ 0.33f,
+ 0.5f,
+ 0.66f,
+ 0.75f,
+ 1f,
+ 1.15f,
+ 1.25f,
+ 1.33f,
+ 1.5f,
+ 1.66f,
+ 1.75f,
+ 2f
+ )
val speedsName = speeds.map { "${it}x" }.toTypedArray()
var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed
@@ -1278,7 +1311,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
if (video?.format == VideoType.CONTAINER || (loadData("settings_download_manager")
?: 0) != 0
) {
- but.visibility = View.VISIBLE
+ //but.visibility = View.VISIBLE TODO: not sure if this is needed
but.setOnClickListener {
download(this, episode, animeTitle.text.toString())
}
@@ -1303,8 +1336,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
dataSource
}
cacheFactory = CacheDataSource.Factory().apply {
- setCache(simpleCache)
+ setCache(Helper.getSimpleCache(this@ExoplayerView))
setUpstreamDataSourceFactory(dataSourceFactory)
+ setCacheWriteDataSinkFactory(null)
}
val mimeType = when (video?.format) {
@@ -1313,15 +1347,33 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
else -> MimeTypes.APPLICATION_MP4
}
- val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
- logger("url: ${video!!.file.url}")
- logger("mimeType: $mimeType")
+ val downloadedMediaItem = if (ext.server.offline) {
+ val key = ext.server.name
+ downloadId = getSharedPreferences(getString(R.string.anime_downloads), MODE_PRIVATE)
+ .getString(key, null)
+ if (downloadId != null) {
+ Helper.downloadManager(this)
+ .downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem()
+ } else {
+ snackString("Download not found")
+ null
+ }
+ } else null
- if (sub != null) {
- val listofnotnullsubs = immutableListOf(sub).filterNotNull()
- builder.setSubtitleConfigurations(listofnotnullsubs)
+ mediaItem = if (downloadedMediaItem == null) {
+ val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
+ logger("url: ${video!!.file.url}")
+ logger("mimeType: $mimeType")
+
+ if (sub != null) {
+ val listofnotnullsubs = immutableListOf(sub).filterNotNull()
+ builder.setSubtitleConfigurations(listofnotnullsubs)
+ }
+ builder.build()
+ } else {
+ downloadedMediaItem
}
- mediaItem = builder.build()
+
//Source
exoSource.setOnClickListener {
@@ -1443,7 +1495,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
exoPlayer.release()
VideoCache.release()
mediaSession?.release()
- if(DiscordServiceRunningSingleton.running) {
+ if (DiscordServiceRunningSingleton.running) {
val stopIntent = Intent(this, DiscordService::class.java)
DiscordServiceRunningSingleton.running = false
stopService(stopIntent)
@@ -1483,7 +1535,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
super.onPause()
orientationListener?.disable()
if (isInitialized) {
- playerView.player?.pause()
+ if (!castPlayer.isPlaying) {
+ playerView.player?.pause()
+ }
saveData(
"${media.id}_${media.anime!!.selectedEpisode}",
exoPlayer.currentPosition,
@@ -1504,7 +1558,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
}
override fun onStop() {
- playerView.player?.pause()
+ if (!castPlayer.isPlaying) {
+ playerView.player?.pause()
+ }
super.onStop()
}
@@ -1576,7 +1632,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
if (isInitialized) {
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) {
preloading = true
- nextEpisode(false) { i ->
+ nextEpisode(false) { i -> //TODO: make sure this works for offline episodes
val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode
val selected = media.selected ?: return@nextEpisode
lifecycleScope.launch(Dispatchers.IO) {
@@ -1797,7 +1853,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
// Enter PiP Mode
@Suppress("DEPRECATION")
- @RequiresApi(Build.VERSION_CODES.N)
private fun enterPipMode() {
wasPlaying = isPlayerPlaying
if (!pipEnabled) return
@@ -1870,6 +1925,47 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
}
}
+
+ private fun startCastPlayer() {
+ castPlayer.setMediaItem(mediaItem)
+ castPlayer.prepare()
+ playerView.player = castPlayer
+ exoPlayer.stop()
+ castPlayer.addListener(object : Player.Listener {
+ //if the player is paused changed, we want to update the UI
+ override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
+ super.onPlayWhenReadyChanged(playWhenReady, reason)
+ if (playWhenReady) {
+ (exoPlay.drawable as Animatable?)?.start()
+ Glide.with(this@ExoplayerView)
+ .load(R.drawable.anim_play_to_pause)
+ .into(exoPlay)
+ } else {
+ (exoPlay.drawable as Animatable?)?.start()
+ Glide.with(this@ExoplayerView)
+ .load(R.drawable.anim_pause_to_play)
+ .into(exoPlay)
+ }
+ }
+ })
+ }
+
+ private fun startExoPlayer() {
+ exoPlayer.setMediaItem(mediaItem)
+ exoPlayer.prepare()
+ playerView.player = exoPlayer
+ castPlayer.stop()
+ }
+
+ override fun onCastSessionAvailable() {
+ startCastPlayer()
+ }
+
+ override fun onCastSessionUnavailable() {
+ startExoPlayer()
+ }
+
+
@SuppressLint("ViewConstructor")
class ExtendedTimeBar(
context: Context,
diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt
index d42bcd94..e90abecb 100644
--- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt
@@ -20,6 +20,7 @@ import ani.dantotsu.*
import ani.dantotsu.databinding.BottomSheetSelectorBinding
import ani.dantotsu.databinding.ItemStreamBinding
import ani.dantotsu.databinding.ItemUrlBinding
+import ani.dantotsu.download.video.Helper
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.others.Download.download
@@ -214,7 +215,8 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
val extractor = links[position]
- holder.binding.streamName.text = extractor.server.name
+ holder.binding.streamName.text = ""//extractor.server.name
+ holder.binding.streamName.visibility = View.GONE
holder.binding.streamRecyclerView.layoutManager = LinearLayoutManager(requireContext())
holder.binding.streamRecyclerView.adapter = VideoAdapter(extractor)
@@ -256,10 +258,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
val binding = holder.binding
val video = extractor.videos[position]
- binding.urlQuality.text =
- if (video.quality != null) "${video.quality}p" else "Default Quality"
- binding.urlNote.text = video.extraNote ?: ""
- binding.urlNote.visibility = if (video.extraNote != null) View.VISIBLE else View.GONE
+ //binding.urlQuality.text =
+ // if (video.quality != null) "${video.quality}p" else "Default Quality"
+ //binding.urlNote.text = video.extraNote ?: ""
+ //binding.urlNote.visibility = if (video.extraNote != null) View.VISIBLE else View.GONE
binding.urlDownload.visibility = View.VISIBLE
binding.urlDownload.setSafeOnClickListener {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor =
@@ -267,11 +269,24 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
position
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
- download(
- requireActivity(),
- media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!,
- media!!.userPreferredName
- )
+ //download(
+ // requireActivity(),
+ // media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!,
+ // media!!.userPreferredName
+ //)
+ val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
+ val video = if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
+ if (video != null) {
+ Helper.startAnimeDownloadService(
+ requireActivity(),
+ media!!.userPreferredName,
+ episode.number,
+ video,
+ null,
+ media,
+ episode.thumb?.url?: media!!.banner?: media!!.cover
+ )
+ }
dismiss()
}
if (video.format == VideoType.CONTAINER) {
@@ -282,11 +297,13 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
"#.##"
).format(video.size ?: 0).toString() + " MB"))
} else {
- binding.urlQuality.text = "Multi Quality"
if ((loadData("settings_download_manager") ?: 0) == 0) {
- binding.urlDownload.visibility = View.GONE
+ ////binding.urlDownload.visibility = View.GONE
}
}
+ binding.urlNote.visibility = View.VISIBLE
+ binding.urlNote.text = video.format.name
+ binding.urlQuality.text = extractor.server.name
}
override fun getItemCount(): Int = extractor.videos.size
diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt
index 342dfe20..9237c6b9 100644
--- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt
@@ -2,12 +2,14 @@ package ani.dantotsu.media.manga
import android.annotation.SuppressLint
import android.app.AlertDialog
+import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.ArrayAdapter
import android.widget.CheckBox
+import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.NumberPicker
@@ -15,12 +17,14 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
+import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.SourceSearchDialogFragment
import ani.dantotsu.media.anime.handleProgress
+import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.MangaReadSources
import ani.dantotsu.parsers.MangaSources
@@ -30,6 +34,7 @@ import com.google.android.material.chip.Chip
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
+
class MangaReadAdapter(
private val media: Media,
private val fragment: MangaReadFragment,
@@ -46,7 +51,7 @@ class MangaReadAdapter(
val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(bind)
}
-
+ private var nestedDialog: AlertDialog? = null
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding
@@ -117,7 +122,7 @@ class MangaReadAdapter(
}
}
- //Subscription
+ //Grids
subscribe = MediaDetailsActivity.PopImageButton(
fragment.lifecycleScope,
binding.animeSourceSubscribe,
@@ -136,98 +141,130 @@ class MangaReadAdapter(
openSettings(fragment.requireContext(), getChannelId(true, media.id))
}
- //Icons
- binding.animeSourceGrid.visibility = View.GONE
- var reversed = media.selected!!.recyclerReversed
- var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.mangaDefaultView
- binding.animeSourceTop.rotation = if (reversed) -90f else 90f
- binding.animeSourceTop.setOnClickListener {
- reversed = !reversed
- binding.animeSourceTop.rotation = if (reversed) -90f else 90f
- fragment.onIconPressed(style, reversed)
- }
+ binding.animeNestedButton.setOnClickListener {
- binding.animeScanlatorTop.setOnClickListener {
val dialogView =
- LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
- val checkboxContainer = dialogView.findViewById(R.id.checkboxContainer)
+ LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
+ val dialogBinding = DialogLayoutBinding.bind(dialogView)
- // Dynamically add checkboxes
-
- options.forEach { option ->
- val checkBox = CheckBox(currContext()).apply {
- text = option
- }
- //set checked if it's already selected
- if (media.selected!!.scanlators != null) {
- checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
- scanlatorSelectionListener?.onScanlatorsSelected()
- } else {
- checkBox.isChecked = true
- }
- checkboxContainer.addView(checkBox)
+ var run = false
+ var reversed = media.selected!!.recyclerReversed
+ var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
+ dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
+ dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
+ dialogBinding.animeSourceTop.setOnClickListener {
+ reversed = !reversed
+ dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f
+ dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down"
+ run = true
}
- // Create AlertDialog
- val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
- .setView(dialogView)
- .setPositiveButton("OK") { dialog, which ->
- //add unchecked to hidden
- hiddenScanlators.clear()
- for (i in 0 until checkboxContainer.childCount) {
- val checkBox = checkboxContainer.getChildAt(i) as CheckBox
- if (!checkBox.isChecked) {
- hiddenScanlators.add(checkBox.text.toString())
- }
- }
- fragment.onScanlatorChange(hiddenScanlators)
- scanlatorSelectionListener?.onScanlatorsSelected()
- }
- .setNegativeButton("Cancel", null)
- .show()
- dialog.window?.setDimAmount(0.8f)
- }
-
- binding.animeDownloadTop.setOnClickListener {
- //Alert dialog asking for the number of chapters to download
- val alertDialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
- alertDialog.setTitle("Multi Chapter Downloader")
- alertDialog.setMessage("Enter the number of chapters to download")
- val input = NumberPicker(currContext())
- input.minValue = 1
- input.maxValue = 20
- input.value = 1
- alertDialog.setView(input)
- alertDialog.setPositiveButton("OK") { dialog, which ->
- fragment.multiDownload(input.value)
+ //Grids
+ dialogBinding.animeSourceGrid.visibility = View.GONE
+ var selected = when (style) {
+ 0 -> dialogBinding.animeSourceList
+ 1 -> dialogBinding.animeSourceCompact
+ else -> dialogBinding.animeSourceList
+ }
+ when (style) {
+ 0 -> dialogBinding.layoutText.text = "List"
+ 1 -> dialogBinding.layoutText.text = "Compact"
+ else -> dialogBinding.animeSourceList
}
- alertDialog.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
- val dialog = alertDialog.show()
- dialog.window?.setDimAmount(0.8f)
- }
-
- var selected = when (style) {
- 0 -> binding.animeSourceList
- 1 -> binding.animeSourceCompact
- else -> binding.animeSourceList
- }
- selected.alpha = 1f
- fun selected(it: ImageView) {
- selected.alpha = 0.33f
- selected = it
selected.alpha = 1f
- }
- binding.animeSourceList.setOnClickListener {
- selected(it as ImageView)
- style = 0
- fragment.onIconPressed(style, reversed)
- }
- binding.animeSourceCompact.setOnClickListener {
- selected(it as ImageView)
- style = 1
- fragment.onIconPressed(style, reversed)
- }
+ fun selected(it: ImageButton) {
+ selected.alpha = 0.33f
+ selected = it
+ selected.alpha = 1f
+ }
+ dialogBinding.animeSourceList.setOnClickListener {
+ selected(it as ImageButton)
+ style = 0
+ dialogBinding.layoutText.text = "List"
+ run = true
+ }
+ dialogBinding.animeSourceCompact.setOnClickListener {
+ selected(it as ImageButton)
+ style = 1
+ dialogBinding.layoutText.text = "Compact"
+ run = true
+ }
+ //Multi download
+ dialogBinding.downloadNo.text = "0"
+ dialogBinding.animeDownloadTop.setOnClickListener {
+ //Alert dialog asking for the number of chapters to download
+ val alertDialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
+ alertDialog.setTitle("Multi Chapter Downloader")
+ alertDialog.setMessage("Enter the number of chapters to download")
+ val input = NumberPicker(currContext())
+ input.minValue = 1
+ input.maxValue = 20
+ input.value = 1
+ alertDialog.setView(input)
+ alertDialog.setPositiveButton("OK") { dialog, which ->
+ dialogBinding.downloadNo.text = "${input.value}"
+ }
+ alertDialog.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
+ val dialog = alertDialog.show()
+ dialog.window?.setDimAmount(0.8f)
+ }
+
+ //Scanlator
+ dialogBinding.animeScanlatorTop.setOnClickListener {
+ val dialogView2 =
+ LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
+ val checkboxContainer = dialogView2.findViewById(R.id.checkboxContainer)
+
+ // Dynamically add checkboxes
+ options.forEach { option ->
+ val checkBox = CheckBox(currContext()).apply {
+ text = option
+ }
+ //set checked if it's already selected
+ if (media.selected!!.scanlators != null) {
+ checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
+ scanlatorSelectionListener?.onScanlatorsSelected()
+ } else {
+ checkBox.isChecked = true
+ }
+ checkboxContainer.addView(checkBox)
+ }
+
+ // Create AlertDialog
+ val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup)
+ .setView(dialogView2)
+ .setPositiveButton("OK") { dialog, which ->
+ //add unchecked to hidden
+ hiddenScanlators.clear()
+ for (i in 0 until checkboxContainer.childCount) {
+ val checkBox = checkboxContainer.getChildAt(i) as CheckBox
+ if (!checkBox.isChecked) {
+ hiddenScanlators.add(checkBox.text.toString())
+ }
+ }
+ fragment.onScanlatorChange(hiddenScanlators)
+ scanlatorSelectionListener?.onScanlatorsSelected()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ dialog.window?.setDimAmount(0.8f)
+ }
+
+ nestedDialog = AlertDialog.Builder(fragment.requireContext() , R.style.MyPopup)
+ .setTitle("Options")
+ .setView(dialogView)
+ .setPositiveButton("OK") { _, _ ->
+ if(run) fragment.onIconPressed(style, reversed)
+ if (dialogBinding.downloadNo.text != "0"){
+ fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt())
+ }
+ }
+ .setNegativeButton("Cancel") { _, _ ->
+ }
+ .create()
+ nestedDialog?.show()
+ }
//Chapter Handling
handleChapters()
}
@@ -385,12 +422,15 @@ class MangaReadAdapter(
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
)
}
- binding?.animeSourceLanguage?.setAdapter(
- ArrayAdapter(
- fragment.requireContext(),
- R.layout.item_dropdown,
- parser.extension.sources.map { it.lang })
+ val adapter = ArrayAdapter(
+ fragment.requireContext(),
+ R.layout.item_dropdown,
+ parser.extension.sources.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
)
+ val items = adapter.count
+ if (items > 1) binding?.animeSourceLanguageContainer?.visibility = View.VISIBLE else binding?.animeSourceLanguageContainer?.visibility = View.GONE
+
+ binding?.animeSourceLanguage?.setAdapter(adapter)
}
}
diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt
index 25a87c9b..51d1c806 100644
--- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt
@@ -29,7 +29,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.*
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
-import ani.dantotsu.download.Download
+import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.manga.MangaDownloaderService
import ani.dantotsu.download.manga.MangaServiceDataSingleton
@@ -37,6 +37,7 @@ import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
+import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaParser
@@ -166,7 +167,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
chapterAdapter =
MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
- for (download in downloadManager.mangaDownloads) {
+ for (download in downloadManager.mangaDownloadedTypes) {
chapterAdapter.stopDownload(download.chapter)
}
@@ -196,26 +197,28 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
override fun onScanlatorsSelected() {
updateChapters()
}
-
fun multiDownload(n: Int) {
//get last viewed chapter
val selected = media.userProgress
val chapters = media.manga?.chapters?.values?.toList()
//filter by selected language
- val progressChapterIndex = chapters?.indexOfFirst { MangaNameAdapter.findChapterNumber(it.number)?.toInt() == selected }?:0
- if (progressChapterIndex < 0 || n < 1) return
- val chaptersToDownload = chapters?.subList(
- progressChapterIndex + 1,
- progressChapterIndex + n + 1
- )
- if (chaptersToDownload != null) {
- for (chapter in chaptersToDownload) {
- onMangaChapterDownloadClick(chapter.title!!)
- }
- }
+ val progressChapterIndex = (chapters?.indexOfFirst { MangaNameAdapter.findChapterNumber(it.number)?.toInt() == selected } ?: 0) + 1
+ if (progressChapterIndex < 0 || n < 1 || chapters == null) return
+
+ // Calculate the end index
+ val endIndex = minOf(progressChapterIndex + n, chapters.size)
+
+ //make sure there are enough chapters
+ val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
+
+
+ for (chapter in chaptersToDownload) {
+ onMangaChapterDownloadClick(chapter.title!!)
+ }
}
+
private fun updateChapters() {
val loadedChapters = model.getMangaChapters().value
if (loadedChapters != null) {
@@ -355,19 +358,19 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
if (show) View.GONE else View.VISIBLE
}
}
+ var itemSelected = false
val allSettings = pkg.sources.filterIsInstance()
if (allSettings.isNotEmpty()) {
var selectedSetting = allSettings[0]
if (allSettings.size > 1) {
- val names = allSettings.map { it.lang }.toTypedArray()
+ val names = allSettings.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
- val dialog = AlertDialog.Builder(requireContext())
+ val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
.setTitle("Select a Source")
- .setSingleChoiceItems(names, selectedIndex) { _, which ->
+ .setSingleChoiceItems(names, selectedIndex) { dialog, which ->
selectedIndex = which
- }
- .setPositiveButton("OK") { dialog, _ ->
selectedSetting = allSettings[selectedIndex]
+ itemSelected = true
dialog.dismiss()
// Move the fragment transaction here
@@ -382,10 +385,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
.addToBackStack(null)
.commit()
}
- .setNegativeButton("Cancel") { dialog, _ ->
- dialog.cancel()
- changeUIVisibility(true)
- return@setNegativeButton
+ .setOnDismissListener {
+ if (!itemSelected) {
+ changeUIVisibility(true)
+ }
}
.show()
dialog.window?.setDimAmount(0.8f)
@@ -481,10 +484,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
fun onMangaChapterRemoveDownloadClick(i: String) {
downloadManager.removeDownload(
- Download(
+ DownloadedType(
media.nameMAL ?: media.nameRomaji,
i,
- Download.Type.MANGA
+ DownloadedType.Type.MANGA
)
)
chapterAdapter.deleteDownload(i)
@@ -499,10 +502,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
// Remove the download from the manager and update the UI
downloadManager.removeDownload(
- Download(
+ DownloadedType(
media.nameMAL ?: media.nameRomaji,
i,
- Download.Type.MANGA
+ DownloadedType.Type.MANGA
)
)
chapterAdapter.purgeDownload(i)
diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt
index 6a18b9e8..d1732b5a 100644
--- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt
@@ -89,7 +89,7 @@ abstract class BaseImageAdapter(
}
} else {
val detector = GestureDetectorCompat(view.context, object : GesturesListener() {
- override fun onSingleClick(event: MotionEvent) = activity.handleController()
+ override fun onSingleClick(event: MotionEvent) = activity.handleController(event = event)
})
view.findViewById(R.id.imgProgCover).apply {
setOnTouchListener { _, event ->
@@ -112,6 +112,9 @@ abstract class BaseImageAdapter(
activity.lifecycleScope.launch { loadImage(holder.bindingAdapterPosition, view) }
}
+ abstract fun isZoomed(): Boolean
+ abstract fun setZoom(zoom: Float)
+
abstract suspend fun loadImage(position: Int, parent: View): Boolean
companion object {
diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt
index 97651499..45ae40f0 100644
--- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ImageAdapter.kt
@@ -91,4 +91,14 @@ open class ImageAdapter(
}
override fun getItemCount(): Int = images.size
+
+ override fun isZoomed(): Boolean {
+ val imageView = activity.findViewById(R.id.imgProgImageNoGestures)
+ return imageView.scale > imageView.minScale
+ }
+
+ override fun setZoom(zoom: Float) {
+ val imageView = activity.findViewById(R.id.imgProgImageNoGestures)
+ imageView.setScaleAndCenter(zoom, imageView.center)
+ }
}
diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt
index d6d72871..067ee56f 100644
--- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt
+++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt
@@ -6,6 +6,7 @@ import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
+import android.content.res.Resources
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
@@ -702,8 +703,60 @@ class MangaReaderActivity : AppCompatActivity() {
goneTimer.schedule(timerTask, controllerDuration)
}
- fun handleController(shouldShow: Boolean? = null) {
+ enum class pressPos {
+ LEFT, RIGHT, CENTER
+ }
+
+ fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
+ var pressLocation = pressPos.CENTER
if (!sliding) {
+ if (event != null && settings.default.layout == PAGED) {
+ if (event.action != MotionEvent.ACTION_UP) return
+ val x = event.rawX.toInt()
+ val y = event.rawY.toInt()
+ val screenWidth = Resources.getSystem().displayMetrics.widthPixels
+ //if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left
+ if (screenWidth / 5 in (x + 1).. screenWidth - screenWidth / 5 && y > screenWidth / 5) {
+ pressLocation = if (settings.default.direction == RIGHT_TO_LEFT) {
+ pressPos.LEFT
+ } else {
+ pressPos.RIGHT
+ }
+ }
+ }
+
+ // if pressLocation is left or right go to previous or next page (paged mode only)
+ if (pressLocation == pressPos.LEFT) {
+
+ if (binding.mangaReaderPager.currentItem > 0) {
+ //if the current images zoomed in, go back to normal before going to previous page
+ if (imageAdapter?.isZoomed() == true) {
+ imageAdapter?.setZoom(1f)
+ }
+ binding.mangaReaderPager.currentItem -= 1
+ return
+ }
+
+ } else if (pressLocation == pressPos.RIGHT) {
+ if (binding.mangaReaderPager.currentItem < maxChapterPage - 1) {
+ //if the current images zoomed in, go back to normal before going to next page
+ if (imageAdapter?.isZoomed() == true) {
+ imageAdapter?.setZoom(1f)
+ }
+ //if right to left, go to previous page
+ binding.mangaReaderPager.currentItem += 1
+ return
+ }
+ }
+
if (!settings.showSystemBars) {
hideBars()
checkNotch()
@@ -796,7 +849,7 @@ class MangaReaderActivity : AppCompatActivity() {
private fun progress(runnable: Runnable) {
if (maxChapterPage - currentChapterPage <= 1 && Anilist.userid != null) {
- if (showProgressDialog) {
+ if (showProgressDialog) {
val dialogView = layoutInflater.inflate(R.layout.item_custom_dialog, null)
val checkbox = dialogView.findViewById(R.id.dialog_checkbox)
@@ -805,8 +858,9 @@ class MangaReaderActivity : AppCompatActivity() {
saveData("${media.id}_progressDialog", isChecked)
showProgressDialog = !isChecked
}
- val incognito = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
- ?.getBoolean("incognito", false) ?: false
+ val incognito =
+ currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
+ ?.getBoolean("incognito", false) ?: false
AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.title_update_progress))
.apply {
@@ -818,7 +872,7 @@ class MangaReaderActivity : AppCompatActivity() {
.setCancelable(false)
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
saveData("${media.id}_save_progress", true)
- updateProgress(
+ updateProgress(
media,
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
.toString()
diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt
index 7154412a..b3e14ed2 100644
--- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt
@@ -22,7 +22,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
-import ani.dantotsu.download.Download
+import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.novel.NovelDownloaderService
import ani.dantotsu.download.novel.NovelServiceDataSingleton
@@ -92,10 +92,10 @@ class NovelReadFragment : Fragment(),
override fun downloadedCheckWithStart(novel: ShowResponse): Boolean {
val downloadsManager = Injekt.get()
if (downloadsManager.queryDownload(
- Download(
+ DownloadedType(
media.nameMAL ?: media.nameRomaji,
novel.name,
- Download.Type.NOVEL
+ DownloadedType.Type.NOVEL
)
)
) {
@@ -124,10 +124,10 @@ class NovelReadFragment : Fragment(),
override fun downloadedCheck(novel: ShowResponse): Boolean {
val downloadsManager = Injekt.get()
return downloadsManager.queryDownload(
- Download(
+ DownloadedType(
media.nameMAL ?: media.nameRomaji,
novel.name,
- Download.Type.NOVEL
+ DownloadedType.Type.NOVEL
)
)
}
@@ -135,10 +135,10 @@ class NovelReadFragment : Fragment(),
override fun deleteDownload(novel: ShowResponse) {
val downloadsManager = Injekt.get()
downloadsManager.removeDownload(
- Download(
+ DownloadedType(
media.nameMAL ?: media.nameRomaji,
novel.name,
- Download.Type.NOVEL
+ DownloadedType.Type.NOVEL
)
)
}
diff --git a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt
index 069fef28..f76e6ff5 100644
--- a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt
+++ b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderActivity.kt
@@ -36,7 +36,7 @@ import ani.dantotsu.saveData
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.CurrentNovelReaderSettings
import ani.dantotsu.settings.CurrentReaderSettings
-import ani.dantotsu.settings.NovelReaderSettings
+import ani.dantotsu.settings.ReaderSettings
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager
@@ -62,7 +62,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
private lateinit var binding: ActivityNovelReaderBinding
private val scope = lifecycleScope
- lateinit var settings: NovelReaderSettings
+ lateinit var settings: ReaderSettings
private lateinit var uiSettings: UserInterfaceSettings
private var notchHeight: Int? = null
@@ -159,9 +159,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
ThemeManager(this).applyTheme()
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
setContentView(binding.root)
-
- settings = loadData("novel_reader_settings", this)
- ?: NovelReaderSettings().apply { saveData("novel_reader_settings", this) }
+ settings = loadData("reader_settings", this)
+ ?: ReaderSettings().apply { saveData("reader_settings", this) }
uiSettings = loadData("ui_settings", this)
?: UserInterfaceSettings().also { saveData("ui_settings", it) }
@@ -271,7 +270,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
binding.bookReader.getAppearance {
currentTheme = it
themes.add(0, it)
- settings.default = loadData("${sanitizedBookId}_current_settings") ?: settings.default
+ settings.defaultLN = loadData("${sanitizedBookId}_current_settings") ?: settings.defaultLN
applySettings()
}
@@ -323,7 +322,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_PAGE_UP -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP)
- if (!settings.default.volumeButtons)
+ if (!settings.defaultLN.volumeButtons)
return false
if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeUp?.invoke()
@@ -333,7 +332,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_PAGE_DOWN -> {
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
- if (!settings.default.volumeButtons)
+ if (!settings.defaultLN.volumeButtons)
return false
if (event.action == KeyEvent.ACTION_DOWN) {
onVolumeDown?.invoke()
@@ -349,13 +348,18 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
fun applySettings() {
- saveData("${sanitizedBookId}_current_settings", settings.default)
+ saveData("${sanitizedBookId}_current_settings", settings.defaultLN)
hideBars()
+ if(settings.defaultLN.useOledTheme) {
+ themes.forEach { theme ->
+ theme.darkBg = Color.parseColor("#000000")
+ }
+ }
currentTheme =
- themes.first { it.name.equals(settings.default.currentThemeName, ignoreCase = true) }
+ themes.first { it.name.equals(settings.defaultLN.currentThemeName, ignoreCase = true) }
- when (settings.default.layout) {
+ when (settings.defaultLN.layout) {
CurrentNovelReaderSettings.Layouts.PAGED -> {
currentTheme?.flow = ReaderFlow.PAGINATED
}
@@ -366,22 +370,22 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
}
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
- when (settings.default.dualPageMode) {
+ when (settings.defaultLN.dualPageMode) {
CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1
CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2
CurrentReaderSettings.DualPageModes.Force -> requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
- currentTheme?.lineHeight = settings.default.lineHeight
- currentTheme?.gap = settings.default.margin
- currentTheme?.maxInlineSize = settings.default.maxInlineSize
- currentTheme?.maxBlockSize = settings.default.maxBlockSize
- currentTheme?.useDark = settings.default.useDarkTheme
+ currentTheme?.lineHeight = settings.defaultLN.lineHeight
+ currentTheme?.gap = settings.defaultLN.margin
+ currentTheme?.maxInlineSize = settings.defaultLN.maxInlineSize
+ currentTheme?.maxBlockSize = settings.defaultLN.maxBlockSize
+ currentTheme?.useDark = settings.defaultLN.useDarkTheme
currentTheme?.let { binding.bookReader.setAppearance(it) }
- if (settings.default.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ if (settings.defaultLN.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
diff --git a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt
index 27a9caaa..760c7eb3 100644
--- a/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/novel/novelreader/NovelReaderSettingsDialogFragment.kt
@@ -30,8 +30,7 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as NovelReaderActivity
- val settings = activity.settings.default
-
+ val settings = activity.settings.defaultLN
val themeLabels = activity.themes.map { it.name }
binding.themeSelect.adapter =
NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels)
@@ -49,7 +48,11 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
-
+ binding.useOledTheme.isChecked = settings.useOledTheme
+ binding.useOledTheme.setOnCheckedChangeListener { _, isChecked ->
+ settings.useOledTheme = isChecked
+ activity.applySettings()
+ }
val layoutList = listOf(
binding.paged,
binding.continuous
@@ -173,6 +176,20 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
binding.maxBlockSize.setText(value.toString())
activity.applySettings()
}
+
+ }
+ binding.incrementMaxBlockSize.setOnClickListener {
+ val value = binding.maxBlockSize.text.toString().toIntOrNull() ?: 720
+ settings.maxBlockSize = value + 10
+ binding.maxBlockSize.setText(settings.maxBlockSize.toString())
+ activity.applySettings()
+ }
+
+ binding.decrementMaxBlockSize.setOnClickListener {
+ val value = binding.maxBlockSize.text.toString().toIntOrNull() ?: 720
+ settings.maxBlockSize = value - 10
+ binding.maxBlockSize.setText(settings.maxBlockSize.toString())
+ activity.applySettings()
}
binding.useDarkTheme.isChecked = settings.useDarkTheme
diff --git a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt
index 342a5a8f..87930156 100644
--- a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt
+++ b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt
@@ -1,6 +1,7 @@
package ani.dantotsu.media.user
import android.annotation.SuppressLint
+import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.View
@@ -14,9 +15,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R
import ani.dantotsu.Refresh
+import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityListBinding
import ani.dantotsu.loadData
import ani.dantotsu.others.LangSet
+import ani.dantotsu.saveData
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.themes.ThemeManager
import com.google.android.material.tabs.TabLayout
@@ -62,7 +65,7 @@ class ListActivity : AppCompatActivity() {
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
val uiSettings = loadData("ui_settings") ?: UserInterfaceSettings()
- if (!uiSettings.immersiveModeList) {
+ if (!uiSettings.immersiveMode) {
this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv)
binding.root.fitsSystemWindows = true
@@ -78,8 +81,7 @@ class ListActivity : AppCompatActivity() {
setContentView(binding.root)
val anime = intent.getBooleanExtra("anime", true)
- binding.listTitle.text =
- intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
+ binding.listTitle.text = (if (anime) "Anime" else "Manga") + " List"
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
@@ -145,7 +147,8 @@ class ListActivity : AppCompatActivity() {
R.id.release -> "release"
else -> null
}
-
+ currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
+ ?.putString("sort_order", sort)?.apply()
binding.listProgressBar.visibility = View.VISIBLE
binding.listViewPager.adapter = null
scope.launch {
diff --git a/app/src/main/java/ani/dantotsu/media/user/ListFragment.kt b/app/src/main/java/ani/dantotsu/media/user/ListFragment.kt
index 3eccf001..48573e50 100644
--- a/app/src/main/java/ani/dantotsu/media/user/ListFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/user/ListFragment.kt
@@ -46,7 +46,7 @@ class ListFragment : Fragment() {
binding.listRecyclerView.layoutManager =
GridLayoutManager(
requireContext(),
- if (grid!!) (screenWidth / 124f).toInt() else 1
+ if (grid!!) (screenWidth / 120f).toInt() else 1
)
binding.listRecyclerView.adapter = adapter
}
diff --git a/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt b/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt
index 25f8f6a4..3d9c439b 100644
--- a/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt
+++ b/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt
@@ -6,25 +6,116 @@ class LanguageMapper {
fun mapLanguageCodeToName(code: String): String {
return when (code) {
"all" -> "Multi"
+ "af" -> "Afrikaans"
+ "am" -> "Amharic"
"ar" -> "Arabic"
+ "as" -> "Assamese"
+ "az" -> "Azerbaijani"
+ "be" -> "Belarusian"
+ "bg" -> "Bulgarian"
+ "bn" -> "Bengali"
+ "bs" -> "Bosnian"
+ "ca" -> "Catalan"
+ "ceb" -> "Cebuano"
+ "cs" -> "Czech"
+ "da" -> "Danish"
"de" -> "German"
+ "el" -> "Greek"
"en" -> "English"
+ "en-Us" -> "English (United States)"
+ "eo" -> "Esperanto"
"es" -> "Spanish"
+ "es-419" -> "Spanish (Latin America)"
+ "et" -> "Estonian"
+ "eu" -> "Basque"
+ "fa" -> "Persian"
+ "fi" -> "Finnish"
+ "fil" -> "Filipino"
+ "fo" -> "Faroese"
"fr" -> "French"
+ "ga" -> "Irish"
+ "gn" -> "Guarani"
+ "gu" -> "Gujarati"
+ "ha" -> "Hausa"
+ "he" -> "Hebrew"
+ "hi" -> "Hindi"
+ "hr" -> "Croatian"
+ "ht" -> "Haitian Creole"
+ "hu" -> "Hungarian"
+ "hy" -> "Armenian"
"id" -> "Indonesian"
+ "ig" -> "Igbo"
+ "is" -> "Icelandic"
"it" -> "Italian"
"ja" -> "Japanese"
+ "jv" -> "Javanese"
+ "ka" -> "Georgian"
+ "kk" -> "Kazakh"
+ "km" -> "Khmer"
+ "kn" -> "Kannada"
"ko" -> "Korean"
+ "ku" -> "Kurdish"
+ "ky" -> "Kyrgyz"
+ "la" -> "Latin"
+ "lb" -> "Luxembourgish"
+ "lo" -> "Lao"
+ "lt" -> "Lithuanian"
+ "lv" -> "Latvian"
+ "mg" -> "Malagasy"
+ "mi" -> "Maori"
+ "mk" -> "Macedonian"
+ "ml" -> "Malayalam"
+ "mn" -> "Mongolian"
+ "mo" -> "Moldovan"
+ "mr" -> "Marathi"
+ "ms" -> "Malay"
+ "mt" -> "Maltese"
+ "my" -> "Burmese"
+ "ne" -> "Nepali"
+ "nl" -> "Dutch"
+ "no" -> "Norwegian"
+ "ny" -> "Chichewa"
"pl" -> "Polish"
+ "pt" -> "Portuguese"
"pt-BR" -> "Portuguese (Brazil)"
+ "pt-PT" -> "Portuguese (Portugal)"
+ "ps" -> "Pashto"
+ "ro" -> "Romanian"
+ "rm" -> "Romansh"
"ru" -> "Russian"
+ "sd" -> "Sindhi"
+ "sh" -> "Serbo-Croatian"
+ "si" -> "Sinhala"
+ "sk" -> "Slovak"
+ "sl" -> "Slovenian"
+ "sm" -> "Samoan"
+ "sn" -> "Shona"
+ "so" -> "Somali"
+ "sq" -> "Albanian"
+ "sr" -> "Serbian"
+ "st" -> "Southern Sotho"
+ "sv" -> "Swedish"
+ "sw" -> "Swahili"
+ "ta" -> "Tamil"
+ "te" -> "Telugu"
+ "tg" -> "Tajik"
"th" -> "Thai"
+ "ti" -> "Tigrinya"
+ "tk" -> "Turkmen"
+ "tl" -> "Tagalog"
+ "to" -> "Tongan"
"tr" -> "Turkish"
"uk" -> "Ukrainian"
+ "ur" -> "Urdu"
+ "uz" -> "Uzbek"
"vi" -> "Vietnamese"
+ "yo" -> "Yoruba"
"zh" -> "Chinese"
"zh-Hans" -> "Chinese (Simplified)"
- else -> ""
+ "zh-Hant" -> "Chinese (Traditional)"
+ "zh-Habt" -> "Chinese (Hakka)"
+ "zu" -> "Zulu"
+ else -> code
}
}
diff --git a/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt b/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt
index 1e38fc0e..75be1465 100644
--- a/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt
+++ b/app/src/main/java/ani/dantotsu/others/imagesearch/ImageSearchActivity.kt
@@ -14,6 +14,7 @@ import ani.dantotsu.App.Companion.context
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.ActivityImageSearchBinding
+import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.others.LangSet
import ani.dantotsu.themes.ThemeManager
@@ -49,6 +50,7 @@ class ImageSearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LangSet.setLocale(this)
+ initActivity(this)
ThemeManager(this).applyTheme()
binding = ActivityImageSearchBinding.inflate(layoutInflater)
setContentView(binding.root)
diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt
index c48a4b3e..dd3faaef 100644
--- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt
@@ -12,11 +12,17 @@ object AnimeSources : WatchSources() {
suspend fun init(fromExtensions: StateFlow>) {
// Initialize with the first value from StateFlow
val initialExtensions = fromExtensions.first()
- list = createParsersFromExtensions(initialExtensions)
+ list = createParsersFromExtensions(initialExtensions) + Lazier(
+ { OfflineAnimeParser() },
+ "Downloaded"
+ )
// Update as StateFlow emits new values
fromExtensions.collect { extensions ->
- list = createParsersFromExtensions(extensions)
+ list = createParsersFromExtensions(extensions) + Lazier(
+ { OfflineAnimeParser() },
+ "Downloaded"
+ )
}
}
diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
index 03b40919..08c69b55 100644
--- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
@@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
+import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@@ -41,6 +42,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import okhttp3.Request
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -112,7 +114,14 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
seasonGroups.keys.sorted().flatMap { season ->
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
- episode.episode_number = episodeCounter++
+ val potentialNumber =
+ AnimeNameAdapter.findEpisodeNumber(episode.name)
+ if (potentialNumber != null) {
+ episode.episode_number = potentialNumber
+ } else {
+ episode.episode_number = episodeCounter
+ }
+ episodeCounter++
}
episode
} ?: emptyList()
@@ -202,7 +211,11 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
}
return Episode(
if (episodeNumberInt.toInt() != -1) {
- episodeNumberInt.toString()
+ if (sEpisode.episode_number % 1 == 0f) {
+ episodeNumberInt.toInt().toString()
+ } else {
+ sEpisode.episode_number.toString()
+ }
} else {
sEpisode.name
},
@@ -603,13 +616,18 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
val fileName = queryPairs.find { it.first == "file" }?.second ?: ""
format = getVideoType(fileName)
+ // this solves a problem no one has, so I'm commenting it out for now
+ //if (format == null) {
+ // val networkHelper = Injekt.get()
+ // format = headRequest(videoUrl, networkHelper)
+ //}
}
- // If the format is still undetermined, log an error or handle it appropriately
+ // If the format is still undetermined, log an error
if (format == null) {
logger("Unknown video format: $videoUrl")
- FirebaseCrashlytics.getInstance()
- .recordException(Exception("Unknown video format: $videoUrl"))
+ //FirebaseCrashlytics.getInstance()
+ // .recordException(Exception("Unknown video format: $videoUrl"))
format = VideoType.CONTAINER
}
val headersMap: Map =
@@ -620,12 +638,12 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
number,
format,
FileUrl(videoUrl, headersMap),
- aniVideo.totalContentLength.toDouble()
+ if (aniVideo.totalContentLength == 0L) null else aniVideo.bytesDownloaded.toDouble()
)
}
private fun getVideoType(fileName: String): VideoType? {
- return when {
+ val type = when {
fileName.endsWith(".mp4", ignoreCase = true) || fileName.endsWith(
".mkv",
ignoreCase = true
@@ -635,6 +653,47 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
fileName.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
else -> null
}
+
+ return type
+ }
+
+ private fun headRequest(fileName: String, networkHelper: NetworkHelper): VideoType? {
+ return try {
+ logger("attempting head request for $fileName")
+ val request = Request.Builder()
+ .url(fileName)
+ .head()
+ .build()
+
+ networkHelper.client.newCall(request).execute().use { response ->
+ val contentType = response.header("Content-Type")
+ val contentDisposition = response.header("Content-Disposition")
+
+ if (contentType != null) {
+ when {
+ contentType.contains("mpegurl", ignoreCase = true) -> VideoType.M3U8
+ contentType.contains("dash", ignoreCase = true) -> VideoType.DASH
+ contentType.contains("mp4", ignoreCase = true) -> VideoType.CONTAINER
+ else -> null
+ }
+ } else if (contentDisposition != null) {
+ when {
+ contentDisposition.contains("mpegurl", ignoreCase = true) -> VideoType.M3U8
+ contentDisposition.contains("dash", ignoreCase = true) -> VideoType.DASH
+ contentDisposition.contains("mp4", ignoreCase = true) -> VideoType.CONTAINER
+ else -> null
+ }
+ } else {
+ logger("failed head request for $fileName")
+ null
+ }
+
+ }
+ } catch (e: Exception) {
+ logger("Exception in headRequest: $e")
+ null
+ }
+
}
private fun TrackToSubtitle(track: Track): Subtitle {
diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
index 03562f68..ba6057eb 100644
--- a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
@@ -156,9 +156,9 @@ abstract class BaseParser {
}
fun checkIfVariablesAreEmpty() {
- if (hostUrl.isEmpty()) throw UninitializedPropertyAccessException("Please provide a `hostUrl` for the Parser")
- if (name.isEmpty()) throw UninitializedPropertyAccessException("Please provide a `name` for the Parser")
- if (saveName.isEmpty()) throw UninitializedPropertyAccessException("Please provide a `saveName` for the Parser")
+ if (hostUrl.isEmpty()) throw UninitializedPropertyAccessException("Cannot find any installed extensions")
+ if (name.isEmpty()) throw UninitializedPropertyAccessException("Cannot find any installed extensions")
+ if (saveName.isEmpty()) throw UninitializedPropertyAccessException("Cannot find any installed extensions")
}
open var showUserText = ""
diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt
index f8535772..95fd4d63 100644
--- a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt
@@ -46,6 +46,19 @@ abstract class WatchSources : BaseSources() {
sEpisode = it.sEpisode
)
}
+ } else if (parser is OfflineAnimeParser) {
+ parser.loadEpisodes(showLink, extra, SAnime.create()).forEach {
+ map[it.number] = Episode(
+ it.number,
+ it.link,
+ it.title,
+ it.description,
+ it.thumbnail,
+ it.isFiller,
+ extra = it.extra,
+ sEpisode = it.sEpisode
+ )
+ }
}
}
return map
diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt
index 0f8a5642..3709ddaa 100644
--- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt
@@ -7,9 +7,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
object MangaSources : MangaReadSources() {
- // Instantiate the static parser
- private val offlineMangaParser by lazy { OfflineMangaParser() }
-
override var list: List> = emptyList()
suspend fun init(fromExtensions: StateFlow>) {
diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt
new file mode 100644
index 00000000..dca2199e
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt
@@ -0,0 +1,106 @@
+package ani.dantotsu.parsers
+
+import android.os.Environment
+import ani.dantotsu.currContext
+import ani.dantotsu.download.DownloadsManager
+import ani.dantotsu.logger
+import ani.dantotsu.media.anime.AnimeNameAdapter
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
+import me.xdrop.fuzzywuzzy.FuzzySearch
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.io.File
+
+class OfflineAnimeParser : AnimeParser() {
+ private val downloadManager = Injekt.get()
+
+ override val name = "Offline"
+ override val saveName = "Offline"
+ override val hostUrl = "Offline"
+ override val isDubAvailableSeparately = false
+ override val isNSFW = false
+
+ override suspend fun loadEpisodes(
+ animeLink: String,
+ extra: Map?,
+ sAnime: SAnime
+ ): List {
+ val directory = File(
+ currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
+ "${DownloadsManager.animeLocation}/$animeLink"
+ )
+ //get all of the folder names and add them to the list
+ val episodes = mutableListOf()
+ if (directory.exists()) {
+ directory.listFiles()?.forEach {
+ if (it.isDirectory) {
+ val episode = Episode(
+ it.name,
+ "$animeLink - ${it.name}",
+ it.name,
+ null,
+ null,
+ sEpisode = SEpisodeImpl()
+ )
+ episodes.add(episode)
+ }
+ }
+ episodes.sortBy { AnimeNameAdapter.findEpisodeNumber(it.number) }
+ return episodes
+ }
+ return emptyList()
+ }
+
+ override suspend fun loadVideoServers(
+ episodeLink: String,
+ extra: Map?,
+ sEpisode: SEpisode
+ ): List {
+ return listOf(
+ VideoServer(
+ episodeLink,
+ offline = true
+ )
+ )
+ }
+
+
+ override suspend fun search(query: String): List {
+ val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
+ val returnTitles: MutableList = mutableListOf()
+ for (title in titles) {
+ if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
+ returnTitles.add(title)
+ }
+ }
+ val returnList: MutableList = mutableListOf()
+ for (title in returnTitles) {
+ returnList.add(ShowResponse(title, title, title))
+ }
+ return returnList
+ }
+
+ override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor {
+ return OfflineVideoExtractor(server)
+ }
+
+}
+
+class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
+ override val server: VideoServer
+ get() = videoServer
+
+ override suspend fun extract(): VideoContainer {
+ val sublist = emptyList()
+ //we need to return a "fake" video so that the app doesn't crash
+ val video = Video(
+ null,
+ VideoType.CONTAINER,
+ "",
+ )
+ return VideoContainer(listOf(video), sublist)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt
index 218a0f9a..deffb420 100644
--- a/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt
@@ -76,7 +76,7 @@ class OfflineMangaParser : MangaParser() {
}
override suspend fun search(query: String): List {
- val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
+ val titles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
val returnTitles: MutableList = mutableListOf()
for (title in titles) {
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt
index ca47b50a..fece57e2 100644
--- a/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt
@@ -3,10 +3,7 @@ package ani.dantotsu.parsers
import android.os.Environment
import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadsManager
-import ani.dantotsu.logger
import ani.dantotsu.media.manga.MangaNameAdapter
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
import me.xdrop.fuzzywuzzy.FuzzySearch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -53,7 +50,7 @@ class OfflineNovelParser: NovelParser() {
}
override suspend fun search(query: String): List {
- val titles = downloadManager.novelDownloads.map { it.title }.distinct()
+ val titles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
val returnTitles: MutableList = mutableListOf()
for (title in titles) {
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
diff --git a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt
index ce56182c..c98b8a42 100644
--- a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt
@@ -57,11 +57,15 @@ data class VideoServer(
val name: String,
val embed: FileUrl,
val extraData: Map? = null,
- val video: eu.kanade.tachiyomi.animesource.model.Video? = null
+ val video: eu.kanade.tachiyomi.animesource.model.Video? = null,
+ val offline: Boolean = false
) : Serializable {
constructor(name: String, embedUrl: String, extraData: Map? = null)
: this(name, FileUrl(embedUrl), extraData)
+ constructor(name: String, offline: Boolean)
+ : this(name, FileUrl(""), null, null, offline)
+
constructor(
name: String,
embedUrl: String,
diff --git a/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt
index bbe3fcd3..b1cd429b 100644
--- a/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt
+++ b/app/src/main/java/ani/dantotsu/settings/CurrentNovelReaderSettings.kt
@@ -11,6 +11,7 @@ data class CurrentNovelReaderSettings(
var justify: Boolean = true,
var hyphenation: Boolean = true,
var useDarkTheme: Boolean = false,
+ var useOledTheme: Boolean = false,
var invert: Boolean = false,
var maxInlineSize: Int = 720,
var maxBlockSize: Int = 1440,
diff --git a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt
index c03ba9e1..0f8efc34 100644
--- a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt
@@ -61,11 +61,6 @@ class FAQActivity : AppCompatActivity() {
currContext()!!.getString(R.string.question_7),
currContext()!!.getString(R.string.answer_7)
),
- Triple(
- R.drawable.ic_round_menu_book_24,
- currContext()!!.getString(R.string.question_8),
- currContext()!!.getString(R.string.answer_8)
- ),
Triple(
R.drawable.ic_round_lock_open_24,
currContext()!!.getString(R.string.question_9),
diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt
index bdbcb7ca..d921b687 100644
--- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt
+++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt
@@ -1,5 +1,6 @@
package ani.dantotsu.settings
+import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.NotificationManager
import android.content.Context
@@ -49,74 +50,66 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
private val extensionsAdapter = AnimeExtensionsAdapter(
{ pkg ->
+ val name= pkg.name
+ val changeUIVisibility: (Boolean) -> Unit = { show ->
+ val activity = requireActivity() as ExtensionsActivity
+ val visibility = if (show) View.VISIBLE else View.GONE
+ activity.findViewById(R.id.viewPager).visibility = visibility
+ activity.findViewById(R.id.tabLayout).visibility = visibility
+ activity.findViewById(R.id.searchView).visibility = visibility
+ activity.findViewById(R.id.languageselect).visibility = visibility
+ activity.findViewById(R.id.extensions).text = if (show) getString(R.string.extensions) else name
+ activity.findViewById(R.id.fragmentExtensionsContainer).visibility =
+ if (show) View.GONE else View.VISIBLE
+ }
+ var itemSelected = false
val allSettings = pkg.sources.filterIsInstance()
if (allSettings.isNotEmpty()) {
var selectedSetting = allSettings[0]
if (allSettings.size > 1) {
- val names = allSettings.map { it.lang }.toTypedArray()
+ val names = allSettings.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
.setTitle("Select a Source")
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
+ itemSelected = true
selectedIndex = which
selectedSetting = allSettings[selectedIndex]
dialog.dismiss()
- // Move the fragment transaction here
- val eActivity = requireActivity() as ExtensionsActivity
- eActivity.runOnUiThread {
- val fragment =
- AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
-
- eActivity.findViewById(R.id.viewPager).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.tabLayout).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.searchView).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.fragmentExtensionsContainer).visibility =
- View.GONE
- }
- parentFragmentManager.beginTransaction()
- .setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
- .replace(R.id.fragmentExtensionsContainer, fragment)
- .addToBackStack(null)
- .commit()
+ val fragment =
+ AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
+ changeUIVisibility(true)
+ }
+ parentFragmentManager.beginTransaction()
+ .setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
+ .replace(R.id.fragmentExtensionsContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ }
+ .setOnDismissListener {
+ if (!itemSelected) {
+ changeUIVisibility(true)
}
}
.show()
dialog.window?.setDimAmount(0.8f)
} else {
// If there's only one setting, proceed with the fragment transaction
- val eActivity = requireActivity() as ExtensionsActivity
- eActivity.runOnUiThread {
- val fragment =
- AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
+ val fragment =
+ AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
+ changeUIVisibility(true)
+ }
+ parentFragmentManager.beginTransaction()
+ .setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
+ .replace(R.id.fragmentExtensionsContainer, fragment)
+ .addToBackStack(null)
+ .commit()
- eActivity.findViewById(R.id.viewPager).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.tabLayout).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.searchView).visibility =
- View.VISIBLE
- eActivity.findViewById(R.id.fragmentExtensionsContainer).visibility =
- View.GONE
- }
- parentFragmentManager.beginTransaction()
- .setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
- .replace(R.id.fragmentExtensionsContainer, fragment)
- .addToBackStack(null)
- .commit()
- }
}
// Hide ViewPager2 and TabLayout
- val activity = requireActivity() as ExtensionsActivity
- activity.findViewById(R.id.viewPager).visibility = View.GONE
- activity.findViewById(R.id.tabLayout).visibility = View.GONE
- activity.findViewById(R.id.searchView).visibility = View.GONE
- activity.findViewById(R.id.fragmentExtensionsContainer).visibility =
- View.VISIBLE
+ changeUIVisibility(false)
} else {
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
.show()
@@ -225,6 +218,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
return ViewHolder(view)
}
+ @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = getItem(position) // Use getItem() from ListAdapter
val nsfw = if (extension.isNsfw) "(18+)" else ""
diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt
index ecef2e58..79db1197 100644
--- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt
+++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt
@@ -47,24 +47,29 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
val skipIcons = loadData("skip_extension_icons") ?: false
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
+ val name= pkg.name
val changeUIVisibility: (Boolean) -> Unit = { show ->
val activity = requireActivity() as ExtensionsActivity
val visibility = if (show) View.VISIBLE else View.GONE
activity.findViewById(R.id.viewPager).visibility = visibility
activity.findViewById(R.id.tabLayout).visibility = visibility
activity.findViewById(R.id.searchView).visibility = visibility
+ activity.findViewById(R.id.languageselect).visibility = visibility
+ activity.findViewById(R.id.extensions).text = if (show) getString(R.string.extensions) else name
activity.findViewById(R.id.fragmentExtensionsContainer).visibility =
if (show) View.GONE else View.VISIBLE
}
+ var itemSelected = false
val allSettings = pkg.sources.filterIsInstance()
if (allSettings.isNotEmpty()) {
var selectedSetting = allSettings[0]
if (allSettings.size > 1) {
- val names = allSettings.map { it.lang }.toTypedArray()
+ val names = allSettings.sortedBy { it.lang }.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray()
var selectedIndex = 0
val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup)
.setTitle("Select a Source")
.setSingleChoiceItems(names, selectedIndex) { dialog, which ->
+ itemSelected = true
selectedIndex = which
selectedSetting = allSettings[selectedIndex]
dialog.dismiss()
@@ -80,6 +85,11 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
.addToBackStack(null)
.commit()
}
+ .setOnDismissListener {
+ if (!itemSelected) {
+ changeUIVisibility(true)
+ }
+ }
.show()
dialog.window?.setDimAmount(0.8f)
} else {
diff --git a/app/src/main/java/ani/dantotsu/settings/NovelReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/NovelReaderSettings.kt
deleted file mode 100644
index 7c72c7e9..00000000
--- a/app/src/main/java/ani/dantotsu/settings/NovelReaderSettings.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package ani.dantotsu.settings
-
-import java.io.Serializable
-
-data class NovelReaderSettings(
- var showSource: Boolean = true,
- var showSystemBars: Boolean = false,
- var default: CurrentNovelReaderSettings = CurrentNovelReaderSettings(),
- var askIndividual: Boolean = true,
-) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt
index 1589d66d..3a373cc1 100644
--- a/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt
+++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettings.kt
@@ -45,6 +45,6 @@ data class PlayerSettings(
var skipTime: Int = 85,
//Other
- var cast: Boolean = false,
+ var cast: Boolean = true,
var pip: Boolean = true
) : Serializable
diff --git a/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt b/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt
index 8a249dd7..e0a91af8 100644
--- a/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt
+++ b/app/src/main/java/ani/dantotsu/settings/ReaderSettings.kt
@@ -8,6 +8,7 @@ data class ReaderSettings(
var autoDetectWebtoon: Boolean = true,
var default: CurrentReaderSettings = CurrentReaderSettings(),
+ var defaultLN: CurrentNovelReaderSettings = CurrentNovelReaderSettings(),
var askIndividual: Boolean = true,
var updateForH: Boolean = false
diff --git a/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt
index 61c87afe..67bd04d7 100644
--- a/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/ReaderSettingsActivity.kt
@@ -1,13 +1,17 @@
package ani.dantotsu.settings
import android.os.Bundle
+import android.view.View
import android.view.ViewGroup
+import android.widget.AdapterView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
+import ani.dantotsu.NoPaddingArrayAdapter
import ani.dantotsu.R
import ani.dantotsu.databinding.ActivityReaderSettingsBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
+import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LangSet
import ani.dantotsu.saveData
@@ -42,7 +46,7 @@ class ReaderSettingsActivity : AppCompatActivity() {
onBackPressedDispatcher.onBackPressed()
}
- //General
+ //Manga Settings
binding.readerSettingsSourceName.isChecked = settings.showSource
binding.readerSettingsSourceName.setOnCheckedChangeListener { _, isChecked ->
settings.showSource = isChecked
@@ -54,14 +58,14 @@ class ReaderSettingsActivity : AppCompatActivity() {
settings.showSystemBars = isChecked
saveData(reader, settings)
}
-
+ //Default Manga
binding.readerSettingsAutoWebToon.isChecked = settings.autoDetectWebtoon
binding.readerSettingsAutoWebToon.setOnCheckedChangeListener { _, isChecked ->
settings.autoDetectWebtoon = isChecked
saveData(reader, settings)
}
- //Default
+
val layoutList = listOf(
binding.readerSettingsPaged,
binding.readerSettingsContinuousPaged,
@@ -185,6 +189,169 @@ class ReaderSettingsActivity : AppCompatActivity() {
saveData(reader, settings)
}
+ //LN settings
+ val layoutListLN = listOf(
+ binding.LNpaged,
+ binding.LNcontinuous
+ )
+
+ binding.LNlayoutText.text = settings.defaultLN.layout.string
+ var selectedLN = layoutListLN[settings.defaultLN.layout.ordinal]
+ selectedLN.alpha = 1f
+
+ layoutListLN.forEachIndexed { index, imageButton ->
+ imageButton.setOnClickListener {
+ selectedLN.alpha = 0.33f
+ selectedLN = imageButton
+ selectedLN.alpha = 1f
+ settings.defaultLN.layout = CurrentNovelReaderSettings.Layouts[index]
+ ?: CurrentNovelReaderSettings.Layouts.PAGED
+ binding.LNlayoutText.text = settings.defaultLN.layout.string
+ saveData(reader, settings)
+ }
+ }
+
+ val dualListLN = listOf(
+ binding.LNdualNo,
+ binding.LNdualAuto,
+ binding.LNdualForce
+ )
+
+ binding.LNdualPageText.text = settings.defaultLN.dualPageMode.toString()
+ var selectedDualLN = dualListLN[settings.defaultLN.dualPageMode.ordinal]
+ selectedDualLN.alpha = 1f
+
+ dualListLN.forEachIndexed { index, imageButton ->
+ imageButton.setOnClickListener {
+ selectedDualLN.alpha = 0.33f
+ selectedDualLN = imageButton
+ selectedDualLN.alpha = 1f
+ settings.defaultLN.dualPageMode = CurrentReaderSettings.DualPageModes[index]
+ ?: CurrentReaderSettings.DualPageModes.Automatic
+ binding.LNdualPageText.text = settings.defaultLN.dualPageMode.toString()
+ saveData(reader, settings)
+ }
+ }
+
+ binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString())
+ binding.LNlineHeight.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f
+ settings.defaultLN.lineHeight = value
+ binding.LNlineHeight.setText(value.toString())
+ saveData(reader, settings)
+ }
+ }
+
+ binding.LNincrementLineHeight.setOnClickListener {
+ val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f
+ settings.defaultLN.lineHeight = value + 0.1f
+ binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNdecrementLineHeight.setOnClickListener {
+ val value = binding.LNlineHeight.text.toString().toFloatOrNull() ?: 1.4f
+ settings.defaultLN.lineHeight = value - 0.1f
+ binding.LNlineHeight.setText(settings.defaultLN.lineHeight.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNmargin.setText(settings.defaultLN.margin.toString())
+ binding.LNmargin.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f
+ settings.defaultLN.margin = value
+ binding.LNmargin.setText(value.toString())
+ saveData(reader, settings)
+ }
+ }
+
+ binding.LNincrementMargin.setOnClickListener {
+ val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f
+ settings.defaultLN.margin = value + 0.01f
+ binding.LNmargin.setText(settings.defaultLN.margin.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNdecrementMargin.setOnClickListener {
+ val value = binding.LNmargin.text.toString().toFloatOrNull() ?: 0.06f
+ settings.defaultLN.margin = value - 0.01f
+ binding.LNmargin.setText(settings.defaultLN.margin.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString())
+ binding.LNmaxInlineSize.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxInlineSize = value
+ binding.LNmaxInlineSize.setText(value.toString())
+ saveData(reader, settings)
+ }
+ }
+
+ binding.LNincrementMaxInlineSize.setOnClickListener {
+ val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxInlineSize = value + 10
+ binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNdecrementMaxInlineSize.setOnClickListener {
+ val value = binding.LNmaxInlineSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxInlineSize = value - 10
+ binding.LNmaxInlineSize.setText(settings.defaultLN.maxInlineSize.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNmaxBlockSize.setText(settings.defaultLN.maxBlockSize.toString())
+ binding.LNmaxBlockSize.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxBlockSize = value
+ binding.LNmaxBlockSize.setText(value.toString())
+ saveData(reader, settings)
+ }
+ }
+ binding.LNincrementMaxBlockSize.setOnClickListener {
+ val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxInlineSize = value + 10
+ binding.LNmaxBlockSize.setText(settings.defaultLN.maxInlineSize.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNdecrementMaxBlockSize.setOnClickListener {
+ val value = binding.LNmaxBlockSize.text.toString().toIntOrNull() ?: 720
+ settings.defaultLN.maxBlockSize = value - 10
+ binding.LNmaxBlockSize.setText(settings.defaultLN.maxBlockSize.toString())
+ saveData(reader, settings)
+ }
+
+ binding.LNuseDarkTheme.isChecked = settings.defaultLN.useDarkTheme
+ binding.LNuseDarkTheme.setOnCheckedChangeListener { _, isChecked ->
+ settings.defaultLN.useDarkTheme = isChecked
+ saveData(reader, settings)
+ }
+
+ binding.LNuseOledTheme.isChecked = settings.defaultLN.useOledTheme
+ binding.LNuseOledTheme.setOnCheckedChangeListener { _, isChecked ->
+ settings.defaultLN.useOledTheme = isChecked
+ saveData(reader, settings)
+ }
+
+ binding.LNkeepScreenOn.isChecked = settings.defaultLN.keepScreenOn
+ binding.LNkeepScreenOn.setOnCheckedChangeListener { _, isChecked ->
+ settings.defaultLN.keepScreenOn = isChecked
+ saveData(reader, settings)
+ }
+
+ binding.LNvolumeButton.isChecked = settings.defaultLN.volumeButtons
+ binding.LNvolumeButton.setOnCheckedChangeListener { _, isChecked ->
+ settings.defaultLN.volumeButtons = isChecked
+ saveData(reader, settings)
+ }
+
//Update Progress
binding.readerSettingsAskUpdateProgress.isChecked = settings.askIndividual
binding.readerSettingsAskUpdateProgress.setOnCheckedChangeListener { _, isChecked ->
diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt
index 2d2a2049..be6e61fd 100644
--- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt
@@ -178,13 +178,18 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListen
binding.customTheme.setOnClickListener {
- val originalColor = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt(
+ val originalColor = getSharedPreferences("Dantotsu", MODE_PRIVATE).getInt(
"custom_theme_int",
Color.parseColor("#6200EE")
)
+ class CustomColorDialog : SimpleColorDialog() { //idk where to put it
+ override fun onPositiveButtonClick() {
+ restartApp()
+ super.onPositiveButtonClick()
+ }
+ }
val tag = "colorPicker"
- SimpleColorDialog.build()
- .title("Custom Theme")
+ CustomColorDialog().title("Custom Theme")
.colorPreset(originalColor)
.colors(this, SimpleColorDialog.BEIGE_COLOR_PALLET)
.allowCustom(true)
@@ -251,9 +256,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListen
binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked ->
saveData("skip_extension_icons", isChecked)
}
- binding.NSFWExtension.isChecked = loadData("NFSWExtension") ?: true
+ binding.NSFWExtension.isChecked = loadData("NSFWExtension") ?: true
binding.NSFWExtension.setOnCheckedChangeListener { _, isChecked ->
- saveData("NFSWExtension", isChecked)
+ saveData("NSFWExtension", isChecked)
}
@@ -400,6 +405,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListen
uiTheme(true, it)
}
+
var previousStart: View = when (uiSettings.defaultStartUpTab) {
0 -> binding.uiSettingsAnime
1 -> binding.uiSettingsHome
@@ -416,6 +422,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListen
initActivity(this)
}
+
binding.uiSettingsAnime.setOnClickListener {
uiTheme(0, it)
}
@@ -782,4 +789,4 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListen
show()
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt
index 15fd20a8..39dd18b1 100644
--- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt
+++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettings.kt
@@ -10,7 +10,6 @@ data class UserInterfaceSettings(
//App
var immersiveMode: Boolean = false,
- var immersiveModeList: Boolean = false,
var smallView: Boolean = true,
var defaultStartUpTab: Int = 1,
var homeLayoutShow: MutableList = mutableListOf(
diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt
index 189d691f..fb49c496 100644
--- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt
@@ -68,13 +68,6 @@ class UserInterfaceSettingsActivity : AppCompatActivity() {
saveData(ui, settings)
restartApp()
}
- binding.uiSettingsImmersiveList.isChecked = settings.immersiveModeList
- binding.uiSettingsImmersiveList.setOnCheckedChangeListener { _, isChecked ->
- settings.immersiveModeList = isChecked
- saveData(ui, settings)
- restartApp()
- }
-
binding.uiSettingsBannerAnimation.isChecked = settings.bannerAnimations
binding.uiSettingsBannerAnimation.setOnCheckedChangeListener { _, isChecked ->
settings.bannerAnimations = isChecked
diff --git a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt
index 045ca140..5ead2138 100644
--- a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt
+++ b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt
@@ -1,5 +1,6 @@
package ani.dantotsu.settings.paging
+import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
@@ -91,18 +92,14 @@ class AnimeExtensionPagingSource(
val availableExtensions =
availableExtensionsFlow.filterNot { it.pkgName in installedExtensions }
val query = searchQuery
- val isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: true
+ val isNsfwEnabled: Boolean = loadData("NSFWExtension") ?: true
val filteredExtensions = if (query.isEmpty()) {
availableExtensions
} else {
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
}
- val filternfsw = if (isNsfwEnabled) {
- filteredExtensions
- } else {
- filteredExtensions.filterNot { it.isNsfw }
- }
+ val filternfsw = if (isNsfwEnabled) filteredExtensions else filteredExtensions.filterNot { it.isNsfw }
return try {
val sublist = filternfsw.subList(
fromIndex = position,
@@ -198,6 +195,7 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen
val extensionIconImageView: ImageView = binding.extensionIconImageView
+ @SuppressLint("SetTextI18n")
fun bind(extension: AnimeExtension.Available) {
val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
diff --git a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt
index 0ae7e01d..eed5bccc 100644
--- a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt
+++ b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt
@@ -1,5 +1,6 @@
package ani.dantotsu.settings.paging
+import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
@@ -91,17 +92,13 @@ class MangaExtensionPagingSource(
val availableExtensions =
availableExtensionsFlow.filterNot { it.pkgName in installedExtensions }
val query = searchQuery
- val isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: true
+ val isNsfwEnabled: Boolean = loadData("NSFWExtension") ?: true
val filteredExtensions = if (query.isEmpty()) {
availableExtensions
} else {
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
}
- val filternfsw = if (isNsfwEnabled) {
- filteredExtensions
- } else {
- filteredExtensions.filterNot { it.isNsfw }
- }
+ val filternfsw = if (isNsfwEnabled) filteredExtensions else filteredExtensions.filterNot { it.isNsfw }
return try {
val sublist = filternfsw.subList(
fromIndex = position,
@@ -194,6 +191,7 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen
}
val extensionIconImageView: ImageView = binding.extensionIconImageView
+ @SuppressLint("SetTextI18n")
fun bind(extension: MangaExtension.Available) {
val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
diff --git a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt b/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt
index c28b5a49..83b3bd70 100644
--- a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt
+++ b/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt
@@ -87,9 +87,7 @@ class Subscription {
progress(index[it.first]!!, parser.name, media.name)
val ep: MangaChapter? =
SubscriptionHelper.getChapter(context, parser, media.id, media.isAdult)
- if (ep != null) currActivity()!!.getString(R.string.chapter) + "${ep.number}${
- if (ep.title != null) " : ${ep.title}" else ""
- } " + currActivity()!!.getString(R.string.just_released) to null
+ if (ep != null) ep.number + " " + currActivity()!!.getString(R.string.just_released) to null
else null
} ?: return@map
createNotification(context.applicationContext, media, text.first, text.second)
diff --git a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt
index 2029177b..8ee3f62d 100644
--- a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt
+++ b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt
@@ -42,14 +42,15 @@ class ThemeManager(private val context: Context) {
.getString("theme", "PURPLE")!!
val themeToApply = when (theme) {
- "PURPLE" -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
"BLUE" -> if (useOLED) R.style.Theme_Dantotsu_BlueOLED else R.style.Theme_Dantotsu_Blue
"GREEN" -> if (useOLED) R.style.Theme_Dantotsu_GreenOLED else R.style.Theme_Dantotsu_Green
+ "PURPLE" -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
"PINK" -> if (useOLED) R.style.Theme_Dantotsu_PinkOLED else R.style.Theme_Dantotsu_Pink
+ "SAIKOU" -> if (useOLED) R.style.Theme_Dantotsu_SaikouOLED else R.style.Theme_Dantotsu_Saikou
"RED" -> if (useOLED) R.style.Theme_Dantotsu_RedOLED else R.style.Theme_Dantotsu_Red
"LAVENDER" -> if (useOLED) R.style.Theme_Dantotsu_LavenderOLED else R.style.Theme_Dantotsu_Lavender
+ "EMERALD" -> if (useOLED) R.style.Theme_Dantotsu_EmeraldOLED else R.style.Theme_Dantotsu_Emerald
"MONOCHROME (BETA)" -> if (useOLED) R.style.Theme_Dantotsu_MonochromeOLED else R.style.Theme_Dantotsu_Monochrome
- "SAIKOU" -> if (useOLED) R.style.Theme_Dantotsu_SaikouOLED else R.style.Theme_Dantotsu_Saikou
else -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
}
@@ -109,14 +110,15 @@ class ThemeManager(private val context: Context) {
companion object {
enum class Theme(val theme: String) {
- PURPLE("PURPLE"),
BLUE("BLUE"),
GREEN("GREEN"),
+ PURPLE("PURPLE"),
PINK("PINK"),
+ SAIKOU("SAIKOU"),
RED("RED"),
LAVENDER("LAVENDER"),
- MONOCHROME("MONOCHROME (BETA)"),
- SAIKOU("SAIKOU");
+ EMERALD("EMERALD"),
+ MONOCHROME("MONOCHROME (BETA)");
companion object {
fun fromString(value: String): Theme {
diff --git a/app/src/main/res/drawable-v24/ic_banner_foreground.xml b/app/src/main/res/drawable-v24/ic_banner_foreground.xml
deleted file mode 100644
index e26ad38f..00000000
--- a/app/src/main/res/drawable-v24/ic_banner_foreground.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bottom_nav.xml b/app/src/main/res/drawable/bottom_nav.xml
index ba236777..b42dd3ce 100644
--- a/app/src/main/res/drawable/bottom_nav.xml
+++ b/app/src/main/res/drawable/bottom_nav.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_round_filter_24.xml b/app/src/main/res/drawable/ic_round_filter_24.xml
new file mode 100644
index 00000000..c5b85484
--- /dev/null
+++ b/app/src/main/res/drawable/ic_round_filter_24.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/monochrome.xml b/app/src/main/res/drawable/monochrome.xml
index 7b1f2b2d..6fd3cc32 100644
--- a/app/src/main/res/drawable/monochrome.xml
+++ b/app/src/main/res/drawable/monochrome.xml
@@ -1,16 +1,16 @@
+
+ android:pathData="M44.26,128C44.26,173.48 80.53,210.4 125.71,211.63L125.71,128.01L642.29,128.01L642.29,639.97L768,639.97L768,128L44.26,128zM642.29,639.97L125.71,639.97L125.71,639.99L642.29,639.99L642.29,639.97zM125.71,639.97L125.71,556.38C80.54,557.6 44.28,594.5 44.26,639.97L125.71,639.97zM125.71,556.38C126.48,556.35 127.23,556.26 128,556.26L384,556.26C479.14,556.26 556.26,479.13 556.26,384C556.26,288.86 479.13,211.74 384,211.74L128,211.74C127.23,211.74 126.48,211.65 125.71,211.63L125.71,286.18L384,286.18C438.02,286.18 481.82,329.98 481.82,384C481.82,438.03 438.02,481.82 384,481.82L125.71,481.82L125.71,556.38zM125.71,481.82L125.71,286.18L0,286.18L0,481.82L125.71,481.82z"/>
-
+ android:pathData="m442,366.7l-76.02,-43.89c-13.32,-7.69 -29.96,1.92 -29.96,17.3v87.78c0,15.38 16.65,24.99 29.96,17.3l76.02,-43.89c13.32,-7.69 13.32,-26.91 0,-34.6Z"/>
diff --git a/app/src/main/res/layout/activity_extensions.xml b/app/src/main/res/layout/activity_extensions.xml
index 85a8db87..728236ba 100644
--- a/app/src/main/res/layout/activity_extensions.xml
+++ b/app/src/main/res/layout/activity_extensions.xml
@@ -81,23 +81,6 @@
app:tabPaddingStart="16dp"
app:tabTextAppearance="@style/NavBarText"
app:tabGravity="fill">
-
-
-
-
+ android:layout_height="match_parent"
+ android:paddingTop="32dp">
diff --git a/app/src/main/res/layout/activity_reader_settings.xml b/app/src/main/res/layout/activity_reader_settings.xml
index 0600341a..e8886066 100644
--- a/app/src/main/res/layout/activity_reader_settings.xml
+++ b/app/src/main/res/layout/activity_reader_settings.xml
@@ -73,6 +73,1119 @@
android:clipToPadding="false"
android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -130,553 +1244,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/bottom_sheet_current_novel_reader_settings.xml b/app/src/main/res/layout/bottom_sheet_current_novel_reader_settings.xml
index 5d746238..993a70e1 100644
--- a/app/src/main/res/layout/bottom_sheet_current_novel_reader_settings.xml
+++ b/app/src/main/res/layout/bottom_sheet_current_novel_reader_settings.xml
@@ -512,6 +512,25 @@
app:thumbTint="@color/button_switch_track"
tools:ignore="VisualLintButtonSize" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/exo_player_control_view.xml b/app/src/main/res/layout/exo_player_control_view.xml
index eb4d99ea..ceef7edc 100644
--- a/app/src/main/res/layout/exo_player_control_view.xml
+++ b/app/src/main/res/layout/exo_player_control_view.xml
@@ -174,12 +174,11 @@
app:srcCompat="@drawable/ic_round_screen_rotation_alt_24"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_manga_offline.xml b/app/src/main/res/layout/fragment_manga_offline.xml
index 995953ea..f829fe32 100644
--- a/app/src/main/res/layout/fragment_manga_offline.xml
+++ b/app/src/main/res/layout/fragment_manga_offline.xml
@@ -35,6 +35,7 @@
android:hint="@string/manga"
android:textColorHint="@color/bg_opp"
android:transitionName="@string/search"
+ android:fontFamily="@font/poppins_bold"
app:boxBackgroundColor="?attr/colorPrimaryContainer"
app:boxCornerRadiusBottomEnd="28dp"
app:boxCornerRadiusBottomStart="28dp"
@@ -80,7 +81,43 @@
+
+
+
+
+
+
+
+
-
+
+ android:paddingStart="25dp"
+ android:paddingEnd="25dp"
+ android:gravity="center"
+ android:scrollbars="none"
+ android:visibility="gone"/>
+
+
diff --git a/app/src/main/res/layout/item_anime_page.xml b/app/src/main/res/layout/item_anime_page.xml
index 7e3b52ba..0657b349 100644
--- a/app/src/main/res/layout/item_anime_page.xml
+++ b/app/src/main/res/layout/item_anime_page.xml
@@ -8,13 +8,6 @@
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:orientation="vertical">
-
@@ -106,7 +107,8 @@
-
@@ -185,19 +178,26 @@
-
+ android:orientation="horizontal">
+
+
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_image.xml b/app/src/main/res/layout/item_image.xml
index 64055928..72a66e12 100644
--- a/app/src/main/res/layout/item_image.xml
+++ b/app/src/main/res/layout/item_image.xml
@@ -7,7 +7,7 @@
app:gest_disableGestures="true"
app:gest_rotationEnabled="true"
app:gest_restrictRotation="true"
- app:gest_doubleTapZoom="3"
+ app:gest_doubleTapZoom="1.5"
app:gest_maxZoom="6">
-
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_navbar.xml b/app/src/main/res/layout/item_navbar.xml
index 417d4e10..0117ac70 100644
--- a/app/src/main/res/layout/item_navbar.xml
+++ b/app/src/main/res/layout/item_navbar.xml
@@ -15,9 +15,8 @@
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
android:background="@drawable/bottom_nav"
- android:elevation="4dp"
- android:padding="8dp"
- android:translationZ="12dp"
+ android:elevation="8dp"
+ android:padding="6dp"
android:visibility="gone"
app:abb_animationDuration="300"
app:abb_animationInterpolator="@anim/over_shoot"
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
index a0a0dece..cf3108b3 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner.png b/app/src/main/res/mipmap-xhdpi/ic_banner.png
index 8f8c3cad..89d36cbd 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_banner.png and b/app/src/main/res/mipmap-xhdpi/ic_banner.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png
new file mode 100644
index 00000000..1bcf6a7a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png differ
diff --git a/app/src/main/res/values-en-rDW/strings.xml b/app/src/main/res/values-en-rDW/strings.xml
deleted file mode 100644
index e50ccd3a..00000000
--- a/app/src/main/res/values-en-rDW/strings.xml
+++ /dev/null
@@ -1,647 +0,0 @@
-
-
- rebelonion/Dantotsu
- dantotsuprefs
-
- Dantotsu
- The NEW Best Anime & Manga app for Android.
-
- Login
- Logout
-
- https://discord.gg/4HPZ5nAWwM
- https://github.com/rebelonion/Dantotsu
-
- Home
- Anime
- Browse Anime
- Manga
- Browse Manga
- Info
- Watch
- Read
-
- Anime List
- Manga List
-
- X(
- :o
- " : "
-
- No Internet Connection
- Refresh
-
- Search
- Search Results
- Sort By
- Genre
- Top Score
- Recently Updated
- Trending Anime
- Popular Anime
- Trending Manga
- Trending Novel
- Popular Manga
-
- Username
- "Chapters Read "
- "Episodes Watched "
- Continue Reading
- Continue Watching
- Recommended
-
- Watch/Read some Anime or Manga to get Recommendations
- All caught up, when New?
- Settings
-
- Add to List
- List Editor
- Add to Favourites
- Notifications
-
- - Reading
- - Watching
- - Completed
- - Paused
- - Dropped
- - Planning
- - Favourites
- - Rewatching
- - Rereading
- - All
-
-
- STATUS
-
- - PLANNING
- - CURRENT
- - COMPLETED
- - REPEATING
- - PAUSED
- - DROPPED
-
-
- - PLANNING
- - WATCHING
- - COMPLETED
- - RE-WATCHING
- - PAUSED
- - DROPPED
-
-
- - PLANNING
- - READING
- - COMPLETED
- - RE-READING
- - PAUSED
- - DROPPED
-
- PROGRESS
- SCORE
- " / 10"
- STARTED AT
- COMPLETED AT
- Save
- Delete
- Remove
-
- Name
- Name Romaji
- Mean Score
- Format
- Status
- Total Episodes
- Total Chapters
- Average Duration
- " min"
- Season
- Start Date
- End Date
- Source
- Studio
- Genres
- Synopsis
- Characters
- Relations
-
- Roles
- Details
-
- Play on Youtube
- Episodes
- Episode
- Chapters
- Chapter
- Wrong Title?
-
- Couldn\'t find anything X( \n
- Try another source.
-
- %1$s is not supported!
- Select Server
- Auto Selecting Server
- Make Default
- Filler
- Adult
- List Only
- Tag
- Tags
- Synonyms
- Trailer
- Opening
- Ending
- Prequel
- Sequel
-
- Anilist Settings
- Extensions
- Downloads
- Settings
- Extensions
- Player Settings
- Only show My Shows in Recently Updated
- Download Manager
- Download in SD card
- No SD card was Found.
- Reader Settings
- Default Source
- Show Youtube Link
- Default Episode Layout
- Default Chapter Layout
- User Interface
- Common
- Theme
- UI Settings
- About
- " Dantotsu is crafted from the ashes of Saikou and based on simplistic yet state-of-the-art elegance. It is an Anilist only client, which also lets you stream-download Anime through extensions & Manga.\nDantotsu literally means the \"best of the best\" in japanese. Well, we would like to say this is the best open source app for anime and manga on Android, what would you say?"
- Developers
- Disclaimer
-
- - Dantotsu by itself only provides an anime and manga tracker and does not provide any anime or manga streaming or downloading capabilities.
- \n\n - Dantotsu or any of its developer/staff don\'t host any of the content found inside Dantotsu. Any and all images and anime/manga information found in the app are taken from various public APIs (AniList, MyAnimeList, Kitsu).
- \n\n - Furthermore, all of the anime/manga links found in Dantotsu are taken from various 3rd party plugins and have no affiliation with Dantotsu or its staff.
- \n\n - Dantotsu or it\'s owners aren\'t liable for any misuse of any of the contents found inside or outside of the app and cannot be held accountable for the distribution of any of the contents found inside the app.
- \n\n - By using Dantotsu, you comply to the fact that the developer of the app is not responsible for any of the contents found in the app. You also agree to the fact that you may not use Dantotsu to download or stream any copyrighted content.
- \n\n - If the internet infringement issues are involved, please contact the source website. The developer does not assume any legal responsibility.
-
- Version %1$s
-
- - You can Long Click an episode/chapter to Mark it as Read.
- - Long Clicking Shows can directly open List Editor.
- - There are few more easter eggs hidden in the App.
- - Challenge: Go to the very bottom of the Popular Anime & Manga
- - Try Long Clicking the Show\'s Title.
- - Damn, why are you wasting your Time?
- - You can Long Click to copy this Message.
- - OMG LOOK! YOU FOUND AN EASTER EGG!?
- - You know who else likes this Animation?
- - MAL support? bruh.
- - Novels? more like NO vels.
- - Long Click the logo to check for App Update
-
-
-
- - Default
- - Theme 1
- - Theme 2
- - Theme 3
- - Theme 4
-
-
- Video
- Show Video Info
- Show Source Name
- Shows what the Resolution of the current video playing, useful for \"Multi Quality\" servers.
- Auto Quality Selection
- Height
- Width
- Automatically uses the closest quality provided by default, ONLY applied for \"Multi Quality\" Servers. Auto changes upon playing a video.
- Default Playback Speed : %1$s
- Cursed Speeds
- Default Resize Mode
- Subtitles
- Subtitles
- Subtitle Color
- Subtitle Outline Color
- Subtitle Outline Type
- Subtitle Background Color
- Subtitle Window Color
- "The subtitle window is the part left and right from them. (where the background isn\'t)"
- Note: Changing Subtitle formatting only works with Soft-Subbed Sources such as Zoro!
- Subtitle Font
- Subtitle Size
-
- Auto
- Autoplay Next Episode
- Automatically disables if there is no interaction with player after 1 hour.
- Auto Skip Fillers
- Skips filler episodes when going to next episode.
-
- Update Progress
- Ask for each Anime \"Individually\"
- Ask for each Manga \"Individually\"
- Turning off will always automatically update progress when the episode is watched.
- Turning off will always automatically update progress when the chapter is read.
- Update Progress for Hentai
- Update Progress for Doujins
- very bold of you sar
- Watch Update Percentage
- The percentage at which your Anilist progress should be updated after watching an episode. \nThis also sets the \% for when to preload links for the next episode.
-
- Behaviour
- Always Continue from where you left off
- Pause when not in Focus
- Volume & Brightness Gestures
- Double tap to Seek
- Fast Forward
- Turning off will show fast forward & rewind buttons
- Seek Time
- Amount of time in seconds for fast forward & rewind.
- Skip Time
- Setting to 0, hides the Skip Button.
- Show Cast Button
- Requires \"Web Video Caster\" app to cast.
- Picture in Picture
- Always Minimize
- Requires PiP to be enabled, makes the player behave like Youtube Player but better.\nAlso hides the PiP button.
-
- App
- Hide Status Bar
- Requires App restart to fully apply.
- Show/Hide Layouts on Home
-
- - Continue Watching
- - Favourite Anime
- - Planned Anime
- - Continue Reading
- - Favourite Manga
- - Planned Manga
- - Recommended
-
-
- Default Start Up Tab
- Small View in Trending Shows
- Animations
- Banner Animations
- Layout Animations
- Overall Speed
- Looks like you don\'t like anything,\nTry liking a show to keep it here.
- Favourite Anime
- Favourite Manga
- Restart the app?
- Next
- Previous
- Current Page
- Dubbed
- Subbed
- Prefer Dubbed Anime
- None
- Selected DNS
- Change if your ISP blocks any source
- Keep Screen On
- Layout
- Spaced Pages
- Direction
-
- General
- Show Status & Navigation Bars
- Auto Detect Webtoon
- If the Manga is not from Japan, the reader will default to Webtoon Reader Settings
- Default Settings
- Horizontal Scroll Bar
- Dual Page
- Shows 2 Images in 1 page, will look weird if the images aren\'t the same size
- True Colors
- (32-bit color Mode) Reduces Banding on the images, may affect performance.
- Image Rotation
- Hide Page Numbers
- Sort by Title
- Sort by Last Updated
- Sort by Score
- Over Scroll to Next/Previous Chapter
- Change pages with Volume Buttons
- Private
- Wrap Images
- Mostly useful for larger devices, removes space between images, if they exist.
- Reload
- Share
- Skip
- Show Skip Time Stamp Button
- Always Load Time Stamps
- Time Stamps
- Other
- Auto Skip OP / ED
- Requires Time Stamps to be Enabled
- TOTAL REPEATS
- Custom Lists
- Want to support Dantotsu\'s Maintainer?\nConsider Donating
- No donation goal atm
- Filter
- Year
- Apply
- Cancel
- This Season
- Next Season
- Previous Season
- Include List
- Calendar
- Planned Anime
- Planned Manga
- Open image by Long Clicking
- Always continue Shows
- Useful if you are getting Handshake Fails
- Use Proxy for Timestamps
- Always check for App Updates
- Author
- Versions
- FAQ
- Accounts
- MyAnimeList
- Login with Anilist!
- Anilist
- How does this work\?
-
- Dantotsu is an Anilist Based App, so for syncing with your MAL Account, It needs an Anilist account to be logged in.
- \nOnce logged in with both Anilist and MAL accounts, the app will automatically update your MAL account whenever:
- \n- Add a new Media
- \n- Edit a Media
- \n- Delete a Media
-
- \n\nNote: The app will not sync old Media\'s from Anilist to MAL, It\'s recommended to sync them.
- \n- Check __FAQs__ for _Easy Method_
-
- \n\nAnd for _Intermediates_ :
- \n- [How to sync Anilist data with MAL](https://anilist.co/forum/thread/2654)
- \n- [How to sync MAL data with Anilist](https://anilist.co/forum/thread/3393)
-
- \n\n_It is not required to sync both MAL and Anilist accounts._
-
- Show notification for Checking Subscriptions
- Notification for Checking Subscriptions
- Subscriptions Update Frequency : %1$s
- Subscriptions Update Frequency
- Amount of time for Dantotsu to periodically check for new Episodes/Chapters\n(Less time will cause more battery consumption)
- Don\'t Update
- Loading Next Chapter
- Grid
- Sort by Release Date
- Crop Borders
- NOTE
-
-
- DAMN! YOU TRULY ARE JOBLESS\nYOU REACHED THE END
- Couldn\'t find any File Manager to open SD card
- Error loading data %1$s
- You Long Click the button to check for App Update
- Saved to:\n%s
- Setting progress to %1$d
- Please Login into anilist account!
- Congrats Vro
- Please Reload.
- Copied "%1$s"
- Please perform BACK again to Exit
- No Internet Connection
- Seems like that wasn\'t found on Anilist.
- Disabled Auto Skipping OP & ED
- Auto Skipping OP & ED
- Copied to Clipboard
- This is the 1st Episode!
- You can long click List Editor button to Reset Auto Update
- Autoplay cancelled, no Interaction for more than 1 Hour.
- Couldn\'t auto select the server, Please try again!
- Logging in MAL
- Getting User Data
- No next Episode Found!
- Try Enabling Banner Animations from Settings
- Please Login with Anilist!
- Auto Update Progress has now been Reset-ed
- Can\'t Wait, huh? fine X(
- Downloading…
- Next Chapter Not Found
- This is the 1st Chapter!
- Adult Stuff?( ͡° ͜ʖ ͡° )
- What did you even open?
- Error getting Data from Anilist.
- Empty Response, Does your internet perhaps suck?
- Error loading MAL User Data
- Failed to load data from MAL
- Error loading Anilist User Data
- Couldn\'t find episode : %1$s
- List Updated
- Deleted from List
- No List ID found, reloading…
- Checking for Update
- Don\'t show again for version %1$s
- No Update Found
- Downloading Update %1$s
- Please give permission to access Files & Folders from Settings, & Try again.
- Started Downloading\n%1$s
- Please install 1DM
- Please install ADM
- Error getting Image Data
- Loading Image Failed
- Copied device info
- Seems like Anilist is down, maybe try using a VPN or you can wait for it to come back.
- Failed to load saved data of %1$d
- Wasn\'t able to get access
- Mal Login : Uri not Found
- Mal Login : codeChallenge not found
- Mal Login : Code not present in Redirected URI
- Refresh Token : Failed to load Saved Token
- Refreshing Token Failed
- Episode %1$d will be released in
- %1$d days %2$d hrs %3$d mins %4$d secs
-
- - Score
- - Popular
- - Trending
- - A-Z
- - Z-A
- - What?
-
- MAIN
- SUPPORTING
-
- FINISHED
- RELEASING
- NOT YET RELEASED
- CANCELLED
- HIATUS
-
- ADAPTATION
- PARENT
- CHARACTER
- SUMMARY
- ALTERNATIVE
- OTHER
- SOURCE
- CONTAINS
-
- Read on Dantotsu
- Watch on Dantotsu
- "Continue : Episode "
- "Continue : "
- "Episode "
- "Episode %1$s"
- "Chapter "
- "Chapter %1$s"
- - just got released!
- Checking Subscriptions
-
- Speed
- Auto Update progress for %1$s?
- Continue from %1$s?
- Update progress on anilist?
- Incognito mode will still ignore progress.
- "Don\'t ask again for %1$s"
- Default Speed
- Default Resize Mode
- Primary Sub Color
- Outline Sub Color
- Outline Type
- Subtitle Font
-
-
- Yes
- No
- Close
- No Chapter
- Turn on 18+ Content from your Anilist Settings
- Available
- Let\'s Go
- Cope
-
- "Watched "
- "Read "
- " out of "
- " out of "
- "Total of "
- "Total of "
- "No Description Available"
-
-
- - Top to Bottom
- - Right to Left
- - Bottom to Top
- - Left to Right
-
-
-
- - Paged
- - Continuous Paged
- - Continuous
-
-
- Selected
- Found
-
- "__Age:__ "
- \n"__Birthday:__ "
- \n"__Gender:__ ""
-
- Male
- Female
-
- What is Dantotsu?\n Why should you use Dantotsu?
- Dantotsu is crafted from the ashes of Saikou and based on simplistic yet state-of-the-art elegance. It is an Anilist only client, which also lets you stream & download Anime / Manga through extensions. \n>Dantotsu (æ–トツ; Dan-totsu) literally means the best of the best in Japanese. Well, we would like to say this is the best open source app for anime and manga on Android, but hey, Try it out yourself & judge!
-
- What are some features of Dantotsu?
- Some mentionable features of Dantotsu are\n\n- Easy and functional way to both, watch anime and read manga and light novels, Ad Free.\n- A completely open source app with a nice UI & Animations\n- 3rd party plugin support \n- Synchronize anime and manga real-time with AniList. Easily categorize anime and manga based on your current status. (Powered by AniList)\n- Find all shows using thoroughly and frequently updated list of all trending, popular and ongoing anime based on scores.\n- View extensive details about anime shows, movies and manga titles. It also features ability to countdown to the next episode of airing anime. (Powered by AniList & MyAnimeList)
-
- What are Artifacts?
- Whenever a developer commits or pull requests a feature or fix, GitHub automatically makes an APK file for you to use. This APK is called an Artifact. Artifacts through pull requests may or may not be added to the main release of the app. Artifacts have a higher chance of having bugs and glitches. To know if new artifacts are available, star the Dantotsu repository and turn on notifications\n\nTo download an Artifact:\n1) Sign In/Up in GitHub\n2) Go to Dantotsu\n3) Go to actions\n4) Press on the workflow run you want to download the artifact of.\n5) Press on artifact\n6) Extract the file using a zip extractor\n7) Install and enjoy.
-
- Is Dantotsu available for PC?
- Currently no (for both Windows and Linux). There isn\'t any estimation when it will be available. But you can download any Android emulator and run Dantotsu on it. For Windows 11 users, they can use the Windows Subsystem for Android (WSA) to run Dantotsu in Windows.
-
- Is Dantotsu available for iOS?
- No, and currently no plans to support iOS
-
- Why are my stats not updating?
- This is because it updates every 48 hours automatically (by Anilist). If you really need to update your stats, you can force update your stats after going to this [link](https://anilist.co/settings/lists).
-
- How to download Episodes?
- 1. Download 1DM or ADM from Google Play Store.
- \n2. Enter the app, give storage access and set your preferences (downloading speed, downloading path etc(optional))
- \n3. Now go to Dantotsu > Settings > Common > Download Managers and choose the download manager you just set up.
- \n4. Go to your desired episode and press on the download icon of any server. There may be a popup to set your preferences again, just press on "Download" and it will be saved in the directed path.
-
- \n\nNote: Direct downloads are also possible without a manager but it\'s not recommended.
-
- How to download Manga Chapters?
- It is yet not possible to download chapters in Dantotsu but this feature will be implemented soon.
-
- How to enable NSFW content?
- You can enable nsfw content by enabling 18+ contents from this [link](https://anilist.co/settings/media).
-
- How to import my MAL/Kitsu list to Anilist?
- Here is how you do it,\n\nExport:\n\n1. Go to this [link](https://malscraper.azurewebsites.net).\n2. Give your Kitsu/MAL username and download both anime and manga list. (They will be in XML format)\nNote: You have to write the username of the tracker you selected\n\nImport:\n\n1. After exporting your anime and manga list from Kitsu/MAL, now go [here](https://anilist.co/settings/import) \n2. Select/drop the anime XML file on the box above.\n|→Select/drop the manga XML file on the box below.
-
- How to import my Anilist/Kitsu list to MAL?
- Here is how you do it,\n\nExport:\n\n1. Go to this [link](https://malscraper.azurewebsites.net/). \n2. Give your Anilist username/Kitsu ID in the \'Username/Kitsu User ID\' box. \n3. Select list type and enable \'update_on_import\'. \n4. Download the file; it will be in .xml format. Be sure to download both Anime and Manga lists.\n\nImport:\n1. To import it in your MAL account, go to this [link](https://myanimelist.net/import.php) and choose \'MyAnimeList Import\' as import type. \n2. Press on \'Choose File\'and select the downloaded anime/manga list XML file. \n3. Press on \'Import Data\'. \nCongratulations, you just imported the selected list to your MAL account.
-
- Why can\'t I find a specific anime/manga title?
- Let\'s say you are looking for Castlevania in Dantotsu. But Anilist doesn\'t have it, so Dantotsu doesn\'t either.\nA solution to the above problem is as follows:\n1) Go to any anime that\'s not in your list.\n2) Go to the watch section.\n3) Select any source and press on the \'Wrong Title?\'.\n4) Now search for Castlevania (The anime you were looking for) and select it.\n5) ENJOY!\n\nIf you can\'t find the anime even through these steps, then that\'s bad luck for you, bud. Even that source doesn\'t have it. Try a different source.
-
- How to fix sources selecting a completely wrong title?
- Dantotsu itself doesn\'t host anything but relies on other sources. When showing the episodes, it chooses the 1st result given by the source after searching for the title. Dantotsu has no way of detecting if that\'s legit or not. So, for this, we have the \'Wrong Title?\' just below the source name(above layouts). You can choose the correct result/title by pressing on it and enjoy its episodes.
-
- How to read coloured mangas?
- Are you in search of coloured manga? Sorry to break it to you but an extremely small amount of mangas have coloured version. Those which has a coloured version is also available in Dantotsu. Let\'s say you want to read the coloured version of chainsaw man. Then follow the below steps ↓\n\n1) Go to Chainsaw Man\n2) Press on \'Read\'\n3) Select any working source\n4) Press on \'Wrong Title\'\n5) Select the colored version chainsaw man\n6) Enjoy\n\nNote: Many sources don\'t have the coloured version available even if it\'s available somewhere on the internet. So try a different source. If none of the sources have it, then a coloured version of your desired manga simply doesn\'t exist. If you can find it on any manga site on the internet, you can suggest that site through the Discord server.
-
- Handshake fails? Why are no timestamps not loading?
- You can fix this issue by enabling \'Proxy\' from \n\`Settings > Anime > Player Settings > Timestamps > Proxy\`.\nIf the timestamps are still not loading but the handshake failed popup is fixed, then the episode you are watching just doesn\'t have timestamps yet for it.
-
- Having trouble with a source?
- Some basic fixes would be :\n\n• Restart the app. \n• Use a different DNS from your settings, preferably, CloudFlare. \n• VPN might work as well. \n\nIf you refuse to try the above steps then just use a different source.\n\nNote: Allanime fixes itself most of the time.
-
- Some useful tips and tricks
- The following presents some tips and tricks you may or may not know about - \n \n \n - By hold pressing the Dantotsu logo in settings, you can check if there are any new updates manually. \n \n - Hold pressing an error message/tag/synonym or title will copy it. \n \n - You can open an episode with other apps by hold pressing any server for that episode. This helps in streaming the episode using other video players or download the episode using download managers. \n \n - You can set up custom lists using this [link](https://anilist.co/settings/lists). (you need to be signed in) \n \n - If your episode/chapter is not being progressed automatically after you finish watching/reading it, then hold press the status bar(planning/repeating/watching button) of that anime/manga. The next time you start a chapter/finish an episode, you will stumble upon a popup. Press yes there.
-
-
- Subscribed! Receiving notifications, when new episodes are released on %1$s.
-
- Unsubscribed, you will not receive any notifications.
- Episodes
- Episode
- Chapter
- Chapters
-
- "Format : %1$s"
- "Sort : %1$s"
- "Not %1$s"
- Search by Image
- Upload Image
- Similarity: %1$s %%
- From %1$s to %2$s
- Invalid URL
- No Anilist ID found
- Error loading image
- Successfully Logged Out
- Try logging-in again
- Error loading Discord User Data
-
- Warning
- View Anime
- View Manga
- Force Legacy Installer
- Extensions
- NSFW Extensions
- Skip loading extension icons
- Material You
- Extension-specific DNS
- Theme:
- User Agent
- Custom Theme
- Custom theme
- Color same as Anime/Manga cover
- OLED theme variant
- Installed Anime
- Available Anime
- Installed Manga
- Color Picker
- Random Selection
- Incognito Mode
-
-
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 73a2114d..fbda0709 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -65,32 +65,32 @@
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 34281556..06f9958b 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -24,6 +24,14 @@
#CD201F
#a3a2a2
#E8EDEDED
+ #93DB00
+ #68AF86
+ #0096AE
+ #000000
+ #FFE1F5FE
+ #FF81D4FA
+ #FF039BE5
+ #FF01579B
#00658e
@@ -88,6 +96,7 @@
#41484D
#000000
+
#426916
#426916
#FFFFFF
@@ -150,6 +159,7 @@
#44483D
#000000
+
#7c4997
#7C4997
#FFFFFF
@@ -181,37 +191,38 @@
#7C4997
#CEC3CE
#000000
- #E7B3FF
- #4A1765
- #62307D
- #F6D9FF
- #D3C0D8
- #382C3E
- #504255
- #F0DCF4
- #F5B7B7
- #4C2526
- #663B3B
- #FFDAD9
- #FFB4AB
- #93000A
- #690005
- #FFDAD6
- #1D1B1E
- #E8E0E5
- #1D1B1E
- #E8E0E5
- #4B444D
- #CEC3CE
- #978E98
- #1D1B1E
- #E8E0E5
- #7C4997
- #000000
- #E7B3FF
- #4B444D
- #000000
+ #E7B3FF
+ #4A1765
+ #62307D
+ #F6D9FF
+ #D3C0D8
+ #382C3E
+ #504255
+ #F0DCF4
+ #F5B7B7
+ #4C2526
+ #663B3B
+ #FFDAD9
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #1D1B1E
+ #E8E0E5
+ #1D1B1E
+ #E8E0E5
+ #4B444D
+ #CEC3CE
+ #978E98
+ #1D1B1E
+ #E8E0E5
+ #7C4997
+ #000000
+ #E7B3FF
+ #4B444D
+ #000000
+
#e800ac
#B30084
#FFFFFF
@@ -274,7 +285,8 @@
#504349
#000000
- #FF007F
+
+ #FF007F
#FF007F
#EEEEEE
@@ -330,7 +342,7 @@
#00FF00
#00FF00
#00FF00
- #1C1B20
+ #1C1B1E
#EEEEEE
#1C1B20
#EEEEEE
@@ -345,135 +357,195 @@
#00FF00
#00FF00
- #c9000b
- #C0000A
- #FFFFFF
- #FFDAD5
- #410001
- #775652
- #FFFFFF
- #FFDAD5
- #2C1512
- #705C2E
- #FFFFFF
- #FCDFA6
- #261A00
- #BA1A1A
- #FFDAD6
- #FFFFFF
- #410002
- #FFFBFF
- #201A19
- #FFFBFF
- #201A19
- #F5DDDA
- #534341
- #857370
- #FBEEEC
- #362F2E
- #FFB4AA
- #000000
- #C0000A
- #D8C2BE
- #000000
- #FFB4AA
- #690003
- #930005
- #FFDAD5
- #E7BDB7
- #442926
- #5D3F3B
- #FFDAD5
- #DFC38C
- #3E2E04
- #574419
- #FCDFA6
- #FFB4AB
- #93000A
- #690005
- #FFDAD6
- #201A19
- #EDE0DE
- #201A19
- #EDE0DE
- #534341
- #D8C2BE
- #A08C89
- #201A19
- #EDE0DE
- #C0000A
- #000000
- #FFB4AA
- #534341
- #000000
+
+ #c9000b
+ #C0000A
+ #FFFFFF
+ #FFDAD5
+ #410001
+ #775652
+ #FFFFFF
+ #FFDAD5
+ #2C1512
+ #705C2E
+ #FFFFFF
+ #FCDFA6
+ #261A00
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FFFBFF
+ #201A19
+ #FFFBFF
+ #201A19
+ #F5DDDA
+ #534341
+ #857370
+ #FBEEEC
+ #362F2E
+ #FFB4AA
+ #000000
+ #C0000A
+ #D8C2BE
+ #000000
+ #FFB4AA
+ #690003
+ #930005
+ #FFDAD5
+ #E7BDB7
+ #442926
+ #5D3F3B
+ #FFDAD5
+ #DFC38C
+ #3E2E04
+ #574419
+ #FCDFA6
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #201A19
+ #EDE0DE
+ #201A19
+ #EDE0DE
+ #534341
+ #D8C2BE
+ #A08C89
+ #201A19
+ #EDE0DE
+ #C0000A
+ #000000
+ #FFB4AA
+ #534341
+ #000000
+
+
+ #6750A4
+ #6750A4
+ #FFFFFF
+ #E9DDFF
+ #22005D
+ #625B71
+ #FFFFFF
+ #E8DEF8
+ #1E192B
+ #7E5260
+ #FFFFFF
+ #FFD9E3
+ #31101D
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FFFBFF
+ #1C1B1E
+ #FFFBFF
+ #1C1B1E
+ #E7E0EB
+ #49454E
+ #7A757F
+ #F4EFF4
+ #313033
+ #CFBCFF
+ #000000
+ #6750A4
+ #CAC4CF
+ #000000
+ #CFBCFF
+ #381E72
+ #4F378A
+ #E9DDFF
+ #CBC2DB
+ #332D41
+ #4A4458
+ #E8DEF8
+ #EFB8C8
+ #4A2532
+ #633B48
+ #FFD9E3
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #1C1B1E
+ #E6E1E6
+ #1C1B1E
+ #E6E1E6
+ #49454E
+ #CAC4CF
+ #948F99
+ #1C1B1E
+ #E6E1E6
+ #6750A4
+ #000000
+ #CFBCFF
+ #49454E
+ #000000
+
+
+ #14AEA7
+ #006A65
+ #FFFFFF
+ #70F7EF
+ #00201E
+ #4A6361
+ #FFFFFF
+ #CCE8E5
+ #051F1E
+ #48607B
+ #FFFFFF
+ #D0E4FF
+ #001D34
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FAFDFB
+ #191C1C
+ #FAFDFB
+ #191C1C
+ #DAE5E3
+ #3F4948
+ #6F7978
+ #EFF1F0
+ #2D3131
+ #4FDAD2
+ #000000
+ #006A65
+ #BEC9C7
+ #000000
+ #4FDAD2
+ #003734
+ #00504C
+ #70F7EF
+ #B0CCC9
+ #1B3533
+ #324B49
+ #CCE8E5
+ #B0C9E7
+ #19324A
+ #314962
+ #D0E4FF
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #191C1C
+ #E0E3E2
+ #191C1C
+ #E0E3E2
+ #3F4948
+ #BEC9C7
+ #889391
+ #191C1C
+ #E0E3E2
+ #006A65
+ #000000
+ #4FDAD2
+ #3F4948
+ #000000
+
+
- #6750A4
- #6750A4
- #FFFFFF
- #E9DDFF
- #22005D
- #625B71
- #FFFFFF
- #E8DEF8
- #1E192B
- #7E5260
- #FFFFFF
- #FFD9E3
- #31101D
- #BA1A1A
- #FFDAD6
- #FFFFFF
- #410002
- #FFFBFF
- #1C1B1E
- #FFFBFF
- #1C1B1E
- #E7E0EB
- #49454E
- #7A757F
- #F4EFF4
- #313033
- #CFBCFF
- #000000
- #6750A4
- #CAC4CF
- #000000
- #CFBCFF
- #381E72
- #4F378A
- #E9DDFF
- #CBC2DB
- #332D41
- #4A4458
- #E8DEF8
- #EFB8C8
- #4A2532
- #633B48
- #FFD9E3
- #FFB4AB
- #93000A
- #690005
- #FFDAD6
- #1C1B1E
- #E6E1E6
- #1C1B1E
- #E6E1E6
- #49454E
- #CAC4CF
- #948F99
- #1C1B1E
- #E6E1E6
- #6750A4
- #000000
- #CFBCFF
- #49454E
- #000000
- #93DB00
- #68AF86
- #0096AE
- #000000
- #FFE1F5FE
- #FF81D4FA
- #FF039BE5
- #FF01579B
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 58b40d9c..23e646c5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -227,7 +227,7 @@
Subtitle Background Color
Subtitle Window Color
"The subtitle window is the part left and right from them. (where the background isn\'t)"
- Note: Changing Subtitle formatting only works with Soft-Subbed Sources such as Zoro!
+ Note: Changing above settings only affects Soft-Subtitles!
Subtitle Font
Subtitle Size
@@ -569,9 +569,6 @@
\n\nNote: Direct downloads are also possible without a manager but it\'s not recommended.
- How to download Manga Chapters?
- It is yet not possible to download chapters in Dantotsu but this feature will be implemented soon.
-
How to enable NSFW content?
You can enable nsfw content by enabling 18+ contents from this [link](https://anilist.co/settings/media).
@@ -648,5 +645,6 @@
Add widget
This is an app widget description
Airing Image
+ animeDownloads
diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml
index f197a318..e2c009ee 100644
--- a/app/src/main/res/values/style.xml
+++ b/app/src/main/res/values/style.xml
@@ -1,5 +1,6 @@
+
-
+
+
+
- @color/bg_opp
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index d672ba53..5b893737 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -198,93 +198,6 @@
- @color/md_theme_light_4_inversePrimary
-
-
-
-
-
-
+
+
+
+
+
+
+
+