fix(android): remove duplicate TrackActions snackbar wiring (DRY 3c)

ShellScaffold already collects TrackActionsViewModel.transientMessages
into its own SnackbarHost. Per-route, the shell's hiltViewModel<
TrackActionsViewModel>() and the screen's hiltViewModel<TrackActions
ViewModel>() resolve to the same NavBackStackEntry-scoped instance,
so any kebab action fires the snackbar twice today (once in the
screen's own SnackbarHost, once in the shell's).

Strip the per-screen wiring from the four shell-wrapped screens that
had it (Library, Search, AlbumDetail, PlaylistDetail): VM param,
SnackbarHostState, LaunchedEffect, and the Scaffold's snackbarHost.
The shell now owns the snackbar exclusively.

NowPlayingScreen is intentionally left untouched -- it's outside the
shell (full-screen route), so its own snackbar wiring is the only
surface that shows the message.
This commit is contained in:
2026-05-30 22:38:48 -04:00
parent 02a6c45958
commit 453f8a387b
4 changed files with 0 additions and 46 deletions
@@ -28,12 +28,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -62,7 +59,6 @@ import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
import com.fabledsword.minstrel.shared.widgets.ServerImage
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
@OptIn(ExperimentalMaterial3Api::class)
@@ -70,16 +66,9 @@ import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
fun AlbumDetailScreen(
navController: NavHostController,
viewModel: AlbumDetailViewModel = hiltViewModel(),
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
trackActionsViewModel.transientMessages.collect { msg ->
snackbarHostState.showSnackbar(msg)
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
@@ -92,7 +81,6 @@ fun AlbumDetailScreen(
},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { inner ->
PullToRefreshScaffold(
onRefresh = { viewModel.refresh().join() },
@@ -18,12 +18,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
@@ -52,7 +49,6 @@ import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
import com.fabledsword.minstrel.shared.widgets.SkeletonAlbumTile
import com.fabledsword.minstrel.shared.widgets.SkeletonArtistTile
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
/**
* Library tab. Five-tab TabBar (Artists / Albums / History / Liked /
@@ -72,16 +68,9 @@ import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewMode
fun LibraryScreen(
navController: NavHostController,
viewModel: LibraryViewModel = hiltViewModel(),
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
) {
var selectedTab by remember { mutableIntStateOf(0) }
val tabs = LIBRARY_TABS
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
trackActionsViewModel.transientMessages.collect { msg ->
snackbarHostState.showSnackbar(msg)
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
@@ -115,7 +104,6 @@ fun LibraryScreen(
}
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { inner ->
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
when (selectedTab) {
@@ -28,8 +28,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@@ -78,7 +76,6 @@ import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
@@ -241,16 +238,9 @@ class PlaylistDetailViewModel @Inject constructor(
fun PlaylistDetailScreen(
navController: NavHostController,
viewModel: PlaylistDetailViewModel = hiltViewModel(),
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
) {
val state by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
trackActionsViewModel.transientMessages.collect { msg ->
snackbarHostState.showSnackbar(msg)
}
}
LaunchedEffect(Unit) {
viewModel.deleted.collect { navController.popBackStack() }
}
@@ -276,7 +266,6 @@ fun PlaylistDetailScreen(
},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { inner ->
PullToRefreshScaffold(
onRefresh = { viewModel.refresh().join() },
@@ -25,8 +25,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
@@ -62,27 +60,19 @@ import com.fabledsword.minstrel.shared.widgets.LoadingCentered
import com.fabledsword.minstrel.shared.widgets.MainAppBarActions
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScreen(
navController: NavHostController,
viewModel: SearchViewModel = hiltViewModel(),
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()
val playerState by playerViewModel.uiState.collectAsStateWithLifecycle()
val playingTrackId = playerState.currentTrack?.id
val focusRequester = remember { FocusRequester() }
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
LaunchedEffect(Unit) {
trackActionsViewModel.transientMessages.collect { msg ->
snackbarHostState.showSnackbar(msg)
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
@@ -113,7 +103,6 @@ fun SearchScreen(
},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { inner ->
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
ResultsPane(