Android: Configuration Tab #272

Closed
opened 2026-10-16 06:15:59 +02:00 by kfickel · 2 comments
Owner

The configuration tab became really full and too long to handle for larger files.

Can you split up the different types (like Dimension, Tag, FileType, Command) into tabs in the Configuration screen?

And while you're at it, can you sort the Tags alphabetically, and add a scroll bar with the name (like with the title on the stream screen)?

The configuration tab became really full and too long to handle for larger files. Can you split up the different types (like Dimension, Tag, FileType, Command) into tabs in the Configuration screen? And while you're at it, can you sort the Tags alphabetically, and add a scroll bar with the name (like with the title on the stream screen)?
Collaborator

Plan

Android only (android/app). The core and the bindings are not touched: the screen already gets everything it needs from ConfigOutline.

Decisions (clarified)

  • Five tabs: Dimensions · Tags · File types · Commands · General. They use a PrimaryScrollableTabRow and a HorizontalPager, the same pattern as StateListScreen (StateList.kt).
  • Search filters every tab. While a query is set, each tab label shows its match count (e.g. Tags 3) so you can see where the hits are.
  • Tags and file types are sorted alphabetically (case-insensitive, with a case-sensitive tiebreak so the order is stable). Both tabs get the FastScroller, and its label is the name of the first visible entry. Dimensions and commands keep the order of the file.

1. Pure logic: ui/ConfigList.kt

  • configSections(outline, query): sort tags and fileTypes with compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }.thenBy { it.name }, and fix its KDoc ("each part in the order of the file" no longer holds for these two).
  • Add enum class ConfigTab { DIMENSIONS, TAGS, FILE_TYPES, COMMANDS, GENERAL } and ConfigSections.count(tab), which gives the number of matches per tab. General counts 1 when there is no query and 0 when there is one, because the general settings are not searchable today.
  • Add entryLabel(...), a pure helper for the scroller: the name of the entry at a lazy-list index, given how many leading rows come before it (header, unplaced chips). It is written like dayAt in FastScroll.kt. For the File types tab the label is _name.
  • Tests in ConfigListTest.kt:
    • tags and file types come out sorted (mixed case, e.g. apple, Banana, cherry) and dimensions and commands keep their order;
    • the counts per tab, with and without a query;
    • entryLabel gives the right name at an index, including indices on the leading rows.

2. Screen: ui/Config.kt

  • ConfigContent keeps the app bar (pin, edit as text, search) and the SearchField. Below them come the issues banner, which stays above the tabs for all tabs, then the tab row and the pager. The unreadable case (outline == null) and Configured.Failed still show only the banner, without tabs.
  • Keep the selected tab with rememberSaveable in ConfigScreen and pass it into ConfigContent as tab / onTab. The content stays free of effects so the screenshot tests can render it. The pager syncs with the tab row the same way as in StateList.kt.
  • Each page is its own LazyColumn:
    • It starts with the Section header and its add button. The Commands page keeps the dropdown of command types, and the General page has no add button.
    • Each entry becomes its own lazy item. Today a whole section is a single item in one OutlinedCard, and the scroller needs one row per entry. Keep the card look by giving each row the shape of its place in the group (top corners on the first row, bottom corners on the last, dividers in between). Alternatively, drop the card on these pages and use plain ListItems with dividers. Pick whichever looks right in the screenshots.
    • The unplaced tags (FlowRow of TagChips) move to the top of the Tags page, shown only when there is no query.
    • When a page has no match for the query, it shows the config_no_match text.
    • On the Tags and File types pages, a FastScroller(state, label = { entryLabel(...) }) sits over the list in a Box, as in Stream.kt.
  • Tab labels and counts go into res/values/strings.xml. Reuse config_dimensions, config_tags, config_file_types, config_commands and config_general where they fit.

3. Screenshot tests: screenshotTest/.../ConfigScreenshots.kt

  • ConfigScreenshot becomes one scene per tab: ConfigDimensionsTabScreenshot, ConfigTagsTabScreenshot (with unplaced tags), ConfigFileTypesTabScreenshot, ConfigCommandsTabScreenshot and ConfigGeneralTabScreenshot. Also add ConfigSearchScreenshot, which shows the counts in the tab row. Keep ConfigUnreadableScreenshot.
  • Add a fixture with a few tags in mixed case and file order so the sorting is visible. The scroller handle is hidden in a still frame, which is fine.
  • Run updateDebugScreenshotTest, look at every changed image and commit them. Delete the old reference of ConfigScreenshot.

