forked from GitHub-Mirror/riotX-android
FAB Menu
FAB Menu WIP FAB Menu WIP FAB Menu WIP
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user