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 97ce3a6a..a5c39662 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 @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -36,6 +37,7 @@ import com.fabledsword.minstrel.models.ArtistRef import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail +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 @@ -73,6 +75,7 @@ sealed interface LikedTabUiState { @HiltViewModel class LikedTabViewModel @Inject constructor( private val repository: LikesRepository, + private val player: PlayerController, ) : ViewModel() { init { @@ -95,6 +98,19 @@ class LikedTabViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS), initialValue = LikedTabUiState.Loading, ) + + /** + * Build a queue from the currently-displayed liked tracks and start + * playback at [initialIndex]. Mirrors Flutter's tap-on-liked-track + * behavior: the visible track list becomes the play queue. Tracks + * with empty streamUrl are ignored as queue entries (the row that + * routes here is itself click-gated on streamUrl). + */ + fun playTracks(tracks: List, initialIndex: Int) { + if (tracks.isEmpty() || initialIndex !in tracks.indices) return + if (tracks[initialIndex].streamUrl.isEmpty()) return + player.setQueue(tracks, initialIndex = initialIndex, source = "liked") + } } // ─── Composable ────────────────────────────────────────────────────── @@ -121,6 +137,9 @@ fun LikedTab( sections = s.sections, onArtistClick = { id -> navController.navigate(ArtistDetail(id)) }, onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) }, + onTrackClick = { index -> + viewModel.playTracks(s.sections.tracks, index) + }, ) } } @@ -131,6 +150,7 @@ private fun LikedContent( sections: LikedSections, onArtistClick: (String) -> Unit, onAlbumClick: (String) -> Unit, + onTrackClick: (Int) -> Unit, ) { LazyColumn( modifier = Modifier.fillMaxSize(), @@ -164,8 +184,8 @@ private fun LikedContent( item { SectionHeader("Tracks", sections.tracks.size) } - items(items = sections.tracks, key = { "trk-${it.id}" }) { track -> - LikedTrackRow(track = track) + itemsIndexed(items = sections.tracks, key = { _, t -> "trk-${t.id}" }) { index, track -> + LikedTrackRow(track = track, onClick = { onTrackClick(index) }) HorizontalDivider() } } @@ -196,15 +216,11 @@ private fun SectionHeader(label: String, count: Int) { } @Composable -private fun LikedTrackRow(track: TrackRef) { +private fun LikedTrackRow(track: TrackRef, onClick: () -> Unit) { Row( modifier = Modifier .fillMaxWidth() - // Track-row taps will route to the player queue once Phase 9 - // wires history + recently-played tap-to-play; for now the - // row is informational so swiping the tab still feels - // responsive without an unfinished playback affordance. - .clickable(enabled = false, onClick = {}) + .clickable(enabled = track.streamUrl.isNotEmpty(), onClick = onClick) .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp),