mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
ui: share bulk download deletion confirmation
This commit is contained in:
@@ -6,10 +6,21 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.subtracks.R
|
||||
import com.subtracks.data.model.BulkDownloadAction
|
||||
import com.subtracks.data.model.DownloadList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class PendingDownloadDelete(
|
||||
val name: String,
|
||||
@@ -18,6 +29,70 @@ data class PendingDownloadDelete(
|
||||
val bytes: Long,
|
||||
)
|
||||
|
||||
class PendingDownloadDeleteController internal constructor(
|
||||
private val scope: CoroutineScope,
|
||||
private val downloadedBytes: State<suspend (DownloadList, String) -> Long>,
|
||||
) {
|
||||
var pending by mutableStateOf<PendingDownloadDelete?>(null)
|
||||
private set
|
||||
|
||||
fun onAction(
|
||||
target: MenuTarget,
|
||||
action: BulkDownloadAction,
|
||||
onAction: (DownloadList, String, BulkDownloadAction) -> Unit,
|
||||
) {
|
||||
val (list, refId) = target.bulkRef() ?: return
|
||||
onAction(target.title, list, refId, action, onAction)
|
||||
}
|
||||
|
||||
fun onAction(
|
||||
name: String,
|
||||
list: DownloadList,
|
||||
refId: String,
|
||||
action: BulkDownloadAction,
|
||||
onAction: (DownloadList, String, BulkDownloadAction) -> Unit,
|
||||
) {
|
||||
if (action == BulkDownloadAction.Delete) {
|
||||
scope.launch {
|
||||
pending = PendingDownloadDelete(name, list, refId, downloadedBytes.value(list, refId))
|
||||
}
|
||||
} else {
|
||||
onAction(list, refId, action)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
pending = null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberPendingDownloadDelete(
|
||||
dismissals: Flow<Unit>,
|
||||
downloadedBytes: suspend (DownloadList, String) -> Long,
|
||||
): PendingDownloadDeleteController {
|
||||
val scope = rememberCoroutineScope()
|
||||
val currentDownloadedBytes = rememberUpdatedState(downloadedBytes)
|
||||
val controller = remember(scope) { PendingDownloadDeleteController(scope, currentDownloadedBytes) }
|
||||
DismissOnRequest(dismissals, controller::dismiss)
|
||||
return controller
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PendingDownloadDeleteConfirmation(
|
||||
controller: PendingDownloadDeleteController,
|
||||
onConfirm: (PendingDownloadDelete) -> Unit,
|
||||
) {
|
||||
controller.pending?.let { pending ->
|
||||
DeleteDownloadsDialog(
|
||||
name = pending.name,
|
||||
bytes = pending.bytes,
|
||||
onConfirm = { onConfirm(pending) },
|
||||
onDismiss = controller::dismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeleteDownloadsDialog(
|
||||
name: String,
|
||||
|
||||
@@ -16,7 +16,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -30,6 +29,7 @@ import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.BulkDownloadAction
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.Disc
|
||||
import com.subtracks.data.model.DownloadList
|
||||
import com.subtracks.data.model.ListDownloadStatus
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.Song
|
||||
@@ -39,16 +39,16 @@ import com.subtracks.playback.PlaybackController
|
||||
import com.subtracks.ui.components.ArtistChooserSheet
|
||||
import com.subtracks.ui.components.CloseWhenDownloadsGone
|
||||
import com.subtracks.ui.components.ContextMenuHost
|
||||
import com.subtracks.ui.components.DeleteDownloadsDialog
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.HeroDetailScaffold
|
||||
import com.subtracks.ui.components.HeroHeader
|
||||
import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
import com.subtracks.ui.components.PendingDownloadDeleteConfirmation
|
||||
import com.subtracks.ui.components.rememberPendingDownloadDelete
|
||||
import com.subtracks.ui.theme.ArtworkColors
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -74,21 +74,16 @@ fun AlbumDetailRoute(
|
||||
val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle()
|
||||
val offline by viewModel.offline.collectAsStateWithLifecycle()
|
||||
CloseWhenDownloadsGone(downloadStatus.downloaded, offline, onBack)
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<Long?>(null) }
|
||||
val dismissals = contextMenuHost?.dismissals ?: emptyFlow()
|
||||
val pendingDelete = rememberPendingDownloadDelete(dismissals) { _, _ -> viewModel.downloadedBytes() }
|
||||
var showArtistChooser by remember { mutableStateOf(false) }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) {
|
||||
pendingDelete = null
|
||||
showArtistChooser = false
|
||||
}
|
||||
DismissOnRequest(dismissals) { showArtistChooser = false }
|
||||
val playback by playbackController.state.collectAsStateWithLifecycle()
|
||||
val context = playback.context
|
||||
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
|
||||
val requestDownloadAction: (BulkDownloadAction) -> Unit = { action ->
|
||||
if (action == BulkDownloadAction.Delete) {
|
||||
scope.launch { pendingDelete = viewModel.downloadedBytes() }
|
||||
} else {
|
||||
viewModel.onDownloadAction(action)
|
||||
pendingDelete.onAction(album?.name.orEmpty(), DownloadList.Album, albumId, action) { _, _, requested ->
|
||||
viewModel.onDownloadAction(requested)
|
||||
}
|
||||
}
|
||||
val actions =
|
||||
@@ -141,13 +136,8 @@ fun AlbumDetailRoute(
|
||||
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
|
||||
)
|
||||
|
||||
pendingDelete?.let { bytes ->
|
||||
DeleteDownloadsDialog(
|
||||
name = album?.name.orEmpty(),
|
||||
bytes = bytes,
|
||||
onConfirm = { viewModel.onDownloadAction(BulkDownloadAction.Delete) },
|
||||
onDismiss = { pendingDelete = null },
|
||||
)
|
||||
PendingDownloadDeleteConfirmation(pendingDelete) {
|
||||
viewModel.onDownloadAction(BulkDownloadAction.Delete)
|
||||
}
|
||||
|
||||
if (showArtistChooser) {
|
||||
|
||||
@@ -59,7 +59,6 @@ import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
@@ -111,13 +110,12 @@ import com.subtracks.ui.components.ArtistHeroArt
|
||||
import com.subtracks.ui.components.CloseWhenDownloadsGone
|
||||
import com.subtracks.ui.components.ContextMenuHost
|
||||
import com.subtracks.ui.components.CoverArt
|
||||
import com.subtracks.ui.components.DeleteDownloadsDialog
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.ListDownloadIndicator
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
import com.subtracks.ui.components.PendingDownloadDelete
|
||||
import com.subtracks.ui.components.bulkRef
|
||||
import com.subtracks.ui.components.PendingDownloadDeleteConfirmation
|
||||
import com.subtracks.ui.components.rememberPendingDownloadDelete
|
||||
import com.subtracks.ui.components.rememberViewportFill
|
||||
import com.subtracks.ui.components.statusBarScrim
|
||||
import com.subtracks.ui.theme.ArtworkColors
|
||||
@@ -130,7 +128,6 @@ import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import com.subtracks.ui.theme.rememberOverlaidNameBusy
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -217,26 +214,13 @@ fun ArtistDetailRoute(
|
||||
val offline by viewModel.offline.collectAsStateWithLifecycle()
|
||||
CloseWhenDownloadsGone(downloadStatus.downloaded, offline, onBack)
|
||||
val albumDownloads by viewModel.albumDownloads.collectAsStateWithLifecycle()
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<PendingDownloadDelete?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val pendingDelete = rememberPendingDownloadDelete(contextMenuHost?.dismissals ?: emptyFlow(), viewModel::downloadedBytes)
|
||||
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
|
||||
val actions =
|
||||
ItemActions(
|
||||
playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) },
|
||||
addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) },
|
||||
bulkDownload = { target, action ->
|
||||
target.bulkRef()?.let { (list, refId) ->
|
||||
if (action == BulkDownloadAction.Delete) {
|
||||
scope.launch {
|
||||
pendingDelete =
|
||||
PendingDownloadDelete(target.title, list, refId, viewModel.downloadedBytes(list, refId))
|
||||
}
|
||||
} else {
|
||||
viewModel.onDownloadAction(list, refId, action)
|
||||
}
|
||||
}
|
||||
},
|
||||
bulkDownload = { target, action -> pendingDelete.onAction(target, action, viewModel::onDownloadAction) },
|
||||
setStar = setStar,
|
||||
viewAlbum = onViewAlbum,
|
||||
)
|
||||
@@ -267,13 +251,8 @@ fun ArtistDetailRoute(
|
||||
onShuffle = viewModel::shuffle,
|
||||
)
|
||||
|
||||
pendingDelete?.let { pending ->
|
||||
DeleteDownloadsDialog(
|
||||
name = pending.name,
|
||||
bytes = pending.bytes,
|
||||
onConfirm = { viewModel.onDownloadAction(pending.list, pending.refId, BulkDownloadAction.Delete) },
|
||||
onDismiss = { pendingDelete = null },
|
||||
)
|
||||
PendingDownloadDeleteConfirmation(pendingDelete) { pending ->
|
||||
viewModel.onDownloadAction(pending.list, pending.refId, BulkDownloadAction.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
@@ -126,13 +125,12 @@ import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import com.subtracks.ui.components.ArtistArt
|
||||
import com.subtracks.ui.components.ContextMenuHost
|
||||
import com.subtracks.ui.components.DeleteDownloadsDialog
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
import com.subtracks.ui.components.PendingDownloadDelete
|
||||
import com.subtracks.ui.components.PendingDownloadDeleteConfirmation
|
||||
import com.subtracks.ui.components.SearchField
|
||||
import com.subtracks.ui.components.bulkRef
|
||||
import com.subtracks.ui.components.rememberPendingDownloadDelete
|
||||
import com.subtracks.ui.components.statusBarScrim
|
||||
import com.subtracks.ui.home.HomeRoute
|
||||
import com.subtracks.ui.home.HomeSection
|
||||
@@ -143,7 +141,6 @@ import com.subtracks.ui.theme.playerSurfaceColor
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import kotlin.math.abs
|
||||
@@ -278,26 +275,13 @@ fun LibraryRoute(
|
||||
val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true), fallbackName = playback.item?.title)
|
||||
val downloadsState = listTab?.let { viewModel.downloads(it).collectAsStateWithLifecycle() }
|
||||
val downloads = downloadsState?.value ?: emptyMap()
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusManager = LocalFocusManager.current
|
||||
var pendingDelete by remember { mutableStateOf<PendingDownloadDelete?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val pendingDelete = rememberPendingDownloadDelete(contextMenuHost?.dismissals ?: emptyFlow(), viewModel::downloadedBytes)
|
||||
val itemActions =
|
||||
ItemActions(
|
||||
playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) },
|
||||
addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) },
|
||||
bulkDownload = { target, action ->
|
||||
target.bulkRef()?.let { (list, refId) ->
|
||||
if (action == BulkDownloadAction.Delete) {
|
||||
scope.launch {
|
||||
pendingDelete =
|
||||
PendingDownloadDelete(target.title, list, refId, viewModel.downloadedBytes(list, refId))
|
||||
}
|
||||
} else {
|
||||
viewModel.onDownloadAction(list, refId, action)
|
||||
}
|
||||
}
|
||||
},
|
||||
bulkDownload = { target, action -> pendingDelete.onAction(target, action, viewModel::onDownloadAction) },
|
||||
setStar = setStar,
|
||||
viewAlbum = onViewAlbum,
|
||||
viewArtist = onViewArtist,
|
||||
@@ -377,13 +361,8 @@ fun LibraryRoute(
|
||||
},
|
||||
)
|
||||
|
||||
pendingDelete?.let { pending ->
|
||||
DeleteDownloadsDialog(
|
||||
name = pending.name,
|
||||
bytes = pending.bytes,
|
||||
onConfirm = { viewModel.onDownloadAction(pending.list, pending.refId, BulkDownloadAction.Delete) },
|
||||
onDismiss = { pendingDelete = null },
|
||||
)
|
||||
PendingDownloadDeleteConfirmation(pendingDelete) { pending ->
|
||||
viewModel.onDownloadAction(pending.list, pending.refId, BulkDownloadAction.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -18,6 +17,7 @@ import androidx.paging.compose.itemKey
|
||||
import com.subtracks.R
|
||||
import com.subtracks.data.model.BulkDownloadAction
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.DownloadList
|
||||
import com.subtracks.data.model.ListDownloadStatus
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.PlaylistSongItem
|
||||
@@ -27,16 +27,16 @@ import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import com.subtracks.ui.components.CloseWhenDownloadsGone
|
||||
import com.subtracks.ui.components.ContextMenuHost
|
||||
import com.subtracks.ui.components.DeleteDownloadsDialog
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.HeroDetailScaffold
|
||||
import com.subtracks.ui.components.HeroHeader
|
||||
import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
import com.subtracks.ui.components.PendingDownloadDeleteConfirmation
|
||||
import com.subtracks.ui.components.rememberPendingDownloadDelete
|
||||
import com.subtracks.ui.theme.ArtworkColors
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -60,16 +60,12 @@ fun PlaylistDetailRoute(
|
||||
val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle()
|
||||
val offline by viewModel.offline.collectAsStateWithLifecycle()
|
||||
CloseWhenDownloadsGone(downloadStatus.downloaded, offline, onBack)
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<Long?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val pendingDelete = rememberPendingDownloadDelete(contextMenuHost?.dismissals ?: emptyFlow()) { _, _ -> viewModel.downloadedBytes() }
|
||||
val playback by playbackController.state.collectAsStateWithLifecycle()
|
||||
val context = playback.context
|
||||
val requestDownloadAction: (BulkDownloadAction) -> Unit = { action ->
|
||||
if (action == BulkDownloadAction.Delete) {
|
||||
scope.launch { pendingDelete = viewModel.downloadedBytes() }
|
||||
} else {
|
||||
viewModel.onDownloadAction(action)
|
||||
pendingDelete.onAction(playlist?.name.orEmpty(), DownloadList.Playlist, playlistId, action) { _, _, requested ->
|
||||
viewModel.onDownloadAction(requested)
|
||||
}
|
||||
}
|
||||
val actions =
|
||||
@@ -104,13 +100,8 @@ fun PlaylistDetailRoute(
|
||||
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Playlist && context.refId == playlistId },
|
||||
)
|
||||
|
||||
pendingDelete?.let { bytes ->
|
||||
DeleteDownloadsDialog(
|
||||
name = playlist?.name.orEmpty(),
|
||||
bytes = bytes,
|
||||
onConfirm = { viewModel.onDownloadAction(BulkDownloadAction.Delete) },
|
||||
onDismiss = { pendingDelete = null },
|
||||
)
|
||||
PendingDownloadDeleteConfirmation(pendingDelete) {
|
||||
viewModel.onDownloadAction(BulkDownloadAction.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,13 @@ import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.model.BulkDownloadAction
|
||||
import com.subtracks.data.model.DownloadList
|
||||
import com.subtracks.ui.theme.SubtracksTheme
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -55,6 +60,115 @@ class DeleteDownloadsDialogTest {
|
||||
composeRule.onNodeWithText("512 B", substring = true).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedWorkflowLooksUpBytesAndConfirmsTheRequestedTargetOnce() {
|
||||
val actions = mutableListOf<Triple<DownloadList, String, BulkDownloadAction>>()
|
||||
lateinit var controller: PendingDownloadDeleteController
|
||||
renderWorkflow(
|
||||
downloadedBytes = { list, refId ->
|
||||
assertEquals(DownloadList.Album, list)
|
||||
assertEquals("al1", refId)
|
||||
1_500_000
|
||||
},
|
||||
onAction = { list, refId, action -> actions += Triple(list, refId, action) },
|
||||
onController = { controller = it },
|
||||
)
|
||||
|
||||
composeRule.runOnIdle {
|
||||
controller.onAction("Kid A", DownloadList.Album, "al1", BulkDownloadAction.Delete) { _, _, _ -> }
|
||||
}
|
||||
composeRule.waitUntil { controller.pending != null }
|
||||
composeRule.onNodeWithText("Kid A", substring = true).assertIsDisplayed()
|
||||
composeRule.onNodeWithText("MB", substring = true).assertIsDisplayed()
|
||||
composeRule.onNodeWithText("Delete").performClick()
|
||||
|
||||
composeRule.runOnIdle {
|
||||
assertEquals(listOf(Triple(DownloadList.Album, "al1", BulkDownloadAction.Delete)), actions)
|
||||
assertNull(controller.pending)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedWorkflowCancelDoesNotConfirm() {
|
||||
val actions = mutableListOf<BulkDownloadAction>()
|
||||
lateinit var controller: PendingDownloadDeleteController
|
||||
renderWorkflow(
|
||||
onAction = { _, _, action -> actions += action },
|
||||
onController = { controller = it },
|
||||
)
|
||||
|
||||
composeRule.runOnIdle {
|
||||
controller.onAction("Kid A", DownloadList.Album, "al1", BulkDownloadAction.Delete) { _, _, _ -> }
|
||||
}
|
||||
composeRule.waitUntil { controller.pending != null }
|
||||
composeRule.onNodeWithText("Cancel").performClick()
|
||||
|
||||
composeRule.runOnIdle {
|
||||
assertEquals(emptyList<BulkDownloadAction>(), actions)
|
||||
assertNull(controller.pending)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedWorkflowDismissesWithTheContextMenu() {
|
||||
val dismissals = MutableSharedFlow<Unit>(replay = 1)
|
||||
lateinit var controller: PendingDownloadDeleteController
|
||||
renderWorkflow(dismissals = dismissals, onController = { controller = it })
|
||||
|
||||
composeRule.runOnIdle {
|
||||
controller.onAction("Kid A", DownloadList.Album, "al1", BulkDownloadAction.Delete) { _, _, _ -> }
|
||||
}
|
||||
composeRule.waitUntil { controller.pending != null }
|
||||
composeRule.runOnIdle { dismissals.tryEmit(Unit) }
|
||||
|
||||
composeRule.waitUntil { controller.pending == null }
|
||||
composeRule.onNodeWithText("Delete").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedWorkflowDispatchesNonDeleteActionsImmediately() {
|
||||
val actions = mutableListOf<Triple<DownloadList, String, BulkDownloadAction>>()
|
||||
var lookups = 0
|
||||
lateinit var controller: PendingDownloadDeleteController
|
||||
renderWorkflow(
|
||||
downloadedBytes = { _, _ ->
|
||||
lookups++
|
||||
0
|
||||
},
|
||||
onAction = { list, refId, action -> actions += Triple(list, refId, action) },
|
||||
onController = { controller = it },
|
||||
)
|
||||
|
||||
composeRule.runOnIdle {
|
||||
controller.onAction("Kid A", DownloadList.Album, "al1", BulkDownloadAction.Download) { list, refId, action ->
|
||||
actions += Triple(list, refId, action)
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.runOnIdle {
|
||||
assertEquals(listOf(Triple(DownloadList.Album, "al1", BulkDownloadAction.Download)), actions)
|
||||
assertEquals(0, lookups)
|
||||
assertNull(controller.pending)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderWorkflow(
|
||||
dismissals: kotlinx.coroutines.flow.Flow<Unit> = emptyFlow(),
|
||||
downloadedBytes: suspend (DownloadList, String) -> Long = { _, _ -> 0 },
|
||||
onAction: (DownloadList, String, BulkDownloadAction) -> Unit = { _, _, _ -> },
|
||||
onController: (PendingDownloadDeleteController) -> Unit,
|
||||
) {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
val controller = rememberPendingDownloadDelete(dismissals, downloadedBytes)
|
||||
onController(controller)
|
||||
PendingDownloadDeleteConfirmation(controller) { pending ->
|
||||
onAction(pending.list, pending.refId, BulkDownloadAction.Delete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun render(
|
||||
bytes: Long = 0,
|
||||
onConfirm: () -> Unit = {},
|
||||
|
||||
Reference in New Issue
Block a user