Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit beb5c89

Browse files
committed
Update navigation views
This removes the DrawerLayout and replaces it with BottomNavigationView or NavigationRailView depending on screen width. Fix: 187299099 Change-Id: Iab8632aa3703e20118f9e4c849f5625d6dd85744
1 parent 10fc003 commit beb5c89

File tree

9 files changed

+273
-178
lines changed

9 files changed

+273
-178
lines changed

depconstraints/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ val junit = "4.13"
5454
val junitExt = "1.1.1"
5555
val lifecycle = "2.3.0"
5656
val lottie = "3.0.0"
57-
val material = "1.3.0"
57+
val material = "1.4.0-beta01"
5858
val mockito = "3.3.1"
5959
val mockitoKotlin = "1.5.0"
6060
val okhttp = "3.10.0"

mobile/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ android {
105105
}
106106

107107
buildFeatures {
108+
viewBinding = true
108109
dataBinding = true
109110
compose = true
110111
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/MainActivity.kt

Lines changed: 54 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,19 @@ import android.app.Activity
2020
import android.content.Intent
2121
import android.net.ConnectivityManager
2222
import android.os.Bundle
23-
import android.view.View
24-
import android.widget.FrameLayout
23+
import android.view.Menu
2524
import androidx.activity.viewModels
2625
import androidx.appcompat.app.AppCompatActivity
2726
import androidx.appcompat.widget.Toolbar
28-
import androidx.core.view.GravityCompat
29-
import androidx.core.view.updatePadding
30-
import androidx.drawerlayout.widget.DrawerLayout
3127
import androidx.lifecycle.Observer
3228
import androidx.navigation.NavController
3329
import androidx.navigation.fragment.NavHostFragment
3430
import androidx.navigation.ui.AppBarConfiguration
3531
import androidx.navigation.ui.setupWithNavController
36-
import androidx.recyclerview.widget.RecyclerView
3732
import com.firebase.ui.auth.IdpResponse
38-
import com.google.android.material.navigation.NavigationView
3933
import com.google.samples.apps.iosched.R
4034
import com.google.samples.apps.iosched.ar.ArActivity
41-
import com.google.samples.apps.iosched.databinding.NavigationHeaderBinding
35+
import com.google.samples.apps.iosched.databinding.ActivityMainBinding
4236
import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions
4337
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
4438
import com.google.samples.apps.iosched.shared.di.CodelabsEnabledFlag
@@ -50,14 +44,8 @@ import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
5044
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
5145
import com.google.samples.apps.iosched.ui.signin.SignOutDialogFragment
5246
import com.google.samples.apps.iosched.util.HeightTopWindowInsetsListener
53-
import com.google.samples.apps.iosched.util.NoopWindowInsetsListener
54-
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
55-
import com.google.samples.apps.iosched.util.navigationItemBackground
56-
import com.google.samples.apps.iosched.util.shouldCloseDrawerFromBackPress
5747
import com.google.samples.apps.iosched.util.signin.FirebaseAuthErrorCodeConverter
5848
import com.google.samples.apps.iosched.util.updateForTheme
59-
import com.google.samples.apps.iosched.widget.HashtagIoDecoration
60-
import com.google.samples.apps.iosched.widget.NavigationBarContentFrameLayout
6149
import dagger.hilt.android.AndroidEntryPoint
6250
import timber.log.Timber
6351
import java.util.UUID
@@ -112,15 +100,10 @@ class MainActivity : AppCompatActivity(), NavigationHost {
112100

113101
private val viewModel: MainActivityViewModel by viewModels()
114102

115-
private lateinit var content: FrameLayout
116-
private lateinit var drawer: DrawerLayout
117-
private lateinit var navigation: NavigationView
118-
private lateinit var navHeaderBinding: NavigationHeaderBinding
103+
private lateinit var binding: ActivityMainBinding
104+
119105
private lateinit var navController: NavController
120106
private lateinit var navHostFragment: NavHostFragment
121-
122-
private lateinit var statusScrim: View
123-
124107
private var currentNavId = NAV_ID_NONE
125108

126109
// For sending pinned sessions as JSON to the AR module
@@ -133,111 +116,36 @@ class MainActivity : AppCompatActivity(), NavigationHost {
133116
// Update for Dark Mode straight away
134117
updateForTheme(viewModel.currentTheme)
135118

136-
setContentView(R.layout.activity_main)
137-
138-
val drawerContainer: NavigationBarContentFrameLayout = findViewById(R.id.drawer_container)
139-
// Let's consume any
140-
drawerContainer.setOnApplyWindowInsetsListener { v, insets ->
141-
// Let the view draw it's navigation bar divider
142-
v.onApplyWindowInsets(insets)
143-
144-
// Consume any horizontal insets and pad all content in. There's not much we can do
145-
// with horizontal insets
146-
v.updatePadding(
147-
left = insets.systemWindowInsetLeft,
148-
right = insets.systemWindowInsetRight
149-
)
150-
insets.replaceSystemWindowInsets(
151-
0, insets.systemWindowInsetTop,
152-
0, insets.systemWindowInsetBottom
153-
)
154-
}
155-
156-
content = findViewById(R.id.content_container)
157-
content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
158-
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
159-
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
160-
// Make the content ViewGroup ignore insets so that it does not use the default padding
161-
content.setOnApplyWindowInsetsListener(NoopWindowInsetsListener)
162-
163-
statusScrim = findViewById(R.id.status_bar_scrim)
164-
statusScrim.setOnApplyWindowInsetsListener(HeightTopWindowInsetsListener)
119+
binding = ActivityMainBinding.inflate(layoutInflater)
120+
setContentView(binding.root)
165121

166-
drawer = findViewById(R.id.drawer)
167-
168-
navHeaderBinding = NavigationHeaderBinding.inflate(layoutInflater).apply {
169-
lifecycleOwner = this@MainActivity
170-
}
122+
binding.statusBarScrim.setOnApplyWindowInsetsListener(HeightTopWindowInsetsListener)
171123

172124
navHostFragment = supportFragmentManager
173125
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
174126

175127
navController = navHostFragment.navController
176128
navController.addOnDestinationChangedListener { _, destination, _ ->
177129
currentNavId = destination.id
178-
val isTopLevelDestination = TOP_LEVEL_DESTINATIONS.contains(destination.id)
179-
val lockMode = if (isTopLevelDestination) {
180-
DrawerLayout.LOCK_MODE_UNLOCKED
181-
} else {
182-
DrawerLayout.LOCK_MODE_LOCKED_CLOSED
183-
}
184-
drawer.setDrawerLockMode(lockMode)
130+
// TODO: hide nav if not a top-level destination?
185131
}
186132

187-
navigation = findViewById(R.id.navigation)
188-
navigation.apply {
189-
// Add the #io19 decoration
190-
val menuView = findViewById<RecyclerView>(R.id.design_navigation_view)?.apply {
191-
addItemDecoration(HashtagIoDecoration(context))
192-
}
193-
// Update the Navigation header view to pad itself down
194-
navHeaderBinding.root.doOnApplyWindowInsets { v, insets, padding ->
195-
v.updatePadding(top = padding.top + insets.systemWindowInsetTop)
196-
// NavigationView doesn't dispatch insets to the menu view, so pad the bottom here.
197-
menuView?.updatePadding(bottom = insets.systemWindowInsetBottom)
198-
}
199-
addHeaderView(navHeaderBinding.root)
200-
201-
itemBackground = navigationItemBackground(context)
202-
203-
menu.findItem(R.id.navigation_map).isVisible = mapFeatureEnabled
204-
menu.findItem(R.id.navigation_codelabs).isVisible = codelabsFeatureEnabled
205-
menu.findItem(R.id.navigation_explore_ar).apply {
206-
// Handle launching new activities, otherwise assume the destination is handled
207-
// by the nav graph. We want to launch a new Activity for only the AR menu
208-
isVisible = exploreArFeatureEnabled
209-
setOnMenuItemClickListener {
210-
if (connectivityManager.activeNetworkInfo?.isConnected == true) {
211-
if (viewModel.arCoreAvailability.value?.isSupported == true) {
212-
analyticsHelper
213-
.logUiEvent(
214-
"Navigate to Explore I/O ARCore supported",
215-
AnalyticsActions.CLICK
216-
)
217-
openExploreAr()
218-
} else {
219-
analyticsHelper
220-
.logUiEvent(
221-
"Navigate to Explore I/O ARCore NOT supported",
222-
AnalyticsActions.CLICK
223-
)
224-
openArCoreNotSupported()
225-
}
226-
} else {
227-
openNoConnection()
228-
}
229-
closeDrawer()
230-
true
231-
}
232-
}
133+
// Either of two different navigation views might exist depending on the configuration.
134+
binding.bottomNavigation?.apply {
135+
configureNavMenu(menu)
233136
setupWithNavController(navController)
137+
setOnItemReselectedListener { } // prevent navigating to the same item
138+
}
139+
binding.navigationRail?.apply {
140+
configureNavMenu(menu)
141+
setupWithNavController(navController)
142+
setOnItemReselectedListener { } // prevent navigating to the same item
234143
}
235144

236145
if (savedInstanceState == null) {
237-
// default to showing Home
238-
val initialNavId = intent.getIntExtra(EXTRA_NAVIGATION_ID, R.id.navigation_feed)
239-
navigation.setCheckedItem(initialNavId) // doesn't trigger listener
240-
navigateTo(initialNavId)
146+
currentNavId = navController.graph.startDestination
147+
val requestedNavId = intent.getIntExtra(EXTRA_NAVIGATION_ID, currentNavId)
148+
navigateTo(requestedNavId)
241149
}
242150

243151
viewModel.theme.observe(this, Observer(::updateForTheme))
@@ -278,14 +186,44 @@ class MainActivity : AppCompatActivity(), NavigationHost {
278186
)
279187
}
280188

189+
private fun configureNavMenu(menu: Menu) {
190+
menu.findItem(R.id.navigation_map)?.isVisible = mapFeatureEnabled
191+
menu.findItem(R.id.navigation_codelabs)?.isVisible = codelabsFeatureEnabled
192+
menu.findItem(R.id.navigation_explore_ar)?.apply {
193+
// Handle launching new activities, otherwise assume the destination is handled
194+
// by the nav graph. We want to launch a new Activity for only the AR menu item.
195+
isVisible = exploreArFeatureEnabled
196+
setOnMenuItemClickListener {
197+
if (connectivityManager.activeNetworkInfo?.isConnected == true) {
198+
if (viewModel.arCoreAvailability.value?.isSupported == true) {
199+
analyticsHelper.logUiEvent(
200+
"Navigate to Explore I/O ARCore supported",
201+
AnalyticsActions.CLICK
202+
)
203+
openExploreAr()
204+
} else {
205+
analyticsHelper.logUiEvent(
206+
"Navigate to Explore I/O ARCore NOT supported",
207+
AnalyticsActions.CLICK
208+
)
209+
openArCoreNotSupported()
210+
}
211+
} else {
212+
openNoConnection()
213+
}
214+
true
215+
}
216+
}
217+
}
218+
281219
override fun registerToolbarWithNavigation(toolbar: Toolbar) {
282-
val appBarConfiguration = AppBarConfiguration(TOP_LEVEL_DESTINATIONS, drawer)
220+
val appBarConfiguration = AppBarConfiguration(TOP_LEVEL_DESTINATIONS)
283221
toolbar.setupWithNavController(navController, appBarConfiguration)
284222
}
285223

286224
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
287225
super.onRestoreInstanceState(savedInstanceState)
288-
currentNavId = navigation.checkedItem?.itemId ?: NAV_ID_NONE
226+
currentNavId = navController.currentDestination?.id ?: NAV_ID_NONE
289227
}
290228

291229
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -304,32 +242,15 @@ class MainActivity : AppCompatActivity(), NavigationHost {
304242
}
305243
}
306244

307-
override fun onBackPressed() {
308-
/**
309-
* If the drawer is open, the behavior changes based on the API level.
310-
* When gesture nav is enabled (Q+), we want back to exit when the drawer is open.
311-
* When button navigation is enabled (on Q or pre-Q) we want to close the drawer on back.
312-
*/
313-
if (drawer.isDrawerOpen(navigation) && drawer.shouldCloseDrawerFromBackPress()) {
314-
closeDrawer()
315-
} else {
316-
super.onBackPressed()
317-
}
318-
}
319-
320-
private fun closeDrawer() {
321-
drawer.closeDrawer(GravityCompat.START)
322-
}
323-
324245
override fun onUserInteraction() {
325246
super.onUserInteraction()
326247
getCurrentFragment()?.onUserInteraction()
327248
}
328249

329250
private fun getCurrentFragment(): MainNavigationFragment? {
330251
return navHostFragment
331-
?.childFragmentManager
332-
?.primaryNavigationFragment as? MainNavigationFragment
252+
.childFragmentManager
253+
.primaryNavigationFragment as? MainNavigationFragment
333254
}
334255

335256
private fun navigateTo(navId: Int) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.iosched.ui
18+
19+
import android.os.Bundle
20+
import androidx.core.view.forEach
21+
import androidx.navigation.NavController
22+
import androidx.navigation.NavDestination
23+
import androidx.navigation.ui.NavigationUI.onNavDestinationSelected
24+
import com.google.android.material.navigationrail.NavigationRailView
25+
import java.lang.ref.WeakReference
26+
27+
/**
28+
* Like BottomNavigationView.setupWithnavController, but for NavigationRailView.
29+
* TODO(jdkoren): A future release of material will combine the listeners (since both views have a
30+
* common superclass), which will make this unnecessary.
31+
*/
32+
fun NavigationRailView.setupWithNavController(navController: NavController) {
33+
setOnItemSelectedListener { item ->
34+
onNavDestinationSelected(item, navController)
35+
}
36+
val weakRef = WeakReference(this)
37+
val listener = object : NavController.OnDestinationChangedListener {
38+
override fun onDestinationChanged(
39+
controller: NavController,
40+
destination: NavDestination,
41+
arguments: Bundle?
42+
) {
43+
val view = weakRef.get()
44+
if (view == null) {
45+
navController.removeOnDestinationChangedListener(this)
46+
} else {
47+
view.menu.forEach { item ->
48+
if (matchNavDestination(destination, item.itemId)) {
49+
item.isChecked = true
50+
}
51+
}
52+
}
53+
}
54+
}
55+
navController.addOnDestinationChangedListener(listener)
56+
}
57+
58+
/**
59+
* Copy of package-private method in NavigationUI. TODO(jdkoren): remove when the above is removed.
60+
*/
61+
private fun matchNavDestination(destination: NavDestination, id: Int): Boolean {
62+
var currentDestination = destination
63+
while (currentDestination.id != id && currentDestination.parent != null) {
64+
currentDestination = currentDestination.parent as NavDestination
65+
}
66+
return currentDestination.id == id
67+
}

0 commit comments

Comments
 (0)