feat: Add wiring and analytics for bottom nav bar#21274
Conversation
208da28 to
781fde8
Compare
781fde8 to
5ccbb46
Compare
sanjaysargam
left a comment
There was a problem hiding this comment.
Correct the PR description, tests are not in this diff. If coming in the follow-up, say so explicitly.
criticalAY
left a comment
There was a problem hiding this comment.
Can the icons in bottom bar and the text always be visible? see how Playstore has it (the icons are animated there but we can ignore that for now)
Sure, I should be able to add text. will have all the fixes out tomorrow, I was making sure it works for the current card browser too |
d503153 to
3ac1f0b
Compare
david-allison
left a comment
There was a problem hiding this comment.
Looks great! Thanks so much!!
|
@ShaanNarendran, I noticed that the toolbar remains visible in CardBrowser, and there's a small visual glitch when navigating from CardBrowser to Statistics. Also, I think the status bar behavior should remain consistent across all top-level screens. |
dfb81c6 to
4cac40b
Compare
Could you pull the latest and see if this issue persists? Seems to work on my emulator |
291ebf3 to
1a2fb70
Compare
This one seems resolved But status bar consistency is still there in my case. Did you test on Light Theme? |
By status bar consistency do you mean what is on top in each destination? Since every destination has their own unique toolbars I'm using those, if we wanted it to be uniform I'd have to recreate the top bars for each destination. But I may be understanding the issue wrong so if you could clarify I'll test it out again |
36ae184 to
1ec0e7e
Compare
49b4e63 to
a4f62ed
Compare
criticalAY
left a comment
There was a problem hiding this comment.
Here is something that might help:
using show/hide to keep the fragments alive is the right call for state preservation The first-open lag is a side effect of that design rather than a bug, but worth a note for the follow-up.
soooo..
Each destination is created lazily on its first tap: showBottomNavFragment(...) only takes the add(...) path the first time, so the whole fragment lifecycle (onCreateView inflation + onViewCreated setup) runs on the main thread, you might want to explore more here but can be done later
Subject: [PATCH] bottom nav changes
---
Index: AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt (revision a4f62edda816b0217f907d1232c64d36a666d8f4)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt (date 1782502339704)
@@ -2246,7 +2246,13 @@
)
companion object {
- /** Material 3 BottomNavigationView standard height in dp */
+ /**
+ * Material 3 BottomNavigationView content height in dp, *excluding* the system
+ * navigation-bar inset (that inset is added separately here via `bars.bottom`).
+ * Used to offset the FAB above the bar. The hosted-fragment container instead uses
+ * the bar's measured height, which already includes the inset so the two must not
+ * be swapped for one another.
+ */
private const val BOTTOM_NAV_HEIGHT_DP = 80
/**
Index: AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt b/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt (revision a4f62edda816b0217f907d1232c64d36a666d8f4)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt (date 1782502243528)
@@ -10,6 +10,7 @@
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.bottomnavigation.BottomNavigationView
@@ -69,11 +70,7 @@
when (item) {
NavigationItem.HOME -> {
fragmentContainer.isVisible = false
- deckPicker.supportFragmentManager.commit {
- deckPicker.supportFragmentManager.fragments.forEach { frag ->
- if (frag.id == R.id.bottom_nav_fragment_container) hide(frag)
- }
- }
+ deckPicker.supportFragmentManager.commit { hideBottomNavFragments() }
contentWrapper.isVisible = true
deckPicker.floatingActionMenu.showFloatingActionButton()
backCallback.isEnabled = false
@@ -82,17 +79,17 @@
NavigationItem.BROWSER -> {
backCallback.isEnabled = true
ensureBrowserViewModel()
- showBottomNavFragment(CardBrowserFragment(), "browser", contentWrapper, fragmentContainer)
+ showBottomNavFragment(::CardBrowserFragment, item.tag, contentWrapper, fragmentContainer)
true
}
NavigationItem.STATS -> {
backCallback.isEnabled = true
- showBottomNavFragment(Statistics(), "stats", contentWrapper, fragmentContainer)
+ showBottomNavFragment(::Statistics, item.tag, contentWrapper, fragmentContainer)
true
}
NavigationItem.MORE -> {
backCallback.isEnabled = true
- showBottomNavFragment(MoreFragment(), "more", contentWrapper, fragmentContainer)
+ showBottomNavFragment(::MoreFragment, item.tag, contentWrapper, fragmentContainer)
true
}
}
@@ -114,7 +111,7 @@
context(deckPicker: DeckPicker)
private fun showBottomNavFragment(
- fragment: Fragment,
+ newFragment: () -> Fragment,
tag: String,
contentWrapper: View,
fragmentContainer: View,
@@ -128,15 +125,21 @@
fragmentContainer.layoutParams = lp
}
deckPicker.supportFragmentManager.commit {
- deckPicker.supportFragmentManager.fragments.forEach { frag ->
- if (frag.id == R.id.bottom_nav_fragment_container) hide(frag)
- }
+ hideBottomNavFragments()
val existing = deckPicker.supportFragmentManager.findFragmentByTag(tag)
if (existing != null) {
show(existing)
} else {
- add(R.id.bottom_nav_fragment_container, fragment, tag)
+ add(R.id.bottom_nav_fragment_container, newFragment(), tag)
}
}
fragmentContainer.isVisible = true
}
+
+/** Hides any fragments currently hosted in the bottom-nav container. */
+context(deckPicker: DeckPicker)
+private fun FragmentTransaction.hideBottomNavFragments() {
+ deckPicker.supportFragmentManager.fragments.forEach { fragment ->
+ if (fragment.id == R.id.bottom_nav_fragment_container) hide(fragment)
+ }
+}
Index: AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt b/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt (revision a4f62edda816b0217f907d1232c64d36a666d8f4)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt (date 1782502216202)
@@ -49,11 +49,13 @@
enum class NavigationItem(
@IdRes val id: Int,
+ /** Fragment tag used to find and reuse this destination's fragment across tab switches. */
+ val tag: String,
) {
- HOME(R.id.nav_home),
- BROWSER(R.id.nav_browser),
- STATS(R.id.nav_stats),
- MORE(R.id.nav_more),
+ HOME(R.id.nav_home, "home"),
+ BROWSER(R.id.nav_browser, "browser"),
+ STATS(R.id.nav_stats, "stats"),
+ MORE(R.id.nav_more, "more"),
;
companion object {
you may need to remove tests from PR description |
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content"> | ||
| android:layout_height="wrap_content" | ||
| android:background="?attr/colorSurface" |
There was a problem hiding this comment.
SearchBar child likely sets its own surface color. Worth confirming there's no double background flash on low-end devices, but this is likely fine
There was a problem hiding this comment.
I haven't seen any flashes on a pixel 6 emulator, any specific model that may encounter this?
|
Done! |
6d2df48 to
47bd97d
Compare
47bd97d to
22dfec6
Compare
Forces the Card Browser to use the self-contained SearchView layout whenever it is hosted within the DeckPicker's bottom navigation, as the legacy layout's MenuProvider bleeds into the host activity's toolbar.
Integrates the bottom navigation bar into the DeckPicker using the `HomeScreenNavigation` controller when the developer option is enabled. Uses a show/hide fragment strategy for preserving tab state and hides the back button in child fragments as appropriate.
Added `fitsSystemWindows=true` alongside `appBarColor` and `colorSurface` to the app bars of the More, Card Browser SearchView, and Statistics destinations to ensure they render edge-to-edge correctly underneath the transparent status bar without overlapping system icons.
22dfec6 to
f487648
Compare
Note
Assisted by Opus 4.6 for general research.
Purpose / Description
This PR adds wiring to the bottom nav bar instantiated in the previous PRs (#21186). It also adds analytics to the More fragment.
Fixes
Approach
The main changes lie in DeckPicker.kt where setupBottomNav() method shows the bar, disables the drawer swipe, and handles tab selection. Each tab hides the deck list content and shows the corresponding fragment using show/hide so we can have state preservation. The CardBrowserFragment's ViewModel is lazily created the first time that the browser tab is tapped since otherwise the app would crash. There were some padding changed to be done to the fab to account for the nav bar height. Minor bugs will be fixed in a follow-up refinement PR along with removing the nav drawer altogether, I've kept it usable for now as a fallback but that will be removed.
How Has This Been Tested?
Compiles successfully, all the features work as intended.
Screen_recording_20260612_120332.webm
Checklist
Please, go through these checks before submitting the PR.