Skip to content

Cache per-item color schemes in ListState to avoid list jank - #27

Open
patrickunterwegs wants to merge 2 commits into
mainfrom
claude/spectacled-code-review-76ssli
Open

Cache per-item color schemes in ListState to avoid list jank#27
patrickunterwegs wants to merge 2 commits into
mainfrom
claude/spectacled-code-review-76ssli

Conversation

@patrickunterwegs

@patrickunterwegs patrickunterwegs commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

Superseded the original approach in this PR (a rememberColorSchemeResolver() Compose helper threaded through 3 screens) with a simpler design that keeps the cache in ListState instead:

  • ListState gains a colorSchemes: Map<Color, ColorScheme> field, plus the resolved theme inputs it's derived from (themeOption, themePaletteStyle, themeAmoled, isSystemDark).
  • ListState.recomputeColorSchemes() rebuilds that map from the distinct seed colors currently in icalEntries — kept deliberately separate from recompute() (filtering/sorting/grouping), since theme changes don't need to redo those, and vice versa.
  • ListViewModel calls recomputeColorSchemes() whenever icalEntries changes, and independently observes UserAppPreferencesStore's theme flows (palette style, AMOLED, theme option) the same way it already observes colors/categories — no new DI needed there.
  • The one piece that can't live in the ViewModel: system dark-mode is only observable via the @Composable isSystemInDarkTheme(). ListScreenRoot — the single shared root for all three list variants (Notes/Tasks/Journals) — bridges it in with one LaunchedEffect dispatching a new OnSystemDarkThemeChanged action.
  • ListItem/TaskListItem and the three list screens now do a plain state.colorSchemes[color] ?: MaterialTheme.colorScheme map lookup instead of calling a remembered resolver function.
  • Color.kt reverts to its original, single-purpose form — rememberColorSchemeResolver() and its supporting helper are removed entirely.

No background/async precomputation — dynamicColorScheme is a plain, cheap, pure function over a small number of distinct colors, so rebuilding the map synchronously inside the existing state-update flow is enough.

Test plan

  • Build all targets locally (as before, this sandbox's Gradle/JDK toolchain download is blocked, so this has only been verified by manual source inspection, not a real compile)
  • Run the Notes/Tasks/Journals list screens with several colored entries; confirm colors render correctly and scrolling/dragging is smooth
  • Toggle theme settings (dark mode, palette style, AMOLED) while a list screen is open and confirm colorSchemes updates correctly
  • Specifically test with the theme option set to "follow system" and toggle the OS dark mode setting while the app is open, to confirm the isSystemInDarkTheme() bridge works

claude added 2 commits July 8, 2026 19:02
getColorSchemeForSeedColor re-read theme preferences and regenerated a
full dynamicColorScheme palette on every ListItem/TaskListItem, on
every recomposition - expensive work repeated per row in a lazy list.

Add rememberColorSchemeResolver(), which reads preferences once and
memoizes the generated ColorScheme per distinct seed color, rebuilding
only when the resolved theme inputs actually change. Wire it into the
three list screens (Notes, Tasks, Journals); ListItem/TaskListItem
gain an optional colorScheme parameter (default null, preserving
existing behavior for every other caller, including previews and
DetailsScreen's subtask list).
Replaces the rememberColorSchemeResolver()-based approach with a
simpler design: ListState now holds a precomputed colorSchemes map,
rebuilt in ListViewModel whenever the entry list or resolved theme
inputs change, via the new recomputeColorSchemes() (kept separate
from recompute(), which doesn't depend on theme).

System dark mode can't be observed outside Compose, so ListScreenRoot
- the one shared root for all three list variants - bridges it in via
a single LaunchedEffect dispatching OnSystemDarkThemeChanged; the
other theme prefs (palette style, AMOLED) are read directly from
UserAppPreferencesStore's existing flows, same pattern already used
for observeColors()/observeCategories().

ListItem/TaskListItem/the three list screens now do a plain
colorSchemes[color] map lookup instead of calling a remembered
resolver function. Color.kt reverts to its original, single-purpose
form.
@patrickunterwegs patrickunterwegs changed the title Memoize per-item color scheme generation to avoid list jank Cache per-item color schemes in ListState to avoid list jank Jul 8, 2026
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.

2 participants