polish library controls and song ordering

This commit is contained in:
austinried
2026-10-04 00:29:56 +00:00
parent f1c030f0e1
commit 448a211d88
3 changed files with 39 additions and 24 deletions
@@ -26,7 +26,7 @@ internal const val SONGS_SQL =
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
"WHERE songs.sourceId = :sourceId " +
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
"songs.title COLLATE NOCASE, songs.id"
"songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id"
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
@@ -350,7 +350,7 @@ interface LibraryDao {
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
"songs.title COLLATE NOCASE, songs.id",
"songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songs(
sourceId: Long,
@@ -367,7 +367,7 @@ interface LibraryDao {
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY albums.albumArtist COLLATE NOCASE DESC, songs.album COLLATE NOCASE DESC, songs.disc DESC, " +
"songs.track DESC, songs.title COLLATE NOCASE DESC, songs.id DESC",
"songs.track DESC, songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id DESC",
)
fun songsByAlbumReversed(
sourceId: Long,
@@ -383,7 +383,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.title COLLATE NOCASE, songs.id",
"ORDER BY songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByTitle(
sourceId: Long,
@@ -399,7 +399,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.title COLLATE NOCASE DESC, songs.id",
"ORDER BY songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByTitleReversed(
sourceId: Long,
@@ -447,7 +447,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.starred IS NULL, songs.starred DESC, songs.title COLLATE NOCASE, songs.id",
"ORDER BY songs.starred IS NULL, songs.starred DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByStarred(
sourceId: Long,
@@ -463,7 +463,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.starred IS NULL, songs.starred ASC, songs.title COLLATE NOCASE, songs.id",
"ORDER BY songs.starred IS NULL, songs.starred ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByStarredReversed(
sourceId: Long,
@@ -479,7 +479,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.created DESC, songs.title COLLATE NOCASE, songs.id",
"ORDER BY songs.created DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByAdded(
sourceId: Long,
@@ -495,7 +495,7 @@ interface LibraryDao {
"(:starredFilter = 2 AND songs.starred IS NULL)) " +
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
"ORDER BY songs.created ASC, songs.title COLLATE NOCASE, songs.id",
"ORDER BY songs.created ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
)
fun songsByAddedReversed(
sourceId: Long,
@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -137,6 +136,8 @@ private fun MainNavigation() {
LaunchedEffect(Unit) { if (nowPlayingOpen) nowPlayingProgress = 1f }
val playerVisible = playback.item != null
var librarySearching by rememberSaveable { mutableStateOf(false) }
val showMiniPlayer = playerVisible && !librarySearching
LaunchedEffect(playerVisible) {
if (!playerVisible) {
settleJob?.cancel()
@@ -148,7 +149,7 @@ private fun MainNavigation() {
val navBarInset = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
Box(Modifier.fillMaxSize()) {
Column(Modifier.fillMaxSize().imePadding()) {
Column(Modifier.fillMaxSize()) {
NavHost(
navController = navController,
modifier =
@@ -185,7 +186,8 @@ private fun MainNavigation() {
onArtistClick = { artist -> navController.navigate(Routes.artist(artist.id, artist.coverArt)) },
onPlaylistClick = { playlist -> navController.navigate(Routes.playlist(playlist.id)) },
onOpenSettings = { navController.navigate(Routes.SETTINGS) },
bottomInset = if (playerVisible) 0.dp else navBarInset,
onSearchActiveChange = { librarySearching = it },
bottomInset = if (showMiniPlayer) 0.dp else navBarInset,
)
}
composable(Routes.SETTINGS) {
@@ -254,7 +256,7 @@ private fun MainNavigation() {
}
}
if (playerVisible) {
if (showMiniPlayer) {
val miniArt = playbackController.coverArt(playback.item, thumbnail = true)
MiniPlayer(
state = playback,
@@ -50,12 +50,13 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
@@ -164,6 +165,7 @@ fun LibraryRoute(
onArtistClick: (Artist) -> Unit,
onPlaylistClick: (Playlist) -> Unit,
onOpenSettings: () -> Unit,
onSearchActiveChange: (Boolean) -> Unit,
bottomInset: Dp,
viewModel: LibraryViewModel = koinViewModel(),
) {
@@ -195,6 +197,7 @@ fun LibraryRoute(
onSongClick = viewModel::playSong,
onSync = viewModel::sync,
onOpenSettings = onOpenSettings,
onSearchActiveChange = onSearchActiveChange,
playingSongId = playingSongId,
listQuery = listQuery,
resetKeys = resetKeys,
@@ -249,6 +252,7 @@ fun LibraryScreen(
onClearFilters: () -> Unit = {},
search: String = "",
onSearchChange: (String) -> Unit = {},
onSearchActiveChange: (Boolean) -> Unit = {},
modifier: Modifier = Modifier,
) {
val pagerState =
@@ -260,6 +264,11 @@ fun LibraryScreen(
var searchActive by rememberSaveable { mutableStateOf(false) }
val searchFocus = remember { FocusRequester() }
DisposableEffect(searchActive) {
onSearchActiveChange(searchActive)
onDispose { onSearchActiveChange(false) }
}
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
onTabSelected(LibraryTab.entries[page])
@@ -524,10 +533,11 @@ private fun ListOptionsSheet(
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
)
if (listQuery.starred != StarredFilter.Any) {
IconButton(onClick = onClearFilters) {
Icon(Icons.Rounded.FilterAltOff, contentDescription = "Clear filters")
}
IconButton(
onClick = onClearFilters,
enabled = listQuery.starred != StarredFilter.Any,
) {
Icon(Icons.Rounded.FilterAltOff, contentDescription = "Clear filters")
}
}
FlowRow(
@@ -582,21 +592,24 @@ private fun SearchField(
modifier: Modifier = Modifier,
) {
LaunchedEffect(Unit) { focusRequester.requestFocus() }
OutlinedTextField(
TextField(
value = value,
onValueChange = onValueChange,
singleLine = true,
label = { Text("Search") },
placeholder = { Text("Search") },
leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) },
trailingIcon = {
IconButton(onClick = onClose) {
Icon(Icons.Rounded.Close, contentDescription = "Close search")
}
},
shape = RoundedCornerShape(4.dp),
colors =
OutlinedTextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.background,
unfocusedContainerColor = MaterialTheme.colorScheme.background,
TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
modifier = modifier.focusRequester(focusRequester),
)