From 6a42832855c661e28b58a2875e93d077f632a865 Mon Sep 17 00:00:00 2001 From: rebelonion <87634197+rebelonion@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:05:29 -0600 Subject: [PATCH] view download status --- .../dantotsu/media/MediaDetailsViewModel.kt | 6 +++ .../media/anime/AnimeWatchFragment.kt | 11 +++--- .../dantotsu/media/anime/EpisodeAdapters.kt | 38 ++++++++++++++++-- .../media/anime/SelectorDialogFragment.kt | 39 +++++++++++++++---- .../java/ani/dantotsu/parsers/BaseSources.kt | 3 ++ app/src/main/res/layout/item_episode_list.xml | 31 ++++++++------- 6 files changed, 96 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index ff3e2b29..7730e885 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -1,19 +1,24 @@ package ani.dantotsu.media import android.app.Activity +import android.content.Intent import android.content.SharedPreferences import android.os.Handler import android.os.Looper +import androidx.annotation.OptIn import androidx.fragment.app.FragmentManager import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.media3.common.util.UnstableApi import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.currActivity import ani.dantotsu.currContext import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.anime.Episode +import ani.dantotsu.media.anime.ExoplayerView import ani.dantotsu.media.anime.SelectorDialogFragment import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.others.AniSkip @@ -260,6 +265,7 @@ class MediaDetailsViewModel : ViewModel() { } } } + //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 71807a02..6d4318a7 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -166,16 +166,12 @@ class AnimeWatchFragment : Fragment() { if (!loaded) { model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources - val offlineMode = model.watchSources!!.list[media.selected!!.sourceIndex].name == "Downloaded" + val offlineMode = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex) headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!) episodeAdapter = EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this, offlineMode = offlineMode) - for (download in downloadManager.animeDownloadedTypes) { - episodeAdapter.stopDownload(download.chapter) - } - binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, episodeAdapter) @@ -514,7 +510,7 @@ class AnimeWatchFragment : Fragment() { model.saveSelected(media.id, selected, requireActivity()) headerAdapter.handleEpisodes() - val isDownloaded = model.watchSources?.list?.get(selected.sourceIndex)?.name == "Downloaded" + val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex) episodeAdapter.offlineMode = isDownloaded episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size) var arr: ArrayList = arrayListOf() @@ -530,6 +526,9 @@ class AnimeWatchFragment : Fragment() { episodeAdapter.arr = arr episodeAdapter.updateType(style ?: uiSettings.animeDefaultView) episodeAdapter.notifyItemRangeInserted(0, arr.size) + for (download in downloadManager.animeDownloadedTypes) { + episodeAdapter.stopDownload(download.chapter) + } } override fun onDestroy() { 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 625a6c51..636714c0 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -1,12 +1,16 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.LinearInterpolator import android.widget.LinearLayout +import androidx.annotation.OptIn +import androidx.core.content.ContextCompat import androidx.lifecycle.coroutineScope +import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.Download import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* @@ -14,11 +18,15 @@ import ani.dantotsu.connections.updateProgress import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeListBinding +import ani.dantotsu.download.anime.AnimeDownloaderService +import ani.dantotsu.download.video.Helper 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 +import kotlin.math.ln +import kotlin.math.pow fun handleProgress(cont: LinearLayout, bar: View, empty: View, mediaId: Int, ep: String) { val curr = loadData("${mediaId}_${ep}") @@ -98,7 +106,7 @@ class EpisodeAdapter( Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) .into(binding.itemEpisodeImage) binding.itemEpisodeNumber.text = ep.number - binding.itemEpisodeTitle.text = title + binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title if (ep.filler) { binding.itemEpisodeFiller.visibility = View.VISIBLE @@ -223,13 +231,26 @@ class EpisodeAdapter( } } + @OptIn(UnstableApi::class) 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" + val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), episodeNumber) + val id = fragment.requireContext().getSharedPreferences( + ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads), + Context.MODE_PRIVATE + ).getString( + taskName, + "" + ) ?: "" + val index = Helper.downloadManager(fragment.requireContext()).downloadIndex + val download = index.getDownload(id) + val size = bytesToHuman(download?.bytesDownloaded?:0) + + arr[position].downloadProgress = "Downloaded" + if (size != null) ": ($size)" else "" notifyItemChanged(position) } } @@ -319,7 +340,7 @@ class EpisodeAdapter( fun bind(episodeNumber: String, progress: String?) { if (progress != null) { binding.itemDownloadStatus.visibility = View.VISIBLE - binding.itemDownloadStatus.text = "$progress" + binding.itemDownloadStatus.text = progress } else { binding.itemDownloadStatus.visibility = View.GONE binding.itemDownloadStatus.text = "" @@ -329,6 +350,7 @@ class EpisodeAdapter( binding.itemDownload.setImageResource(R.drawable.ic_sync) startOrContinueRotation(episodeNumber) } else if (downloadedEpisodes.contains(episodeNumber)) { + binding.itemDownloadStatus.visibility = View.VISIBLE // Show checkmark binding.itemDownload.setImageResource(R.drawable.ic_circle_check) //binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places @@ -338,6 +360,7 @@ class EpisodeAdapter( //binding.itemDownload.setColorFilter(typedValue2.data) }, 1000) } else { + binding.itemDownloadStatus.visibility = View.GONE // Show download icon binding.itemDownload.setImageResource(R.drawable.ic_circle_add) } @@ -371,5 +394,14 @@ class EpisodeAdapter( fun updateType(t: Int) { type = t } + + private fun bytesToHuman(bytes: Long): String? { + if (bytes < 0) return null + val unit = 1000 + if (bytes < unit) return "$bytes B" + val exp = (Math.log(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = ("KMGTPE")[exp - 1] + return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) + } } 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 ec129ad9..feec1d7d 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.graphics.Color import android.net.Uri import android.os.Bundle +import android.util.Log import android.util.TypedValue import android.view.HapticFeedbackConstants import android.view.LayoutInflater @@ -31,6 +32,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.text.DecimalFormat +import java.util.concurrent.CountDownLatch + class SelectorDialogFragment : BottomSheetDialogFragment() { private var _binding: BottomSheetSelectorBinding? = null @@ -43,7 +46,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { private var makeDefault = false private var selected: String? = null private var launch: Boolean? = null - private var isDownload: Boolean? = null + private var isDownloadMenu: Boolean? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -51,7 +54,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { selected = it.getString("server") launch = it.getBoolean("launch", true) prevEpisode = it.getString("prev") - isDownload = it.getBoolean("isDownload") + isDownloadMenu = it.getBoolean("isDownload") } } @@ -79,10 +82,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { val ep = media?.anime?.episodes?.get(media?.anime?.selectedEpisode) episode = ep if (ep != null) { - if (isDownload == true) { + if (isDownloadMenu == true) { binding.selectorMakeDefault.visibility = View.GONE } - if (selected != null && isDownload == false) { + + if (selected != null && isDownloadMenu == false) { binding.selectorListContainer.visibility = View.GONE binding.selectorAutoListContainer.visibility = View.VISIBLE binding.selectorAutoText.text = selected @@ -100,7 +104,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { fun load() { val size = - ep.extractors?.find { it.server.name == selected }?.videos?.size + if (model.watchSources!!.isDownloadedSource(media!!.selected!!.sourceIndex)) { + ep.extractors?.firstOrNull()?.videos?.size + } else { + ep.extractors?.find { it.server.name == selected }?.videos?.size + } + if (size != null && size >= media!!.selected!!.video) { media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedExtractor = selected @@ -150,6 +159,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { ep.extractorCallback = { scope.launch { adapter.add(it) + if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) { + adapter.perfromClick(0) + } } } model.getEpisode().observe(this) { @@ -169,6 +181,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { } else { media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep) adapter.addAll(ep.extractors) + if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) { + adapter.perfromClick(0) + } binding.selectorProgressBar.visibility = View.GONE } } @@ -184,7 +199,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { prevEpisode = null dismiss() - if (launch!!) { + if (launch!! || model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)) { stopAddingToList() val intent = Intent(activity, ExoplayerView::class.java) ExoplayerView.media = media @@ -241,6 +256,14 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { notifyItemRangeInserted(0, extractors.size) } + fun perfromClick(position: Int) { + val extractor = links[position] + media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor = + extractor.server.name + media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = 0 + startExoplayer(media!!) + } + private inner class StreamViewHolder(val binding: ItemStreamBinding) : RecyclerView.ViewHolder(binding.root) } @@ -262,7 +285,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { val binding = holder.binding val video = extractor.videos[position] - if (isDownload == true) { + if (isDownloadMenu == true) { binding.urlDownload.visibility = View.VISIBLE } else { binding.urlDownload.visibility = View.GONE @@ -318,7 +341,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { RecyclerView.ViewHolder(binding.root) { init { itemView.setSafeOnClickListener { - if (isDownload == true) { + if (isDownloadMenu == true) { binding.urlDownload.performClick() return@setSafeOnClickListener } diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt index 95fd4d63..5aa6ffc2 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt @@ -16,6 +16,9 @@ abstract class WatchSources : BaseSources() { ?: EmptyAnimeParser() } + fun isDownloadedSource(i: Int): Boolean { + return get(i) is OfflineAnimeParser + } suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap { return tryWithSuspend(true) { diff --git a/app/src/main/res/layout/item_episode_list.xml b/app/src/main/res/layout/item_episode_list.xml index 1e863f9b..28b5ceb2 100644 --- a/app/src/main/res/layout/item_episode_list.xml +++ b/app/src/main/res/layout/item_episode_list.xml @@ -47,8 +47,8 @@ @@ -77,7 +77,7 @@ - - + +