Skip to content

Let users customize library tabs - #1799

Open
jmarti326 wants to merge 2 commits into
utkarshdalal:masterfrom
jmarti326:jmarti326-customizable-store-tabs
Open

Let users customize library tabs#1799
jmarti326 wants to merge 2 commits into
utkarshdalal:masterfrom
jmarti326:jmarti326-customizable-store-tabs

Conversation

@jmarti326

@jmarti326 jmarti326 commented Aug 8, 2026

Copy link
Copy Markdown

Hi, this adds a small option to choose which store tabs appear in the library.

Users can hide stores they do not use and reorder the remaining tabs. The All tab stays visible, and hidden stores still appear in All.

The editor adapts to phones and landscape handhelds. I tested it in portrait on a Galaxy Z Flip and in landscape on a Retroid Pocket 5.

I hope this makes the library a little cleaner for people who only use a few stores.

Showcase

Retroid Pocket 5, landscape

Library tab customization on a Retroid Pocket 5

Galaxy Z Flip, portrait

Library tab customization on a Galaxy Z Flip

Videos

signal-2026-08-08-17-29-15-808.mp4
signal-2026-08-08-17-30-05-577.mp4

Summary by cubic

Let users show, hide, and reorder library tabs, with All always visible. Preferences persist independently from source filters, and tab navigation only cycles through visible tabs.

  • New Features
    • Added LibraryTabsCustomizer with live preview: bottom sheet on compact and a two‑column dialog on wide screens; includes switches, move up/down, long‑press drag, and Reset.
    • Persist ordered tab visibility via PrefManager.libraryTabPreferences and LibraryTabPreference; normalize/serialize to keep ALL first/visible, drop unsupported values, and auto‑add new tabs.
    • Updated LibraryViewModel to toggle visibility, reorder tabs, reset to defaults, ignore selection of hidden tabs, and fall back to ALL if the active tab is hidden.
    • LibraryTabBar now takes a tabs list and shows an Edit button to open the customizer; touch, controller, and accessibility friendly.
    • Next/previous traversal uses visibleLibraryTabs; added unit tests for normalization, preference round‑trip, and traversal behavior.

Written for commit 735a15d. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Customize library tabs by showing, hiding, reordering, and resetting them.
    • Access tab customization from the library navigation bar.
    • Navigate only through visible tabs, with the main “All” tab always available.
    • View tab counts and live previews while customizing.
  • Accessibility

    • Reorder tabs using drag-and-drop or directional controls.
    • Added localized guidance and accessibility labels for customization actions.
  • Bug Fixes

    • Hidden or unsupported tabs are handled consistently while preserving saved preferences.

@jmarti326
jmarti326 requested a review from utkarshdalal as a code owner August 8, 2026 21:31
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds persistent library-tab visibility and ordering preferences. Navigation uses visible tabs. The library UI provides responsive controls for visibility, ordering, reset, previews, counts, and accessibility actions.

Changes

Library tab customization

Layer / File(s) Summary
Preference normalization and persisted state
app/src/main/java/app/gamenative/ui/enums/LibraryTab.kt, app/src/main/java/app/gamenative/PrefManager.kt, app/src/main/java/app/gamenative/ui/data/LibraryState.kt, app/src/test/java/app/gamenative/ui/enums/LibraryTabTest.kt
Defines LibraryTabPreference, normalizes and serializes preferences, persists them, derives visible tabs, and tests traversal and round-tripping.
Visible-tab navigation and preference actions
app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt
Updates navigation, visibility changes, ordering, reset behavior, persistence, and fallback to ALL.
Tab customization controls
app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt, app/src/main/res/values/strings.xml
Adds compact and wide customization surfaces with previews, visibility switches, counts, reset controls, arrow movement, drag reordering, and accessibility actions.
Library screen and tab-bar integration
app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt, app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt
Passes visible tabs and callbacks through the library UI, opens the customizer, handles dismissal, and hides the bottom action bar while it is open.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LibraryTabBar
  participant LibraryTabsCustomizer
  participant LibraryViewModel
  participant PrefManager
  User->>LibraryTabBar: select customize action
  LibraryTabBar->>LibraryTabsCustomizer: open customizer
  User->>LibraryTabsCustomizer: change visibility or order
  LibraryTabsCustomizer->>LibraryViewModel: submit preference change
  LibraryViewModel->>PrefManager: persist normalized preferences
  LibraryViewModel-->>LibraryTabBar: update visible tabs
Loading

Possibly related PRs

Suggested reviewers: utkarshdalal

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: allowing users to customize library tabs.
Description check ✅ Passed The description explains the feature, rationale, testing, and includes screenshots and videos, but omits the template's Type of Change and Checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jmarti326