4. Docs

  • AGENTS.md: update the Config.kt and ConfigList.kt entries in the Android section (tabs, sorted tags and file types, FastScroller now also used by the configuration, ConfigTab, entryLabel). Update the FastScroller.kt line ("the Stream tab uses it"), and the screenshot list if needed.
  • README.md: in the app section on the configuration screen, describe the tabs, the sorting and the scroller.
  • REQUIREMENTS.md: update the requirement of the app's configuration screen (R35, its sub-item on the configuration list) to match.

Verification

  • nix build .#streamd-app, which runs the unit tests and validateDebugScreenshotTest.
  • nix flake check. Nothing outside android/ changes, but per AGENTS.md this runs anyway.
  • Commits: feat(android): … for the code and screenshots, docs: … for the docs.
## Plan Android only (`android/app`). The core and the bindings are not touched: the screen already gets everything it needs from `ConfigOutline`. ### Decisions (clarified) - **Five tabs**: Dimensions · Tags · File types · Commands · General. They use a `PrimaryScrollableTabRow` and a `HorizontalPager`, the same pattern as `StateListScreen` (`StateList.kt`). - **Search filters every tab.** While a query is set, each tab label shows its match count (e.g. `Tags 3`) so you can see where the hits are. - **Tags and file types are sorted alphabetically** (case-insensitive, with a case-sensitive tiebreak so the order is stable). Both tabs get the `FastScroller`, and its label is the **name** of the first visible entry. Dimensions and commands keep the order of the file. ### 1. Pure logic: `ui/ConfigList.kt` - `configSections(outline, query)`: sort `tags` and `fileTypes` with `compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }.thenBy { it.name }`, and fix its KDoc ("each part in the order of the file" no longer holds for these two). - Add `enum class ConfigTab { DIMENSIONS, TAGS, FILE_TYPES, COMMANDS, GENERAL }` and `ConfigSections.count(tab)`, which gives the number of matches per tab. General counts 1 when there is no query and 0 when there is one, because the general settings are not searchable today. - Add `entryLabel(...)`, a pure helper for the scroller: the name of the entry at a lazy-list index, given how many leading rows come before it (header, unplaced chips). It is written like `dayAt` in `FastScroll.kt`. For the File types tab the label is `_name`. - Tests in `ConfigListTest.kt`: - tags and file types come out sorted (mixed case, e.g. `apple`, `Banana`, `cherry`) and dimensions and commands keep their order; - the counts per tab, with and without a query; - `entryLabel` gives the right name at an index, including indices on the leading rows. ### 2. Screen: `ui/Config.kt` - `ConfigContent` keeps the app bar (pin, edit as text, search) and the `SearchField`. Below them come the issues banner, which stays above the tabs for all tabs, then the tab row and the pager. The unreadable case (`outline == null`) and `Configured.Failed` still show only the banner, without tabs. - Keep the selected tab with `rememberSaveable` in `ConfigScreen` and pass it into `ConfigContent` as `tab` / `onTab`. The content stays free of effects so the screenshot tests can render it. The pager syncs with the tab row the same way as in `StateList.kt`. - Each page is its own `LazyColumn`: - It starts with the `Section` header and its add button. The Commands page keeps the dropdown of command types, and the General page has no add button. - **Each entry becomes its own lazy item.** Today a whole section is a single item in one `OutlinedCard`, and the scroller needs one row per entry. Keep the card look by giving each row the shape of its place in the group (top corners on the first row, bottom corners on the last, dividers in between). Alternatively, drop the card on these pages and use plain `ListItem`s with dividers. Pick whichever looks right in the screenshots. - The **unplaced tags** (`FlowRow` of `TagChip`s) move to the top of the **Tags** page, shown only when there is no query. - When a page has no match for the query, it shows the `config_no_match` text. - On the Tags and File types pages, a `FastScroller(state, label = { entryLabel(...) })` sits over the list in a `Box`, as in `Stream.kt`. - Tab labels and counts go into `res/values/strings.xml`. Reuse `config_dimensions`, `config_tags`, `config_file_types`, `config_commands` and `config_general` where they fit. ### 3. Screenshot tests: `screenshotTest/.../ConfigScreenshots.kt` - `ConfigScreenshot` becomes one scene per tab: `ConfigDimensionsTabScreenshot`, `ConfigTagsTabScreenshot` (with unplaced tags), `ConfigFileTypesTabScreenshot`, `ConfigCommandsTabScreenshot` and `ConfigGeneralTabScreenshot`. Also add `ConfigSearchScreenshot`, which shows the counts in the tab row. Keep `ConfigUnreadableScreenshot`. - Add a fixture with a few tags in mixed case and file order so the sorting is visible. The scroller handle is hidden in a still frame, which is fine. - Run `updateDebugScreenshotTest`, look at every changed image and commit them. Delete the old reference of `ConfigScreenshot`. ### 4. Docs - `AGENTS.md`: update the `Config.kt` and `ConfigList.kt` entries in the Android section (tabs, sorted tags and file types, `FastScroller` now also used by the configuration, `ConfigTab`, `entryLabel`). Update the `FastScroller.kt` line ("the Stream tab uses it"), and the screenshot list if needed. - `README.md`: in the app section on the configuration screen, describe the tabs, the sorting and the scroller. - `REQUIREMENTS.md`: update the requirement of the app's configuration screen (R35, its sub-item on the configuration list) to match. ### Verification - `nix build .#streamd-app`, which runs the unit tests and `validateDebugScreenshotTest`. - `nix flake check`. Nothing outside `android/` changes, but per AGENTS.md this runs anyway. - Commits: `feat(android): …` for the code and screenshots, `docs: …` for the docs.
Collaborator

Implementation note

Implemented in PR #273 (branch 272_config-tabs).

  • Time: about 21 minutes (06:18 to 06:39 CEST), including the Android build, recording the screenshots and nix flake check.
  • Tokens: roughly 150k of context, estimated. The session was cut off once while verification was running, and that run was repeated.

Findings

  • The plan worked as written; neither the core nor the bindings needed a change.
  • The scroller needs one lazy row per entry, so the single OutlinedCard per section was replaced. Each entry is now its own row, grouped by shape: rounded at the ends of a group and slightly rounded between rows. In the screenshots this looks clean in light and dark.
  • entryLabel treats the header row (which holds the unplaced tags on the Tags tab) as a single leading row, so the label stays correct whether or not the unplaced tags are shown.
  • Entry keys in the lazy list are prefixed (entry:<name>). Without this, a tag named header or nothing would clash with the keys of the header or the empty-search row.
  • The General tab counts 1 without a search and 0 with one, since the general settings are not searched.
  • ConfigUnreadableScreenshot changed slightly because the banner is now padded on its own instead of sitting inside the list.
  • Verified with nix build .#streamd-app (unit tests and screenshot validation) and nix flake check; both pass.
## Implementation note Implemented in PR #273 (branch `272_config-tabs`). - **Time:** about 21 minutes (06:18 to 06:39 CEST), including the Android build, recording the screenshots and `nix flake check`. - **Tokens:** roughly 150k of context, estimated. The session was cut off once while verification was running, and that run was repeated. ### Findings - The plan worked as written; neither the core nor the bindings needed a change. - The scroller needs one lazy row per entry, so the single `OutlinedCard` per section was replaced. Each entry is now its own row, grouped by shape: rounded at the ends of a group and slightly rounded between rows. In the screenshots this looks clean in light and dark. - `entryLabel` treats the header row (which holds the unplaced tags on the Tags tab) as a single leading row, so the label stays correct whether or not the unplaced tags are shown. - Entry keys in the lazy list are prefixed (`entry:<name>`). Without this, a tag named `header` or `nothing` would clash with the keys of the header or the empty-search row. - The General tab counts 1 without a search and 0 with one, since the general settings are not searched. - `ConfigUnreadableScreenshot` changed slightly because the banner is now padded on its own instead of sitting inside the list. - Verified with `nix build .#streamd-app` (unit tests and screenshot validation) and `nix flake check`; both pass.
Sign in to join this conversation.
No labels
android
cli
epic
planned
tui
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
kfickel/streamd#272
No description provided.