Skip to content

Commit 669d3b7

Browse files
feat: support download start/pause/cancel all
1 parent 67600d5 commit 669d3b7

5 files changed

Lines changed: 106 additions & 10 deletions

File tree

android/src/main/kotlin/project/pipepipe/app/download/DownloadManager.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ class DownloadManager(private val context: Context) {
2929
private val TAG = "DownloadManager"
3030

3131
init {
32-
// Resume any active downloads on initialization
32+
// Pause all active downloads on initialization
3333
scope.launch {
3434
val activeDownloads = DatabaseOperations.getActiveDownloads()
35-
Log.d(TAG, "Found ${activeDownloads.size} active downloads to resume")
35+
Log.d(TAG, "Pausing ${activeDownloads.size} active downloads")
3636

37-
// Start up to maxConcurrent downloads
38-
activeDownloads.take(maxConcurrent).forEach { download ->
39-
startWorker(download.id)
37+
activeDownloads.forEach { download ->
38+
DatabaseOperations.updateDownloadStatus(download.id, DownloadStatus.PAUSED.name, null)
4039
}
4140
}
4241
}

android/src/main/kotlin/project/pipepipe/app/ui/screens/DownloadScreen.kt

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import androidx.compose.foundation.lazy.LazyColumn
77
import androidx.compose.foundation.lazy.items
88
import androidx.compose.material.icons.Icons
99
import androidx.compose.material.icons.filled.ArrowBack
10+
import androidx.compose.material.icons.filled.Close
11+
import androidx.compose.material.icons.filled.Pause
12+
import androidx.compose.material.icons.filled.PlayArrow
1013
import androidx.compose.material3.*
1114
import androidx.compose.runtime.*
1215
import androidx.compose.ui.Alignment
@@ -16,14 +19,14 @@ import androidx.compose.ui.unit.dp
1619
import androidx.compose.ui.unit.sp
1720
import androidx.core.content.FileProvider
1821
import androidx.lifecycle.viewmodel.compose.viewModel
19-
import androidx.navigation.NavController
2022
import dev.icerock.moko.resources.compose.stringResource
2123
import kotlinx.coroutines.Dispatchers
2224
import kotlinx.coroutines.GlobalScope
2325
import kotlinx.coroutines.delay
2426
import kotlinx.coroutines.launch
2527
import project.pipepipe.app.MR
2628
import project.pipepipe.app.SharedContext.navController
29+
import project.pipepipe.app.database.DatabaseOperations
2730
import project.pipepipe.app.download.DownloadManagerHolder
2831
import project.pipepipe.app.helper.ToastManager
2932
import project.pipepipe.app.ui.component.CustomTopBar
@@ -41,6 +44,8 @@ fun DownloadScreen(
4144
val uiState by viewModel.uiState.collectAsState()
4245
val context = LocalContext.current
4346

47+
var showCancelAllDialog by remember { mutableStateOf(false) }
48+
4449
// Auto-refresh downloads periodically
4550
LaunchedEffect(Unit) {
4651
while (true) {
@@ -65,7 +70,48 @@ fun DownloadScreen(
6570
IconButton(onClick = { navController.navigateUp() }) {
6671
Icon(
6772
imageVector = Icons.Default.ArrowBack,
68-
contentDescription = "Back"
73+
contentDescription = stringResource(MR.strings.back)
74+
)
75+
}
76+
},
77+
actions = {
78+
IconButton(
79+
onClick = {
80+
GlobalScope.launch(Dispatchers.IO) {
81+
viewModel.getDownloadsByStatus(listOf(DownloadStatus.PAUSED)).forEach { download ->
82+
DownloadManagerHolder.instance.resumeDownload(download.id)
83+
}
84+
viewModel.refreshDownloads()
85+
}
86+
}
87+
) {
88+
Icon(
89+
imageVector = Icons.Default.PlayArrow,
90+
contentDescription = stringResource(MR.strings.start_all)
91+
)
92+
}
93+
IconButton(
94+
onClick = {
95+
GlobalScope.launch(Dispatchers.IO) {
96+
viewModel.getActiveDownloads()
97+
.forEach { download ->
98+
DownloadManagerHolder.instance.pauseDownload(download.id)
99+
}
100+
viewModel.refreshDownloads()
101+
}
102+
}
103+
) {
104+
Icon(
105+
imageVector = Icons.Default.Pause,
106+
contentDescription = stringResource(MR.strings.pause_all)
107+
)
108+
}
109+
IconButton(
110+
onClick = { showCancelAllDialog = true }
111+
) {
112+
Icon(
113+
imageVector = Icons.Default.Close,
114+
contentDescription = stringResource(MR.strings.cancel_all)
69115
)
70116
}
71117
}
@@ -116,6 +162,7 @@ fun DownloadScreen(
116162
CircularProgressIndicator()
117163
}
118164
}
165+
119166
filteredDownloads.isEmpty() -> {
120167
Box(
121168
modifier = Modifier.fillMaxSize(),
@@ -145,6 +192,7 @@ fun DownloadScreen(
145192
}
146193
}
147194
}
195+
148196
else -> {
149197
LazyColumn(
150198
modifier = Modifier
@@ -226,5 +274,49 @@ fun DownloadScreen(
226274
}
227275
}
228276
}
277+
278+
if (showCancelAllDialog) {
279+
AlertDialog(
280+
onDismissRequest = { showCancelAllDialog = false },
281+
title = {
282+
Text(stringResource(MR.strings.cancel_all))
283+
},
284+
text = {
285+
Text(stringResource(MR.strings.cancel_all_confirmation))
286+
},
287+
confirmButton = {
288+
TextButton(
289+
onClick = {
290+
showCancelAllDialog = false
291+
GlobalScope.launch(Dispatchers.IO) {
292+
viewModel.getDownloadsByStatus(
293+
listOf(
294+
DownloadStatus.QUEUED,
295+
DownloadStatus.FETCHING_INFO,
296+
DownloadStatus.PREPROCESSING,
297+
DownloadStatus.DOWNLOADING,
298+
DownloadStatus.POSTPROCESSING,
299+
DownloadStatus.PAUSED,
300+
)
301+
)
302+
.forEach { download ->
303+
DownloadManagerHolder.instance.cancelDownload(download.id)
304+
}
305+
viewModel.refreshDownloads()
306+
}
307+
}
308+
) {
309+
Text(stringResource(MR.strings.confirm))
310+
}
311+
},
312+
dismissButton = {
313+
TextButton(
314+
onClick = { showCancelAllDialog = false }
315+
) {
316+
Text(stringResource(MR.strings.cancel))
317+
}
318+
}
319+
)
320+
}
229321
}
230322
}

library/src/commonMain/kotlin/project/pipepipe/app/viewmodel/DownloadViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ class DownloadViewModel : BaseViewModel<DownloadUiState>(DownloadUiState()) {
5050
/**
5151
* Get downloads filtered by status
5252
*/
53-
suspend fun getDownloadsByStatus(status: DownloadStatus): List<DownloadItemState> {
53+
suspend fun getDownloadsByStatus(statusList: List<DownloadStatus>): List<DownloadItemState> {
5454
return try {
5555
val downloads = DatabaseOperations.getAllDownloads()
5656
downloads
57-
.filter { it.status == status.name }
57+
.filter { DownloadStatus.valueOf(it.status) in statusList }
5858
.map { it.toDownloadItemState() }
5959
} catch (e: Exception) {
6060
emptyList()

library/src/commonMain/moko-resources/base/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,4 +688,9 @@
688688
<string name="playlist_remove_partially_watched">Remove partially watched</string>
689689
<string name="playlist_remove_fully_watched">Remove fully watched</string>
690690
<string name="playlist_menu_share_url_list">Share URL List</string>
691+
<!-- Download actions -->
692+
<string name="start_all">Start All</string>
693+
<string name="pause_all">Pause All</string>
694+
<string name="cancel_all">Cancel All</string>
695+
<string name="cancel_all_confirmation">Are you sure you want to cancel all active downloads?</string>
691696
</resources>

library/src/commonMain/sqldelight/project/pipepipe/database/AppDatabase.sq

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ selectDownloadsByStatus:
659659
SELECT * FROM downloads WHERE status = ? ORDER BY created_at DESC;
660660

661661
selectActiveDownloads:
662-
SELECT * FROM downloads WHERE status IN ('QUEUED', 'FETCHING_INFO', 'DOWNLOADING') ORDER BY created_at ASC;
662+
SELECT * FROM downloads WHERE status IN ('QUEUED', 'FETCHING_INFO', 'DOWNLOADING', 'PREPROCESSING', 'POSTPROCESSING') ORDER BY created_at ASC;
663663

664664
insertDownload:
665665
INSERT OR REPLACE INTO downloads(url, title, image_url, duration, download_type, quality, codec, format_id, status, created_at)

0 commit comments

Comments
 (0)