mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Downloads: performance improvements and merge adapters (#1145)
This commit is contained in:
parent
b9746c2b17
commit
b06d9f224d
13 changed files with 488 additions and 527 deletions
|
@ -0,0 +1,223 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
|
||||||
|
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
||||||
|
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
||||||
|
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
|
||||||
|
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
||||||
|
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
||||||
|
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
||||||
|
|
||||||
|
abstract class VisualDownloadCached(
|
||||||
|
open val currentBytes: Long,
|
||||||
|
open val totalBytes: Long,
|
||||||
|
open val data: VideoDownloadHelper.DownloadCached
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Just to be extra-safe with areContentsTheSame
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is VisualDownloadCached) return false
|
||||||
|
|
||||||
|
if (currentBytes != other.currentBytes) return false
|
||||||
|
if (totalBytes != other.totalBytes) return false
|
||||||
|
if (data != other.data) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = currentBytes.hashCode()
|
||||||
|
result = 31 * result + totalBytes.hashCode()
|
||||||
|
result = 31 * result + data.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class VisualDownloadChildCached(
|
||||||
|
override val currentBytes: Long,
|
||||||
|
override val totalBytes: Long,
|
||||||
|
override val data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
): VisualDownloadCached(currentBytes, totalBytes, data)
|
||||||
|
|
||||||
|
data class VisualDownloadHeaderCached(
|
||||||
|
override val currentBytes: Long,
|
||||||
|
override val totalBytes: Long,
|
||||||
|
override val data: VideoDownloadHelper.DownloadHeaderCached,
|
||||||
|
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
||||||
|
val currentOngoingDownloads: Int,
|
||||||
|
val totalDownloads: Int,
|
||||||
|
): VisualDownloadCached(currentBytes, totalBytes, data)
|
||||||
|
|
||||||
|
data class DownloadClickEvent(
|
||||||
|
val action: Int,
|
||||||
|
val data: VideoDownloadHelper.DownloadEpisodeCached
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DownloadHeaderClickEvent(
|
||||||
|
val action: Int,
|
||||||
|
val data: VideoDownloadHelper.DownloadHeaderCached
|
||||||
|
)
|
||||||
|
|
||||||
|
class DownloadAdapter(
|
||||||
|
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
|
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VIEW_TYPE_HEADER = 0
|
||||||
|
private const val VIEW_TYPE_CHILD = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DownloadViewHolder(
|
||||||
|
private val binding: ViewBinding,
|
||||||
|
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
|
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun bind(card: VisualDownloadCached?) {
|
||||||
|
when (binding) {
|
||||||
|
is DownloadHeaderEpisodeBinding -> binding.apply {
|
||||||
|
if (card == null || card !is VisualDownloadHeaderCached) return@apply
|
||||||
|
val d = card.data
|
||||||
|
|
||||||
|
downloadHeaderPoster.apply {
|
||||||
|
setImage(d.poster)
|
||||||
|
setOnClickListener {
|
||||||
|
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadHeaderTitle.text = d.name
|
||||||
|
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
||||||
|
|
||||||
|
if (card.child != null) {
|
||||||
|
downloadHeaderGotoChild.isVisible = false
|
||||||
|
|
||||||
|
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
||||||
|
downloadButton.isVisible = true
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
mediaClickCallback.invoke(
|
||||||
|
DownloadClickEvent(
|
||||||
|
DOWNLOAD_ACTION_PLAY_FILE,
|
||||||
|
card.child
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadButton.isVisible = false
|
||||||
|
downloadHeaderGotoChild.isVisible = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadHeaderInfo.text =
|
||||||
|
downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
||||||
|
.format(
|
||||||
|
card.totalDownloads,
|
||||||
|
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(
|
||||||
|
R.string.episode
|
||||||
|
) else downloadHeaderInfo.context.getString(
|
||||||
|
R.string.episodes
|
||||||
|
),
|
||||||
|
mbString
|
||||||
|
)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// You probably formatted incorrectly
|
||||||
|
downloadHeaderInfo.text = "Error"
|
||||||
|
logError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is DownloadChildEpisodeBinding -> binding.apply {
|
||||||
|
if (card == null || card !is VisualDownloadChildCached) return@apply
|
||||||
|
val d = card.data
|
||||||
|
|
||||||
|
val posDur = DataStoreHelper.getViewPos(d.id)
|
||||||
|
downloadChildEpisodeProgress.apply {
|
||||||
|
if (posDur != null) {
|
||||||
|
val visualPos = posDur.fixVisual()
|
||||||
|
max = (visualPos.duration / 1000).toInt()
|
||||||
|
progress = (visualPos.position / 1000).toInt()
|
||||||
|
isVisible = true
|
||||||
|
} else isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback)
|
||||||
|
|
||||||
|
downloadChildEpisodeText.apply {
|
||||||
|
text = context.getNameFull(d.name, d.episode, d.season)
|
||||||
|
isSelected = true // Needed for text repeating
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadChildEpisodeHolder.setOnClickListener {
|
||||||
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
|
||||||
|
val binding = when (viewType) {
|
||||||
|
VIEW_TYPE_HEADER -> {
|
||||||
|
DownloadHeaderEpisodeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_CHILD -> {
|
||||||
|
DownloadChildEpisodeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("Invalid view type")
|
||||||
|
}
|
||||||
|
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
|
||||||
|
holder.bind(getItem(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
val card = getItem(position)
|
||||||
|
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
||||||
|
override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
|
||||||
|
return oldItem.data.id == newItem.data.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
@ -22,7 +21,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
object DownloadButtonSetup {
|
object DownloadButtonSetup {
|
||||||
fun handleDownloadClick(click: DownloadClickEvent) {
|
fun handleDownloadClick(click: DownloadClickEvent) {
|
||||||
val id = click.data.id
|
val id = click.data.id
|
||||||
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
|
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
DOWNLOAD_ACTION_DELETE_FILE -> {
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|
||||||
|
|
||||||
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
|
||||||
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
|
||||||
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
|
|
||||||
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
|
||||||
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
|
||||||
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
|
||||||
|
|
||||||
data class VisualDownloadChildCached(
|
|
||||||
val currentBytes: Long,
|
|
||||||
val totalBytes: Long,
|
|
||||||
val data: VideoDownloadHelper.DownloadEpisodeCached,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DownloadClickEvent(val action: Int, val data: VideoDownloadHelper.DownloadEpisodeCached)
|
|
||||||
|
|
||||||
class DownloadChildAdapter(
|
|
||||||
var cardList: List<VisualDownloadChildCached>,
|
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return DownloadChildViewHolder(
|
|
||||||
DownloadChildEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
|
||||||
clickCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
when (holder) {
|
|
||||||
is DownloadChildViewHolder -> {
|
|
||||||
holder.bind(cardList[position])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return cardList.size
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadChildViewHolder
|
|
||||||
constructor(
|
|
||||||
val binding: DownloadChildEpisodeBinding,
|
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
/*private val title: TextView = itemView.download_child_episode_text
|
|
||||||
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
|
||||||
private val holder: CardView = itemView.download_child_episode_holder
|
|
||||||
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress
|
|
||||||
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
|
||||||
private val downloadImage: ImageView = itemView.download_child_episode_download*/
|
|
||||||
|
|
||||||
|
|
||||||
fun bind(card: VisualDownloadChildCached) {
|
|
||||||
val d = card.data
|
|
||||||
|
|
||||||
val posDur = getViewPos(d.id)
|
|
||||||
binding.downloadChildEpisodeProgress.apply {
|
|
||||||
if (posDur != null) {
|
|
||||||
val visualPos = posDur.fixVisual()
|
|
||||||
max = (visualPos.duration / 1000).toInt()
|
|
||||||
progress = (visualPos.position / 1000).toInt()
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.downloadButton.setDefaultClickListener(card.data, binding.downloadChildEpisodeTextExtra, clickCallback)
|
|
||||||
|
|
||||||
binding.downloadChildEpisodeText.apply {
|
|
||||||
text = context.getNameFull(d.name, d.episode, d.season)
|
|
||||||
isSelected = true // is needed for text repeating
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
binding.downloadChildEpisodeHolder.setOnClickListener {
|
|
||||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
|
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
|
@ -40,7 +39,8 @@ class DownloadChildFragment : Fragment() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
var binding: FragmentChildDownloadsBinding? = null
|
private var binding: FragmentChildDownloadsBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -48,7 +48,7 @@ class DownloadChildFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
|
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
|
||||||
binding = localBinding
|
binding = localBinding
|
||||||
return localBinding.root//inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
return localBinding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateList(folder: String) = main {
|
private fun updateList(folder: String) = main {
|
||||||
|
@ -60,7 +60,11 @@ class DownloadChildFragment : Fragment() {
|
||||||
}.mapNotNull {
|
}.mapNotNull {
|
||||||
val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(ctx, it.id)
|
val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(ctx, it.id)
|
||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
VisualDownloadChildCached(info.fileLength, info.totalBytes, it)
|
VisualDownloadChildCached(
|
||||||
|
currentBytes = info.fileLength,
|
||||||
|
totalBytes = info.totalBytes,
|
||||||
|
data = it,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
|
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
|
||||||
if (eps.isEmpty()) {
|
if (eps.isEmpty()) {
|
||||||
|
@ -68,9 +72,7 @@ class DownloadChildFragment : Fragment() {
|
||||||
return@main
|
return@main
|
||||||
}
|
}
|
||||||
|
|
||||||
(binding?.downloadChildList?.adapter as DownloadChildAdapter? ?: return@main).cardList =
|
(binding?.downloadChildList?.adapter as? DownloadAdapter)?.submitList(eps)
|
||||||
eps
|
|
||||||
binding?.downloadChildList?.adapter?.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,31 +100,39 @@ class DownloadChildFragment : Fragment() {
|
||||||
setAppBarNoScrollFlagsOnTV()
|
setAppBarNoScrollFlagsOnTV()
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val adapter = DownloadAdapter(
|
||||||
DownloadChildAdapter(
|
{},
|
||||||
ArrayList(),
|
{ downloadClickEvent ->
|
||||||
) { click ->
|
handleDownloadClick(downloadClickEvent)
|
||||||
handleDownloadClick(click)
|
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||||
|
setUpDownloadDeleteListener(folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding?.downloadChildList?.apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
setItemViewCacheSize(20)
|
||||||
|
this.adapter = adapter
|
||||||
|
setLinearListLayout(
|
||||||
|
isHorizontal = false,
|
||||||
|
nextRight = FOCUS_SELF,
|
||||||
|
nextDown = FOCUS_SELF,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateList(folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpDownloadDeleteListener(folder: String) {
|
||||||
downloadDeleteEventListener = { id: Int ->
|
downloadDeleteEventListener = { id: Int ->
|
||||||
val list = (binding?.downloadChildList?.adapter as DownloadChildAdapter?)?.cardList
|
val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
if (list.any { it.data.id == id }) {
|
if (list.any { it.data.id == id }) {
|
||||||
updateList(folder)
|
updateList(folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
binding?.downloadChildList?.adapter = adapter
|
|
||||||
binding?.downloadChildList?.setLinearListLayout(
|
|
||||||
isHorizontal = false,
|
|
||||||
nextDown = FOCUS_SELF,
|
|
||||||
nextRight = FOCUS_SELF
|
|
||||||
)//layoutManager = GridLayoutManager(context, 1)
|
|
||||||
|
|
||||||
updateList(folder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,14 +10,15 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
|
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
|
||||||
|
@ -42,11 +43,9 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
|
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
|
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
|
||||||
|
|
||||||
class DownloadFragment : Fragment() {
|
class DownloadFragment : Fragment() {
|
||||||
|
@ -63,33 +62,30 @@ class DownloadFragment : Fragment() {
|
||||||
|
|
||||||
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
||||||
main {
|
main {
|
||||||
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
|
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
|
||||||
binding?.downloadList?.adapter?.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
if (downloadDeleteEventListener != null) {
|
downloadDeleteEventListener?.let {
|
||||||
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!!
|
VideoDownloadManager.downloadDeleteEvent -= it
|
||||||
downloadDeleteEventListener = null
|
|
||||||
}
|
}
|
||||||
|
downloadDeleteEventListener = null
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
var binding: FragmentDownloadsBinding? = null
|
private var binding: FragmentDownloadsBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
downloadsViewModel =
|
downloadsViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
ViewModelProvider(this)[DownloadViewModel::class.java]
|
|
||||||
|
|
||||||
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
|
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
|
||||||
binding = localBinding
|
binding = localBinding
|
||||||
return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false)
|
return localBinding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||||
|
@ -97,7 +93,6 @@ class DownloadFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
|
||||||
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
||||||
|
|
||||||
observe(downloadsViewModel.noDownloadsText) {
|
observe(downloadsViewModel.noDownloadsText) {
|
||||||
|
@ -108,135 +103,109 @@ class DownloadFragment : Fragment() {
|
||||||
binding?.downloadLoading?.isVisible = false
|
binding?.downloadLoading?.isVisible = false
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.availableBytes) {
|
observe(downloadsViewModel.availableBytes) {
|
||||||
binding?.downloadFreeTxt?.text =
|
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
|
||||||
getString(R.string.storage_size_format).format(
|
|
||||||
getString(R.string.free_storage),
|
|
||||||
formatShortFileSize(view.context, it)
|
|
||||||
)
|
|
||||||
binding?.downloadFree?.setLayoutWidth(it)
|
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.usedBytes) {
|
observe(downloadsViewModel.usedBytes) {
|
||||||
binding?.apply {
|
updateStorageInfo(view.context, it, R.string.used_storage, binding?.downloadUsedTxt, binding?.downloadUsed)
|
||||||
downloadUsedTxt.text =
|
binding?.downloadStorageAppbar?.isVisible = it > 0
|
||||||
getString(R.string.storage_size_format).format(
|
|
||||||
getString(R.string.used_storage),
|
|
||||||
formatShortFileSize(view.context, it)
|
|
||||||
)
|
|
||||||
downloadUsed.setLayoutWidth(it)
|
|
||||||
downloadStorageAppbar.isVisible = it > 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.downloadBytes) {
|
observe(downloadsViewModel.downloadBytes) {
|
||||||
binding?.apply {
|
updateStorageInfo(view.context, it, R.string.app_storage, binding?.downloadAppTxt, binding?.downloadApp)
|
||||||
downloadAppTxt.text =
|
|
||||||
getString(R.string.storage_size_format).format(
|
|
||||||
getString(R.string.app_storage),
|
|
||||||
formatShortFileSize(view.context, it)
|
|
||||||
)
|
|
||||||
downloadApp.setLayoutWidth(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val adapter = DownloadAdapter(
|
||||||
DownloadHeaderAdapter(
|
|
||||||
ArrayList(),
|
|
||||||
{ click ->
|
{ click ->
|
||||||
|
handleItemClick(click)
|
||||||
|
},
|
||||||
|
{ downloadClickEvent ->
|
||||||
|
handleDownloadClick(downloadClickEvent)
|
||||||
|
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||||
|
setUpDownloadDeleteListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding?.downloadList?.apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
setItemViewCacheSize(20)
|
||||||
|
this.adapter = adapter
|
||||||
|
setLinearListLayout(
|
||||||
|
isHorizontal = false,
|
||||||
|
nextRight = FOCUS_SELF,
|
||||||
|
nextUp = FOCUS_SELF,
|
||||||
|
nextDown = FOCUS_SELF,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding?.downloadStreamButton?.apply {
|
||||||
|
isGone = isLayout(TV)
|
||||||
|
setOnClickListener { showStreamInputDialog(it.context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
handleScroll(scrollY - oldScrollY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadsViewModel.updateList(requireContext())
|
||||||
|
fixPaddingStatusbar(binding?.downloadRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
0 -> {
|
0 -> {
|
||||||
if (click.data.type.isMovieType()) {
|
if (!click.data.type.isMovieType()) {
|
||||||
//wont be called
|
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
|
||||||
} else {
|
|
||||||
val folder = DataStore.getFolderName(
|
|
||||||
DOWNLOAD_EPISODE_CACHE,
|
|
||||||
click.data.id.toString()
|
|
||||||
)
|
|
||||||
activity?.navigate(
|
activity?.navigate(
|
||||||
R.id.action_navigation_downloads_to_navigation_download_child,
|
R.id.action_navigation_downloads_to_navigation_download_child,
|
||||||
DownloadChildFragment.newInstance(click.data.name, folder)
|
DownloadChildFragment.newInstance(click.data.name, folder)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
(activity as AppCompatActivity?)?.loadResult(
|
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
|
||||||
click.data.url,
|
}
|
||||||
click.data.apiName
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
private fun setUpDownloadDeleteListener() {
|
||||||
{ downloadClickEvent ->
|
|
||||||
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
|
|
||||||
handleDownloadClick(downloadClickEvent)
|
|
||||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
|
||||||
context?.let { ctx ->
|
|
||||||
downloadsViewModel.updateList(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadDeleteEventListener = { id ->
|
downloadDeleteEventListener = { id ->
|
||||||
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
|
val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList
|
||||||
if (list != null) {
|
if (list?.any { it.data.id == id } == true) {
|
||||||
if (list.any { it.data.id == id }) {
|
context?.let { downloadsViewModel.updateList(it) }
|
||||||
context?.let { ctx ->
|
|
||||||
setList(ArrayList())
|
|
||||||
downloadsViewModel.updateList(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
binding?.downloadList?.apply {
|
|
||||||
this.adapter = adapter
|
|
||||||
setLinearListLayout(
|
|
||||||
isHorizontal = false,
|
|
||||||
nextRight = FOCUS_SELF,
|
|
||||||
nextUp = FOCUS_SELF,
|
|
||||||
nextDown = FOCUS_SELF
|
|
||||||
)
|
|
||||||
//layoutManager = GridLayoutManager(context, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be visible in emulator layout
|
private fun updateStorageInfo(
|
||||||
binding?.downloadStreamButton?.isGone = isLayout(TV)
|
context: Context,
|
||||||
binding?.downloadStreamButton?.setOnClickListener {
|
bytes: Long,
|
||||||
val dialog =
|
@StringRes stringRes: Int,
|
||||||
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
|
textView: TextView?,
|
||||||
|
view: View?
|
||||||
|
) {
|
||||||
|
textView?.text = getString(R.string.storage_size_format).format(getString(stringRes), formatShortFileSize(context, bytes))
|
||||||
|
view?.setLayoutWidth(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showStreamInputDialog(context: Context) {
|
||||||
|
val dialog = Dialog(context, R.style.AlertDialogCustom)
|
||||||
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
|
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
|
||||||
|
|
||||||
dialog.setContentView(binding.root)
|
dialog.setContentView(binding.root)
|
||||||
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
// If user has clicked the switch do not interfere
|
|
||||||
var preventAutoSwitching = false
|
var preventAutoSwitching = false
|
||||||
binding.hlsSwitch.setOnClickListener {
|
binding.hlsSwitch.setOnClickListener { preventAutoSwitching = true }
|
||||||
preventAutoSwitching = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun activateSwitchOnHls(text: String?) {
|
|
||||||
binding.hlsSwitch.isChecked = normalSafeApiCall {
|
|
||||||
URI(text).path?.substringAfterLast(".")?.contains("m3u")
|
|
||||||
} == true
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
|
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
|
||||||
if (!preventAutoSwitching)
|
if (!preventAutoSwitching) activateSwitchOnHls(text?.toString(), binding)
|
||||||
activateSwitchOnHls(text?.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
|
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip?.getItemAt(0)?.text?.toString()?.let { copy ->
|
||||||
0
|
|
||||||
)?.text?.toString()?.let { copy ->
|
|
||||||
val fixedText = copy.trim()
|
val fixedText = copy.trim()
|
||||||
binding.streamUrl.setText(fixedText)
|
binding.streamUrl.setText(fixedText)
|
||||||
activateSwitchOnHls(fixedText)
|
activateSwitchOnHls(fixedText, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.applyBtt.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
|
@ -245,7 +214,6 @@ class DownloadFragment : Fragment() {
|
||||||
showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT)
|
||||||
} else {
|
} else {
|
||||||
val referer = binding.streamReferer.text?.toString()
|
val referer = binding.streamReferer.text?.toString()
|
||||||
|
|
||||||
activity?.navigate(
|
activity?.navigate(
|
||||||
R.id.global_to_navigation_player,
|
R.id.global_to_navigation_player,
|
||||||
GeneratorPlayer.newInstance(
|
GeneratorPlayer.newInstance(
|
||||||
|
@ -257,7 +225,6 @@ class DownloadFragment : Fragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,18 +233,18 @@ class DownloadFragment : Fragment() {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
|
||||||
val dy = scrollY - oldScrollY
|
|
||||||
if (dy > 0) { //check for scroll down
|
|
||||||
binding?.downloadStreamButton?.shrink() // hide
|
|
||||||
} else if (dy < -5) {
|
|
||||||
binding?.downloadStreamButton?.extend() // show
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
downloadsViewModel.updateList(requireContext())
|
|
||||||
|
|
||||||
fixPaddingStatusbar(binding?.downloadRoot)
|
private fun activateSwitchOnHls(text: String?, binding: StreamInputBinding) {
|
||||||
|
binding.hlsSwitch.isChecked = normalSafeApiCall {
|
||||||
|
URI(text).path?.substringAfterLast(".")?.contains("m3u")
|
||||||
|
} == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleScroll(dy: Int) {
|
||||||
|
if (dy > 0) {
|
||||||
|
binding?.downloadStreamButton?.shrink()
|
||||||
|
} else if (dy < -5) {
|
||||||
|
binding?.downloadStreamButton?.extend()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,149 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class VisualDownloadHeaderCached(
|
|
||||||
val currentOngoingDownloads: Int,
|
|
||||||
val totalDownloads: Int,
|
|
||||||
val totalBytes: Long,
|
|
||||||
val currentBytes: Long,
|
|
||||||
val data: VideoDownloadHelper.DownloadHeaderCached,
|
|
||||||
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DownloadHeaderClickEvent(
|
|
||||||
val action: Int,
|
|
||||||
val data: VideoDownloadHelper.DownloadHeaderCached
|
|
||||||
)
|
|
||||||
|
|
||||||
class DownloadHeaderAdapter(
|
|
||||||
var cardList: List<VisualDownloadHeaderCached>,
|
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return DownloadHeaderViewHolder(
|
|
||||||
DownloadHeaderEpisodeBinding.inflate(
|
|
||||||
LayoutInflater.from(parent.context),
|
|
||||||
parent,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
clickCallback,
|
|
||||||
movieClickCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
when (holder) {
|
|
||||||
is DownloadHeaderViewHolder -> {
|
|
||||||
holder.bind(cardList[position])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return cardList.size
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadHeaderViewHolder
|
|
||||||
constructor(
|
|
||||||
val binding: DownloadHeaderEpisodeBinding,
|
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
/*private val poster: ImageView? = itemView.download_header_poster
|
|
||||||
private val title: TextView = itemView.download_header_title
|
|
||||||
private val extraInfo: TextView = itemView.download_header_info
|
|
||||||
private val holder: CardView = itemView.episode_holder
|
|
||||||
|
|
||||||
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
|
|
||||||
private val downloadImage: ImageView = itemView.download_header_episode_download
|
|
||||||
private val normalImage: ImageView = itemView.download_header_goto_child*/
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
fun bind(card: VisualDownloadHeaderCached) {
|
|
||||||
val d = card.data
|
|
||||||
|
|
||||||
binding.downloadHeaderPoster.apply {
|
|
||||||
setImage(d.poster)
|
|
||||||
setOnClickListener {
|
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.apply {
|
|
||||||
|
|
||||||
binding.downloadHeaderTitle.text = d.name
|
|
||||||
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
|
||||||
|
|
||||||
//val isMovie = d.type.isMovieType()
|
|
||||||
if (card.child != null) {
|
|
||||||
//downloadHeaderProgressDownloaded.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
// downloadHeaderEpisodeDownload.visibility = View.VISIBLE
|
|
||||||
binding.downloadHeaderGotoChild.visibility = View.GONE
|
|
||||||
|
|
||||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, movieClickCallback)
|
|
||||||
downloadButton.isVisible = true
|
|
||||||
/*setUpButton(
|
|
||||||
card.currentBytes,
|
|
||||||
card.totalBytes,
|
|
||||||
downloadBar,
|
|
||||||
downloadImage,
|
|
||||||
extraInfo,
|
|
||||||
card.child,
|
|
||||||
movieClickCallback
|
|
||||||
)*/
|
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
|
||||||
movieClickCallback.invoke(
|
|
||||||
DownloadClickEvent(
|
|
||||||
DOWNLOAD_ACTION_PLAY_FILE,
|
|
||||||
card.child
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
downloadButton.isVisible = false
|
|
||||||
// downloadHeaderProgressDownloaded.visibility = View.GONE
|
|
||||||
// downloadHeaderEpisodeDownload.visibility = View.GONE
|
|
||||||
binding.downloadHeaderGotoChild.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
try {
|
|
||||||
downloadHeaderInfo.text =
|
|
||||||
downloadHeaderInfo.context.getString(R.string.extra_info_format).format(
|
|
||||||
card.totalDownloads,
|
|
||||||
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(R.string.episode) else downloadHeaderInfo.context.getString(
|
|
||||||
R.string.episodes
|
|
||||||
),
|
|
||||||
mbString
|
|
||||||
)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
// you probably formatted incorrectly
|
|
||||||
downloadHeaderInfo.text = "Error"
|
|
||||||
logError(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,6 +39,8 @@ class DownloadViewModel : ViewModel() {
|
||||||
val availableBytes: LiveData<Long> = _availableBytes
|
val availableBytes: LiveData<Long> = _availableBytes
|
||||||
val downloadBytes: LiveData<Long> = _downloadBytes
|
val downloadBytes: LiveData<Long> = _downloadBytes
|
||||||
|
|
||||||
|
private var previousVisual: List<VisualDownloadHeaderCached>? = null
|
||||||
|
|
||||||
fun updateList(context: Context) = viewModelScope.launchSafe {
|
fun updateList(context: Context) = viewModelScope.launchSafe {
|
||||||
val children = withContext(Dispatchers.IO) {
|
val children = withContext(Dispatchers.IO) {
|
||||||
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||||
|
@ -53,7 +55,6 @@ class DownloadViewModel : ViewModel() {
|
||||||
// parentId : downloadsCount
|
// parentId : downloadsCount
|
||||||
val totalDownloads = HashMap<Int, Int>()
|
val totalDownloads = HashMap<Int, Int>()
|
||||||
|
|
||||||
|
|
||||||
// Gets all children downloads
|
// Gets all children downloads
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
for (c in children) {
|
for (c in children) {
|
||||||
|
@ -69,7 +70,7 @@ class DownloadViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cached = withContext(Dispatchers.IO) { // wont fetch useless keys
|
val cached = withContext(Dispatchers.IO) { // Won't fetch useless keys
|
||||||
totalDownloads.entries.filter { it.value > 0 }.mapNotNull {
|
totalDownloads.entries.filter { it.value > 0 }.mapNotNull {
|
||||||
context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||||
DOWNLOAD_HEADER_CACHE,
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
@ -79,7 +80,7 @@ class DownloadViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val visual = withContext(Dispatchers.IO) {
|
val visual = withContext(Dispatchers.IO) {
|
||||||
cached.mapNotNull { // TODO FIX
|
cached.mapNotNull {
|
||||||
val downloads = totalDownloads[it.id] ?: 0
|
val downloads = totalDownloads[it.id] ?: 0
|
||||||
val bytes = totalBytesUsedByChild[it.id] ?: 0
|
val bytes = totalBytesUsedByChild[it.id] ?: 0
|
||||||
val currentBytes = currentBytesUsedByChild[it.id] ?: 0
|
val currentBytes = currentBytesUsedByChild[it.id] ?: 0
|
||||||
|
@ -91,32 +92,37 @@ class DownloadViewModel : ViewModel() {
|
||||||
getFolderName(it.id.toString(), it.id.toString())
|
getFolderName(it.id.toString(), it.id.toString())
|
||||||
)
|
)
|
||||||
VisualDownloadHeaderCached(
|
VisualDownloadHeaderCached(
|
||||||
0,
|
currentBytes = currentBytes,
|
||||||
downloads,
|
totalBytes = bytes,
|
||||||
bytes,
|
data = it,
|
||||||
currentBytes,
|
child = movieEpisode,
|
||||||
it,
|
currentOngoingDownloads = 0,
|
||||||
movieEpisode
|
totalDownloads = downloads,
|
||||||
)
|
)
|
||||||
}.sortedBy {
|
}.sortedBy {
|
||||||
(it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0)
|
(it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0)
|
||||||
} // episode sorting by episode, lowest to highest
|
} // Episode sorting by episode, lowest to highest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only update list if different from the previous one to prevent duplicate initialization
|
||||||
|
if (visual != previousVisual) {
|
||||||
|
previousVisual = visual
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val stat = StatFs(Environment.getExternalStorageDirectory().path)
|
val stat = StatFs(Environment.getExternalStorageDirectory().path)
|
||||||
|
val localBytesAvailable = stat.availableBytes
|
||||||
val localBytesAvailable = stat.availableBytes//stat.blockSizeLong * stat.blockCountLong
|
|
||||||
val localTotalBytes = stat.blockSizeLong * stat.blockCountLong
|
val localTotalBytes = stat.blockSizeLong * stat.blockCountLong
|
||||||
val localDownloadedBytes = visual.sumOf { it.totalBytes }
|
val localDownloadedBytes = visual.sumOf { it.totalBytes }
|
||||||
|
|
||||||
_usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes)
|
_usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes)
|
||||||
_availableBytes.postValue(localBytesAvailable)
|
_availableBytes.postValue(localBytesAvailable)
|
||||||
_downloadBytes.postValue(localDownloadedBytes)
|
_downloadBytes.postValue(localDownloadedBytes)
|
||||||
} catch (t : Throwable) {
|
} catch (t: Throwable) {
|
||||||
_downloadBytes.postValue(0)
|
_downloadBytes.postValue(0)
|
||||||
logError(t)
|
logError(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
_headerCards.postValue(visual)
|
_headerCards.postValue(visual)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -192,15 +192,15 @@ class EpisodeAdapter(
|
||||||
downloadButton.isVisible = hasDownloadSupport
|
downloadButton.isVisible = hasDownloadSupport
|
||||||
downloadButton.setDefaultClickListener(
|
downloadButton.setDefaultClickListener(
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
name = card.name,
|
||||||
card.poster,
|
poster = card.poster,
|
||||||
card.episode,
|
episode = card.episode,
|
||||||
card.season,
|
season = card.season,
|
||||||
card.id,
|
id = card.id,
|
||||||
card.parentId,
|
parentId = card.parentId,
|
||||||
card.rating,
|
rating = card.rating,
|
||||||
card.description,
|
description = card.description,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
), null
|
), null
|
||||||
) {
|
) {
|
||||||
when (it.action) {
|
when (it.action) {
|
||||||
|
@ -343,15 +343,15 @@ class EpisodeAdapter(
|
||||||
downloadButton.isVisible = hasDownloadSupport
|
downloadButton.isVisible = hasDownloadSupport
|
||||||
downloadButton.setDefaultClickListener(
|
downloadButton.setDefaultClickListener(
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
name = card.name,
|
||||||
card.poster,
|
poster = card.poster,
|
||||||
card.episode,
|
episode = card.episode,
|
||||||
card.season,
|
season = card.season,
|
||||||
card.id,
|
id = card.id,
|
||||||
card.parentId,
|
parentId = card.parentId,
|
||||||
card.rating,
|
rating = card.rating,
|
||||||
card.description,
|
description = card.description,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
), null
|
), null
|
||||||
) {
|
) {
|
||||||
when (it.action) {
|
when (it.action) {
|
||||||
|
|
|
@ -185,8 +185,6 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer
|
binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//player_view?.apply {
|
//player_view?.apply {
|
||||||
//alpha = 0.0f
|
//alpha = 0.0f
|
||||||
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
|
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
|
||||||
|
@ -200,9 +198,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
// fillAfter = true
|
// fillAfter = true
|
||||||
//}
|
//}
|
||||||
//startAnimation(fadeIn)
|
//startAnimation(fadeIn)
|
||||||
// }
|
//}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTrailers(trailers: List<ExtractorLink>?) {
|
private fun setTrailers(trailers: List<ExtractorLink>?) {
|
||||||
|
@ -630,15 +626,15 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
downloadButton.setDefaultClickListener(
|
downloadButton.setDefaultClickListener(
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
ep.name,
|
name = ep.name,
|
||||||
ep.poster,
|
poster = ep.poster,
|
||||||
0,
|
episode = 0,
|
||||||
null,
|
season = null,
|
||||||
ep.id,
|
id = ep.id,
|
||||||
ep.id,
|
parentId = ep.id,
|
||||||
null,
|
rating = null,
|
||||||
null,
|
description = null,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
) { click ->
|
) { click ->
|
||||||
|
|
|
@ -705,13 +705,13 @@ class ResultViewModel2 : ViewModel() {
|
||||||
DOWNLOAD_HEADER_CACHE,
|
DOWNLOAD_HEADER_CACHE,
|
||||||
parentId.toString(),
|
parentId.toString(),
|
||||||
VideoDownloadHelper.DownloadHeaderCached(
|
VideoDownloadHelper.DownloadHeaderCached(
|
||||||
apiName,
|
apiName = apiName,
|
||||||
url,
|
url = url,
|
||||||
currentType,
|
type = currentType,
|
||||||
currentHeaderName,
|
name = currentHeaderName,
|
||||||
currentPoster,
|
poster = currentPoster,
|
||||||
parentId,
|
id = parentId,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -722,15 +722,15 @@ class ResultViewModel2 : ViewModel() {
|
||||||
), // 3 deep folder for faster acess
|
), // 3 deep folder for faster acess
|
||||||
episode.id.toString(),
|
episode.id.toString(),
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
episode.name,
|
name = episode.name,
|
||||||
episode.poster,
|
poster = episode.poster,
|
||||||
episode.episode,
|
episode = episode.episode,
|
||||||
episode.season,
|
season = episode.season,
|
||||||
episode.id,
|
id = episode.id,
|
||||||
parentId,
|
parentId = parentId,
|
||||||
episode.rating,
|
rating = episode.rating,
|
||||||
episode.description,
|
description = episode.description,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2776,13 +2776,13 @@ class ResultViewModel2 : ViewModel() {
|
||||||
DOWNLOAD_HEADER_CACHE,
|
DOWNLOAD_HEADER_CACHE,
|
||||||
mainId.toString(),
|
mainId.toString(),
|
||||||
VideoDownloadHelper.DownloadHeaderCached(
|
VideoDownloadHelper.DownloadHeaderCached(
|
||||||
apiName,
|
apiName = apiName,
|
||||||
validUrl,
|
url = validUrl,
|
||||||
loadResponse.type,
|
type = loadResponse.type,
|
||||||
loadResponse.name,
|
name = loadResponse.name,
|
||||||
loadResponse.posterUrl,
|
poster = loadResponse.posterUrl,
|
||||||
mainId,
|
id = mainId,
|
||||||
System.currentTimeMillis(),
|
cacheTime = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (loadTrailers)
|
if (loadTrailers)
|
||||||
|
|
|
@ -25,7 +25,7 @@ object SearchHelper {
|
||||||
SEARCH_ACTION_PLAY_FILE -> {
|
SEARCH_ACTION_PLAY_FILE -> {
|
||||||
if (card is DataStoreHelper.ResumeWatchingResult) {
|
if (card is DataStoreHelper.ResumeWatchingResult) {
|
||||||
val id = card.id
|
val id = card.id
|
||||||
if(id == null) {
|
if (id == null) {
|
||||||
showToast(R.string.error_invalid_id, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_id, Toast.LENGTH_SHORT)
|
||||||
} else {
|
} else {
|
||||||
if (card.isFromDownload) {
|
if (card.isFromDownload) {
|
||||||
|
@ -33,15 +33,15 @@ object SearchHelper {
|
||||||
DownloadClickEvent(
|
DownloadClickEvent(
|
||||||
DOWNLOAD_ACTION_PLAY_FILE,
|
DOWNLOAD_ACTION_PLAY_FILE,
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
name = card.name,
|
||||||
card.posterUrl,
|
poster = card.posterUrl,
|
||||||
card.episode ?: 0,
|
episode = card.episode ?: 0,
|
||||||
card.season,
|
season = card.season,
|
||||||
id,
|
id = id,
|
||||||
card.parentId ?: return,
|
parentId = card.parentId ?: return,
|
||||||
null,
|
rating = null,
|
||||||
null,
|
description = null,
|
||||||
System.currentTimeMillis()
|
cacheTime = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,17 +3,21 @@ package com.lagradost.cloudstream3.utils
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
object VideoDownloadHelper {
|
object VideoDownloadHelper {
|
||||||
|
abstract class DownloadCached(
|
||||||
|
@JsonProperty("id") open val id: Int,
|
||||||
|
)
|
||||||
|
|
||||||
data class DownloadEpisodeCached(
|
data class DownloadEpisodeCached(
|
||||||
@JsonProperty("name") val name: String?,
|
@JsonProperty("name") val name: String?,
|
||||||
@JsonProperty("poster") val poster: String?,
|
@JsonProperty("poster") val poster: String?,
|
||||||
@JsonProperty("episode") val episode: Int,
|
@JsonProperty("episode") val episode: Int,
|
||||||
@JsonProperty("season") val season: Int?,
|
@JsonProperty("season") val season: Int?,
|
||||||
@JsonProperty("id") val id: Int,
|
|
||||||
@JsonProperty("parentId") val parentId: Int,
|
@JsonProperty("parentId") val parentId: Int,
|
||||||
@JsonProperty("rating") val rating: Int?,
|
@JsonProperty("rating") val rating: Int?,
|
||||||
@JsonProperty("description") val description: String?,
|
@JsonProperty("description") val description: String?,
|
||||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||||
)
|
override val id: Int,
|
||||||
|
): DownloadCached(id)
|
||||||
|
|
||||||
data class DownloadHeaderCached(
|
data class DownloadHeaderCached(
|
||||||
@JsonProperty("apiName") val apiName: String,
|
@JsonProperty("apiName") val apiName: String,
|
||||||
|
@ -21,9 +25,9 @@ object VideoDownloadHelper {
|
||||||
@JsonProperty("type") val type: TvType,
|
@JsonProperty("type") val type: TvType,
|
||||||
@JsonProperty("name") val name: String,
|
@JsonProperty("name") val name: String,
|
||||||
@JsonProperty("poster") val poster: String?,
|
@JsonProperty("poster") val poster: String?,
|
||||||
@JsonProperty("id") val id: Int,
|
|
||||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||||
)
|
override val id: Int,
|
||||||
|
): DownloadCached(id)
|
||||||
|
|
||||||
data class ResumeWatching(
|
data class ResumeWatching(
|
||||||
@JsonProperty("parentId") val parentId: Int,
|
@JsonProperty("parentId") val parentId: Int,
|
||||||
|
|
|
@ -59,12 +59,12 @@
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/download_header_goto_child"
|
android:id="@+id/download_header_goto_child"
|
||||||
android:layout_width="50dp"
|
android:layout_width="@dimen/download_size"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="@dimen/download_size"
|
||||||
android:layout_gravity="center_vertical|end"
|
android:layout_gravity="center_vertical|end"
|
||||||
android:layout_marginStart="-50dp"
|
android:layout_marginStart="-50dp"
|
||||||
android:contentDescription="@string/download"
|
android:contentDescription="@string/download"
|
||||||
android:padding="50dp"
|
android:padding="10dp"
|
||||||
android:src="@drawable/ic_baseline_keyboard_arrow_right_24" />
|
android:src="@drawable/ic_baseline_keyboard_arrow_right_24" />
|
||||||
|
|
||||||
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton
|
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton
|
||||||
|
|
Loading…
Reference in a new issue