mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Push initial UI
This commit is contained in:
parent
3016a5176f
commit
068b218f7a
8 changed files with 276 additions and 157 deletions
|
@ -27,7 +27,6 @@ const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
|
||||||
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
||||||
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
||||||
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
||||||
const val DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES = 6
|
|
||||||
|
|
||||||
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
|
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
|
||||||
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
|
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
|
||||||
|
@ -58,15 +57,11 @@ abstract class VisualDownloadCached(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DownloadActionEventBase(
|
|
||||||
open val action: Int,
|
|
||||||
open val data: VideoDownloadHelper.DownloadEpisodeCached?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class VisualDownloadChildCached(
|
data class VisualDownloadChildCached(
|
||||||
override val currentBytes: Long,
|
override val currentBytes: Long,
|
||||||
override val totalBytes: Long,
|
override val totalBytes: Long,
|
||||||
override val data: VideoDownloadHelper.DownloadEpisodeCached,
|
override val data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
val selected: Boolean = false,
|
||||||
): VisualDownloadCached(currentBytes, totalBytes, data)
|
): VisualDownloadCached(currentBytes, totalBytes, data)
|
||||||
|
|
||||||
data class VisualDownloadHeaderCached(
|
data class VisualDownloadHeaderCached(
|
||||||
|
@ -76,17 +71,13 @@ data class VisualDownloadHeaderCached(
|
||||||
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
||||||
val currentOngoingDownloads: Int,
|
val currentOngoingDownloads: Int,
|
||||||
val totalDownloads: Int,
|
val totalDownloads: Int,
|
||||||
|
val selected: Boolean = false,
|
||||||
): VisualDownloadCached(currentBytes, totalBytes, data)
|
): VisualDownloadCached(currentBytes, totalBytes, data)
|
||||||
|
|
||||||
data class DownloadClickEvent(
|
data class DownloadClickEvent(
|
||||||
override val action: Int,
|
val action: Int,
|
||||||
override val data: VideoDownloadHelper.DownloadEpisodeCached
|
val data: VideoDownloadHelper.DownloadEpisodeCached
|
||||||
): DownloadActionEventBase(action, data)
|
)
|
||||||
|
|
||||||
data class DownloadDeleteEvent(
|
|
||||||
override val action: Int,
|
|
||||||
val items: List<VideoDownloadHelper.DownloadEpisodeCached?>
|
|
||||||
): DownloadActionEventBase(action, null)
|
|
||||||
|
|
||||||
data class DownloadHeaderClickEvent(
|
data class DownloadHeaderClickEvent(
|
||||||
val action: Int,
|
val action: Int,
|
||||||
|
@ -94,19 +85,20 @@ data class DownloadHeaderClickEvent(
|
||||||
)
|
)
|
||||||
|
|
||||||
class DownloadAdapter(
|
class DownloadAdapter(
|
||||||
private val actionCallback: (DownloadActionEventBase) -> Unit,
|
private val headerClickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
|
private val selectedChangedCallback: (Int, String, Boolean) -> Unit,
|
||||||
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
|
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
|
private var showDeleteCheckbox: Boolean = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val VIEW_TYPE_HEADER = 0
|
private const val VIEW_TYPE_HEADER = 0
|
||||||
private const val VIEW_TYPE_CHILD = 1
|
private const val VIEW_TYPE_CHILD = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class DownloadViewHolder(
|
inner class DownloadViewHolder(
|
||||||
private val binding: ViewBinding,
|
private val binding: ViewBinding
|
||||||
private val actionCallback: (DownloadActionEventBase) -> Unit,
|
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(card: VisualDownloadCached?) {
|
fun bind(card: VisualDownloadCached?) {
|
||||||
|
@ -116,86 +108,105 @@ class DownloadAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun bindHeader(card: VisualDownloadHeaderCached?) {
|
private fun bindHeader(card: VisualDownloadHeaderCached?) {
|
||||||
if (binding !is DownloadHeaderEpisodeBinding) return
|
if (binding !is DownloadHeaderEpisodeBinding || card == null) return
|
||||||
card ?: return
|
|
||||||
val d = card.data
|
|
||||||
|
|
||||||
|
val data = card.data
|
||||||
binding.apply {
|
binding.apply {
|
||||||
downloadHeaderPoster.apply {
|
downloadHeaderPoster.apply {
|
||||||
setImage(d.poster)
|
setImage(data.poster)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_LOAD_RESULT, d))
|
headerClickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_LOAD_RESULT, data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
downloadHeaderTitle.text = data.name
|
||||||
downloadHeaderTitle.text = d.name
|
val formattedSize = formatShortFileSize(itemView.context, card.totalBytes)
|
||||||
val formattedSizeString = formatShortFileSize(itemView.context, card.totalBytes)
|
|
||||||
|
|
||||||
if (card.child != null) {
|
if (card.child != null) {
|
||||||
downloadHeaderGotoChild.isVisible = false
|
handleChildDownload(card, formattedSize)
|
||||||
|
} else handleParentDownload(card, formattedSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes)
|
private fun DownloadHeaderEpisodeBinding.handleChildDownload(
|
||||||
if (status == DownloadStatusTell.IsDone) {
|
card: VisualDownloadHeaderCached,
|
||||||
// We do this here instead if we are finished downloading
|
formattedSize: String
|
||||||
// so that we can use the value from the view model
|
) {
|
||||||
// rather than extra unneeded disk operations and to prevent a
|
card.child ?: return
|
||||||
// delay in updating download icon state.
|
downloadHeaderGotoChild.isVisible = false
|
||||||
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
|
||||||
downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes)
|
|
||||||
// We will let the view model handle this
|
|
||||||
downloadButton.doSetProgress = false
|
|
||||||
downloadButton.progressBar.progressDrawable =
|
|
||||||
downloadButton.getDrawableFromStatus(status)
|
|
||||||
?.let { ContextCompat.getDrawable(downloadButton.context, it) }
|
|
||||||
downloadHeaderInfo.text = formattedSizeString
|
|
||||||
} else {
|
|
||||||
downloadButton.doSetProgress = true
|
|
||||||
downloadButton.progressBar.progressDrawable =
|
|
||||||
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, actionCallback)
|
val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes)
|
||||||
downloadButton.isVisible = true
|
if (status == DownloadStatusTell.IsDone) {
|
||||||
|
// We do this here instead if we are finished downloading
|
||||||
|
// so that we can use the value from the view model
|
||||||
|
// rather than extra unneeded disk operations and to prevent a
|
||||||
|
// delay in updating download icon state.
|
||||||
|
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
||||||
|
downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes)
|
||||||
|
// We will let the view model handle this
|
||||||
|
downloadButton.doSetProgress = false
|
||||||
|
downloadButton.progressBar.progressDrawable =
|
||||||
|
downloadButton.getDrawableFromStatus(status)
|
||||||
|
?.let { ContextCompat.getDrawable(downloadButton.context, it) }
|
||||||
|
downloadHeaderInfo.text = formattedSize
|
||||||
|
} else {
|
||||||
|
downloadButton.doSetProgress = true
|
||||||
|
downloadButton.progressBar.progressDrawable =
|
||||||
|
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
||||||
|
}
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
||||||
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
downloadButton.isVisible = !showDeleteCheckbox
|
||||||
}
|
|
||||||
} else {
|
|
||||||
downloadButton.isVisible = false
|
|
||||||
downloadHeaderGotoChild.isVisible = true
|
|
||||||
|
|
||||||
try {
|
episodeHolder.apply {
|
||||||
downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
setOnClickListener {
|
||||||
.format(
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||||
card.totalDownloads,
|
}
|
||||||
downloadHeaderInfo.context.resources.getQuantityString(
|
setOnLongClickListener {
|
||||||
R.plurals.episodes,
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_LONG_CLICK, card.child))
|
||||||
card.totalDownloads
|
true
|
||||||
),
|
}
|
||||||
formattedSizeString
|
}
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// You probably formatted incorrectly
|
|
||||||
downloadHeaderInfo.text = "Error"
|
|
||||||
logError(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
deleteCheckbox.apply {
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_GO_TO_CHILD, d))
|
isVisible = showDeleteCheckbox
|
||||||
}
|
isChecked = card.selected
|
||||||
|
setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
selectedChangedCallback.invoke(card.data.id, card.data.name, isChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindChild(card: VisualDownloadChildCached?) {
|
@SuppressLint("SetTextI18n")
|
||||||
if (binding !is DownloadChildEpisodeBinding) return
|
private fun DownloadHeaderEpisodeBinding.handleParentDownload(
|
||||||
card ?: return
|
card: VisualDownloadHeaderCached,
|
||||||
val d = card.data
|
formattedSize: String
|
||||||
|
) {
|
||||||
|
downloadButton.isVisible = false
|
||||||
|
downloadHeaderGotoChild.isVisible = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format).format(
|
||||||
|
card.totalDownloads,
|
||||||
|
downloadHeaderInfo.context.resources.getQuantityString(R.plurals.episodes, card.totalDownloads),
|
||||||
|
formattedSize
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
downloadHeaderInfo.text = "Error"
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
headerClickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_GO_TO_CHILD, card.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindChild(card: VisualDownloadChildCached?) {
|
||||||
|
if (binding !is DownloadChildEpisodeBinding || card == null) return
|
||||||
|
|
||||||
|
val data = card.data
|
||||||
binding.apply {
|
binding.apply {
|
||||||
val posDur = getViewPos(d.id)
|
val posDur = getViewPos(data.id)
|
||||||
downloadChildEpisodeProgress.apply {
|
downloadChildEpisodeProgress.apply {
|
||||||
isVisible = posDur != null
|
isVisible = posDur != null
|
||||||
posDur?.let {
|
posDur?.let {
|
||||||
|
@ -205,14 +216,14 @@ class DownloadAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val status = downloadButton.getStatus(d.id, card.currentBytes, card.totalBytes)
|
val status = downloadButton.getStatus(data.id, card.currentBytes, card.totalBytes)
|
||||||
if (status == DownloadStatusTell.IsDone) {
|
if (status == DownloadStatusTell.IsDone) {
|
||||||
// We do this here instead if we are finished downloading
|
// We do this here instead if we are finished downloading
|
||||||
// so that we can use the value from the view model
|
// so that we can use the value from the view model
|
||||||
// rather than extra unneeded disk operations and to prevent a
|
// rather than extra unneeded disk operations and to prevent a
|
||||||
// delay in updating download icon state.
|
// delay in updating download icon state.
|
||||||
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
||||||
downloadButton.applyMetaData(d.id, card.currentBytes, card.totalBytes)
|
downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes)
|
||||||
// We will let the view model handle this
|
// We will let the view model handle this
|
||||||
downloadButton.doSetProgress = false
|
downloadButton.doSetProgress = false
|
||||||
downloadButton.progressBar.progressDrawable =
|
downloadButton.progressBar.progressDrawable =
|
||||||
|
@ -225,16 +236,16 @@ class DownloadAdapter(
|
||||||
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, actionCallback)
|
downloadButton.setDefaultClickListener(data, downloadChildEpisodeTextExtra, mediaClickCallback)
|
||||||
downloadButton.isVisible = true
|
downloadButton.isVisible = true
|
||||||
|
|
||||||
downloadChildEpisodeText.apply {
|
downloadChildEpisodeText.apply {
|
||||||
text = context.getNameFull(d.name, d.episode, d.season)
|
text = context.getNameFull(data.name, data.episode, data.season)
|
||||||
isSelected = true // Needed for text repeating
|
isSelected = true // Needed for text repeating
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadChildEpisodeHolder.setOnClickListener {
|
downloadChildEpisodeHolder.setOnClickListener {
|
||||||
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,7 +258,7 @@ class DownloadAdapter(
|
||||||
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
|
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
|
||||||
else -> throw IllegalArgumentException("Invalid view type")
|
else -> throw IllegalArgumentException("Invalid view type")
|
||||||
}
|
}
|
||||||
return DownloadViewHolder(binding, actionCallback, clickCallback)
|
return DownloadViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
|
||||||
|
@ -262,6 +273,12 @@ class DownloadAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDeleteCheckboxVisibility(visible: Boolean) {
|
||||||
|
if (showDeleteCheckbox == visible) return
|
||||||
|
showDeleteCheckbox = visible
|
||||||
|
notifyItemRangeChanged(0, itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
||||||
override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
|
override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
|
||||||
return oldItem.data.id == newItem.data.id
|
return oldItem.data.id == newItem.data.id
|
||||||
|
|
|
@ -20,11 +20,10 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
|
|
||||||
object DownloadButtonSetup {
|
object DownloadButtonSetup {
|
||||||
fun handleDownloadClick(click: DownloadActionEventBase) {
|
fun handleDownloadClick(click: DownloadClickEvent) {
|
||||||
|
val id = click.data.id
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
DOWNLOAD_ACTION_DELETE_FILE -> {
|
||||||
if (click !is DownloadDeleteEvent) return
|
|
||||||
val id = click.items.firstOrNull()?.id ?: return
|
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
||||||
val dialogClickListener =
|
val dialogClickListener =
|
||||||
|
@ -43,9 +42,9 @@ object DownloadButtonSetup {
|
||||||
.setMessage(
|
.setMessage(
|
||||||
ctx.getString(R.string.delete_message).format(
|
ctx.getString(R.string.delete_message).format(
|
||||||
ctx.getNameFull(
|
ctx.getNameFull(
|
||||||
click.items.firstOrNull()?.name,
|
click.data.name,
|
||||||
click.items.firstOrNull()?.episode,
|
click.data.episode,
|
||||||
click.items.firstOrNull()?.season
|
click.data.season
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -59,17 +58,15 @@ object DownloadButtonSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> {
|
DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> {
|
||||||
val id = click.data?.id ?: return
|
|
||||||
VideoDownloadManager.downloadEvent.invoke(
|
VideoDownloadManager.downloadEvent.invoke(
|
||||||
Pair(id, VideoDownloadManager.DownloadActionType.Pause)
|
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Pause)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DOWNLOAD_ACTION_RESUME_DOWNLOAD -> {
|
DOWNLOAD_ACTION_RESUME_DOWNLOAD -> {
|
||||||
val id = click.data?.id ?: return
|
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) {
|
if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) {
|
||||||
VideoDownloadManager.downloadEvent.invoke(
|
VideoDownloadManager.downloadEvent.invoke(
|
||||||
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
|
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
||||||
|
@ -77,19 +74,18 @@ object DownloadButtonSetup {
|
||||||
VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg)
|
VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg)
|
||||||
} else {
|
} else {
|
||||||
VideoDownloadManager.downloadEvent.invoke(
|
VideoDownloadManager.downloadEvent.invoke(
|
||||||
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
|
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DOWNLOAD_ACTION_LONG_CLICK -> {
|
DOWNLOAD_ACTION_LONG_CLICK -> {
|
||||||
val id = click.data?.id ?: return
|
|
||||||
activity?.let { act ->
|
activity?.let { act ->
|
||||||
val length =
|
val length =
|
||||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||||
act,
|
act,
|
||||||
id
|
click.data.id
|
||||||
)?.fileLength
|
)?.fileLength
|
||||||
?: 0
|
?: 0
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
|
@ -100,20 +96,19 @@ object DownloadButtonSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DOWNLOAD_ACTION_PLAY_FILE -> {
|
DOWNLOAD_ACTION_PLAY_FILE -> {
|
||||||
val id = click.data?.id ?: return
|
|
||||||
activity?.let { act ->
|
activity?.let { act ->
|
||||||
val info =
|
val info =
|
||||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||||
act,
|
act,
|
||||||
id
|
click.data.id
|
||||||
) ?: return
|
) ?: return
|
||||||
val keyInfo = getKey<VideoDownloadManager.DownloadedFileInfo>(
|
val keyInfo = getKey<VideoDownloadManager.DownloadedFileInfo>(
|
||||||
VideoDownloadManager.KEY_DOWNLOAD_INFO,
|
VideoDownloadManager.KEY_DOWNLOAD_INFO,
|
||||||
id.toString()
|
click.data.id.toString()
|
||||||
) ?: return
|
) ?: return
|
||||||
val parent = getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
val parent = getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||||
DOWNLOAD_HEADER_CACHE,
|
DOWNLOAD_HEADER_CACHE,
|
||||||
click.data?.parentId.toString()
|
click.data.parentId.toString()
|
||||||
) ?: return
|
) ?: return
|
||||||
|
|
||||||
act.navigate(
|
act.navigate(
|
||||||
|
@ -123,11 +118,11 @@ object DownloadButtonSetup {
|
||||||
ExtractorUri(
|
ExtractorUri(
|
||||||
uri = info.path,
|
uri = info.path,
|
||||||
|
|
||||||
id = id,
|
id = click.data.id,
|
||||||
parentId = click.data?.parentId,
|
parentId = click.data.parentId,
|
||||||
name = act.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName
|
name = act.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName
|
||||||
season = click.data?.season,
|
season = click.data.season,
|
||||||
episode = click.data?.episode,
|
episode = click.data.episode,
|
||||||
headerName = parent.name,
|
headerName = parent.name,
|
||||||
tvType = parent.type,
|
tvType = parent.type,
|
||||||
|
|
||||||
|
@ -144,13 +139,13 @@ object DownloadButtonSetup {
|
||||||
// keyInfo.basePath,
|
// keyInfo.basePath,
|
||||||
// keyInfo.relativePath,
|
// keyInfo.relativePath,
|
||||||
// keyInfo.displayName,
|
// keyInfo.displayName,
|
||||||
// click.data?.parentId,
|
// click.data.parentId,
|
||||||
// click.data?.id,
|
// click.data.id,
|
||||||
// headerName ?: "null",
|
// headerName ?: "null",
|
||||||
// if (click.data.episode <= 0) null else click.data.episode,
|
// if (click.data.episode <= 0) null else click.data.episode,
|
||||||
// click.data.season
|
// click.data.season
|
||||||
// ),
|
// ),
|
||||||
// getViewPos(click.data?.id)?.position ?: 0
|
// getViewPos(click.data.id)?.position ?: 0
|
||||||
//)
|
//)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,8 +107,13 @@ class DownloadChildFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter = DownloadAdapter(
|
val adapter = DownloadAdapter(
|
||||||
{ actionEvent -> handleActionEvent(folder, actionEvent, context) },
|
{},
|
||||||
{}
|
{ downloadClickEvent ->
|
||||||
|
handleDownloadClick(downloadClickEvent)
|
||||||
|
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||||
|
setUpDownloadDeleteListener(folder)
|
||||||
|
}
|
||||||
|
}, { _, _, _ -> }
|
||||||
)
|
)
|
||||||
|
|
||||||
binding?.downloadChildList?.apply {
|
binding?.downloadChildList?.apply {
|
||||||
|
@ -125,25 +130,6 @@ class DownloadChildFragment : Fragment() {
|
||||||
updateList(folder)
|
updateList(folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleActionEvent(folder: String, actionEvent: DownloadActionEventBase, context: Context?) {
|
|
||||||
when (actionEvent.action) {
|
|
||||||
DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES -> {
|
|
||||||
if (actionEvent is DownloadDeleteEvent) {
|
|
||||||
context?.let { downloadsViewModel.handleMultiDelete(it, actionEvent) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
|
||||||
val downloadDeleteEvent = DownloadDeleteEvent(
|
|
||||||
action = DOWNLOAD_ACTION_DELETE_FILE,
|
|
||||||
items = listOf(actionEvent.data)
|
|
||||||
)
|
|
||||||
handleDownloadClick(downloadDeleteEvent)
|
|
||||||
setUpDownloadDeleteListener(folder)
|
|
||||||
}
|
|
||||||
else -> handleDownloadClick(actionEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpDownloadDeleteListener(folder: String) {
|
private fun setUpDownloadDeleteListener(folder: String) {
|
||||||
downloadDeleteEventListener = { id: Int ->
|
downloadDeleteEventListener = { id: Int ->
|
||||||
val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList
|
val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList
|
||||||
|
|
|
@ -91,6 +91,10 @@ class DownloadFragment : Fragment() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
||||||
|
|
||||||
|
// We always want fresh selections
|
||||||
|
// when navigating to downloads
|
||||||
|
downloadsViewModel.resetSelected()
|
||||||
|
|
||||||
observe(downloadsViewModel.headerCards) {
|
observe(downloadsViewModel.headerCards) {
|
||||||
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(it)
|
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(it)
|
||||||
binding?.downloadLoading?.isVisible = false
|
binding?.downloadLoading?.isVisible = false
|
||||||
|
@ -106,10 +110,32 @@ class DownloadFragment : Fragment() {
|
||||||
observe(downloadsViewModel.downloadBytes) {
|
observe(downloadsViewModel.downloadBytes) {
|
||||||
updateStorageInfo(view.context, it, R.string.app_storage, binding?.downloadAppTxt, binding?.downloadApp)
|
updateStorageInfo(view.context, it, R.string.app_storage, binding?.downloadAppTxt, binding?.downloadApp)
|
||||||
}
|
}
|
||||||
|
observe(downloadsViewModel.selectedIds) {
|
||||||
|
handleSelectedChange(it)
|
||||||
|
updateSelectedState(it)
|
||||||
|
binding?.downloadDeleteToolbar?.btnDelete?.text =
|
||||||
|
getString(R.string.delete_count).format(it.count())
|
||||||
|
}
|
||||||
|
|
||||||
val adapter = DownloadAdapter(
|
val adapter = DownloadAdapter(
|
||||||
{ actionEvent -> handleActionEvent(actionEvent, context) },
|
{ click -> handleItemClick(click) },
|
||||||
{ click -> handleItemClick(click) }
|
{ downloadClickEvent ->
|
||||||
|
handleDownloadClick(downloadClickEvent)
|
||||||
|
when (downloadClickEvent.action) {
|
||||||
|
DOWNLOAD_ACTION_DELETE_FILE -> setUpDownloadDeleteListener()
|
||||||
|
DOWNLOAD_ACTION_LONG_CLICK -> downloadClickEvent.data.name?.let {
|
||||||
|
downloadsViewModel.addSelected(
|
||||||
|
downloadClickEvent.data.id,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ id, name, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
downloadsViewModel.addSelected(id, name)
|
||||||
|
} else downloadsViewModel.removeSelected(id)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
binding?.downloadList?.apply {
|
binding?.downloadList?.apply {
|
||||||
|
@ -144,25 +170,6 @@ class DownloadFragment : Fragment() {
|
||||||
fixPaddingStatusbar(binding?.downloadRoot)
|
fixPaddingStatusbar(binding?.downloadRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleActionEvent(actionEvent: DownloadActionEventBase, context: Context?) {
|
|
||||||
when (actionEvent.action) {
|
|
||||||
DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES -> {
|
|
||||||
if (actionEvent is DownloadDeleteEvent) {
|
|
||||||
context?.let { downloadsViewModel.handleMultiDelete(it, actionEvent) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
|
||||||
val downloadDeleteEvent = DownloadDeleteEvent(
|
|
||||||
action = DOWNLOAD_ACTION_DELETE_FILE,
|
|
||||||
items = listOf(actionEvent.data)
|
|
||||||
)
|
|
||||||
handleDownloadClick(downloadDeleteEvent)
|
|
||||||
setUpDownloadDeleteListener()
|
|
||||||
}
|
|
||||||
else -> handleDownloadClick(actionEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
DOWNLOAD_ACTION_GO_TO_CHILD -> {
|
DOWNLOAD_ACTION_GO_TO_CHILD -> {
|
||||||
|
@ -180,6 +187,37 @@ class DownloadFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSelectedChange(selected: HashMap<Int, String>) {
|
||||||
|
val adapter = (binding?.downloadList?.adapter as? DownloadAdapter)
|
||||||
|
if (selected.isNotEmpty()) {
|
||||||
|
binding?.downloadDeleteToolbar?.downloadDeleteToolbar?.isVisible = true
|
||||||
|
binding?.downloadStorageAppbar?.isVisible = false
|
||||||
|
binding?.downloadDeleteToolbar?.btnDelete?.setOnClickListener {
|
||||||
|
context?.let { ctx -> downloadsViewModel.handleMultiDelete(ctx) }
|
||||||
|
}
|
||||||
|
binding?.downloadDeleteToolbar?.btnCancel?.setOnClickListener {
|
||||||
|
downloadsViewModel.resetSelected()
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter?.setDeleteCheckboxVisibility(true)
|
||||||
|
} else {
|
||||||
|
binding?.downloadDeleteToolbar?.downloadDeleteToolbar?.isVisible = false
|
||||||
|
binding?.downloadStorageAppbar?.isVisible =
|
||||||
|
// Make sure we don't display it early
|
||||||
|
!downloadsViewModel.headerCards.value.isNullOrEmpty() &&
|
||||||
|
downloadsViewModel.usedBytes.value?.let { it > 0 } == true
|
||||||
|
adapter?.setDeleteCheckboxVisibility(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSelectedState(selected: HashMap<Int, String>) {
|
||||||
|
val currentList = downloadsViewModel.headerCards.value ?: return
|
||||||
|
val updatedList = currentList.map { header ->
|
||||||
|
header.copy(selected = selected.keys.contains(header.data.id))
|
||||||
|
}
|
||||||
|
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(updatedList)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setUpDownloadDeleteListener() {
|
private fun setUpDownloadDeleteListener() {
|
||||||
downloadDeleteEventListener = { id ->
|
downloadDeleteEventListener = { id ->
|
||||||
val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList
|
val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList
|
||||||
|
|
|
@ -34,12 +34,42 @@ class DownloadViewModel : ViewModel() {
|
||||||
private val _availableBytes = MutableLiveData<Long>()
|
private val _availableBytes = MutableLiveData<Long>()
|
||||||
private val _downloadBytes = MutableLiveData<Long>()
|
private val _downloadBytes = MutableLiveData<Long>()
|
||||||
|
|
||||||
|
private val _selectedIds = MutableLiveData<HashMap<Int, String>>(HashMap())
|
||||||
|
|
||||||
val usedBytes: LiveData<Long> = _usedBytes
|
val usedBytes: LiveData<Long> = _usedBytes
|
||||||
val availableBytes: LiveData<Long> = _availableBytes
|
val availableBytes: LiveData<Long> = _availableBytes
|
||||||
val downloadBytes: LiveData<Long> = _downloadBytes
|
val downloadBytes: LiveData<Long> = _downloadBytes
|
||||||
|
|
||||||
|
val selectedIds: LiveData<HashMap<Int, String>> = _selectedIds
|
||||||
|
|
||||||
private var previousVisual: List<VisualDownloadHeaderCached>? = null
|
private var previousVisual: List<VisualDownloadHeaderCached>? = null
|
||||||
|
|
||||||
|
fun addSelected(id: Int, name: String) {
|
||||||
|
_selectedIds.value?.let { selectedIds ->
|
||||||
|
selectedIds[id] = name
|
||||||
|
_selectedIds.postValue(selectedIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSelected(id: Int) {
|
||||||
|
_selectedIds.value?.let { selectedIds ->
|
||||||
|
selectedIds.remove(id)
|
||||||
|
_selectedIds.postValue(selectedIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSelected() {
|
||||||
|
_selectedIds.postValue(HashMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSelectedIds(): List<Int> {
|
||||||
|
return _selectedIds.value?.keys?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSelectedNames(): List<String> {
|
||||||
|
return _selectedIds.value?.values?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
fun updateList(context: Context) = viewModelScope.launchSafe {
|
fun updateList(context: Context) = viewModelScope.launchSafe {
|
||||||
val children = withContext(Dispatchers.IO) {
|
val children = withContext(Dispatchers.IO) {
|
||||||
context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||||
|
@ -127,12 +157,9 @@ class DownloadViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleMultiDelete(context: Context, event: DownloadDeleteEvent) = viewModelScope.launchSafe {
|
fun handleMultiDelete(context: Context) = viewModelScope.launchSafe {
|
||||||
val ids: List<Int> = event.items.mapNotNull { it?.id }
|
val ids: List<Int> = getSelectedIds()
|
||||||
.takeIf { it.isNotEmpty() } ?: return@launchSafe
|
val names: List<String> = getSelectedNames()
|
||||||
|
|
||||||
val names: List<String> = event.items.mapNotNull { it?.name }
|
|
||||||
.takeIf { it.isNotEmpty() } ?: return@launchSafe
|
|
||||||
|
|
||||||
showDeleteConfirmationDialog(context, ids, names)
|
showDeleteConfirmationDialog(context, ids, names)
|
||||||
}
|
}
|
||||||
|
|
41
app/src/main/res/layout/download_delete_toolbar.xml
Normal file
41
app/src/main/res/layout/download_delete_toolbar.xml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
tools:layout_height="100dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:id="@+id/download_delete_toolbar"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btnCancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDelete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:text="@string/delete"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
|
@ -77,5 +77,16 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:nextFocusLeft="@id/episode_holder"
|
android:nextFocusLeft="@id/episode_holder"
|
||||||
android:padding="10dp" />
|
android:padding="10dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/delete_checkbox"
|
||||||
|
android:layout_width="@dimen/download_size"
|
||||||
|
android:layout_height="@dimen/download_size"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:layout_marginStart="-50dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:nextFocusLeft="@id/episode_holder"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:visibility="gone" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
|
@ -8,6 +8,10 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:context=".ui.download.DownloadFragment">
|
tools:context=".ui.download.DownloadFragment">
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/download_delete_toolbar"
|
||||||
|
android:id="@+id/download_delete_toolbar" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue