[MBL-19896][Student] Fix edge-to-edge Snackbar gap and offline indicator positioning#3630
Conversation
…tor positioning - Remove duplicate setOnApplyWindowInsetsListener on bottomBarContainer (the second listener was silently overwriting the first, killing offline indicator margin logic) - Use height extension (56dp + navigationBars.bottom) in both portrait and landscape instead of bottomMargin, so the bottom bar draws behind the nav bar without a gap - Always consume navigationBars.bottom in the fullscreen listener since bottomBarContainer already handles that space, preventing double-counting in child views - Add listener on fullScreenCoordinatorLayout to strip bottom insets before the CoordinatorLayout stores them, so Snackbars don't add extra bottom margin regardless of whether the offline indicator is visible Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
🧪 Unit Test Results✅ 📱 Student App
✅ 🌅 Horizon
✅ 📦 Submodules
📊 Summary
Last updated: Wed, 15 Apr 2026 08:45:17 GMT |
📊 Code Coverage Report
|
… position The fullScreenCoordinatorLayout listener was stripping navigationBars.bottom, but LinearLayout propagates each child's returned insets to the next sibling, so offlineIndicator and bottomBarContainer also received bottom=0. This caused bottomBarContainer's listener to set offlineIndicator.bottomMargin=0, leaving it behind the system navigation bar in screens like CourseBrowserFragment. Instead, fix the Snackbar margin surgically in EditDashboardFragment by consuming insets on the Snackbar view itself — the CoordinatorLayout is already positioned above the bottomBarContainer so no extra navigationBars.bottom margin is needed. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
-
When I’m in offline mode and open any search field, a large gap appears above the keyboard. On the Assignment page, this gap remains even after closing the keyboard. (See 1. attached video.)
-
+1 Teacher app: In portrait mode, the bottom buttons completely overlap the toast message text. (See 2. attached video.)
3852.mp4
Screen_Recording_20260409_153601_Canvas.Teacher.mp4
…es Snackbar regression - Fix keyboard gap with 3-button navigation: siblings of fullScreenCoordinatorLayout receive raw system insets independently, so bottomBarContainer always sets offlineIndicator.bottomMargin = navBar.bottom when the bottom bar is hidden. Mirror that margin in heightBelowContent so the adjusted IME inset correctly accounts for the offline indicator's nav-bar clearance space. - Replace hardcoded 56.toPx with view.minimumHeight derived from XML minHeight so the bottom bar content height is not a magic number in code. - Fix All Courses Snackbar appearing behind nav buttons in Teacher app: move the inset-consuming logic into StudentEditDashboardRouter.showSnackbar() via the EditDashboardRouter interface default method, keeping the Student-specific fix isolated from the shared EditDashboardFragment. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
PR Review — Update Analysis
This PR fixes a cluster of edge-to-edge window-inset bugs in the Student app: a blank gap above the keyboard when the IME opens, incorrect bottom-bar sizing in both portrait and landscape, and a double-counted nav-bar margin on the Edit Dashboard snackbar.
What changed in this update
| Area | Change |
|---|---|
NavigationActivity |
Adds IME-inset adjustment (adjustedImeBottom) so child screens subtract the height of views below the content area before padding for the keyboard. Also switches bottom-bar sizing from bottomMargin to explicit height, and removes the now-redundant bottomBarContainer insets listener. |
activity_navigation.xml |
Adds android:minHeight="56dp" so minimumHeight is reliable at runtime. |
StudentEditDashboardRouter |
Overrides showSnackbar to block nav-bar insets from propagating to the Snackbar view when edge-to-edge is enforced. |
EditDashboardRouter (interface) |
Promotes snackbar creation to a default interface method so Teacher/Parent apps get sensible baseline behaviour without changes. |
EditDashboardFragment |
Delegates to editDashboardRouter.showSnackbar(); removes inline Snackbar.make. |
Overall assessment
The approach is sound. The inset arithmetic is well-commented and handles the known edge cases (offline indicator visible/hidden, bottom bar visible/hidden, landscape vs portrait). Three focused comments are raised below covering a layout-timing assumption, an overly broad inset consumption, and an implicit contract in the interface default.
| val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) | ||
| val expectedBottomBarHeight = if (bottomBarContainer.isVisible) (bottomBarContainer.minimumHeight + navigationBars.bottom) else 0 | ||
| val expectedOfflineMargin = if (bottomBarContainer.isVisible) 0 else navigationBars.bottom |
There was a problem hiding this comment.
Potential layout-timing issue with view heights
bottomBarDivider.height and offlineIndicator.root.height could both return 0 if the insets listener fires before these views have been measured and laid out.
In practice, IME insets only arrive when the keyboard opens (after a view gets focus), so the layout should be complete by then — but this is fragile. If the offline indicator becomes visible at the same time the keyboard opens (e.g., network lost while typing), its height might not yet reflect the final value.
Consider defensively using minimumHeight (already set in XML for bottomBarContainer) or requesting a re-dispatch after layout:
// Safer alternative for offlineIndicator height:
val offlineHeight = if (offlineIndicator.root.isVisible)
maxOf(offlineIndicator.root.height, offlineIndicator.root.minimumHeight) + expectedOfflineMargin
else 0Or at minimum, add a comment noting the assumption that the IME only appears post-layout.
| // In Student app the Snackbar anchors to fullScreenCoordinatorLayout, which is already | ||
| // positioned above the bottom bar. Consuming the navigation bar insets prevents the | ||
| // Snackbar from adding an extra bottom margin that would double-count the nav bar space. | ||
| ViewCompat.setOnApplyWindowInsetsListener(snackbar.view) { _, _ -> WindowInsetsCompat.CONSUMED } |
There was a problem hiding this comment.
WindowInsetsCompat.CONSUMED blocks all inset types, not just navigation bars
Returning CONSUMED intercepts every inset type (display cutout, system gesture exclusions, tappable element insets, IME, etc.), not only the navigation bar inset. This is more aggressive than the comment implies and could prevent the Snackbar from correctly handling future inset types added by Material or AndroidX.
A more surgical fix would be to zero out only the navigation bar portion:
ViewCompat.setOnApplyWindowInsetsListener(snackbar.view) { _, insets ->
val types = WindowInsetsCompat.Type.navigationBars()
WindowInsetsCompat.Builder(insets)
.setInsets(types, Insets.NONE)
.build()
}This keeps other inset types intact while still preventing the double-counted nav-bar margin.
| interface EditDashboardRouter { | ||
| fun routeCourse(canvasContext: CanvasContext?) | ||
|
|
||
| fun showSnackbar(fragment: Fragment, resId: Int) { |
There was a problem hiding this comment.
Interface default method introduces a Material library dependency into pandautils
The default showSnackbar implementation imports Snackbar directly into the router interface. Since pandautils already uses Material this is not a blocking concern, but it's worth noting that Teacher/Parent apps that implement EditDashboardRouter without overriding this method will silently inherit the Material-anchored behaviour — including any future edge-to-edge quirks — unless they also override it.
Consider documenting this contract, or making the default a no-op and requiring each app to opt in:
fun showSnackbar(fragment: Fragment, resId: Int) { /* apps must override */ }This would make it explicit that each consuming app is responsible for its own snackbar positioning, which aligns with the pattern used for routeCourse.
Test plan:
refs: MBL-19896
affects: Student
release note: Fixed a gap appearing between the Snackbar and the Android navigation bar on the All Courses page in offline mode, and fixed the offline indicator being hidden behind the navigation bar on secondary screens.