feat(android): Library AppBar — Shuffle all button
Audit v2 #22. LibraryRepository.shuffleLibrary wraps the existing LibraryApi.shuffleLibrary endpoint; LibraryViewModel.shuffleAll fires PlayerController.setQueue with the response. LibraryScreen's TopAppBar gains a Lucide.Shuffle IconButton to the left of the existing MainAppBarActions row. Offline-fallback (client-side shuffle over the local cache index) is part of the larger Connectivity slice (audit #21) and not wired in this commit — pressing Shuffle when offline just fails silently for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -121,4 +121,16 @@ class LibraryRepository @Inject constructor(
|
||||
val wire = api.getTrack(id)
|
||||
trackDao.upsertAll(listOf(wire.toEntity()))
|
||||
}
|
||||
|
||||
/**
|
||||
* "Shuffle all" — server picks random tracks across the entire
|
||||
* library. Backs the Library AppBar Shuffle button. No cache hit;
|
||||
* the user-facing semantics are "give me something fresh".
|
||||
*/
|
||||
suspend fun shuffleLibrary(limit: Int = SHUFFLE_DEFAULT_LIMIT): List<TrackRef> =
|
||||
api.shuffleLibrary(limit = limit).map { it.toDomain() }
|
||||
|
||||
private companion object {
|
||||
const val SHUFFLE_DEFAULT_LIMIT = 100
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -83,6 +85,13 @@ fun LibraryScreen(
|
||||
TopAppBar(
|
||||
title = { Text("Library") },
|
||||
actions = {
|
||||
IconButton(onClick = viewModel::shuffleAll) {
|
||||
Icon(
|
||||
imageVector = com.composables.icons.lucide.Lucide.Shuffle,
|
||||
contentDescription = "Shuffle all",
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
MainAppBarActions(
|
||||
navController = navController,
|
||||
currentRouteName = Library::class.qualifiedName,
|
||||
|
||||
@@ -18,8 +18,9 @@ private const val SHARE_STOP_TIMEOUT_MS = 5_000L
|
||||
|
||||
@HiltViewModel
|
||||
class LibraryViewModel @Inject constructor(
|
||||
repository: LibraryRepository,
|
||||
private val repository: LibraryRepository,
|
||||
private val syncController: SyncController,
|
||||
private val player: com.fabledsword.minstrel.player.PlayerController,
|
||||
) : ViewModel() {
|
||||
|
||||
val uiState: StateFlow<LibraryUiState> =
|
||||
@@ -50,4 +51,21 @@ class LibraryViewModel @Inject constructor(
|
||||
fun refresh(): Job = viewModelScope.launch {
|
||||
syncController.syncSafe()
|
||||
}
|
||||
|
||||
/**
|
||||
* "Shuffle all" — fetches a random sample across the entire
|
||||
* library and starts playback. Silent on transport failure (user
|
||||
* can re-tap; the action has no inline error surface).
|
||||
*/
|
||||
fun shuffleAll() {
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
val tracks = repository.shuffleLibrary()
|
||||
.filter { it.streamUrl.isNotEmpty() }
|
||||
if (tracks.isNotEmpty()) {
|
||||
player.setQueue(tracks, initialIndex = 0, source = "library_shuffle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user