Expanding Fab menu

This commit is contained in:
Benoit Marty 2019-06-03 18:10:57 +02:00
parent 3475b169ea
commit fc5edcdf0f
6 changed files with 197 additions and 11 deletions

View file

@ -0,0 +1,19 @@
/*
* 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
const val ANIMATION_DURATION_SHORT = 200L

View file

@ -0,0 +1,37 @@
/*
* 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.animation.Animator
open class SimpleAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
// No op
}
override fun onAnimationEnd(animation: Animator?) {
// No op
}
override fun onAnimationCancel(animation: Animator?) {
// No op
}
override fun onAnimationStart(animation: Animator?) {
// No op
}
}

View file

@ -138,8 +138,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
} }
private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean {
if (fm.backStackEntryCount == 0) // if (fm.backStackEntryCount == 0)
return false // return false
val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed()
for (f in reverseOrder) { for (f in reverseOrder) {
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager)

View file

@ -16,6 +16,7 @@
package im.vector.riotredesign.features.home.room.list package im.vector.riotredesign.features.home.room.list
import android.animation.Animator
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -29,8 +30,11 @@ 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.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R 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.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.extensions.observeEvent 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.StateView
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -43,10 +47,12 @@ data class RoomListParams(
) : Parcelable ) : Parcelable
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed {
lateinit var fabButton: FloatingActionButton lateinit var fabButton: FloatingActionButton
private var isFabMenuOpened = false
enum class DisplayMode(@StringRes val titleRes: Int) { enum class DisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home), HOME(R.string.bottom_action_home),
PEOPLE(R.string.bottom_action_people), PEOPLE(R.string.bottom_action_people),
@ -75,6 +81,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
roomListViewModel.openRoomLiveData.observeEvent(this) { roomListViewModel.openRoomLiveData.observeEvent(this) {
navigator.openRoom(it) navigator.openRoom(it)
} }
isFabMenuOpened = false
} }
private fun setupCreateRoomButton() { private fun setupCreateRoomButton() {
@ -87,17 +95,30 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
fabButton.isVisible = true fabButton.isVisible = true
createRoomButton.setOnClickListener { createRoomButton.setOnClickListener {
// TODO Is it the expected action? toggleFabMenu()
navigator.openRoomDirectory()
} }
createChatRoomButton.setOnClickListener { createChatRoomButton.setOnClickListener {
// TODO Is it the expected action? createDirectChat()
navigator.openRoomDirectory()
} }
createGroupRoomButton.setOnClickListener { createGroupRoomButton.setOnClickListener {
navigator.openRoomDirectory() openRoomDirectory()
} }
createRoomItemChat.setOnClickListener {
toggleFabMenu()
createDirectChat()
}
createRoomItemGroup.setOnClickListener {
toggleFabMenu()
openRoomDirectory()
}
createRoomTouchGuard.setOnClickListener {
toggleFabMenu()
}
createRoomTouchGuard.isClickable = false
// Hide FAB when list is scrolling // Hide FAB when list is scrolling
epoxyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { epoxyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
@ -116,6 +137,63 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
}) })
} }
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() {
navigator.openRoomDirectory()
}
private fun createDirectChat() {
vectorBaseActivity.notImplemented("creating direct chat")
}
private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@ -199,6 +277,15 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
stateView.state = StateView.State.Error(message) stateView.state = StateView.State.Error(message)
} }
override fun onBackPressed(): Boolean {
if (isFabMenuOpened) {
toggleFabMenu()
return true
}
return super.onBackPressed()
}
// RoomSummaryController.Callback ************************************************************** // RoomSummaryController.Callback **************************************************************
override fun onRoomSelected(room: RoomSummary) { override fun onRoomSelected(room: RoomSummary) {

View file

@ -15,6 +15,45 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<!-- Create several FABs to manage different icon size. maxImageSize cannot be set programmatically --> <!-- Create several FABs to manage different icon size. maxImageSize cannot be set programmatically -->
<View
android:id="@+id/createRoomTouchGuard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:background="@android:color/background_dark" />
<!-- Sub menu item 2 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createRoomItemGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"
app:maxImageSize="32dp"
tools:fab_colorNormal="?attr/colorAccent"
tools:fab_colorPressed="?attr/colorAccent"
tools:translationY="-146dp"
tools:visibility="visible" />
<!-- Sub menu item 1 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createRoomItemChat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_fab_add_chat"
android:visibility="gone"
app:maxImageSize="34dp"
tools:fab_colorNormal="?attr/colorAccent"
tools:fab_colorPressed="?attr/colorAccent"
tools:translationY="-76dp"
tools:visibility="visible" />
<!-- Menu -->
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createRoomButton" android:id="@+id/createRoomButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -36,7 +75,7 @@
android:src="@drawable/ic_fab_add_chat" android:src="@drawable/ic_fab_add_chat"
android:visibility="gone" android:visibility="gone"
app:maxImageSize="34dp" app:maxImageSize="34dp"
tools:layout_margin="66dp" tools:layout_margin="76dp"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
@ -45,11 +84,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="16dp" android:layout_margin="16dp"
android:scaleType="center"
android:src="@drawable/ic_fab_add_room" android:src="@drawable/ic_fab_add_room"
android:visibility="gone" android:visibility="gone"
app:maxImageSize="32dp" app:maxImageSize="32dp"
tools:layout_margin="116dp" tools:layout_margin="136dp"
tools:visibility="visible" /> tools:visibility="visible" />
</im.vector.riotredesign.core.platform.StateView> </im.vector.riotredesign.core.platform.StateView>

View file

@ -26,4 +26,8 @@
<dimen name="pill_avatar_size">16dp</dimen> <dimen name="pill_avatar_size">16dp</dimen>
<dimen name="pill_min_height">20dp</dimen> <dimen name="pill_min_height">20dp</dimen>
<dimen name="pill_text_padding">4dp</dimen> <dimen name="pill_text_padding">4dp</dimen>
<dimen name="fab_menu_offset_1">76dp</dimen>
<dimen name="fab_menu_offset_2">146dp</dimen>
</resources> </resources>