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 {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-android-extensions'
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
package com.lagradost.cloudstream3.ui.download
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
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.core.widget.ContentLoadingProgressBar
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||
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.VideoDownloadHelper
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
|
||||
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
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.getViewPos
|
||||
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 java.util.*
|
||||
|
||||
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
||||
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
||||
|
@ -41,8 +33,37 @@ data class DownloadClickEvent(val action: Int, val data: VideoDownloadHelper.Dow
|
|||
class DownloadChildAdapter(
|
||||
var cardList: List<VisualDownloadChildCached>,
|
||||
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 {
|
||||
return DownloadChildViewHolder(
|
||||
|
@ -55,6 +76,7 @@ class DownloadChildAdapter(
|
|||
when (holder) {
|
||||
is DownloadChildViewHolder -> {
|
||||
holder.bind(cardList[position])
|
||||
mBoundViewHolders.add(holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +89,9 @@ class DownloadChildAdapter(
|
|||
constructor(
|
||||
itemView: View,
|
||||
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 extraInfo: TextView = itemView.download_child_episode_text_extra
|
||||
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 downloadImage: ImageView = itemView.download_child_episode_download
|
||||
|
||||
var localCard : VisualDownloadChildCached? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(card: VisualDownloadChildCached) {
|
||||
localCard = card
|
||||
val d = card.data
|
||||
|
||||
val posDur = itemView.context.getViewPos(d.id)
|
||||
|
@ -90,7 +117,7 @@ class DownloadChildAdapter(
|
|||
}
|
||||
|
||||
title.text = d.name ?: "Episode ${d.episode}" //TODO FIX
|
||||
DownloadButtonSetup.setUpButton(
|
||||
downloadButton.setUpButton(
|
||||
card.currentBytes,
|
||||
card.totalBytes,
|
||||
progressBarDownload,
|
||||
|
@ -104,5 +131,21 @@ class DownloadChildAdapter(
|
|||
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? {
|
||||
return inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
||||
}
|
||||
|
@ -58,6 +68,8 @@ class DownloadChildFragment : Fragment() {
|
|||
download_child_list?.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
@ -82,7 +94,7 @@ class DownloadChildFragment : Fragment() {
|
|||
handleDownloadClick(activity, name, click)
|
||||
}
|
||||
|
||||
VideoDownloadManager.downloadDeleteEvent += { id ->
|
||||
downloadDeleteEventListener = { id: Int ->
|
||||
val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList
|
||||
if (list != null) {
|
||||
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.layoutManager = GridLayoutManager(context, 1)
|
||||
|
||||
|
|
|
@ -11,19 +11,15 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.isMovieType
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
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.DataStore.getFolderName
|
||||
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_result.*
|
||||
|
||||
class DownloadFragment : Fragment() {
|
||||
private lateinit var downloadsViewModel: DownloadViewModel
|
||||
|
@ -41,9 +37,19 @@ class DownloadFragment : Fragment() {
|
|||
this.layoutParams = param
|
||||
}
|
||||
|
||||
fun setList(list : List<VisualDownloadHeaderCached>) {
|
||||
(download_list?.adapter as DownloadHeaderAdapter? ?: return).cardList = list
|
||||
(download_list?.adapter as DownloadHeaderAdapter? ?: return).notifyDataSetChanged()
|
||||
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
||||
(download_list?.adapter as DownloadHeaderAdapter?)?.cardList = list
|
||||
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")
|
||||
|
@ -76,6 +82,8 @@ class DownloadFragment : Fragment() {
|
|||
return inflater.inflate(R.layout.fragment_downloads, container, false)
|
||||
}
|
||||
|
||||
var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
||||
|
@ -95,13 +103,13 @@ class DownloadFragment : Fragment() {
|
|||
},
|
||||
{ downloadClickEvent ->
|
||||
handleDownloadClick(activity, downloadClickEvent.data.name, downloadClickEvent)
|
||||
if(downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
downloadsViewModel.updateList(requireContext())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
VideoDownloadManager.downloadDeleteEvent += { id ->
|
||||
downloadDeleteEventListener = { id ->
|
||||
val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList
|
||||
if (list != null) {
|
||||
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.layoutManager = GridLayoutManager(context, 1)
|
||||
downloadsViewModel.updateList(requireContext())
|
||||
|
|
|
@ -12,10 +12,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.isMovieType
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.setUpButton
|
||||
import com.lagradost.cloudstream3.utils.IDisposable
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import kotlinx.android.synthetic.main.download_header_episode.view.*
|
||||
import java.util.*
|
||||
|
||||
data class VisualDownloadHeaderCached(
|
||||
val currentOngoingDownloads: Int,
|
||||
|
@ -32,8 +32,37 @@ class DownloadHeaderAdapter(
|
|||
var cardList: List<VisualDownloadHeaderCached>,
|
||||
private val clickCallback: (DownloadHeaderClickEvent) -> 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 {
|
||||
return DownloadHeaderViewHolder(
|
||||
|
@ -47,6 +76,7 @@ class DownloadHeaderAdapter(
|
|||
when (holder) {
|
||||
is DownloadHeaderViewHolder -> {
|
||||
holder.bind(cardList[position])
|
||||
mBoundViewHolders.add(holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +90,9 @@ class DownloadHeaderAdapter(
|
|||
itemView: View,
|
||||
private val clickCallback: (DownloadHeaderClickEvent) -> 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 title: TextView = itemView.download_header_title
|
||||
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 downloadImage: ImageView = itemView.download_header_episode_download
|
||||
private val normalImage: ImageView = itemView.download_header_goto_child
|
||||
var localCard: VisualDownloadHeaderCached? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(card: VisualDownloadHeaderCached) {
|
||||
localCard = card
|
||||
val d = card.data
|
||||
if (d.poster != null) {
|
||||
|
||||
|
@ -93,8 +127,7 @@ class DownloadHeaderAdapter(
|
|||
downloadBar.visibility = View.VISIBLE
|
||||
downloadImage.visibility = View.VISIBLE
|
||||
normalImage.visibility = View.GONE
|
||||
|
||||
setUpButton(
|
||||
/*setUpButton(
|
||||
card.currentBytes,
|
||||
card.totalBytes,
|
||||
downloadBar,
|
||||
|
@ -102,7 +135,7 @@ class DownloadHeaderAdapter(
|
|||
extraInfo,
|
||||
card.child,
|
||||
movieClickCallback
|
||||
)
|
||||
)*/
|
||||
|
||||
holder.setOnClickListener {
|
||||
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.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
||||
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.VideoDownloadManager
|
||||
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_large.view.*
|
||||
import java.util.*
|
||||
|
||||
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
||||
const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2
|
||||
|
@ -49,12 +49,43 @@ class EpisodeAdapter(
|
|||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : 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
|
||||
private var layout: Int = 0
|
||||
fun updateLayout() {
|
||||
layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||
R.layout.result_episode_large
|
||||
else R.layout.result_episode
|
||||
layout =
|
||||
if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||
R.layout.result_episode_large
|
||||
else R.layout.result_episode
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
|
@ -62,7 +93,7 @@ class EpisodeAdapter(
|
|||
R.layout.result_episode_large
|
||||
else R.layout.result_episode*/
|
||||
|
||||
return CardViewHolder(
|
||||
return EpisodeCardViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
||||
hasDownloadSupport,
|
||||
clickCallback,
|
||||
|
@ -72,8 +103,9 @@ class EpisodeAdapter(
|
|||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is CardViewHolder -> {
|
||||
is EpisodeCardViewHolder -> {
|
||||
holder.bind(cardList[position])
|
||||
mBoundViewHolders.add(holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,26 +114,31 @@ class EpisodeAdapter(
|
|||
return cardList.size
|
||||
}
|
||||
|
||||
class CardViewHolder
|
||||
class EpisodeCardViewHolder
|
||||
constructor(
|
||||
itemView: View,
|
||||
private val hasDownloadSupport: Boolean,
|
||||
val hasDownloadSupport: Boolean,
|
||||
private val clickCallback: (EpisodeClickEvent) -> 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 episodeRating: TextView? = itemView.episode_rating
|
||||
private val episodeDescript: TextView? = itemView.episode_descript
|
||||
private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress
|
||||
private val episodePoster: ImageView? = itemView.episode_poster
|
||||
|
||||
private val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded
|
||||
private val episodeDownloadImage: ImageView = itemView.result_episode_download
|
||||
val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded
|
||||
val episodeDownloadImage: ImageView = itemView.result_episode_download
|
||||
|
||||
private val episodeHolder = itemView.episode_holder
|
||||
|
||||
var localCard: ResultEpisode? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(card: ResultEpisode) {
|
||||
localCard = card
|
||||
val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}"
|
||||
episodeText.text = name
|
||||
|
||||
|
@ -148,11 +185,15 @@ class EpisodeAdapter(
|
|||
|
||||
episodeDownloadImage.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)
|
||||
|
||||
DownloadButtonSetup.setUpButton(
|
||||
downloadButton.setUpButton(
|
||||
downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null,
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
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.ui.WatchType
|
||||
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.setUpMaterialButton
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
@ -70,7 +71,6 @@ import java.util.*
|
|||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
|
||||
const val MAX_SYNO_LENGH = 300
|
||||
|
||||
data class ResultEpisode(
|
||||
|
@ -156,6 +156,7 @@ class ResultFragment : Fragment() {
|
|||
private var currentHeaderName: String? = null
|
||||
private var currentType: TvType? = null
|
||||
private var currentEpisodes: List<ResultEpisode>? = null
|
||||
var downloadButton : EasyDownloadButton? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -167,8 +168,15 @@ class ResultFragment : Fragment() {
|
|||
return inflater.inflate(R.layout.fragment_result, container, false)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
(result_episodes?.adapter as EpisodeAdapter?)?.killAdapter()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
|
||||
downloadButton?.dispose()
|
||||
|
||||
super.onDestroy()
|
||||
activity?.let {
|
||||
it.window?.navigationBarColor =
|
||||
|
@ -873,8 +881,9 @@ class ResultFragment : Fragment() {
|
|||
val localId = d.getId()
|
||||
val file =
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), localId)
|
||||
|
||||
setUpMaterialButton(
|
||||
downloadButton?.dispose()
|
||||
downloadButton = EasyDownloadButton()
|
||||
downloadButton?.setUpMaterialButton(
|
||||
file?.fileLength,
|
||||
file?.totalBytes,
|
||||
result_movie_progress_downloaded,
|
||||
|
|
|
@ -131,8 +131,10 @@ class SearchFragment : Fragment() {
|
|||
when (it) {
|
||||
is Resource.Success -> {
|
||||
it.value.let { data ->
|
||||
(cardSpace?.adapter as SearchAdapter?)?.cardList = data
|
||||
cardSpace?.adapter?.notifyDataSetChanged()
|
||||
if(data != null) {
|
||||
(cardSpace?.adapter as SearchAdapter?)?.cardList = ArrayList( data.filterNotNull())
|
||||
cardSpace?.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
searchExitIcon.alpha = 1f
|
||||
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