Skip to content
This repository was archived by the owner on Nov 21, 2024. It is now read-only.

Commit db4cd2d

Browse files
committed
Refactor BottomNavigationDrawer to fragment
- Moved all navigation into a single RecyclerView. - Nav RecyclerView can be fully expanded, removing the profile icon and translating the foreground sheet to meet top of screen. - Made status bar transparent Change-Id: I4ce358e0f34fb333f91de19a4d2909b7d3a2dbd8
1 parent 1be5a37 commit db4cd2d

40 files changed

+1130
-577
lines changed

app/src/main/java/com/materialstudies/reply/ui/nav/QuarterRotateSlideAction.kt renamed to app/src/main/java/com/materialstudies/reply/data/EmailFolder.kt

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.materialstudies.reply.ui.nav
17+
package com.materialstudies.reply.data
1818

19-
import android.view.View
20-
import com.materialstudies.reply.util.lerp
19+
import androidx.recyclerview.widget.DiffUtil
2120

22-
class QuarterRotateSlideAction(
23-
private val chevronView: View
24-
) : OnSlideAction {
21+
/**
22+
* Alias to represent a folder (a String title) into which emails can be placed.
23+
*/
24+
typealias EmailFolder = String
2525

26-
override fun onSlide(sheet: View, slideOffset: Float) {
27-
chevronView.rotation = lerp(
28-
0F,
29-
180F,
30-
slideOffset
31-
)
32-
}
33-
}
26+
object EmailFolderDiff : DiffUtil.ItemCallback<EmailFolder>() {
27+
override fun areItemsTheSame(oldItem: EmailFolder, newItem: EmailFolder) = oldItem == newItem
28+
override fun areContentsTheSame(oldItem: EmailFolder, newItem: EmailFolder) = oldItem == newItem
29+
}

app/src/main/java/com/materialstudies/reply/data/EmailStore.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,13 @@ class EmailStore {
154154
}
155155
}
156156

157+
fun getAllFolders() = listOf(
158+
"Receipts",
159+
"Pine Elementary",
160+
"Taxes",
161+
"Vacation",
162+
"Mortgage",
163+
"Grocery coupons"
164+
)
157165
}
158166

app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,21 @@ import androidx.navigation.findNavController
3131
import com.materialstudies.reply.R
3232
import com.materialstudies.reply.databinding.ActivityMainBinding
3333
import com.materialstudies.reply.ui.nav.AlphaSlideAction
34+
import com.materialstudies.reply.ui.nav.BottomNavDrawerFragment
3435
import com.materialstudies.reply.ui.nav.ChangeSettingsMenuStateAction
3536
import com.materialstudies.reply.ui.nav.QuarterRotateSlideAction
3637
import com.materialstudies.reply.ui.nav.ShowHideFabStateAction
3738
import com.materialstudies.reply.util.contentView
39+
import kotlin.LazyThreadSafetyMode.NONE
3840

3941
class MainActivity : AppCompatActivity(),
4042
Toolbar.OnMenuItemClickListener,
4143
NavController.OnDestinationChangedListener {
4244

4345
private val binding: ActivityMainBinding by contentView(R.layout.activity_main)
46+
private val bottomNavDrawer: BottomNavDrawerFragment by lazy(NONE) {
47+
supportFragmentManager.findFragmentById(R.id.bottom_nav_drawer) as BottomNavDrawerFragment
48+
}
4449

4550
override fun onCreate(savedInstanceState: Bundle?) {
4651
super.onCreate(savedInstanceState)
@@ -62,7 +67,7 @@ class MainActivity : AppCompatActivity(),
6267
setHideMotionSpecResource(R.animator.fab_hide)
6368
}
6469

65-
binding.bottomNavigationDrawer.apply {
70+
bottomNavDrawer.apply {
6671
addOnSlideAction(QuarterRotateSlideAction(binding.bottomAppBarChevron))
6772
addOnSlideAction(AlphaSlideAction(binding.bottomAppBarTitle, true))
6873
addOnStateChangedAction(ShowHideFabStateAction(binding.fab))
@@ -84,14 +89,14 @@ class MainActivity : AppCompatActivity(),
8489
R.drawable.ic_more_vert_on_branded
8590
)
8691
setNavigationOnClickListener {
87-
binding.bottomNavigationDrawer.toggle()
92+
bottomNavDrawer.toggle()
8893
}
8994
setOnMenuItemClickListener(this@MainActivity)
9095
}
9196

9297
// Set up the BottomNavigationDrawer's open/close affordance
9398
binding.bottomAppBarContentContainer.setOnClickListener {
94-
binding.bottomNavigationDrawer.toggle()
99+
bottomNavDrawer.toggle()
95100
}
96101
}
97102

