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

[WIP] Adding left side navigation to Layouts above 1024dp #18

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Comment on lines +71 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I think this could be combined into a single binding.fab?.apply { (same applies to other changes below)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeaa me and @hunterstich were talking about this yesterday. This as of now is a get stuff moving and started (probably gonna push the nav stuff until some of the other parts are built) but there is some changes we have to make to make this cleaner and hopefully less gross around the optionals and all that as there's definitely some lifecycle things that can cause issues here.

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()
}
}

Expand Down Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
// }
// }
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
43 changes: 43 additions & 0 deletions Reply/app/src/main/res/layout-w1024dp/activity_main.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2019 Google Inc.

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

http://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.
-->
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >

<fragment
android:id="@+id/rail_navigation"
android:layout_width="@dimen/max_rail_nav_width"
android:layout_height="match_parent"
android:name="com.materialstudies.reply.ui.nav.RailNavigationFragment"
app:layout_constraintLeft_toLeftOf="parent" />

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph"
app:layout_constraintLeft_toRightOf="@id/rail_navigation"
app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>
Loading