mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
address review: signal transient dismissal with an event flow, not an int
An Int counter was already rejected for the now-playing launch signal; use a broadcast Flow on the shared ContextMenuHost and collect it, so the dismissal is an event rather than an ever-increasing number.
This commit is contained in:
@@ -402,13 +402,13 @@ private fun MainNavigation() {
|
||||
onOpenLicenses = { navController.navigate(Routes.LICENSES) },
|
||||
onEditServer = { id -> navController.navigate(Routes.editServer(id)) },
|
||||
onBack = { navController.popBackStack() },
|
||||
dismissRequests = contextMenuHost.dismissRequests,
|
||||
dismissals = contextMenuHost.dismissals,
|
||||
)
|
||||
}
|
||||
composable(Routes.DOWNLOADS) {
|
||||
DownloadsRoute(
|
||||
onBack = { navController.popBackStack() },
|
||||
dismissRequests = contextMenuHost.dismissRequests,
|
||||
dismissals = contextMenuHost.dismissals,
|
||||
)
|
||||
}
|
||||
composable(Routes.SEARCH) {
|
||||
@@ -607,7 +607,7 @@ private fun MainNavigation() {
|
||||
AddSourceRoute(
|
||||
onSaved = { navController.popBackStack() },
|
||||
onBack = { navController.popBackStack() },
|
||||
dismissRequests = contextMenuHost.dismissRequests,
|
||||
dismissals = contextMenuHost.dismissals,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
@@ -618,7 +618,7 @@ private fun MainNavigation() {
|
||||
onSaved = { navController.popBackStack() },
|
||||
onBack = { navController.popBackStack() },
|
||||
sourceId = entry.arguments?.getLong("sourceId"),
|
||||
dismissRequests = contextMenuHost.dismissRequests,
|
||||
dismissals = contextMenuHost.dismissals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -57,6 +56,9 @@ import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.SongDownload
|
||||
import com.subtracks.data.source.StarType
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import com.subtracks.data.model.Album as AlbumModel
|
||||
import com.subtracks.data.model.Artist as ArtistModel
|
||||
@@ -149,10 +151,11 @@ class ContextMenuHost {
|
||||
var actions by mutableStateOf(ItemActions())
|
||||
private set
|
||||
|
||||
// Bumped when an external event (opening now playing) should close menu-like UI the host does
|
||||
private val transientDismissals = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
|
||||
// Fires when an external event (opening now playing) should close menu-like UI the host does
|
||||
// not own, such as the library's list options sheet.
|
||||
var dismissRequests by mutableIntStateOf(0)
|
||||
private set
|
||||
val dismissals: Flow<Unit> = transientDismissals.asSharedFlow()
|
||||
|
||||
fun show(
|
||||
target: MenuTarget,
|
||||
@@ -169,17 +172,17 @@ class ContextMenuHost {
|
||||
|
||||
fun dismissTransients() {
|
||||
dismiss()
|
||||
dismissRequests++
|
||||
transientDismissals.tryEmit(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DismissOnRequest(
|
||||
dismissRequests: Int,
|
||||
dismissals: Flow<Unit>,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(dismissRequests) {
|
||||
if (dismissRequests > 0) onDismiss()
|
||||
LaunchedEffect(dismissals) {
|
||||
dismissals.collect { onDismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ import com.subtracks.data.model.DownloadedSong
|
||||
import com.subtracks.ui.components.DeleteDownloadsDialog
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.EmptyState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
sealed interface DownloadDeleteTarget {
|
||||
@@ -71,12 +73,12 @@ sealed interface DownloadDeleteTarget {
|
||||
@Composable
|
||||
fun DownloadsRoute(
|
||||
onBack: () -> Unit,
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
viewModel: DownloadsViewModel = koinViewModel(),
|
||||
) {
|
||||
val tree by viewModel.tree.collectAsStateWithLifecycle()
|
||||
var pending by remember { mutableStateOf<DownloadDeleteTarget?>(null) }
|
||||
DismissOnRequest(dismissRequests) { pending = null }
|
||||
DismissOnRequest(dismissals) { pending = null }
|
||||
DownloadsScreen(
|
||||
tree = tree,
|
||||
loadEncoding = viewModel::encoding,
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
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
|
||||
@@ -74,7 +75,7 @@ fun AlbumDetailRoute(
|
||||
CloseWhenDownloadsGone(downloadStatus.downloaded, offline, onBack)
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<Long?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissRequests ?: 0) { pendingDelete = null }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val playback by playbackController.state.collectAsStateWithLifecycle()
|
||||
val context = playback.context
|
||||
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
|
||||
|
||||
@@ -105,6 +105,7 @@ import com.subtracks.ui.theme.baseArtworkColors
|
||||
import com.subtracks.ui.theme.heroBarColor
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import com.subtracks.ui.theme.rememberOverlaidNameBusy
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
@@ -160,7 +161,7 @@ fun ArtistDetailRoute(
|
||||
val albumDownloads by viewModel.albumDownloads.collectAsStateWithLifecycle()
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<PendingDownloadDelete?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissRequests ?: 0) { pendingDelete = null }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
|
||||
val actions =
|
||||
ItemActions(
|
||||
|
||||
@@ -134,6 +134,7 @@ import com.subtracks.ui.theme.librarySurfaceColor
|
||||
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
|
||||
@@ -270,7 +271,7 @@ fun LibraryRoute(
|
||||
val downloads = downloadsState?.value ?: emptyMap()
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<PendingDownloadDelete?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissRequests ?: 0) { pendingDelete = null }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val itemActions =
|
||||
ItemActions(
|
||||
playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) },
|
||||
@@ -341,7 +342,7 @@ fun LibraryRoute(
|
||||
albumDownloads = downloads.takeIf { listTab == LibraryListTab.Albums }.orEmpty(),
|
||||
artistDownloads = downloads.takeIf { listTab == LibraryListTab.Artists }.orEmpty(),
|
||||
playlistDownloads = downloads.takeIf { listTab == LibraryListTab.Playlists }.orEmpty(),
|
||||
dismissRequests = contextMenuHost?.dismissRequests ?: 0,
|
||||
dismissals = contextMenuHost?.dismissals ?: emptyFlow(),
|
||||
homeContent = { topInset, bottomInset ->
|
||||
HomeRoute(
|
||||
onAlbumClick = onAlbumClick,
|
||||
@@ -406,7 +407,7 @@ fun LibraryScreen(
|
||||
albumDownloads: Map<String, ListDownloadStatus> = emptyMap(),
|
||||
artistDownloads: Map<String, ListDownloadStatus> = emptyMap(),
|
||||
playlistDownloads: Map<String, ListDownloadStatus> = emptyMap(),
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
homeContent: @Composable (topInset: Dp, bottomInset: Dp) -> Unit = { _, _ -> },
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -419,7 +420,7 @@ fun LibraryScreen(
|
||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||
val searchFocus = remember { FocusRequester() }
|
||||
|
||||
DismissOnRequest(dismissRequests) { showOptions = false }
|
||||
DismissOnRequest(dismissals) { showOptions = false }
|
||||
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.settledPage }.collect { page ->
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.subtracks.ui.components.ItemActions
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
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
|
||||
@@ -61,7 +62,7 @@ fun PlaylistDetailRoute(
|
||||
CloseWhenDownloadsGone(downloadStatus.downloaded, offline, onBack)
|
||||
val scope = rememberCoroutineScope()
|
||||
var pendingDelete by remember { mutableStateOf<Long?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissRequests ?: 0) { pendingDelete = null }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val playback by playbackController.state.collectAsStateWithLifecycle()
|
||||
val context = playback.context
|
||||
val requestDownloadAction: (BulkDownloadAction) -> Unit = { action ->
|
||||
|
||||
@@ -48,6 +48,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.subtracks.R
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@@ -56,7 +58,7 @@ fun AddSourceRoute(
|
||||
onSaved: () -> Unit,
|
||||
onBack: (() -> Unit)?,
|
||||
sourceId: Long? = null,
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
viewModel: AddSourceViewModel = koinViewModel(key = sourceId?.toString() ?: "new") { parametersOf(sourceId) },
|
||||
) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
@@ -71,7 +73,7 @@ fun AddSourceRoute(
|
||||
onSave = { viewModel.save(onSaved) },
|
||||
onDelete = { viewModel.delete(onSaved) },
|
||||
onBack = onBack,
|
||||
dismissRequests = dismissRequests,
|
||||
dismissals = dismissals,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,13 +90,13 @@ fun AddSourceScreen(
|
||||
onSave: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onBack: (() -> Unit)?,
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val nameFocus = remember { FocusRequester() }
|
||||
val addressFocus = remember { FocusRequester() }
|
||||
var confirmingDelete by remember { mutableStateOf(false) }
|
||||
DismissOnRequest(dismissRequests) { confirmingDelete = false }
|
||||
DismissOnRequest(dismissals) { confirmingDelete = false }
|
||||
LaunchedEffect(state.nameError, state.addressError) {
|
||||
when {
|
||||
state.nameError -> nameFocus.requestFocus()
|
||||
|
||||
@@ -58,6 +58,8 @@ import com.subtracks.data.model.Source
|
||||
import com.subtracks.data.prefs.StreamQuality
|
||||
import com.subtracks.ui.components.DismissOnRequest
|
||||
import com.subtracks.ui.components.rememberViewportFill
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
private val bitrateOptions = listOf(0, 24, 32, 64, 96, 128, 192, 256, 320)
|
||||
@@ -72,7 +74,7 @@ fun SettingsRoute(
|
||||
onOpenLicenses: () -> Unit,
|
||||
onEditServer: (Long) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
viewModel: SettingsViewModel = koinViewModel(),
|
||||
) {
|
||||
val sources by viewModel.sources.collectAsStateWithLifecycle()
|
||||
@@ -107,7 +109,7 @@ fun SettingsRoute(
|
||||
onOpenDownloads = onOpenDownloads,
|
||||
onOpenLicenses = onOpenLicenses,
|
||||
onBack = onBack,
|
||||
dismissRequests = dismissRequests,
|
||||
dismissals = dismissals,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -136,11 +138,11 @@ fun SettingsScreen(
|
||||
onOpenDownloads: () -> Unit,
|
||||
onOpenLicenses: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
dismissRequests: Int = 0,
|
||||
dismissals: Flow<Unit> = emptyFlow(),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var dialog by remember { mutableStateOf<SettingsDialog?>(null) }
|
||||
DismissOnRequest(dismissRequests) { dialog = null }
|
||||
DismissOnRequest(dismissals) { dialog = null }
|
||||
val listState = rememberLazyListState()
|
||||
val fill = rememberViewportFill(listState)
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class LibraryOptionsTest {
|
||||
onOpenSettings = {},
|
||||
sortOptions = sortOptionsFor(LibraryTab.Albums),
|
||||
starredSupported = true,
|
||||
dismissRequests = host.dismissRequests,
|
||||
dismissals = host.dismissals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user