FAB Menu WIP

FAB Menu WIP

FAB Menu WIP
This commit is contained in:
Benoit Marty
2019-06-05 18:59:41 +02:00
parent f9bfda059f
commit acedff4e89
13 changed files with 542 additions and 146 deletions

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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.
*/
package im.vector.riotredesign.core.animations
import androidx.transition.Transition
open class SimpleTransitionListener : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
// No op
}
override fun onTransitionResume(transition: Transition) {
// No op
}
override fun onTransitionPause(transition: Transition) {
// No op
}
override fun onTransitionCancel(transition: Transition) {
// No op
}
override fun onTransitionStart(transition: Transition) {
// No op
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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.
*/
package im.vector.riotredesign.core.animations
import android.content.Context
import android.util.AttributeSet
import androidx.transition.ChangeBounds
import androidx.transition.ChangeTransform
import androidx.transition.Fade
import androidx.transition.TransitionSet
class VectorFullTransitionSet : TransitionSet {
constructor() {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
private fun init() {
ordering = ORDERING_TOGETHER
addTransition(Fade(Fade.OUT))
.addTransition(ChangeBounds())
.addTransition(ChangeTransform())
.addTransition(Fade(Fade.IN))
}
}

View File

@ -16,7 +16,6 @@
package im.vector.riotredesign.features.home.room.list
import android.animation.Animator
import android.os.Bundle
import android.os.Parcelable
import androidx.annotation.StringRes
@ -25,18 +24,16 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.*
import com.google.android.material.floatingactionbutton.FloatingActionButton
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.animations.ANIMATION_DURATION_SHORT
import im.vector.riotredesign.core.animations.SimpleAnimatorListener
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.platform.OnBackPressed
import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.room.list.widget.FabMenuView
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject
@ -47,11 +44,7 @@ data class RoomListParams(
) : Parcelable
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed {
lateinit var fabButton: FloatingActionButton
private var isFabMenuOpened = false
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed, FabMenuView.Listener {
enum class DisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home),
@ -82,21 +75,16 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
navigator.openRoom(it)
}
isFabMenuOpened = false
createChatFabMenu.listener = this
}
private fun setupCreateRoomButton() {
fabButton = when (roomListParams.displayMode) {
DisplayMode.HOME -> createRoomButton
DisplayMode.PEOPLE -> createChatRoomButton
else -> createGroupRoomButton
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.isVisible = true
DisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
else -> createGroupRoomButton.isVisible = true
}
fabButton.isVisible = true
createRoomButton.setOnClickListener {
toggleFabMenu()
}
createChatRoomButton.setOnClickListener {
createDirectChat()
}
@ -104,93 +92,34 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
openRoomDirectory()
}
createRoomItemChat.setOnClickListener {
toggleFabMenu()
createDirectChat()
}
createRoomItemGroup.setOnClickListener {
toggleFabMenu()
openRoomDirectory()
}
createRoomTouchGuard.setOnClickListener {
toggleFabMenu()
}
createRoomTouchGuard.isClickable = false
// Hide FAB when list is scrolling
epoxyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
fabButton.removeCallbacks(showFabRunnable)
createChatFabMenu.removeCallbacks(showFabRunnable)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
fabButton.postDelayed(showFabRunnable, 1000)
createChatFabMenu.postDelayed(showFabRunnable, 1000)
}
RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> {
fabButton.hide()
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.hide()
DisplayMode.PEOPLE -> createChatRoomButton.hide()
else -> createGroupRoomButton.hide()
}
}
}
}
})
}
private fun toggleFabMenu() {
isFabMenuOpened = !isFabMenuOpened
if (isFabMenuOpened) {
createRoomItemChat.isVisible = true
createRoomItemGroup.isVisible = true
createRoomButton.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.rotation(135f)
createRoomItemChat.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.translationY(-resources.getDimension(R.dimen.fab_menu_offset_1))
createRoomItemGroup.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.translationY(-resources.getDimension(R.dimen.fab_menu_offset_2))
createRoomTouchGuard.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.alpha(0.6f)
.setListener(null)
createRoomTouchGuard.isClickable = true
} else {
createRoomButton.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.rotation(0f)
createRoomItemChat.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.translationY(0f)
createRoomItemGroup.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.translationY(0f)
createRoomTouchGuard.animate()
.setDuration(ANIMATION_DURATION_SHORT)
.alpha(0f)
.setListener(object : SimpleAnimatorListener() {
override fun onAnimationCancel(animation: Animator?) {
animation?.removeListener(this)
}
override fun onAnimationEnd(animation: Animator?) {
// Use isFabMenuOpened because it may have been open meanwhile
createRoomItemChat.isVisible = isFabMenuOpened
createRoomItemGroup.isVisible = isFabMenuOpened
}
})
createRoomTouchGuard.isClickable = false
}
}
private fun openRoomDirectory() {
override fun openRoomDirectory() {
navigator.openRoomDirectory()
}
private fun createDirectChat() {
override fun createDirectChat() {
vectorBaseActivity.notImplemented("creating direct chat")
}
@ -206,7 +135,13 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
}
private val showFabRunnable = Runnable {
fabButton.show()
if (isAdded) {
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.show()
DisplayMode.PEOPLE -> createChatRoomButton.show()
else -> createGroupRoomButton.show()
}
}
}
private fun renderState(state: RoomListViewState) {
@ -278,8 +213,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
}
override fun onBackPressed(): Boolean {
if (isFabMenuOpened) {
toggleFabMenu()
if (createChatFabMenu.onBackPressed()) {
return true
}

View File

@ -0,0 +1,160 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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.
*/
package im.vector.riotredesign.features.home.room.list.widget
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.ChangeTransform
import androidx.transition.Transition
import androidx.transition.TransitionManager
import im.vector.riotredesign.R
import im.vector.riotredesign.core.animations.ANIMATION_DURATION_SHORT
import im.vector.riotredesign.core.animations.SimpleTransitionListener
import im.vector.riotredesign.core.animations.VectorFullTransitionSet
import kotlinx.android.synthetic.main.merge_fab_menu_view.view.*
class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
var listener: Listener? = null
private var isFabMenuOpened = false
init {
inflate(context, R.layout.merge_fab_menu_view, this)
}
override fun onFinishInflate() {
super.onFinishInflate()
// Collapse
ConstraintSet().also {
it.clone(context, R.layout.constraint_set_fab_menu_close)
it.applyTo(this)
}
createRoomItemChat.isVisible = false
createRoomItemChatLabel.isVisible = false
createRoomItemGroup.isVisible = false
createRoomItemGroupLabel.isVisible = false
// Collapse end
createRoomButton.setOnClickListener {
toggleFabMenu()
}
listOf(createRoomItemChat, createRoomItemChatLabel)
.forEach {
it.setOnClickListener {
closeFabMenu()
listener?.createDirectChat()
}
}
listOf(createRoomItemGroup, createRoomItemGroupLabel)
.forEach {
it.setOnClickListener {
closeFabMenu()
listener?.openRoomDirectory()
}
}
createRoomTouchGuard.setOnClickListener {
closeFabMenu()
}
}
fun show() {
createRoomButton.show()
}
fun hide() {
createRoomButton.hide()
}
private fun openFabMenu() {
if (isFabMenuOpened) {
return
}
toggleFabMenu()
}
private fun closeFabMenu() {
if (!isFabMenuOpened) {
return
}
toggleFabMenu()
}
private fun toggleFabMenu() {
isFabMenuOpened = !isFabMenuOpened
TransitionManager.beginDelayedTransition(parent as? ViewGroup ?: this,
VectorFullTransitionSet().apply {
duration = ANIMATION_DURATION_SHORT
ChangeTransform()
addListener(object : SimpleTransitionListener() {
override fun onTransitionEnd(transition: Transition) {
// Hide the view after the transition for a better visual effect
createRoomItemChat.isVisible = isFabMenuOpened
createRoomItemChatLabel.isVisible = isFabMenuOpened
createRoomItemGroup.isVisible = isFabMenuOpened
createRoomItemGroupLabel.isVisible = isFabMenuOpened
}
})
})
if (isFabMenuOpened) {
// Animate manually the rotation for a better effect
createRoomButton.animate().setDuration(ANIMATION_DURATION_SHORT).rotation(135f)
ConstraintSet().also {
it.clone(context, R.layout.constraint_set_fab_menu_open)
it.applyTo(this)
}
} else {
createRoomButton.animate().setDuration(ANIMATION_DURATION_SHORT).rotation(0f)
ConstraintSet().also {
it.clone(context, R.layout.constraint_set_fab_menu_close)
it.applyTo(this)
}
}
}
fun onBackPressed(): Boolean {
if (isFabMenuOpened) {
closeFabMenu()
return true
}
return false
}
interface Listener {
fun createDirectChat()
fun openRoomDirectory()
}
}