Push initial UI

This commit is contained in:
Luna712 2024-07-06 15:10:43 -06:00 committed by GitHub
parent 3016a5176f
commit 068b218f7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 276 additions and 157 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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