Copy link
Copy Markdown
Author
signal-2026-08-08-17-21-02-616 signal-2026-08-08-17-27-01-478
signal-2026-08-08-17-29-15-808.mp4
signal-2026-08-08-17-30-05-577.mp4

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.planning/sketches/001-store-tab-customization/index.html:
- Around line 101-103: Update the compact-layout rules for .move-controls so
narrow-screen users retain an accessible tab-reorder action; either keep the
move buttons visible below 700px or implement equivalent touch and keyboard
reordering before hiding them. Preserve the existing .store-row and .sheet
responsive styling.

In @.planning/sketches/themes/default.css:
- Line 13: Update the --font-sans declaration to quote the case-sensitive font
family names Inter and BlinkMacSystemFont, preserving the existing fallback
order and all other stack entries.

In `@app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt`:
- Around line 343-353: Update LibraryViewModel.onLibraryTabMoved to reject
hidden tabs and move the selected tab relative to adjacent visible, non-ALL
preferences rather than raw list indices; add coverage for hidden preferences
between visible tabs. In LibraryTabsCustomizer, calculate canMoveUp and
canMoveDown from visible non-ALL preferences and disable reorder controls for
hidden tabs.

In
`@app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt`:
- Around line 444-447: Add a semantics content description to the Switch in the
LibraryTabsCustomizer tab row using the tab’s localized label resource, while
preserving its existing checked state and onCheckedChange behavior.

In `@app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt`:
- Around line 477-479: Update the parent key handler and global key listener
guards in LibraryScreen so B, L1, and R1 navigation/focus-bootstrap commands are
blocked while isTabCustomizerOpen is true. Handle B by dismissing the tab
customizer first, and add !isTabCustomizerOpen to every relevant
library-navigation and focus-bootstrap condition while preserving normal
behavior when the customizer is closed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 73a94995-c660-4d89-b2d7-8429c975e96d

📥 Commits

Reviewing files that changed from the base of the PR and between bd1c439 and 4718906.

📒 Files selected for processing (14)
  • .planning/sketches/001-store-tab-customization/PLAN.md
  • .planning/sketches/001-store-tab-customization/README.md
  • .planning/sketches/001-store-tab-customization/index.html
  • .planning/sketches/MANIFEST.md
  • .planning/sketches/themes/default.css
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/ui/data/LibraryState.kt
  • app/src/main/java/app/gamenative/ui/enums/LibraryTab.kt
  • app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt
  • app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt
  • app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt
  • app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/ui/enums/LibraryTabTest.kt

