diff --git a/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleTransitionListener.kt b/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleTransitionListener.kt new file mode 100644 index 00000000..0e644990 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/animations/SimpleTransitionListener.kt @@ -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 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/animations/VectorFullTransitionSet.kt b/vector/src/main/java/im/vector/riotredesign/core/animations/VectorFullTransitionSet.kt new file mode 100644 index 00000000..666e6ced --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/animations/VectorFullTransitionSet.kt @@ -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)) + } + +} 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 19b031cf..247ab070 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,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 } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/widget/FabMenuView.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/widget/FabMenuView.kt new file mode 100644 index 00000000..525efb06 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/widget/FabMenuView.kt @@ -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() + } + +} \ No newline at end of file diff --git a/vector/src/main/res/drawable/vector_label_background.xml b/vector/src/main/res/drawable/vector_label_background.xml new file mode 100644 index 00000000..6e0f1c85 --- /dev/null +++ b/vector/src/main/res/drawable/vector_label_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml index a2c79905..fd9d5d27 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml @@ -159,6 +159,5 @@ app:layout_constraintStart_toEndOf="@+id/composer_avatar_view" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem/random" /> - \ No newline at end of file diff --git a/vector/src/main/res/layout/constraint_set_fab_menu_close.xml b/vector/src/main/res/layout/constraint_set_fab_menu_close.xml new file mode 100644 index 00000000..23b1ede5 --- /dev/null +++ b/vector/src/main/res/layout/constraint_set_fab_menu_close.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/constraint_set_fab_menu_open.xml b/vector/src/main/res/layout/constraint_set_fab_menu_open.xml new file mode 100644 index 00000000..8aac13b6 --- /dev/null +++ b/vector/src/main/res/layout/constraint_set_fab_menu_open.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index c785b7ad..8701d79e 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -1,6 +1,4 @@ - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/merge_fab_menu_view.xml b/vector/src/main/res/layout/merge_fab_menu_view.xml new file mode 100644 index 00000000..18db4683 --- /dev/null +++ b/vector/src/main/res/layout/merge_fab_menu_view.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/dimens.xml b/vector/src/main/res/values/dimens.xml index 8b19873d..886cde2e 100644 --- a/vector/src/main/res/values/dimens.xml +++ b/vector/src/main/res/values/dimens.xml @@ -27,7 +27,4 @@ 20dp 4dp - - 76dp - 146dp \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index de308c3d..3554d619 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -37,4 +37,7 @@ "This room can't be previewed" "The preview of world-readable room is not supported yet in RiotX" + "Rooms" + "Direct Messages" + \ No newline at end of file diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml index b3b987c0..b3ce18f1 100644 --- a/vector/src/main/res/values/styles_riot.xml +++ b/vector/src/main/res/values/styles_riot.xml @@ -265,4 +265,17 @@ @id/messageMemberNameView @id/messageBottomInfo + + + \ No newline at end of file