-
-
Notifications
You must be signed in to change notification settings - Fork 420
Let users customize library tabs #1799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0d6f843
fe49b70
4bdfbc1
eba3dfa
59d956e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ data class LibraryState( | |
|
|
||
| // Current library tab for quick filter access | ||
| val currentTab: LibraryTab = LibraryTab.ALL, | ||
| val visibleLibraryTabs: List<LibraryTab> = PrefManager.libraryTabs.filter { it in LibraryTab.visibleEntries }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Prompt for AI agents |
||
|
|
||
| // Per-source game counts for tab badges | ||
| val allCount: Int = 0, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,9 +91,12 @@ enum class LibraryTab( | |
| showEpic = false, | ||
| showAmazon = false, | ||
| installedOnly = false, | ||
| ); | ||
| ), | ||
| ; | ||
|
|
||
| companion object { | ||
| val configurableEntries = listOf(STEAM, GOG, EPIC, AMAZON) | ||
|
|
||
| /** | ||
| * Tabs shown in the UI. Custom (LOCAL) games work on all flavors: legacy maps folders | ||
| * in place via all-files access, modern imports them into app-owned storage. | ||
|
|
@@ -105,16 +108,51 @@ enum class LibraryTab( | |
| return result | ||
| } | ||
|
|
||
| fun LibraryTab.next(): LibraryTab { | ||
| val values = visibleEntries | ||
| fun normalizeVisibleTabs( | ||
| serialized: String, | ||
| supportedTabs: List<LibraryTab> = visibleEntries, | ||
| ): List<LibraryTab> { | ||
| val supported = supportedTabs.distinct() | ||
| if (serialized.isBlank()) return supported | ||
|
|
||
| if (!serialized.startsWith(VISIBLE_TABS_PREFIX)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The legacy migration branch in Prompt for AI agents |
||
| val hiddenTabs = serialized | ||
| .split(',') | ||
| .map { it.trim() } | ||
| .filter { it.startsWith(HIDDEN_PREFIX) } | ||
| .map { it.removePrefix(HIDDEN_PREFIX) } | ||
| .toSet() | ||
| return supported.filter { it !in configurableEntries || it.name !in hiddenTabs } | ||
| } | ||
|
|
||
| val selected = serialized | ||
| .removePrefix(VISIBLE_TABS_PREFIX) | ||
| .split(',') | ||
| .mapNotNull { token -> | ||
| val value = token.trim() | ||
| entries.firstOrNull { it.name == value } | ||
| } | ||
| .toSet() | ||
|
|
||
| return supported.filter { it !in configurableEntries || it in selected } | ||
| } | ||
|
|
||
| fun serializeVisibleTabs(tabs: List<LibraryTab>): String = | ||
| tabs.distinct().joinToString(",", prefix = VISIBLE_TABS_PREFIX) { it.name } | ||
|
|
||
| fun LibraryTab.next(visibleTabs: List<LibraryTab> = visibleEntries): LibraryTab { | ||
| val values = visibleTabs.ifEmpty { listOf(ALL) } | ||
| val index = values.indexOf(this).coerceAtLeast(0) | ||
| return values[(index + 1) % values.size] | ||
| } | ||
|
|
||
| fun LibraryTab.previous(): LibraryTab { | ||
| val values = visibleEntries | ||
| fun LibraryTab.previous(visibleTabs: List<LibraryTab> = visibleEntries): LibraryTab { | ||
| val values = visibleTabs.ifEmpty { listOf(ALL) } | ||
| val index = values.indexOf(this).coerceAtLeast(0) | ||
| return values[if (index == 0) values.size - 1 else index - 1] | ||
| } | ||
|
|
||
| private const val HIDDEN_PREFIX = "!" | ||
| private const val VISIBLE_TABS_PREFIX = "v2:" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,7 @@ import app.gamenative.ui.util.rememberWindowWidthClass | |
| @Composable | ||
| fun LibraryTabBar( | ||
| currentTab: LibraryTab, | ||
| tabs: List<LibraryTab>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Reordering the selected tab can leave it off-screen because the centering effect only observes Prompt for AI agents |
||
| tabCounts: Map<LibraryTab, Int>, | ||
| onTabSelected: (LibraryTab) -> Unit, | ||
| onOptionsClick: () -> Unit, | ||
|
|
@@ -86,6 +87,7 @@ fun LibraryTabBar( | |
| when (widthClass) { | ||
| WindowWidthClass.COMPACT -> CompactLibraryTabBar( | ||
| currentTab = currentTab, | ||
| tabs = tabs, | ||
| tabCounts = tabCounts, | ||
| onTabSelected = onTabSelected, | ||
| onOptionsClick = onOptionsClick, | ||
|
|
@@ -100,6 +102,7 @@ fun LibraryTabBar( | |
|
|
||
| else -> ExpandedLibraryTabBar( | ||
| currentTab = currentTab, | ||
| tabs = tabs, | ||
| tabCounts = tabCounts, | ||
| onTabSelected = onTabSelected, | ||
| onOptionsClick = onOptionsClick, | ||
|
|
@@ -121,6 +124,7 @@ fun LibraryTabBar( | |
| @Composable | ||
| private fun CompactLibraryTabBar( | ||
| currentTab: LibraryTab, | ||
| tabs: List<LibraryTab>, | ||
| tabCounts: Map<LibraryTab, Int>, | ||
| onTabSelected: (LibraryTab) -> Unit, | ||
| onOptionsClick: () -> Unit, | ||
|
|
@@ -132,7 +136,6 @@ private fun CompactLibraryTabBar( | |
| onNextTab: () -> Unit, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| val tabs = LibraryTab.visibleEntries | ||
| val currentIndex = tabs.indexOf(currentTab) | ||
| val scrollState = rememberScrollState() | ||
| val tabPositions = remember { mutableStateMapOf<Int, Float>() } | ||
|
|
@@ -347,6 +350,7 @@ private fun CompactIconButton( | |
| @Composable | ||
| private fun ExpandedLibraryTabBar( | ||
| currentTab: LibraryTab, | ||
| tabs: List<LibraryTab>, | ||
| tabCounts: Map<LibraryTab, Int>, | ||
| onTabSelected: (LibraryTab) -> Unit, | ||
| onOptionsClick: () -> Unit, | ||
|
|
@@ -358,7 +362,6 @@ private fun ExpandedLibraryTabBar( | |
| onNextTab: () -> Unit, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| val tabs = LibraryTab.visibleEntries | ||
| val currentIndex = tabs.indexOf(currentTab) | ||
| val scrollState = rememberScrollState() | ||
|
|
||
|
|
@@ -696,6 +699,7 @@ private fun Preview_LibraryTabBar() { | |
| ) { | ||
| LibraryTabBar( | ||
| currentTab = LibraryTab.ALL, | ||
| tabs = LibraryTab.visibleEntries, | ||
| tabCounts = mapOf( | ||
| LibraryTab.ALL to 42, | ||
| LibraryTab.STEAM to 30, | ||
|
|
@@ -725,6 +729,7 @@ private fun Preview_LibraryTabBar_Steam() { | |
| ) { | ||
| LibraryTabBar( | ||
| currentTab = LibraryTab.STEAM, | ||
| tabs = LibraryTab.visibleEntries, | ||
| tabCounts = mapOf( | ||
| LibraryTab.ALL to 42, | ||
| LibraryTab.STEAM to 30, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -1432,6 +1432,9 @@ | |||||
| <string name="review_overwhelmingly_negative">Overvældende negativ</string> | ||||||
| <string name="settings_interface_show_recommendations_title">Vis spilanbefalinger</string> | ||||||
| <string name="settings_interface_show_recommendations_subtitle">Vis personlige anbefalinger. At holde dette slået til hjælper med at støtte GameNative.</string> | ||||||
| <string name="settings_interface_library_tabs_title">Bibliotekfaner</string> | ||||||
| <string name="settings_interface_library_tabs_subtitle">Vælg, hvilke butiksfaner der vises i biblioteket.</string> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Include that The subtitle omits the behavior guarantee present in the feature contract. Danish users may think that hiding all store tabs removes every library tab. Translate the full meaning, for example: 🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Tell Danish users that Prompt for AI agents
Suggested change
|
||||||
| <string name="settings_interface_library_tabs_none">Ingen butiksfaner</string> | ||||||
| <string name="steam_save_export_success">Gemte filer eksporteret</string> | ||||||
| <string name="steam_save_export_no_saves_found">Ingen gemte filer fundet</string> | ||||||
| <string name="steam_save_export_failed">Kunne ikke eksportere gemte filer: %s</string> | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Make
libraryTabspersistence last-write-wins.libraryTabscallssetPref, which launches eachLIBRARY_TAB_PREFERENCESedit independently onDispatchers.IO. DataStore serializes edits, but rapid assignments can reacheditout of assignment order. An older tab list can become the final persisted value and return after restart. Add a per-preference generation check likefavoritePersistenceVersion, or serialize these writes in assignment order.🤖 Prompt for AI Agents