view download status

This commit is contained in:
rebelonion 2024-01-16 22:05:29 -06:00
parent 84fc5e6e2c
commit 6a42832855
6 changed files with 96 additions and 32 deletions

View file

@ -1,19 +1,24 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.OptIn
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.media3.common.util.UnstableApi
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.loadData import ani.dantotsu.loadData
import ani.dantotsu.logger import ani.dantotsu.logger
import ani.dantotsu.media.anime.Episode import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.anime.ExoplayerView
import ani.dantotsu.media.anime.SelectorDialogFragment import ani.dantotsu.media.anime.SelectorDialogFragment
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.AniSkip import ani.dantotsu.others.AniSkip
@ -260,6 +265,7 @@ class MediaDetailsViewModel : ViewModel() {
} }
} }
} }
//Manga //Manga
var mangaReadSources: MangaReadSources? = null var mangaReadSources: MangaReadSources? = null

View file

@ -166,16 +166,12 @@ 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" val offlineMode = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!) headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
episodeAdapter = episodeAdapter =
EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this, offlineMode = offlineMode) 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)
@ -514,7 +510,7 @@ 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" val isDownloaded = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
episodeAdapter.offlineMode = isDownloaded 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()
@ -530,6 +526,9 @@ class AnimeWatchFragment : Fragment() {
episodeAdapter.arr = arr episodeAdapter.arr = arr
episodeAdapter.updateType(style ?: uiSettings.animeDefaultView) episodeAdapter.updateType(style ?: uiSettings.animeDefaultView)
episodeAdapter.notifyItemRangeInserted(0, arr.size) episodeAdapter.notifyItemRangeInserted(0, arr.size)
for (download in downloadManager.animeDownloadedTypes) {
episodeAdapter.stopDownload(download.chapter)
}
} }
override fun onDestroy() { override fun onDestroy() {

View file

@ -1,12 +1,16 @@
package ani.dantotsu.media.anime package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
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.view.animation.LinearInterpolator
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.Download
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
@ -14,11 +18,15 @@ import ani.dantotsu.connections.updateProgress
import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding
import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.databinding.ItemEpisodeListBinding
import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.video.Helper
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.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.ln
import kotlin.math.pow
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}")
@ -98,7 +106,7 @@ class EpisodeAdapter(
Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0)
.into(binding.itemEpisodeImage) .into(binding.itemEpisodeImage)
binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeNumber.text = ep.number
binding.itemEpisodeTitle.text = title binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title
if (ep.filler) { if (ep.filler) {
binding.itemEpisodeFiller.visibility = View.VISIBLE binding.itemEpisodeFiller.visibility = View.VISIBLE
@ -223,13 +231,26 @@ class EpisodeAdapter(
} }
} }
@OptIn(UnstableApi::class)
fun stopDownload(episodeNumber: String) { fun stopDownload(episodeNumber: String) {
activeDownloads.remove(episodeNumber) activeDownloads.remove(episodeNumber)
downloadedEpisodes.add(episodeNumber) downloadedEpisodes.add(episodeNumber)
// Find the position of the chapter and notify only that item // Find the position of the chapter and notify only that item
val position = arr.indexOfFirst { it.number == episodeNumber } val position = arr.indexOfFirst { it.number == episodeNumber }
if (position != -1) { 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) notifyItemChanged(position)
} }
} }
@ -319,7 +340,7 @@ class EpisodeAdapter(
fun bind(episodeNumber: String, progress: String?) { fun bind(episodeNumber: String, progress: String?) {
if (progress != null) { if (progress != null) {
binding.itemDownloadStatus.visibility = View.VISIBLE binding.itemDownloadStatus.visibility = View.VISIBLE
binding.itemDownloadStatus.text = "$progress" binding.itemDownloadStatus.text = progress
} else { } else {
binding.itemDownloadStatus.visibility = View.GONE binding.itemDownloadStatus.visibility = View.GONE
binding.itemDownloadStatus.text = "" binding.itemDownloadStatus.text = ""
@ -329,6 +350,7 @@ class EpisodeAdapter(
binding.itemDownload.setImageResource(R.drawable.ic_sync) binding.itemDownload.setImageResource(R.drawable.ic_sync)
startOrContinueRotation(episodeNumber) startOrContinueRotation(episodeNumber)
} else if (downloadedEpisodes.contains(episodeNumber)) { } else if (downloadedEpisodes.contains(episodeNumber)) {
binding.itemDownloadStatus.visibility = View.VISIBLE
// Show checkmark // Show checkmark
binding.itemDownload.setImageResource(R.drawable.ic_circle_check) binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places //binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
@ -338,6 +360,7 @@ class EpisodeAdapter(
//binding.itemDownload.setColorFilter(typedValue2.data) //binding.itemDownload.setColorFilter(typedValue2.data)
}, 1000) }, 1000)
} else { } else {
binding.itemDownloadStatus.visibility = View.GONE
// Show download icon // Show download icon
binding.itemDownload.setImageResource(R.drawable.ic_circle_add) binding.itemDownload.setImageResource(R.drawable.ic_circle_add)
} }
@ -371,5 +394,14 @@ class EpisodeAdapter(
fun updateType(t: Int) { fun updateType(t: Int) {
type = t 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)
}
} }

