mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Expanding Fab menu
This commit is contained in:
parent
3475b169ea
commit
fc5edcdf0f
6 changed files with 197 additions and 11 deletions
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue