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

View file

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

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

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? { 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)

View file

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

View file

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

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

View file

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

View file

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

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