Repository navigation
Conversation
… on up (#86) Instagram-style: the floating GlassBottomBar subtly recedes (shrink + fade + slide toward the edge) as you scroll DOWN into content and returns when you scroll UP, so the app feels more dynamic without hiding the tabs. - One NestedScrollConnection on the NavHost drives it, so every screen gets the behaviour with zero per-screen wiring. onPreScroll only flips a Boolean on a direction change; a 220ms tween animates a single `collapse` float. - The bar applies `collapse` as a graphicsLayer transform ONLY (scale ~0.88, alpha to 0.6, translateY 22dp, bottom-centre pivot). No relayout — the Scaffold keeps the bar's reserved space — so scrolling stays smooth (GPU transform per frame). - Reduce Motion keeps the bar fully shown (no animation). Subtle by design; the transform constants are the tuning knobs. Android only for now. Android compiles locally.
…e off (#86) Feedback: the collapsed bar slid too far down (into the gesture-nav area) and didn't read as transparent. Retuned: fade toward see-through (alpha to ~0.35), gentler shrink (0.92), and only a hair of drift (22dp -> 6dp) so it recedes IN PLACE rather than falling off the bottom. Android compiles.
…tabs (#86) Feedback (correctly, twice): the pill's dark translucent fill read as a solid "bar" behind the tabs and blocked the content. Drop the fill entirely — the pill is now just the hairline capsule outline + tab icons floating over the content, which shows straight through. Also removed the drop shadow (also read as a slab). Android compiles.
…" band (#86) The full-width band behind the pill was the Scaffold's RESERVED bottomBar region (filled with the flat app background), sitting below the tinted content. Moved the bar out of the bottomBar slot and overlay it (Box + align bottom-centre) on full-bleed content, so the content's background fills continuously behind the now-transparent pill — no band. NestedScroll moves to the full-bleed NavHost. Note: content now scrolls UNDER the floating transparent pill (iOS-style); screens keep their 28dp bottom padding, so the last item can sit partly behind the bar at the very bottom of a scroll — a bottom-padding bump is the follow-up if that bites. Android compiles.
tigercraft4
left a comment
There was a problem hiding this comment.
Prototype feedback (non-blocking) — the graphicsLayer-only recede is the right perf call and Reduce Motion is respected. Two things that would bite before this leaves prototype, plus a jitter nit.
| // #86: NO fill. The dark translucent fill read as a solid "bar" behind the tabs; the content | ||
| // should show straight through. Keep the pill SHAPE via the hairline outline only (a floating | ||
| // capsule of tabs over the content), no fill and no drop shadow (which also read as a slab). | ||
| color = Color.Transparent, |
There was a problem hiding this comment.
Legibility. Dropping the fill to Color.Transparent + removing the shadow means the tab icons/labels now sit directly over arbitrary content (charts, bright cards) with only a hairline outline behind them. On busy/light backgrounds the tabs can wash out. The iOS FloatingTabBar keeps a translucent material for exactly this — suggest keeping a light scrim/blur (or a subtle fill) rather than fully transparent.
| modifier = Modifier.padding(inner), | ||
| // #86: full-bleed so content (and its background) fills behind the floating bar. The scroll | ||
| // direction is observed here (one place → every screen) to drive the bar recede. | ||
| modifier = Modifier.fillMaxSize().nestedScroll(barNestedScroll), |
There was a problem hiding this comment.
The bar is no longer a reserved bottomBar slot, so content now scrolls the full height with the pill floating over it. Unless each screen adds bottom content padding (≈ pill height + inset), the last list item can be hidden behind the pill. Worth threading a contentPadding through the screens (or a shared bottom spacer).
| override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { | ||
| if (!reduceMotion) { | ||
| // available.y < 0 = content moving up (finger swipes up, reading down) → collapse. | ||
| if (available.y < -3f) barCollapsed = true |
There was a problem hiding this comment.
nit: toggling on raw available.y at a ±3px threshold is jittery — a tiny scroll or fling settle can flip the bar. Consider a small accumulated-delta threshold or debouncing the collapse/expand transition.
#354) * Scroll-reactive bottom navigation — hybrid collapse with Reactive / Expanded / Compact setting (#86) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: preserve bar collapse state across tab/route switches (#86) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix NestedScrollSource.UserInput → Drag for Compose BOM 2024.06 (#86) UserInput was added in Compose 1.7 (BOM 2024.09+); Drag is the pre-1.7 equivalent for active user-gesture scrolls and compiles cleanly against the current BOM 2024.06.00. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract the two pure decisions from AppRoot's inline collapse composition into testable top-level functions — isBarEffectivelyCollapsed() and resolveBarCollapse() — and add BottomBarBehaviorTest. Behavior-preserving: the composable now calls the extracted functions instead of the inline if/else chain. Covers storage-string resolution (unknown/null -> REACTIVE, round-trip), the COMPACT-always / EXPANDED-never / REACTIVE-follows-flag collapse decision, and the fraction composition (Reduce Motion snaps to 0/1, non-reactive rides the settle, reactive-collapsed takes max(precompression, settle), reactive-expanding follows precompression then settle). 9 tests, testFullDebugUnitTest green. Follows up #354: that PR left the Android-unit-tests box unchecked; this is the test gate before the feature promotes from prototype-scroll-navbar to main.
Settings > Appearance > Hide bar when scrolling. Default off, and unavailable unless the overlay bar is on - in the slot layout the Scaffold has reserved the bar's space, so hiding it would leave an empty band rather than give the space back. One NestedScrollConnection on the shell, so every screen inherits the behaviour with no per-screen wiring. The bar moves by graphicsLayer only - GPU, no relayout - and the animating float is read inside the layer block rather than in composition, so the shell is not recomposed sixty times a second while the bar moves. Presence is a derivedStateOf, so the bar leaves the tree once hidden and cannot be focused by TalkBack or catch a tap it should not. Reduce Motion keeps the bar visible rather than snapping it away, and the toggle greys out to say so instead of accepting a tap it cannot act on. A route change resets it, so a screen never opens with its navigation hidden. The shape is from the #90 prototype - one connection, transform-only, decision in tested plain functions. Raised on #1839.
|
Retiring this: the prototype shipped. Verified directly rather than from the audit in #2059, whose evidence line for this one happened to |
Prototype for #86 (Quaii) — a subtle, scroll-reactive bottom bar like Instagram's.
Behaviour
The floating
GlassBottomBarrecedes (a subtle shrink + fade + slide toward the bottom edge) as you scroll down into content, and returns when you scroll up — so the app feels more dynamic without hiding the tabs. Kept partial by design (scale ~0.88, alpha to 0.6, translateY 22dp) so the tabs stay reachable — "subtle but noticeable," per the request.How it's wired (built for smooth scroll)
NestedScrollConnectionon theNavHostdrives it — every screen gets the behaviour with no per-screen wiring (scrollable children dispatch their deltas up to it).onPreScrollonly flips a Boolean on a direction change; a 220ms tween animates a singlecollapsefloat.collapseas agraphicsLayertransform only (GPU, per-frame) — no relayout, because theScaffoldkeeps the bar's reserved space. So the content doesn't reflow and scrolling stays smooth.Scope / testing
Surface, easy + low-risk). If the feel lands, I'll port to the iOS floating two-island bar + FAB.compileFullDebugKotlin, exit 0).translationY/alpha — tell me which feel matches the video.File:
android/app/src/main/java/com/noop/ui/AppRoot.kt.