View file

@ -6,6 +6,7 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.LayoutInflater import android.view.LayoutInflater
@ -31,6 +32,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.concurrent.CountDownLatch
class SelectorDialogFragment : BottomSheetDialogFragment() { class SelectorDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetSelectorBinding? = null private var _binding: BottomSheetSelectorBinding? = null
@ -43,7 +46,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 private var isDownloadMenu: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -51,7 +54,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") isDownloadMenu = it.getBoolean("isDownload")
} }
} }
@ -79,10 +82,11 @@ 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 (isDownloadMenu == true) {
binding.selectorMakeDefault.visibility = View.GONE binding.selectorMakeDefault.visibility = View.GONE
} }
if (selected != null && isDownload == false) {
if (selected != null && isDownloadMenu == 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
@ -100,7 +104,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
fun load() { fun load() {
val size = 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) { if (size != null && size >= media!!.selected!!.video) {
media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedExtractor = media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedExtractor =
selected selected
@ -150,6 +159,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
ep.extractorCallback = { ep.extractorCallback = {
scope.launch { scope.launch {
adapter.add(it) adapter.add(it)
if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) {
adapter.perfromClick(0)
}
} }
} }
model.getEpisode().observe(this) { model.getEpisode().observe(this) {
@ -169,6 +181,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
} else { } else {
media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep) media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep)
adapter.addAll(ep.extractors) adapter.addAll(ep.extractors)
if (model.watchSources!!.isDownloadedSource(media?.selected!!.sourceIndex)) {
adapter.perfromClick(0)
}
binding.selectorProgressBar.visibility = View.GONE binding.selectorProgressBar.visibility = View.GONE
} }
} }
@ -184,7 +199,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
prevEpisode = null prevEpisode = null
dismiss() dismiss()
if (launch!!) { if (launch!! || model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)) {
stopAddingToList() stopAddingToList()
val intent = Intent(activity, ExoplayerView::class.java) val intent = Intent(activity, ExoplayerView::class.java)
ExoplayerView.media = media ExoplayerView.media = media
@ -241,6 +256,14 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
notifyItemRangeInserted(0, extractors.size) 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) : private inner class StreamViewHolder(val binding: ItemStreamBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }
@ -262,7 +285,7 @@ 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]
if (isDownload == true) { if (isDownloadMenu == true) {
binding.urlDownload.visibility = View.VISIBLE binding.urlDownload.visibility = View.VISIBLE
} else { } else {
binding.urlDownload.visibility = View.GONE binding.urlDownload.visibility = View.GONE
@ -318,7 +341,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
init { init {
itemView.setSafeOnClickListener { itemView.setSafeOnClickListener {
if (isDownload == true) { if (isDownloadMenu == true) {
binding.urlDownload.performClick() binding.urlDownload.performClick()
return@setSafeOnClickListener return@setSafeOnClickListener
} }

View file

@ -16,6 +16,9 @@ abstract class WatchSources : BaseSources() {
?: EmptyAnimeParser() ?: EmptyAnimeParser()
} }
fun isDownloadedSource(i: Int): Boolean {
return get(i) is OfflineAnimeParser
}
suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> { suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> {
return tryWithSuspend(true) { return tryWithSuspend(true) {

View file

@ -47,8 +47,8 @@
<ImageView <ImageView
android:id="@+id/itemEpisodeImage" android:id="@+id/itemEpisodeImage"
android:layout_width="180dp" android:layout_width="140dp"
android:layout_height="109dp" android:layout_height="85dp"
android:layout_gravity="center" android:layout_gravity="center"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@ -77,7 +77,7 @@
<LinearLayout <LinearLayout
android:id="@+id/itemEpisodeProgressCont" android:id="@+id/itemEpisodeProgressCont"
android:layout_width="180dp" android:layout_width="140dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
@ -106,7 +106,7 @@
android:layout_margin="8dp" android:layout_margin="8dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:minHeight="92dp" android:minHeight="69dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -131,17 +131,6 @@
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 <ImageButton
@ -155,6 +144,18 @@
</LinearLayout> </LinearLayout>
<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=""
android:padding="16dp"
app:lineHeight="15sp" />
<TextView <TextView
android:id="@+id/itemEpisodeDesc" android:id="@+id/itemEpisodeDesc"
android:layout_width="match_parent" android:layout_width="match_parent"