diff --git a/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt b/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt index adb39b43..d1497d1c 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt @@ -30,7 +30,7 @@ import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.player.PlayerController import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt index 2edc2080..0c394143 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt @@ -56,8 +56,8 @@ import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.LikeButton -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel +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) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt index d3c4c2fa..9c7956e8 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt @@ -44,7 +44,7 @@ import com.fabledsword.minstrel.nav.Library import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.MainAppBarActions -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel /** * Library tab. Five-tab TabBar (Artists / Albums / History / Liked / diff --git a/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt b/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt index 7e03adbd..918d5f94 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt @@ -41,7 +41,7 @@ import com.fabledsword.minstrel.player.PlayerController import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.HorizontalScrollRow import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow diff --git a/android/app/src/main/java/com/fabledsword/minstrel/models/wire/RadioWire.kt b/android/app/src/main/java/com/fabledsword/minstrel/models/wire/RadioResponseWire.kt similarity index 100% rename from android/app/src/main/java/com/fabledsword/minstrel/models/wire/RadioWire.kt rename to android/app/src/main/java/com/fabledsword/minstrel/models/wire/RadioResponseWire.kt diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt index 35fb5909..0801e899 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt @@ -38,6 +38,13 @@ import kotlin.coroutines.resumeWithException * the StateFlow projection means each ViewModel doesn't have to attach * its own Player.Listener. */ +// PlayerController is the UI-side facade over the full Media3 surface +// (transport + queue + radio + lifecycle). All the public methods are +// legitimately part of one cohesive controller — splitting into +// "TransportController", "QueueController", etc. would scatter related +// state for no gain. Suppress the per-class function-count cap rather +// than fragment. +@Suppress("TooManyFunctions") @Singleton class PlayerController @Inject constructor( @ApplicationContext private val context: Context, diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt index 05bbd02e..34dc22eb 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt @@ -49,8 +49,8 @@ import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.nav.Queue import com.fabledsword.minstrel.shared.widgets.EmptyState -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel +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.LocalActionColors @@ -121,28 +121,36 @@ fun NowPlayingScreen( onNext = viewModel::skipToNext, ) Spacer(Modifier.height(16.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - ViewQueueButton(onClick = { navController.navigate(Queue) }) - TrackActionsButton( - track = track, - hideQueueActions = true, - onNavigateToAlbum = { id -> - navController.popBackStack() - navController.navigate(AlbumDetail(id)) - }, - onNavigateToArtist = { id -> - navController.popBackStack() - navController.navigate(ArtistDetail(id)) - }, - ) - } + BottomActionsRow(navController = navController, track = track) } } } +@Composable +private fun BottomActionsRow( + navController: NavHostController, + track: com.fabledsword.minstrel.models.TrackRef, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + ViewQueueButton(onClick = { navController.navigate(Queue) }) + TrackActionsButton( + track = track, + hideQueueActions = true, + onNavigateToAlbum = { id -> + navController.popBackStack() + navController.navigate(AlbumDetail(id)) + }, + onNavigateToArtist = { id -> + navController.popBackStack() + navController.navigate(ArtistDetail(id)) + }, + ) + } +} + @Composable private fun ViewQueueButton(onClick: () -> Unit) { IconButton(onClick = onClick) { diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt index 2f2eed42..675cc607 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt @@ -67,8 +67,8 @@ import com.fabledsword.minstrel.playlists.data.PlaylistDetailRef import com.fabledsword.minstrel.playlists.data.PlaylistsRepository import com.fabledsword.minstrel.shared.widgets.LikeButton import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel +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.flow.MutableStateFlow diff --git a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt index 861907f7..05619f2b 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt @@ -1,3 +1,9 @@ +@file:Suppress("TooManyFunctions") +// SearchScreen legitimately owns the screen-shell composable + a handful +// of small section/row helpers (ResultsPane, ResultsList, three section +// builders, two row variants, hint, loader). Splitting into a widgets/ +// subdirectory for a single screen would be over-engineering. + package com.fabledsword.minstrel.search.ui import androidx.compose.foundation.clickable @@ -51,8 +57,8 @@ import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton -import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistSheet.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistSheet.kt similarity index 98% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistSheet.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistSheet.kt index f5343e2a..6ad1a419 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistSheet.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistSheet.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt similarity index 97% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistViewModel.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt index 643684f0..fb5f6865 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/AddToPlaylistViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/HideTrackSheet.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/HideTrackSheet.kt similarity index 86% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/HideTrackSheet.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/HideTrackSheet.kt index 9fece751..8ffd2cd0 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/HideTrackSheet.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/HideTrackSheet.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -93,17 +93,23 @@ fun HideTrackSheet( maxLines = 2, ) Spacer(Modifier.height(16.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - TextButton(onClick = onDismiss) { Text("Cancel") } - Spacer(Modifier.padding(horizontal = 4.dp)) - Button(onClick = { onSubmit(reason, notes.trim()) }) { - Text("Hide") - } - } + HideSheetButtons( + onCancel = onDismiss, + onSubmit = { onSubmit(reason, notes.trim()) }, + ) Spacer(Modifier.height(8.dp)) } } } + +@Composable +private fun HideSheetButtons(onCancel: () -> Unit, onSubmit: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton(onClick = onCancel) { Text("Cancel") } + Spacer(Modifier.padding(horizontal = 4.dp)) + Button(onClick = onSubmit) { Text("Hide") } + } +} diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsButton.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsButton.kt similarity index 96% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsButton.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsButton.kt index b3227e3e..45266fb3 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsButton.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsButton.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.compose.material3.Icon import androidx.compose.material3.IconButton diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsSheet.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsSheet.kt similarity index 58% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsSheet.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsSheet.kt index 25b197fe..139e8b55 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsSheet.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsSheet.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -63,7 +63,6 @@ fun TrackActionsSheet( val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val isLiked by viewModel.isLikedFlow(track.id).collectAsStateWithLifecycle(initialValue = false) val isHidden by viewModel.isHiddenFlow(track.id).collectAsStateWithLifecycle(initialValue = false) - var showAddToPlaylist by remember { mutableStateOf(false) } var showHideSheet by remember { mutableStateOf(false) } @@ -71,71 +70,111 @@ fun TrackActionsSheet( onDismissRequest = onDismiss, sheetState = sheetState, ) { - Column(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) { - if (!hideQueueActions) { - MenuItem(Lucide.ListVideo, "Play next") { - viewModel.playNext(track) - onDismiss() - } - MenuItem(Lucide.ListMusic, "Add to queue") { - viewModel.enqueue(track) - onDismiss() - } - HorizontalDivider() + TrackActionMenuItems( + track = track, + hideQueueActions = hideQueueActions, + isLiked = isLiked, + isHidden = isHidden, + viewModel = viewModel, + onDismiss = onDismiss, + onNavigateToAlbum = onNavigateToAlbum, + onNavigateToArtist = onNavigateToArtist, + onShowAddToPlaylist = { showAddToPlaylist = true }, + onShowHideSheet = { showHideSheet = true }, + ) + } + TrackActionSubSheets( + track = track, + showAddToPlaylist = showAddToPlaylist, + showHideSheet = showHideSheet, + viewModel = viewModel, + onDismissParent = onDismiss, + onCloseAddToPlaylist = { showAddToPlaylist = false }, + onCloseHideSheet = { showHideSheet = false }, + ) +} + +@Composable +private fun TrackActionMenuItems( + track: TrackRef, + hideQueueActions: Boolean, + isLiked: Boolean, + isHidden: Boolean, + viewModel: TrackActionsViewModel, + onDismiss: () -> Unit, + onNavigateToAlbum: (String) -> Unit, + onNavigateToArtist: (String) -> Unit, + onShowAddToPlaylist: () -> Unit, + onShowHideSheet: () -> Unit, +) { + Column(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) { + if (!hideQueueActions) { + MenuItem(Lucide.ListVideo, "Play next") { + viewModel.playNext(track); onDismiss() } - MenuItem(Lucide.Heart, if (isLiked) "Unlike" else "Like") { - viewModel.toggleLike(track.id, isLiked) - onDismiss() - } - MenuItem(Lucide.ListPlus, "Add to playlist…") { - showAddToPlaylist = true - } - MenuItem(Lucide.Radio, "Start radio") { - viewModel.startRadio(track.id) - onDismiss() + MenuItem(Lucide.ListMusic, "Add to queue") { + viewModel.enqueue(track); onDismiss() } HorizontalDivider() - MenuItem(Lucide.Disc3, "Go to album") { - onDismiss() - if (track.albumId.isNotEmpty()) onNavigateToAlbum(track.albumId) - } - MenuItem(Lucide.User, "Go to artist") { - onDismiss() - if (track.artistId.isNotEmpty()) onNavigateToArtist(track.artistId) - } - HorizontalDivider() - MenuItem( - icon = if (isHidden) Lucide.Eye else Lucide.EyeOff, - label = if (isHidden) "Unhide" else "Hide", - ) { - if (isHidden) { - viewModel.unflag(track.id) - onDismiss() - } else { - showHideSheet = true - } + } + MenuItem(Lucide.Heart, if (isLiked) "Unlike" else "Like") { + viewModel.toggleLike(track.id, isLiked); onDismiss() + } + MenuItem(Lucide.ListPlus, "Add to playlist…", onShowAddToPlaylist) + MenuItem(Lucide.Radio, "Start radio") { + viewModel.startRadio(track.id); onDismiss() + } + HorizontalDivider() + MenuItem(Lucide.Disc3, "Go to album") { + onDismiss() + if (track.albumId.isNotEmpty()) onNavigateToAlbum(track.albumId) + } + MenuItem(Lucide.User, "Go to artist") { + onDismiss() + if (track.artistId.isNotEmpty()) onNavigateToArtist(track.artistId) + } + HorizontalDivider() + MenuItem( + icon = if (isHidden) Lucide.Eye else Lucide.EyeOff, + label = if (isHidden) "Unhide" else "Hide", + ) { + if (isHidden) { + viewModel.unflag(track.id); onDismiss() + } else { + onShowHideSheet() } } } +} +@Composable +private fun TrackActionSubSheets( + track: TrackRef, + showAddToPlaylist: Boolean, + showHideSheet: Boolean, + viewModel: TrackActionsViewModel, + onDismissParent: () -> Unit, + onCloseAddToPlaylist: () -> Unit, + onCloseHideSheet: () -> Unit, +) { if (showAddToPlaylist) { AddToPlaylistSheet( onPick = { playlistId -> viewModel.appendToPlaylist(playlistId, track.id) - showAddToPlaylist = false - onDismiss() + onCloseAddToPlaylist() + onDismissParent() }, - onDismiss = { showAddToPlaylist = false }, + onDismiss = onCloseAddToPlaylist, ) } if (showHideSheet) { HideTrackSheet( onSubmit = { reason, notes -> viewModel.flag(track, reason, notes) - showHideSheet = false - onDismiss() + onCloseHideSheet() + onDismissParent() }, - onDismiss = { showHideSheet = false }, + onDismiss = onCloseHideSheet, ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsViewModel.kt similarity index 98% rename from android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsViewModel.kt rename to android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsViewModel.kt index 3aab8a57..9d885dbd 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/track_actions/TrackActionsViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/TrackActionsViewModel.kt @@ -1,4 +1,4 @@ -package com.fabledsword.minstrel.shared.widgets.track_actions +package com.fabledsword.minstrel.shared.widgets.trackactions import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope