mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
ui: finish search focus and artist art review follow-ups
Avoid re-requesting search focus when the screen is restored, keep the cheaper album-credit-first art lookup, and test restoration plus association-table invalidation.
This commit is contained in:
@@ -165,28 +165,35 @@ internal const val ARTISTS_FILTER =
|
||||
"OR (length(:search) < 3 AND (instr(lower(name), lower(:search)) > 0 " +
|
||||
"OR instr(lower(sortName), lower(:search)) > 0))) "
|
||||
|
||||
internal const val ARTIST_COVER_ART =
|
||||
"COALESCE(artists.coverArt, (" +
|
||||
"SELECT credit_albums.coverArt FROM (" +
|
||||
"SELECT 0 AS creditRank, albums.id, albums.coverArt, albums.year, albums.sortName FROM albums " +
|
||||
internal const val ARTIST_ALBUM_CREDIT_ART =
|
||||
"SELECT albums.coverArt FROM (" +
|
||||
"SELECT albums.id, albums.coverArt, albums.year, albums.sortName FROM albums " +
|
||||
"INDEXED BY index_albums_sourceId_artistId " +
|
||||
"WHERE albums.sourceId = artists.sourceId AND albums.artistId = artists.id " +
|
||||
"UNION " +
|
||||
"SELECT 0, albums.id, albums.coverArt, albums.year, albums.sortName FROM album_artists " +
|
||||
"UNION ALL " +
|
||||
"SELECT albums.id, albums.coverArt, albums.year, albums.sortName FROM album_artists " +
|
||||
"JOIN albums ON albums.sourceId = album_artists.sourceId AND albums.id = album_artists.albumId " +
|
||||
"WHERE album_artists.sourceId = artists.sourceId AND album_artists.artistId = artists.id " +
|
||||
"UNION " +
|
||||
"SELECT 1, albums.id, albums.coverArt, albums.year, albums.sortName FROM songs " +
|
||||
"WHERE album_artists.sourceId = artists.sourceId AND album_artists.artistId = artists.id" +
|
||||
") albums ORDER BY albums.year DESC, albums.sortName COLLATE NOCASE, albums.id LIMIT 1"
|
||||
|
||||
internal const val ARTIST_TRACK_CREDIT_ART =
|
||||
"SELECT albums.coverArt FROM (" +
|
||||
"SELECT albums.id, albums.coverArt, albums.year, albums.sortName FROM songs " +
|
||||
"JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = artists.sourceId AND songs.artistId = artists.id " +
|
||||
"UNION " +
|
||||
"SELECT 1, albums.id, albums.coverArt, albums.year, albums.sortName FROM song_artists " +
|
||||
"UNION ALL " +
|
||||
"SELECT albums.id, albums.coverArt, albums.year, albums.sortName FROM song_artists " +
|
||||
"JOIN songs ON songs.sourceId = song_artists.sourceId AND songs.id = song_artists.songId " +
|
||||
"JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE song_artists.sourceId = artists.sourceId AND song_artists.artistId = artists.id" +
|
||||
") credit_albums " +
|
||||
"ORDER BY credit_albums.creditRank, credit_albums.year DESC, credit_albums.sortName COLLATE NOCASE, " +
|
||||
"credit_albums.id LIMIT 1))"
|
||||
") albums ORDER BY albums.year DESC, albums.sortName COLLATE NOCASE, albums.id LIMIT 1"
|
||||
|
||||
internal const val ARTIST_COVER_ART =
|
||||
"COALESCE(artists.coverArt, CASE " +
|
||||
"WHEN EXISTS (SELECT 1 FROM albums WHERE albums.sourceId = artists.sourceId AND albums.artistId = artists.id) " +
|
||||
"OR EXISTS (SELECT 1 FROM album_artists " +
|
||||
"WHERE album_artists.sourceId = artists.sourceId AND album_artists.artistId = artists.id) " +
|
||||
"THEN ($ARTIST_ALBUM_CREDIT_ART) ELSE ($ARTIST_TRACK_CREDIT_ART) END)"
|
||||
|
||||
internal const val ARTIST_COLUMNS =
|
||||
"artists.sourceId, artists.id, artists.name, artists.albumCount, artists.starred, " +
|
||||
|
||||
@@ -57,7 +57,7 @@ fun SearchField(
|
||||
}
|
||||
}
|
||||
var focusRequested by rememberSaveable { mutableStateOf(false) }
|
||||
LaunchedEffect(focusRequested) {
|
||||
LaunchedEffect(Unit) {
|
||||
if (!focusRequested) {
|
||||
focusRequester.requestFocus()
|
||||
focusRequested = true
|
||||
|
||||
@@ -273,6 +273,7 @@ fun LibraryRoute(
|
||||
val downloadsState = listTab?.let { viewModel.downloads(it).collectAsStateWithLifecycle() }
|
||||
val downloads = downloadsState?.value ?: emptyMap()
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusManager = LocalFocusManager.current
|
||||
var pendingDelete by remember { mutableStateOf<PendingDownloadDelete?>(null) }
|
||||
DismissOnRequest(contextMenuHost?.dismissals ?: emptyFlow()) { pendingDelete = null }
|
||||
val itemActions =
|
||||
@@ -312,7 +313,10 @@ fun LibraryRoute(
|
||||
onSync = viewModel::sync,
|
||||
onOpenSettings = onOpenSettings,
|
||||
onOpenSearch = onOpenSearch,
|
||||
onItemLongClick = { contextMenuHost?.show(it, itemActions) },
|
||||
onItemLongClick = {
|
||||
focusManager.clearFocus()
|
||||
contextMenuHost?.show(it, itemActions)
|
||||
},
|
||||
listQuery = displayQuery,
|
||||
resetKeys = resetKeys,
|
||||
offline = offline,
|
||||
|
||||
@@ -124,7 +124,10 @@ fun SearchRoute(
|
||||
focusManager.clearFocus()
|
||||
viewModel.play(it)
|
||||
},
|
||||
onLongClick = { contextMenuHost?.show(it, actions) },
|
||||
onLongClick = {
|
||||
focusManager.clearFocus()
|
||||
contextMenuHost?.show(it, actions)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -677,6 +677,32 @@ class LibraryDaoTest {
|
||||
|
||||
dao.upsertSongs(listOf(song(sourceId, "s-1", "Song", starred = null)))
|
||||
awaitUntil("artist paging observes song writes") { songInvalidated.get() }
|
||||
|
||||
val albumCreditInvalidated = AtomicBoolean(false)
|
||||
val albumCreditPaging = dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.Name, false))
|
||||
albumCreditPaging.registerInvalidatedCallback { albumCreditInvalidated.set(true) }
|
||||
albumCreditPaging.page()
|
||||
|
||||
db.useWriterConnection { connection ->
|
||||
connection.usePrepared(
|
||||
"INSERT INTO album_artists (sourceId, albumId, position, artistId, name) " +
|
||||
"VALUES ($sourceId, 'al-1', 0, 'ar-1', 'Artist')",
|
||||
) { it.step() }
|
||||
}
|
||||
awaitUntil("artist paging observes album artist writes") { albumCreditInvalidated.get() }
|
||||
|
||||
val songCreditInvalidated = AtomicBoolean(false)
|
||||
val songCreditPaging = dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.Name, false))
|
||||
songCreditPaging.registerInvalidatedCallback { songCreditInvalidated.set(true) }
|
||||
songCreditPaging.page()
|
||||
|
||||
db.useWriterConnection { connection ->
|
||||
connection.usePrepared(
|
||||
"INSERT INTO song_artists (sourceId, songId, position, artistId, name) " +
|
||||
"VALUES ($sourceId, 's-1', 0, 'ar-1', 'Artist')",
|
||||
) { it.step() }
|
||||
}
|
||||
awaitUntil("artist paging observes song artist writes") { songCreditInvalidated.get() }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -46,7 +46,7 @@ class ArtistChooserSheetTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun opensTheChosenArtistBeforeTheSheetFinishesClosing() {
|
||||
fun opensTheChosenArtistImmediately() {
|
||||
var opened: ArtistRef? = null
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
|
||||
@@ -6,7 +6,10 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsFocused
|
||||
import androidx.compose.ui.test.assertIsNotFocused
|
||||
import androidx.compose.ui.test.hasSetTextAction
|
||||
import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
@@ -91,7 +94,7 @@ class SearchScreenTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typingResumesAtTheEndOfTheQueryAfterTheScreenIsRecreated() {
|
||||
fun typingKeepsTheCursorAtTheEndAfterTheFieldIsRecreated() {
|
||||
var query by mutableStateOf("road")
|
||||
var show by mutableStateOf(true)
|
||||
composeRule.setContent {
|
||||
@@ -120,6 +123,21 @@ class SearchScreenTest {
|
||||
.hideSoftInputFromWindow(composeRule.activity.window.decorView.windowToken, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoringTheScreenDoesNotStealFocus() {
|
||||
val restorationTester = StateRestorationTester(composeRule)
|
||||
restorationTester.setContent {
|
||||
SubtracksTheme {
|
||||
SearchScreen(query = "road", onQueryChange = {}, results = SearchResults())
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onNode(hasSetTextAction()).assertIsFocused()
|
||||
restorationTester.emulateSavedInstanceStateRestore()
|
||||
|
||||
composeRule.onNode(hasSetTextAction()).assertIsNotFocused()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun idsSharedAcrossTypesDoNotCollide() {
|
||||
composeRule.setContent {
|
||||
|
||||
Reference in New Issue
Block a user