mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Initial support for multi deleting downloads
This is probably a horrible way to do this, I just am not sure of the best way to go about this to be honest
This commit is contained in:
parent
c1b5f5c128
commit
7b1f59fdb4
6 changed files with 125 additions and 43 deletions
|
@ -27,6 +27,7 @@ const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
|
|||
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
||||
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
||||
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_LOAD_RESULT = 1
|
||||
|
@ -57,6 +58,11 @@ abstract class VisualDownloadCached(
|
|||
}
|
||||
}
|
||||
|
||||
abstract class DownloadActionEventBase(
|
||||
open val action: Int,
|
||||
open val data: VideoDownloadHelper.DownloadEpisodeCached?
|
||||
)
|
||||
|
||||
data class VisualDownloadChildCached(
|
||||
override val currentBytes: Long,
|
||||
override val totalBytes: Long,
|
||||
|
@ -73,9 +79,14 @@ data class VisualDownloadHeaderCached(
|
|||
): VisualDownloadCached(currentBytes, totalBytes, data)
|
||||
|
||||
data class DownloadClickEvent(
|
||||
val action: Int,
|
||||
val data: VideoDownloadHelper.DownloadEpisodeCached
|
||||
)
|
||||
override val action: Int,
|
||||
override 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(
|
||||
val action: Int,
|
||||
|
@ -83,8 +94,8 @@ data class DownloadHeaderClickEvent(
|
|||
)
|
||||
|
||||
class DownloadAdapter(
|
||||
private val actionCallback: (DownloadActionEventBase) -> Unit,
|
||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
|
||||
|
||||
companion object {
|
||||
|
@ -94,8 +105,8 @@ class DownloadAdapter(
|
|||
|
||||
inner class DownloadViewHolder(
|
||||
private val binding: ViewBinding,
|
||||
private val actionCallback: (DownloadActionEventBase) -> Unit,
|
||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(card: VisualDownloadCached?) {
|
||||
|
@ -145,11 +156,11 @@ class DownloadAdapter(
|
|||
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
||||
}
|
||||
|
||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, actionCallback)
|
||||
downloadButton.isVisible = true
|
||||
|
||||
episodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||
}
|
||||
} else {
|
||||
downloadButton.isVisible = false
|
||||
|
@ -214,7 +225,7 @@ class DownloadAdapter(
|
|||
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
|
||||
}
|
||||
|
||||
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, mediaClickCallback)
|
||||
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, actionCallback)
|
||||
downloadButton.isVisible = true
|
||||
|
||||
downloadChildEpisodeText.apply {
|
||||
|
@ -223,7 +234,7 @@ class DownloadAdapter(
|
|||
}
|
||||
|
||||
downloadChildEpisodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +247,7 @@ class DownloadAdapter(
|
|||
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
|
||||
else -> throw IllegalArgumentException("Invalid view type")
|
||||
}
|
||||
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
|
||||
return DownloadViewHolder(binding, actionCallback, clickCallback)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
|
||||
|
|
|
@ -17,19 +17,21 @@ import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
import kotlinx.coroutines.MainScope
|
||||
|
||||
object DownloadButtonSetup {
|
||||
fun handleDownloadClick(click: DownloadClickEvent) {
|
||||
val id = click.data.id
|
||||
fun handleDownloadClick(click: DownloadActionEventBase) {
|
||||
when (click.action) {
|
||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
||||
if (click !is DownloadDeleteEvent) return
|
||||
val id = click.items.firstOrNull()?.id ?: return
|
||||
activity?.let { ctx ->
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
||||
val dialogClickListener =
|
||||
DialogInterface.OnClickListener { _, which ->
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id)
|
||||
VideoDownloadManager.deleteFilesAndUpdateSettings(ctx, listOf(id), MainScope())
|
||||
}
|
||||
DialogInterface.BUTTON_NEGATIVE -> {
|
||||
}
|
||||
|
@ -41,9 +43,9 @@ object DownloadButtonSetup {
|
|||
.setMessage(
|
||||
ctx.getString(R.string.delete_message).format(
|
||||
ctx.getNameFull(
|
||||
click.data.name,
|
||||
click.data.episode,
|
||||
click.data.season
|
||||
click.items.firstOrNull()?.name,
|
||||
click.items.firstOrNull()?.episode,
|
||||
click.items.firstOrNull()?.season
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -57,15 +59,17 @@ object DownloadButtonSetup {
|
|||
}
|
||||
}
|
||||
DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> {
|
||||
val id = click.data?.id ?: return
|
||||
VideoDownloadManager.downloadEvent.invoke(
|
||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Pause)
|
||||
Pair(id, VideoDownloadManager.DownloadActionType.Pause)
|
||||
)
|
||||
}
|
||||
DOWNLOAD_ACTION_RESUME_DOWNLOAD -> {
|
||||
val id = click.data?.id ?: return
|
||||
activity?.let { ctx ->
|
||||
if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) {
|
||||
VideoDownloadManager.downloadEvent.invoke(
|
||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
|
||||
)
|
||||
} else {
|
||||
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
||||
|
@ -73,18 +77,19 @@ object DownloadButtonSetup {
|
|||
VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg)
|
||||
} else {
|
||||
VideoDownloadManager.downloadEvent.invoke(
|
||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DOWNLOAD_ACTION_LONG_CLICK -> {
|
||||
val id = click.data?.id ?: return
|
||||
activity?.let { act ->
|
||||
val length =
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||
act,
|
||||
click.data.id
|
||||
id
|
||||
)?.fileLength
|
||||
?: 0
|
||||
if (length > 0) {
|
||||
|
@ -95,19 +100,20 @@ object DownloadButtonSetup {
|
|||
}
|
||||
}
|
||||
DOWNLOAD_ACTION_PLAY_FILE -> {
|
||||
val id = click.data?.id ?: return
|
||||
activity?.let { act ->
|
||||
val info =
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||
act,
|
||||
click.data.id
|
||||
id
|
||||
) ?: return
|
||||
val keyInfo = getKey<VideoDownloadManager.DownloadedFileInfo>(
|
||||
VideoDownloadManager.KEY_DOWNLOAD_INFO,
|
||||
click.data.id.toString()
|
||||
id.toString()
|
||||
) ?: return
|
||||
val parent = getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
click.data.parentId.toString()
|
||||
click.data?.parentId.toString()
|
||||
) ?: return
|
||||
|
||||
act.navigate(
|
||||
|
@ -117,11 +123,11 @@ object DownloadButtonSetup {
|
|||
ExtractorUri(
|
||||
uri = info.path,
|
||||
|
||||
id = click.data.id,
|
||||
parentId = click.data.parentId,
|
||||
id = id,
|
||||
parentId = click.data?.parentId,
|
||||
name = act.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName
|
||||
season = click.data.season,
|
||||
episode = click.data.episode,
|
||||
season = click.data?.season,
|
||||
episode = click.data?.episode,
|
||||
headerName = parent.name,
|
||||
tvType = parent.type,
|
||||
|
||||
|
@ -138,17 +144,47 @@ object DownloadButtonSetup {
|
|||
// keyInfo.basePath,
|
||||
// keyInfo.relativePath,
|
||||
// keyInfo.displayName,
|
||||
// click.data.parentId,
|
||||
// click.data.id,
|
||||
// click.data?.parentId,
|
||||
// click.data?.id,
|
||||
// headerName ?: "null",
|
||||
// if (click.data.episode <= 0) null else click.data.episode,
|
||||
// click.data.season
|
||||
// ),
|
||||
// getViewPos(click.data.id)?.position ?: 0
|
||||
// getViewPos(click.data?.id)?.position ?: 0
|
||||
//)
|
||||
)
|
||||
}
|
||||
}
|
||||
DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES -> {
|
||||
activity?.let { ctx ->
|
||||
if (click !is DownloadDeleteEvent) return
|
||||
val ids: List<Int> = click.items.mapNotNull { it?.id }
|
||||
.takeIf { it.isNotEmpty() } ?: return@let
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
||||
val dialogClickListener =
|
||||
DialogInterface.OnClickListener { _, which ->
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
VideoDownloadManager.deleteFilesAndUpdateSettings(ctx, ids, MainScope())
|
||||
}
|
||||
DialogInterface.BUTTON_NEGATIVE -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
builder.setTitle(R.string.delete_files)
|
||||
.setMessage(
|
||||
ctx.getString(R.string.delete_multiple_message)
|
||||
)
|
||||
.setPositiveButton(R.string.delete, dialogClickListener)
|
||||
.setNegativeButton(R.string.cancel, dialogClickListener)
|
||||
.show().setDefaultFocus()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,13 +102,17 @@ class DownloadChildFragment : Fragment() {
|
|||
}
|
||||
|
||||
val adapter = DownloadAdapter(
|
||||
{},
|
||||
{ downloadClickEvent ->
|
||||
handleDownloadClick(downloadClickEvent)
|
||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
{ actionEvent ->
|
||||
if (actionEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
val downloadDeleteEvent = DownloadDeleteEvent(
|
||||
action = DOWNLOAD_ACTION_DELETE_FILE,
|
||||
items = listOf(actionEvent.data)
|
||||
)
|
||||
handleDownloadClick(downloadDeleteEvent)
|
||||
setUpDownloadDeleteListener(folder)
|
||||
}
|
||||
}
|
||||
} else handleDownloadClick(actionEvent)
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
binding?.downloadChildList?.apply {
|
||||
|
|
|
@ -108,14 +108,18 @@ class DownloadFragment : Fragment() {
|
|||
}
|
||||
|
||||
val adapter = DownloadAdapter(
|
||||
{ actionEvent ->
|
||||
if (actionEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
val downloadDeleteEvent = DownloadDeleteEvent(
|
||||
action = DOWNLOAD_ACTION_DELETE_FILE,
|
||||
items = listOf(actionEvent.data)
|
||||
)
|
||||
handleDownloadClick(downloadDeleteEvent)
|
||||
setUpDownloadDeleteListener()
|
||||
} else handleDownloadClick(actionEvent)
|
||||
},
|
||||
{ click ->
|
||||
handleItemClick(click)
|
||||
},
|
||||
{ downloadClickEvent ->
|
||||
handleDownloadClick(downloadClickEvent)
|
||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
setUpDownloadDeleteListener()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.work.WorkManager
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.api.Log
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
|
@ -29,6 +30,7 @@ import com.lagradost.cloudstream3.MainActivity
|
|||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.services.VideoDownloadService
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
@ -42,6 +44,8 @@ import kotlinx.coroutines.CancellationException
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
|
@ -1705,7 +1709,28 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
fun deleteFileAndUpdateSettings(context: Context, id: Int): Boolean {
|
||||
fun deleteFilesAndUpdateSettings(context: Context, ids: List<Int>, scope: CoroutineScope) {
|
||||
scope.launchSafe(Dispatchers.IO) {
|
||||
val deleteJobs = ids.map { id ->
|
||||
async {
|
||||
id to deleteFileAndUpdateSettings(context, id)
|
||||
}
|
||||
}
|
||||
val results = deleteJobs.awaitAll()
|
||||
|
||||
val failedDeletes = results.filterNot { it.second }
|
||||
if (failedDeletes.isNotEmpty()) {
|
||||
failedDeletes.forEach { (id, _) ->
|
||||
// TODO show a toast if some failed?
|
||||
Log.e("FileDeletion", "Failed to delete file with ID: $id")
|
||||
}
|
||||
} else {
|
||||
Log.i("FileDeletion", "All files deleted successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteFileAndUpdateSettings(context: Context, id: Int): Boolean {
|
||||
val success = deleteFile(context, id)
|
||||
if (success) context.removeKey(KEY_DOWNLOAD_INFO, id.toString())
|
||||
return success
|
||||
|
|
|
@ -300,6 +300,7 @@
|
|||
<string name="episode_short">E</string>
|
||||
<string name="no_episodes_found">No Episodes found</string>
|
||||
<string name="delete_file">Delete File</string>
|
||||
<string name="delete_files">Delete Files</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="pause">Pause</string>
|
||||
|
@ -311,6 +312,7 @@
|
|||
<string name="go_back_30">-30</string>
|
||||
<string name="go_forward_30">+30</string>
|
||||
<string name="delete_message" formatted="true">This will permanently delete %s\nAre you sure?</string>
|
||||
<string name="delete_multiple_message" formatted="true">This will permanently delete all of: %s\nAre you sure?</string>
|
||||
<string name="resume_time_left" formatted="true">%dm\nremaining</string>
|
||||
<string name="resume_remaining" formatted="true">%s\nremaining</string>
|
||||
<string name="status_ongoing">Ongoing</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue