fix(android): LikedTab tracks tap to play

Track rows in the Liked tab had .clickable(enabled = false) with a
"Phase 9" deferral comment, silently breaking the most common
interaction on the list. Mirror the HistoryTab pattern: inject
PlayerController, expose playTracks(list, index), and route taps
through to PlayerController.setQueue with source = "liked". Row
clickability is gated on streamUrl so rows that can't be played
stay inert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 10:31:45 -04:00
parent 484ad6c496
commit f628ae4479
@@ -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<TrackRef>, 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),