The shared shell was only mounted while on Home/Search/Discover/
Calendar; navigating to any other native screen (Detail, Sources,
Player, Settings, AddonStore) disposed it entirely, so returning to
any of those four tabs remounted from scratch — fresh stores, fresh
StateFlow defaults (isLoading=true, empty items), and a real re-fetch,
even though the underlying HomeViewModel data was already cached. That
produced the "blank calendar/grid when coming back from another page"
symptom.
FluxaAppHost (mobile) is now always composed as a background layer
behind AppRoutesHost's native AnimatedContent, which only draws on top
(opaque) when the active screen isn't one of those four. The shell
itself is never disposed by navigating elsewhere, so its stores keep
their last-known state and returning to a tab is instant.
Also replaced Discover's full-screen blocking spinner on first load
with a skeleton grid (18 placeholder cards) so the filter row stays
visible and the transition to real results doesn't hide the whole
screen behind a spinner.
MaterialTheme { ... } never set a colorScheme, so every unstyled
Material3 component (OutlinedTextField borders/labels, DropdownMenu
surface/text) rendered with the stock light scheme against the app's
black background instead of matching FluxaColors — the actual cause
of the dropdown/search-bar UI looking broken. Added a dark ColorScheme
built from FluxaColors so every shared screen inherits consistent
theming without per-component overrides.
Calendar was still list-only; rebuilt it as an actual numbered month
grid: a localized weekday header, day cells with leading/trailing
blanks so weeks align (pure-Kotlin Sakamoto's algorithm for the first
weekday and a leap-year-aware days-in-month table, no new platform
expect/actual needed), a dot on days with releases, and a selected-day
detail panel below the grid — matching native's grid+tap-to-see-detail
behavior instead of a flat scrollable date-grouped list.
AppRoutesHost was mounting a fresh FluxaAppHost per Screen navigation,
so every tab switch among Home/Search/Discover/Calendar recreated
CatalogHomeStore and re-triggered CatalogAction.Refresh, which force-
reloads the whole Home catalog over the network regardless of which
tab was actually opened. Hoist the shared shell out of AnimatedContent
so it stays mounted across those four screens and only remounts when
leaving them entirely (Detail/Sources/Player/Settings/etc., same as
before).
Discover regressions from the last pass, now fixed to match native:
- Filter chips replaced with dropdown menus (DropdownMenu/MenuItem)
matching native's Type/Catalog/Genre picker pattern.
- Grid restored to a fixed 3-column layout instead of adaptive sizing.
- Added the inline search box native's Explore screen has, reusing
SearchResultRows/SearchResults (now internal, not private) so typing
in Discover shows grouped search results instead of the filtered
browse grid, same as native.
Wires Screen.Search/Explore/Calendar to FluxaAppHost on mobile, same
pattern as Home. TV keeps using the existing native screens untouched.
To preserve native feature parity in the shared UI:
- Search: per-addon grouped result rows (was a flat grid), a clear
history action, and a query-specific "no results for X" message with
a recent-history fallback, matching native's MobileSearchScreen.
- Discover: filter chips are now a scrollable LazyRow instead of a
plain Row, avoiding the same overflow-wrap bug fixed on Home's row
headers when there are many genre options.
- Calendar: added month navigation (prev/next + localized month
title), day-grouped list headers via the existing cross-platform
localizedShortMonthDay/localizedMonthTitle helpers, replacing the
flat undated list.
Deliberately not replicated (documented simplification, not a bug):
native's Search and Explore screens each embed a blended search+browse
UX (explore filters shown pre-typing in Search; inline search box in
Explore). Shared keeps Search and Discover as clean separate screens
since both capabilities are still fully reachable via their own tabs.
Also skipped: Explore's list/grid view toggle, swipe-to-change-month
and the day-grid calendar view (list view covers the same data).
Row title now truncates with weight+ellipsis instead of squeezing "View
All" into a vertical letter stack. LazyColumn reserves 120dp bottom
padding to clear the floating mobile bottom nav bar (matching the
native HomeScreen constant). Catalog row titles use
displayHomeCategoryTitle() instead of the addon-qualified
semanticName, matching what native screens show.
FluxaHomeContent gains a real hero: a looping HorizontalPager (virtual
page count wraparound) over the first row's top items, dot indicators,
5s auto-advance, and a distinct "home-hero:" cache key so it decodes a
fresh full-size bitmap instead of reusing the small poster card's
cached one. CatalogItemUiModel gains a backdropUrl so the hero can use
Meta.homeHeroBackdrop() (the same high-res backdrop native hero UI
uses) instead of stretching the small vertical poster artwork.
Renders FluxaAppHost for Screen.Home on DeviceType.Mobile using the
already-built AndroidFluxaPlatformServices bridge, replacing the native
HomeRoute/MobileHomeHero path for that screen. FluxaApp/FluxaAppHost
gain showNavigationBar and external destination params so Android can
drive navigation from its own bottom bar instead of the shared shell's
internal one, and an onPlayRequested hook lets Detail's Play action
hand off to the native Sources/Player flow. Android TV is untouched.