From 92637deb4872500d9114c5ec3a0ff6704fca1578 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Tue, 29 Sep 2026 07:23:17 +0000 Subject: [PATCH] start the library on the stored tab instead of scrolling to it The pager's settled-page collector reports its initial value, so a cold start wrote Albums back before the stored tab had been read - racing the very setting this branch added, able to pin the app to Albums and overwrite the choice. The screen waits for the stored tab before composing now, so the pager latches the right page and the initial report matches it, which also drops the spurious scroll and the search clear that came with it. The report is guarded anyway. The data layer no longer names a UI tab from a coincidentally same-named enum: it returns the raw stored value and the view model owns the Albums fallback. --- .../subtracks/data/prefs/UserPreferences.kt | 2 +- .../com/subtracks/ui/library/LibraryScreen.kt | 10 +++++-- .../subtracks/ui/library/LibraryViewModel.kt | 6 ++-- .../data/prefs/UserPreferencesTest.kt | 28 ++++--------------- .../subtracks/ui/library/LibraryTabTest.kt | 16 +++++++++++ 5 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 app/src/test/kotlin/com/subtracks/ui/library/LibraryTabTest.kt diff --git a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt index 4c3cfa2e..90217aa1 100644 --- a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt +++ b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt @@ -134,7 +134,7 @@ class UserPreferences( private fun listQueryKey(tab: LibraryListTab) = stringPreferencesKey("list_query_${tab.key}") - fun libraryTab(): Flow = store.data.map { prefs -> prefs[libraryTabKey] ?: LibraryListTab.Albums.name } + fun libraryTab(): Flow = store.data.map { prefs -> prefs[libraryTabKey] } suspend fun setLibraryTab(tab: String) { store.edit { prefs -> prefs[libraryTabKey] = tab } 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 264d5067..a4a0d9d8 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -151,6 +151,8 @@ data class SortOption( fun LibraryTab.listTab(): LibraryListTab = LibraryListTab.valueOf(name) +internal fun libraryTabFor(name: String?): LibraryTab = LibraryTab.entries.firstOrNull { it.name == name } ?: LibraryTab.Albums + fun sortOptionsFor(tab: LibraryTab): List = when (tab) { LibraryTab.Albums -> { @@ -195,8 +197,10 @@ fun LibraryRoute( viewModel: LibraryViewModel = koinViewModel(), playbackController: PlaybackController = koinInject(), ) { - val selectedTab by viewModel.selectedTab.collectAsStateWithLifecycle() - var previousTabName by rememberSaveable { mutableStateOf(selectedTab.name) } + // Wait for the stored tab before composing: the pager latches its initial page, so starting on + // the default and scrolling to the stored one would flash and, worse, report the default back. + val selectedTab = viewModel.selectedTab.collectAsStateWithLifecycle().value ?: return + var previousTabName by remember { mutableStateOf(selectedTab.name) } val previousTab = LibraryTab.entries.firstOrNull { it.name == previousTabName } ?: LibraryTab.Albums val syncing by viewModel.syncing.collectAsStateWithLifecycle() val listTab = selectedTab.listTab() @@ -241,7 +245,7 @@ fun LibraryRoute( ) LibraryScreen( selectedTab = selectedTab, - onTabSelected = { viewModel.selectTab(it) }, + onTabSelected = { tab -> if (viewModel.selectedTab.value != tab) viewModel.selectTab(tab) }, syncing = syncing, bottomInset = bottomInset, artwork = artwork, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt index ad927cb2..3ac317d2 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt @@ -135,11 +135,11 @@ class LibraryViewModel( fun listQuery(tab: LibraryListTab): StateFlow = listQueries.getValue(tab) - val selectedTab: StateFlow = + val selectedTab: StateFlow = userPreferences .libraryTab() - .map { name -> LibraryTab.entries.firstOrNull { it.name == name } ?: LibraryTab.Albums } - .stateIn(viewModelScope, SharingStarted.Eagerly, LibraryTab.Albums) + .map(::libraryTabFor) + .stateIn(viewModelScope, SharingStarted.Eagerly, null) fun selectTab(tab: LibraryTab) { viewModelScope.launch { userPreferences.setLibraryTab(tab.name) } diff --git a/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt b/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt index 635663e0..4a584a09 100644 --- a/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import org.junit.runner.RunWith import java.io.File @@ -32,6 +33,7 @@ class UserPreferencesTest { ListQuery("Added", descending = true, starred = StarredFilter.Starred, downloaded = true), ) prefs.setListQuery(LibraryListTab.Artists, ListQuery("AlbumCount", descending = true)) + prefs.setListQuery(LibraryListTab.Playlists, ListQuery("Updated", starred = StarredFilter.NotStarred)) assertEquals( ListQuery("Added", descending = true, starred = StarredFilter.Starred, downloaded = true), @@ -41,30 +43,10 @@ class UserPreferencesTest { ListQuery("AlbumCount", descending = true), prefs.listQuery(LibraryListTab.Artists).first(), ) - - file.delete() - } - - @Test - fun everyTabsListQueryIsKeptIndependently() = - runTest { - val file = File.createTempFile("user-prefs", ".preferences_pb").apply { delete() } - val store = PreferenceDataStoreFactory.create(scope = backgroundScope) { file } - val prefs = UserPreferences(store) - - prefs.setListQuery(LibraryListTab.Albums, ListQuery("Added", descending = true, downloaded = true)) - prefs.setListQuery(LibraryListTab.Artists, ListQuery("AlbumCount", starred = StarredFilter.NotStarred)) - prefs.setListQuery(LibraryListTab.Playlists, ListQuery("Updated", descending = true)) - assertEquals( - ListQuery("Added", descending = true, downloaded = true), - prefs.listQuery(LibraryListTab.Albums).first(), + ListQuery("Updated", starred = StarredFilter.NotStarred), + prefs.listQuery(LibraryListTab.Playlists).first(), ) - assertEquals( - ListQuery("AlbumCount", starred = StarredFilter.NotStarred), - prefs.listQuery(LibraryListTab.Artists).first(), - ) - assertEquals(ListQuery("Updated", descending = true), prefs.listQuery(LibraryListTab.Playlists).first()) file.delete() } @@ -76,7 +58,7 @@ class UserPreferencesTest { val store = PreferenceDataStoreFactory.create(scope = backgroundScope) { file } val prefs = UserPreferences(store) - assertEquals("Albums", prefs.libraryTab().first()) + assertNull(prefs.libraryTab().first()) prefs.setLibraryTab("Playlists") diff --git a/app/src/test/kotlin/com/subtracks/ui/library/LibraryTabTest.kt b/app/src/test/kotlin/com/subtracks/ui/library/LibraryTabTest.kt new file mode 100644 index 00000000..5775dcc5 --- /dev/null +++ b/app/src/test/kotlin/com/subtracks/ui/library/LibraryTabTest.kt @@ -0,0 +1,16 @@ +package com.subtracks.ui.library + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LibraryTabTest { + @Test + fun theStoredNameMapsToItsTabAndAnythingElseToAlbums() { + assertEquals(LibraryTab.Playlists, libraryTabFor("Playlists")) + assertEquals(LibraryTab.Albums, libraryTabFor(null)) + assertEquals(LibraryTab.Albums, libraryTabFor("Books")) + } +}