Skip to content

Scroll-reactive bottom navigation — hybrid collapse with Reactive / E… - #354

Merged
ryanbr merged 3 commits into
ryanbr:prototype-scroll-navbarfrom
Quaii:prototype-scroll-navbar
Jul 13, 2026
Merged

ryanbr merged 3 commits into
ryanbr:prototype-scroll-navbarfrom
Quaii:prototype-scroll-navbar

Conversation

@Quaii

@Quaii Quaii commented Jul 13, 2026

Copy link
Copy Markdown

PR description — here's a filled-out version based on what we verified:


What this PR does

Implements the scroll-reactive bottom navigation on iOS and Android with a hybrid precompression + threshold-settle collapse (instead of the earlier immediate-collapse approach). Adds a user-configurable behavior setting — Reactive, Expanded, or Compact — in Settings. Also adds StrandMotion.navigationBarCollapse / navigationBarExpand animation tokens to the design system.

Type of change

  • New feature

How it was tested

UI-only change; no BLE path touched. Tested visually on device — scroll-down triggers precompression then settles to compact, scroll-up expands immediately. Behavior setting updates the bar live from Settings. Reduce Motion snaps rather than interpolates.

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/)
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest)
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

Part of #86


Quaii and others added 2 commits July 13, 2026 02:21
…xpanded / Compact setting (#86)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ryanbr

ryanbr commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Thanks for this — it's a careful implementation and the cross-platform parity is genuinely well done. One blocker and one thing to verify before it can land.

🔴 Blocker: the Android side doesn't compile

AppRoot.kt:335 references NestedScrollSource.UserInput, which doesn't exist in this project's Compose version. app/build.gradle.kts pins Compose BOM 2024.06.00, and NestedScrollSource.UserInput wasn't introduced until Compose 1.7 (BOM 2024.09+). This version only has .Drag / .Fling.

Fix — use the pre-1.7 equivalent for user-drag gestures:

// AppRoot.kt:335
if (delta > 0f && source == NestedScrollSource.Drag) {

I applied that locally and the whole module then compiled clean (compileFullDebugKotlin) and the unit suite passed — so it's the only Android compile error, a one-line change. This lines up with the checklist: ./gradlew testFullDebugUnitTest is unchecked, so the Android side hadn't been built yet. Worth running it before pushing the fix.

🟡 Please verify the Swift app-target compiles

The checklist covers swift test for the package you touched (StrandDesign / Motion tokens — that part is CI-covered and fine). But RootTabView.swift, ScreenScaffold.swift, SettingsView.swift, and LiquidTodayView.swift are app-target Swift, and no default CI compiles those (app-build.yml is disabled). A compile error there passes every green check.

I confirmed statically that the symbols resolve (NoopMotion.gated, NoopMetrics.space1/2/3/5/6/controlHeight, the new StrandMotion.navigationBar{Collapse,Expand}, and the #if os(iOS)-scoped env keys / PreferenceKey), and the #if os(iOS) gating looks right — but that's not a compile guarantee. Please build both app targets locally (xcodebuild … Strand for macOS and NOOPiOS for iOS, since ScreenScaffold/SettingsView/LiquidTodayView are shared and must compile for both), or I can kick off an app-build run on the branch.

🟢 What's already solid

  • Parity is correct despite a real trap — the two platforms name spacing tokens differently (Swift spaceN is a scale index → space6 = 24pt; Android spaceN is the dp value → space24 = 24dp). The mapping came out right: collapse threshold 24 = 24, noise floor 0.5 = 0.5, precompression 0.16 on both. The behavior matches.
  • Setting parity: same key noop.bottomBarBehavior, same reactive/expanded/compact values, same default, loaded on launch.
  • Design tokens: this actually improves compliance — hardcoded 2/7/8/22 spacing became NoopMetrics.space*, and StrandAlpha.navigationGlass / Metrics.tabBarClearance are named tokens.
  • Reduce Motion handled on both (snap vs interpolate, state preserved at the 0.5 threshold), and no BLE path is touched.

Minor: the new preference isn't in the .noopbak whitelist — fine for a device-local UI choice, just flagging it's intentionally not backed up.

Once the .Drag fix is in and both app targets build, this is good to go.

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>
@Quaii

Quaii commented Jul 13, 2026

Copy link
Copy Markdown
Author

Fixed: NestedScrollSource.UserInput → .Drag in AppRoot.kt:335 (commit 88e3a63).

All 3 Platforms compile fine :)

@tigercraft4

Copy link
Copy Markdown

Heads up: #90 (by the maintainer) is an open prototype for the same scroll-reactive bottom bar on #86 — Android-only, simpler immediate-collapse. This PR is broader (both platforms + configurable behavior + design tokens), so it may supersede #90, but flagging so they get reconciled rather than landing independently.

@ryanbr
ryanbr merged commit 6b1c1db into ryanbr:prototype-scroll-navbar Jul 13, 2026
1 of 4 checks passed
ryanbr added a commit that referenced this pull request Jul 13, 2026
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.
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.

3 participants