diff --git a/vector/src/main/java/im/vector/riotredesign/core/animations/Constants.kt b/vector/src/main/java/im/vector/riotredesign/core/animations/Constants.kt new file mode 100644 index 00000000..4d9c59aa --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/animations/Constants.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleAnimatorListener.kt b/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleAnimatorListener.kt new file mode 100644 index 00000000..f1aad8f1 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleAnimatorListener.kt @@ -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 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 7b64bb39..538666e5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -138,8 +138,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { } private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { - if (fm.backStackEntryCount == 0) - return false + // if (fm.backStackEntryCount == 0) + // return false + val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() for (f in reverseOrder) { val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 7e13e665..19b031cf 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -16,6 +16,7 @@ package im.vector.riotredesign.features.home.room.list +import android.animation.Animator import android.os.Bundle import android.os.Parcelable 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.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 kotlinx.android.parcel.Parcelize @@ -43,10 +47,12 @@ data class RoomListParams( ) : Parcelable -class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { +class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed { lateinit var fabButton: FloatingActionButton + private var isFabMenuOpened = false + enum class DisplayMode(@StringRes val titleRes: Int) { HOME(R.string.bottom_action_home), PEOPLE(R.string.bottom_action_people), @@ -75,6 +81,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { roomListViewModel.openRoomLiveData.observeEvent(this) { navigator.openRoom(it) } + + isFabMenuOpened = false } private fun setupCreateRoomButton() { @@ -87,17 +95,30 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { fabButton.isVisible = true createRoomButton.setOnClickListener { - // TODO Is it the expected action? - navigator.openRoomDirectory() + toggleFabMenu() } createChatRoomButton.setOnClickListener { - // TODO Is it the expected action? - navigator.openRoomDirectory() + createDirectChat() } 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 epoxyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { 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() { val layoutManager = LinearLayoutManager(context) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() @@ -199,6 +277,15 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { stateView.state = StateView.State.Error(message) } + override fun onBackPressed(): Boolean { + if (isFabMenuOpened) { + toggleFabMenu() + return true + } + + return super.onBackPressed() + } + // RoomSummaryController.Callback ************************************************************** override fun onRoomSelected(room: RoomSummary) { diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index b94aceab..c785b7ad 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -15,6 +15,45 @@ android:layout_height="match_parent" /> + + + + + + + + + + diff --git a/vector/src/main/res/values/dimens.xml b/vector/src/main/res/values/dimens.xml index 90443bc5..8b19873d 100644 --- a/vector/src/main/res/values/dimens.xml +++ b/vector/src/main/res/values/dimens.xml @@ -26,4 +26,8 @@ 16dp 20dp 4dp + + + 76dp + 146dp \ No newline at end of file