@@ -145,11 +150,10 @@ class MainActivity : AppCompatActivity(),
145150
}
146151
}
147152

148-
149153
override fun onMenuItemClick(item: MenuItem?): Boolean {
150154
when (item?.itemId) {
151155
R.id.menu_theme -> {
152-
binding.bottomNavigationDrawer.close()
156+
bottomNavDrawer.close()
153157
showDarkThemeMenu()
154158
}
155159
}

app/src/main/java/com/materialstudies/reply/ui/home/EmailSwipeActionDrawable.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ class EmailSwipeActionDrawable(context: Context) : Drawable() {
5555

5656
private val icon = ContextCompat.getDrawable(context, R.drawable.ic_twotone_star)!!
5757
private val iconMargin = context.resources.getDimension(R.dimen.keyline_6)
58-
private val iconIntrinsicWidth = icon?.intrinsicWidth ?: 0
59-
private val iconIntrinsicHeight = icon?.intrinsicHeight ?: 0
58+
private val iconIntrinsicWidth = icon.intrinsicWidth
59+
private val iconIntrinsicHeight = icon.intrinsicHeight
6060

6161
@ColorInt private val iconTint = context.getColorFromAttr(R.attr.colorOnBackground)
6262
@ColorInt private val iconTintActive = context.getColorFromAttr(R.attr.colorOnSecondary)

app/src/main/java/com/materialstudies/reply/ui/nav/AlphaSlideAction.kt

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright 2019 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.materialstudies.reply.ui.nav
18+
19+
import android.content.res.ColorStateList
20+
import android.os.Bundle
21+
import android.view.LayoutInflater
22+
import android.view.View
23+
import android.view.ViewGroup
24+
import android.widget.FrameLayout
25+
import androidx.fragment.app.Fragment
26+
import androidx.lifecycle.Observer
27+
import com.google.android.material.bottomsheet.BottomSheetBehavior
28+
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
29+
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
30+
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
31+
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
32+
import com.google.android.material.bottomsheet.BottomSheetBehavior.from
33+
import com.google.android.material.shape.MaterialShapeDrawable
34+
import com.materialstudies.reply.App
35+
import com.materialstudies.reply.R
36+
import com.materialstudies.reply.data.EmailStore
37+
import com.materialstudies.reply.databinding.FragmentBottomNavDrawerBinding
38+
import com.materialstudies.reply.util.getColorFromAttr
39+
import kotlin.LazyThreadSafetyMode.NONE
40+
41+
/**
42+
* A [Fragment] which acts as a bottom navigation drawer.
43+
*/
44+
class BottomNavDrawerFragment : Fragment(), NavigationAdapter.NavigationAdapterListener {
45+
46+
private lateinit var emailStore: EmailStore
47+
private lateinit var navigationModel: NavigationModel
48+
49+
private lateinit var binding: FragmentBottomNavDrawerBinding
50+
51+
private val behavior: BottomSheetBehavior<FrameLayout> by lazy(NONE) {
52+
from(binding.backgroundContainer)
53+
}
54+
55+
private val bottomSheetCallback = BottomNavigationDrawerCallback()
56+
57+
private val backgroundShapeDrawable: MaterialShapeDrawable by lazy(NONE) {
58+
MaterialShapeDrawable(
59+
binding.backgroundContainer.context,
60+
null,
61+
R.attr.bottomSheetStyle,
62+
0
63+
).apply {
64+
fillColor = ColorStateList.valueOf(
65+
requireContext().getColorFromAttr(R.attr.colorBrandedVariantSurface)
66+
)
67+
elevation = resources.getDimension(R.dimen.plane_08)
68+
initializeElevationOverlay(requireContext())
69+
}
70+
}
71+
72+
private val foregroundShapeDrawable: MaterialShapeDrawable by lazy(NONE) {
73+
MaterialShapeDrawable(
74+
binding.foregroundContainer.context,
75+
null,
76+
R.attr.bottomSheetStyle,
77+
0
78+
).apply {
79+
fillColor = ColorStateList.valueOf(
80+
requireContext().getColorFromAttr(R.attr.colorBrandedSurface)
81+
)
82+
elevation = resources.getDimension(R.dimen.plane_16)
83+
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_NEVER
84+
initializeElevationOverlay(requireContext())
85+
shapeAppearanceModel.topEdge = SemiCircleEdgeCutoutTreatment(
86+
resources.getDimension(R.dimen.keyline_3),
87+
resources.getDimension(R.dimen.keyline_5),
88+
0F,
89+
resources.getDimension(R.dimen.navigation_drawer_profile_image_size)
90+
)
91+
}
92+
}
93+
94+
override fun onCreateView(
95+
inflater: LayoutInflater,
96+
container: ViewGroup?,
97+
savedInstanceState: Bundle?
98+
): View? {
99+
binding = FragmentBottomNavDrawerBinding.inflate(inflater, container, false)
100+
binding.foregroundContainer.setOnApplyWindowInsetsListener { view, windowInsets ->
101+
// Record the window's top inset so it can be applied when the bottom sheet is slide up
102+
// to meet the top edge of the screen.
103+
view.setTag(
104+
R.id.tag_system_window_inset_top,
105+
windowInsets.systemWindowInsetTop
106+
)
107+
windowInsets
108+
}
109+
return binding.root
110+
}
111+
112+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
113+
super.onViewCreated(view, savedInstanceState)
114+
emailStore = (requireActivity().application as App).emailStore
115+
navigationModel = NavigationModel(emailStore)
116+
binding.run {
117+
backgroundContainer.background = backgroundShapeDrawable
118+
foregroundContainer.background = foregroundShapeDrawable
119+
120+
scrimView.setOnClickListener { close() }
121+
122+
bottomSheetCallback.apply {
123+
// Scrim view transforms
124+
addOnSlideAction(AlphaSlideAction(scrimView))
125+
addOnStateChangedAction(VisibilityStateAction(scrimView))
126+
// Foreground transforms
127+
addOnSlideAction(ForegroundSheetTransformSlideAction(
128+
binding.foregroundContainer,
129+
foregroundShapeDrawable,
130+
binding.profileImageView
131+
))
132+
// Recycler transforms
133+
addOnStateChangedAction(ScrollToTopStateAction(navRecyclerView))
134+
}
135+
136+
behavior.setBottomSheetCallback(bottomSheetCallback)
137+
behavior.state = STATE_HIDDEN
138+
139+
val adapter = NavigationAdapter(this@BottomNavDrawerFragment)
140+
binding.navRecyclerView.adapter = adapter
141+
navigationModel.navigationList.observe(this@BottomNavDrawerFragment, Observer {
142+
adapter.submitList(it)
143+
})
144+
navigationModel.setNavigationMenuItemChecked(0)
145+
}
146+
}
147+
148+
fun toggle() {
149+
when (behavior.state) {
150+
STATE_HIDDEN -> open()
151+
STATE_HALF_EXPANDED, STATE_EXPANDED, STATE_COLLAPSED -> close()
152+
}
153+
}
154+
155+
fun open() {
156+
behavior.state = STATE_HALF_EXPANDED
157+
}
158+
159+
fun close() {
160+
behavior.state = STATE_HIDDEN
161+
}
162+
163+
fun addOnSlideAction(action: OnSlideAction) {
164+
bottomSheetCallback.addOnSlideAction(action)
165+
}
166+
167+
fun addOnStateChangedAction(action: OnStateChangedAction) {
168+
bottomSheetCallback.addOnStateChangedAction(action)
169+
}
170+
171+
override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
172+
if (navigationModel.setNavigationMenuItemChecked(item.id)) close()
173+
}
174+
175+
override fun onNavEmailFolderClicked(folder: NavigationModelItem.NavEmailFolder) {
176+
// Do nothing
177+
}
178+
}

0 commit comments

Comments
 (0)