forked from recloudstream/cloudstream
fixed mem leak
This commit is contained in:
parent
8ff87e5108
commit
f270f9f551
12 changed files with 427 additions and 227 deletions
|
@ -1,8 +1,8 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'kotlin-android-extensions'
|
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
|
id 'kotlin-android-extensions'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
@ -1,24 +1,13 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.view.View
|
|
||||||
import android.view.animation.DecelerateInterpolator
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
|
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||||
import com.lagradost.cloudstream3.ui.player.UriData
|
import com.lagradost.cloudstream3.ui.player.UriData
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
object DownloadButtonSetup {
|
object DownloadButtonSetup {
|
||||||
|
@ -95,167 +84,4 @@ object DownloadButtonSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUpDownloadButton(
|
|
||||||
setupCurrentBytes: Long?,
|
|
||||||
setupTotalBytes: Long?,
|
|
||||||
progressBar: ContentLoadingProgressBar,
|
|
||||||
textView: TextView?,
|
|
||||||
data: VideoDownloadHelper.DownloadEpisodeCached,
|
|
||||||
downloadView: View,
|
|
||||||
downloadImageChangeCallback: (Pair<Int, String>) -> Unit,
|
|
||||||
clickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
): () -> Unit {
|
|
||||||
var lastState: VideoDownloadManager.DownloadType? = null
|
|
||||||
var currentBytes = setupCurrentBytes ?: 0
|
|
||||||
var totalBytes = setupTotalBytes ?: 0
|
|
||||||
var needImageUpdate = false
|
|
||||||
|
|
||||||
fun changeDownloadImage(state: VideoDownloadManager.DownloadType) {
|
|
||||||
lastState = state
|
|
||||||
if (currentBytes <= 0) needImageUpdate = true
|
|
||||||
val img = if (currentBytes > 0) {
|
|
||||||
when (state) {
|
|
||||||
VideoDownloadManager.DownloadType.IsPaused -> Pair(
|
|
||||||
R.drawable.ic_baseline_play_arrow_24,
|
|
||||||
"Download Paused"
|
|
||||||
)
|
|
||||||
VideoDownloadManager.DownloadType.IsDownloading -> Pair(R.drawable.netflix_pause, "Downloading")
|
|
||||||
else -> Pair(R.drawable.ic_baseline_delete_outline_24, "Downloaded")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Pair(R.drawable.netflix_download, "Download")
|
|
||||||
}
|
|
||||||
downloadImageChangeCallback.invoke(img)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
fun fixDownloadedBytes(setCurrentBytes: Long, setTotalBytes: Long, animate: Boolean) {
|
|
||||||
currentBytes = setCurrentBytes
|
|
||||||
totalBytes = setTotalBytes
|
|
||||||
|
|
||||||
if (currentBytes == 0L) {
|
|
||||||
changeDownloadImage(VideoDownloadManager.DownloadType.IsStopped)
|
|
||||||
textView?.visibility = View.GONE
|
|
||||||
progressBar?.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
if (lastState == VideoDownloadManager.DownloadType.IsStopped) {
|
|
||||||
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
|
||||||
}
|
|
||||||
textView?.visibility = View.VISIBLE
|
|
||||||
progressBar?.visibility = View.VISIBLE
|
|
||||||
val currentMbString = "%.1f".format(setCurrentBytes / 1000000f)
|
|
||||||
val totalMbString = "%.1f".format(setTotalBytes / 1000000f)
|
|
||||||
|
|
||||||
textView?.text =
|
|
||||||
"${currentMbString}MB / ${totalMbString}MB"
|
|
||||||
|
|
||||||
progressBar?.let { bar ->
|
|
||||||
bar.max = (setTotalBytes / 1000).toInt()
|
|
||||||
|
|
||||||
if (animate) {
|
|
||||||
val animation: ObjectAnimator = ObjectAnimator.ofInt(
|
|
||||||
bar,
|
|
||||||
"progress",
|
|
||||||
bar.progress,
|
|
||||||
(setCurrentBytes / 1000).toInt()
|
|
||||||
)
|
|
||||||
animation.duration = 500
|
|
||||||
animation.setAutoCancel(true)
|
|
||||||
animation.interpolator = DecelerateInterpolator()
|
|
||||||
animation.start()
|
|
||||||
} else {
|
|
||||||
bar.progress = (setCurrentBytes / 1000).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fixDownloadedBytes(currentBytes, totalBytes, false)
|
|
||||||
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
|
||||||
|
|
||||||
val downloadProgressEventListener = { downloadData: Triple<Int, Long, Long> ->
|
|
||||||
if (data.id == downloadData.first) {
|
|
||||||
if (downloadData.second != currentBytes || downloadData.third != totalBytes) { // TO PREVENT WASTING UI TIME
|
|
||||||
Coroutines.runOnMainThread {
|
|
||||||
fixDownloadedBytes(downloadData.second, downloadData.third, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadStatusEventListener = { downloadData: Pair<Int, VideoDownloadManager.DownloadType> ->
|
|
||||||
if (data.id == downloadData.first) {
|
|
||||||
if (lastState != downloadData.second || needImageUpdate) { // TO PREVENT WASTING UI TIME
|
|
||||||
Coroutines.runOnMainThread {
|
|
||||||
changeDownloadImage(downloadData.second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoDownloadManager.downloadProgressEvent += downloadProgressEventListener
|
|
||||||
VideoDownloadManager.downloadStatusEvent += downloadStatusEventListener
|
|
||||||
|
|
||||||
downloadView.setOnClickListener {
|
|
||||||
if (currentBytes <= 0) {
|
|
||||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
|
|
||||||
} else {
|
|
||||||
val list = arrayListOf(
|
|
||||||
Pair(DOWNLOAD_ACTION_PLAY_FILE, R.string.popup_play_file),
|
|
||||||
Pair(DOWNLOAD_ACTION_DELETE_FILE, R.string.popup_delete_file),
|
|
||||||
)
|
|
||||||
|
|
||||||
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
|
||||||
if ((currentBytes * 100 / totalBytes) < 98) {
|
|
||||||
list.add(
|
|
||||||
if (lastState == VideoDownloadManager.DownloadType.IsDownloading)
|
|
||||||
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
|
||||||
else
|
|
||||||
Pair(DOWNLOAD_ACTION_RESUME_DOWNLOAD, R.string.popup_resume_download)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
it.popupMenuNoIcons(
|
|
||||||
list
|
|
||||||
) {
|
|
||||||
clickCallback.invoke(DownloadClickEvent(itemId, data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
VideoDownloadManager.downloadProgressEvent -= downloadProgressEventListener
|
|
||||||
VideoDownloadManager.downloadStatusEvent -= downloadStatusEventListener
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUpMaterialButton(
|
|
||||||
setupCurrentBytes: Long?,
|
|
||||||
setupTotalBytes: Long?,
|
|
||||||
progressBar: ContentLoadingProgressBar,
|
|
||||||
downloadButton: MaterialButton,
|
|
||||||
textView: TextView?,
|
|
||||||
data: VideoDownloadHelper.DownloadEpisodeCached,
|
|
||||||
clickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
): () -> Unit {
|
|
||||||
return setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadButton, {
|
|
||||||
downloadButton?.setIconResource(it.first)
|
|
||||||
downloadButton?.text = it.second
|
|
||||||
}, clickCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUpButton(
|
|
||||||
setupCurrentBytes: Long?,
|
|
||||||
setupTotalBytes: Long?,
|
|
||||||
progressBar: ContentLoadingProgressBar,
|
|
||||||
downloadImage: ImageView,
|
|
||||||
textView: TextView?,
|
|
||||||
data: VideoDownloadHelper.DownloadEpisodeCached,
|
|
||||||
clickCallback: (DownloadClickEvent) -> Unit,
|
|
||||||
): () -> Unit {
|
|
||||||
return setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadImage, {
|
|
||||||
downloadImage?.setImageResource(it.first)
|
|
||||||
}, clickCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
|
interface DownloadButtonViewHolder {
|
||||||
|
var downloadButton : EasyDownloadButton
|
||||||
|
fun reattachDownloadButton()
|
||||||
|
}
|
|
@ -1,28 +1,20 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
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.DecelerateInterpolator
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
|
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadState
|
|
||||||
import kotlinx.android.synthetic.main.download_child_episode.view.*
|
import kotlinx.android.synthetic.main.download_child_episode.view.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
||||||
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
||||||
|
@ -41,8 +33,37 @@ data class DownloadClickEvent(val action: Int, val data: VideoDownloadHelper.Dow
|
||||||
class DownloadChildAdapter(
|
class DownloadChildAdapter(
|
||||||
var cardList: List<VisualDownloadChildCached>,
|
var cardList: List<VisualDownloadChildCached>,
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
private val clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) :
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||||
|
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
||||||
|
return Collections.unmodifiableSet(mBoundViewHolders)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun killAdapter() {
|
||||||
|
getAllBoundViewHolders()?.forEach { view ->
|
||||||
|
view?.downloadButton?.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
mBoundViewHolders.remove(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.reattachDownloadButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return DownloadChildViewHolder(
|
return DownloadChildViewHolder(
|
||||||
|
@ -55,6 +76,7 @@ class DownloadChildAdapter(
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is DownloadChildViewHolder -> {
|
is DownloadChildViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position])
|
||||||
|
mBoundViewHolders.add(holder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +89,9 @@ class DownloadChildAdapter(
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
private val clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
||||||
|
override var downloadButton = EasyDownloadButton()
|
||||||
|
|
||||||
private val title: TextView = itemView.download_child_episode_text
|
private val title: TextView = itemView.download_child_episode_text
|
||||||
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
||||||
private val holder: CardView = itemView.download_child_episode_holder
|
private val holder: CardView = itemView.download_child_episode_holder
|
||||||
|
@ -75,8 +99,11 @@ class DownloadChildAdapter(
|
||||||
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
||||||
private val downloadImage: ImageView = itemView.download_child_episode_download
|
private val downloadImage: ImageView = itemView.download_child_episode_download
|
||||||
|
|
||||||
|
var localCard : VisualDownloadChildCached? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: VisualDownloadChildCached) {
|
fun bind(card: VisualDownloadChildCached) {
|
||||||
|
localCard = card
|
||||||
val d = card.data
|
val d = card.data
|
||||||
|
|
||||||
val posDur = itemView.context.getViewPos(d.id)
|
val posDur = itemView.context.getViewPos(d.id)
|
||||||
|
@ -90,7 +117,7 @@ class DownloadChildAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
title.text = d.name ?: "Episode ${d.episode}" //TODO FIX
|
title.text = d.name ?: "Episode ${d.episode}" //TODO FIX
|
||||||
DownloadButtonSetup.setUpButton(
|
downloadButton.setUpButton(
|
||||||
card.currentBytes,
|
card.currentBytes,
|
||||||
card.totalBytes,
|
card.totalBytes,
|
||||||
progressBarDownload,
|
progressBarDownload,
|
||||||
|
@ -104,5 +131,21 @@ class DownloadChildAdapter(
|
||||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun reattachDownloadButton() {
|
||||||
|
downloadButton.dispose()
|
||||||
|
val card = localCard
|
||||||
|
if (card != null) {
|
||||||
|
downloadButton.setUpButton(
|
||||||
|
card.currentBytes,
|
||||||
|
card.totalBytes,
|
||||||
|
progressBarDownload,
|
||||||
|
downloadImage,
|
||||||
|
extraInfo,
|
||||||
|
card.data,
|
||||||
|
clickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,16 @@ class DownloadChildFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
(download_child_list?.adapter as DownloadChildAdapter?)?.killAdapter()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
return inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
||||||
}
|
}
|
||||||
|
@ -58,6 +68,8 @@ class DownloadChildFragment : Fragment() {
|
||||||
download_child_list?.adapter?.notifyDataSetChanged()
|
download_child_list?.adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -82,7 +94,7 @@ class DownloadChildFragment : Fragment() {
|
||||||
handleDownloadClick(activity, name, click)
|
handleDownloadClick(activity, name, click)
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoDownloadManager.downloadDeleteEvent += { id ->
|
downloadDeleteEventListener = { id: Int ->
|
||||||
val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList
|
val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
if (list.any { it.data.id == id }) {
|
if (list.any { it.data.id == id }) {
|
||||||
|
@ -91,6 +103,8 @@ class DownloadChildFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
download_child_list.adapter = adapter
|
download_child_list.adapter = adapter
|
||||||
download_child_list.layoutManager = GridLayoutManager(context, 1)
|
download_child_list.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
|
|
|
@ -11,19 +11,15 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.isMovieType
|
import com.lagradost.cloudstream3.isMovieType
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.fragment_child_downloads.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_downloads.*
|
import kotlinx.android.synthetic.main.fragment_downloads.*
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
|
||||||
|
|
||||||
class DownloadFragment : Fragment() {
|
class DownloadFragment : Fragment() {
|
||||||
private lateinit var downloadsViewModel: DownloadViewModel
|
private lateinit var downloadsViewModel: DownloadViewModel
|
||||||
|
@ -41,9 +37,19 @@ class DownloadFragment : Fragment() {
|
||||||
this.layoutParams = param
|
this.layoutParams = param
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setList(list : List<VisualDownloadHeaderCached>) {
|
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
||||||
(download_list?.adapter as DownloadHeaderAdapter? ?: return).cardList = list
|
(download_list?.adapter as DownloadHeaderAdapter?)?.cardList = list
|
||||||
(download_list?.adapter as DownloadHeaderAdapter? ?: return).notifyDataSetChanged()
|
download_list?.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
(download_list?.adapter as DownloadHeaderAdapter?)?.killAdapter()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||||
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
@ -76,6 +82,8 @@ class DownloadFragment : Fragment() {
|
||||||
return inflater.inflate(R.layout.fragment_downloads, container, false)
|
return inflater.inflate(R.layout.fragment_downloads, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
||||||
|
@ -95,13 +103,13 @@ class DownloadFragment : Fragment() {
|
||||||
},
|
},
|
||||||
{ downloadClickEvent ->
|
{ downloadClickEvent ->
|
||||||
handleDownloadClick(activity, downloadClickEvent.data.name, downloadClickEvent)
|
handleDownloadClick(activity, downloadClickEvent.data.name, downloadClickEvent)
|
||||||
if(downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||||
downloadsViewModel.updateList(requireContext())
|
downloadsViewModel.updateList(requireContext())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
VideoDownloadManager.downloadDeleteEvent += { id ->
|
downloadDeleteEventListener = { id ->
|
||||||
val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList
|
val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
if (list.any { it.data.id == id }) {
|
if (list.any { it.data.id == id }) {
|
||||||
|
@ -111,6 +119,8 @@ class DownloadFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
download_list.adapter = adapter
|
download_list.adapter = adapter
|
||||||
download_list.layoutManager = GridLayoutManager(context, 1)
|
download_list.layoutManager = GridLayoutManager(context, 1)
|
||||||
downloadsViewModel.updateList(requireContext())
|
downloadsViewModel.updateList(requireContext())
|
||||||
|
|
|
@ -12,10 +12,10 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
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 com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.isMovieType
|
import com.lagradost.cloudstream3.utils.IDisposable
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.setUpButton
|
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.android.synthetic.main.download_header_episode.view.*
|
import kotlinx.android.synthetic.main.download_header_episode.view.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
data class VisualDownloadHeaderCached(
|
data class VisualDownloadHeaderCached(
|
||||||
val currentOngoingDownloads: Int,
|
val currentOngoingDownloads: Int,
|
||||||
|
@ -32,8 +32,37 @@ class DownloadHeaderAdapter(
|
||||||
var cardList: List<VisualDownloadHeaderCached>,
|
var cardList: List<VisualDownloadHeaderCached>,
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) :
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||||
|
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
||||||
|
return Collections.unmodifiableSet(mBoundViewHolders)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun killAdapter() {
|
||||||
|
getAllBoundViewHolders()?.forEach { view ->
|
||||||
|
view?.downloadButton?.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
mBoundViewHolders.remove(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.reattachDownloadButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return DownloadHeaderViewHolder(
|
return DownloadHeaderViewHolder(
|
||||||
|
@ -47,6 +76,7 @@ class DownloadHeaderAdapter(
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is DownloadHeaderViewHolder -> {
|
is DownloadHeaderViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position])
|
||||||
|
mBoundViewHolders.add(holder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +90,9 @@ class DownloadHeaderAdapter(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
||||||
|
override var downloadButton = EasyDownloadButton()
|
||||||
|
|
||||||
private val poster: ImageView = itemView.download_header_poster
|
private val poster: ImageView = itemView.download_header_poster
|
||||||
private val title: TextView = itemView.download_header_title
|
private val title: TextView = itemView.download_header_title
|
||||||
private val extraInfo: TextView = itemView.download_header_info
|
private val extraInfo: TextView = itemView.download_header_info
|
||||||
|
@ -69,9 +101,11 @@ class DownloadHeaderAdapter(
|
||||||
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
|
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
|
||||||
private val downloadImage: ImageView = itemView.download_header_episode_download
|
private val downloadImage: ImageView = itemView.download_header_episode_download
|
||||||
private val normalImage: ImageView = itemView.download_header_goto_child
|
private val normalImage: ImageView = itemView.download_header_goto_child
|
||||||
|
var localCard: VisualDownloadHeaderCached? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: VisualDownloadHeaderCached) {
|
fun bind(card: VisualDownloadHeaderCached) {
|
||||||
|
localCard = card
|
||||||
val d = card.data
|
val d = card.data
|
||||||
if (d.poster != null) {
|
if (d.poster != null) {
|
||||||
|
|
||||||
|
@ -93,8 +127,7 @@ class DownloadHeaderAdapter(
|
||||||
downloadBar.visibility = View.VISIBLE
|
downloadBar.visibility = View.VISIBLE
|
||||||
downloadImage.visibility = View.VISIBLE
|
downloadImage.visibility = View.VISIBLE
|
||||||
normalImage.visibility = View.GONE
|
normalImage.visibility = View.GONE
|
||||||
|
/*setUpButton(
|
||||||
setUpButton(
|
|
||||||
card.currentBytes,
|
card.currentBytes,
|
||||||
card.totalBytes,
|
card.totalBytes,
|
||||||
downloadBar,
|
downloadBar,
|
||||||
|
@ -102,7 +135,7 @@ class DownloadHeaderAdapter(
|
||||||
extraInfo,
|
extraInfo,
|
||||||
card.child,
|
card.child,
|
||||||
movieClickCallback
|
movieClickCallback
|
||||||
)
|
)*/
|
||||||
|
|
||||||
holder.setOnClickListener {
|
holder.setOnClickListener {
|
||||||
movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||||
|
@ -120,5 +153,21 @@ class DownloadHeaderAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun reattachDownloadButton() {
|
||||||
|
downloadButton.dispose()
|
||||||
|
val card = localCard
|
||||||
|
if (card?.child != null) {
|
||||||
|
downloadButton.setUpButton(
|
||||||
|
card.currentBytes,
|
||||||
|
card.totalBytes,
|
||||||
|
downloadBar,
|
||||||
|
downloadImage,
|
||||||
|
extraInfo,
|
||||||
|
card.child,
|
||||||
|
movieClickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines
|
||||||
|
import com.lagradost.cloudstream3.utils.IDisposable
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
|
class EasyDownloadButton : IDisposable {
|
||||||
|
override fun dispose() {
|
||||||
|
try {
|
||||||
|
downloadProgressEventListener?.let { VideoDownloadManager.downloadProgressEvent -= it }
|
||||||
|
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent -= it }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadProgressEventListener: ((Triple<Int, Long, Long>) -> Unit)? = null
|
||||||
|
var downloadStatusEventListener: ((Pair<Int, VideoDownloadManager.DownloadType>) -> Unit)? = null
|
||||||
|
|
||||||
|
fun setUpMaterialButton(
|
||||||
|
setupCurrentBytes: Long?,
|
||||||
|
setupTotalBytes: Long?,
|
||||||
|
progressBar: ContentLoadingProgressBar,
|
||||||
|
downloadButton: MaterialButton,
|
||||||
|
textView: TextView?,
|
||||||
|
data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadButton, {
|
||||||
|
downloadButton?.setIconResource(it.first)
|
||||||
|
downloadButton?.text = it.second
|
||||||
|
}, clickCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUpButton(
|
||||||
|
setupCurrentBytes: Long?,
|
||||||
|
setupTotalBytes: Long?,
|
||||||
|
progressBar: ContentLoadingProgressBar,
|
||||||
|
downloadImage: ImageView,
|
||||||
|
textView: TextView?,
|
||||||
|
data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadImage, {
|
||||||
|
downloadImage?.setImageResource(it.first)
|
||||||
|
}, clickCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUpDownloadButton(
|
||||||
|
setupCurrentBytes: Long?,
|
||||||
|
setupTotalBytes: Long?,
|
||||||
|
progressBar: ContentLoadingProgressBar,
|
||||||
|
textView: TextView?,
|
||||||
|
data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
downloadView: View,
|
||||||
|
downloadImageChangeCallback: (Pair<Int, String>) -> Unit,
|
||||||
|
clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
var lastState: VideoDownloadManager.DownloadType? = null
|
||||||
|
var currentBytes = setupCurrentBytes ?: 0
|
||||||
|
var totalBytes = setupTotalBytes ?: 0
|
||||||
|
var needImageUpdate = false
|
||||||
|
|
||||||
|
fun changeDownloadImage(state: VideoDownloadManager.DownloadType) {
|
||||||
|
lastState = state
|
||||||
|
if (currentBytes <= 0) needImageUpdate = true
|
||||||
|
val img = if (currentBytes > 0) {
|
||||||
|
when (state) {
|
||||||
|
VideoDownloadManager.DownloadType.IsPaused -> Pair(
|
||||||
|
R.drawable.ic_baseline_play_arrow_24,
|
||||||
|
"Download Paused"
|
||||||
|
)
|
||||||
|
VideoDownloadManager.DownloadType.IsDownloading -> Pair(R.drawable.netflix_pause, "Downloading")
|
||||||
|
else -> Pair(R.drawable.ic_baseline_delete_outline_24, "Downloaded")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Pair(R.drawable.netflix_download, "Download")
|
||||||
|
}
|
||||||
|
downloadImageChangeCallback.invoke(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun fixDownloadedBytes(setCurrentBytes: Long, setTotalBytes: Long, animate: Boolean) {
|
||||||
|
currentBytes = setCurrentBytes
|
||||||
|
totalBytes = setTotalBytes
|
||||||
|
|
||||||
|
if (currentBytes == 0L) {
|
||||||
|
changeDownloadImage(VideoDownloadManager.DownloadType.IsStopped)
|
||||||
|
textView?.visibility = View.GONE
|
||||||
|
progressBar?.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
if (lastState == VideoDownloadManager.DownloadType.IsStopped) {
|
||||||
|
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
||||||
|
}
|
||||||
|
textView?.visibility = View.VISIBLE
|
||||||
|
progressBar?.visibility = View.VISIBLE
|
||||||
|
val currentMbString = "%.1f".format(setCurrentBytes / 1000000f)
|
||||||
|
val totalMbString = "%.1f".format(setTotalBytes / 1000000f)
|
||||||
|
|
||||||
|
textView?.text =
|
||||||
|
"${currentMbString}MB / ${totalMbString}MB"
|
||||||
|
|
||||||
|
progressBar?.let { bar ->
|
||||||
|
bar.max = (setTotalBytes / 1000).toInt()
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
val animation: ObjectAnimator = ObjectAnimator.ofInt(
|
||||||
|
bar,
|
||||||
|
"progress",
|
||||||
|
bar.progress,
|
||||||
|
(setCurrentBytes / 1000).toInt()
|
||||||
|
)
|
||||||
|
animation.duration = 500
|
||||||
|
animation.setAutoCancel(true)
|
||||||
|
animation.interpolator = DecelerateInterpolator()
|
||||||
|
animation.start()
|
||||||
|
} else {
|
||||||
|
bar.progress = (setCurrentBytes / 1000).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fixDownloadedBytes(currentBytes, totalBytes, false)
|
||||||
|
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
||||||
|
|
||||||
|
downloadProgressEventListener = { downloadData: Triple<Int, Long, Long> ->
|
||||||
|
if (data.id == downloadData.first) {
|
||||||
|
if (downloadData.second != currentBytes || downloadData.third != totalBytes) { // TO PREVENT WASTING UI TIME
|
||||||
|
Coroutines.runOnMainThread {
|
||||||
|
fixDownloadedBytes(downloadData.second, downloadData.third, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadStatusEventListener = { downloadData: Pair<Int, VideoDownloadManager.DownloadType> ->
|
||||||
|
if (data.id == downloadData.first) {
|
||||||
|
if (lastState != downloadData.second || needImageUpdate) { // TO PREVENT WASTING UI TIME
|
||||||
|
Coroutines.runOnMainThread {
|
||||||
|
changeDownloadImage(downloadData.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadProgressEventListener?.let { VideoDownloadManager.downloadProgressEvent += it }
|
||||||
|
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent += it }
|
||||||
|
|
||||||
|
downloadView.setOnClickListener {
|
||||||
|
if (currentBytes <= 0) {
|
||||||
|
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
|
||||||
|
} else {
|
||||||
|
val list = arrayListOf(
|
||||||
|
Pair(DOWNLOAD_ACTION_PLAY_FILE, R.string.popup_play_file),
|
||||||
|
Pair(DOWNLOAD_ACTION_DELETE_FILE, R.string.popup_delete_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
||||||
|
if ((currentBytes * 100 / totalBytes) < 98) {
|
||||||
|
list.add(
|
||||||
|
if (lastState == VideoDownloadManager.DownloadType.IsDownloading)
|
||||||
|
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||||
|
else
|
||||||
|
Pair(DOWNLOAD_ACTION_RESUME_DOWNLOAD, R.string.popup_resume_download)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.popupMenuNoIcons(
|
||||||
|
list
|
||||||
|
) {
|
||||||
|
clickCallback.invoke(DownloadClickEvent(itemId, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,15 +13,15 @@ import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
|
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
||||||
const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2
|
const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2
|
||||||
|
@ -49,12 +49,43 @@ class EpisodeAdapter(
|
||||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||||
|
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
||||||
|
return Collections.unmodifiableSet(mBoundViewHolders)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun killAdapter() {
|
||||||
|
getAllBoundViewHolders()?.forEach { view ->
|
||||||
|
view?.downloadButton?.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.downloadButton.dispose()
|
||||||
|
mBoundViewHolders.remove(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is DownloadButtonViewHolder) {
|
||||||
|
holder.reattachDownloadButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private var layout: Int = 0
|
private var layout: Int = 0
|
||||||
fun updateLayout() {
|
fun updateLayout() {
|
||||||
layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
layout =
|
||||||
R.layout.result_episode_large
|
if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||||
else R.layout.result_episode
|
R.layout.result_episode_large
|
||||||
|
else R.layout.result_episode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
@ -62,7 +93,7 @@ class EpisodeAdapter(
|
||||||
R.layout.result_episode_large
|
R.layout.result_episode_large
|
||||||
else R.layout.result_episode*/
|
else R.layout.result_episode*/
|
||||||
|
|
||||||
return CardViewHolder(
|
return EpisodeCardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
||||||
hasDownloadSupport,
|
hasDownloadSupport,
|
||||||
clickCallback,
|
clickCallback,
|
||||||
|
@ -72,8 +103,9 @@ class EpisodeAdapter(
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is CardViewHolder -> {
|
is EpisodeCardViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position])
|
||||||
|
mBoundViewHolders.add(holder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,26 +114,31 @@ class EpisodeAdapter(
|
||||||
return cardList.size
|
return cardList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
class CardViewHolder
|
class EpisodeCardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
private val hasDownloadSupport: Boolean,
|
val hasDownloadSupport: Boolean,
|
||||||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
||||||
|
override var downloadButton = EasyDownloadButton()
|
||||||
|
|
||||||
private val episodeText: TextView = itemView.episode_text
|
private val episodeText: TextView = itemView.episode_text
|
||||||
private val episodeRating: TextView? = itemView.episode_rating
|
private val episodeRating: TextView? = itemView.episode_rating
|
||||||
private val episodeDescript: TextView? = itemView.episode_descript
|
private val episodeDescript: TextView? = itemView.episode_descript
|
||||||
private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress
|
private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress
|
||||||
private val episodePoster: ImageView? = itemView.episode_poster
|
private val episodePoster: ImageView? = itemView.episode_poster
|
||||||
|
|
||||||
private val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded
|
val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded
|
||||||
private val episodeDownloadImage: ImageView = itemView.result_episode_download
|
val episodeDownloadImage: ImageView = itemView.result_episode_download
|
||||||
|
|
||||||
private val episodeHolder = itemView.episode_holder
|
private val episodeHolder = itemView.episode_holder
|
||||||
|
|
||||||
|
var localCard: ResultEpisode? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
|
localCard = card
|
||||||
val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}"
|
val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}"
|
||||||
episodeText.text = name
|
episodeText.text = name
|
||||||
|
|
||||||
|
@ -148,11 +185,15 @@ class EpisodeAdapter(
|
||||||
|
|
||||||
episodeDownloadImage.visibility = if (hasDownloadSupport) View.VISIBLE else View.GONE
|
episodeDownloadImage.visibility = if (hasDownloadSupport) View.VISIBLE else View.GONE
|
||||||
episodeDownloadBar.visibility = if (hasDownloadSupport) View.VISIBLE else View.GONE
|
episodeDownloadBar.visibility = if (hasDownloadSupport) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDownloadSupport) {
|
override fun reattachDownloadButton() {
|
||||||
|
downloadButton.dispose()
|
||||||
|
val card = localCard
|
||||||
|
if (hasDownloadSupport && card != null) {
|
||||||
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(itemView.context, card.id)
|
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(itemView.context, card.id)
|
||||||
|
|
||||||
DownloadButtonSetup.setUpButton(
|
downloadButton.setUpButton(
|
||||||
downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null,
|
downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null,
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name, card.poster, card.episode, card.season, card.id, 0, card.rating, card.descript
|
card.name, card.poster, card.episode, card.season, card.id, 0, card.rating, card.descript
|
||||||
|
|
|
@ -50,8 +50,9 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.setUpMaterialButton
|
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerData
|
import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
@ -70,7 +71,6 @@ import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
|
||||||
const val MAX_SYNO_LENGH = 300
|
const val MAX_SYNO_LENGH = 300
|
||||||
|
|
||||||
data class ResultEpisode(
|
data class ResultEpisode(
|
||||||
|
@ -156,6 +156,7 @@ class ResultFragment : Fragment() {
|
||||||
private var currentHeaderName: String? = null
|
private var currentHeaderName: String? = null
|
||||||
private var currentType: TvType? = null
|
private var currentType: TvType? = null
|
||||||
private var currentEpisodes: List<ResultEpisode>? = null
|
private var currentEpisodes: List<ResultEpisode>? = null
|
||||||
|
var downloadButton : EasyDownloadButton? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -167,8 +168,15 @@ class ResultFragment : Fragment() {
|
||||||
return inflater.inflate(R.layout.fragment_result, container, false)
|
return inflater.inflate(R.layout.fragment_result, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
(result_episodes?.adapter as EpisodeAdapter?)?.killAdapter()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
|
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
|
||||||
|
downloadButton?.dispose()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
activity?.let {
|
activity?.let {
|
||||||
it.window?.navigationBarColor =
|
it.window?.navigationBarColor =
|
||||||
|
@ -873,8 +881,9 @@ class ResultFragment : Fragment() {
|
||||||
val localId = d.getId()
|
val localId = d.getId()
|
||||||
val file =
|
val file =
|
||||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), localId)
|
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), localId)
|
||||||
|
downloadButton?.dispose()
|
||||||
setUpMaterialButton(
|
downloadButton = EasyDownloadButton()
|
||||||
|
downloadButton?.setUpMaterialButton(
|
||||||
file?.fileLength,
|
file?.fileLength,
|
||||||
file?.totalBytes,
|
file?.totalBytes,
|
||||||
result_movie_progress_downloaded,
|
result_movie_progress_downloaded,
|
||||||
|
|
|
@ -131,8 +131,10 @@ class SearchFragment : Fragment() {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
it.value.let { data ->
|
it.value.let { data ->
|
||||||
(cardSpace?.adapter as SearchAdapter?)?.cardList = data
|
if(data != null) {
|
||||||
cardSpace?.adapter?.notifyDataSetChanged()
|
(cardSpace?.adapter as SearchAdapter?)?.cardList = ArrayList( data.filterNotNull())
|
||||||
|
cardSpace?.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
searchExitIcon.alpha = 1f
|
searchExitIcon.alpha = 1f
|
||||||
search_loading_bar.alpha = 0f
|
search_loading_bar.alpha = 0f
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
interface IDisposable {
|
||||||
|
fun dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
object IDisposableHelper {
|
||||||
|
fun <T : IDisposable> using(disposeObject: T, work: (T) -> Unit) {
|
||||||
|
work.invoke(disposeObject)
|
||||||
|
disposeObject.dispose()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue