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:
@@ -28,12 +28,9 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
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.ServerImage
|
||||||
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
|
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
|
||||||
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -70,16 +66,9 @@ import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
|||||||
fun AlbumDetailScreen(
|
fun AlbumDetailScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: AlbumDetailViewModel = hiltViewModel(),
|
viewModel: AlbumDetailViewModel = hiltViewModel(),
|
||||||
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
|
|
||||||
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
trackActionsViewModel.transientMessages.collect { msg ->
|
|
||||||
snackbarHostState.showSnackbar(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -92,7 +81,6 @@ fun AlbumDetailScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
|
||||||
) { inner ->
|
) { inner ->
|
||||||
PullToRefreshScaffold(
|
PullToRefreshScaffold(
|
||||||
onRefresh = { viewModel.refresh().join() },
|
onRefresh = { viewModel.refresh().join() },
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.PullToRefreshScaffold
|
||||||
import com.fabledsword.minstrel.shared.widgets.SkeletonAlbumTile
|
import com.fabledsword.minstrel.shared.widgets.SkeletonAlbumTile
|
||||||
import com.fabledsword.minstrel.shared.widgets.SkeletonArtistTile
|
import com.fabledsword.minstrel.shared.widgets.SkeletonArtistTile
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Library tab. Five-tab TabBar (Artists / Albums / History / Liked /
|
* Library tab. Five-tab TabBar (Artists / Albums / History / Liked /
|
||||||
@@ -72,16 +68,9 @@ import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewMode
|
|||||||
fun LibraryScreen(
|
fun LibraryScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: LibraryViewModel = hiltViewModel(),
|
viewModel: LibraryViewModel = hiltViewModel(),
|
||||||
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
|
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableIntStateOf(0) }
|
var selectedTab by remember { mutableIntStateOf(0) }
|
||||||
val tabs = LIBRARY_TABS
|
val tabs = LIBRARY_TABS
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
trackActionsViewModel.transientMessages.collect { msg ->
|
|
||||||
snackbarHostState.showSnackbar(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -115,7 +104,6 @@ fun LibraryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
|
||||||
) { inner ->
|
) { inner ->
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
|
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
|
|||||||
-11
@@ -28,8 +28,6 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
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.SkeletonTrackRow
|
||||||
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
|
||||||
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -241,16 +238,9 @@ class PlaylistDetailViewModel @Inject constructor(
|
|||||||
fun PlaylistDetailScreen(
|
fun PlaylistDetailScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: PlaylistDetailViewModel = hiltViewModel(),
|
viewModel: PlaylistDetailViewModel = hiltViewModel(),
|
||||||
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
|
|
||||||
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
trackActionsViewModel.transientMessages.collect { msg ->
|
|
||||||
snackbarHostState.showSnackbar(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.deleted.collect { navController.popBackStack() }
|
viewModel.deleted.collect { navController.popBackStack() }
|
||||||
}
|
}
|
||||||
@@ -276,7 +266,6 @@ fun PlaylistDetailScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
|
||||||
) { inner ->
|
) { inner ->
|
||||||
PullToRefreshScaffold(
|
PullToRefreshScaffold(
|
||||||
onRefresh = { viewModel.refresh().join() },
|
onRefresh = { viewModel.refresh().join() },
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
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.MainAppBarActions
|
||||||
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(
|
fun SearchScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: SearchViewModel = hiltViewModel(),
|
viewModel: SearchViewModel = hiltViewModel(),
|
||||||
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
|
|
||||||
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val playerState by playerViewModel.uiState.collectAsStateWithLifecycle()
|
val playerState by playerViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val playingTrackId = playerState.currentTrack?.id
|
val playingTrackId = playerState.currentTrack?.id
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
trackActionsViewModel.transientMessages.collect { msg ->
|
|
||||||
snackbarHostState.showSnackbar(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -113,7 +103,6 @@ fun SearchScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
|
||||||
) { inner ->
|
) { inner ->
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
|
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
|
||||||
ResultsPane(
|
ResultsPane(
|
||||||
|
|||||||
Reference in New Issue
Block a user