fixed mem leak

This commit is contained in:
LagradOst 2021-07-28 21:14:45 +02:00
parent 8ff87e5108
commit f270f9f551
12 changed files with 427 additions and 227 deletions

View file

@ -1,8 +1,8 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'kotlin-android-extensions'
}
android {

View file

@ -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)
}
}

View file

@ -0,0 +1,6 @@
package com.lagradost.cloudstream3.ui.download
interface DownloadButtonViewHolder {
var downloadButton : EasyDownloadButton
fun reattachDownloadButton()
}

View file

@ -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
)
}
}
}
}

View file

@ -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)

View file

@ -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())

View file

@ -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
)
}
}
}
}

View file

@ -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))
}
}
}
}
}

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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()
}
}