Comment on lines +101 to +103
.store-row { grid-template-columns: 25px 1fr auto; }
.move-controls { display: none; }
.sheet { max-height: 90%; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep a reorder action on compact layouts.

Lines 101-102 hide the move buttons below 700px. The drag indicator has no drag implementation. Users on narrow screens cannot reorder tabs.

Keep accessible move buttons, or implement touch and keyboard reorder behavior before hiding them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.planning/sketches/001-store-tab-customization/index.html around lines 101 -
103, Update the compact-layout rules for .move-controls so narrow-screen users
retain an accessible tab-reorder action; either keep the move buttons visible
below 700px or implement equivalent touch and keyboard reordering before hiding
them. Preserve the existing .store-row and .sheet responsive styling.

Comment thread .planning/sketches/themes/default.css Outdated
--color-accent: #00d4ff;
--color-success: #10b981;
--color-danger: #ef4444;
--font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the Stylelint errors in the font stack.

Stylelint reports Inter and BlinkMacSystemFont as invalidly cased keywords. Quote these font family names, or exclude this token from the configured rule.

Proposed fix
-  --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "BlinkMacSystemFont", "Segoe UI", sans-serif;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "BlinkMacSystemFont", "Segoe UI", sans-serif;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 13-13: Expected "Inter" to be "inter" (value-keyword-case)

(value-keyword-case)


[error] 13-13: Expected "BlinkMacSystemFont" to be "blinkmacsystemfont" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.planning/sketches/themes/default.css at line 13, Update the --font-sans
declaration to quote the case-sensitive font family names Inter and
BlinkMacSystemFont, preserving the existing fallback order and all other stack
entries.

Source: Linters/SAST tools

Comment on lines +343 to +353
fun onLibraryTabMoved(tab: LibraryTab, offset: Int) {
if (tab == LibraryTab.ALL || offset == 0) return
updateLibraryTabPreferences { preferences ->
val mutable = preferences.toMutableList()
val currentIndex = mutable.indexOfFirst { it.tab == tab }
if (currentIndex < 1) return@updateLibraryTabPreferences preferences
val targetIndex = (currentIndex + offset).coerceIn(1, mutable.lastIndex)
if (targetIndex == currentIndex) return@updateLibraryTabPreferences preferences
val item = mutable.removeAt(currentIndex)
mutable.add(targetIndex, item)
mutable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move tabs relative to visible tabs only.

Hidden tabs currently consume movement positions. For example, with ALL, STEAM, !GOG, EPIC, moving EPIC up moves it across hidden GOG but leaves the visible order unchanged. The preview and tab bar therefore do not change after an enabled move action.

  • app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt#L343-L353: reject moves for hidden tabs. Find the adjacent visible non-ALL preference, then move relative to that preference.
  • app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt#L289-L297: calculate canMoveUp and canMoveDown from visible non-ALL preferences. Disable reorder controls for hidden tabs.
  • Add a test with hidden preferences between two visible tabs.

The PR objective requires users to reorder visible tabs.

📍 Affects 2 files
  • app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt#L343-L353 (this comment)
  • app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt#L289-L297
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt` around lines
343 - 353, Update LibraryViewModel.onLibraryTabMoved to reject hidden tabs and
move the selected tab relative to adjacent visible, non-ALL preferences rather
than raw list indices; add coverage for hidden preferences between visible tabs.
In LibraryTabsCustomizer, calculate canMoveUp and canMoveDown from visible
non-ALL preferences and disable reorder controls for hidden tabs.

Comment on lines +444 to +447
Switch(
checked = preference.isVisible,
onCheckedChange = { onVisibilityChanged(tab, it) },
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt"
if [ -f "$file" ]; then
  echo "== file lines =="
  sed -n '400,470p' "$file" | nl -ba -v400
  echo
  echo "== surrounding imports/usages =="
  rg -n "LibraryTabPreferenceRow|tabLabelResId|labelResId|Switch\\(" app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt
else
  echo "file not found"
  echo "candidates:"
  fd -i 'LibraryTabsCustomizer.kt' .
fi

Repository: utkarshdalal/GameNative

Length of output: 218


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt"
if [ -f "$file" ]; then
  echo "== file exists =="
  wc -l "$file"
  echo
  echo "== lines 400-470 =="
  sed -n '400,470p' "$file" | awk '{printf "%6d\t%s\n", NR+399, $0}'
  echo
  echo "== relevant symbols/usages =="
  rg -n "LibraryTabPreferenceRow|labelResId|Switch\\(|semantics\\(|Modifier\\." app/src/main/java/app/gamenative/ui/screen/library/components/$file
else
  echo "file not found"
  fd -i 'LibraryTabsCustomizer.kt' .
fi

Repository: utkarshdalal/GameNative

Length of output: 2987


Label the visibility switch with the tab name.

The Switch has no semantic label, so screen readers can expose it separately from the row text without identifying which tab it controls. Add a semantics label, such as stringResource(tab.labelResId), to the Switch while preserving its checked-state semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt`
around lines 444 - 447, Add a semantics content description to the Switch in the
LibraryTabsCustomizer tab row using the tab’s localized label resource, while
preserving its existing checked state and onCheckedChange behavior.

Comment thread app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt
Persist tab visibility and ordering separately from library source filters, add an adaptive customization surface, and keep controller traversal aligned with visible tabs.
Use a dedicated two-column dialog on landscape handhelds while retaining the compact bottom sheet on narrow screens.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt:75">
P2: Reordering the selected tab can leave it off-screen because the centering effect only observes `currentTab`, not the new tab order. Include the tab list or `currentIndex` in the effect key so customization re-centers the selected tab.</violation>
</file>

<file name=".planning/sketches/001-store-tab-customization/index.html">

<violation number="1" location=".planning/sketches/001-store-tab-customization/index.html:101">
P1: On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@media (max-width: 700px) {
.game-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }
.library-head, .tab-shell, .game-grid { padding-left: 14px; padding-right: 14px; }
.store-row { grid-template-columns: 25px 1fr auto; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .planning/sketches/001-store-tab-customization/index.html, line 101:

<comment>On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.</comment>

<file context>
@@ -0,0 +1,294 @@
+    @media (max-width: 700px) {
+      .game-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }
+      .library-head, .tab-shell, .game-grid { padding-left: 14px; padding-right: 14px; }
+      .store-row { grid-template-columns: 25px 1fr auto; }
+      .move-controls { display: none; }
+      .sheet { max-height: 90%; }
</file context>

@Composable
fun LibraryTabBar(
currentTab: LibraryTab,
tabs: List<LibraryTab>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 currentTab, not the new tab order. Include the tab list or currentIndex in the effect key so customization re-centers the selected tab.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt, line 75:

<comment>Reordering the selected tab can leave it off-screen because the centering effect only observes `currentTab`, not the new tab order. Include the tab list or `currentIndex` in the effect key so customization re-centers the selected tab.</comment>

<file context>
@@ -71,12 +72,14 @@ import app.gamenative.ui.util.rememberWindowWidthClass
 @Composable
 fun LibraryTabBar(
     currentTab: LibraryTab,
+    tabs: List<LibraryTab>,
     tabCounts: Map<LibraryTab, Int>,
     onTabSelected: (LibraryTab) -> Unit,
</file context>

@jmarti326
jmarti326 force-pushed the jmarti326-customizable-store-tabs branch from 4718906 to 735a15d Compare August 8, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant