Hotfix v2026.05.15.1 — allow discovery-mix variants in playlists CHECK constraints #49
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.models.TrackRef
|
||||||
import com.fabledsword.minstrel.nav.AlbumDetail
|
import com.fabledsword.minstrel.nav.AlbumDetail
|
||||||
import com.fabledsword.minstrel.nav.ArtistDetail
|
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.EmptyState
|
||||||
import com.fabledsword.minstrel.shared.widgets.HorizontalScrollRow
|
import com.fabledsword.minstrel.shared.widgets.HorizontalScrollRow
|
||||||
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
||||||
@@ -73,6 +75,7 @@ sealed interface LikedTabUiState {
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LikedTabViewModel @Inject constructor(
|
class LikedTabViewModel @Inject constructor(
|
||||||
private val repository: LikesRepository,
|
private val repository: LikesRepository,
|
||||||
|
private val player: PlayerController,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -95,6 +98,19 @@ class LikedTabViewModel @Inject constructor(
|
|||||||
started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
|
started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
|
||||||
initialValue = LikedTabUiState.Loading,
|
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 ──────────────────────────────────────────────────────
|
// ─── Composable ──────────────────────────────────────────────────────
|
||||||
@@ -121,6 +137,9 @@ fun LikedTab(
|
|||||||
sections = s.sections,
|
sections = s.sections,
|
||||||
onArtistClick = { id -> navController.navigate(ArtistDetail(id)) },
|
onArtistClick = { id -> navController.navigate(ArtistDetail(id)) },
|
||||||
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
||||||
|
onTrackClick = { index ->
|
||||||
|
viewModel.playTracks(s.sections.tracks, index)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,6 +150,7 @@ private fun LikedContent(
|
|||||||
sections: LikedSections,
|
sections: LikedSections,
|
||||||
onArtistClick: (String) -> Unit,
|
onArtistClick: (String) -> Unit,
|
||||||
onAlbumClick: (String) -> Unit,
|
onAlbumClick: (String) -> Unit,
|
||||||
|
onTrackClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -164,8 +184,8 @@ private fun LikedContent(
|
|||||||
item {
|
item {
|
||||||
SectionHeader("Tracks", sections.tracks.size)
|
SectionHeader("Tracks", sections.tracks.size)
|
||||||
}
|
}
|
||||||
items(items = sections.tracks, key = { "trk-${it.id}" }) { track ->
|
itemsIndexed(items = sections.tracks, key = { _, t -> "trk-${t.id}" }) { index, track ->
|
||||||
LikedTrackRow(track = track)
|
LikedTrackRow(track = track, onClick = { onTrackClick(index) })
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,15 +216,11 @@ private fun SectionHeader(label: String, count: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LikedTrackRow(track: TrackRef) {
|
private fun LikedTrackRow(track: TrackRef, onClick: () -> Unit) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// Track-row taps will route to the player queue once Phase 9
|
.clickable(enabled = track.streamUrl.isNotEmpty(), onClick = onClick)
|
||||||
// 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 = {})
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
|||||||
Reference in New Issue
Block a user