diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 5fa21e50..a82367b5 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -259,6 +259,7 @@ class MainActivity : AppCompatActivity() { } } } + //TODO: Remove this GlobalScope.launch(Dispatchers.IO) { val index = Helper.downloadManager(this@MainActivity).downloadIndex val downloadCursor = index.getDownloads() diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index 168b6a27..335f8ca2 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -157,6 +157,14 @@ class AnimeDownloaderService : Service() { val 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( this@AnimeDownloaderService, ExoplayerDownloadService::class.java, @@ -191,7 +199,7 @@ class AnimeDownloaderService : Service() { } @androidx.annotation.OptIn(UnstableApi::class) - suspend fun download(task: DownloadTask) { + suspend fun download(task: AnimeDownloadTask) { try { val downloadManager = Helper.downloadManager(this@AnimeDownloaderService) withContext(Dispatchers.Main) { @@ -209,7 +217,7 @@ class AnimeDownloaderService : Service() { notificationManager.notify(NOTIFICATION_ID, builder.build()) } - broadcastDownloadStarted(task.getTaskName()) + broadcastDownloadStarted(task.episode) currActivity()?.let { Helper.downloadVideo( @@ -228,7 +236,7 @@ class AnimeDownloaderService : Service() { 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()) + broadcastDownloadFailed(task.episode) return@withContext } @@ -252,7 +260,7 @@ class AnimeDownloaderService : Service() { " episode: ${task.episode}" ) ) - broadcastDownloadFailed(task.getTaskName()) + broadcastDownloadFailed(task.episode) break } if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) { @@ -274,7 +282,7 @@ class AnimeDownloaderService : Service() { DownloadedType.Type.ANIME, ) ) - broadcastDownloadFinished(task.getTaskName()) + broadcastDownloadFinished(task.episode) break } if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) { @@ -285,7 +293,7 @@ class AnimeDownloaderService : Service() { break } broadcastDownloadProgress( - task.getTaskName(), + task.episode, download.percentDownloaded.toInt() ) if (notifi) { @@ -299,14 +307,14 @@ class AnimeDownloaderService : Service() { logger("Exception while downloading file: ${e.message}") snackString("Exception while downloading file: ${e.message}") FirebaseCrashlytics.getInstance().recordException(e) - broadcastDownloadFailed(task.getTaskName()) + broadcastDownloadFailed(task.episode) } } @androidx.annotation.OptIn(UnstableApi::class) suspend fun hasDownloadStarted( downloadManager: DownloadManager, - task: DownloadTask, + task: AnimeDownloadTask, timeout: Long ): Boolean { val startTime = System.currentTimeMillis() @@ -322,7 +330,7 @@ class AnimeDownloaderService : Service() { } @OptIn(DelicateCoroutinesApi::class) - private fun saveMediaInfo(task: DownloadTask) { + private fun saveMediaInfo(task: AnimeDownloadTask) { GlobalScope.launch(Dispatchers.IO) { val directory = File( 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 { - putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) + putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) } sendBroadcast(intent) } - private fun broadcastDownloadFinished(chapterNumber: String) { + private fun broadcastDownloadFinished(episodeNumber: String) { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FINISHED).apply { - putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) + putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) } sendBroadcast(intent) } - private fun broadcastDownloadFailed(chapterNumber: String) { + private fun broadcastDownloadFailed(episodeNumber: String) { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_FAILED).apply { - putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) + putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) } sendBroadcast(intent) } - private fun broadcastDownloadProgress(chapterNumber: String, progress: Int) { + private fun broadcastDownloadProgress(episodeNumber: String, progress: Int) { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_PROGRESS).apply { - putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, chapterNumber) + putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) putExtra("progress", progress) } sendBroadcast(intent) @@ -448,7 +456,7 @@ class AnimeDownloaderService : Service() { } - data class DownloadTask( + data class AnimeDownloadTask( val title: String, val episode: String, val video: Video, @@ -461,6 +469,12 @@ class AnimeDownloaderService : Service() { fun getTaskName(): String { return "$title - $episode" } + + companion object { + fun getTaskName(title: String, episode: String): String { + return "$title - $episode" + } + } } companion object { @@ -473,7 +487,7 @@ class AnimeDownloaderService : Service() { object AnimeServiceDataSingleton { var video: Video? = null var sourceMedia: Media? = null - var downloadQueue: Queue = ConcurrentLinkedQueue() + var downloadQueue: Queue = ConcurrentLinkedQueue() @Volatile var isServiceRunning: Boolean = false 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 3ccf4db0..af5d5cc0 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -230,7 +230,7 @@ object Helper { } } - val downloadTask = AnimeDownloaderService.DownloadTask( + val animeDownloadTask = AnimeDownloaderService.AnimeDownloadTask( title, episode, video, @@ -255,7 +255,7 @@ object Helper { getString(context, R.string.anime_downloads), Context.MODE_PRIVATE ).getString( - downloadTask.getTaskName(), + animeDownloadTask.getTaskName(), "" ) ?: "", false @@ -264,7 +264,7 @@ object Helper { getString(context, R.string.anime_downloads), Context.MODE_PRIVATE ).edit() - .remove(downloadTask.getTaskName()) + .remove(animeDownloadTask.getTaskName()) .apply() downloadsManger.removeDownload( DownloadedType( @@ -273,7 +273,7 @@ object Helper { DownloadedType.Type.ANIME ) ) - AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) + AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) if (!AnimeServiceDataSingleton.isServiceRunning) { val intent = Intent(context, AnimeDownloaderService::class.java) ContextCompat.startForegroundService(context, intent) @@ -283,7 +283,7 @@ object Helper { .setNegativeButton("No") { _, _ -> } .show() } else { - AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) + AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) if (!AnimeServiceDataSingleton.isServiceRunning) { val intent = Intent(context, AnimeDownloaderService::class.java) ContextCompat.startForegroundService(context, intent) diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index 8d6ac19d..ff3e2b29 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -242,7 +242,8 @@ class MediaDetailsViewModel : ViewModel() { i: String, manager: FragmentManager, launch: Boolean = true, - prevEp: String? = null + prevEp: String? = null, + isDownload: Boolean = false ) { Handler(Looper.getMainLooper()).post { if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) { @@ -254,13 +255,11 @@ class MediaDetailsViewModel : ViewModel() { } media.selected = this.loadSelected(media) val selector = - SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp) + SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp, isDownload) selector.show(manager, "dialog") } } } - - //Manga var mangaReadSources: MangaReadSources? = null 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 6ff3ac38..71807a02 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -2,6 +2,10 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint 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.Parcelable import android.view.LayoutInflater @@ -9,17 +13,25 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.Toast +import androidx.annotation.OptIn import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat import androidx.core.math.MathUtils import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels 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.GridLayoutManager import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* 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.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsViewModel @@ -43,6 +55,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import kotlin.math.ceil import kotlin.math.max import kotlin.math.roundToInt @@ -62,6 +76,8 @@ class AnimeWatchFragment : Fragment() { private lateinit var headerAdapter: AnimeWatchAdapter private lateinit var episodeAdapter: EpisodeAdapter + val downloadManager = Injekt.get() + var screenWidth = 0f private var progress = View.VISIBLE @@ -81,6 +97,21 @@ class AnimeWatchFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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) screenWidth = resources.displayMetrics.widthPixels.dp @@ -135,9 +166,15 @@ class AnimeWatchFragment : Fragment() { if (!loaded) { model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources + val offlineMode = model.watchSources!!.list[media.selected!!.sourceIndex].name == "Downloaded" + headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!) 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 = ConcatAdapter(headerAdapter, episodeAdapter) @@ -381,6 +418,90 @@ class AnimeWatchFragment : Fragment() { 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") private fun reload() { val selected = model.loadSelected(media) @@ -393,6 +514,8 @@ class AnimeWatchFragment : Fragment() { model.saveSelected(media.id, selected, requireActivity()) headerAdapter.handleEpisodes() + val isDownloaded = model.watchSources?.list?.get(selected.sourceIndex)?.name == "Downloaded" + episodeAdapter.offlineMode = isDownloaded episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size) var arr: ArrayList = arrayListOf() if (media.anime!!.episodes != null) { @@ -412,6 +535,7 @@ class AnimeWatchFragment : Fragment() { override fun onDestroy() { model.watchSources?.flushText() super.onDestroy() + requireContext().unregisterReceiver(downloadStatusReceiver) } var state: Parcelable? = null 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 cc4ce613..535174cd 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/Episode.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/Episode.kt @@ -14,6 +14,7 @@ data class Episode( var selectedExtractor: String? = null, var selectedVideo: Int = 0, var selectedSubtitle: Int? = -1, + var downloadProgress: String? = null, @Transient var extractors: MutableList? = null, @Transient var extractorCallback: ((VideoExtractor) -> Unit)? = null, var allStreams: Boolean = false, diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index f2984f13..625a6c51 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -4,7 +4,10 @@ import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.LinearInterpolator import android.widget.LinearLayout +import androidx.lifecycle.coroutineScope +import androidx.media3.exoplayer.offline.Download import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* import ani.dantotsu.connections.updateProgress @@ -14,6 +17,8 @@ import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.media.Media import com.bumptech.glide.Glide 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) { val curr = loadData("${mediaId}_${ep}") @@ -36,7 +41,8 @@ class EpisodeAdapter( private var type: Int, private val media: Media, private val fragment: AnimeWatchFragment, - var arr: List = arrayListOf() + var arr: List = arrayListOf(), + var offlineMode: Boolean ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -101,6 +107,7 @@ class EpisodeAdapter( binding.itemEpisodeFiller.visibility = View.GONE binding.itemEpisodeFillerView.visibility = View.GONE } + holder.bind(ep.number, ep.downloadProgress) binding.itemEpisodeDesc.visibility = if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE binding.itemEpisodeDesc.text = ep.desc ?: "" @@ -204,6 +211,61 @@ class EpisodeAdapter( override fun getItemCount(): Int = arr.size + private val activeDownloads = mutableSetOf() + private val downloadedEpisodes = mutableSetOf() + + 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) : RecyclerView.ViewHolder(binding.root) { init { @@ -226,11 +288,26 @@ class EpisodeAdapter( inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) : RecyclerView.ViewHolder(binding.root) { + private val activeCoroutines = mutableSetOf() init { itemView.setOnClickListener { if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0) 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 { if (binding.itemEpisodeDesc.maxLines == 3) binding.itemEpisodeDesc.maxLines = 100 @@ -238,6 +315,57 @@ class EpisodeAdapter( 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) { 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 ba979f80..93d00689 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -1328,18 +1328,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL ext.onVideoPlayed(video) } - val but = playerView.findViewById(R.id.exo_download) - if (video?.format == VideoType.CONTAINER || (loadData("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 httpClient = okHttpClient.newBuilder().apply { ignoreAllSSLErrors() 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 500696a1..ec129ad9 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -43,6 +43,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { private var makeDefault = false private var selected: String? = null private var launch: Boolean? = null + private var isDownload: Boolean? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -50,6 +51,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { selected = it.getString("server") launch = it.getBoolean("launch", true) prevEpisode = it.getString("prev") + isDownload = it.getBoolean("isDownload") } } @@ -77,8 +79,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { val ep = media?.anime?.episodes?.get(media?.anime?.selectedEpisode) episode = ep if (ep != null) { - - if (selected != null) { + if (isDownload == true) { + binding.selectorMakeDefault.visibility = View.GONE + } + if (selected != null && isDownload == false) { binding.selectorListContainer.visibility = View.GONE binding.selectorAutoListContainer.visibility = View.VISIBLE binding.selectorAutoText.text = selected @@ -258,11 +262,11 @@ 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.urlDownload.visibility = View.VISIBLE + if (isDownload == true) { + binding.urlDownload.visibility = View.VISIBLE + } else { + binding.urlDownload.visibility = View.GONE + } binding.urlDownload.setSafeOnClickListener { media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor = extractor.server.name @@ -314,6 +318,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { RecyclerView.ViewHolder(binding.root) { init { itemView.setSafeOnClickListener { + if (isDownload == true) { + binding.urlDownload.performClick() + return@setSafeOnClickListener + } tryWith(true) { media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor = extractor.server.name @@ -345,13 +353,15 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { fun newInstance( server: String? = null, la: Boolean = true, - prev: String? = null + prev: String? = null, + isDownload: Boolean ): SelectorDialogFragment = SelectorDialogFragment().apply { arguments = Bundle().apply { putString("server", server) putBoolean("launch", la) putString("prev", prev) + putBoolean("isDownload", isDownload) } } } 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 ceef7edc..8b1eedd4 100644 --- a/app/src/main/res/layout/exo_player_control_view.xml +++ b/app/src/main/res/layout/exo_player_control_view.xml @@ -148,18 +148,6 @@ app:tint="#fff" tools:ignore="ContentDescription,SpeakableTextPresentCheck" /> - - @@ -130,8 +131,28 @@ android:text="@string/empty" app:lineHeight="15sp" /> + + + +