verbose downloading

This commit is contained in:
rebelonion 2024-01-16 17:53:46 -06:00
parent 8375cb5c03
commit 84fc5e6e2c
11 changed files with 337 additions and 63 deletions

View file

@ -259,6 +259,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
//TODO: Remove this
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
val index = Helper.downloadManager(this@MainActivity).downloadIndex val index = Helper.downloadManager(this@MainActivity).downloadIndex
val downloadCursor = index.getDownloads() val downloadCursor = index.getDownloads()

View file

@ -157,6 +157,14 @@ class AnimeDownloaderService : Service() {
val url = val url =
AnimeServiceDataSingleton.downloadQueue.find { it.getTaskName() == taskName }?.video?.file?.url AnimeServiceDataSingleton.downloadQueue.find { it.getTaskName() == taskName }?.video?.file?.url
?: "" ?: ""
DownloadService.sendSetStopReason(
this@AnimeDownloaderService,
ExoplayerDownloadService::class.java,
url,
androidx.media3.exoplayer.offline.Download.STATE_REMOVING,
false
)
DownloadService.sendRemoveDownload( DownloadService.sendRemoveDownload(
this@AnimeDownloaderService, this@AnimeDownloaderService,
ExoplayerDownloadService::class.java, ExoplayerDownloadService::class.java,
@ -191,7 +199,7 @@ class AnimeDownloaderService : Service() {
} }
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
suspend fun download(task: DownloadTask) { suspend fun download(task: AnimeDownloadTask) {
try { try {
val downloadManager = Helper.downloadManager(this@AnimeDownloaderService) val downloadManager = Helper.downloadManager(this@AnimeDownloaderService)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -209,7 +217,7 @@ class AnimeDownloaderService : Service() {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
broadcastDownloadStarted(task.getTaskName()) broadcastDownloadStarted(task.episode)
currActivity()?.let { currActivity()?.let {
Helper.downloadVideo( Helper.downloadVideo(
@ -228,7 +236,7 @@ class AnimeDownloaderService : Service() {
builder.setContentText("${task.title} - ${task.episode} Download failed to start") builder.setContentText("${task.title} - ${task.episode} Download failed to start")
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download failed to start") snackString("${task.title} - ${task.episode} Download failed to start")
broadcastDownloadFailed(task.getTaskName()) broadcastDownloadFailed(task.episode)
return@withContext return@withContext
} }
@ -252,7 +260,7 @@ class AnimeDownloaderService : Service() {
" episode: ${task.episode}" " episode: ${task.episode}"
) )
) )
broadcastDownloadFailed(task.getTaskName()) broadcastDownloadFailed(task.episode)
break break
} }
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) { if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) {
@ -274,7 +282,7 @@ class AnimeDownloaderService : Service() {
DownloadedType.Type.ANIME, DownloadedType.Type.ANIME,
) )
) )
broadcastDownloadFinished(task.getTaskName()) broadcastDownloadFinished(task.episode)
break break
} }
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) { if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) {
@ -285,7 +293,7 @@ class AnimeDownloaderService : Service() {
break break
} }
broadcastDownloadProgress( broadcastDownloadProgress(
task.getTaskName(), task.episode,
download.percentDownloaded.toInt() download.percentDownloaded.toInt()
) )
if (notifi) { if (notifi) {
@ -299,14 +307,14 @@ class AnimeDownloaderService : Service() {
logger("Exception while downloading file: ${e.message}") logger("Exception while downloading file: ${e.message}")
snackString("Exception while downloading file: ${e.message}") snackString("Exception while downloading file: ${e.message}")
FirebaseCrashlytics.getInstance().recordException(e) FirebaseCrashlytics.getInstance().recordException(e)
broadcastDownloadFailed(task.getTaskName()) broadcastDownloadFailed(task.episode)
} }
} }
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
suspend fun hasDownloadStarted( suspend fun hasDownloadStarted(
downloadManager: DownloadManager, downloadManager: DownloadManager,
task: DownloadTask, task: AnimeDownloadTask,
timeout: Long timeout: Long
): Boolean { ): Boolean {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
@ -322,7 +330,7 @@ class AnimeDownloaderService : Service() {
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
private fun saveMediaInfo(task: DownloadTask) { private fun saveMediaInfo(task: AnimeDownloadTask) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
val directory = File( val directory = File(
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -406,30 +414,30 @@ class AnimeDownloaderService : Service() {
} }
} }
private fun broadcastDownloadStarted(chapterNumber: String) { private fun broadcastDownloadStarted(episodeNumber: String) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
} }
sendBroadcast(intent) sendBroadcast(intent)
} }
private fun broadcastDownloadFinished(chapterNumber: String) { private fun broadcastDownloadFinished(episodeNumber: String) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FINISHED).apply { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FINISHED).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
} }
sendBroadcast(intent) sendBroadcast(intent)
} }
private fun broadcastDownloadFailed(chapterNumber: String) { private fun broadcastDownloadFailed(episodeNumber: String) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FAILED).apply { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FAILED).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
} }
sendBroadcast(intent) sendBroadcast(intent)
} }
private fun broadcastDownloadProgress(chapterNumber: String, progress: Int) { private fun broadcastDownloadProgress(episodeNumber: String, progress: Int) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_PROGRESS).apply { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_PROGRESS).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
putExtra("progress", progress) putExtra("progress", progress)
} }
sendBroadcast(intent) sendBroadcast(intent)
@ -448,7 +456,7 @@ class AnimeDownloaderService : Service() {
} }
data class DownloadTask( data class AnimeDownloadTask(
val title: String, val title: String,
val episode: String, val episode: String,
val video: Video, val video: Video,
@ -461,6 +469,12 @@ class AnimeDownloaderService : Service() {
fun getTaskName(): String { fun getTaskName(): String {
return "$title - $episode" return "$title - $episode"
} }
companion object {
fun getTaskName(title: String, episode: String): String {
return "$title - $episode"
}
}
} }
companion object { companion object {
@ -473,7 +487,7 @@ class AnimeDownloaderService : Service() {
object AnimeServiceDataSingleton { object AnimeServiceDataSingleton {
var video: Video? = null var video: Video? = null
var sourceMedia: Media? = null var sourceMedia: Media? = null
var downloadQueue: Queue<AnimeDownloaderService.DownloadTask> = ConcurrentLinkedQueue() var downloadQueue: Queue<AnimeDownloaderService.AnimeDownloadTask> = ConcurrentLinkedQueue()
@Volatile @Volatile
var isServiceRunning: Boolean = false var isServiceRunning: Boolean = false

View file

@ -230,7 +230,7 @@ object Helper {
} }
} }
val downloadTask = AnimeDownloaderService.DownloadTask( val animeDownloadTask = AnimeDownloaderService.AnimeDownloadTask(
title, title,
episode, episode,
video, video,
@ -255,7 +255,7 @@ object Helper {
getString(context, R.string.anime_downloads), getString(context, R.string.anime_downloads),
Context.MODE_PRIVATE Context.MODE_PRIVATE
).getString( ).getString(
downloadTask.getTaskName(), animeDownloadTask.getTaskName(),
"" ""
) ?: "", ) ?: "",
false false
@ -264,7 +264,7 @@ object Helper {
getString(context, R.string.anime_downloads), getString(context, R.string.anime_downloads),
Context.MODE_PRIVATE Context.MODE_PRIVATE
).edit() ).edit()
.remove(downloadTask.getTaskName()) .remove(animeDownloadTask.getTaskName())
.apply() .apply()
downloadsManger.removeDownload( downloadsManger.removeDownload(
DownloadedType( DownloadedType(
@ -273,7 +273,7 @@ object Helper {
DownloadedType.Type.ANIME DownloadedType.Type.ANIME
) )
) )
AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)
if (!AnimeServiceDataSingleton.isServiceRunning) { if (!AnimeServiceDataSingleton.isServiceRunning) {
val intent = Intent(context, AnimeDownloaderService::class.java) val intent = Intent(context, AnimeDownloaderService::class.java)
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
@ -283,7 +283,7 @@ object Helper {
.setNegativeButton("No") { _, _ -> } .setNegativeButton("No") { _, _ -> }
.show() .show()
} else { } else {
AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)
if (!AnimeServiceDataSingleton.isServiceRunning) { if (!AnimeServiceDataSingleton.isServiceRunning) {
val intent = Intent(context, AnimeDownloaderService::class.java) val intent = Intent(context, AnimeDownloaderService::class.java)
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)

View file

@ -242,7 +242,8 @@ class MediaDetailsViewModel : ViewModel() {
i: String, i: String,
manager: FragmentManager, manager: FragmentManager,
launch: Boolean = true, launch: Boolean = true,
prevEp: String? = null prevEp: String? = null,
isDownload: Boolean = false
) { ) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) { if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) {
@ -254,13 +255,11 @@ class MediaDetailsViewModel : ViewModel() {
} }
media.selected = this.loadSelected(media) media.selected = this.loadSelected(media)
val selector = val selector =
SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp) SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp, isDownload)
selector.show(manager, "dialog") selector.show(manager, "dialog")
} }
} }
} }
//Manga //Manga
var mangaReadSources: MangaReadSources? = null var mangaReadSources: MangaReadSources? = null

View file

@ -2,6 +2,10 @@ package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,17 +13,25 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.OptIn
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.* import ani.dantotsu.*
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.video.ExoplayerDownloadService
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
@ -43,6 +55,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -62,6 +76,8 @@ class AnimeWatchFragment : Fragment() {
private lateinit var headerAdapter: AnimeWatchAdapter private lateinit var headerAdapter: AnimeWatchAdapter
private lateinit var episodeAdapter: EpisodeAdapter private lateinit var episodeAdapter: EpisodeAdapter
val downloadManager = Injekt.get<DownloadsManager>()
var screenWidth = 0f var screenWidth = 0f
private var progress = View.VISIBLE private var progress = View.VISIBLE
@ -81,6 +97,21 @@ class AnimeWatchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val intentFilter = IntentFilter().apply {
addAction(ACTION_DOWNLOAD_STARTED)
addAction(ACTION_DOWNLOAD_FINISHED)
addAction(ACTION_DOWNLOAD_FAILED)
addAction(ACTION_DOWNLOAD_PROGRESS)
}
ContextCompat.registerReceiver(
requireContext(),
downloadStatusReceiver,
intentFilter,
ContextCompat.RECEIVER_EXPORTED
)
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
screenWidth = resources.displayMetrics.widthPixels.dp screenWidth = resources.displayMetrics.widthPixels.dp
@ -135,9 +166,15 @@ class AnimeWatchFragment : Fragment() {
if (!loaded) { if (!loaded) {
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
val offlineMode = model.watchSources!!.list[media.selected!!.sourceIndex].name == "Downloaded"
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!) headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
episodeAdapter = episodeAdapter =
EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this) EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this, offlineMode = offlineMode)
for (download in downloadManager.animeDownloadedTypes) {
episodeAdapter.stopDownload(download.chapter)
}
binding.animeSourceRecycler.adapter = binding.animeSourceRecycler.adapter =
ConcatAdapter(headerAdapter, episodeAdapter) ConcatAdapter(headerAdapter, episodeAdapter)
@ -381,6 +418,90 @@ class AnimeWatchFragment : Fragment() {
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager) model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
} }
fun onAnimeEpisodeDownloadClick(i: String) {
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
}
fun onAnimeEpisodeStopDownloadClick(i: String) {
val cancelIntent = Intent().apply {
action = AnimeDownloaderService.ACTION_CANCEL_DOWNLOAD
putExtra(AnimeDownloaderService.EXTRA_TASK_NAME, AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i))
}
requireContext().sendBroadcast(cancelIntent)
// Remove the download from the manager and update the UI
downloadManager.removeDownload(
DownloadedType(
media.mainName(),
i,
DownloadedType.Type.ANIME
)
)
episodeAdapter.purgeDownload(i)
}
@OptIn(UnstableApi::class)
fun onAnimeEpisodeRemoveDownloadClick(i: String) {
downloadManager.removeDownload(
DownloadedType(
media.mainName(),
i,
DownloadedType.Type.ANIME
)
)
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
val id = requireContext().getSharedPreferences(
ContextCompat.getString(requireContext(), R.string.anime_downloads),
Context.MODE_PRIVATE
).getString(
taskName,
""
) ?: ""
requireContext().getSharedPreferences(
ContextCompat.getString(requireContext(), R.string.anime_downloads),
Context.MODE_PRIVATE
).edit().remove(taskName).apply()
DownloadService.sendRemoveDownload(
requireContext(),
ExoplayerDownloadService::class.java,
id,
true
)
episodeAdapter.deleteDownload(i)
}
private val downloadStatusReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (!this@AnimeWatchFragment::episodeAdapter.isInitialized) return
when (intent.action) {
ACTION_DOWNLOAD_STARTED -> {
val chapterNumber = intent.getStringExtra(EXTRA_EPISODE_NUMBER)
chapterNumber?.let { episodeAdapter.startDownload(it) }
}
ACTION_DOWNLOAD_FINISHED -> {
val chapterNumber = intent.getStringExtra(EXTRA_EPISODE_NUMBER)
chapterNumber?.let { episodeAdapter.stopDownload(it) }
}
ACTION_DOWNLOAD_FAILED -> {
val chapterNumber = intent.getStringExtra(EXTRA_EPISODE_NUMBER)
chapterNumber?.let {
episodeAdapter.purgeDownload(it)
}
}
ACTION_DOWNLOAD_PROGRESS -> {
val chapterNumber = intent.getStringExtra(EXTRA_EPISODE_NUMBER)
val progress = intent.getIntExtra("progress", 0)
chapterNumber?.let {
episodeAdapter.updateDownloadProgress(it, progress)
}
}
}
}
}
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun reload() { private fun reload() {
val selected = model.loadSelected(media) val selected = model.loadSelected(media)
@ -393,6 +514,8 @@ class AnimeWatchFragment : Fragment() {
model.saveSelected(media.id, selected, requireActivity()) model.saveSelected(media.id, selected, requireActivity())
headerAdapter.handleEpisodes() headerAdapter.handleEpisodes()
val isDownloaded = model.watchSources?.list?.get(selected.sourceIndex)?.name == "Downloaded"
episodeAdapter.offlineMode = isDownloaded
episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size) episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size)
var arr: ArrayList<Episode> = arrayListOf() var arr: ArrayList<Episode> = arrayListOf()
if (media.anime!!.episodes != null) { if (media.anime!!.episodes != null) {
@ -412,6 +535,7 @@ class AnimeWatchFragment : Fragment() {
override fun onDestroy() { override fun onDestroy() {
model.watchSources?.flushText() model.watchSources?.flushText()
super.onDestroy() super.onDestroy()
requireContext().unregisterReceiver(downloadStatusReceiver)
} }
var state: Parcelable? = null var state: Parcelable? = null

View file

@ -14,6 +14,7 @@ data class Episode(
var selectedExtractor: String? = null, var selectedExtractor: String? = null,
var selectedVideo: Int = 0, var selectedVideo: Int = 0,
var selectedSubtitle: Int? = -1, var selectedSubtitle: Int? = -1,
var downloadProgress: String? = null,
@Transient var extractors: MutableList<VideoExtractor>? = null, @Transient var extractors: MutableList<VideoExtractor>? = null,
@Transient var extractorCallback: ((VideoExtractor) -> Unit)? = null, @Transient var extractorCallback: ((VideoExtractor) -> Unit)? = null,
var allStreams: Boolean = false, var allStreams: Boolean = false,

View file

@ -4,7 +4,10 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.lifecycle.coroutineScope
import androidx.media3.exoplayer.offline.Download
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
@ -14,6 +17,8 @@ import ani.dantotsu.databinding.ItemEpisodeListBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) { fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) {
val curr = loadData<Long>("${mediaId}_${ep}") val curr = loadData<Long>("${mediaId}_${ep}")
@ -36,7 +41,8 @@ class EpisodeAdapter(
private var type: Int, private var type: Int,
private val media: Media, private val media: Media,
private val fragment: AnimeWatchFragment, private val fragment: AnimeWatchFragment,
var arr: List<Episode> = arrayListOf() var arr: List<Episode> = arrayListOf(),
var offlineMode: Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -101,6 +107,7 @@ class EpisodeAdapter(
binding.itemEpisodeFiller.visibility = View.GONE binding.itemEpisodeFiller.visibility = View.GONE
binding.itemEpisodeFillerView.visibility = View.GONE binding.itemEpisodeFillerView.visibility = View.GONE
} }
holder.bind(ep.number, ep.downloadProgress)
binding.itemEpisodeDesc.visibility = binding.itemEpisodeDesc.visibility =
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
binding.itemEpisodeDesc.text = ep.desc ?: "" binding.itemEpisodeDesc.text = ep.desc ?: ""
@ -204,6 +211,61 @@ class EpisodeAdapter(
override fun getItemCount(): Int = arr.size override fun getItemCount(): Int = arr.size
private val activeDownloads = mutableSetOf<String>()
private val downloadedEpisodes = mutableSetOf<String>()
fun startDownload(episodeNumber: String) {
activeDownloads.add(episodeNumber)
// Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) {
notifyItemChanged(position)
}
}
fun stopDownload(episodeNumber: String) {
activeDownloads.remove(episodeNumber)
downloadedEpisodes.add(episodeNumber)
// Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) {
arr[position].downloadProgress = "Downloaded"
notifyItemChanged(position)
}
}
fun deleteDownload(episodeNumber: String) {
downloadedEpisodes.remove(episodeNumber)
// Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) {
arr[position].downloadProgress = null
notifyItemChanged(position)
}
}
fun purgeDownload(episodeNumber: String) {
activeDownloads.remove(episodeNumber)
downloadedEpisodes.remove(episodeNumber)
// Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) {
arr[position].downloadProgress = "Failed"
notifyItemChanged(position)
}
}
fun updateDownloadProgress(episodeNumber: String, progress: Int) {
// Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) {
arr[position].downloadProgress = "Downloading: $progress%"
notifyItemChanged(position)
}
}
inner class EpisodeCompactViewHolder(val binding: ItemEpisodeCompactBinding) : inner class EpisodeCompactViewHolder(val binding: ItemEpisodeCompactBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
init { init {
@ -226,11 +288,26 @@ class EpisodeAdapter(
inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) : inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
private val activeCoroutines = mutableSetOf<String>()
init { init {
itemView.setOnClickListener { itemView.setOnClickListener {
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0) if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
fragment.onEpisodeClick(arr[bindingAdapterPosition].number) fragment.onEpisodeClick(arr[bindingAdapterPosition].number)
} }
binding.itemDownload.setOnClickListener {
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
val episodeNumber = arr[bindingAdapterPosition].number
if (activeDownloads.contains(episodeNumber)) {
fragment.onAnimeEpisodeStopDownloadClick(episodeNumber)
return@setOnClickListener
} else if (downloadedEpisodes.contains(episodeNumber)) {
fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber)
return@setOnClickListener
} else {
fragment.onAnimeEpisodeDownloadClick(episodeNumber)
}
}
}
binding.itemEpisodeDesc.setOnClickListener { binding.itemEpisodeDesc.setOnClickListener {
if (binding.itemEpisodeDesc.maxLines == 3) if (binding.itemEpisodeDesc.maxLines == 3)
binding.itemEpisodeDesc.maxLines = 100 binding.itemEpisodeDesc.maxLines = 100
@ -238,6 +315,57 @@ class EpisodeAdapter(
binding.itemEpisodeDesc.maxLines = 3 binding.itemEpisodeDesc.maxLines = 3
} }
} }
fun bind(episodeNumber: String, progress: String?) {
if (progress != null) {
binding.itemDownloadStatus.visibility = View.VISIBLE
binding.itemDownloadStatus.text = "$progress"
} else {
binding.itemDownloadStatus.visibility = View.GONE
binding.itemDownloadStatus.text = ""
}
if (activeDownloads.contains(episodeNumber)) {
// Show spinner
binding.itemDownload.setImageResource(R.drawable.ic_sync)
startOrContinueRotation(episodeNumber)
} else if (downloadedEpisodes.contains(episodeNumber)) {
// Show checkmark
binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
binding.itemDownload.postDelayed({
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
binding.itemDownload.rotation = 0f
//binding.itemDownload.setColorFilter(typedValue2.data)
}, 1000)
} else {
// Show download icon
binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
}
}
private fun startOrContinueRotation(episodeNumber: String) {
if (!isRotationCoroutineRunningFor(episodeNumber)) {
val scope = fragment.lifecycle.coroutineScope
scope.launch {
// Add chapter number to active coroutines set
activeCoroutines.add(episodeNumber)
while (activeDownloads.contains(episodeNumber)) {
binding.itemDownload.animate().rotationBy(360f).setDuration(1000)
.setInterpolator(
LinearInterpolator()
).start()
delay(1000)
}
// Remove chapter number from active coroutines set
activeCoroutines.remove(episodeNumber)
}
}
}
private fun isRotationCoroutineRunningFor(episodeNumber: String): Boolean {
return episodeNumber in activeCoroutines
}
} }
fun updateType(t: Int) { fun updateType(t: Int) {

View file

@ -1328,18 +1328,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
ext.onVideoPlayed(video) ext.onVideoPlayed(video)
} }
val but = playerView.findViewById<ImageButton>(R.id.exo_download)
if (video?.format == VideoType.CONTAINER || (loadData<Int>("settings_download_manager")
?: 0) != 0
) {
//but.visibility = View.VISIBLE TODO: not sure if this is needed
but.setOnClickListener {
download(this, episode, animeTitle.text.toString())
}
} else {
but.visibility = View.GONE
}
val simpleCache = VideoCache.getInstance(this) val simpleCache = VideoCache.getInstance(this)
val httpClient = okHttpClient.newBuilder().apply { val httpClient = okHttpClient.newBuilder().apply {
ignoreAllSSLErrors() ignoreAllSSLErrors()

View file

@ -43,6 +43,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
private var makeDefault = false private var makeDefault = false
private var selected: String? = null private var selected: String? = null
private var launch: Boolean? = null private var launch: Boolean? = null
private var isDownload: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -50,6 +51,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
selected = it.getString("server") selected = it.getString("server")
launch = it.getBoolean("launch", true) launch = it.getBoolean("launch", true)
prevEpisode = it.getString("prev") prevEpisode = it.getString("prev")
isDownload = it.getBoolean("isDownload")
} }
} }
@ -77,8 +79,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
val ep = media?.anime?.episodes?.get(media?.anime?.selectedEpisode) val ep = media?.anime?.episodes?.get(media?.anime?.selectedEpisode)
episode = ep episode = ep
if (ep != null) { if (ep != null) {
if (isDownload == true) {
if (selected != null) { binding.selectorMakeDefault.visibility = View.GONE
}
if (selected != null && isDownload == false) {
binding.selectorListContainer.visibility = View.GONE binding.selectorListContainer.visibility = View.GONE
binding.selectorAutoListContainer.visibility = View.VISIBLE binding.selectorAutoListContainer.visibility = View.VISIBLE
binding.selectorAutoText.text = selected binding.selectorAutoText.text = selected
@ -258,11 +262,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val video = extractor.videos[position] val video = extractor.videos[position]
//binding.urlQuality.text = if (isDownload == true) {
// if (video.quality != null) "${video.quality}p" else "Default Quality" binding.urlDownload.visibility = View.VISIBLE
//binding.urlNote.text = video.extraNote ?: "" } else {
//binding.urlNote.visibility = if (video.extraNote != null) View.VISIBLE else View.GONE binding.urlDownload.visibility = View.GONE
binding.urlDownload.visibility = View.VISIBLE }
binding.urlDownload.setSafeOnClickListener { binding.urlDownload.setSafeOnClickListener {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor =
extractor.server.name extractor.server.name
@ -314,6 +318,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
init { init {
itemView.setSafeOnClickListener { itemView.setSafeOnClickListener {
if (isDownload == true) {
binding.urlDownload.performClick()
return@setSafeOnClickListener
}
tryWith(true) { tryWith(true) {
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor =
extractor.server.name extractor.server.name
@ -345,13 +353,15 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
fun newInstance( fun newInstance(
server: String? = null, server: String? = null,
la: Boolean = true, la: Boolean = true,
prev: String? = null prev: String? = null,
isDownload: Boolean
): SelectorDialogFragment = ): SelectorDialogFragment =
SelectorDialogFragment().apply { SelectorDialogFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putString("server", server) putString("server", server)
putBoolean("launch", la) putBoolean("launch", la)
putString("prev", prev) putString("prev", prev)
putBoolean("isDownload", isDownload)
} }
} }
} }

View file

@ -148,18 +148,6 @@
app:tint="#fff" app:tint="#fff"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" /> tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<ImageButton
android:id="@+id/exo_download"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:backgroundTint="#00FFFFFF"
android:src="@drawable/ic_round_download_24"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="#fff"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -101,9 +101,10 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:minHeight="92dp" android:minHeight="92dp"
android:orientation="vertical"> android:orientation="vertical">
@ -130,8 +131,28 @@
android:text="@string/empty" android:text="@string/empty"
app:lineHeight="15sp" /> app:lineHeight="15sp" />
<TextView
android:id="@+id/itemDownloadStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fontFamily="@font/poppins_bold"
android:maxLines="5"
android:visibility="gone"
android:text=""
app:lineHeight="15sp" />
</LinearLayout> </LinearLayout>
<ImageButton
android:id="@+id/itemDownload"
android:layout_width="24dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/selectableItemBackground"
app:tint="?attr/colorOnBackground"
app:srcCompat="@drawable/ic_round_download_24" />
</LinearLayout> </LinearLayout>
<TextView <TextView