From 448a211d8816576ca3cdd0cd8f6b2c39b1e438e5 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:22:24 +0000 Subject: [PATCH] polish library controls and song ordering --- .../com/subtracks/data/db/LibraryDao.kt | 18 +++++----- .../main/kotlin/com/subtracks/ui/AppRoot.kt | 10 +++--- .../com/subtracks/ui/library/LibraryScreen.kt | 35 +++++++++++++------ 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt index 97ddc51b..c56148b1 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -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, diff --git a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt index 9e1c188a..fa68c3e2 100644 --- a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt +++ b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt @@ -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, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index ef887304..d037f952 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -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), )