mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 16:30:31 +05:30
Merge pull request #4101 from Bnyro/stop-download
Notification action to stop download
This commit is contained in:
commit
ef14d1a04a
@ -24,6 +24,9 @@ interface DownloadDao {
|
||||
@Query("SELECT * FROM downloaditem WHERE id = :id")
|
||||
suspend fun findDownloadItemById(id: Int): DownloadItem
|
||||
|
||||
@Query("DELETE FROM downloaditem WHERE id = :id")
|
||||
suspend fun deleteDownloadItemById(id: Int)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertDownload(download: Download)
|
||||
|
||||
|
@ -6,6 +6,8 @@ sealed class DownloadStatus {
|
||||
|
||||
object Paused : DownloadStatus()
|
||||
|
||||
object Stopped : DownloadStatus()
|
||||
|
||||
data class Progress(
|
||||
val progress: Long,
|
||||
val downloaded: Long,
|
||||
|
@ -25,5 +25,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_RESUME"
|
||||
const val ACTION_DOWNLOAD_PAUSE =
|
||||
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_PAUSE"
|
||||
const val ACTION_DOWNLOAD_STOP =
|
||||
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_STOP"
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.util.remove
|
||||
import androidx.core.util.set
|
||||
import androidx.core.util.valueIterator
|
||||
import androidx.lifecycle.LifecycleService
|
||||
@ -37,6 +38,7 @@ import com.github.libretube.parcelable.DownloadData
|
||||
import com.github.libretube.receivers.NotificationReceiver
|
||||
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_PAUSE
|
||||
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_RESUME
|
||||
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_STOP
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
@ -45,23 +47,25 @@ import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createFile
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.fileSize
|
||||
import kotlin.math.min
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createFile
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.fileSize
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Download service with custom implementation of downloading using [HttpURLConnection].
|
||||
@ -87,9 +91,11 @@ class DownloadService : LifecycleService() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
val downloadId = intent?.getIntExtra("id", -1)
|
||||
when (intent?.action) {
|
||||
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
|
||||
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
|
||||
ACTION_DOWNLOAD_RESUME -> resume(downloadId!!)
|
||||
ACTION_DOWNLOAD_PAUSE -> pause(downloadId!!)
|
||||
ACTION_DOWNLOAD_STOP -> stop(downloadId!!)
|
||||
}
|
||||
|
||||
val downloadData = intent?.parcelableExtra<DownloadData>(IntentData.downloadData)
|
||||
@ -200,7 +206,7 @@ class DownloadService : LifecycleService() {
|
||||
notificationBuilder
|
||||
.setContentText(
|
||||
totalRead.formatAsFileSize() + " / " +
|
||||
item.downloadSize.formatAsFileSize()
|
||||
item.downloadSize.formatAsFileSize()
|
||||
)
|
||||
.setProgress(
|
||||
item.downloadSize.toInt(),
|
||||
@ -230,6 +236,11 @@ class DownloadService : LifecycleService() {
|
||||
}
|
||||
}
|
||||
|
||||
if (_downloadFlow.firstOrNull { it.first == item.id }?.second == DownloadStatus.Stopped) {
|
||||
downloadQueue.remove(item.id, false)
|
||||
return
|
||||
}
|
||||
|
||||
val completed = when {
|
||||
totalRead < item.downloadSize -> {
|
||||
_downloadFlow.emit(item.id to DownloadStatus.Paused)
|
||||
@ -324,9 +335,30 @@ class DownloadService : LifecycleService() {
|
||||
fun pause(id: Int) {
|
||||
downloadQueue[id] = false
|
||||
|
||||
// Stop the service if no downloads are active.
|
||||
stopServiceIfDone()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop downloading job for given [id]. If no downloads are active, stop the service.
|
||||
*/
|
||||
private fun stop(id: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadQueue[id] = false
|
||||
_downloadFlow.emit(id to DownloadStatus.Stopped)
|
||||
|
||||
lifecycleScope.launch {
|
||||
val item = Database.downloadDao().findDownloadItemById(id)
|
||||
notificationManager.cancel(item.getNotificationId())
|
||||
Database.downloadDao().deleteDownloadItemById(id)
|
||||
stopServiceIfDone()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop service if no downloads are active
|
||||
*/
|
||||
private fun stopServiceIfDone() {
|
||||
if (downloadQueue.valueIterator().asSequence().none { it }) {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
|
||||
ServiceCompat.stopForeground(this@DownloadService, ServiceCompat.STOP_FOREGROUND_DETACH)
|
||||
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
|
||||
stopSelf()
|
||||
}
|
||||
@ -398,6 +430,7 @@ class DownloadService : LifecycleService() {
|
||||
.setOngoing(true)
|
||||
.clearActions()
|
||||
.addAction(getPauseAction(item.id))
|
||||
.addAction(getStopAction(item.id))
|
||||
|
||||
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
||||
}
|
||||
@ -421,6 +454,7 @@ class DownloadService : LifecycleService() {
|
||||
.setSmallIcon(R.drawable.ic_pause)
|
||||
.setContentText(getString(R.string.download_paused))
|
||||
.addAction(getResumeAction(item.id))
|
||||
.addAction(getStopAction(item.id))
|
||||
}
|
||||
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
||||
}
|
||||
@ -437,6 +471,21 @@ class DownloadService : LifecycleService() {
|
||||
).build()
|
||||
}
|
||||
|
||||
private fun getStopAction(id: Int): NotificationCompat.Action {
|
||||
val intent = Intent(this, NotificationReceiver::class.java).apply {
|
||||
action = ACTION_DOWNLOAD_STOP
|
||||
putExtra("id", id)
|
||||
}
|
||||
|
||||
// the request code must differ from the one of the pause/resume action
|
||||
val requestCode = Int.MAX_VALUE / 2 - id
|
||||
return NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_stop,
|
||||
getString(R.string.stop),
|
||||
PendingIntentCompat.getBroadcast(this, requestCode, intent, FLAG_UPDATE_CURRENT, false)
|
||||
).build()
|
||||
}
|
||||
|
||||
private fun getPauseAction(id: Int): NotificationCompat.Action {
|
||||
val intent = Intent(this, NotificationReceiver::class.java)
|
||||
.setAction(ACTION_DOWNLOAD_PAUSE)
|
||||
|
@ -24,12 +24,12 @@ import com.github.libretube.receivers.DownloadReceiver
|
||||
import com.github.libretube.services.DownloadService
|
||||
import com.github.libretube.ui.adapters.DownloadsAdapter
|
||||
import com.github.libretube.ui.viewholders.DownloadsViewHolder
|
||||
import kotlin.io.path.fileSize
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.io.path.fileSize
|
||||
|
||||
class DownloadsFragment : Fragment() {
|
||||
private var _binding: FragmentDownloadsBinding? = null
|
||||
@ -165,13 +165,15 @@ class DownloadsFragment : Fragment() {
|
||||
downloadOverlay.visibility = View.GONE
|
||||
}
|
||||
|
||||
DownloadStatus.Stopped -> Unit
|
||||
|
||||
is DownloadStatus.Progress -> {
|
||||
downloadOverlay.visibility = View.VISIBLE
|
||||
resumePauseBtn.setImageResource(R.drawable.ic_pause)
|
||||
if (progressBar.isIndeterminate) return
|
||||
progressBar.incrementProgressBy(status.progress.toInt())
|
||||
val progressInfo = progressBar.progress.formatAsFileSize() +
|
||||
" /\n" + progressBar.max.formatAsFileSize()
|
||||
" /\n" + progressBar.max.formatAsFileSize()
|
||||
fileSize.text = progressInfo
|
||||
}
|
||||
|
||||
|
10
app/src/main/res/drawable/ic_stop.xml
Normal file
10
app/src/main/res/drawable/ic_stop.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,6h12v12H6z" />
|
||||
</vector>
|
@ -168,6 +168,7 @@
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="player_autoplay">Autoplay</string>
|
||||
<string name="instance_frontend_url">URL to instance frontend</string>
|
||||
<string name="quality">Quality</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user