diff --git a/Reply/app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt b/Reply/app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt
index 09090b3..63eb824 100644
--- a/Reply/app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt
+++ b/Reply/app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt
@@ -45,8 +45,8 @@ class MainActivity : AppCompatActivity(),
NavController.OnDestinationChangedListener {
private val binding: ActivityMainBinding by contentView(R.layout.activity_main)
- private val bottomNavDrawer: BottomNavDrawerFragment by lazy(NONE) {
- supportFragmentManager.findFragmentById(R.id.bottom_nav_drawer) as BottomNavDrawerFragment
+ private val bottomNavDrawer: BottomNavDrawerFragment? by lazy(NONE) {
+ supportFragmentManager.findFragmentById(R.id.bottom_nav_drawer) as BottomNavDrawerFragment?
}
// Keep track of the current Email being viewed, if any, in order to pass the correct email id
@@ -68,43 +68,49 @@ class MainActivity : AppCompatActivity(),
}
// Set a custom animation for showing and hiding the FAB
- binding.fab.apply {
+ binding.fab?.let { fab ->
+ fab.apply {
setShowMotionSpecResource(R.animator.fab_show)
setHideMotionSpecResource(R.animator.fab_hide)
setOnClickListener {
findNavController(R.id.nav_host_fragment)
- .navigate(ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId))
+ .navigate(ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId))
+ }
}
}
- bottomNavDrawer.apply {
- addOnSlideAction(HalfClockwiseRotateSlideAction(binding.bottomAppBarChevron))
- addOnSlideAction(AlphaSlideAction(binding.bottomAppBarTitle, true))
- addOnStateChangedAction(ShowHideFabStateAction(binding.fab))
- addOnStateChangedAction(ChangeSettingsMenuStateAction { showSettings ->
- // Toggle between the current destination's BAB menu and the menu which should
- // be displayed when the BottomNavigationDrawer is open.
- binding.bottomAppBar.replaceMenu(if (showSettings) {
- R.menu.bottom_app_bar_settings_menu
- } else {
- getBottomAppBarMenuForDestination()
- })
- })
-
- addOnSandwichSlideAction(HalfCounterClockwiseRotateSlideAction(binding.bottomAppBarChevron))
+ bottomNavDrawer?.let { bottomNavBar ->
+ bottomNavBar.apply {
+ addOnSlideAction(HalfClockwiseRotateSlideAction(binding.bottomAppBarChevron!!))
+ addOnSlideAction(AlphaSlideAction(binding.bottomAppBarTitle!!, true))
+ addOnStateChangedAction(ShowHideFabStateAction(binding.fab!!))
+ addOnStateChangedAction(ChangeSettingsMenuStateAction { showSettings ->
+ // Toggle between the current destination's BAB menu and the menu which should
+ // be displayed when the BottomNavigationDrawer is open.
+ binding.bottomAppBar?.replaceMenu(if (showSettings) {
+ R.menu.bottom_app_bar_settings_menu
+ } else {
+ getBottomAppBarMenuForDestination()
+ })
+ })
+
+ addOnSandwichSlideAction(HalfCounterClockwiseRotateSlideAction(binding.bottomAppBarChevron!!))
+ }
}
// Set up the BottomAppBar menu
- binding.bottomAppBar.apply {
- setNavigationOnClickListener {
- bottomNavDrawer.toggle()
+ binding.bottomAppBar?.let { bottomAppBar ->
+ bottomAppBar.apply {
+ setNavigationOnClickListener {
+ bottomNavDrawer?.toggle()
+ }
+ setOnMenuItemClickListener(this@MainActivity)
}
- setOnMenuItemClickListener(this@MainActivity)
}
// Set up the BottomNavigationDrawer's open/close affordance
- binding.bottomAppBarContentContainer.setOnClickListener {
- bottomNavDrawer.toggle()
+ binding.bottomAppBarContentContainer?.setOnClickListener {
+ bottomNavDrawer?.toggle()
}
}
@@ -154,42 +160,42 @@ class MainActivity : AppCompatActivity(),
private fun setBottomAppBarForHome(@MenuRes menuRes: Int) {
binding.run {
- fab.setImageState(intArrayOf(-android.R.attr.state_activated), true)
- bottomAppBar.visibility = View.VISIBLE
- bottomAppBar.replaceMenu(menuRes)
- fab.contentDescription = getString(R.string.fab_compose_email_content_description)
- bottomAppBarTitle.visibility = View.VISIBLE
- bottomAppBar.performShow()
- fab.show()
+ fab?.setImageState(intArrayOf(-android.R.attr.state_activated), true)
+ bottomAppBar?.visibility = View.VISIBLE
+ bottomAppBar?.replaceMenu(menuRes)
+ fab?.contentDescription = getString(R.string.fab_compose_email_content_description)
+ bottomAppBarTitle?.visibility = View.VISIBLE
+ bottomAppBar?.performShow()
+ fab?.show()
}
}
private fun setBottomAppBarForEmail(@MenuRes menuRes: Int) {
binding.run {
- fab.setImageState(intArrayOf(android.R.attr.state_activated), true)
- bottomAppBar.visibility = View.VISIBLE
- bottomAppBar.replaceMenu(menuRes)
- fab.contentDescription = getString(R.string.fab_reply_email_content_description)
- bottomAppBarTitle.visibility = View.INVISIBLE
- bottomAppBar.performShow()
- fab.show()
+ fab?.setImageState(intArrayOf(android.R.attr.state_activated), true)
+ bottomAppBar?.visibility = View.VISIBLE
+ bottomAppBar?.replaceMenu(menuRes)
+ fab?.contentDescription = getString(R.string.fab_reply_email_content_description)
+ bottomAppBarTitle?.visibility = View.INVISIBLE
+ bottomAppBar?.performShow()
+ fab?.show()
}
}
private fun setBottomAppBarForCompose() {
binding.run {
- bottomAppBar.performHide()
- fab.hide()
+ bottomAppBar?.performHide()
+ fab?.hide()
// Hide the BottomAppBar to avoid it showing above the keyboard
// when composing a new email.
- bottomAppBar.visibility = View.GONE
+ bottomAppBar?.visibility = View.GONE
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_settings -> {
- bottomNavDrawer.close()
+ bottomNavDrawer?.close()
showDarkThemeMenu()
}
}
diff --git a/Reply/app/src/main/java/com/materialstudies/reply/ui/nav/RailNavigationFragment.kt b/Reply/app/src/main/java/com/materialstudies/reply/ui/nav/RailNavigationFragment.kt
new file mode 100644
index 0000000..5e9b5ea
--- /dev/null
+++ b/Reply/app/src/main/java/com/materialstudies/reply/ui/nav/RailNavigationFragment.kt
@@ -0,0 +1,137 @@
+package com.materialstudies.reply.ui.nav
+
+import android.animation.ValueAnimator
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.observe
+import com.materialstudies.reply.R
+import com.materialstudies.reply.databinding.FragmentRailNavigationBinding
+import com.materialstudies.reply.util.FastOutUltraSlowIn
+import com.materialstudies.reply.util.lerp
+import kotlin.math.abs
+
+class RailNavigationFragment : Fragment(), NavigationAdapter.NavigationAdapterListener {
+
+ /**
+ * Enumeration of states in which the account picker can be in.
+ */
+ enum class RailState {
+
+ /**
+ * The account picker is not visible. The navigation drawer is in its default state.
+ */
+ CLOSED,
+
+ /**
+ * the account picker is visible and open.
+ */
+ OPEN,
+
+ /**
+ * The account picker sandwiching animation is running. The account picker is neither open
+ * nor closed.
+ */
+ SETTLING
+ }
+
+ private lateinit var binding: FragmentRailNavigationBinding
+
+ private var railState: RailState = RailState.OPEN
+ private var railAnim: ValueAnimator? = null
+ private val railInterp = FastOutUltraSlowIn()
+ private var railProgress: Float = 1F
+ set(value) {
+ if (field != value) {
+ onRailProgressChanged(value)
+ val newState = when (value) {
+ 1F -> RailState.OPEN
+ 0F -> RailState.CLOSED
+ else -> RailState.SETTLING
+ }
+ if (railState != newState) onRailStateChanged(newState)
+ railState = newState
+ field = value
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentRailNavigationBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.run {
+ val adapter = NavigationAdapter(this@RailNavigationFragment)
+ navRecyclerView.adapter = adapter
+ NavigationModel.navigationList.observe(this@RailNavigationFragment) {
+ adapter.submitList(it)
+ }
+ NavigationModel.setNavigationMenuItemChecked(0)
+
+ if (!resources.getBoolean(R.bool.drawerLockedOpen)) {
+ logoImageView.setOnClickListener { toggleRail() }
+ }
+ }
+ }
+
+ override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
+ // TODO
+ }
+
+ override fun onNavEmailFolderClicked(folder: NavigationModelItem.NavEmailFolder) {
+ // TODO
+ }
+
+ private fun toggleRail() {
+ val initialProgress = railProgress
+ val newProgress = when (railState) {
+ RailState.CLOSED -> 1F
+ RailState.OPEN -> 0F
+ RailState.SETTLING -> return
+ }
+ railAnim?.cancel()
+ railAnim = ValueAnimator.ofFloat(initialProgress, newProgress).apply {
+ addUpdateListener { railProgress = animatedValue as Float }
+ interpolator = railInterp
+ duration = (abs(newProgress - initialProgress) * 250F).toLong()
+ }
+ railAnim?.start()
+ }
+
+ private fun onRailProgressChanged(progress: Float) {
+ // TODO
+ val railWidth = lerp(
+ resources.getDimension(R.dimen.min_rail_nav_width),
+ resources.getDimension(R.dimen.max_rail_nav_width),
+ 0F,
+ 1F,
+ progress
+ )
+
+ binding.run {
+ val params = railContainer.layoutParams
+ params.width = railWidth.toInt()
+ railContainer.layoutParams = params
+
+ settingsIcon.alpha = progress
+ logoTitleTextView.alpha = progress
+ }
+ }
+
+ private fun onRailStateChanged(state: RailState) {
+ // TODO: Look into animating the height of the fab manually instead of shrinking.
+// when (state) {
+// RailState.CLOSED -> {
+// binding.composeFab.shrink()
+// }
+// }
+ }
+}
\ No newline at end of file
diff --git a/Reply/app/src/main/java/com/materialstudies/reply/util/FastOutUltraSlowIn.kt b/Reply/app/src/main/java/com/materialstudies/reply/util/FastOutUltraSlowIn.kt
new file mode 100644
index 0000000..d5dfcd6
--- /dev/null
+++ b/Reply/app/src/main/java/com/materialstudies/reply/util/FastOutUltraSlowIn.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.materialstudies.reply.util
+
+import android.view.animation.Interpolator
+import androidx.core.view.animation.PathInterpolatorCompat
+
+/**
+ * A custom Interpolator that dramatically slows as an animation end, avoiding sudden motion
+ * stops for large moving components (ie. shared element cards).
+ */
+class FastOutUltraSlowIn : Interpolator {
+
+ private val pathInterpolator = PathInterpolatorCompat.create(
+ 0.185F,
+ 0.770F,
+ 0.135F,
+ 0.975F
+ )
+
+ override fun getInterpolation(fraction: Float): Float {
+ return pathInterpolator.getInterpolation(fraction)
+ }
+}
\ No newline at end of file
diff --git a/Reply/app/src/main/res/layout-w1024dp/activity_main.xml b/Reply/app/src/main/res/layout-w1024dp/activity_main.xml
new file mode 100644
index 0000000..58848f3
--- /dev/null
+++ b/Reply/app/src/main/res/layout-w1024dp/activity_main.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Reply/app/src/main/res/layout/fragment_rail_navigation.xml b/Reply/app/src/main/res/layout/fragment_rail_navigation.xml
new file mode 100644
index 0000000..ad64f50
--- /dev/null
+++ b/Reply/app/src/main/res/layout/fragment_rail_navigation.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Reply/app/src/main/res/values-w1024dp/bool.xml b/Reply/app/src/main/res/values-w1024dp/bool.xml
new file mode 100644
index 0000000..d9c8cd7
--- /dev/null
+++ b/Reply/app/src/main/res/values-w1024dp/bool.xml
@@ -0,0 +1,4 @@
+
+
+ true
+
\ No newline at end of file
diff --git a/Reply/app/src/main/res/values/bool.xml b/Reply/app/src/main/res/values/bool.xml
new file mode 100644
index 0000000..49c6343
--- /dev/null
+++ b/Reply/app/src/main/res/values/bool.xml
@@ -0,0 +1,4 @@
+
+
+ false
+
\ No newline at end of file
diff --git a/Reply/app/src/main/res/values/dimens.xml b/Reply/app/src/main/res/values/dimens.xml
index 457450b..28ebda5 100644
--- a/Reply/app/src/main/res/values/dimens.xml
+++ b/Reply/app/src/main/res/values/dimens.xml
@@ -32,4 +32,8 @@
42dp
32dp
+
+
+ 300dp
+ 88dp